вторник, 18 декабря 2012 г.

hibernate-configuration-3.0.dtd

Обнаружил вчера WTF в старом проекте:

Заголовок файла hibernate.cfg.xml

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "/usr/local/etc/hibernate-configuration-3.0.dtd">

При попытке выполнить локально естественно все падает с ошибкой с очень длинным стеком вызовов, но упирается все в:

Caused by: org.hibernate.HibernateException: Could not parse configuration: /hibernate.cfg.xml
    at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:1586)
~[hibernate-core-3.5.6-Final.jar:3.5.6-Final]
...
Caused by: org.dom4j.DocumentException: /usr/local/etc/hibernate-configuration-3.0.dtd (No such file or directory) Nested exception: /usr/local/etc/hibernate-configuration-3.0.dtd (No such file or directory)
    at org.dom4j.io.SAXReader.read(SAXReader.java:484) ~[dom4j-1.6.1.jar:1.6.1]
    at org.hibernate.cfg.Configuration.doConfigure(Configuration.java:1576) ~[hibernate-core-3.5.6-Final.jar:3.5.6-Final]

Собственно все правильно, с чего бы там лежать этому файлу? Меняю на

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

Компилирую, запускаю, работает. На всякий случай отключаю сеть, проверяю, работает.
Лезу в историю svn, вижу мучительные попытки заставить код работать. Первая версия содержала правильный заголовок. Далее идут попытки что-то исправить: переключиться на http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd, убрать ссылку на dtd вовсе, подставить относительный путь к файлу, и в завершении - захардкодить абсолютный путь к файлу. Что пытались исправить, непонятно, но проблема явно была где-то в другом месте.

Ссылка на исходники DTDEntityResolver версии 3.5
и javadoc

В hibernate 3.6 поменяли url с http://hibernate.sourceforge.net на http://www.hibernate.org, но старый url все еще можно использовать, правда в лог выведется предупреждение о том, что вы используете старую версию.

среда, 5 декабря 2012 г.

Maven и определяемые на этапе сборки параметры проекта

Недавно прикрутил для своего маленького maven проекта сборку через Jenkins. В целом процесс занял совсем немного времени, Jenkins очень удобная и простая вещь. Единственной проблемой оказались настроечные параметры (параметры соединения с базой данных, RMI хост-порт и т.п), которые отличаются для девелоперского и продакшн окружения.

После непродолжительного гугления родилась такая схема:

1. Создаем файл, в котором хранятся все возможные настроечные параметры, назовем его dev.properties, например
jdbc.url=jdbc:postgresql://localhost/test
jdbc.username=test_user
jdbc.password=test_password

2. Определяем файлы, в которые должны быть на этапе сборки проставлены данные параметры. В терминах maven этот процесс носит название фильтрация ресурсов. В нужных файлах проставляем плэйсхолдеры, к примеру в файле config.properties:
jdbc.url=${jdbc.url}
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
3. Теперь самая хитрая часть. Поскольку свойства maven проекта по сути синглетоны (как и у ant), то они сохраняют переданное при инициализации значение. Т.е если вызывать maven с параметрами командной строки -Djdbc.username=test, а в pom.xml определить <jdbc.username>xxx</jdbc.username> то параметр jdbc.username будет иметь значение test.
4. Создадим фильтр, чтобы не хранить свойства в pom.xml. Для этого используем файл, созданный на шаге 1.
<filters>
   <filter>src/main/resources/dev.properties</filter>
</filters>
Теперь по умолчанию наши свойства проекта будут грузиться из dev.config, но их можно будет переопределить через параметры командной строки.

5. Последний штрих - фильтрация ресурсов. Для этого версия maven-resources-plugin должна быть не меньше 2.3
<resources>
   <resource>
       <includes>
          <include>config.properties</include>
          <include>logback.xml</include>
       </includes>
       <directory>src/main/resources</directory>
       <filtering>true</filtering>
   </resource>
   <resource>
      <directory>src/main/resources</directory>
         <excludes>
           <exclude>dev.properties</exclude>
         </excludes>
       <filtering>false</filtering>
   </resource>
</resources>
6. Настраиваем Jenkins, подставляя нужные значения в поле MAVEN_OPTS (build->advanced->MAVEN_OPTS)
полезные ссылки: Пример фильтрации ресурсов в maven

четверг, 1 ноября 2012 г.

