Table of contents
- Table of contents
- Configuring the Web Service
- Seting Flex up as Web Service client
- Defining the output format
- Custom Datatypes
Web services are great ... untill you have to implement one for a project with a pretty complicated data-model.
In my current Project, I had to use an Adobe Flex client to communicate with a web service. In general this schould be a pretty easy task ... unfortunately it was trickier than I thought.
I have tried quite a lot of solutions. My first approach was to use Cocoon for generating SOAP Messages using Xml pipelining. Cocoon is great for generating output in any format. Unfortunately it's a total overkill to add a full blown Cocoon just for the web interface. I hat quite some trouble adding Cocoon as frontend to a project using Jackrabit, Groovy, Spring and JPA. After a classpath nightmare I dropped this. Besides, after solving the integration problems, I would still have to implement the WSDL, Schema creation and request parsing myself.
Spring-WS suffered the same "problems". I would have to interpret the SOAP message on Xml level which would have been a very long and booring task.
I was looking for a framework which was able to take care of the mashaling and unmarchaling of SOAP request with a minimum of fuss. After having a look at several other solutions I finally came across Apache CXF, wich is a fusion of Objectweb Celtix and Codehaus XFire. CXF promissed to be what I was looking for. First tests looked extremely promissing.
Unfortunately documentation is quite fragmented. Especially when dealing with Spring and the Aegis data binding. The Aegis documentation refers to the XFire documentation. This is no longer maintained and in the meanwhile the gap between the old XFire Aegis and the CXF Aegis is getting greater and greater. One example would be the handling of polymorphism in web services. The CXF documentation references the XFire documentation. Unfortunately the XFire documentation mentions Interfaces to be not supported due to Xml Schema problems. The CXF documentation says it's possible, but not how.
A lot of configuring options are described by samples configuring the CXF endpoints and servers programatically. When reding the documentation I really got confused by the 3 or 4 ways to configure CXF using spring. Every sample seemed to use a differen approach.
I had to figgure out quite a lot of stuff the hard way by digging into the source-code.
This is why I decided to write this text. I hope I helps others a little with this great Tool.
Configuring the Web Service
It is our goal to make a Spring bean available as a Web Service. For this there are several options. I have decided to use the Apache CXF Framework, as it minimizes the overhead needed to achieve this task. CXF dynamically creates the WSDL and Xml Schemata for a Web Service by using reflection to analyze a service class. This works wuite well without additional configuration for very simple interfaces. Simple means, services using only simple datatypes or classes, with no-args constructors and getters and setters for all important properties.
Here a Spring configuration making a simple Service available as Web-Service:
When initializing this Spring context the bean with the interface "SimpleService" should be available under the url: "http://localhost:9000/simpleService". When accessing the following url, the wsdl definition for this service should be returned: "http://localhost:9000/simpleService?wsdl".
If everything is working now - lucky you - there is nothing more to do for now. If not - wellcome to the club - the following sections will cover individual options an problems I ran into.
Seting Flex up as Web Service client
Accessing our previously created Web Service from Flex is pretty eays using Adobes Flex Builder.
By klicking on the "Import Web Service ... (WSDL)" item in the "Data" menu a wizard will take care of creating a Web Service client for our Web Service automatically. All you have to do is provide the Url of the wsdl document decribing the Web Service. In our case this is "http://localhost:9000/simpleService?wsdl". Next you have to define in which package the generated code will be placed. After clicking on the finish button a lot of files will be created.
As all processing in Flex is done asynchron, there is no "listXYZ()" method returning a list of XYZs.
For the next part of this tutorial, let's assume our Web Service has a method called "List<XYZ> listXYZ(String filter)" which simply returns a list of "XYZ" objects.
At first we have to get our hands on an instance of the Web Service object itself. This is done by the following code (Assuming the webservice was called "TestWebservice"):
When debugging the communication I would certainly sugest to enable Flex logging mechanisms, so executing the following code fragment, will make Flex dump a lot of communication info to the Eclipse console:
The output generated now, together with a tool automatically formatting xml (I use oXygen for this) certainly helps a lot when debugging the communication. Especially if not everything is working out of the box, as it was in my case.
In order to actually use the Web Service, there are two ways of doing this:
Using the addXYZListener methods
This is the Flex-Default method. In this an event listener is registered for the individual methods of the webservice. Here is an example:
This will register the method "wsCallbackListXYZResult" as listener for listXYZ results. Whenever this method is called and the server sends a response, this code will be executed. It is important to note, that multiple listeners can register for the same method. The Web Service method is called by the following code Fragment:
At first this creates a Request object, which generally has the same name as the method we are calling, but starting with a capital letter. This object contains properties for each parameter used in the method call. In our case above, we simply set the filter property. This request object is then provided to the webservice call-method. As soon as the server sends a response, the callback method will process it.
In the above code the result of the method call is retrieved from the _result property of the result event and a little sample processing is done.
In my case I had to execute one method in several places of my application. I could have had one central xyz-list somewhere, but I wanted to build my application a little more flexible. With this approach I would have had multiple listeners for these method calls and every time a method is called, all of them would have been executed, which is quite a waste of processor time.
This is why I would suggest the following approach when dealing with more complex applications.
Using the token method
When having a more detailed look ad the listXYZ method of the Web Service object, you can see, that it returnes an object of type "AsyncToken". This Token can be used to register an event listener for one individual method call, which makes it possible to decouple Web Service calls.
The following example illustrates the this method:
There is one difference though. In the first method, the callback functions can be of the more concrete type ListXYZResultEvent. When using the token based approach, the signature of the callback method can only be ResultEvent!
In this example I also added a listener that is called in case of errors. There is one important thing to note. The AsyncToken has to be saved on a class-level, or as soon as the callListXYZ method is finished processing, the token is invalidated and no method is ever called.
As Mentioned in the above chapter, CXF uses reflection to generate the Web-Service adapter. There are several cases, when we need to provide a little more information for CXF to work correctly.
One of these Cases is when using inheritance. Usually the return type is defined as an Interface or (abstract) base class. In this case there is no way for CXF to find out all the available options using reflection. Therefore we have to provide a list of potential classes. Additionally it is necesarry to add a "type" attribuite to the SOAP output.
If for example we have a class Person and sub-types MalePerson and FemalePerson the configuration would look like this:
In the above configuration we have extended the simple:server configuration with a dataBinding element. This overrides the default Databindinf of CXF (JAXB 2.x is the default DataBinding) with an Aegis DataBinding. I pretty much liked the Aegis DataBinding, as it was able to generate a much cleaner type-defintion for even complex types in the resulting WSDL.
In this DataBinding we generally tell CXF to output the type of element as a soap attibute. This is done by setting the "writeXsiTypes" property to "true". Additionally we have to tell CXF about the options. In out case these are FemalePerson and MalePerson. As soon as one of the service methods returnes one of these types, special output is generated representing these individual types.
Outputting the xsiType is not generally required for webservices, but when having a look at how Flex handles SOAP input, I understood the why this is necessary. The webservice outputs all Peroson objects inside a Person xml-element, no matter what type they actually are. Depending on their acutal type these contain different additional elements. For the client there is no way of finding out which type best fits this information. This is why Flex checks, if the Person element has a type attribue. If this is present the type defined by this attibute overrides the element-name and Flex instantiates a container object of the right type.
The above code configures Spring and CXF to use the stand-allone Jetty implementation. If this configuration was to be deployed as a war-archive, then the following line:
Would have to be replaced with:
The code for starting and configuring a CXF Webservice is relatively minimalistic
This is possible, because Spring initializes one singleton instance of all beans not configured as scope="prototype" when initializing the spring context. With this it initializes all Server instances needed.
Defining the output format
There are several defaults, that would make your Web Service not quite look and behave as you whish.
As default mapping, Aegis anf CXF will output every property as an element.
As the names of parameters of a method are not available using reflection the signature of every generated method would be "method(arg0, arg1, ...).
All parameters to methods defined are optional per default. That means, that null-values can be passed to them.
If you want to take control of the output format, you can do so by adding an Xml file with the same name as the class you want to map with a file ending of ".aegis.xml". You can either put the file directly next to your class or save it in a separate directory. When saving in another directory you have to make sure the file is still acessible using the classpath notation of the original file. In our case we would have to place the "FemalePerson.aegis.xml" File in a directory that is accessible using "de/cware/model".
The format is pretty simple. Here a sample file for defining the signature format of methods where all parameters are to be treated as non-optional properties:
Here an exampe of a bean mapping file, taking care of how the output is created:
Per default the properties firstName, lastName and age would have ben exposed in the web service as elements with names matching the property names. If we want to control the names used in the web service, the mappedName defines which name is used in the web interface.
In the above case we want the fields to be called "first-name" and "last-name" as they look nicer in the Xml. Additionally we want the properties to become attributes of Person instead of child elements. This is done by the "style" attribute, which can be ser to "element" (default) or "attribute".
Sometime Aegis and CXF would expose more properties than intended. As soon as a public property or a public get- or is-method is found inside a class, it is exposed under the property name that would usually relate to the method name. If we want to prevent properties from being exposed, we can tell Aegis to ignore these (virtual) properties by adding a "ignore" attribute to that Property.
Sometimes we use non-standard Types inside our applications. This is no big deal as long as we have control over the source of those types. We get into trouble as soon as we use third party types. In this example I will demonstrate how to use custom types by adding mapping capabilities for the "org.safehaus.uuid.UUID" class.
UUIDs are great if you need ids that are unique between different server instances ... unfortunately in one of my projects they were a real pita when creating the web service interface for classes using UUIDs.
The problem is, that you can't create UUIDs by creating an instance using the no-args constructor and then setting all properties findable by reflection. in case of the UUIDs I would like to output them as simple strings. In read direction "uuid.toString()" does the job and in the reverse direction "UUID.valueOf(...)". Pretty simple, but how do I get Aegis to use these?
Starting point for this mapping is to create a custom Type mapping. This is done by extending the Type class. I won't realy go into detail here, since the code is quite straight forward and I have to admit, that I did a lot by trial and error and simply cannot explain everythig
Here is my code for my UuidType:
There are now two ways to use this mapping. You can make Aegis generally use this type mapping whenever it encounters a UUID object or you can explicitly use it. Later aproach might be suitable when using a Type that is not widely spread in the webservice or if multiple different implementations should be used.
In order to make Aegis use this custom type mapping only in certain places, the ".aegis.xml" files are used. Here an example:
This configuration tells Aegis to use the UuidType for mapping the id property and to make the property appear as a simple string property on the web service. Setting the "typeName" attribute to "xsd:string" would result in this UUID being serialized as simple string datatype. This would be great when using the mapping for output only. As soon as you nead to read UUIDs this would fail, because the client would send a parameter as type "String", which Aegis would parse using a "StringType" instead of our UuidType and would then try to set a UUID property with a String datatype. This would result in an IllegalArgumentException. By setting this to the non-existent type of "uuidString" Aegis and CXF create a new type ... unfortunately reverse engineering the UUID would result in the following schema definition:
Unfortunately this is not what we want. When having a look at the methods implemented by the Type class, the method writeSchema looked interesting. For the UUID type we would like to create a simple type with the following definition:
This is why I created an alternate implementation of "writeSchema". After adding the following method to the UuidType class, we are "hopefully" finished without any side-effects:
When using CXF 2.2 the signature of the createSchemahas changed a little, as the CXF guys have thrown overboard using JDom for schema creation and have switched to Apache commons WS objects. This changes the writeSchema method to the following:
Configuring our type as a global type is a little more tricky, especially when using spring.
In order to register a type you have to get an instance of the currently configured typeMapping. This can be externaly configured and set in the AegisContext object. If no mapping is specified, the DefaultTypeMapping is used.
As soon as we have an instance of the TypeMapping, we can use the "register" method to register the new type.
Unfortunately when using Spring, the AegisContext is defined with a scope of "prototype". This makes Spring create a new Object each time it is requested. This is why my first attempts to register cold do nothing else than fail. To make things even worse, there seems to be no way to conifgure this in a nice way when using Spring. The only construct I came up was a lot of really freaky MethodInvokingFactoryBean-stuff but finally failled, because I was not able to dynamically call the "DefaultTypeMapping.createDefaultTypeMapping" with a signature of "Boolean, Boolean" (when using reflection Java does seem to matter if it's "boolean" or "Boolean").
The final solution was to create my own Context-class. This was directly derived from AegisContext. All I had to do here, was to reimplement the "initialize" method and to register my custom type.
After switching to this context in my Spring configuration, the UUIDs worked perfectly
Unfortunately there seem to be some issues, when trasmitting the UUIDs as attributes instead of elements, as soon as the UUIDs are sent as attributes, the UUID-values are all null. I'll deal with this as soon as I need to ... currently I simply quit using attribute-transports.
In order to get Flex and CXF to talk to each other we have to patch both CXF and Flex.
Patching CXFs Agis databinding to fix element-order problems
Unfortunately there is currently a bug in the current Aegis databinding implementation of the BeanType implementation.
When using extension of schema datatypes, which are based on sequences, it is required to serialize the elements of the base type and append the extension elements afterwards. Unfortunately Aegis serializes the elements of the current class first and then the ones of the class the current bean is derived from. This makes Flex fail loading the response correctly and would result in only the properties of the real class being set and all derived properties are "null".
In order to fix this, you have to modify the following file: apache-cxf-2.1.4-src\rt\databinding\aegis\src\main\java\org\apache\cxf\aegis\type\basic\BeanType.java. Here inside the method "writeObjectInternal" the two last code blocks have to be swapped:
Solves this part of the problem.
Now all you have to do is build and install this module in your local repository and use it.
If you are using Nexus, you should manually remove the 2.1.4 version from any cache repository and manually install the modified version. Strangely, when trying to build it as 220.127.116.11 it won't work.
NOTE: This issue will be fixed in version 2.2 and 2.1.5 of the CXF framework
Fortunately the above Problem seems to be fixed in the freshly released version 2.2 of the CXF framework. A few things have changed though:
When configuring the Stand-Allone Jetty version of CXF in Spring, I have to import three additional configurations. Strangely in 2.1.4 there was no need to import these, even if the tuorials mentioned them. Without these you will propably get strange "Injection of resource methods failed" and "No bean named 'cxf' is defined".
After adding them, all is fine ... well allmost
It seems that another bug was introduced in 2.2 which wasn't the case in 2.1.4. This bug affects the schema creation. As soon as a class uses a class of a different package, the definition of this is located in a different schema. The old CXF version automatically generated xsd:import elements to import these schemas. This doesn't affect the validity of the generated schema. Unfortunately without this information the Flex SOAPDecoder is not able to devode the message correctly.
This Bug has been reported and is fixed in the 2.2.1 version of CXF. Untill then ther are three possibilities to use Flex together with CXF:
- Deploy the Webservice using CXF 2.1.4 and create the Webservice client using the Flex Builder and then switch to CXF 2.2 (It was great to have an old version in my SVN
- Import the WSDL in the Flex Builder and manually add the Import statements. After this, re-generate the client code (!!! Without refreshing the WSDL !!!)
- Download the CXF source and manually build and install CXF in your local repository. You can then use the 2.2.1-SNAPSHOT version.
- CXF Snapshot builds should be available, but I never managed to use them. Propably this is due to problems with my Nexus, but as I had the code anyway, I decided to build myself.
Hopefully version 2.2.1 will be released soon.
Patching Flex to work with polymorph datatypes
Fixing the SOAPDecoder
This is no real patching. It's more implementing a custom Decoder class and changing the Webservice to use this instead of the default SOAPDecoder.
The problem with the SOAPDecoder is, that it only respects the type defined in the Xml Schema and ignores the type hint sent with the soap response. When using polymorph results, this would result in only reading base classes and dropping all the extension stuff. In order to instantiate the correct type, we have to add an additional check to the decodeElementTopLevel method, that tests if a type attribute is sent in the response and if this is true, to use this instead of the one defined in the Schema. In my current version there is a little flaw, that it should check if this returned type is an implementation of the base type, but as the SOAP messages are created dynamically, I skipped this. I'll try to add this when I have a little more spare time.
Unfortunately there seems to still be an issue, if a derived type is used inside the response somewhere. Here the SchemaManager seems to be unable to find the types.
Fixing the SOAPEncoder
As soon as you start sending back objects that are derived from the base type, that is defined in the method signature, you run into big problems, when using the default SOAPEncoder in Flex. This is caused of the way the SOAPEncoder looks up types. As far as I reverse engineered this, I takes a look at the schema and uses this type. In our case this woulb be the base class and CXF and Aegis would start complaining about problems instantiating an abstract class (Or something equivalent). One possibility to use the SOAPEncoder without patching, would be to modify the generated model classes in Flex to implement the interface mx.rpc.xml.IXMLSchemaInstance, which defined a xsiType property. Manually implementing this would result in the desired output. Unfortunately I hav two problems with this approach. As soon as I modified my classes to implement the interface, xsiType properties were not only created for my custom classes, but for all properties and method signatures used inside the class. CXF and Aegis seemed to have some problems with reading xsiType attributes for simple Java datatypes such as String, Integer, Double, ... The more serious problem was, that I would have to modify about 120 Classes in my current application by hand and each time I updated the Webservice, I would have to do it again. This is why this approach was out of the question for me and I created a custom CxfSoapEncoder.
In it's current state it is a little more hacked, than I wanted to, but for the sake of sharing my efforts, I'll here comes my solution.
The SchemaTypeRegistry created by flex has one major drawback. It is able to return a flex-type based on a xsiType, but not the other way around. Creating a custom SchemaTypeRegistry seemed to be the right thing to do, but if I didn't want to update it's usage everywhere in the code, I had to create it as a proxy to the original implementation. This way the original implementation is used for lookups in the one direction and my proxy type registry only handles the other direction. Here is the ful code of this proxy type registry:
All that has to be modified to use this registry are the calls to register the custom types in the base Web Service class called Base[Webservicename]. Every call:
has to be changed to:
After this is done, we can start creating our custom CxfSoapEncoder which extends the original SOAPEncoder. I'll paste the code in now and explain the individual parts afterwards:
In general, all that happens, is that our registry is checked, if an xsiType is registered for the given object. If it is, this QName is used, otherwise the normal SOAPEncoder method is used.
Unfortunately I had two problems:
CXF generates a class called string2stringMap for mapping Map<String,String> maps. (I guess other combinations of Map signatures will genrate similar types). The main Problem is, that the first letter is lower case. Here Flex will try to serialize a "string2StringMap" class, which will most certainly fail because it is called "String2stringMap".
Modifying the CxfSoapEncodet to the following form will fix this problem:
Additionally I ran into problems when the types generated were defined in the Xml Schema using simpleType instead of complexType. This is the case for my custom UUID type and for all Enum types. When encountering one of these types, the SOAPEncoder will run into a recursive-loop causing an Error. To avoid this I had to change back the registration of these types from:
This will prevent the custom types being returned by these types.
After this the CXF communication worked like a charm ... I wonder what my next problem will be
Solving problems with Flex and decoding Enum datatpyes
Unfortunately there are quite some problems when decoding Enum datatypes. In versions of the Flex SDK prior to SDK 3.3 when decoding propably all Enums have a value of "null". This problem has been fixed in SDK version 3.3 so an upgrade to this should fix the problems.