barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

понедельник, 31 декабря 2012 г.

Java: получение вывода, выданного запущенным bat-ником

Уже как-то писал, как из Java вызывать bat-ники, теперь расскажу, как получить после этого то, что bat-ник вывел в "консоль".
Делается это так:
Process proc = Runtime.getRuntime().exec("cmd /c start my.bat");
proc.waitFor();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
IOUtils.copy(proc.getInputStream(), bos);
String batOutput = new String(bos.toByteArray(),"Cp866");
Здесь используется класс IOUtils из commons-io. Результат получаем в переменной  batOutput.

WSO2 ESB: как заставить прокси сервис использовать SOAP 1.1 в исходящих сообщениях

Проблема:

Есть прокси-сервис на WSO2 ESB 4.5.1, вызывающий конечный веб-сервис по http. Конечный сервис реализован на JAX-WS и использует версию SOAP 1.1. При попытке использовать SOAP 1.2 (используемый шиной по-умолчанию) валится с исключением:
com.sun.xml.ws.server.UnsupportedMediaException: Unsupported Content-Type: application/soap+xml; charset=UTF-8; action="xxx" Supported ones are: [text/xml]
Необходимо заставить прокси-сервис WSO2 использовать именно версию 1.1.

Решение:

Добавить атрибут format="soap11" в настройку конечной точки на шине:
<endpoint xmlns="http://ws.apache.org/ns/synapse" name="[EP_NAME]">
    <address format="soap11" statistics="disable" trace="disable" uri="[EP_URI]">
...

пятница, 28 декабря 2012 г.

maven-war-plugin: исключение файлов из WAR-ника

Исключение файлов из собираемого WAR-ника выполняется с помощью тега <packagingExcludes/> (ниже приведен пример исключения log4j.properties):

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.2</version>
<configuration>
<packagingExcludes>WEB-INF/classes/log4j.properties</packagingExcludes>
</configuration>
</plugin>

WSO2 ESB: быстрый trace содержимого сообщения в консоль

Конечно, для трэйса можно использовать log-медиатор, но если нужно всего лишь вывалить содержимое envelope`а в консоль, можно воспользоваться script-медиатором:
<script language="js"><![CDATA[
print(mc.getEnvelope());
]]></script>

com.sun.istack.internal.SAXException2: unable to marshal type as an element because it is not known to this context

Проблема:

Есть набор классов, сгенерированных wsimport`ом. Расположены эти классы в пакете "my.package". В проекте в этот же пакет добавлен ещё один класс "MyExtraClass", и есть желание выполнить его маршаллизацию:
MyExtraClass obj = new MyExtraClass();
...
JAXBContext jc = JAXBContext.newInstance("my.package");
Marshaller marshaller = jc.createMarshaller();
StringWriter xmlWriter = new StringWriter();
marshaller.marshal(obj, xmlWriter); 
Однако, последний вызов валится с исключением:
com.sun.istack.internal.SAXException2: unable to marshal type "my.package.MyExtraClass" as an element because it is not known to this context
Т.е., по-видимому, JAXB этого класса по какой-то причине не видит.

Причина:

wsimport, помимо классов, соответствующих XML-типам, сгенерировал в пакете my.package также класс ObjectFactory, определяющий, какие классы будут известны JAXB-контексту. Естественно, эта ObjectFactory о новом классе MyExtraClass ничего не знает, поэтому ничего о нем не знает и JAXB-контекст.

Решение:

Т.к. ObjectFactory менять не хочется, он ведь генерируется при компиляции автоматически, нужно добавить наш MyExtraClass в JAXB-контекст каким-то другим способом. Для этого подойдет такой вариант:
  1. Создать в пакете "my.package" файл "jaxb.index"
  2. Записать в этот файл название нашего класса, без указания пакета, т.е. просто:
    MyExtraClass
Все, теперь класс будет маршаллизироваться без проблем.

DQL: использование объединений типов и повторяющихся атрибутов в запросе

Если просто попробовать использовать в запросе несколько типов в объединении и выбирать при этом какой-нибудь повторяющийся атрибут (например, тот же "i_folder_id"), будет валится ошибка:
[DM_QUERY2_E_REPEAT_TYPE_JOIN]error: "Your query is selecting repeating attributes and joining types."
Выход - добавить в конец запроса "ENABLE (ROW_BASED)", тогда для каждого значения повторяющегося атрибута будет возвращена отдельная строка, и запрос выполнится без ошибок (но приведет, возможно, к дублированию одих и тех же объектов в разных строках).

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