Использование Apache POI для разбора документов Word

     В данной статье описывается как скопировать часть текстового docx файла в другой docx файл, с сохранением используемых стилей. Задача кажется тривиальной, но на практике возникли некоторые трудности.
     Итак, предположим, есть большой docx файл, состоящий из логически разделенных частей, разделенных, скажем, разрывом страницы (Ctrl+Enter). Требуется создать на каждый такой кусок отдельный файл, с сохранением форматирования текста.
    Воспользуемся библиотекой Apache POI  для разбора файла. Для этого создадим проект и добавим в зависимости POI 3.8. Версия 3.7 не подходит по причине того, что в ней нет возможности перенести стили из одного документа в другой.
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.8</version>
        </dependency>

И собственно код для разбора и создания документов:

        XWPFDocument doc = new XWPFDocument(new FileInputStream("/home/username/test.docx"));
        List<XWPFParagraph> list = doc.getParagraphs();
        XWPFDocument tmp = new XWPFDocument();
        tmp.createStyles();
        String fileName = "test";
        boolean isNew = true;

        for (XWPFParagraph p : list) {
            XWPFStyles style = tmp.getStyles();
            if (p.getStyleID() != null && !style.styleExist(p.getStyleID())) {
                style.addStyle(doc.getStyles().getStyle(p.getStyle()));
            }

            // формируем имя файла из первых символов
            if (isNew || StringUtils.isBlank(fileName)) {
                fileName = p.getParagraphText().trim();
                if (fileName.length() > 100) {
                    fileName = StringUtils.left(fileName, 50);
                }
            }
            XWPFParagraph tmpParagraph = tmp.createParagraph();
            tmpParagraph.setStyle(p.getStyle());
            isNew = false;
            for (XWPFRun r : p.getRuns()) {
                XWPFRun tmpRun = tmpParagraph.createRun();
                tmpRun.setTextPosition(r.getTextPosition());

                // важно использовать именно метод toString() поскольку 
                // этот метод сохраняет возможные символы "\n", которые getText обрезает
                tmpRun.setText(r.toString());
                tmpRun.setBold(r.isBold());
                tmpRun.setFontFamily(r.getFontFamily());
                tmpRun.setFontSize(r.getFontSize());
                tmpRun.setItalic(r.isItalic());
                tmpRun.setStrike(r.isStrike());
                tmpRun.setSubscript(r.getSubscript());
                tmpRun.setUnderline(r.getUnderline());
                // метод
isPageBreak всегда возвращает false, 
                // независимо от того, содержится ли разрыв страницы в параграфе или нет
                // так что используем грязный хак
                p.setPageBreak(r.getCTR().toString().contains("<w:br w:type=\"page\"/>"));
            }
            if (p.isPageBreak()) {
                try {
                    isNew = true;
                    tmp.write(new FileOutputStream("/home/username/result/" + fileName + ".docx"));
                } catch (IOException e) {
                    e.printStackTrace();
                }
                tmp = new XWPFDocument();

                // требуется версия POI  >= 3.8 чтобы сделать это
                tmp.createStyles();
            }
        }
        try {

             // сохраним последний кусок в файл
            tmp.write(new FileOutputStream("/home/username/result/" + fileName + ".docx"));
        } catch (IOException e) {
            e.printStackTrace();
        }

Сравнение работы геосервисов Google и Yandex

    Не так давно мне пришлось заниматься проблемой определения географических координат (широта, долгота) множества точек, зная только их адрес, что предоставило хорошую возможность для изучения возможностей сервисов геолокации, предоставляемые Google и Yandex.
    Использовать сервисы геолокации оказалось не просто, а очень просто. Для доступа к функциям не нужно генерировать никаких ключей, просто формируем запрос, дергаем URL сервиса по GET, конвертируем вернувшийся JSON в java объекты и сохраняем результат в базе.
    Исходные данные примерно таковы: несколько тысяч адресов примерно следующего формата: Область, Улица, Строение или Улица, Строение и  отдельно от них Город. Все адреса находятся в России, что несколько сужает область поиска. Адреса набирались вручную и давно, и могут содержать опечатки, описки и устаревшие сведения.
    Итак, первым пробуем Google geocoding API.
