Статьи

Apache CXF 2.3: реализация спецификации SOAP поверх JMS

Если вы работаете над созданием критически важных веб-сервисов и не можете позволить себе потерянные сообщения, использование надежной транспортной функции обмена сообщениями Java Message Services (JMS) может быть приемлемым вариантом.   Этот подход позволяет развертывать критически важные веб-службы без необходимости разработки дополнительного, часто сложного, надежного механизма взаимодействия с сообщениями.

Автором этой статьи является Виллем Цзян , разработчик FuseSource и участник Apache CXF.

 

Фактически, недавно выпущенная спецификация SOAP / JMS разработана специально для того, чтобы позволить приложениям SOAP / JMS различных поставщиков веб-сервисов легко обмениваться данными, а недавно выпущенный CXF 2.3 обеспечивает полную реализацию этой важной спецификации SOAP / JMS.

 

В этой статье дается обзор того, как разработчики могут использовать Apache CXF 2.3 или Fuse Services Framework для создания и развертывания надежных веб-сервисов с использованием SOAP поверх JMS. Fuse Services Framework — это производимый и полностью поддерживаемый дистрибутив Apache CXF.

 

Спецификация SOAP over JMS

 

Спецификация SOAP over JMS определяет, как SOAP связывается с системой обмена сообщениями, поддерживающей JMS. Связывание указывается как для SOAP 1.1, так и для SOAP 1.2 с использованием той же платформы привязки протокола SOAP 1.2, которая используется для SOAP / HTTP. Важно помнить, что эта спецификация не определяет никакого проводного формата для сообщения SOAP / JMS. Он только определяет, как определить соединение с пунктом назначения, например, заголовки сообщений JMS и свойства, которые будут использоваться для привязки SOAP на основе JMS API.

 

Кроме того, важно помнить, что в этом процессе, если вы хотите подключиться к месту назначения JMS, вам нужно использовать JNDI для управляемых объектов JMS (таких как свойства ConnectionFactory, Destination и timeToLive для MessageProducer). В SOAP / Спецификация JMS, есть три вида свойств, которые будут использоваться в привязке SOAP поверх JMS.

 

Подключение пункта назначения:

 

имя

Описание

lookupVarant

Определяет методику, используемую для поиска заданного имени пункта назначения.

destinationName

Указывает имя пункта назначения для поиска в соответствии с lookupVariant. Если вариант «jndi», это имя назначения интерфейса Java Naming and Directory Interface (JNDI).

jndiConnectionFactoryName

Указывает имя JNDI фабрики соединений.

jndiInitialContextFactory

Задает полное имя класса Java для использования InitialContextFactory.

jndiURL

Указывает URL-адрес поставщика JNDI.

jndiContextParameter

Предоставляет механизм для установки дополнительных, произвольных свойств среды JNDI, отличных от jndiURL и jndiInitialContextFactory.

 

SOAP / JMS также определяет несколько свойств для заголовка сообщения JMS.   Например:

 

имя

Описание

DeliveryMode

Указывает, является ли запрошенное сообщение PERSISTENT или NON_PERSISTENT

время жить

Время жизни в миллисекундах сообщения запроса.

приоритет

Приоритет JMS, связанный с сообщением запроса.

replyToName

Указывает имя получателя, которому будет отправлено ответное сообщение.

topicReplyToName

Указывает имя целевой темы, на которую будет отправлено ответное сообщение.

 

И свойства сообщения JMS:

 

имя

Описание

targetService

Используется реализацией сервиса для отправки запроса сервиса.

bindingVersion

Указывает версию используемой привязки SOAP / JMS.

Тип содержимого

Описывает содержимое сообщения SOAP.

SOAPAction

Как с SOAP / HTTP

isFault

Это свойство указывает, соответствует ли сообщение SOAP / JMS ошибке SOAP.

requestURI

Определяет JMS URI службы.

 

Последние пять свойств сообщения JMS (bindingVersion, contentType, soapAction, isFault, requestURI) используются во время выполнения SOAP / JMS, что позволяет вам указать другие свойства в WSDL, как показано в следующем примере кода:

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

Спецификация SOAP / JMS позволяет указывать различные свойства JMS, используемые в привязке WSDL, сервисе и порте. Вы также можете указать эти свойства, используя унифицированный идентификатор ресурса (URI) JMS для порта. Значения, указанные в службе, будут распространяться на все порты, а все значения, указанные в привязке, будут распространяться на все порты, использующие эту привязку. Например, jndiInitialContextFactory может быть указан для службы WSDL и, в свою очередь, затем подразумевается для всех содержащихся в нем элементов порта WSDL.

Если свойство указано на нескольких уровнях в документе WSDL, наиболее конкретный параметр должен иметь приоритет. Например, URI сначала указывается в атрибуте местоположения элемента адреса, а затем в порт устанавливаются другие свойства, затем служба, затем привязка. 

Перед введением Apache CXF 2.3, если вы хотите определить информацию об адресе конечной точки JMS для службы, вы можете указать адрес только в порту WSDL в качестве транспортного расширения JMS Apache CXF, как указано ниже:

<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

По сравнению со старой транспортной реализацией JMS Apache CXF спецификация SOAP / JMS предоставляет значительно более гибкий способ настройки адреса конечной точки JMS, а URI JMS для порта упрощает публикацию и использование службы SOAP / JMS.

Как и в случае с ранее описанным URI, URI JMS начинается с «jms:» и сопровождается вариантом jms. и JMS-Dest. Вы можете указать другие свойства в качестве параметра запроса.

jms-uri = "jms:" jms-variant ":" jms-dest [ "?" param *( "&" param ) ]

Свойства спецификации и представления URI: 


Свойство спецификации

URI Представление

DeliveryMode

как параметр запроса deliveryMode

destinationName

как часть jms-dest синтаксиса URI

lookupVariant

как jms-вариант части синтаксиса

jndiConnectionFactoryName

как параметр запроса jndiConnectionFactoryName

jndiInitialContextFactory

как параметр запроса jndiInitialContextFactory

jndiURL

в качестве параметра jndiURL querey

jndiContextParameter

в качестве параметра запроса, объединяющего строку «jndi» с атрибутом имени jndiContextParameter

replyToName

как параметр запроса replyToName

topicReplyToName

как параметр запроса topicReplyToName

приоритет

как приоритетный параметр запроса

targetSerice

в качестве параметра запроса targetService

время жить

как параметр запроса timeToLive

 

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