Showing posts with label web service. Show all posts
Showing posts with label web service. 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.

Friday, August 8, 2014

Dozer Deep Nesting...A different approach. (No Setters, No Problem.)


Have you ever had the issue of trying to do a deep nested mapping using Dozer, but you keep running into issues because the destination object doesn't have a set method for the property you need to populate?

We see this a lot when dealing with generated JAX-WS code.  The JAXB classes generated from the schema typically do not create set methods for collections.  So, if you are trying to create a Dozer mapping to map from the JAX-WS/JAXB generated code to and from your application domain classes, you can run into some issues if you start trying to do deep mappings.

Here are the classes I want to populate via Dozer (made up for an example... all in package com.barry.to).  Starting with the first one and going down each class contains the class below it.  Usually in a List.



public class StartDestObject {
 
 private HaveNestedListNoSetter haveNestedListNoSetter;

 public HaveNestedListNoSetter getHaveNestedListNoSetter() {
  return haveNestedListNoSetter;
 }

 public void setHaveNestedListNoSetter(HaveNestedListNoSetter haveNestedListNoSetter) {
  this.haveNestedListNoSetter = haveNestedListNoSetter;
 }

}

public class HaveNestedListNoSetter {
 
 private List<HaveListNoSetter> haveListNoSetterList = new ArrayList<HaveListNoSetter>();

 public List<HaveListNoSetter> getHaveListNoSetterList() {
  return haveListNoSetterList;
 }
}

public class HaveListNoSetter {
 
 private List<Thing> things = new ArrayList<Thing>();

 public List<Thing> getThings() {
  return things;
 } 
}

public class Thing {

 private String name;
 private String value;
 public String getName() {
  return name;
 }
 public void setName(String name) {
  this.name = name;
 }
 public String getValue() {
  return value;
 }
 public void setValue(String value) {
  this.value = value;
 }
 
}


Notice that none of my classes that contain collections have any set methods for the List.

So, the idea is that I want to map a much simpler object into this object tree...  Here's my much simpler object structure:

package com.barry.from;

import java.util.List;

public class ItemHolder {
 
 private List<Item> items;

 public List<Item> getItems() {
  return items;
 }

 public void setItems(List<Item> items) {
  this.items = items;
 }
}
public class Item {
 
 private String itemName;
 private String itemValue;
 
 public Item getItem(){
  return this;
 }
 public String getItemName() {
  return itemName;
 }
 public void setItemName(String name) {
  this.itemName = name;
 }
 public String getItemValue() {
  return itemValue;
 }
 public void setItemValue(String value) {
  this.itemValue = value;
 }
}




Here's my test to validate it's working:



public class DozerTest {

 Mapper mapper;
 
 Item item = new Item();
 
 @Before
 public void setup(){
  
  List<String> files = new ArrayList<String>();
  files.add("dozer-bean-mappings.xml");
  mapper = new DozerBeanMapper(files);
  
  item = new Item();
  
  item.setItemName("gear");
  item.setItemValue("Value");
 }
 
 @Test
 public void testEvenMoreNestedMapping(){
  
  ItemHolder itemHolder = new ItemHolder();
  
  itemHolder.setItems(new ArrayList<Item>());
  
  itemHolder.getItems().add(item);
  
  StartDestObject startDestObject = mapper.map(itemHolder, StartDestObject.class);
  
  assertNotNull(startDestObject);
  
  assertEquals(item.getItemName(), startDestObject.getHaveNestedListNoSetter().getHaveListNoSetterList().get(0).getThings().get(0).getName());
  assertEquals(item.getItemValue(), startDestObject.getHaveNestedListNoSetter().getHaveListNoSetterList().get(0).getThings().get(0).getValue());
 }



I'm creating a single Item instance and then set that in the ItemHolder's list.  Then I want to map that Item instance into the Thing class that is deeply nested down at the bottom of my StartDestObject tree.

The trick to allow this deep mapping to work with nested collections that have no set methods is to Not do a deep mapping.  Instead, you break the mappings up into smaller chunks.  You create a mapping that goes from the ItemHolder's item to the StartDestObject's haveNestedListNoSetter property.  That gets us to the first step.  Then Dozer would try to figure out how to map from Item to HaveNestedListNoSetter class.  So, you create a mapping for this which actually just passes the Item (via "this") onto the first item in the HaveNestedListNoSetter's list.  Then you just keep this pattern up, utilizing the "this" to keep passing the Item instance along and then use the is-accessible="true" attribute to access the lists with no set method.

If you're not following my description, here's the dozer mapping to make it all work:

<?xml version="1.0" encoding="UTF-8"?>
<mappings xmlns="http://dozer.sourceforge.net"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://dozer.sourceforge.net
          http://dozer.sourceforge.net/schema/beanmapping.xsd">

  <configuration>
    <stop-on-errors>true</stop-on-errors>
    <wildcard>true</wildcard>
  </configuration>

  <mapping>
    <class-a>com.barry.from.ItemHolder</class-a>
    <class-b>com.barry.to.StartDestObject</class-b>
       <field> 
        <a>items[0]</a>
        <b>haveNestedListNoSetter</b>
        <a-hint>com.barry.from.Item</a-hint> 
      </field>
  </mapping>

   <mapping>
    <class-a>com.barry.from.Item</class-a>
    <class-b>com.barry.to.HaveNestedListNoSetter</class-b>
       <field> 
        <a>this</a>
        <b is-accessible="true">haveListNoSetterList[0]</b>
        <a-hint>com.barry.from.Item</a-hint> 
        <b-hint>com.barry.to.HaveListNoSetter</b-hint>
      </field>
  </mapping>

  <mapping>
    <class-a>com.barry.from.Item</class-a>
    <class-b>com.barry.to.HaveListNoSetter</class-b>
       <field> 
        <a>this</a>
        <b is-accessible="true">things[0]</b>
      <b-hint>com.barry.to.Thing</b-hint>
      </field>
  </mapping>
 
  <mapping>
    <class-a>com.barry.from.Item</class-a>
    <class-b>com.barry.to.Thing</class-b>
    <field> 
        <a>itemName</a>
        <b>name</b>
      </field>
      <field> 
        <a>itemValue</a>
        <b>value</b>
      </field>
  </mapping>
                   
</mappings>



This may make for more mappings, but I don't know of a better solution without creating custom mapping classes or adding setters to the generated code (which sometimes you don't have access to).

If this isn't perfectly clear or you have a better way, please post in the comments.

Thanks!