Плюсы, которые я вначале не посчитал за плюсы, а понял, что это плюсы, только после того, как воспользовался аналогичным сервисом от Яндекса:
  • Все просто и удобно. Можно ограничить область поиска, можно выбрать язык результатов поиска. Не избыточный формат возвращаемых данных.
  • Правильный поиск с приемлемой точностью. Можно рассчитывать на то, что если нашелся только один адрес, это будет корректный адрес. Поиск происходит последовательно, т.е вначале ищется населенный пункт, потом улица, потом дом. Следовательно, если населенный пункт не найден, то дальше поиск не осуществляется. Если не найдена улица или дом, поиск вернет координаты населенного пункта или центра улицы соответственно, указав найденную точность в результатах.
  • Не пытается исправить ошибки в адресе, если они есть. Другими словами, не пытается подсунуть "вероятно правильный" результат.
  • Возвращает почтовый индекс, если адрес найден с точностью до дома.
Минусы:
  • Не всегда достаточно полная база, особенно по маленьким городам, что приводит к низкой точности координат (часто до города).
  •  Формат возвращаемого результата не соответствует общепринятому в России, т.е. результат возвращается в формате "Советская ул., 10, Тюмень, Тюменская область, Россия, 625003". Кончено результат также возвращается разбитый на части по административным единицам, да и этот в принципе не сложно развернуть, но все же неудобно.
  • Иногда возвращается адрес конкретного объекта, например: "Сальское Медицинское Училище, ГОУ, Кирова ул., 17, Сальск, Ростовская область, Россия, 347630" Мне в принципе не нужно знать адрес учреждения, которое находится по данному адресу.
  • Ограничение на частоту выполнения запросов. Есть ограничение на количество запросов за день (2,5 тыс) и некое ограничение на частоту запросов, из-за чего пришлось получать адреса очень медленно и с задержками.
     В результате порядка 10% адресов оказалось невозможно  распознать, в основном из-за того, что нашлось более одного адреса. Некоторые адреса были неправильно написаны, например Кольчугин вместо Кольчугино. Некоторые адреса реально не существовали, например "Улица Тестовая д 123" в городе Бобруйск. Но больше всего проблем возникало при написании адреса в виде "Большая Сухаревская пл., 16/18 стр.2". В этом случае Гугл часто возвращал более одного результата, находя к примеру в данном случае дом 16 и дом 2.
    Подумав немного, я решил уточнить результаты с помощью Яндекса. Ведь Яндекс находится в России, значит, теоретически, результаты должны быть лучше.
   Итак, Yandex geocode API.
Плюсы:
  • Находит адрес с точностью до дома даже в маленьких городах.
  • Иногда правильно исправляет опечатки.
  • Общепринятый формат вывода результата.
Минусы:
  • Неправильный, избыточный поиск. Назовем его "боязнь ничего не найти". Вернуть любой более-менее похожий результат. Иногда это срабатывает, например, уже упоминавшийся населенный пункт Кольчугин во Владимирской области был правильно исправлен на Кольчугино, но иногда приводит к тому, что возвращается просто неверный результат, совершенно в другом городе. Например, "Смоленская обл., Рудня, ул. Киреева, 66" превращается в "Россия, Смоленская область, Смоленск, улица Кирова".
  • При указании точного адреса может вернуться более одного результата. Чтобы не ходить далеко, возьмем пример, приведенный на сайте самого Яндекса. "ул. Тверская, дом 7". Данный запрос вернет 5 результатов. Помимо ожидаемой Тверской улицы, сервис вернет также 4 Тверские-Ямские улицы. При этом у всех (кроме одной) будет точность до дома. Какой логикой руководствовались создатели сервиса, мне непонятно. Убедиться своими глазами.
  • Избыточность в описании результатов. Может быть создатели сервиса от Яндекса считают, что это придает солидности, мне же кажется, что это просто добавляет ненужной работы и программисту, и сетевому оборудованию. Количество различных java объектов, необходимых для разбора ответа от Яндекса в 2 раза больше, чем для Гугла.
Как результат: сервис от Гугла предоставляет недостаточную точность результатов в небольших городах, а сервисом от Яндекса пользоваться можно только в том случае, если проводится ручная проверка результатов.

Код для работы с сервисом Яндекса:

Использование Spring для работы со встроенным HornetQ сервером

       В предыдущем посте рассматривался пример внедрения HornetQ сервера в приложение. Попробуем немного усовершенствовать его, переложив часть работы на Spring.
       Необходимо подключить к проекту spring-jms.jar, после чего добавить немного кода в конфиг Spring
