Integracja usług sieciowych z użyciem Apache Camel – praktyczny przykład – część 2
Integracja usług sieciowych z użyciem Apache Camel – praktyczny przykład – część 2
Zanim zaczniesz czytać ten wpis koniecznie zobacz pierwszą część – https://javaleader.pl/2020/04/14/integracja-uslug-sieciowych-z-uzyciem-apache-camel-praktyczny-przyklad-czesc-1/. W tej części zajmiemy się integracją usług z użyciem Apache Camel. Warto odświeżyć sobie wiedzę z zakresu XML, XSD, XSLT.
Tworzymy nowy projekt Apache Maven bez konkretnego archetypu – niezbędne zależności:
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <cxf.version>2.7.0</cxf.version> <spring.version>3.0.7.RELEASE</spring.version> <slf4j.version>1.6.2</slf4j.version> </properties>
zależności do org.apache.camel:
<dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-core</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-spring</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-stream</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-http</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-cxf</artifactId> <version>2.10.2</version> </dependency> <dependency> <groupId>org.apache.camel</groupId> <artifactId>camel-soap</artifactId> <version>2.3.0</version> </dependency>
zależności do org.apache.cxf:
<dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxrs</artifactId> <version>${cxf.version}</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-frontend-jaxws</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-transports-http-jetty</artifactId> <version>2.6.2</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-rt-bindings-soap</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>org.apache.cxf</groupId> <artifactId>cxf-tools-common</artifactId> <version>2.7.0</version> </dependency>
zależności do org.slf4j oraz do log4j:
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.6</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.6</version> </dependency>
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
pozostałe zależności:
<dependency> <groupId>jdom</groupId> <artifactId>jdom</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.4</version> </dependency>
Wtyczka niezbędna do wygenerowania odpowiednich klas na podstawie plików WSDL:
<plugin> <groupId>org.apache.cxf</groupId> <artifactId>cxf-codegen-plugin</artifactId> <version>2.7.0</version> <executions> <execution> <id>generate-sources</id> <phase>generate-sources</phase> <configuration> <sourceRoot>${basedir}/src/main/java</sourceRoot> <wsdlOptions> <wsdlOption> <wsdl>${basedir}/src/main/resources/wsdl/ReservationService.wsdl</wsdl> </wsdlOption> <wsdlOption> <wsdl>${basedir}/src/main/resources/wsdl/ReservationServiceAirlineA.wsdl</wsdl> </wsdlOption> <wsdlOption> <wsdl>${basedir}/src/main/resources/wsdl/ReservationServiceAirlineB.wsdl</wsdl> </wsdlOption> </wsdlOptions> </configuration> <goals> <goal>wsdl2java</goal> </goals> </execution> </executions> </plugin>
Pliki *.WSDL – zamieszczamy w katalogu ./src/main/resources/wsdl:
- ReservationService.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://air.sample.com/quote/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="AirLineQuote" targetNamespace="http://air.sample.com/quote/"> <wsdl:types> <xsd:schema targetNamespace="http://air.sample.com/quote/"> <xsd:element name="getQuote"> <xsd:complexType> <xsd:sequence> <xsd:element name ="getQuote" type="tns:getQuoteRequest" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="getQuoteRequest"> <xsd:sequence> <xsd:element name="source" type="xsd:string"></xsd:element> <xsd:element name="destination" type="xsd:string"></xsd:element> <xsd:element name="date" type="xsd:string"></xsd:element> <xsd:element name="airline" type="xsd:string" minOccurs="0"></xsd:element> </xsd:sequence> </xsd:complexType> <xsd:element name="getQuoteResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name = "getQuoteResponse" type="tns:getQuoteResp" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="getQuoteResp"> <xsd:sequence> <xsd:element name="price" minOccurs="0" maxOccurs="unbounded" type="tns:Quote"></xsd:element> </xsd:sequence> </xsd:complexType> <xsd:complexType name="Quote"> <xsd:sequence> <xsd:element name="airLine" type="xsd:string" minOccurs="0" /> <xsd:element name="price" type="xsd:string" minOccurs="0" /> </xsd:sequence> </xsd:complexType> <xsd:element name="getQuoteFault"> <xsd:complexType> <xsd:sequence> <xsd:element name="code" type="xsd:string"></xsd:element> <xsd:element name="msg" type="xsd:string"></xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="getQuoteOperationRequest"> <wsdl:part element="tns:getQuote" name="parameters" /> </wsdl:message> <wsdl:message name="getQuoteOperationResponse"> <wsdl:part element="tns:getQuoteResponse" name="parameters" /> </wsdl:message> <wsdl:message name="getQuoteOperationFault"> <wsdl:part name="parameters" element="tns:getQuoteFault"></wsdl:part> </wsdl:message> <wsdl:portType name="AirLineQuote"> <wsdl:operation name="getQuoteOperation"> <wsdl:input message="tns:getQuoteOperationRequest" /> <wsdl:output message="tns:getQuoteOperationResponse" /> <wsdl:fault name="fault" message="tns:getQuoteOperationFault"></wsdl:fault> </wsdl:operation> </wsdl:portType> <wsdl:binding name="AirLineQuoteSOAP" type="tns:AirLineQuote"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="getQuoteOperation"> <soap:operation soapAction="http://air.sample.com/quote/getQuoteOperation" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="AirLineQuoteService"> <wsdl:port binding="tns:AirLineQuoteSOAP" name="AirLineQuoteSOAP"> <soap:address location="http://localhost:8080/QuoteService" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
- ReservationServiceAirlineA.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://aira.sample.com/quote/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="AirLineAQuote" targetNamespace="http://aira.sample.com/quote/"> <wsdl:types> <xsd:schema targetNamespace="http://aira.sample.com/quote/"> <xsd:element name="getQuote"> <xsd:complexType> <xsd:sequence> <xsd:element name="getQuote" type="tns:getQuoteRequest" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="getQuoteRequest"> <xsd:sequence> <xsd:element name="source" type="xsd:string"></xsd:element> <xsd:element name="destination" type="xsd:string"></xsd:element> <xsd:element name="date" type="xsd:string"></xsd:element> <xsd:element name="airline" type="xsd:string" minOccurs="0"></xsd:element> </xsd:sequence> </xsd:complexType> <xsd:element name="getQuoteResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name="getQuoteResponse" type="tns:getQuoteResp" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="getQuoteResp"> <xsd:sequence> <xsd:element name="price" minOccurs="0" type="tns:Quote"></xsd:element> </xsd:sequence> </xsd:complexType> <xsd:complexType name="Quote"> <xsd:sequence> <xsd:element name="airLine" type="xsd:string" fixed="AirLineA"/> <xsd:element name="price" type="xsd:string" /> </xsd:sequence> </xsd:complexType> <xsd:element name="getQuoteFault"> <xsd:complexType> <xsd:sequence> <xsd:element name="code" type="xsd:string"></xsd:element> <xsd:element name="msg" type="xsd:string"></xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="getQuoteOperationRequest"> <wsdl:part element="tns:getQuote" name="parameters" /> </wsdl:message> <wsdl:message name="getQuoteOperationResponse"> <wsdl:part element="tns:getQuoteResponse" name="parameters" /> </wsdl:message> <wsdl:message name="getQuoteOperationFault"> <wsdl:part name="parameters" element="tns:getQuoteFault"></wsdl:part> </wsdl:message> <wsdl:portType name="AirLineAQuote"> <wsdl:operation name="getQuoteOperation"> <wsdl:input message="tns:getQuoteOperationRequest" /> <wsdl:output message="tns:getQuoteOperationResponse" /> <wsdl:fault name="fault" message="tns:getQuoteOperationFault"></wsdl:fault> </wsdl:operation> </wsdl:portType> <wsdl:binding name="AirLineQuoteSOAP" type="tns:AirLineAQuote"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="getQuoteOperation"> <soap:operation soapAction="http://aira.sample.com/quote/getQuoteOperation" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="AirLineAQuoteService"> <wsdl:port binding="tns:AirLineQuoteSOAP" name="AirLineQuoteSOAP"> <soap:address location="http://localhost:8080/QuoteOperation" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
- ReservationServiceAirlineB.wsdl
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://airb.sample.com/quote/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="AirLineBQuote" targetNamespace="http://airb.sample.com/quote/"> <wsdl:types> <xsd:schema targetNamespace="http://airb.sample.com/quote/"> <xsd:element name="getQuote"> <xsd:complexType> <xsd:sequence> <xsd:element name="getQuote" type="tns:getQuoteRequest" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="getQuoteRequest"> <xsd:sequence> <xsd:element name="source" type="xsd:string"></xsd:element> <xsd:element name="destination" type="xsd:string"></xsd:element> <xsd:element name="date" type="xsd:string"></xsd:element> <xsd:element name="airline" type="xsd:string" minOccurs="0"></xsd:element> </xsd:sequence> </xsd:complexType> <xsd:element name="getQuoteResponse"> <xsd:complexType> <xsd:sequence> <xsd:element name="getQuoteResponse" type="tns:getQuoteResp" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="getQuoteResp"> <xsd:sequence> <xsd:element name="price" minOccurs="0" type="tns:Quote"></xsd:element> </xsd:sequence> </xsd:complexType> <xsd:complexType name="Quote"> <xsd:sequence> <xsd:element name="airLine" type="xsd:string" fixed="AirLineB"/> <xsd:element name="price" type="xsd:string" /> </xsd:sequence> </xsd:complexType> <xsd:element name="getQuoteFault"> <xsd:complexType> <xsd:sequence> <xsd:element name="code" type="xsd:string"></xsd:element> <xsd:element name="msg" type="xsd:string"></xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <wsdl:message name="getQuoteOperationRequest"> <wsdl:part element="tns:getQuote" name="parameters" /> </wsdl:message> <wsdl:message name="getQuoteOperationResponse"> <wsdl:part element="tns:getQuoteResponse" name="parameters" /> </wsdl:message> <wsdl:message name="getQuoteOperationFault"> <wsdl:part name="parameters" element="tns:getQuoteFault"></wsdl:part> </wsdl:message> <wsdl:portType name="AirLineBQuote"> <wsdl:operation name="getQuoteOperation"> <wsdl:input message="tns:getQuoteOperationRequest" /> <wsdl:output message="tns:getQuoteOperationResponse" /> <wsdl:fault name="fault" message="tns:getQuoteOperationFault"></wsdl:fault> </wsdl:operation> </wsdl:portType> <wsdl:binding name="AirLineQuoteSOAP" type="tns:AirLineBQuote"> <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="getQuoteOperation"> <soap:operation soapAction="http://airb.sample.com/quote/getQuoteOperation" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="AirLineBQuoteService"> <wsdl:port binding="tns:AirLineQuoteSOAP" name="AirLineQuoteSOAP"> <soap:address location="http://localhost:8080/QuoteOperation" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
Generujemy narzędziem wsdl2java niezbędne klasy na podstawie plików WSDL:
mvn generate:sources
Apache Camel Context
Plik ./webapp/WEB-INF/camel-context.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:broker="http://activemq.apache.org/schema/core" xmlns:cxf="http://camel.apache.org/schema/cxf" xmlns:camel="http://camel.apache.org/schema/spring" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd http://camel.apache.org/schema/cxf http://camel.apache.org/schema/cxf/camel-cxf.xsd"> </beans>
za pomocą cxf-component defniujemy Camel CXF Endpoint dla zdefiniowanych usług:
<!-- Endpoint of MockService for Airline A --> <cxf:cxfEndpoint id="airlineAEndpoint" address="http://localhost:8080/cxf-sample-airlineA-1.0/services/AirLineAQuote" serviceClass="com.sample.aira.quote.AirLineAQuote" wsdlURL="wsdl/ReservationServiceAirlineA.wsdl"> <cxf:properties> <entry key="dataFormat" value="PAYLOAD" /> </cxf:properties> </cxf:cxfEndpoint> <!-- Endpoint of MockService for Airline B --> <cxf:cxfEndpoint id="airlineBEndpoint" address="http://localhost:8080/cxf-sample-airlineB-1.0/services/AirLineBQuote" serviceClass="com.sample.airb.quote.AirLineBQuote" wsdlURL="wsdl/ReservationServiceAirlineB.wsdl"> <cxf:properties> <entry key="dataFormat" value="PAYLOAD" /> </cxf:properties> </cxf:cxfEndpoint> <!-- Endpoint of Reservation Service --> <cxf:cxfEndpoint id="reservationService" address="/QuoteService" serviceClass="com.sample.air.quote.AirLineQuote" wsdlURL="wsdl/ReservationService.wsdl"> <cxf:properties> <entry key="dataFormat" value="PAYLOAD" /> <entry key="schema-validation-enabled" value="true" /> </cxf:properties> </cxf:cxfEndpoint>
domyślnie camel-cxf korzysta z dataFormat=POJO. Potrzebujemy XMLa aby przekazywać go miedzy wywołaniami w konfiguracji Apache Camel. Aby to zrealizować należy dodać:
<entry key="dataFormat" value="PAYLOAD" />
Jeśli zależy nam na walidacji requesta na podstawie schematu zdefiniowanege w danym pliku WSDL to należy dodać:
<entry key="schema-validation-enabled" value="true" />
W tagach:
<camel:camelContext id="camelContext">...</camel:camelContext>
definiujemy routing:
<camel:route id="airLineQuoteRoute"> <camel:from uri="cxf:bean:reservationService" /> <camel:to uri="bean:requestValidator" /> <camel:choice> <camel:when> <camel:simple>${header.airLines} == 'AirLineA'</camel:simple> <camel:to uri="direct:getQuoteA"></camel:to> </camel:when> <camel:when> <camel:simple>${header.airLines} == 'AirLineB'</camel:simple> <camel:to uri="direct:getQuoteB"></camel:to> </camel:when> <camel:when> <camel:simple>${header.airLines} == 'All'</camel:simple> <camel:to uri="direct:getQuoteMulticast"></camel:to> </camel:when> </camel:choice> <!-- To handle exceptions occurred --> <camel:onException> <camel:exception>java.lang.Exception</camel:exception> </camel:onException> </camel:route>
Przychodzi request który jest przetwarzany za pomoca beana requestValidator:
<bean id="requestValidator" class="com.sample.processor.RequestValidator" />
Klasa RequestValidator sprawdza czy wysłany request posiada prawidłowe dane i ustawia dodatkowo nagłówek header:
public class RequestValidator implements Processor { private static final Logger LOGGER = Logger .getLogger(RequestValidator.class); public static final String XPATH_ORIGIN = "//*[local-name() = 'source']"; public static final String XPATH_DESTINATION = "//*[local-name() = 'destination']"; public static final String XPATH_DATE = "//*[local-name() = 'date']"; public static final String XPATH_AIRLINES = "//*[local-name() = 'airline']"; @Override public void process(Exchange exchange) throws Exception { String requestXML = exchange.getIn().getBody(String.class); XPathReaderUtil xpathReader = new XPathReaderUtil(requestXML); getNodeValue(exchange, xpathReader, XPATH_ORIGIN, "source"); getNodeValue(exchange, xpathReader, XPATH_DESTINATION, "destination"); getNodeValue(exchange, xpathReader, XPATH_DATE, "date"); getNodeValue(exchange, xpathReader, XPATH_AIRLINES, "airline"); } private void getNodeValue(Exchange exchange, XPathReaderUtil xpathReader, String xpathName, String nodeName) throws Exception { String nodeValue = null; try { nodeValue = xpathReader.getNodeValue(xpathName); if ("".equals(nodeValue)) { throw (new Exception()); } if ("airline".equals(nodeName)) { setAirLine(exchange, nodeValue); } } catch (Exception exception) { if ("source".equals(nodeName)) { throw (new Exception("Please provide Source - Starting place!")); } else if ("destination".equals(nodeName)) { throw (new Exception("Please provide Destination!")); } else if ("date".equals(nodeName)) { throw (new Exception("Please provide Travel date")); } else if ("airline".equals(nodeName)) { setAirLine(exchange, "All"); } } } private void setAirLine(Exchange exchange, String airLines) throws Exception { if (airLines != null) { if ("AirLineA".equals(airLines) || "AirLineB".equals(airLines) || "All".equals(airLines)) { exchange.getIn().setHeader("airLines", airLines); } else { exchange.getIn().setHeader("airLines", "NONE"); throw (new Exception("Please provide valid Airline name or All")); } } } }
Przykładowo, jeśli zostanie wysłany request – bez podanego źródła:
<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:quot="http://air.sample.com/quote/"> <soapenv:Header/> <soapenv:Body> <quot:getQuote> <getQuote> <source></source> <destination>CHN</destination> <date>25/09/2013</date> <airline>All</airline> </getQuote> </quot:getQuote> </soapenv:Body> </soapenv:Envelope>
W odpowiedzi otrzymamy:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <soap:Fault> <faultcode>soap:Server</faultcode> <faultstring>Please provide Source - Starting place!</faultstring> </soap:Fault> </soap:Body> </soap:Envelope>
na podstawie wartości ustawionego nagłówka przekazujemy sterowanie dalej, do kolejnych faz routingu.
AirLineA -> direct:getQuoteA
AirLineB -> direct:getQuoteB
All -> direct:getQuoteMulticast
Routing dla serwisu AirLineA:
Definiujemy kolejny routing:
<camel:route id="getQuoteAirLineARoute"> <camel:from uri="direct:getQuoteA" /> <camel:to uri="bean:getXMLStreamSource" /> <camel:to uri="xslt://airlineA_req_transform.xsl" /> <camel:setHeader headerName="operationNamespace"> <camel:simple>http://aira.sample.com/quote/</camel:simple> </camel:setHeader> <camel:setHeader headerName="SOAPAction"> <camel:simple>http://aira.sample.com/quote/getQuoteOperation</camel:simple> </camel:setHeader> <camel:to uri="cxf:bean:airlineAEndpoint" /> <camel:to uri="xslt://airline_resp_transform.xsl" /> <camel:onException> <camel:exception>java.lang.Exception</camel:exception> <camel:to uri="bean:exceptionHandler" /> </camel:onException> <camel:log message="Route getQuoteAirLineARoute Ends .... " /> </camel:route>
i beana:
<bean id="getXMLStreamSource" class="com.sample.processor.PrepareXMLStreamProcessor" />
public class PrepareXMLStreamProcessor implements Processor { @Override public void process(Exchange exchange) throws Exception { String requestXML = exchange.getIn().getBody(String.class); StringReader stringReader = new StringReader(requestXML); StreamSource streamSource = new StreamSource(stringReader); exchange.getIn().setBody(streamSource); } }
oraz beana do obsługi błędów:
<bean id="exceptionHandler" class="com.sample.processor.ExceptionHandler" />
public class ExceptionHandler implements Processor { @Override public void process(Exchange exchange) throws Exception { Exception exception = (Exception) exchange.getProperty(Exchange.EXCEPTION_CAUGHT); if (exception instanceof org.apache.cxf.transport.http.HTTPException) { throw (new Exception("TargetService Not Available!")); } if (exception instanceof org.apache.cxf.interceptor.Fault){ throw (new Exception("Unable to communicate with the Service!")); } else { throw (new Exception("unknown exception occured!")); } } }
żadanie zostanie przeprocesowane i poddane transformacji XSLT:
Plik \src\main\resources\airlineA_req_transform.xsl:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:res="http://air.sample.com/quote/" xmlns:quot="http://aira.sample.com/quote/"> <xsl:output method="xml" indent="yes" encoding="UTF-8" /> <xsl:template match="text()|@*" /> <xsl:template match="//res:getQuote//getQuote"> <xsl:element name="quot:getQuote"> <xsl:element name="getQuote"> <xsl:element name="source"> <xsl:value-of select="source"></xsl:value-of> </xsl:element> <xsl:element name="destination"> <xsl:value-of select="destination"></xsl:value-of> </xsl:element> <xsl:element name="date"> <xsl:value-of select="date"></xsl:value-of> </xsl:element> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet>
dodajemy nagłówki które są niezbędne w komunikacji SOAP:
<camel:setHeader headerName="operationNamespace"> <camel:simple>http://aira.sample.com/quote/</camel:simple> </camel:setHeader> <camel:setHeader headerName="SOAPAction"> <camel:simple>http://aira.sample.com/quote/getQuoteOperation</camel:simple> </camel:setHeader>
żadanie wysyłane jest do endpointa:
airlineAEndpoint
następnie odpowiedź jest poddawana transformacji XSLT:
Plik src\main\resources\airline_resp_transform.xsl:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:resa="http://aira.sample.com/quote/" xmlns:resb="http://airb.sample.com/quote/" xmlns:quot="http://air.sample.com/quote/"> <xsl:output method="xml" omit-xml-declaration="yes" indent="yes" encoding="UTF-8" /> <xsl:template match="text()|@*" /> <xsl:template match="//resa:getQuoteResponse//getQuoteResponse//price"> <xsl:element name="quot:getQuoteResponse"> <xsl:element name="getQuoteResponse"> <xsl:element name="price"> <xsl:element name="airLine"> <xsl:value-of select="//price//airLine"></xsl:value-of> </xsl:element> <xsl:element name="price"> <xsl:value-of select="//price//price"></xsl:value-of> </xsl:element> </xsl:element> </xsl:element> </xsl:element> </xsl:template> <xsl:template match="//resb:getQuoteResponse//getQuoteResponse//price"> <xsl:element name="quot:getQuoteResponse"> <xsl:element name="getQuoteResponse"> <xsl:element name="price"> <xsl:element name="airLine"> <xsl:value-of select="//price//airLine"></xsl:value-of> </xsl:element> <xsl:element name="price"> <xsl:value-of select="//price//price"></xsl:value-of> </xsl:element> </xsl:element> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet>
Routing dla serwisu AirLineB:
<camel:route id="getQuoteAirLineBRoute"> <camel:from uri="direct:getQuoteB" /> <camel:to uri="bean:getXMLStreamSource" /> <camel:to uri="xslt://airlineB_req_transform.xsl" /> <camel:setHeader headerName="operationNamespace"> <camel:simple>http://airb.sample.com/quote/</camel:simple> </camel:setHeader> <camel:setHeader headerName="SOAPAction"> <camel:simple>http://airb.sample.com/quote/getQuoteOperation</camel:simple> </camel:setHeader> <camel:to uri="cxf:bean:airlineBEndpoint" /> <camel:to uri="xslt://airline_resp_transform.xsl" /> <camel:onException> <camel:exception>java.lang.Exception</camel:exception> <camel:to uri="bean:exceptionHandler" /> </camel:onException> </camel:route>
analogicznie jak poprzednio:
Plik \src\main\resources\airlineB_req_transform.xsl:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:res="http://air.sample.com/quote/" xmlns:quot="http://airb.sample.com/quote/"> <xsl:output method="xml" indent="yes" encoding="UTF-8" /> <xsl:template match="text()|@*" /> <xsl:template match="//res:getQuote//getQuote"> <xsl:element name="quot:getQuote"> <xsl:element name="getQuote"> <xsl:element name="source"> <xsl:value-of select="source"></xsl:value-of> </xsl:element> <xsl:element name="destination"> <xsl:value-of select="destination"></xsl:value-of> </xsl:element> <xsl:element name="date"> <xsl:value-of select="date"></xsl:value-of> </xsl:element> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet>
Routing dla przetwarzania równlogełego:
Jeśli klient chciałby dokonać agregacji wyników z dwóch różnych serwisów czyli podał w zapytaniu:
<airline>All</airline>
należy pobrać dane z dwóch serwisów (najlepiej równolegle) i dokonać agregacji wyników. W tym celu definiujemy kolejny routing:
<camel:route id="getQuoteMultiCastRoute"> <camel:from uri="direct:getQuoteMulticast" /> <camel:multicast strategyRef="aggregateData" parallelProcessing="true"> <camel:to uri="direct:getQuoteA" /> <camel:to uri="direct:getQuoteB" /> </camel:multicast> <camel:to uri="bean:processResponse" /> <camel:to uri="xslt://process_response.xsl" /> <camel:onException> <camel:exception>java.lang.Exception</camel:exception> <camel:to uri="bean:exceptionHandler" /> </camel:onException> </camel:route>
definicja beana – aggregateData:
<bean id="aggregateData" class="com.sample.aggregate.AggregateResponse" />
public class AggregateResponse implements AggregationStrategy { @Override public Exchange aggregate(Exchange oldExchange, Exchange newExchange) { Exception exception = (Exception) newExchange.getProperty(Exchange.EXCEPTION_CAUGHT); if (exception == null) { if (oldExchange == null && newExchange != null) { return newExchange; } else if (oldExchange != null && newExchange != null) { newExchange.getIn().setBody( oldExchange.getIn().getBody(String.class) + newExchange.getIn().getBody(String.class)); } return newExchange; } return oldExchange; } }
definicja beana – processResponse:
<bean id="processResponse" class="com.sample.processor.ProcessResponse" />
public class ProcessResponse implements Processor { @Override public void process(Exchange exchange) throws Exception { StringBuffer multicastResponseXML = new StringBuffer("<aggregatedResponse>\n"); multicastResponseXML.append(exchange.getIn().getBody(String.class)); multicastResponseXML.append("\n </aggregatedResponse>"); StringReader stringReader = new StringReader(multicastResponseXML.toString()); StreamSource streamSource = new StreamSource(stringReader); exchange.getIn().setBody(streamSource); } }
zagregowana odpowiedź poddawana jest transformacie XSLT:
Plik \src\main\resources\process_response.xsl
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:res="http://air.sample.com/quote/"> <xsl:output method="xml" indent="yes" encoding="UTF-8" /> <xsl:template match="text()|@*" /> <xsl:template match="//aggregatedResponse"> <xsl:element name="res:getQuoteResponse"> <xsl:element name="getQuoteResponse"> <xsl:for-each select="//res:getQuoteResponse"> <xsl:element name="price"> <xsl:element name="airLine"> <xsl:value-of select="getQuoteResponse//price//airLine"></xsl:value-of> </xsl:element> <xsl:element name="price"> <xsl:value-of select="getQuoteResponse//price//price"></xsl:value-of> </xsl:element> </xsl:element> </xsl:for-each> </xsl:element> </xsl:element> </xsl:template> </xsl:stylesheet>
Przykładowy request:
<?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:quot="http://air.sample.com/quote/"> <soapenv:Header/> <soapenv:Body> <quot:getQuote> <getQuote> <source>HYD</source> <destination>CHN</destination> <date>25/09/2013</date> <airline>All</airline> </getQuote> </quot:getQuote> </soapenv:Body> </soapenv:Envelope>
zagregowana odpowiedź:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <res:getQuoteResponse xmlns:res="http://air.sample.com/quote/"> <getQuoteResponse> <price> <airLine>INDIGO</airLine> <price>4000</price> </price> <price> <airLine>SPICEJET</airLine> <price>4500</price> </price> </getQuoteResponse> </res:getQuoteResponse> </soap:Body> </soap:Envelope>
W projekcie użyliśmy następujących komponentów:
- Camel CXF Endpoint,
- Camel Bean,
- Camel Direct,
- Camel XSLT,
- Camel Multicast EIP,
- Camel Aggregator EIP.
W ostatniej części projektu zobaczysz w jaki sposób za pomoca modułu Camel WireTap EIP dokonać logowania akcji występujących w projekcie.
Leave a comment