In this entry I discuss reasons to switch from Axis to XFire, and then how to get XFire as a server working nicely with .Net as a client.
I have written some past entries about Java/.Net web service interop, specifically how to get a Java Axis server working with a .Net 1.1 client. Now with .Net 2.0 we have nullable types built in to the language which removes the need to use the 3rd party library NullableTypes to handle nullable values.
I am aware that using a WSDL-first approach tends to lead to better interoperability, but here I will only look at generating WSDL from Java classes, and generating the .Net proxy code from WSDL using Microsoft's WSDL tool. The simple fact of the matter is that people are using the tools this way.
So with nullable types built in to the language things work a little better, but Axis still has some of the same problems interoperating with .Net that it did before:
So lets take a look at XFire. After working with it (version 1.1) a bit, I found that:
I recommend having the source code for XFire handy when you're working with it. Though there is documentation, there is a lot you can figure out from the source code and it is clean enough that you won't get lost looking at it.
With XFire's default configuration, it produces WSDL that has the attribute minOccurs="0" in addition to nillable="true" on properties. .Net 2.0 interprets this to mean "not only can you pass a null value for it, it can also not exist at all" which means that null is not the same thing as nonexistant. In the web service proxy, for any property that is a type such as integer, decimal, etc it creates a boolean property in the form propertyNameSpecified (where "propertyName" is the name of the property). When you are passing an object to the web service, you have to set propertyNameSpecified to true for the value of the property to be passed. It is not smart enough to know when you set a value for the property.
So you have a couple of options here:
I suppose the advantage of leaving minOccurs="0" in the WSDL means less to be transmitted over the wire, especially if you have objects with a large number of properties and/or large arrays of such objects. But I chose the latter option since it simplified things and made my WSDL say exactly what I meant it to. They just added the ability to enable/disable minOccurs="0" right before the release of 1.1. However, at the time of this writing documentation was/is non-existant for this. The JIRA issue I just linked to doesn't give you enough information to configure it (it is also slightly inaccurate), so I will document how to do it here:
I am going to describe how to configure this if you have configured XFire using Spring like the included example that comes with XFire. First, you need to get the file "xfire-spring/src/main/org/codehaus/xfire/spring/xfire.xml" from the source code (or jar file). Copy it into your project, as you will be modifying it. Make sure to change your web.xml to reflect the new location of xfire.xml. Then change the import for customEditors.xml to point to it in your classpath (unless you want to copy that over too):
<import resource="classpath:org/codehaus/xfire/spring/customEditors.xml"/>
Now add this to xfire.xml to:
<bean id="aegisTypeConfiguration" class="org.codehaus.xfire.aegis.type.Configuration">
<property name="defaultMinOccurs" value="1"/>
</bean>
Note that there are other options in the Configuration class, so you may want to look at its Javadoc.
Now modify xfire.typeMappingRegistry and inject aegisTypeConfiguration into the configuration property:
<bean id="xfire.typeMappingRegistry"
class="org.codehaus.xfire.aegis.type.DefaultTypeMappingRegistry"
init-method="createDefaultMappings" singleton="true">
<property name="configuration" ref="aegisTypeConfiguration"/>
</bean>
However, this still does not cover date fields. For date fields one option is to using an Aegis map file. For this you will create a file called ClassName.aegis.xml where ClassName is the name of the class that is serialized which contains the date field, and the file is placed in the same package/folder as that class. The file may look something like this, where datePropertyName and anotherDatePropertyName are Date fields you wish to be a nillable:
<mappings>
<mapping>
<property name="datePropertyName" nillable="true"/>
<property name="anotherDatePropertyName" nillable="true"/>
</mapping>
</mappings>
If you are using Java 5, you have the option of using Aegis Java 5 Annotations. For example:
import java.util.Date;
import org.codehaus.xfire.aegis.type.java5.XmlElement;
public class ClassThatHasDate {
private Date someDate;
@XmlElement(minOccurs="1", nillable=true)
public Date getSomeDate() { return someDate; }
public void setSomeDate(Date someDate) { this.someDate = someDate; }
// ...
Be sure to annotate the get method of your property, and also to import to correct XmlElement.
That should be all you need, assuming your Spring-configured XFire was working before you made the changes. You should notice a change in your WSDL (the minOccurs="0" attributes should no longer be there).
If you were using HttpSession in Axis to hold session data, you'll find that XFire does things a little differently. It abstracts away HttpSession and makes it completely unaccessible. Instead, it provides a Session object to store and retrieve session attributes. To access it, you can get it from the MessageContext object. To get MessageContext, you can either include it as a method parameter for your web method, or create a Handler which will receieve it. Once you have the Session object, HttpSession can be retrieved from it. For example, you may have a handler that looks like this:
import javax.servlet.http.HttpSession;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
import org.codehaus.xfire.transport.Session;
import org.codehaus.xfire.transport.http.XFireHttpSession;
public class SessionHandler extends AbstractHandler {
public void invoke(MessageContext context) throws Exception {
Session session = context.getSession();
if(session instanceof XFireHttpSession) {
XFireHttpSession xFireHttpSession = (XFireHttpSession) session;
HttpSession httpSession = xFireHttpSession.getSession();
// Do something with httpSession ...
}
}
}
Speaking of Handlers, it is very important that you NOT configure handlers in xfire.xml, it will throw a NullPointerException if you do. Instead, configure them in xfire-servlet.xml in your XFireExport configuration. In general, I would probably avoid modifying xfire.xml unless there was no other way to configure something, like in the case of minOccurs (correct me if there is a better way).
One thing I was hoping would interoperate better was serializing custom exceptions as SoapFaults. Unfortunately, I have not had any luck get the web service tool in .Net 2.0 to recognize custom exceptions that I expose. If anyone figures this out please comment here or e-mail me!
Comments
discuss
I think you worry too much on this subject but you posted a good question there !
That problem still continues.
That problem still continues. The solution is not to write abstract classes for webServices, but some 'GenericWebService' classes carrying the core functionality methodz and using delegation to them does the simple trick your concrete webService's WSDL is correctly generated.
public void
public void invoke(MessageContext context) throws Exception {
Session session = context.getSession();
The MessageContext don't have getSession() method
org.codehaus.xfire.MessageContext
Note that this is a org.codehaus.xfire.MessageContext which does have getSession(). I will add some of the imports to clear up the confusion.
Mapping File did the trick!
I must say thank you so much. I have been trying to find a simple and straight forward solution to java.util.Date defaulting to nillable="false" or actually the lack of the attribute in the generated WSDL. Creating a mapping file as you described did the trick. Now my WSDL has nillable="true" for my java.util.Date fields.
Should be part of the XFire docs
This posting was so absolutely helpful that I believe it should be part of the official XFire docs. For the record, minOccurs=0 and nillable=true causes the XFire clientgen to map Strings to type JAXBElement (instead of just a normal String), which is really unnecessary. By changing the defaultMinOccurs and defaultNillable, I can now tweak the XFire clientgen! Thank you!
XFire Spring Config for Inheritence mappings
I'm actually trying to do exactly what you describe. The one issue I seems to be running into is that my services are returning Abstract classes. I can't seem to figure out where in my Spring Config to notify XFire of what the subclass types are so that it can properly generate my WSDL for .Net to user. I think it's a matter of correctly configuring my typeMappingRegistry, but I can't seem to figure out how.
Any ideas?
Thanks,
Matt
That problem still continues.
That problem still continues. The solution is not to write abstract classes for webServices, but some 'GenericWebService' classes carrying the core functionality methodz and using delegation to them does the simple trick your concrete webService's WSDL is correctly generated.