Это наш встроенный HornetQ сервер, он будет стартовать и останавливаться автоматически при поднятии контекста.

<bean class="org.hornetq.jms.server.embedded.EmbeddedJMS"
    destroy-method="stop"
     id="jmsServer"
     init-method="start"/>

Локатор ConnecitonFactory. Этот класс возвращает экземпляр ConnecitonFactory. Обычно ConnecitonFactory биндится на JNDI, но не в нашем случае, так что требуется специальный класс для того, чтобы получить ее.

 <bean class="example.jms.JmsConnecitonFactoryLocator"
                         depends-on="jmsServer"
                         factory-method="lookupConnectionFactory"
                         id="jmsConnectionFactory">
      <constructor-arg= name="server" ref="jmsServer" />
</bean>

Это стандартный JMSTemplate, с его помошью мы будет отсылать сообщения в очередь

<bean class="org.springframework.jms.core.JmsTemplate"
                         depends-on="jmsServer"
                         id="jmsQueueTemplate">
        <property name="connectionFactory">
            <ref bean="jmsConnectionFactory"/>
        </property>    
</bean>
JmsQueueLocator - класс-фабрика для получения экземпляра очереди. Необходим для того, чтобы организовать приемник сообщений.


<bean class="example.jms.JmsQueueLocator"
               depends-on="jmsServer"
               factory-method="lookupQueue"
               id="paymentQueue">
        <constructor-arg name="server" ref="jmsServer" />
        <constructor-arg name="queueName" value="queue/paymentQueue" />
</bean>
Собственно класс, отвественный за получение сообщений. Поддерживает несколько конкурентных получателей.Обратите внимание на ref="transactionPusher". Данный бин определяется в коде посредством аннотации @Component(value = "transactionPusher")


<bean id="jmsContainerPayment"  
            class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="jmsConnectionFactory"/>
        <property name="destination" ref="paymentQueue"/>
        <property name="messageListener" ref="transactionPusher" />
        <property name="concurrentConsumers" value="5"/>
</bean>


Теперь код:
Класс-фабрика для получения соединения с нашим встроенным JMS сервером

public class JmsConnecitonFactoryLocator {
    private static final Logger logger = LoggerFactory.getLogger(JmsConnecitonFactoryLocator.class);

    public static HornetQJMSConnectionFactory lookupConnectionFactory(EmbeddedJMS server){
        HornetQJMSConnectionFactory cf = (HornetQJMSConnectionFactory) server.lookup("ConnectionFactory");
        if(cf == null){
            logger.error("connection factory is null");
        }else{
            logger.info("connection factory is not null");
        }
        return cf;
    }
}


Класс-фабрика для получения экземпляра очереди

public class JmsQueueLocator {
    public static Queue lookupQueue(EmbeddedJMS server, String queueName){
        return (Queue) server.lookup(queueName);
    }
}


Класс - приемник сообщений


@Component(value = "transactionPusher")
public class TransactionPusher implements MessageListener {
    private static final Logger logger = LoggerFactory.getLogger(TransactionPusher.class);

    @Override
    public void onMessage(Message message) {

       try{
            TextMessage m = (TextMessage)message;
            m.acknowledge(); // говорим, что успешно приняли сообщение.
            // сделать что-то полезное
        } catch (JMSException e) {  

            logger.error("JMS exceptoin: ", e);
        }
    }
}


Пример использования JMSTemplate для отсылки сообщений:

public void addPaymentToQueue(final Transaction t) throws JMSException {
        jmsTemplate.send(paymentQueue, new MessageCreator() {
            @Override
            public Message createMessage(Session session) throws JMSException {
                TextMessage msg = session.createTextMessage();
                msg.setText(String.valueOf(t.getId()));
                return msg;
            }
        });
}


     Как можно заметить, Spring берет на себя значительную часть работы по организации рутинных операций, позволяя сосредоточиться на программировании бизнес логики приема и отправки сообщений. Класс DefaultMessageListenerContainer позволяет настраивать количество конкурентных потребителей, а также динамически увеличивать количество потребителей при увеличении нагрузки.





пятница, 17 августа 2012 г.

Отложенная доставка JMS сообщений в HornetQ

