XFire Custom Types

XFire Custom Types – 25th February 2008 – XFire 1.2.6

  • Custom Types are types registered with XFire that perform their own mapping to/from XML.
  • "complex types" are your own beans/objects which don’t have a custom type mapping, therefore XFire will attempt to map to/from XML on its own.
  • They *do* work, but you have to be careful that you set everything up right, otherwise you’ll see a StackOverflowError and many other exceptions.
  • XFire sometimes gets terribly confused if one of your own (complex) types is put inside a map (e.g. of <String,Object>) – unless you create a custom type mapping as described below. (see bug XFIRE-831)
  • I couldn’t find a proper example of this on the web & spent several days discovering the correct way to do this.

Example: java.util.Date
Date objects are automatically converted to XMLGregorianCalendar by XFire. This behaviour may not be appropriate since converting back from an XMLGregorianCalendar to a Date is very slow. This example overrides the normal mapping for java.util.Date so that only a long value is passed across the wire, then reparsed back into a java.util.Date again.

  1. Define your type mapping class. The below defines a mapping for java.util.Date, so that it will be read and written as a String containing a long value (the number of milliseconds from the Date object). Note that this class must be available on the classpath for both client and server.
    import java.util.Date;
     
    import javax.xml.namespace.QName;
     
    import org.codehaus.xfire.MessageContext;
    import org.codehaus.xfire.aegis.MessageReader;
    import org.codehaus.xfire.aegis.MessageWriter;
    import org.codehaus.xfire.aegis.type.basic.ObjectType;
    import org.codehaus.xfire.aegis.type.java5.Java5TypeCreator;
    import org.codehaus.xfire.fault.XFireFault;
     
    public class DateType extends ObjectType {
    	private static final Class&lt;java.util.Date&gt; componentClass = java.util.Date.class;
    	protected QName qname = new Java5TypeCreator().createQName(componentClass);
     
    	public DateType()
    	{
    		setNillable(true);
    		setTypeClass(java.util.Date.class);
    		setSchemaType(qname);
    		System.out.println(&quot;Created DateType&quot;);
    	}
     
    	public Object readObject(MessageReader reader, MessageContext context) throws XFireFault
    	{	
    		String value = reader.getValue();
    		System.out.println(&quot;Reading DateType from '&quot;+value+&quot;'&quot;);
     
    		if (value == null)
    			return null;
     
    		try {
        		Long l = Long.parseLong(value);
        		return new Date(l.longValue());
    		} catch( NumberFormatException e ) {
    			throw new XFireFault(&quot;Could not parse Date: &quot; + e.getMessage(), e, XFireFault.SENDER);
    		}
    	}
     
    	public void writeObject(Object object, MessageWriter writer, MessageContext context)
    	{
    		System.out.println(&quot;Writing DateType..&quot;);
     
    		assert( object instanceof Date );
     
    		Date d = (Date) object;
    		writer.writeValue(&quot;&quot;+d.getTime());
    	}
    }
  2. Register the type mapping with both the client and the server

    Registering with the client is easy. I dare say you already have a convenience method that allows you to get hold of a Service. The important parts from mine look something like this:

    	String serviceURL = ... // replace with your service URL
    	Class serviceClass = ... // replace with the class object for your service (e.g. MyService.class)
    	Service service = new ObjectServiceFactory().create(serviceClass);
     
    	// register our new type mapping class
    	TypeMapping tm = ((AegisBindingProvider)service.getBindingProvider()).getTypeMapping(serviceModel);
    	tm.register(new DateType());	
     
    	serviceObject = new XFireProxyFactory().create(service, serviceURL);
    	System.out.println(&quot;Created service object.&quot;);
     
    	// then cast your serviceObject to the correct type (e.g. MyService) and use it.

    Registering with the server is somewhat more confusing. I tried several things and ended up extending XFireConfigurableServlet so that the type mappings were registered inside the init() method:

    public class MyXFireServlet extends XFireConfigurableServlet {
    	public MyXFireServlet() {
    		super();
    		System.out.println(&quot;Creating MyXFireServlet.&quot;);
    	}
     
    	public void init() throws ServletException {
    		super.init();
     
    		System.out.println(&quot;Initializing My XFire Servlet..&quot;);
     
    		ServiceRegistry serviceRegistry = getXFire().getServiceRegistry();
    		for( Service service : (Collection&lt;Service&gt;) serviceRegistry.getServices() ) {
    			TypeMapping tm = ((AegisBindingProvider) service.getBindingProvider()).getTypeMapping(service);
    			tm.register(new DateType());
    			System.out.println(&quot;Registering DateType for service &quot;+service.getName());
    		}
     
    		System.out.println(&quot;Initialization complete.&quot;);
    	}
    }

    If you do this, you should also update your web.xml file so that your servlet definition for XFire uses your customized servlet, like this:

    	&lt;!-- XFire --&gt;
    	&lt;servlet&gt;
    		&lt;servlet-name&gt;XFireServlet&lt;/servlet-name&gt;
    		&lt;display-name&gt;XFire Servlet&lt;/display-name&gt;
    		&lt;servlet-class&gt;my.package.name.MyXFireServlet&lt;/servlet-class&gt;
    	&lt;/servlet&gt;

    Now your type mapping will be used whenever XFire encounters an object of the type registered by setTypeClass().

Note: if sending Date objects created by Hibernate, they may be of type java.sql.Date – in which case XFire will still convert them to XMLGregorianCalendar unless you add a type mapping (as above) for java.sql.Date. Depending on your database vendor, you may also have to register mappings for vendor-specific types (e.g. com.sybase.jdbc2.tds.SybTimestamp).

You can define type mappings for any of your own objects – just substitute your fully-qualified class name where java.util.Date is used in the example above, and customize the readObject() and writeObject() methods to read and write a representation of your object.

For this example, I used the 3.2.4 version of the woodstox library. If you don’t specify woodstox as a dependency, you may get a cryptic com.bea.xml.stream.MXParserFactory not found error.

Hints:

If you see the following then your client is almost certainly not aware of a custom type mapping that it needs in order to parse a type that it has encountered (possibly in a Map or List). Check that you’ve registered it with the appropriate code that creates your service object.

java.lang.StackOverflowError
	at com.ctc.wstx.util.StringVector.findLastNonInterned(StringVector.java:194)
	at com.ctc.wstx.sr.NsInputElementStack.getNamespaceURI(NsInputElementStack.java:503)
	at com.ctc.wstx.sr.BasicStreamReader.getNamespaceURI(BasicStreamReader.java:775)
	at org.codehaus.xfire.util.stax.DepthXMLStreamReader.getNamespaceURI(DepthXMLStreamReader.java:142)
	at org.codehaus.xfire.util.stax.DepthXMLStreamReader.getNamespaceURI(DepthXMLStreamReader.java:142)
	at org.codehaus.xfire.aegis.stax.ElementReader.getNamespaceForPrefix(ElementReader.java:271)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.extractQName(ObjectType.java:146)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:97)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:133)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:133)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:133)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:133)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:133)
	at org.codehaus.xfire.aegis.type.basic.ObjectType.readObject(ObjectType.java:133)

Dansette