JAX-WS: обертка ответов от разных веб-методов в одинаковые теги

Задача:

В JAX-WS-веб-сервисе (используется имплементация JAX-WS RI)  есть несколько методов, возвращающих объекты одного типа (пусть это будет класс OperResultClass):
@WebMethod(operationName = "operationA")
public OperResultClass operationA(){...}
@WebMethod(operationName = "operationB")
public OperResultClass operationB(){...}
@WebMethod(operationName = "operationC")
public OperResultClass operationC(){...}...
Необходимо, чтобы XML, получаемый на их выходе, выглядел так:
<MyResponseWrapperTag xmlns="http://soap.response">
<MyResponseTag>
<!-- содержимое объекта-ответа -->
</MyResponseTag>
</MyResponseWrapperTag>
Т.е. необходимо задать для всех методов:
  1. Имя наружного тега и его пространство имен
  2. Имя внутреннего тега и его пространство имен

Решение:

Второй пункт делается просто: каждый метод аннотируется следующим образом:
@WebResult(name="MyResponseTag", targetNamespace="http://soap.response")
После этого мы будем иметь правильный внутренний тег, а вот наружный будет генерироваться JAX-WS`ом автоматически, основываясь на имени метода (он будет состоять из имени метода + "Response").

Для задания имени наружного тега используется аннотация @ResponseWrapper. Но использовать её так:
  @ResponseWrapper(
          localName="MyResponseWrapperTag",
          targetNamespace="http://soap.response"
        )
не получится - будет валиться исключение:
Two classes have the same XML type name "{http://soap.response}MyResponseWrapperTag". Use @XmlType.name and @XmlType.namespace to assign different names to them.
Тут дело в том, что для оберток вокруг ответа JAX-WS генерирует свои классы, по одному на каждый метод. Т.о, для каждого веб-метода будет использоваться свой класс, в то время как из-за @ResponseWrapper всем им будет назначен одно и тот же имя XML-типа. 
Выход из ситуации - самостоятельно задавать имя класса-обертки, чтобы JAX-WS не генерировал своих классов. Делается это следующим образом:
@ResponseWrapper(          className="my.MyResponseWrapper",
          localName="MyResponseWrapperTag",
          targetNamespace="http://soap.response"
        )
Теперь нужно создать этот самый класс-wrapper "my.MyResponseWrapper". Он будет выглядеть примерно так:
@XmlType(name="MyResponseWrapperType",namespace="http://soap.response")
@XmlAccessorType(XmlAccessType.FIELD)
public class MyResponseWrapper{
  @XmlElement(name="MyResponseTag",namespace="http://soap.response")
  public OperResultClass MyResponseTag;
}
Здесь важно, чтобы параметры аннотации @XmlElement совпадали с таковыми в аннотации @WebResult метода. Namespace можно не указывать, если он задан на уровне пакета в package-info.java:
@XmlSchema(namespace = "http://soap.response", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED)
package my;
import javax.xml.bind.annotation.XmlSchema;



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

javax.xml.ws.spi.FactoryFinder$ConfigurationError

Проблема:

При попытке в приложении создать объект клиента к веб-сервису, построенного на JAX-WS, валится исключение:
javax.xml.ws.spi.FactoryFinder$ConfigurationError: Provider org.apache.cxf.jaxws.spi.ProviderImpl not found
Причина:

Где-то в CLASSPATH переопределен JAX-WS провайдер на Apache Cxf, при этом самого Cxf в CLASSPATH нет (ну или, если используется OSGI, он может быть просто не импортирован).

Решение:

Возможных решения, в общем-то, 3:
  • Найти и положить в CLASSPATH / импортировать бандл Cxf
  • Найти, где в CLASSPATH переопределен JAX-WS-провайдер и удалить переопределение. Переопределяется он файлом META-INF/services/javax.xml.ws.spi.Provider, в который записано имя класса провайдера.
  • Переопределить провайдер обратно в своем приложении так, чтобы Ваш файл лежал в CLASSPATH ближе, чем вражеский. В случае с собираемым Maven`ом приложением нужно создать файл src/main/resources/META-INF/services/javax.xml.ws.spi.Provider, и записать в него (если Вы хотите вернутся к провайдеру по-умочанию, который идет с JDK): 
    com.sun.xml.internal.ws.spi.ProviderImpl


MS Office 2010: неработающие Ctrl-комбинации