Иногда при обработке JMS сообщения возникла ситуация, когда нужно повторить операцию через какое-то время. Можно вместо того, чтобы висеть в Thread.sleep(), занимая ценные системные ресурсы, просто повторно послать сообщение обратно в очередь, указав время, когда его следует начать обрабатывать.
Стандарт JMS никак не регламентирует возможность доставки сообщений с определенной задержкой, тем не менее, поскольку это весьма полезная возможность, многие производители включают ее в реализацию.
Для HornetQ произвести задержку в доставке сообщения можно просто установив нужное значение в свойство _HQ_SCHED_DELIVERY (или Message.HDR_SCHEDULED_DELIVERY_TIME)
// задержка в 5 секунд относительно текущего момента.
message.setLongProperty("_HQ_SCHED_DELIVERY", System.currentTimeMillis() + 5000);
Источники:
HornetQ User Manual
Sending delayed JMS Messages

понедельник, 6 августа 2012 г.

Внедрение HornetQ JMS 2.2.5 сервера и клиента в приложение

В этой статье рассказывается, как внедрить в свое приложение замечательный HornetQ сервер. И это оказалось проще, чем я предполагал вначале.
Мне требовалась асинхронность обработки сообщений, потокобезопастность и возможность мониторинга, и желательно не писать много своего кода. Так что я остановился на HornetQ как уже знакомой мне реализации, которая удовлетворяем моим запросам.
Возьмем версию HornetQ 2.2.5 как последнюю стабильную на данный момент.
Зависимоcти для maven:
<dependency>
   <groupId>org.hornetq</groupId>
   <artifactId>hornetq-core</artifactId>
   <version>2.2.5.Final</version>
   <scope>compile</scope>
</dependency>
   <dependency>
   <groupId>org.hornetq</groupId>
   <artifactId>hornetq-jms</artifactId>
   <version>2.2.5.Final</version>
   <scope>compile</scope>
</dependency>
<dependency>
   <groupId>org.hornetq</groupId>
   <artifactId>hornetq-logging</artifactId>
   <version>2.2.5.Final</version>
   <scope>compile</scope>
</dependency>
<dependency>
   <groupId>org.jboss.netty</groupId>
   <artifactId>netty</artifactId>
   <version>3.2.3.Final</version>
</dependency>
<dependency>
   <groupId>org.jboss.spec.javax.jms</groupId>
   <artifactId>jboss-jms-api_1.1_spec</artifactId>
   <version>1.0.0.Final</version>
   <scope>compile</scope>
</dependency>
Конфигурация сервера и очередей.

В classpath должны находиться файлы hornetq-configuration.xml, содержащий настройки сервера, hornetq-jms.xml с настройками очередей, и hornetq-users.xml, содержащий настройки пользователей.
hornetq-configuration.xml

<configuration xmlns="urn:hornetq"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="urn:hornetq /schema/hornetq-configuration.xsd">

    <persistence-enabled>false</persistence-enabled>
    <!-- Connectors -->

    <connectors>
        <connector name="in-vm">
            <factory-class>org.hornetq.core.remoting.impl.invm.InVMConnectorFactory</factory-class>
        </connector>
    </connectors>

    <acceptors>
        <acceptor name="in-vm">
            <factory-class>org.hornetq.core.remoting.impl.invm.InVMAcceptorFactory</factory-class>
        </acceptor>
    </acceptors>

    <!-- Other config -->

    <security-settings>
        <!--security for example queue-->
        <security-setting match="#">
            <permission type="createDurableQueue" roles="guest"/>
            <permission type="deleteDurableQueue" roles="guest"/>
            <permission type="createNonDurableQueue" roles="guest"/>
            <permission type="deleteNonDurableQueue" roles="guest"/>
            <permission type="consume" roles="guest"/>
            <permission type="send" roles="guest"/>
        </security-setting>
    </security-settings>
</configuration>

hornetq-jms.xml 
<configuration xmlns="urn:hornetq"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">

    <connection-factory name="ConnectionFactory">
        <connectors>
            <connector-ref connector-name="in-vm"/>
        </connectors>
        <entries>
            <entry name="ConnectionFactory"/>
        </entries>
        <consumer-window-size>0</consumer-window-size>
        <retry-interval>1000</retry-interval>
        <retry-interval-multiplier>1.5</retry-interval-multiplier>
        <max-retry-interval>60000</max-retry-interval>
        <reconnect-attempts>1000</reconnect-attempts>
    </connection-factory>

    <!--the queue used by the example-->
    <queue name="paymentQueue">
        <entry name="queue/paymentQueue"/>
    </queue>
    <queue name="statusQueue">
        <entry name="queue/statusQueue"/>
    </queue>

