Showing posts with label XML. Show all posts
Showing posts with label XML. Show all posts

Thursday, January 15, 2015

JAX-WS or JAXB working with the any type

Let's say you are working with a Schema that uses an "any" type like in the ws-security schema: (http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd):



<xsd:complextype name="SecurityHeaderType">
 <xsd:annotation>
  <xsd:documentation>This complexType defines header block to use for security-relevant data directed at a specific SOAP actor.</xsd:documentation>
 </xsd:annotation>
 <xsd:sequence>
          <xsd:any maxoccurs="unbounded" minoccurs="0" processcontents="lax">
   <xsd:annotation>
    <xsd:documentation>The use of "any" is to allow extensibility and different forms of security data.</xsd:documentation>
   </xsd:annotation>
  </xsd:any>
 </xsd:sequence>
 <xsd:anyattribute namespace="##other" processcontents="lax">
</xsd:anyattribute></xsd:complextype>
You generate the Java code using xjc or wsimport and then are wondering how to use the generated SecurityHeaderType object to put something like a username and password inside of it.

You may try to do something like this:



                // build the security header
  SecurityHeaderType headerWSSE = new SecurityHeaderType();
  
  // Create the userName and password that will go in the security header
  UsernameTokenType userNameToken = new UsernameTokenType();
  AttributedString userNameAttribute = new AttributedString();
  userNameAttribute.setValue("joebob");
  userNameToken.setUsername(userNameAttribute );
  
  PasswordString passwordString = new PasswordString();
  passwordString.setValue("secret");
  userNameToken.getAny().add(passwordString);
  headerWSSE.getAny().add(userNameToken);

(NOTE: UsernameTokenType and PasswordString types are both defined in the ws-security schema found here.)
If you run with this code, you will most likely get an error like this when it attempts to create the XML:

javax.xml.ws.WebServiceException: com.sun.istack.internal.XMLStreamException2: javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.internal.SAXException2: unable to marshal type "org.oasis_open.docs.wss._2004._01.oasis_200401_wss_wssecurity_secext_1_0.UsernameTokenType" as an element because it is missing an @XmlRootElement annotation]

The reason is that when xjc or wsimport generated the JAXB annotated classes it didn't create XmlRootElement annotations for UsernameTokenType or PasswordString.  So, it doesn't know how to handle them properly when they are added to the "any" collection.

So, one fix, is to manually add the XmlRootElement annotations to the UsernameTokenType and PasswordString classes.  However, I generally do not like the idea of manually modifying generated code.  It should be throw away to a certain degree.  That is if the schema changes and I need to re-generate I'd like to be able to just wipe the previously generated code and re-generate the new code.

Another option, is to use a plug-in like the annotate plugin to allow you to create a custom JAXB binding file that will add the XmlRootElement annotation as part of the code generation process.
You can find the annotate plugin here. http://confluence.highsource.org/display/J2B/Annotate+Plugin

The third option you have is to write your code in such a way that it wraps the UsernameTokenType and PasswordString objects in JAXBElement objects that can tell it how to create the root element names.  Here's the code for that:



                // build the security header
  SecurityHeaderType headerWSSE = new SecurityHeaderType();
  
  // Create the userName and password that will go in the security header
  UsernameTokenType userNameToken = new UsernameTokenType();
  AttributedString userNameAttribute = new AttributedString();
  userNameAttribute.setValue("joebob");
  userNameToken.setUsername(userNameAttribute );
  
  PasswordString passwordString = new PasswordString();
  passwordString.setValue("secret");
  
  // Since SecurityHeaderType takes "any" type, we must create JAXBElements to properly set the root elements... stupid "any" type!
  QName qnamePassword = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "Password");
  JAXBElement jaxbPassword = new JAXBElement(qnamePassword ,PasswordString.class,passwordString);
  userNameToken.getAny().add(jaxbPassword);
  
  QName qname = new QName("http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", "UsernameToken");
  JAXBElement jaxbUserName = new JAXBElement(qname ,UsernameTokenType.class,userNameToken);
   
  // Add our JAXBElements to the headers "any" list.
  headerWSSE.getAny().add(jaxbUserName);

This final approach requires you to create a QName instance to set the namespace and the root element name. Then you can create a JAXBElement that wraps your actual PasswordString and UsernameTokenType objects.  In my case, I wanted the PasswordString inside the UsernameToken so that's why you see that I added PasswordString into the usernameToken's any collection.

The code above produces the following XML:




<ns3:Security xmlns:ns3="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" >
 <ns3:UsernameToken>
  <ns3:Username>joebob</ns3:Username>
  <ns3:Password>secret</ns3:Password>
 </ns3:UsernameToken>
</ns3:Security>

Your namespace pre-fix may vary of course and I removed some of the extra namespace entries that were declared as part of the Security element since they weren't used in this node.

Hope this helps!  If you have any questions, feel free to post them below.

Wednesday, February 26, 2014

Deploying older JAX-WS applications on WAS 7 or higher (Web Service not recognized)


At my company, we've recently been making a large move from Websphere Application Server(WAS) V6.1 to v8.0.  Some of our web service applications have noticed that if they were exposing JAX-WS service in their app and they simply deployed their app to WAS v8.0, their JAX-WS service was not recognized.  That is there are no errors, but the service is not exposed in anyway.  You can't reach it.

This turns out to be due to the way WAS does the scanning for the JAX-WS annotations.  This article from IBM helps explain it, but I'll give my abbreviated version as well...