Неожиданно, как всегда в самый неподходящий момент, перестали работать все Crtl-комбинации в MS Office. Не знаю, как вернуть все обратно одним нажатием, но вручную переназначить все комбинации клавиш обратно можно так:
  1. File->Options->Customize Ribbon -> Keyboard shortcuts (внизу) -> Customize 
  2. Выбираем All Commands в левом списке, а затем в правом, последовательно, FileSave, EditCut, EditCopy, EditPaste и т.п. по всем нужным комбинациям, для каждой вводим Ctrl-комбинацию в поле "Press new shortcut key", после чего жмем Assign.

воскресенье, 16 декабря 2012 г.

jQuery: сериализация объекта / массива в строку GET-запроса

Для сериализации объектов / массивов в строку GET-запроса в jQuery есть метод param (который используется ей самой, например, в ajax()):
var myparams = {a:"a",b:"b"};
var str = $.param(myparams); // запишет в str "a=a&b=b"

пятница, 14 декабря 2012 г.

Error starting Sun's native2ascii: sun.tools.native2ascii.Main

При сборке maven`ом чужого проекта вывалилась ошибка:
Error starting Sun's native2ascii: sun.tools.native2ascii.Main
Причина её в том, что я использовал JDK 1.7. С 1.6 все собралось.

DQL: сравнение дат

Задача:

В некотором атрибуте хранится дата. Необходимо осуществить поиск по этому атрибуту,  но с точностью до даты, т.е. время в поиске участвовать не должно.

Решение:

Использовать функцию DATEFLOOR, например:
SELECT * FROM xxx WHERE DATEFLOOR(day,my_date_attr) = DATEFLOOR(day,DATE('12-12-2012'))
Нужно учитывать, что DATEFLOOR осуществляет округление до времени 00:00:00 по GMT, т.е. если текущий часовой пояс GMT+4, то DATEFLOOR(day,DATE('12-12-2012')) вернет не '12-12-2012 00:00:00', а '12-12-2012 04:00:00'. 

четверг, 13 декабря 2012 г.

java.util.Date to XMLGregorianCalendar

Собственно тривиально:
import java.util.Date;
import java.util.GregorianCalendar;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;

GregorianCalendar cal = new GregorianCalendar();
cal.setTime(new Date());
XMLGregorianCalendar xmlDate = DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);

Куда в WSO2 ESB подкладывать свои OSGI-бандлы

Методом проб и ошибок я определил, что свои OSGI-бандлы нужно подкладывать в repository/components/lib, потому что из dropins и extensions они почему-то не видятся. Если добавлять через ESB Artifacts -> Add - тоже О_о (хотя надо ещё раз попробовать).

WSO2 ESB: отправка multipart-сообщения MailTransportSender`ом

Чтобы отправляемое сообщение стало multipart, нужно установить свойство "transport.mail.Format" в области видимости "axis2" в значение "Multipart":
<property name="transport.mail.Format" value="Multipart" scope="axis2"/> 
Толку правда от этого не много. Управлять составом частей формируемого multipart-сообщения мы не можем, оно будет состоять ровно из 2ух частей: первой "text/plain" с текстом "Web Service Message Attached", и второй, содержащей собственно наше сообщение. 

Доступ к объекту org.apache.axis2.context.MessageContext из script-mediator