</configuration>

hornetq-jms.xml
<configuration xmlns="urn:hornetq" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
               xsi:schemaLocation="urn:hornetq /schema/hornetq-users.xsd">
    <!-- the default user.  this is used where username is null-->
    <defaultuser name="guest" password="guest">
        <role name="guest"/>
    </defaultuser>
</configuration>


Старт встроенного сервера очень прост:
  
import org.hornetq.jms.server.embedded.EmbeddedJMS;

  EmbeddedJMS server = new EmbeddedJMS();
  server.start();
Далее создаем нужное число потребителей сообщений:
 
            QueueConnectionFactory cf = (QueueConnectionFactory) server.lookup("ConnectionFactory");
            Queue queue = (Queue) server.lookup("queue/paymentQueue");
            QueueConnection conn = cf.createQueueConnection("guest", "guest");
            for(int i = 0; i< Config.getConsumerCount(); i++){
                Session consumerSession = conn.createSession(false, Session.CLIENT_ACKNOWLEDGE);
                MessageConsumer consumer = consumerSession.createConsumer(queue);
                consumer.setMessageListener(new PaymentSender());
            }
Класс PaymentSender должен реализовывать интерфейс javax.jms.MessageListener
Запускаем обработчики:
 
conn.start();
Теперь нужно создать класс для отсылки сообщений.
 
    private static QueueConnectionFactory qconFactory = null;
    private static QueueConnection qcon = null;


    public static void init() throws JMSException {
        qconFactory = (QueueConnectionFactory) HornetqListener.server.lookup("ConnectionFactory");
        qcon = qconFactory.createQueueConnection("guest", "guest");

    }

    public static void destroy() throws JMSException {
        qcon.close();
    }

    public static void addPaymentToQueue(Transaction t) throws JMSException {

        QueueSession qsession = qcon.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
        Queue queue = (Queue) HornetqListener.server.lookup("queue/paymentQueue");
        QueueSender qsender = qsession.createSender(queue);
        qcon.start();
        TextMessage msg = qsession.createTextMessage();
        msg.setText(String.valueOf(t.getId()));
        logger.debug("message {} putted in queue 'queue/paymentQueue'", msg.getText());
        qsender.send(msg);
        qsession.close();
    }
HornetqListener.server - это статическая переменная, в которую мы сохранили созданный экземпляр объекта EmbeddedJMS
Мониторинг очередей реализуем через JMX
private static int max_queue = 200;

QueueView paymentqueue = jmxQuery("paymentQueue");
if(paymentqueue.getMessagesInQueue() > max_queue)
  max_queue = (int) paymentqueue.getMessagesInQueue();
String percentPayment = (new Double(paymentqueue.getMessagesInQueue()))/max_queue * 100d + "%";

private QueueView jmxQuery(String queueName) throws Exception {
        MBeanServer mbeanServer = java.lang.management.ManagementFactory.getPlatformMBeanServer();
        QueueView queueView = new QueueView();

        ObjectName name = new ObjectName("org.hornetq:module=JMS,type=Queue,name=\""+ queueName +"\"");

        queueView.setConsumersCount((Integer) mbeanServer.getAttribute(name, "ConsumerCount"));
        queueView.setMessagesDelivering((Integer) mbeanServer.getAttribute(name, "DeliveringCount"));
        queueView.setMessagesInQueue((Long) mbeanServer.getAttribute(name, "MessageCount"));
        queueView.setMessagesAdded((Long) mbeanServer.getAttribute(name, "MessagesAdded"));
        queueView.setName((String) mbeanServer.getAttribute(name, "Name"));

        name = new ObjectName("org.hornetq:module=Core,type=Server");

        queueView.setConnectionCount((Integer) mbeanServer.getAttribute(name, "ConnectionCount"));
        queueView.setThreadPoolMaxSize((Integer) mbeanServer.getAttribute(name, "ThreadPoolMaxSize"));
        queueView.setServerVersion((String) mbeanServer.getAttribute(name, "Version"));
        return queueView;
    }
Конфигурационные файлы взяты отсюда