barbitoff programmer`s blog

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

суббота, 23 марта 2013 г.

HTTP server stubbing

Нашел неплохой вариант для стаббинга HTTP-сервера, позволяющий автоматизировать тестирование HTTP-клиентов. Называется сабж Wiremock, имеет неплохую документацию с примерами. Есть в репозитории мавена и подключается к проекту так:
<dependency> <groupId>com.github.tomakehurst</groupId> <artifactId>wiremock</artifactId> <version>1.30</version> <scope>test</scope> </dependency>
Использовать его просто (по крайней мере, в тривиальных тестах), ниже приведен пример теста, проверяющего реакцию некоторого тестируемого класса на коды ответа HTTP-сервера 200 и 404:
import static com.github.tomakehurst.wiremock.client.WireMock.*;
...
private static final int STUB_PORT = 17777;
@Rule
public WireMockRule wireMockRule = new WireMockRule(STUB_PORT);

@Test
void someTest() {
 stubFor(get(urlEqualTo("/200"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "text/html")
.withBody("200 OK")));
// дальше некий код, запрашивающий "http://localhost:"+STUB_PORT+"/200"
stubFor(get(urlEqualTo("/404"))
.willReturn(aResponse()
.withStatus(404)
.withHeader("Content-Type", "text/html")
.withBody("404 Not Found")));
// дальше некий код, запрашивающий "http://localhost:"+STUB_PORT+"/404"
}
Проект правда неидеален, например, проверить basic-аутентификацию у меня так не получилось, метод verify почему-то игнорирует требование наличия заголовка "Authorization". Хотя я, честно говоря, особо не заморачивался с этой проблемой, возможно она решаема и без копания в исходниках Wiremock`а (которые, кстати, доступны на гитхабе: https://github.com/tomakehurst/wiremock).

пятница, 22 марта 2013 г.

Maven и unit-тестирование классов, использующих servlet-api

Проблема:

Имеется maven-проект веб-приложения, у него есть следующая зависимость:
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>6.0</version>
<scope>provided</scope>
</dependency>
Есть некий класс, использующий servlet-api (расширующий HttpServlet). Для него написан unit-тест, при попытке выполнить который валится ошибка:
org.apache.maven.surefire.util.SurefireReflectionException: java.lang.reflect.InvocationTargetException; nested exception is java.lang.reflect.InvocationTargetException: null
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
Caused by: java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/servlet/http/HttpServlet
at java.lang.ClassLoader.defineClass1(Native Method)
...
Причина:

javaee-web-api не может быть использован при выполнении java-кода, т.к. не содержит тел методов.

Решение:

Добавить test-зависимость, содержащую имплементацию servlet-api, например:
<dependency>
<groupId>tomcat</groupId>
<artifactId>servlet</artifactId>
<version>4.1.36</version>
<scope>test</scope>
</dependency>

JAXWS-RI 2.1.4 на weblogic 10.3.5: java.lang.IllegalArgumentException: com.sun.xml.internal.messaging.sa aj.soap.LocalStrings != com.sun.xml.messaging.saaj.soap.LocalStrings

Проблема:

Есть приложение, использующее JAXWS-RI 2.1.4 (поставляется вместе с библиотеками ri в WEB-INF/lib), развернутое на weblogic 10.3.5. В начале при попытке вызова в приложении jaxws-клиента к удаленному веб-сервису падала ошибка:
java.lang.ExceptionInInitializerError
        at com.sun.xml.ws.api.addressing.WSEndpointReference.toSpec(WSEndpointReference.java:637)
...
Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2 counts of IllegalAnnotationExceptions
Two classes have the same XML type name "address". Use @XmlType.name and @XmlType.namespace to assign different names to them.
После некоторых поисков решение было найдено тут: http://metro.java.net/guide/ch02.html#weblogic-10. В соответствии с этой инструкцией в weblogic.xml было прописано:
<!DOCTYPE weblogic-web-app PUBLIC "-//BEA Systems, Inc.//DTD Web Application 8.1//EN" "http://www.bea.com/servers/wls810/dtd/weblogic810-web-jar.dtd">
<weblogic-web-app>
   <description>Weblogic Webapp</description>
   <container-descriptor>
      <prefer-application-packages>
         <package-name>com.ctc.*</package-name>
         <package-name>com.sun.xml.*</package-name>
         <package-name>com.sun.istack.*</package-name>
         <package-name>com.sun.msv.datatype.*</package-name>
         <package-name>com.sun.msv.driver.*</package-name>
         <package-name>com.sun.msv.grammar.*</package-name>
         <package-name>com.sun.msv.reader.*</package-name>
         <package-name>com.sun.msv.relaxns.*</package-name>
         <package-name>com.sun.msv.scanner.*</package-name>
         <package-name>com.sun.msv.util.*</package-name>
         <package-name>com.sun.msv.verifier.*</package-name>
         <package-name>com.sun.msv.writer.*</package-name>
         <package-name>com.sun.org.apache.xml.internal.*</package-name>
         <package-name>com.sun.wsit.*</package-name>
         <package-name>javax.jws.*</package-name>
         <package-name>javax.xml.bind.*</package-name>
         <package-name>javax.xml.soap.*</package-name>
         <package-name>javax.xml.stream.*</package-name>
         <package-name>javax.xml.ws.*</package-name>
         <package-name>javax.xml.activation.*</package-name>
         <package-name>javax.xml.annotation.*</package-name>
         <package-name>javax.xml.mail.*</package-name>
         <package-name>javax.xml.security.*</package-name>
         <package-name>javax.xml.registry.*</package-name>
         <package-name>javax.xml.rpc.*</package-name>
         <package-name>javax.xml.crypto.*</package-name>
         <package-name>javanet.staxutils.*</package-name>
         <package-name>jp.gr.xml.*</package-name>
         <package-name>org.codehaus.stax2.*</package-name>
         <package-name>org.glassfish.gmbal.*</package-name>
         <package-name>org.iso_relax.*</package-name>
         <package-name>org.jcp.xml.dsig.*</package-name>
         <package-name>org.jvnet.*</package-name>
         <package-name>org.relaxng.*</package-name>
         <package-name>org.apache.commons.*</package-name>
         <package-name>org.apache.xerces.*</package-name>
         <package-name>javax.namespace.xml.*</package-name>
      </prefer-application-packages>
      <prefer-application-resources>
         <resource-name>META-INF/services/javax.xml.ws.*</resource-name>
         <resource-name>META-INF/services/com.sun.xml.ws.*</resource-name>
         <resource-name>META-INF/services/com.sun.tools.ws.*</resource-name>
      </prefer-application-resources>
   </container-descriptor>
</weblogic-web-app>

А в WEB-INF/classes были закинуты классы в соответствие с п.3 раздела "Known Issues" по той же ссылке. 
Однако, счастья не наступило, ошибка превратилась в:
java.lang.IllegalArgumentException: com.sun.xml.internal.messaging.sa aj.soap.LocalStrings != com.sun.xml.messaging.saaj.soap.LocalStrings
При последующих вызовах стало падать просто:
java.lang.NoClassDefFoundError: Could not initialize class com.sun.xml.ws.api.BindingID  
Причина:

Причина в поставляемой с приложением библиотеке saaj-impl (она в него пришла вместе с зависимостью от JAXWS-RI), а именно в конфликте содержащегося в ней класса com.sun.xml.messaging.saaj.soap.SAAJMetaFactoryImpl с классом  com.sun.xml.internal.messaging.saaj.soap.SAAJMetaFactoryImpl из rt.jar в JDK (подробнее причина описана тут: http://azagozdzinski.wordpress.com/2011/02/15/saaj-problem/). Сначала проблема вылечилась достаточно просто, добавлением в weblogic.xml следующей строки:
<prefer-application-resources>
<resource-name>META-INF/services/javax.xml.ws.*</resource-name>
<resource-name>META-INF/services/javax.xml.soap.*</resource-name>
<resource-name>META-INF/services/com.sun.xml.ws.*</resource-name>
<resource-name>META-INF/services/com.sun.tools.ws.*</resource-name>
</prefer-application-resources>
Однако, вскоре проблема вернулась обратно. Оказалось, на тот же weblogic было задеплоено другое приложение, также идущее со своим JAX-WS RI, однако более поздней версии - 2.2 (и соответственно, библиотеками saaj-api-1.3.4 и saaj-impl-1.3.19 против 1.3 и 1.3.1 соотв. в моем приложении). На этот раз беда решилась обновлением saaj в моем приложении до тех же 1.3.4 и 1.3.19.
Вот такие вот беды вызывает желание использовать несколько имплементаций JAX-WS в пределах одной JVM (в конечном счете их оказалось аж 3: родная имплементация weblogic'а, а также 2 разные версии jaxws-ri).

понедельник, 11 марта 2013 г.

Maven module project: выполнения гола только одного из модулей

Задача:

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

Решение:

Можно воспользоваться плагином maven-invoker-plugin. Ниже приведен пример его конфига, выполняющий гол "deploy" модулемя "MyModule1":
     <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-invoker-plugin</artifactId>
        <version>1.8</version>
        <configuration>
          <debug>true</debug>
          <pomIncludes>
            <pomInclude>MyModule1/pom.xml</pomInclude>
          </pomIncludes>
          <goals>
            <goal>deploy</goal>
          </goals>
          <projectsDirectory>${basedir}</projectsDirectory>
          <properties>
            <myprop1>${myprop1}</deployment.carbon.host>
          </properties>
        </configuration>
      </plugin>
Также в вышеприведенном примере выполняется передача дочерней сборке одного из параметров родительской (myprop1).
Выполнить описанное в конфигурации действие можно либо навесив его выполнение на какую-либо из фаз родительского проекта (через <execution>), либо вызывая его явно для родительского проекта:
mvn invoker:run

maven-surefire-plugin: задание шаблона для имен тестов

Долго не мог понять, почему maven-surefire-plugin не запускает мой тест-класс, названный BlaBlaTest_2. Дело оказалось в том, что по-умолчанию для имен классов тестов задан шаблон **/*Test.java. Управлять этим шаблоном можно с помощью конфигурации плагина:
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.13</version>
        <configuration>
          <includes>
            <include>**/*Test.java</include>
            <include>**/*Test_*.java</include>
          </includes>
        </configuration>
      </plugin>

cmd: Скобки в значениях переменных и группировки в if

Проблема:

В bat-нике есть некоторая переменная, значение которой содержит скобки. В моем случае переменная эта была CLASSPATH, и содержала она строку "C:/Program Files (x86)/IBM/WebSphere MQ/Java/lib/com.ibm.mq.jar". В bat-нике эта переменная используется следующим образом (в моем случае батником был стартовый скрипт Oracle Weblogic 10.3.5):
if NOT "%PRE_CLASSPATH%"=="" (
set CLASSPATH=%PRE_CLASSPATH%;%CLASSPATH%
)
, т.е. для группировки используются круглые скобки.
При попытке выполнить bat-ник валится ошибка:
set CLASSPATH=;C:/Program Files (x86)/IBM/WebSphere MQ/Java/lib/com.ibm.mq.jar

/IBM/WebSphere was unexpected at this time.
Причина:

При раскрытии переменной присутствующая в ней закрывающая скобка воспринимается интерпретатором как закрытие группировки после "if", а остаток строки интерпретируется как следующая команда ("/IBM/WebSphere...").

Решение:

Использовать отложенное раскрытие проблемной переменной, заключив её в "!" вместо "%". В таком случае значение переменной будет подставляться в команду при непосредственном её выполнении и уже не будет приводить к ситнаксическим ошибкам:
if NOT "%PRE_CLASSPATH%"=="" (
set CLASSPATH=%PRE_CLASSPATH%;!CLASSPATH!
)
Правда, перед этим нужно это самое отложенное раскрытие разрешить:
setlocal enabledelayedexpansion
Спасибо http://stackoverflow.com/questions/2410501/parenthesis-in-windows-cmd-script-variable-values-not-allowed.

Weblogic: ошибка "weblogic.nodemanager.common.ConfigException: Native version is enabled but nodemanager native library could not be loaded"

Проблема:

При попытке стартануть weblogic 10.3.5 под Win7 x64 с помощью соотв. bat-ника валится ошибка:
19.02.2013 13:05:52 weblogic.nodemanager.server.NMServerConfig initDomainsMap
INFO: Loading domains file: C:\Oracle\MIDDLE~1\WLSERV~1.3\common\NODEMA~1\nodema
nager.domains
<19.02.2013 13:05:52> <SEVERE> <Fatal error in node manager server>
weblogic.nodemanager.common.ConfigException: Native version is enabled but nodem
anager native library could not be loaded
        at weblogic.nodemanager.server.NMServerConfig.initProcessControl(NMServe
rConfig.java:249)
        ...
        at weblogic.NodeManager.main(NodeManager.java:31)
Caused by: java.lang.UnsatisfiedLinkError: C:\Oracle\Middleware\wlserver_10.3\se
rver\native\win\32\nodemanager.dll: Can't load IA 32-bit .dll on a AMD 64-bit pl
atform
        at java.lang.ClassLoader$NativeLibrary.load(Native Method)
        ...
        at java.lang.System.loadLibrary(System.java:1028)
        at weblogic.nodemanager.util.WindowsProcessControl.<init>(WindowsProcess
Control.java:17)
        at weblogic.nodemanager.util.ProcessControlFactory.getProcessControl(Pro
cessControlFactory.java:24)
        at weblogic.nodemanager.server.NMServerConfig.initProcessControl(NMServe
rConfig.java:247)
        ... 5 more
Причина:

Вполне очевидна и связана с невозможностью загрузить 32-битную dll-ку на 64-битной платформе.

Решение:

Т.к. в моей ситуации использование этой самой нативной dll-ки критично не было, я попросту отключил её использование, установив в файле $WL_HOME/common/nodemanager/nodemanager.properties свойство:
NativeVersionEnabled=false