В коде пользовательского Java-медиатора можно получить доступ к axis2-контексту, прикастовав контекст сообщения Axis2MessageContext и вызвав getAxis2MessageContext() (это может понадобится, например, для получения параметров прокси-сервиса). В script-медиаторе, к сожалению, метода getAxis2MessageContext() нет, при попытке сделать mc.getAxis2MessageContext() валится:
com.sun.phobos.script.util.ExtendedScriptException: org.mozilla.javascript.EcmaError: TypeError: Cannot find function ge
tAxis2MessageContext. (<Unknown Source>#2) in <Unknown Source> at line number 2
        at com.sun.phobos.script.javascript.RhinoCompiledScript.eval(RhinoCompiledScript.java:68)
        at javax.script.CompiledScript.eval(CompiledScript.java:92)
Дело в том, что переменная mc в скрипт-медиаторах является объектом класса org.apache.synapse.mediators.bsf.ScriptMessageContext (а не org.apache.synapse.core.axis2.Axis2MessageContext, как в случае с Java-медиатором), в котором действительно нет метода getAxis2MessageContext(). Поэтому, к сожалению, функционал, который можно реализовать с помощью script-медиатора, несколько уже, чем таковой для Java-медиаторов.

UnsupportedClassVersionError


Исключение:
java.lang.UnsupportedClassVersionError: ... :minor version 51.0
говорит о том, что класс может быть запущен только JRE 7.

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

WSO2 ESB: раздельная настройка логирования для разных прокси-сервисов


В WSO2 ESB можно разделить логи, относящиеся к разным прокси-сервисам. Для этого нужно в файл настройки логирования (repository/conf/log4j.properties) добавить следующее:
log4j.category.SERVICE_LOGGER.MyProxy=FATAL, MYPROXYADAPTER
log4j.additivity.MYPROXYADAPTER=false
log4j.appender.MYPROXYADAPTER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.MYPROXYADAPTER.File=${carbon.home}/repository/logs/custom/MyProxy.log
log4j.appender.MYPROXYADAPTER.Append=true
log4j.appender.MYPROXYADAPTER.layout=org.apache.log4j.PatternLayout
log4j.appender.MYPROXYADAPTER.layout.ConversionPattern=%d{HH:mm:ss,SSS}%n%m%n
 , где "MyProxy" - имя прокси-сервиса, для которого настраивается логирование. Конфигурация аппендера может быть любой другой, здесь я её привел просто для примера.


WSO2 ESB 4.5.1: разделитель - новая строка или табуляция в Log-медиаторе

Задача:

Выполнять логирование Log-медиатором с разделителем - новой строкой (чтобы, например, при уровне лога "full" все логируемые параметры сообщения, а также его содержимое, располагались на разных строках).

Решение:

Использовать в XML-конфигурации в качестве разделителя числовую ссылку на символ, соответствующую символу перевода строки (line feed) - "&#x000A;" или табуляции - "&#x0009;":
<log level="full" category="INFO" separator="&#x000A;">
<property name="myprop" expression="synapse:get-property('myprop')"/>
</log>

Carriage return и line feed в виде numeric character reference в XML

Символу Carriage return (CR, \r) соответствует числовая ссылка на символ (numeric character reference, не знаю как менее коряво перевести) "&#x000D;", а line feed (LF, \n) - "&#x000A;".

суббота, 8 декабря 2012 г.

WSO2 ESB: UnmappableCharacterException при отправке сообщения sender`ом HttpCoreNIOSender

Проблема:

Есть 2 прокси-сервиса, один - VFS -> JMS, принимающий файлы, обрабатывающий их и сладывающий сформированный SOAP в очередь, и другой - JMS->HTTP, отправляющий это сообщение конечному сервису. В последнем при отправке сообщения sender`ом HttpCoreNIOSender валится исключение:
[2012-12-06 10:20:18,108] ERROR - ClientHandler I/O Error submitting request : Input length = 1
java.nio.charset.UnmappableCharacterException: Input length = 1
        at java.nio.charset.CoderResult.throwException(CoderResult.java:278)
        at org.apache.http.impl.nio.reactor.SessionOutputBufferImpl.writeLine(SessionOutputBufferImpl.java:160)
        at org.apache.http.impl.nio.codecs.AbstractMessageWriter.write(AbstractMessageWriter.java:93)
        at org.apache.synapse.transport.nhttp.LoggingNHttpClientConnection$LoggingNHttpMessageWriter.write(LoggingNHttpC
lientConnection.java:137)
        at org.apache.http.impl.nio.DefaultNHttpClientConnection.submitRequest(DefaultNHttpClientConnection.java:241)
        at org.apache.synapse.transport.nhttp.LoggingNHttpClientConnection.submitRequest(LoggingNHttpClientConnection.ja
va:78)
        at org.apache.synapse.transport.nhttp.ClientHandler.processConnection(ClientHandler.java:256)
        at org.apache.synapse.transport.nhttp.ClientHandler.connected(ClientHandler.java:204)
        at org.apache.http.impl.nio.DefaultClientIOEventDispatch.connected(DefaultClientIOEventDispatch.java:134)
        at org.apache.http.impl.nio.reactor.BaseIOReactor.sessionCreated(BaseIOReactor.java:284)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.processNewChannels(AbstractIOReactor.java:419)
        at org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:286)
        at org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)
        at org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:54
2)
        at java.lang.Thread.run(Thread.java:722)

Причина:

Причина оказалась в русских именах файлов. Дело в том, что VFS кладет имя файла и полный путь к нему в транспортные заголовки "FILE_NAME" и "FILE_PATH". Эти заголовки сохраняются при прохождении сообщения через JMS и попадают sender`у. А тот, видимо, не любит не-ASCII-символы в заголовках и валится. В качестве workaround`а можно удалять проблемные заголовки перед отправкой сообщения:
<property action="REMOVE" name="FILE_NAME"
                scope="transport" value=""/>
<property action="REMOVE" name="FILE_PATH"
                scope="transport" value=""/>
, или, если они нужны, скажем, в out sequence, можно перекладывать значения в свойства в области видимости default.

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

DFC: получение идентификатора объекта, созданного с помощью CREATE DQL-запроса

При выполнении CREATE DQL-запроса с помощью DfQuery идентификатор объекта возвращается как аттрибут "object_created" единственной строки результирующей коллекции:
IDfCollection col = query.execute(session, DfQuery.DF_QUERY);
col.next();
IDfId newId = col.getId("object_created");

WSO2 ESB 4.5.1: "org.apache.axis2.AxisFault: Transport error: 415 Error: Unsupported Media Type" при отправке сообщения на backend-сервис

Проблема:

При проксировании запроса возникает ошибка:
[2012-12-05 18:21:58,341]  INFO - HTTPSender Unable to sendViaPost to url[http://localhost:8084/mysrv]
org.apache.axis2.AxisFault: Transport error: 415 Error: Unsupported Media Type
        at org.apache.axis2.transport.http.HTTPSender.handleResponse(HTTPSender.java:308)
        at org.apache.axis2.transport.http.HTTPSender.sendViaPost(HTTPSender.java:194)
        at org.apache.axis2.transport.http.HTTPSender.send(HTTPSender.java:75)
        at org.apache.axis2.transport.http.CommonsHTTPTransportSender.writeMessageWithCommons(CommonsHTTPTransportSende
.java:450)
        at org.apache.axis2.transport.http.CommonsHTTPTransportSender.invoke(CommonsHTTPTransportSender.java:277)
        at org.apache.axis2.engine.AxisEngine$TransportNonBlockingInvocationWorker.run(AxisEngine.java:627)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:722)
Причина:

Заголовок Content-Type, передаваемый шиной конечному веб-сервису, не поддерживается последним. В моем случае значение заголовка было "application/xml", тогда как сервис ожидал "text/xml" (о чем кстати он и писал в свои логи).

Решение:

В цепочке медиации установить свойство "messageType" в области видимости "axis2":
<property name="messageType" value="text/xml" scope="axis2"/>
Тогда Message Builder сформирует правильное значение HTTP-заголовка. 

WSO2 ESB: Установка заголовка SOAPAction

Установка заголовка SOAPAction для исходящего сообщения выполяется с помощью медиатора "header":
<header action="set" name="Action" value="someSoapAction"/>

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

Synapse: использование пространств имен в Xpath в конфигурации

Чтобы конфигурации Synapse использовать пространства имен в выражениях Xpath, нужно в в том теге, в котором xpath используется (или в любом теге-предшественнике этого тега) объявить нужный namespace, после чего его префикс можно будет использовать в Xpath:
<switch source="count($body/myns:MyNode)" xmlns:myns="http://my.ns"

Commons Codec 1.4: encodeBase64String и multi-line chunking

Версии Commons Codec до 1.5 выполняют multi-line chunking (разбитие результата на строки равной длины) при вызове encodeBase64String(byte[] binaryData): http://commons.apache.org/codec/apidocs/org/apache/commons/codec/binary/Base64.html#encodeBase64String(byte[]). Т.к. в WSO2 ESB 4.5.1 входит именно версия 1.4, приходится с этим мириться. Если нужно все же получить результат одной строкой, придется сделать replace:
Base64.encodeBase64String(strToEncode).replace(System.lineSeparator(),"")
Или, для 6ой явы:
Base64.encodeBase64String(strToEncode).replace(System.getProperty("line.seperator"),"")

понедельник, 3 декабря 2012 г.

WSO2 ESB 4.5.1: установка имени очереди для JMS-транспорта прокси-сервиса

По-умолчанию, JMS-транспорт прокси-сервиса слушает очередь, имя которой совпадает с именем самого сервиса. Чтобы указать имя очереди явно, нужно задать параметр  сервиса "transport.jms.Destination":
<proxy xmlns="http://ws.apache.org/ns/synapse"
    name="myProxy" transports="jms">
    ...
<parameter name="transport.jms.Destination">myQueue</parameter>
</proxy>