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.
- 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<java.util.Date> 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("Created DateType"); } public Object readObject(MessageReader reader, MessageContext context) throws XFireFault { String value = reader.getValue(); System.out.println("Reading DateType from '"+value+"'"); if (value == null) return null; try { Long l = Long.parseLong(value); return new Date(l.longValue()); } catch( NumberFormatException e ) { throw new XFireFault("Could not parse Date: " + e.getMessage(), e, XFireFault.SENDER); } } public void writeObject(Object object, MessageWriter writer, MessageContext context) { System.out.println("Writing DateType.."); assert( object instanceof Date ); Date d = (Date) object; writer.writeValue(""+d.getTime()); } }
- 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("Created service object."); // 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("Creating MyXFireServlet."); } public void init() throws ServletException { super.init(); System.out.println("Initializing My XFire Servlet.."); ServiceRegistry serviceRegistry = getXFire().getServiceRegistry(); for( Service service : (Collection<Service>) serviceRegistry.getServices() ) { TypeMapping tm = ((AegisBindingProvider) service.getBindingProvider()).getTypeMapping(service); tm.register(new DateType()); System.out.println("Registering DateType for service "+service.getName()); } System.out.println("Initialization complete."); } }
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:
<!-- XFire --> <servlet> <servlet-name>XFireServlet</servlet-name> <display-name>XFire Servlet</display-name> <servlet-class>my.package.name.MyXFireServlet</servlet-class> </servlet>
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)