Consuming Zenfolio Web Service in Java with Maven
I want to automate loading of the photographs I take into the photo sharing service that I use: Zenfolio. Good news: there is a published API.
JAX-WS (wsimport)
Bad news: the WSDL file that describes the service is in the RPC/Encoded style. At least, I think it is. In NetBeans (my IDE of choice), New Web Service Client Dialog fails:
Web Service Client can not be created by JAXWS:wsimport utility. Reason: undefined simple or complex type ‘soapenc:Array’.
JAX-RPC (wscompile)
It seem that wscompile does support this style. When I choose “JAX-RPC Style” in the same NetBeans dialog, code does get generated. There are problems with using wscompile, though.
First, NetBeans does not give me the option of using wscompile in a Maven-based project. The reason is explained in NetBeans Issue 159404: “There is no plan to implement JAX-RPC support for NB 6.7, since there is no JAX-RPC:wscompile maven plugin.”
This is not a deal-breaker, since I can probably integrate some Ant fragments into the POM using AntRun plugin or some such, or just tolerate this one project not be Mavenized. There is a much bigger problem with wscompile and Zenfolio service. The API has a Group object that (is analogous to a folder and) is used to group together sets of photographs and/or other Groups. The WSDL mentions that Group has a property called “elements” with the type:
<s:complexType name="ArrayOfChoice1">
<s:choice minOccurs="0" maxOccurs="unbounded">
<s:element minOccurs="0" maxOccurs="1" name="Group" type="tns:Group" />
<s:element minOccurs="0" maxOccurs="1" name="PhotoSet" type="tns:PhotoSet" />
</s:choice>
</s:complexType>
Code generated by wscompile represents that property as SOAPElement, which makes it unworkable.
Axis1
The other web services stack worth trying is Axis1.
NetBeans IDE does not provide any integration with Axis1. I found some descriptions how to deal with such a WSDL using Axis1 and Maven. Code generation works, and the code generated for the array of the elements of the Group is not as generic as SOAPElement. Unfortunately, only the first element of the list can be accessed! This is not workable.
Axis2
Axis2 is supported by NetBeans for the non-Maven-based projects. Code generation works, but only server-side code gets generated: skeletons, but no stubs!
For maven-based projects, NetBeans does not provide any support for Axis2. Again, I found some instructions on the use of this plugin. Some quoted a org.apache.axis2.maven group for the plugin, but org.apache.axis2 works much better :)
I tweaked the plugin configuration somewhat, using WSDL2Code documentation as a clue to what can be tweaked:
- Default location of the WSDL file was overriden.
- Unwrapping was enabled, so that the aplication code doesn’t need to deal with classes like “LoadGroupHierarchyResponse”, and can work directly with Groups.
- Data-binding classes were unpacked from the stub.
This is how the plugin is configured:
<plugin>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-wsdl2code-maven-plugin</artifactId>
<version>1.5</version>
<executions>
<execution>
<goals>
<goal>wsdl2code</goal>
</goals>
</execution>
</executions>
<configuration>
<wsdlFile>src/main/resources/META-INF/wsdl/zenfolio.wsdl</wsdlFile>
<packageName>com.zenfolio.www.api._1_1</packageName>
<databindingName>adb</databindingName>
<unpackClasses>true</unpackClasses>
<unwrap>true</unwrap>
<skipBuildXml>true</skipBuildXml>
<overWrite>true</overWrite>
</configuration>
</plugin>
Axis2 plugin as dependency
One
post mentioned an important fact that others neglected to mention: the plugin needs to be included among the
dependencies also. Not listing the plugin as a dependency results in
java.lang.NoClassDefFoundError: javax/wsdl/WSDLException
.
To avoid packaging the plugin in any assembly that includes dependencies, it is in the “provided” scope.
Here is a fragment from my pom.xml that describes the Axis2 dependency:
<dependency>
<groupId>org.apache.axis2</groupId>
<artifactId>axis2-wsdl2code-maven-plugin</artifactId>
<version>1.5</version>
<scope>provided</scope>
</dependency>
Something must have changed reasonably recently. I do not see any problems any more when the plugin is not declared as direct dependency!
DataHandler instead of byte[] in Axis2
Unexpectedly, Axis2’s WSDL2Code in some instances generates code that is more difficult to work with than the code generated by wscompile and Axis1. In the AuthChallenge, there are some byte array properties (byte[]). Axis2 - and only Axis2! - exposes them as DataHandlers! So, instead of working with the underlying array, I need to read the content of the stream based on it and reconstruct that array.
This is not a deal-breaker, but it is annoying. Especially since I need to turn byte arrays into DataHandlers when replying to the authentication challenge :)
Setting headers and/or Cookies
To access content that requires authentication, I have to include an authentication token obtained during authentication with every request. Zenfolio is looking for this token both in a header and in a cookie.
Again, there are many posts on this topic, none of which seems to be self-sufficiently-clear :) This is what I ended up doing:
final Options options = ((Stub) zenfolio)._getServiceClient().getOptions();
final List headers = new ArrayList();
headers.add(new Header("X-Zenfolio-Token", authToken));
options.setProperty(HTTPConstants.HTTP_HEADERS, headers);
Conclusion
Web-services formulated in “obsolete” JAX-RPC style can be consumed with the help of modern tools like Axis2 and Maven.
NetBeans should provide integration with Axis2 for Maven-based projects - and make this post irrelevant ;)