Moose is an XML marshalling framework. Moose includes a library of components, which allow developers to create bi-directional mappings between XML and Java classes. Once a mapping has been created, Moose will load XML data into a strongly-typed Java object graph. Moose can also generate XML data from a Java object graph. We refer to each of these operations as "unmarshalling" and "marshalling".
In order to get started with Moose, we'll create a classic "hello world" example.
The following "plain old Java object" will serve as the centerpiece of our mapping.
public class HelloWorld {
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected String name;
}
We're going to want instances of this class to be represented by XML that looks like the following.
<hello-world><name>Moose</name></hello-world>
Building a mapping with Moose is as simple as adding the appropriate annotations to our class.
import com.quigley.moose.mapping.provider.annotation.XML; import com.quigley.moose.mapping.provider.annotation.XMLField; @XML(name="hello-world")public class HelloWorld { public String getName() { return name; } public void setName(String name) { this.name = name; } @XMLField(name="name")
protected String name; }
|
The | |
|
The |
The annotations let us keep our mapping information in-line with the other components of our mapping domain classes.
Now that we've created an annotated domain class, we'll need to get the rest of the framework configured.
We'll create an Application class to contain the
example.
import com.quigley.moose.mapping.Mapping;
import com.quigley.moose.mapping.MarshallingContext;
import com.quigley.moose.mapping.provider.annotation.AnnotationMappingProvider;
import com.quigley.moose.mapping.provider.annotation.StaticClassesProvider;
public class Application {
public static void main(String[] args) {
StaticClassesProvider classesProvider = new StaticClassesProvider();
classesProvider.addClass(HelloWorld.class);
AnnotationMappingProvider mappingProvider =
new AnnotationMappingProvider(classesProvider);
Mapping mapping = mappingProvider.getMapping();
}
}
The Mapping type is the central framework element in
Moose. A Mapping instance contains all of the information
about binding XML and Java. In order to use Moose to marshall XML data, we'll
need a configured Mapping instance. There are a couple of
different ways to get a Mapping instance--either use a
MappingProvider subclass to obtain one, or assemble one
directly.
In this example we'll use the AnnotationMappingProvider
to obtain our Mapping instance. The
AnnotationMappingProvider understands the Moose
annotations and can use that information to configure and return a
Mapping instance.
In order to build a Mapping, the
AnnotationMappingProvider needs to obtain a list of the
mapping domain classes in the classpath. The
AnnotationMappingProvider uses an
AnnotatedClassesProvider subclass to locate these
classes. In this example, we're using a
StaticClassesProvider and configuring it with our
single HelloWorld class.
Moose ships with other AnnotatedClassesProvider types,
including a ScannotationClassesProvider implementation,
which can auto-discover your mapping classes in your application's classpath.
These providers can be configured to accomodate a variety of scenarios.
Once a Mapping instance is configured and available, it
can be used to marshall Java objects to XML and unmarshall XML back to Java
objects.
Moose uses a MarshallingContext instance when
marshalling. The MarshallingContext contains state
information that Moose uses when traversing your object graph, and also contains
the XML output. Your application will need a new
MarshallingContext whenever it needs to
marshall.
HelloWorld helloWorld = new HelloWorld();
helloWorld.setName("Moose");
MarshallingContext ctx = new MarshallingContext();
mapping.marshall(helloWorld, ctx);
String xml = ctx.getOutput().toString();
System.out.println(xml);
The listing above shows the final steps in the marshalling operation--creating
a new MarshallingContext, the marshalling invocation and
then retrieving the XML data out of the context.
Unmarshalling XML back to a graph of Java objects is just as simple as marshalling Java to XML.
HelloWorld newWorld = (HelloWorld) mapping.unmarshall(xml);
System.out.println("Name: " + newWorld.getName());
The Mapping class provides
unmarshall methods that can read from
InputStream and Reader, if those
are more useful in your application.
Moose provides a schema generator, which can create an XML schema document for any properly configured Mapping instance.
String xsd = SchemaGenerator.generate(mapping);
System.out.println("XSD:\n\n" + xsd);
The following listing shows the entire application, including the mapping creation, marshalling, unmarshalling and schema generation.
import com.quigley.moose.mapping.Mapping;
import com.quigley.moose.mapping.MarshallingContext;
import com.quigley.moose.mapping.provider.annotation.AnnotationMappingProvider;
import com.quigley.moose.mapping.provider.annotation.StaticClassesProvider;
import com.quigley.moose.schema.SchemaGenerator;
public class Application {
public static void main(String[] args) {
StaticClassesProvider classesProvider = new StaticClassesProvider();
classesProvider.addClass(HelloWorld.class);
AnnotationMappingProvider mappingProvider =
new AnnotationMappingProvider(classesProvider);
Mapping mapping = mappingProvider.getMapping();
HelloWorld helloWorld = new HelloWorld();
helloWorld.setName("Moose");
MarshallingContext ctx = new MarshallingContext();
mapping.marshall(helloWorld, ctx);
String xml = ctx.getOutput().toString();
System.out.println("XML: " + xml);
HelloWorld newWorld = (HelloWorld) mapping.unmarshall(xml);
System.out.println("Name: " + newWorld.getName());
String xsd = SchemaGenerator.generate(mapping);
System.out.println("XSD:\n\n" + xsd);
}
}
In order to use Moose you'll need to include the
quigley-moose-0.4.0.jar archive in your application's
classpath. Moose also depends on the excellent JDOM library, which will also be
required in your application's classpath. The Moose distribution includes the
version of JDOM that Moose has been tested against--this version may be older
than the "current" JDOM release (but we strive to stay current).
If you've followed along with the example, and your classpath is properly configured, the application should compile and run. A successful run will yield the output shown below.
XML: <hello-world><name>Moose</name></hello-world> Name: Moose XSD: <?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="hello-world" type="hello-world-type"/> <xs:complexType name="hello-world-type"> <xs:sequence> <xs:element name="name" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:schema>
Even though Moose is suited for most kinds of XML marshalling, it was designed to be an exceptional framework for creating web services in Java. Moose includes out of the box support for Spring Web Services.
In order to demonstrate Moose's Spring Web Services integration, this chapter will
focus on the construction of an example service, which we will cleverly refer to as
ExampleService.
This chapter doesn't try to be a comprehensive tutorial on Spring Web Services. Instead, we'll focus on explaining how we've used Spring Web Services to build our Moose ExampleService.
The Moose source distribution includes the examples in the
src/example/ directory. Inside the
src/example/ directory there is a
java/ directory containing the Java source files for the
examples. The other files contained underneath the examples tree include the
following:
deploy/log4j.propertiesdeploy/jetty-6.1.10-quigley.zip
web/web.xml
web/spring-ws-servlet.xml
![]()
The ExampleService implements a very basic service that handles orders for software licenses. The service supports two operations: "create puchase order" and "retrieve purchase order".
The service operations are implemented as a pair of Request
and Response objects.
package com.quigley.moose.example.service; import com.quigley.moose.mapping.provider.annotation.XML; import com.quigley.moose.mapping.provider.annotation.XMLField; @XML(name="create-purchase-orderRequest")public class CreatePurchaseOrderRequest { public PurchaseOrder getPurchaseOrder() { return purchaseOrder; } public void setPurchaseOrder(PurchaseOrder purchaseOrder) { this.purchaseOrder = purchaseOrder; } public String toString() { return "CreatePurchaseOrderRequest(purchaseOrder(" + purchaseOrder + "))"; } @XMLField(name="purchase-order") protected PurchaseOrder purchaseOrder; }
|
Notice the naming. We're appending |
package com.quigley.moose.example.service;
import com.quigley.moose.mapping.provider.annotation.XML;
import com.quigley.moose.mapping.provider.annotation.XMLField;
@XML(name="create-purchase-orderResponse")
public class CreatePurchaseOrderResponse {
public Identifier getIdentifier() {
return identifier;
}
public void setIdentifier(Identifier identifier) {
this.identifier = identifier;
}
public String toString() {
return "CreatePurchaseOrderResponse(identifier(" + identifier + "))";
}
@XMLField(name="identifier")
protected Identifier identifier;
}
And here are the Request and Response
classes for the "retrieve purchase order" operation.
First the Request.
package com.quigley.moose.example.service;
import com.quigley.moose.mapping.provider.annotation.XML;
import com.quigley.moose.mapping.provider.annotation.XMLField;
@XML(name="retrieve-purchase-orderRequest")
public class RetrievePurchaseOrderRequest {
public Identifier getIdentifier() {
return identifier;
}
public void setIdentifier(Identifier identifier) {
this.identifier = identifier;
}
public String toString() {
return "RetrievePurchaseOrderRequest(identifier(" + identifier + "))";
}
@XMLField(name="identifier")
protected Identifier identifier;
}
And then the Response.
package com.quigley.moose.example.service;
import com.quigley.moose.mapping.provider.annotation.XML;
import com.quigley.moose.mapping.provider.annotation.XMLField;
@XML(name="retrieve-purchase-orderResponse")
public class RetrievePurchaseOrderResponse {
public PurchaseOrder getPurchaseOrder() {
return purchaseOrder;
}
public void setPurchaseOrder(PurchaseOrder purchaseOrder) {
this.purchaseOrder = purchaseOrder;
}
public String toString() {
return "RetrievePurchaseOrderResponse(purchaseOrder(" + purchaseOrder + "))";
}
@XMLField(name="purchase-order")
protected PurchaseOrder purchaseOrder;
}
Most of the "glue" that ties the ExampleService together exists in the Spring context configuration. Let's take a look at our context file.
The first items that we need to put into our Spring context configuration is
a MooseMarshaller instance. The
MooseMarshaller is a subclass of the Spring Web
Services AbstractMarshaller.
<bean id="mooseMarshaller" class="com.quigley.moose.spring.MooseMarshaller">
<property name="mappingProvider"><ref bean="mooseMappingProvider"/></property>
</bean>
The MooseMarshaller requires that a
mappingProperty bean be injected. We'll need to define
one in our Spring context configuration.
<bean id="mooseMappingProvider" class="com.quigley.moose.mapping.provider.annotation.AnnotationMappingProvider">
<property name="xmlNamespace"><value>http://quigley.com/moose/example/service/</value></property>
<property name="xmlPrefix"><value>es</value></property>
<property name="annotatedClassesProvider"><ref bean="mooseClassesProvider"/></property>
</bean>
We'll use a standard Moose AnnotationMappingProvider.
You can see that we've configured the XML namespace and XML namespace prefix.
There is also a bean reference to an
AnnotatedClassesProvider. The
AnnotationMappingProvider can work with any
AnnotatedClassesProvider subtype.
<bean id="mooseClassesProvider" class="com.quigley.moose.mapping.provider.annotation.StaticClassesProvider">
<property name="classes">
<list>
<value>com.quigley.moose.example.service.CreatePurchaseOrderRequest</value>
<value>com.quigley.moose.example.service.CreatePurchaseOrderResponse</value>
<value>com.quigley.moose.example.service.Identifier</value>
<value>com.quigley.moose.example.service.PurchaseOrder</value>
<value>com.quigley.moose.example.service.RetrievePurchaseOrderRequest</value>
<value>com.quigley.moose.example.service.RetrievePurchaseOrderResponse</value>
</list>
</property>
</bean>
The StaticClassesProvider is a very simple
AnnotatedClassesProvider. It takes a single
classes property, which includes a list of the classes
that should be in the Mapping.
We're going to want Spring Web Services to take care of generating the WSDL
for our service. The WSDL generator requires an XML schema for the base of the
WSDL. We can configure Moose to include a SimpleXsdSchema
instance.
<bean id="mooseSchema" class="com.quigley.moose.spring.MooseXsdSchema">
<property name="mappingProvider"><ref bean="mooseMappingProvider"/></property>
</bean>
Notice that the MooseXsdSchema takes a reference to the
same mooseMappingProvider bean that we defined earlier.
Now that we've configured the basic Moose components, we can configure the Spring Web Services beans and tie our service together.
<bean id="serviceEndpoint" class="com.quigley.moose.example.service.ServiceEndpoint"/>
Much of Spring Web Services is configured by simply putting beans into the
Spring context. The example above, ServiceEndpoint, is
exposed by its presence in the context. If you look at that class, you'll notice
that the class is annotated with an Endpoint annotation.
This tells Spring Web Services that this is an endpoint class.
<bean id="service" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema"><ref bean="mooseSchema"/></property>
<property name="portTypeName"><value>ExampleService</value></property>
<property name="locationUri"><value>http://localhost:8080/example/</value></property>
</bean>
The DefaultWsdl11Definition bean exposes a WSDL for our
service. We've injected the mooseSchema bean to provide the
WSDL with the schema generated from our Moose mapping.
<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="mooseMarshaller"/>
</bean>
The GenericMarshallingMethodEndpointAdapter is a bean
which tells Spring Web Services how to marshall and unmarshall the parameters
and return values from the endpoint methods. It takes a
AbstractMarshaller as a constructor argument. We'll
supply it with our mooseMarshaller instance.
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>
The final bean tells Spring Web Services how to decide which
Endpoint method to invoke for each request. In this
case, we're going to use a
PayloadRootAnnotationMethodEndpointMapping. This bean
uses annotations on each method of the endpoint, which tie the methods to
specific XML root object names.
Here is the entire Spring context configuration.
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
<!--
Spring WS Configuration
-->
<bean id="serviceEndpoint" class="com.quigley.moose.example.service.ServiceEndpoint"/>
<bean id="service" class="org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition">
<property name="schema"><ref bean="mooseSchema"/></property>
<property name="portTypeName"><value>ExampleService</value></property>
<property name="locationUri"><value>http://localhost:8080/example/</value></property>
</bean>
<bean class="org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter">
<constructor-arg ref="mooseMarshaller"/>
</bean>
<bean class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping"/>
<!--
Moose Configuration
-->
<bean id="mooseMarshaller" class="com.quigley.moose.spring.MooseMarshaller">
<property name="mappingProvider"><ref bean="mooseMappingProvider"/></property>
</bean>
<bean id="mooseMappingProvider" class="com.quigley.moose.mapping.provider.annotation.AnnotationMappingProvider">
<property name="xmlNamespace"><value>http://quigley.com/moose/example/service/</value></property>
<property name="xmlPrefix"><value>es</value></property>
<property name="annotatedClassesProvider"><ref bean="mooseClassesProvider"/></property>
</bean>
<bean id="mooseClassesProvider" class="com.quigley.moose.mapping.provider.annotation.StaticClassesProvider">
<property name="classes">
<list>
<value>com.quigley.moose.example.service.CreatePurchaseOrderRequest</value>
<value>com.quigley.moose.example.service.CreatePurchaseOrderResponse</value>
<value>com.quigley.moose.example.service.Identifier</value>
<value>com.quigley.moose.example.service.PurchaseOrder</value>
<value>com.quigley.moose.example.service.RetrievePurchaseOrderRequest</value>
<value>com.quigley.moose.example.service.RetrievePurchaseOrderResponse</value>
</list>
</property>
</bean>
<bean id="mooseSchema" class="com.quigley.moose.spring.MooseXsdSchema">
<property name="mappingProvider"><ref bean="mooseMappingProvider"/></property>
</bean>
</beans>
An Endpoint class is the entry point into your web service.
ExampleService defines a
ServiceEndpoint class. Typically, each method of the
Endpoint corresponds a service operation name.
Here is an example operation method:
@PayloadRoot(localPart="create-purchase-orderRequest", namespace="http://quigley.com/moose/example/service/")public CreatePurchaseOrderResponse createPurchaseOrder(CreatePurchaseOrderRequest request) { log.info("Received: " + request); Identifier identifier = new Identifier(); identifier.setValue(identifierSequence++); purchaseOrderDatabase.put(identifier.getValue(), request.getPurchaseOrder()); CreatePurchaseOrderResponse response = new CreatePurchaseOrderResponse(); response.setIdentifier(identifier); return response; }
|
The |
In order to build and run the ExampleService, you'll need a working Ant installation. I'm going to assume that Ant is in your shell's path.
Extract the Moose source distribution (if you haven't already) and change into the
root folder of the distribution. The following command will build
ExampleService for you:
$ ant -f build-example.xml example-service
Buildfile: /home/michael/Repos/Moose-0.4.0/build-example.xml
compile:
[mkdir] Created dir: /home/michael/Repos/Moose-0.4.0/build/classes
[javac] Compiling 54 source files to /home/michael/Repos/Moose-0.4.0/build/classes
package:
[mkdir] Created dir: /home/michael/Repos/Moose-0.4.0/build/dist
[jar] Building jar: /home/michael/Repos/Moose-0.4.0/build/dist/quigley-moose-0.4.0.jar
example-service-compile:
[mkdir] Created dir: /home/michael/Repos/Moose-0.4.0/build/example-service/classes
[javac] /home/michael/Repos/Moose-0.4.0/build-example.xml:26: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds
[javac] Compiling 11 source files to /home/michael/Repos/Moose-0.4.0/build/example-service/classes
example-service-package:
[mkdir] Created dir: /home/michael/Repos/Moose-0.4.0/build/example-service/dist
[war] Building war: /home/michael/Repos/Moose-0.4.0/build/example-service/dist/example-service.war
example-service-deploy-scaffolding:
[mkdir] Created dir: /home/michael/Repos/Moose-0.4.0/build/example-service/deploy
[unzip] Expanding: /home/michael/Repos/Moose-0.4.0/src/example/deploy/jetty-6.1.10-quigley.zip into /home/michael/Repos/Moose-0.4.0/build/example-service/deploy
[copy] Copying 1 file to /home/michael/Repos/Moose-0.4.0/build/example-service/deploy/server/resources
example-service-deploy:
[unzip] Expanding: /home/michael/Repos/Moose-0.4.0/build/example-service/dist/example-service.war into /home/michael/Repos/Moose-0.4.0/build/example-service/deploy/server/webapps/example
example-service:
BUILD SUCCESSFUL
Total time: 4 seconds
Once you've successfully built the service, you can start it up as follows:
$ build/example-service/deploy/server/bin/run.sh [2010-01-27 15:34:54,637] <INFO> [sun.reflect.NativeMethodAccessorImpl:invoke0()] Logging to org.slf4j.impl.Log4jLoggerAdapter(org.mortbay.log) via org.mortbay.log.Slf4jLog [2010-01-27 15:34:54,728] <INFO> [sun.reflect.NativeMethodAccessorImpl:invoke0()] jetty-6.1.10 [2010-01-27 15:34:54,824] <INFO> [sun.reflect.NativeMethodAccessorImpl:invoke0()] NO JSP Support for /example, did not find org.apache.jasper.servlet.JspServlet [2010-01-27 15:35:05,560] <INFO> [sun.reflect.NativeMethodAccessorImpl:invoke0()] Initializing Spring FrameworkServlet 'spring-ws' [2010-01-27 15:35:05,561] <INFO> [org.springframework.web.servlet.FrameworkServlet:initServletBean()] FrameworkServlet 'spring-ws': initialization started [2010-01-27 15:35:05,573] <INFO> [org.springframework.context.support.AbstractApplicationContext:prepareRefresh()] Refreshing org.springframework.web.context.support.XmlWebApplicationContext@4026e9f9: display name [WebApplicationContext for namespace 'spring-ws-servlet']; startup date [Wed Jan 27 15:35:05 EST 2010]; root of context hierarchy [2010-01-27 15:35:05,613] <INFO> [org.springframework.beans.factory.xml.XmlBeanDefinitionReader:loadBeanDefinitions()] Loading XML bean definitions from ServletContext resource [/WEB-INF/spring-ws-servlet.xml] [2010-01-27 15:35:05,875] <INFO> [org.springframework.context.support.AbstractApplicationContext:obtainFreshBeanFactory()] Bean factory for application context [org.springframework.web.context.support.XmlWebApplicationContext@4026e9f9]: org.springframework.beans.factory.support.DefaultListableBeanFactory@4929b0e1 [2010-01-27 15:35:05,957] <INFO> [org.springframework.beans.factory.support.DefaultListableBeanFactory:preInstantiateSingletons()] Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@4929b0e1: defining beans [org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,serviceEndpoint,service,org.springframework.ws.server.endpoint.adapter.GenericMarshallingMethodEndpointAdapter#0,org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping#0,mooseMarshaller,mooseMappingProvider,mooseClassesProvider,mooseSchema]; root of factory hierarchy [2010-01-27 15:35:06,256] <INFO> [org.springframework.ws.soap.saaj.SaajSoapMessageFactory:afterPropertiesSet()] Creating SAAJ 1.3 MessageFactory with SOAP 1.1 Protocol [2010-01-27 15:35:06,286] <INFO> [org.springframework.web.servlet.FrameworkServlet:initServletBean()] FrameworkServlet 'spring-ws': initialization completed in 724 ms [2010-01-27 15:35:06,310] <INFO> [sun.reflect.NativeMethodAccessorImpl:invoke0()] Started SelectChannelConnector@0.0.0.0:8080
You can retrieve the WSDL of the service at the following URL:
http://localhost:8080/example/service.wsdl
Tools like SoapUI are able to parse the WSDL and allow you to make invocations to
the service. You'll notice that there are two operations availble,
create-purchase-order and
retrieve-purchase-order.
We'll invoke the create-purchase-order operation with the
following message:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://quigley.com/moose/example/service/">
<soapenv:Header/>
<soapenv:Body>
<ser:create-purchase-orderRequest>
<ser:purchase-order>
<ser:licensee>Michael Quigley</ser:licensee>
<ser:license-type>New</ser:license-type>
<ser:amount>9999.99</ser:amount>
</ser:purchase-order>
</ser:create-purchase-orderRequest>
</soapenv:Body>
</soapenv:Envelope>
If we send that message to the service, it will respond with the following response:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<es:create-purchase-orderResponse xmlns:es="http://quigley.com/moose/example/service/">
<es:identifier>
<es:value>100</es:value>
</es:identifier>
</es:create-purchase-orderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
If you notice, inside of the SOAP body, there is an XML payload that corresponds
to the CreatePurchaseOrderRequest and
CreatePurchaseOrderResponse objects. Moose is doing all of
the XML to Java to XML marshalling in this scenario.
To complete the example, you can retrieve the purchase order you just created with the following request:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://quigley.com/moose/example/service/">
<soapenv:Header/>
<soapenv:Body>
<ser:retrieve-purchase-orderRequest>
<ser:identifier>
<ser:value>100</ser:value>
</ser:identifier>
</ser:retrieve-purchase-orderRequest>
</soapenv:Body>
</soapenv:Envelope>
And the service will respond by marshalling the requested purchase order back out:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<es:retrieve-purchase-orderResponse xmlns:es="http://quigley.com/moose/example/service/">
<es:purchase-order>
<es:licensee>Michael Quigley</es:licensee>
<es:license-type>New</es:license-type>
<es:amount>9999.99</es:amount>
</es:purchase-order>
</es:retrieve-purchase-orderResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
This chapter will outline the types of mappings that are possible with Moose.
We refer to the collection of Moose-annotated Java objects as a "mapping domain". Your application may consist of multiple different types of "domain objects". Some objects might comprise your model or view components. Other components might encapsulate groups of logic or algorithms. A Moose mapping exists to give you a set of objects that allow you to get XML data into and out of your application.
Let's consider a scenario where the desired XML schema differs significantly from the structure of an existing collection of Java objects. Instead of trying to coerce Moose into dealing with the impedance mismatch of the two different data structures, it would be better to create a set of Java domain objects which closely mimic the desired XML structure and use Java code to handle the necessary transformations.
Classes in the mapping domain are usually simple Java beans. Each class must have a setter and getter method for each mapped field, and the class also must contain a no-argument constructor.
The following annotations are used to configure the mapping behavior of your mapping domain classes.
The @XML annotation is used to define the class-level
mapping configuration:
@XML(name="example")
public class Example {
...
}
The @XML annotation accepts the following parameters:
Table 4.1. @XML Annotation Parameters
| Attribute Name | Description |
|---|---|
name |
Sets the XML name of the annotated class. |
The XML structure defined by the example above has the form:
<example>...</example>
The @XMLArray annotation is used to define the
mapping configuration for array fields:
@XML(name="example")
public class Example {
...
@XMLArray(name="values", elementName="value")
private Long[] values;
}
The @XMLArray annotation accepts the following parameters:
Table 4.2. @XMLArray Annotation Parameters
| Attribute Name | Description |
|---|---|
name |
Sets the XML name of the annotated field. |
elementName |
Sets the XML name of the elements of the array. |
The XML structure defined by the example above has the form:
<example><values><value/>...</values></example>
Be sure to see XML Name Usage Scenarios for
clarification on how XML names are used in @XMLArray fields.
The @XMLCustomField annotation is used to define
fields that are processed by custom application code.
The @XMLField annotation is used to define the
mapping configuration for primitive fields, fields of Java types, or fields
of other types contained in the mapping:
The @XMLField annotation accepts the following parameters:
Table 4.3. @XMLField Annotation Parameters
| Attribute Name | Description |
|---|---|
name |
Sets the XML name of the annotated field. |
style |
Accepts an enumeration value from
|
@XML(name="example")
public class Example {
...
@XMLField(name="title")
private String title;
}
The XML structure defined by the example above has the form:
<example><title/></example>
By switching the style parameter to XMLFieldStyle.Attribute,
like so:
@XML(name="example")
public class Example {
...
@XMLField(name="title", style=XMLFieldStyle.Attribute)
private String title;
}
The following XML will be generated:
<example title=""/>
It is important to understand how XML names are used in various scenarios. In a simple
case like the example for @XMLField, the title field
refers to a non-Moose-annotated type; a type not present in the mapping. So, in that case it's
simple to understand that the title field will be mapped to the
title XML name.
Here's a more complicated scenario:
@XML(name="a")
public class A {
@XMLField(name="title")
private String title;
}
@XML(name="b")
public class B {
@XMLField(name="a-field")
private A aField;
}
When a B instance is marshalled out to XML, the output will look like
the following:
<b><a-field><title>The Title</title></a-field></b>
Notice that <a-field/> contains the output of the A
field, but that it doesn't use the XML name provided in the @XML annotation
for class A. The name provided in the @XML
annotation for a mapped class is only used when that class is the root of an object graph or XML
input. Otherwise, the XML names provided in the annotations "override" any names specified in an
@XML annotation.
This behavioral pattern is followed throughout Moose.
The @XMLList annotation is used to define the mapping
configuration for java.util.List fields.
The @XMLList annotation accepts the following parameters:
Table 4.4. @XMLList Annotation Parameters
| Attribute Name | Description |
|---|---|
name |
Sets the XML name of the annotated field. |
elementName |
Sets the XML name of the elements of the array. This is not used with inline style lists. |
style |
Lists can be "rooted", where the elements of the list are contained within an element just for that purpose, or "inline", where the elements of the list are represented inline with the other fields of the type.
The |
@XML(name="list-example")
public class Example {
@XMLList(name="strings", elementName="string")
private List<String> strings;
}
The XML structure defined by the example above has the form:
<list-example><strings><string/><string/>...</strings></list-example>
An inline-style list would be configured as follows:
@XML(name="list-example")
public class Example {
@XMLList(name="string", style=XMLListStyle.INLINE)
private List<String> strings;
}
This would generate XML like:
<list-example><string/><string/>...</list-example>
Be sure to see XML Name Usage Scenarios for
clarification on how XML names are used in @XMLList fields.
As of version 0.4.0, Moose requires that your java.util.List fields include a
parameterized type.
The @XMLMap annotation is used to define the mapping
configuration for java.util.Map fields.
@XML(name="map-example")
public class Example {
@XMLMap(name="dictionary", pairName="entry", keyName="key", valueName="value")
private Map<String, String> dictionary;
}
The @XMLMap annotation accepts the following parameters:
Table 4.5. @XMLMap Annotation Parameters
| Attribute Name | Description |
|---|---|
name |
Sets the XML name of the annotated field. |
pairName |
Sets the XML name of each pair of values in the |
keyName |
Sets the XML name of the key value in the pair. |
valueName |
Sets the XML name of the value in the pair. |
The XML structure defined by the example above has the form:
<map-example><dictionary><entry><key/><value/></entry>...</dictionary></map-example>
Be sure to see XML Name Usage Scenarios for
clarification on how XML names are used in both key and value objects of @XMLMap fields.
As of version 0.4.0, Moose requires that your java.util.Map fields include
parameterized types.