четверг, марта 29, 2012

Реализация Webservices с использованием Apache Camel и CXF


В этой статье рассмотрим как можно использовать вебсервисы cxf с использованием Apache Camel в качестве транспорта.
Наш роутер будет работать с сообщением как с JAXB Объктом.
Первым делом создадим Maven-проект на основе camel-archetype-java. У вас должен быть установлен соответствующий плагин для Eclipse

Зададим необходимые значения Group Id и Artifact ID

В результате у нас будет сгенерирована следующая структура проекта:

В качестве контейнера для вебсервисов, будем использовать Jetty.
Добавляем все необходимые зависимости.
А именно:

camel-cxf
camel-jetty
camel-jaxb
cxf-rt-transports-http-jetty

Вот так вот выглядит часть pom.xml моего проекта с добавлеными зависимостями:
<dependency>
 <groupId>org.apache.camel</groupId>
 <artifactId>camel-cxf</artifactId>
 <version>2.9.1</version>
</dependency>
<dependency>
 <groupId>org.apache.camel</groupId>
 <artifactId>camel-jetty</artifactId>
 <version>2.9.1</version>
</dependency>
<dependency>
 <groupId>org.apache.camel</groupId>
 <artifactId>camel-jaxb</artifactId>
 <version>2.9.1</version>
</dependency>
<dependency>
 <groupId>org.apache.cxf</groupId>
 <artifactId>cxf-rt-transports-http-jetty</artifactId>
 <version>2.5.1</version>
</dependency>
Проверяем что проект собирается: pom.xml/Run As/Maven build

Maven скачает необходимые зависимости и выведет следующее:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Создадим простую wsdl для нашего веб-сервиса и разместим ее в нашем проекте
simplews\src\main\resources\wsdl\simplews.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
 xmlns:tns="http://com.bssys/simplews/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
 xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="simplews"
 targetNamespace="http://com.bssys/simplews/">
 <wsdl:types>
  <xsd:schema targetNamespace="http://com.bssys/simplews/">
   <xsd:element name="UserOperation" type="tns:RequestBO" />
   <xsd:element name="UserOperationResponse" type="tns:ResponseBO" />
   <xsd:complexType name="RequestBO">
    <xsd:sequence>
     <xsd:element name="user" type="xsd:string" />
    </xsd:sequence>
   </xsd:complexType>
   <xsd:complexType name="ResponseBO">
    <xsd:sequence>
     <xsd:element name="account" type="xsd:string"
      minOccurs="1" maxOccurs="unbounded" />
    </xsd:sequence>
   </xsd:complexType>
  </xsd:schema>
 </wsdl:types>
 <wsdl:message name="UserOperationRequest">
  <wsdl:part element="tns:UserOperation" name="parameters" />
 </wsdl:message>
 <wsdl:message name="UserOperationResponse">
  <wsdl:part element="tns:UserOperationResponse" name="parameters" />
 </wsdl:message>
 <wsdl:portType name="simplews">
  <wsdl:operation name="UserOperation">
   <wsdl:input message="tns:UserOperationRequest" />
   <wsdl:output message="tns:UserOperationResponse" />
  </wsdl:operation>
 </wsdl:portType>
 <wsdl:binding name="simplewsSOAP" type="tns:simplews">
  <soap:binding style="document"
   transport="http://schemas.xmlsoap.org/soap/http" />
  <wsdl:operation name="UserOperation">
   <soap:operation soapAction="http://com.bssys/simplews/UserOperation" />
   <wsdl:input>
    <soap:body use="literal" />
   </wsdl:input>
   <wsdl:output>
    <soap:body use="literal" />
   </wsdl:output>
  </wsdl:operation>
 </wsdl:binding>
 <wsdl:service name="simplews">
  <wsdl:port binding="tns:simplewsSOAP" name="simplewsSOAP">
   <soap:address location="http://www.example.org/" />
  </wsdl:port>
 </wsdl:service>
</wsdl:definitions>
Интерфейс простой. На входе имя пользователя, на выходе список его неких аккаунтов.
RequestBO это запрос
ResponseBO это ответ