Turns out that on WAS 6.1 with the WS Feature pack, WAS would scan your web app for JAX-WS annotations and if a Web Service was found it would expose it properly.  However, as of WAS 7, only Java EE 5 compliant modules are scanned.  That means if you created your app for WAS 6.1, then your web project would have been Java EE 1.4 compatible.  Check out wikipedia for a nice chart of WAS versions and their version support.

Java EE 1.4 means your web module will be version 2.4 where in Java EE 5, your web module would be 2.5.  So, if your web module (war) is not 2.5, then WAS 7 and beyond will not scan it for JAX-WS annotations and therefore your JAX-WS web services will never be exposed.

There are two fixes for this.

1) Upgrade your web module to 2.5 to be Java EE 5 compliant and then WAS 7 and higher will scan it for JAX-WS services.

2) Add a special IBM specific flag in your web applciations META-INF/MANIFEST.MF file like this:
Manifest-Version: 1.0
UseWSFEP61ScanPolicy: true

In the second option, this tell WAS to scan for the JAX-WS annotations even though its an older web module.


If you are using maven you may need to modify your POM file if maven is creating your MANIFEST.MF files.  This stackoverflow post helped us.

Here's what we ended up with:

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceDirectory>webapp</warSourceDirectory>
<webXml>webapp/WEB-INF/web.xml</webXml>
<archive>
<manifest>
<addClasspath>true</addClasspath>
</manifest>
<manifestEntries>
        <UseWSFEP61ScanPolicy>true</UseWSFEP61ScanPolicy>
      </manifestEntries>
</archive>
</configuration>
</plugin>
...



With this configuration we were able to have maven build our META-INF/MANIFEST.MF file in the war and add the special flag "UseWSFEP61ScanPolicy"

Hope this helps.



Building JAX-WS application with Maven


I was having issues building a JAX-WS application using Maven(NOTE: I'm using maven 2.0.11), when I generated my JAX-WS code using JAX-WS 2.2.  I saw errors like this:

cannot find symbol
symbol  : method required()
location: @interface javax.xml.bind.annotation.XmlElementRef

or

cannot find symbol
symbol  : constructor Service(java.net.URL,javax.xml.namespace.QName,javax.xml.ws.WebServiceFeature[])
location: class javax.xml.ws.Service

This blog really helped me find my solution.

I'll summarize briefly what the issue was.

Java comes with several specifications APIs packaged into it, like JAX-WS & JAXB. However, the version of these specifications are not always the latest.   That is, newer versions of JAX-WS and JAXB may be released, but are not in the version of Java you may be using to compile with.  Even if the newer version is available on your server runtime, they may not be available in your JDK.  And even if you try to explicitly try to add the newer versions of the JAX-WS and JAXB jars to your maven dependency section, it still will not work.

In order to override the built in versions of the JAX-WS and JAXB, you have to utilize the "endorsed" folder.   Follow this link to learn more about the "endorsed" folder.

In short, adding newer versions of the jars to an endorsed folder in your JDK allow them to override any embedded versions.

So, the big question then is how to do this with maven.  Here's the answer..

You use the maven-dependency-plugin to create the "endorsed" folder and copy the newer version of the jars into it.  Then you update the maven-compiler-plugin to refer to this endorsed folder.

Here's a sample POM.


<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArguments>
<endorseddirs>${project.build.directory}/endorsed</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<phase>validate</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/endorsed</outputDirectory>
<silent>true</silent>
<artifactItems>
<artifactItem>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.2</version>
<type>jar</type>
</artifactItem>
<artifactItem>
<groupId>javax.xml.ws</groupId>
<artifactId>jaxws-api</artifactId>
<version>2.2</version>
<type>jar</type>
</artifactItem>
</artifactItems>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>


With these settings we were able to successfully build our JAX-WS 2.2 code with Maven.

Hope this helps!




Tuesday, July 23, 2013

JAX-WS Retrieving HTTP Headers


Here's a quick blog on how to retrieve HTTP Headers from a JAX-WS service.  In this example, I'm exposing a web service(I'm providing the service).  I created a JAX-WS handler following these directions


In the handleMessage() method, use the SOAPMessageContext to get the http headers by using the "get" method and passing the  javax.xml.ws.handler.MessageContext's HTTP_REQUEST_HEADERS property.


This will return a Map<String, List<String>>.  Where the key is the http header name and the List<String> is the value... I'm not sure why it's a list, but I just retrieved the first value and got what I expected.

Here's the code:



For the following message(raw view from SOAP UI):

I got the following output in my console:


Hope this helps!  Let me know if you have questions or suggestions!



Tuesday, March 19, 2013

Create a JAX-WS Handler for Logging


This is a quick post about how to create a JAX-WS Handler for a client... actually it's really the same for a provider as well. I show how to get at the body to print it out... of course I'd recommend you use a logging framework instead of System.out.print*... Enjoy...

Create a java class that implements SOAPHandler<SOAPMessageContext>…


Implement the appropriate method (usually handleMessage())…


Create an xml file to hold the handler configuration and make sure the <handler-class> has the fully qualified name of you new Handler class.
<?xml version="1.0" encoding="UTF-8"?>
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee">
<handler-chain>
<handler>
<handler-class>com.nationwide.handler.LogHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>

Save this handler config xml file at the same location as the Service class, that is the generated class that extends javax.xml.ws.Service.
Now update this service class to add the @javax.jws.HandlerChain annotation and set it’s “file” attribute equal to the name of your config file.