Apache CXF 2.3: The Implementation of SOAP Over JMS Specification
Join the DZone community and get the full member experience.
Join For FreeIf you are working on building mission critical Web services and cannot afford any lost messages , leveraging the reliable messaging transport feature of the Java Message Services(JMS) may be a viable option. This approach allows you to deploy critical Web services without requiring an additional, often complex reliable message interaction mechanism be developed.
This article was authored by Willem Jiang, a FuseSource developer and Apache CXF contributor.
In fact, the recent released SOAP/JMS Specification is designed expressly to allow different Web services vendors’ SOAP/JMS applications to easily communicate and the recent released CXF 2.3 provides a full implementation of this important SOAP/JMS specification.
This article will give an overview of how developers can use Apache CXF 2.3, or Fuse Services Framework, to build and deploy reliable Web services using SOAP over JMS. Fuse Services Framework is a productized and fully supported distribution of Apache CXF.
SOAP over JMS specification
The SOAP over JMS specification defines how SOAP binds to a messaging system that supports JMS. Binding is specified for both SOAP 1.1 and SOAP 1.2 using the same SOAP 1.2 Protocol Binding Framework used for SOAP/HTTP. It’s important to remember that this specification doesn’t define any wire format for the SOAP/JMS message. It only specifies how to define the connection to a destination, for example the JMS message headers and properties which will be used for the SOAP binding based on the JMS API.
Also, it is important to remember in this process is that if you want to connect to a JMS Destination, you need to use JNDI for JMS administered objects (such as the ConnectionFactory, Destinations and timeToLive properties for a MessageProducer).. In the SOAP/JMS specification, there are three kinds of properties which will be used in the SOAP over JMS binding.
Connecting a destination:
Name |
Description |
lookupVarant |
Specifies the technique to use for looking up the given Destination name. |
destinationName |
Specifies the name of a Destination, for lookup as per the lookupVariant. If the variant is “jndi”, this is the Java Naming and Directory Interface (JNDI) name of the destination. |
jndiConnectionFactoryName |
Specifies the JNDI name of the connection factory. |
jndiInitialContextFactory |
Specifies the fully qualified Java class name of the InitialContextFactory to use. |
jndiURL |
Specifies the JNDI provider URL. |
jndiContextParameter |
Provides mechanism to set additional, arbitrary JNDI environment properties, other than jndiURL and jndiInitialContextFactory. |
SOAP/JMS also defines several properties for the JMS message header. For example:
Name |
Description |
deliveryMode |
Indicates whether the requested message is PERSISTENT or NON_PERSISTENT |
timeToLive |
The lifetime, in milliseconds, of the request message. |
priority |
The JMS priority associated with the request message. |
replyToName |
Specifies the name of the destination to which a response message will be sent. |
topicReplyToName |
Specifies the name of the topic destination to which a response message will be sent. |
And JMS message properties:
Name |
Description |
targetService |
Used by the service implementation to dispatch the service request. |
bindingVersion |
Specifies the version of SOAP/JMS binding that is being used. |
contentType |
Describes the content of the SOAP message. |
soapAction |
As with SOAP/HTTP |
isFault |
This property indicates whether a SOAP/JMS message corresponds a SOAP fault. |
requestURI |
Specifies the JMS URI of the service. |
The last five JMS message properties (bindingVersion, contentType, soapAction, isFault, requestURI) are used at the runtime of SOAP/JMS, allowing you to specify the other properties in the WSDL, as expressed in the below code example:
<wsdl:binding name="JMSGreeterPortBinding" type="tns:JMSGreeterPortType"> <soap:binding style="document" transport="http://www.w3.org/2010/soapjms/" /> <!-- You can specify the soap/jms binding properties in binding --> <soapjms:deliveryMode>NON_PERSISTENT</soapjms:deliveryMode> <wsdl:operation name="greetMe"> <soap:operation soapAction="test" style="document" /> <wsdl:input name="greetMeRequest"> <soap:body use="literal" /> </wsdl:input> <wsdl:output name="greetMeResponse"> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> ... </wsdl:binding> <wsdl:service name="JMSGreeterService"> <!-- You can specify the soap/jms binding properties in the service --> <soapjms:jndiConnectionFactoryName>ConnectionFactory</soapjms:jndiConnectionFactoryName> <soapjms:jndiInitialContextFactory> org.apache.activemq.jndi.ActiveMQInitialContextFactory </soapjms:jndiInitialContextFactory> <soapjms:jndiURL>tcp://localhost:61616</soapjms:jndiURL> <soapjms:priority>6</soapjms:priority> <soapjms:timeToLive>2000</soapjms:timeToLive> <wsdl:port binding="tns:JMSGreeterPortBinding" name="GreeterPort"> <!-- You can specify the soap/jms binding properties in port --> <soapjms:replyToName>dynamicQueues/soap.jms.example.replyQueue</soapjms:replyToName--> <soapjms:deliveryMode>PERSISTENT</soapjms:deliveryMode> <soapjms:priority>4</soapjms:priority> <soapjms:timeToLive>30000</soapjms:timeToLive> <soap:address location="jms:jndi:dynamicQueues/soap.jms.example"/> </wsdl:port> </wsdl:service>
The SOAP/JMS specification allows you to specify the various JMS properties used in the WSDL binding , service and port. You can also specify these properties by using the JMS Uniform Resource Identifier(URI) for the port. Values specified at the service will propagate to all ports while all values specified at the binding will propagate to all ports using that binding. For example, the jndiInitialContextFactory can be indicated for WSDL service, and in turn, it is then implied for all of the contained WSDL port elements.
If a property is specified at multiple levels within the WSDL document, the most specific setting must take precedence. For example, URI specified in the address element’s location attribute first, and then other properties set on the port, then service, then binding.
Before the introduction of Apache CXF 2.3, if you wanted to define a JMS endpoint address information for a service you could only specify the address in WSDL port as the Apache CXF JMS transport extension as indicated below:
<wsdl:service name="JMSGreeterService"> <wsdl:port binding="tns:JMSGreeterPortBinding" name="GreeterPort"> <jms:address destinationStyle="queue" jndiConnectionFactoryName="ConnectionFactory" jndiDestinationName="dynamicQueues/test.cxf.jmstransport.queue"> <jms:JMSNamingProperty name="java.naming.factory.initial" value="org.apache.activemq.jndi.ActiveMQInitialContextFactory"/> <jms:JMSNamingProperty name="java.naming.provider.url" value="tcp://localhost:61616"/> </jms:address> <jms:clientConfig useConduitIdSelector="false"/> </wsdl:port> </wsdl:service>
JMS URI
When compared to the old JMS transport implementation of Apache CXF, the SOAP/JMS specification provides a significantly more flexible way to configure the JMS endpoint address, and the JMS URI for the port makes both publishing and consuming the SOAP/JMS service easier.
As with the previously described URI, the JMS URI start with “jms:” and is followed by the jms-variant. and jms-dest. You can specify other properties as query parameter.
jms-uri = "jms:" jms-variant ":" jms-dest [ "?" param *( "&" param ) ]
Specification Properties and URI Representations:
Specification Property |
URI Representation |
deliveryMode |
as deliveryMode query parameter |
destinationName |
as jms-dest portion of URI syntax |
lookupVariant |
as jms-variant portion of the syntax |
jndiConnectionFactoryName |
as jndiConnectionFactoryName query parameter |
jndiInitialContextFactory |
as jndiInitialContextFactory query parameter |
jndiURL |
as jndiURL querey parameter |
jndiContextParameter |
as a query parameter combining the string “jndi” with the jndiContextParameter’s name attribute |
replyToName |
as replyToName query parameter |
topicReplyToName |
as topicReplyToName query parameter |
priority |
as priority query parameter |
targetSerice |
as targetService query parameter |
timeToLive |
as timeToLive query parameter |
Prior to the release of Apache CXF 2.3, it was impossible for you to start the Apache CXF server by calling the JAXWS API with a single line of address without WSDL such as the http transport does.
Object implementor = new GreeterJMSImpl();
String address = "http://localhost:9000/cxfservice";
Endpoint.publish(address, implementor);
With the help of JMS URI, using Apache CXF 2.3 you can set the JMS endpoint address with a single line of JMS URI:
Object implementor = new GreeterJMSImpl();
String address = "jms:jndi:dynamicalQueue/example.soap.jms.queue?"
+ "jndiConnectionFactoryName=ConnectionFactory"
+ "&jndiInitialContextFactory=org.apache.activemq.jndi.ActiveMQInitialContextFactory";
Endpoint.publish(address, implementor);
Example
Now that we’ve covered some of the basics for reliable service delivery using SOAP/JMS in Apache CXF 2.3, the following example illustrates a different way to provide or consume the Web service with or without WSDL by using the Apache CXF API or JAXWS API.
You can find the WSDL in the $EXAMPLE/wsdl directory, and we used the Apache CXF wsdl to java to generate the Service Endpoint Interface (SEI), you can find the generated file in the $EXAMPLE/target/generated directory after running the “mvn clean install” from the root.
If you want to run the example, you need to start the JMS broker first. Here we use ActiveMQ broker service to create the broker as so:
BrokerService broker = new BrokerService();
broker.setDataDirectory("target/activemq-data");
broker.addConnector("tcp://localhost:61616");
broker.start();
System.out.println("JMS broker ready ...");
Thread.sleep(125 * 60 * 1000);
System.out.println("JMS broker exiting");
broker.stop();
System.exit(0);
You can use mvn -Pjms.broker to start the JMS broker.
Start the server by using “mvn -Pserver” in a new console, and start the client by using “mvn -Pclient” in another console. The server and client are created by using JAXWS API with WSDL.
Server:
// There is a wsdlLocation annotation attribute in the GreeterJMSImplWithWSDL.class
Object implementor = new GreeterJMSImplWithWSDL();
String address = "jms:jndi:dynamicQueues/soap.jms.example";
Endpoint.publish(address, implementor);
Client:
File wsdl = new File("./wsdl/jms_greeter.wsdl");
JMSGreeterService service = new JMSGreeterService(wsdl.toURI().toURL(), SERVICE_NAME);
JMSGreeterPortType client = (JMSGreeterPortType)service.getPort(PORT_NAME, JMSGreeterPortType.class);
return client;
Apache CXF 2.3 will build ServiceModel from the WSDL or the Java code. If you are using WSDL it should be relatively easy to determine if you are using the SOAP/JMS binding or not.
But what if you are using the code first developing model without WSDL?
In that situation, you can specify the transportID with “http://www.w3.org/2010/soapjms/” to make sure Apache CXF 2.3 uses the SOAP/JMS implementation if there is no WSDL information in your SEI when you use the Apache CXF API.
Server:
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
Object implementor = new GreeterJMSImplWithWSDL();
factory.setServiceBean(implementor);
factory.setTransportId(JMSSpecConstants.SOAP_JMS_SPECIFICATION_TRANSPORTID);
factory.setAddress(JMS_ENDPOINT_URI);
factory.create();
Client:
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setTransportId(JMSSpecConstants.SOAP_JMS_SPECIFICATION_TRANSPORTID);
factory.setAddress(JMS_ENDPOINT_URI);
JMSGreeterPortType client = factory.create(JMSGreeterPortType.class);
If you are using the JAXWS API, CXF will chose the SOAP/JMS implementation if the address is using the JMS URI, but be sure to specify the transport id when you creating the port.
Server
Object implementor = new GreeterJMSImplWithoutWSDL();
String address = "jms:jndi:dynamicQueues/soap.jms.example";
Endpoint.publish(address, implementor);
Client
Service service = Service.create(SERVICE_NAME);
// Add a port to the Service with the SOAP JMS transport ID
service.addPort(PORT_NAME, JMSSpecConstants.SOAP_JMS_SPECIFICATION_TRANSPORTID, JMS_ENDPOINT_URI);
JMSGreeterPortType client = service.getPort(PORT_NAME, JMSGreeterPortType.class);
If you want to try out this out, you need to use the “mvn -Pserver ” or “mvn -Pclient” with the profile “-Pcxf” (using CXF API without WSDL) or “-Pjaxws” (using JAXWS API without WSDL), eg “mvn -Pserver -Pcxf” or “mvn -Pclient -Pjaxws”.
I work for FuseSource, a company that offers subscriptions and professional services for Apache CXF.
For the examples in this article I am using Fuse Services Framework, a productized and supported distribution of Apache CXF.
There are some great CXF training videos
Free download of CXF
If you have any questions about this article, feel free to post them in the CXF forum
My twitter: willemjiang
My blog
Opinions expressed by DZone contributors are their own.
Comments