Этого уже достаточно чтобы реализовать вебсервис. Генерации классов не требуется. Для разработки логики на Camel будет необходимо вручную работать с SOAP пакетом.
Чуть позже будем использовать jaxb чтобы избавиться от рутинной работы с xml и сконцентрироваться на бизнес-логике.

Заглянем в spring контекст, сгенерированый по умолчанию
scr/main/java/META-INF/spring/camel-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor 
 license agreements. See the NOTICE file distributed with this work for additional 
 information regarding copyright ownership. The ASF licenses this file to 
 You under the Apache License, Version 2.0 (the "License"); you may not use 
 this file except in compliance with the License. You may obtain a copy of 
 the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required 
 by applicable law or agreed to in writing, software distributed under the 
 License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 
 OF ANY KIND, either express or implied. See the License for the specific 
 language governing permissions and limitations under the License. -->

<!-- Configures the Camel Context -->

<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

 <camelContext xmlns="http://camel.apache.org/schema/spring">
  <package>com.bssys.simplews</package>
 </camelContext>

</beans>
Добавляем необходимые namespaсe для cxf и прописываем cxf-bean
Вот что у нас получилось:
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://camel.apache.org/schema/cxf"
 xsi:schemaLocation="
       http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

 <cxf:cxfEndpoint id="SimpleWSEndpoint" address="http://0.0.0.0:8083/SimpleWS"
  xmlns:tns="http://www.bssys.org/simplews/" wsdlURL="/wsdl/simplews.wsdl" />
  
 <bean id="SimpleLogic" class="com.bssys.simplews.SimpleLogicProcess"/>

 <camelContext xmlns="http://camel.apache.org/schema/spring">
  <package>com.bssys.simplews</package>
 </camelContext>

</beans>
Мы добавили namespace
xmlns:cxf="http://camel.apache.org/schema/cxf"
schemaLocation
http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd
и собственно cxfEndpoint
<cxf:cxfEndpoint id="SimpleWSEndpoint" address="http://0.0.0.0:9093/SimpleWS"
  xmlns:tns="http://com.bssys/simplews/" wsdlURL="/wsdl/simplews.wsdl" />
id- это имя конечной точки, которое будет использоваться в Camel route
address- адрес, по которому будет подниматься веб-сервис
xmlns:tns="http://www.bssys.org/simplews/" - 'это namespace нашей wsdl
wsdlURL=локальный путь к wsdl

Теперь откроем MyRouteBuilder.java, который также был сгенерирован при создании проекта, удалим сгенерированый роутинг и напишем следующее

from("cxf:bean:SimpleWSEndpoint?dataFormat=PAYLOAD").log("${body}");

Следует обратить внимание на параметер dataFormat=PAYLOAD, который задает тип работы endpoint. Почитать об этом можно в документации

Теперь проверим Запускаем и проверяем работу:

[                          main] MainSupport                    INFO  Apache Camel 2.9.1 starting
[                          main] SpringCamelContext             INFO  Apache Camel 2.9.1 (CamelContext: camel-1) is starting
[                          main] ManagementStrategyFactory      INFO  JMX enabled. Using ManagedManagementStrategy.
[                          main] ultManagementLifecycleStrategy INFO  StatisticsLevel at All so enabling load performance statistics
[                          main] AnnotationTypeConverterLoader  INFO  Found 3 packages with 15 @Converter classes to load
[                          main] DefaultTypeConverter           INFO  Loaded 169 core type converters (total 169 type converters)
[                          main] AnnotationTypeConverterLoader  INFO  Loaded 5 @Converter classes
[                          main] DefaultTypeConverter           INFO  Loaded additional 23 type converters (total 192 type converters) in 0.016 seconds
[                          main] CxfEndpoint                    WARN  The endpoint/port name of cxf://bean:SimpleWSEndpoint?dataFormat=PAYLOAD is empty, cxf will try to load the first one in wsdl for you.
[                          main] CxfEndpoint                    WARN  The service name of cxf://bean:SimpleWSEndpoint?dataFormat=PAYLOAD is empty, cxf will try to load the first one in wsdl for you.
[                          main] ReflectionServiceFactoryBean   INFO  Creating Service {http://com.bssys/simplews/}simplews from WSDL: /wsdl/simplews.wsdl
[                          main] ServerImpl                     INFO  Setting the server's publish address to be http://0.0.0.0:8083/SimpleWS
[                          main] Server                         INFO  jetty-7.5.4.v20111024
[                          main] AbstractConnector              INFO  Started SelectChannelConnector@0.0.0.0:8083 STARTING
[                          main] ContextHandler                 INFO  started o.e.j.s.h.ContextHandler{,null}
[                          main] SpringCamelContext             INFO  Route: route1 started and consuming from: Endpoint[cxf://bean:SimpleWSEndpoint?dataFormat=PAYLOAD]
[                          main] SpringCamelContext             INFO  Total 1 routes, of which 1 is started.
[                          main] SpringCamelContext             INFO  Apache Camel 2.9.1 (CamelContext: camel-1) started in 1.422 seconds

Можно проверить доступность WSDL по адресу http://127.0.0.1:8083/SimpleWS/?wsdl

Запустив стандартный SOAP UI, Webservice explorer в Eclipse или любой другой клиент и выполнив запрос, увидим в логах следующее:
<sim:UserOperation xmlns:sim="http://com.bssys/simplews/">
     <user>Sergey</user>
</sim:UserOperation>

Рассмотрим что произошло. При старте приложения была поднята конечная точка для cxf-webservices. Apache Camel слушает конечную точку, и при поступлении запроса выводит тело запроса в лог.

Заметили что наш сервис не возвращает ответа? Теперь воспользуемся JAXB чтобы разработать бизнес логику нашего вебсервиса!

Сгенерируем необходимые артефакты с помощью команды:
xjc -p com.bssys.simplews -wsdl simplews.wsdl
parsing a schema...
compiling a schema...
com\bssys\simplews\ObjectFactory.java
com\bssys\simplews\RequestBO.java
com\bssys\simplews\ResponseBO.java
com\bssys\simplews\package-info.java

Скопируем полученные исходники в папку с исходниками.

Создаем Processor который будет выполнять всю работу.

package com.bssys.simplews;

import java.util.List;

import javax.xml.bind.JAXBElement;

import org.apache.camel.Exchange;
import org.apache.camel.Processor;

public class SimpleLogicProcess implements Processor{

 @Override
 public void process(Exchange exchange) throws Exception {
  // TODO Auto-generated method stub
  RequestBO body = exchange.getIn().getBody(RequestBO.class);
  ObjectFactory factory = new ObjectFactory();
  ResponseBO responseBO = factory.createResponseBO();
  List list = responseBO.getAccount();
  list.add("account1");
  list.add("account2");
  list.add("account3");
  JAXBElement userOperationResponse = factory.createUserOperationResponse(responseBO);
  exchange.getOut().setBody(userOperationResponse);
 }

}

Наш Processor берет поступающий запрос как объект класса RequestBO. Потом создает класс ответа ResponseBO, заполняет его и устанавливает как тело ответа.
С помощью JAXB мы работаем с xml как с java-объектами, в этом вся прелесть!

Для того чтобы его можно было использовать в Camel-route добавим описание в контекст:

<bean id="SimpleLogic" class="com.bssys.simplews.SimpleLogicProcess"/>

Теперь немного модифицируем роутинг MyRouteBuilder.java, для работы с jaxb.
Вот такой итоговый роутинг у нас получился:
 // TODO create Camel routes here.
     JaxbDataFormat jaxb = new JaxbDataFormat("com.bssys.simplews");
     from("cxf:bean:SimpleWSEndpoint?dataFormat=PAYLOAD").unmarshal(jaxb).log("${body}").processRef("SimpleLogic").marshal(jaxb);

Запускаем. и проверяем с помощью любого клиента.
Вот результат работы в SOAP UI.


Удачи

Комментариев нет: