Moose Developer Guide

Michael Quigley

$Revision: 1207 $ $Date: 2010-04-23 16:56:20 -0400 (Fri, 23 Apr 2010) $


1. Introduction
2. Hello World
2.1. Create the Mapping Domain Classes
2.2. The Mapping Application
2.2.1. Configuring the Mapping
2.2.2. Marshalling Java to XML
2.2.3. Unmarshalling XML to Java
2.2.4. XML Schema Generation
2.2.5. Putting It All Together
2.3. Running the Application
2.3.1. The Application Classpath
2.3.2. The Generated Output
3. ExampleService
3.1. Finding the ExampleService Sources
3.2. The ExampleService Domain Classes
3.3. The Spring Configuration
3.3.1. Configuring the Moose Components
3.3.2. Configuring Spring Web Services
3.3.3. Putting It All Together
3.4. The Endpoint
3.5. Building and Running ExampleService
4. Designing Moose Mappings
4.1. Mapping Concepts
4.1.1. Mapping Domains
4.1.2. Mapping Annotations

Chapter 1. Introduction

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".

Chapter 2. Hello World

In order to get started with Moose, we'll create a classic "hello world" example.

2.1. Create the Mapping Domain Classes

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") 1
public class HelloWorld {
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	@XMLField(name="name") 2
	protected String name;
}                
                

1

The @XML annotation configures this class to be included as part of a Moose mapping. The name attribute defines what this class will be named when marshalled to XML.

2

The @XMLField annotation tells Moose how to marshall the name field of our class. Here we're telling Moose to name the output of this field "name".

The annotations let us keep our mapping information in-line with the other components of our mapping domain classes.

2.2. The Mapping Application

Now that we've created an annotated domain class, we'll need to get the rest of the framework configured.

2.2.1. Configuring the Mapping

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.

2.2.2. Marshalling Java to XML

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.

2.2.3. Unmarshalling XML to Java

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.

2.2.4. XML Schema Generation

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);

2.2.5. Putting It All Together

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);		
	}
}

2.3. Running the Application

2.3.1. The Application Classpath

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).

2.3.2. The Generated Output

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>

Chapter 3. ExampleService

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.

3.1. Finding the ExampleService Sources

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.properties 1
deploy/jetty-6.1.10-quigley.zip 2
web/web.xml 3
web/spring-ws-servlet.xml 4
                

1

LOG4J configuration file for debugging

2

A stripped-down Jetty installation useful for web services development

3

The web.xml for the example service

4

The Spring context definition for the example service

3.2. The ExampleService Domain Classes

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") 1
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;
}
                

1

Notice the naming. We're appending Request to the name of this class in that specific naming format. Spring Web Services looks for Request and Response names in the XSD when doing WSDL generation.

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;
}
                

3.3. The Spring Configuration

Most of the "glue" that ties the ExampleService together exists in the Spring context configuration. Let's take a look at our context file.

3.3.1. Configuring the Moose Components

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.

3.3.2. Configuring Spring Web Services

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.

3.3.3. Putting It All Together

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>
                    

3.4. The Endpoint

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/") 1
	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;
	}
            

1

The PayloadRoot annotation tells Spring Web Services how to match incoming XML requests to the methods in your Endpoint. In this example, when the Spring servlet sees an XML element named create-purchase-orderRequest as the root of the payload (matching the namespace, also defined in the annotation), it will invoke this method. In the process, Spring will also use the GenericMarshallingEndpointAdapter (which in our case, uses Moose) to marshall the request into the CreatePurchaseOrderRequest instance needed by the method parameter. When the method returns the CreatePurchaseOrderResponse instance, Spring will again use the GenericMarshallingEndpointAdapter to marshall that object out to XML.

3.5. Building and Running ExampleService

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>

Chapter 4. Designing Moose Mappings

This chapter will outline the types of mappings that are possible with Moose.

4.1. Mapping Concepts

4.1.1. Mapping Domains

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.

4.1.2. Mapping Annotations

The following annotations are used to configure the mapping behavior of your mapping domain classes.

4.1.2.1. @XML

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 NameDescription
name

Sets the XML name of the annotated class.


The XML structure defined by the example above has the form:

<example>...</example>

4.1.2.2. @XMLArray

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 NameDescription
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.

4.1.2.3. @XMLCustomField

The @XMLCustomField annotation is used to define fields that are processed by custom application code.

4.1.2.4. @XMLField

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 NameDescription
name

Sets the XML name of the annotated field.

style

Accepts an enumeration value from com.quigley.moose.mapping.provider.annotation. Possible values include Node and Attribute.

Node style fields are contained within an element. This is the default behavior.

Attribute style fields are represented as an attribute of the containing element.


@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=""/>
4.1.2.4.1. XML Name Usage Scenarios

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.

4.1.2.5. @XMLList

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 NameDescription
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 style parameter takes values from the XMLListStyle enumeration. The default value is ROOTED. Set this to INLINE for inline-style lists.


@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.

4.1.2.6. @XMLMap

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 NameDescription
name

Sets the XML name of the annotated field.

pairName

Sets the XML name of each pair of values in the Map.

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.