1. Получил странную ошибку при генерации: Two declarations cause a collision in the ObjectFactory class.
2. Непонятно где ошибка, поскольку xsd без перевода строки
3. Скачал локально, отформатировал, переписал все импорты в xsd и wsdl
4. Проблемных мест было два, в первой xsd:
первое объявление
<xs:element name="PersonAddress" nillable="true" type="tns:PersonAddress"/>
второе объявление
<xs:complexType name="Person">
<xs:sequence>
<xs:element minOccurs="0" name="Address" nillable="true" type="tns:PersonAddress"/>
skip
во второй:
<xs:element name="BankFlags" nillable="true" type="tns:BankFlags"/>
<xs:complexType name="Bank">
<xs:sequence>
<xs:element minOccurs="0" name="Flags" nillable="true" type="tns:BankFlags"/>
skip
т.е ObjectFactory генерируется не совсем корректно для подобных случаев.
5. Решение нашлось тут: http://stackoverflow.com/questions/13422253/xjc-two-declarations-cause-a-collision-in-the-objectfactory-class
6. Создал фалы биндингов для этих случаев:
jaxb-binding-xsd4.xml
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="1.0">
<jaxb:bindings schemaLocation="xsd4.xsd">
<jaxb:bindings node="//xs:element[@name='PersonAddress']">
<jaxb:factoryMethod name="TypePersonAddress"/>
</jaxb:bindings>
</jaxb:bindings>
</jaxb:bindings>
и
jaxb-binding-xsd9.xml
<jaxb:bindings xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
version="1.0">
<jaxb:bindings schemaLocation="xsd9.xsd">
<jaxb:bindings node="//xs:element[@name='BankFlags']">
<jaxb:factoryMethod name="TypeBankFlags"/>
</jaxb:bindings>
</jaxb:bindings>
</jaxb:bindings>
7. Прописываем параметры в pom.xml
<plugin>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-codegen-plugin</artifactId>
<version>2.7.3</version>
<executions>
<execution>
<goals>
<goal>wsdl2java</goal>
</goals>
<configuration>
<sourceRoot>${basedir}/target/generated-sources</sourceRoot>
<wsdlOptions>
<wsdlOption>
<extraargs>
<extraarg>-verbose</extraarg> <extraarg>-b</extraarg>
<extraarg>${basedir}/src/main/resources/jaxb-binding-xsd4.xml</extraarg>
<extraarg>-b</extraarg>
<extraarg>${basedir}/src/main/resources/jaxb-binding-xsd9.xml</extraarg>
</extraargs>
<wsdl>
${basedir}/src/main/resources/service.wsdl
</wsdl>
<wsdlLocation>classpath:service.wsdl</wsdlLocation>
</wsdlOption>
</wsdlOptions>
</configuration>
</execution>
</executions>
</plugin>
8. Классы сгенерировались, ура!
четверг, 11 апреля 2013 г.
четверг, 7 марта 2013 г.
HTTPS в Java
В этой заметке расскажу о, на первый взгляд, простой вещи - https соединении.
HTTPS - это по сути обычный HTTP, но данные передаются в зашифрованном виде с помощью SSL/TLS.
Установка соединения происходит в два этапа, соединение всегда инициирует клиент:
После этого осуществляется собственно обмен данными, зашифрованными с помощью сессионного ключа.
С практической точки зрения клиенту необходимо только решить, доверяет ли он серверу или нет, и предоставить свой сертификат, если нужно.
В java существует специальное хранилище cacerts, в котором уже находятся многие удостоверяющие центры. Это так называемые root CA, которым безусловно доверяют.
При получении серверного сертификата (цепочки сертификатов) клиент проверяет, приводит ли цепочка сертификатов к известному ему CA, которому он доверяет, и если он не находит такую цепочку, выбрасывается эксепшн вида
Так что пути решения проблемы два:
$openssl s_client -connect ${REMHOST}:${REMPORT} и скопировать в файл все что между строк -----BEGIN CERTIFICATE----- и -----END CERTIFICATE----- (включая их)
Полученный файл сертификата нужно добавить к cacerts командой
$keytool -import -keystore cacerts -alias [myalias] -file server_cert.pem
SSLSocketFactory socketFactory = createSSLContext().getSocketFactory();
HttpsURLConnection connection = (HttpsURLConnection) (url).openConnection();
connection.setSSLSocketFactory(socketFactory);
private final SSLContext createSSLContext()
throws NoSuchAlgorithmException, KeyStoreException,
CertificateException, IOException,
UnrecoverableKeyException, KeyManagementException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream in = new FileInputStream("server.pem");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
try {
X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
trustStore.setCertificateEntry("server_alias", cacert);
} finally {
IOUtils.closeQuietly(in);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
О том, как предоставить клиентский сертификат, в следующий раз.
Полезные ссылки:
http://www.zytrax.com/tech/survival/ssl.html
HTTPS - это по сути обычный HTTP, но данные передаются в зашифрованном виде с помощью SSL/TLS.
Установка соединения происходит в два этапа, соединение всегда инициирует клиент:
- Handshake
- Data
После этого осуществляется собственно обмен данными, зашифрованными с помощью сессионного ключа.
С практической точки зрения клиенту необходимо только решить, доверяет ли он серверу или нет, и предоставить свой сертификат, если нужно.
В java существует специальное хранилище cacerts, в котором уже находятся многие удостоверяющие центры. Это так называемые root CA, которым безусловно доверяют.
При получении серверного сертификата (цепочки сертификатов) клиент проверяет, приводит ли цепочка сертификатов к известному ему CA, которому он доверяет, и если он не находит такую цепочку, выбрасывается эксепшн вида
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target.
Так что пути решения проблемы два:
- Добавить серверный сертификат в cacerts
$openssl s_client -connect ${REMHOST}:${REMPORT} и скопировать в файл все что между строк -----BEGIN CERTIFICATE----- и -----END CERTIFICATE----- (включая их)
Полученный файл сертификата нужно добавить к cacerts командой
$keytool -import -keystore cacerts -alias [myalias] -file server_cert.pem
- Установить trustManager при создании sslContext.
SSLSocketFactory socketFactory = createSSLContext().getSocketFactory();
HttpsURLConnection connection = (HttpsURLConnection) (url).openConnection();
connection.setSSLSocketFactory(socketFactory);
private final SSLContext createSSLContext()
throws NoSuchAlgorithmException, KeyStoreException,
CertificateException, IOException,
UnrecoverableKeyException, KeyManagementException {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
FileInputStream in = new FileInputStream("server.pem");
KeyStore trustStore = KeyStore.getInstance("JKS");
trustStore.load(null);
try {
X509Certificate cacert = (X509Certificate) cf.generateCertificate(in);
trustStore.setCertificateEntry("server_alias", cacert);
} finally {
IOUtils.closeQuietly(in);
}
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(trustStore);
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
О том, как предоставить клиентский сертификат, в следующий раз.
Полезные ссылки:
http://www.zytrax.com/tech/survival/ssl.html
вторник, 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 все еще можно использовать, правда в лог выведется предупреждение о том, что вы используете старую версию.
Заголовок файла 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, например
2. Определяем файлы, в которые должны быть на этапе сборки проставлены данные параметры. В терминах maven этот процесс носит название фильтрация ресурсов. В нужных файлах проставляем плэйсхолдеры, к примеру в файле config.properties:
4. Создадим фильтр, чтобы не хранить свойства в pom.xml. Для этого используем файл, созданный на шаге 1.
5. Последний штрих - фильтрация ресурсов. Для этого версия maven-resources-plugin должна быть не меньше 2.3
полезные ссылки: Пример фильтрации ресурсов в maven
После непродолжительного гугления родилась такая схема:
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}3. Теперь самая хитрая часть. Поскольку свойства maven проекта по сути синглетоны (как и у ant), то они сохраняют переданное при инициализации значение. Т.е если вызывать maven с параметрами командной строки -Djdbc.username=test, а в pom.xml определить <jdbc.username>xxx</jdbc.username> то параметр jdbc.username будет иметь значение test.
jdbc.username=${jdbc.username}
jdbc.password=${jdbc.password}
4. Создадим фильтр, чтобы не хранить свойства в pom.xml. Для этого используем файл, созданный на шаге 1.
<filters>Теперь по умолчанию наши свойства проекта будут грузиться из dev.config, но их можно будет переопределить через параметры командной строки.
<filter>src/main/resources/dev.properties</filter>
</filters>
5. Последний штрих - фильтрация ресурсов. Для этого версия maven-resources-plugin должна быть не меньше 2.3
<resources>6. Настраиваем Jenkins, подставляя нужные значения в поле MAVEN_OPTS (build->advanced->MAVEN_OPTS)
<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>
полезные ссылки: Пример фильтрации ресурсов в 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();
}
Итак, предположим, есть большой 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.
Плюсы, которые я вначале не посчитал за плюсы, а понял, что это плюсы, только после того, как воспользовался аналогичным сервисом от Яндекса:
Подумав немного, я решил уточнить результаты с помощью Яндекса. Ведь Яндекс находится в России, значит, теоретически, результаты должны быть лучше.
Итак, Yandex geocode API.
Плюсы:
Код для работы с сервисом Яндекса:
Использовать сервисы геолокации оказалось не просто, а очень просто. Для доступа к функциям не нужно генерировать никаких ключей, просто формируем запрос, дергаем URL сервиса по GET, конвертируем вернувшийся JSON в java объекты и сохраняем результат в базе.
Исходные данные примерно таковы: несколько тысяч адресов примерно следующего формата: Область, Улица, Строение или Улица, Строение и отдельно от них Город. Все адреса находятся в России, что несколько сужает область поиска. Адреса набирались вручную и давно, и могут содержать опечатки, описки и устаревшие сведения.
Итак, первым пробуем Google geocoding API.
Плюсы, которые я вначале не посчитал за плюсы, а понял, что это плюсы, только после того, как воспользовался аналогичным сервисом от Яндекса:
- Все просто и удобно. Можно ограничить область поиска, можно выбрать язык результатов поиска. Не избыточный формат возвращаемых данных.
- Правильный поиск с приемлемой точностью. Можно рассчитывать на то, что если нашелся только один адрес, это будет корректный адрес. Поиск происходит последовательно, т.е вначале ищется населенный пункт, потом улица, потом дом. Следовательно, если населенный пункт не найден, то дальше поиск не осуществляется. Если не найдена улица или дом, поиск вернет координаты населенного пункта или центра улицы соответственно, указав найденную точность в результатах.
- Не пытается исправить ошибки в адресе, если они есть. Другими словами, не пытается подсунуть "вероятно правильный" результат.
- Возвращает почтовый индекс, если адрес найден с точностью до дома.
- Не всегда достаточно полная база, особенно по маленьким городам, что приводит к низкой точности координат (часто до города).
- Формат возвращаемого результата не соответствует общепринятому в России, т.е. результат возвращается в формате "Советская ул., 10, Тюмень, Тюменская область, Россия, 625003". Кончено результат также возвращается разбитый на части по административным единицам, да и этот в принципе не сложно развернуть, но все же неудобно.
- Иногда возвращается адрес конкретного объекта, например: "Сальское Медицинское Училище, ГОУ, Кирова ул., 17, Сальск, Ростовская область, Россия, 347630" Мне в принципе не нужно знать адрес учреждения, которое находится по данному адресу.
- Ограничение на частоту выполнения запросов. Есть ограничение на количество запросов за день (2,5 тыс) и некое ограничение на частоту запросов, из-за чего пришлось получать адреса очень медленно и с задержками.
Подумав немного, я решил уточнить результаты с помощью Яндекса. Ведь Яндекс находится в России, значит, теоретически, результаты должны быть лучше.
Итак, 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 позволяет настраивать количество конкурентных потребителей, а также динамически увеличивать количество потребителей при увеличении нагрузки.
Необходимо подключить к проекту 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 позволяет настраивать количество конкурентных потребителей, а также динамически увеличивать количество потребителей при увеличении нагрузки.
Подписаться на:
Сообщения (Atom)
