4
votes

I am trying to understand how to integrate Apache Camel with any web service that provides a WSDL to generate its classes to afterward call his methods to return some requests.

I've studied a little about camel-spring-ws and camel-cxf packages. As I can see Spring Web Services Component does not support the use of WSDL but CXF does, however it only supports connections with JAX-WS services hosted in CXF.

If I receive a WSDL from a customer, could I use CXF? Or would I need to create a custom component to use his methods?

As far as I can see the simplest way to implement it would be creating a Process or a Bean to invokes the remote web service.

I've tried to implement a producer to call a remote web service. My beans.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:camel="http://camel.apache.org/schema/spring"
       xmlns:cxf="http://camel.apache.org/schema/cxf"
       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-3.0.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">

    <import resource="classpath:META-INF/cxf/cxf.xml"/> 

    <cxf:cxfEndpoint 
        id="osvEndpoint" 
        address="http://10.193.1.90:8767/" 
        serviceClass="siemens_hiq8000.SiemensHiq8000PortType"/> 

    <bean id="osvWebServiceProcessor" class="br.com.willianantunes.processor.OsvWebServiceProcessor" />

</beans>

My route:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:camel="http://camel.apache.org/schema/spring"
       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-3.0.xsd
       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">

    <routeContext id="osvWebServiceInvoke" xmlns="http://camel.apache.org/schema/spring">
        <route>
            <from uri="quartz2://test?cron=0/10+*+*+*+*+?"/>
            <process ref="osvWebServiceProcessor" />
            <to uri="cxf:bean:osvEndpoint"/>
            <to uri="log:live?level=INFO" />
        </route>
    </routeContext>

</beans>

And my processor:

public class OsvWebServiceProcessor implements Processor
{
    @Override
    public void process(Exchange exchange) throws Exception
    {
        Message inMessage = exchange.getIn();

        // The method to be called
        inMessage.setHeader(CxfConstants.OPERATION_NAME, "getVersion");

        // Parameters to be passed into the web service
        List<Object> params = new ArrayList<Object>();
        ResultCodeStructHolder resultCodeStructHolder = new ResultCodeStructHolder();
        VersionDataHolder versionDataHolder = new VersionDataHolder();
        params.add(resultCodeStructHolder);
        params.add(versionDataHolder);
        inMessage.setBody(params);
    }
}

The method getVersion needs some parameters as followed:

public void getVersion(siemens_hiq8000.holders.ResultCodeStructHolder result, 
siemens_hiq8000.holders.VersionDataHolder versionData) throws java.rmi.RemoteException;

How can I pass them? These holders must be filled with the response of the web service. When I run my project I get the following error:

[main] INFO org.apache.cxf.service.factory.ReflectionServiceFactoryBean - Creating Service {http://siemens_hiq8000/}SiemensHiq8000PortType from class siemens_hiq8000.SiemensHiq8000PortType
Exception in thread "main" java.lang.NoSuchMethodError: org.apache.cxf.wsdl11.WSDLEndpointFactory.createEndpointInfo(Lorg/apache/cxf/service/model/ServiceInfo;Lorg/apache/cxf/service/model/BindingInfo;Ljava/util/List;)Lorg/apache/cxf/service/model/EndpointInfo;
    at org.apache.cxf.frontend.AbstractWSDLBasedEndpointFactory.createEndpointInfo(AbstractWSDLBasedEndpointFactory.java:287)
    at org.apache.cxf.frontend.AbstractWSDLBasedEndpointFactory.createEndpoint(AbstractWSDLBasedEndpointFactory.java:144)
    at org.apache.cxf.frontend.ClientFactoryBean.create(ClientFactoryBean.java:91)
    at org.apache.camel.component.cxf.CxfSpringEndpoint.createClient(CxfSpringEndpoint.java:116)
    at org.apache.camel.component.cxf.CxfProducer.doStart(CxfProducer.java:76)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.impl.DefaultCamelContext.startService(DefaultCamelContext.java:2869)
    at org.apache.camel.impl.DefaultCamelContext.doAddService(DefaultCamelContext.java:1097)
    at org.apache.camel.impl.DefaultCamelContext.addService(DefaultCamelContext.java:1058)
    at org.apache.camel.impl.ProducerCache.doGetProducer(ProducerCache.java:405)
    at org.apache.camel.impl.ProducerCache.acquireProducer(ProducerCache.java:123)
    at org.apache.camel.processor.SendProcessor.doStart(SendProcessor.java:219)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:74)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:59)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:103)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:89)
    at org.apache.camel.processor.DelegateAsyncProcessor.doStart(DelegateAsyncProcessor.java:79)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:74)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:59)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:103)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:89)
    at org.apache.camel.processor.RedeliveryErrorHandler.doStart(RedeliveryErrorHandler.java:1272)
    at org.apache.camel.support.ChildServiceSupport.start(ChildServiceSupport.java:44)
    at org.apache.camel.support.ChildServiceSupport.start(ChildServiceSupport.java:31)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:74)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:59)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:103)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:89)
    at org.apache.camel.processor.interceptor.DefaultChannel.doStart(DefaultChannel.java:153)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:74)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:59)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:103)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:61)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:103)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:89)
    at org.apache.camel.processor.MulticastProcessor.doStart(MulticastProcessor.java:1060)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:74)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:59)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:103)
    at org.apache.camel.util.ServiceHelper.startServices(ServiceHelper.java:89)
    at org.apache.camel.processor.DelegateAsyncProcessor.doStart(DelegateAsyncProcessor.java:79)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.util.ServiceHelper.startService(ServiceHelper.java:74)
    at org.apache.camel.impl.RouteService.startChildService(RouteService.java:340)
    at org.apache.camel.impl.RouteService.warmUp(RouteService.java:182)
    at org.apache.camel.impl.DefaultCamelContext.doWarmUpRoutes(DefaultCamelContext.java:3090)
    at org.apache.camel.impl.DefaultCamelContext.safelyStartRouteServices(DefaultCamelContext.java:3020)
    at org.apache.camel.impl.DefaultCamelContext.doStartOrResumeRoutes(DefaultCamelContext.java:2797)
    at org.apache.camel.impl.DefaultCamelContext.doStartCamel(DefaultCamelContext.java:2653)
    at org.apache.camel.impl.DefaultCamelContext.access$000(DefaultCamelContext.java:167)
    at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:2467)
    at org.apache.camel.impl.DefaultCamelContext$2.call(DefaultCamelContext.java:2463)
    at org.apache.camel.impl.DefaultCamelContext.doWithDefinedClassLoader(DefaultCamelContext.java:2486)
    at org.apache.camel.impl.DefaultCamelContext.doStart(DefaultCamelContext.java:2463)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.impl.DefaultCamelContext.start(DefaultCamelContext.java:2432)
    at org.apache.camel.spring.SpringCamelContext.maybeStart(SpringCamelContext.java:255)
    at org.apache.camel.spring.SpringCamelContext.onApplicationEvent(SpringCamelContext.java:121)
    at org.apache.camel.spring.CamelContextFactoryBean.onApplicationEvent(CamelContextFactoryBean.java:332)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:151)
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:128)
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:331)
    at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:773)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:483)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
    at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:93)
    at org.apache.camel.spring.Main.createDefaultApplicationContext(Main.java:216)
    at org.apache.camel.spring.Main.doStart(Main.java:156)
    at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
    at org.apache.camel.main.MainSupport.run(MainSupport.java:150)
    at br.com.willianantunes.test.Program.main(Program.java:12)

Solution

For the ones who want a camel-cxf to act like a producer, just follow the steps bellow as an example:

  1. First I download the last WSDL file available for OpenScape Voice here in this developer page.
  2. The client code is necessary to inform in the option serviceClass the SEI (Service Endpoint Interface). You can generate issuing the following command:

wsdl2java -client -d "TargetFolderHere" -autoNameResolution "OpenScape-Voice_V8.00.28.01.wsdl"

  1. Insert the generated client classes in your project. The same applies to the WSDL file.
  2. In your beans.xml inform the cxfEndpoint configuration.

    <import resource="classpath:META-INF/cxf/cxf.xml" />    
    
    <cxf:cxfEndpoint xmlns:urn="urn:openscape-voice"
        id="osvEndpoint" 
        address="http://10.193.1.90:8767/" 
        serviceClass="voice.openscape.OpenscapeVoicePortType"
        wsdlURL="OpenScape-Voice_V8.00.28.01.wsdl"
        serviceName="urn:openscape_voice">  
    </cxf:cxfEndpoint>
    
    <bean id="osvWebServiceProcessor" class="br.com.willianantunes.processor.OsvWebServiceProcessor" />
    
  3. Before the web service call, configure a processor to inform the parameters and the method to execute.

    Message inMessage = exchange.getIn();
    
    // The method to be called
    inMessage.setHeader(CxfConstants.OPERATION_NAME, "GetVersion");
    
    // Parameters to be passed into the web service
    List<Object> params = new ArrayList<Object>();
    
    Holder<ResultCodeStruct> result = new Holder<>();
    Holder<VersionData> versionData = new Holder<>();
    
    params.add(result);
    params.add(versionData);
    inMessage.setBody(params);
    
  4. Now you can configure the route and insert in your camelContext as a ref to accomplish the task.

    <routeContext id="osvWebServiceInvoke" xmlns="http://camel.apache.org/schema/spring">
        <route>
            <from uri="quartz2://test?cron=0/10+*+*+*+*+?"/>
            <process ref="osvWebServiceProcessor"/>
            <to uri="cxf:bean:osvEndpoint"/>
            <to uri="log:live?level=INFO"/>
        </route>
    </routeContext>
    

And the log prints the following:

- Quartz scheduler 'DefaultQuartzScheduler-main-application' initialized from an externally provided properties instance.
- Quartz scheduler version: 2.2.1
- Job Camel_main-application.test (triggerType=CronTriggerImpl, jobClass=CamelJob) is scheduled. Next fire date is Tue Mar 31 09:12:00 BRT 2015
- AllowUseOriginalMessage is enabled. If access to the original message is not needed, then its recommended to turn this option off as it may improve performance.
- StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
- Creating Service {urn:openscape-voice}openscape_voice from WSDL: OpenScape-Voice_V8.00.28.01.wsdl
- Could not find endpoint/port for {urn:openscape-voice}openscape_voicePortTypePort in wsdl. Using {urn:openscape-voice}openscape_voice.
- Route: route1 started and consuming from: Endpoint[quartz2://test?cron=0%2F10+*+*+*+*+%3F]
- Starting scheduler.
- Scheduler DefaultQuartzScheduler-main-application_$_NON_CLUSTERED started.
- Total 1 routes, of which 1 is started.
- Apache Camel 2.15.0 (CamelContext: main-application) started in 10.759 seconds
- Exchange[ExchangePattern: InOnly, BodyType: org.apache.cxf.message.MessageContentsList, Body: [null, javax.xml.ws.Holder@508696f5, javax.xml.ws.Holder@333cf1ba]]

I had some problems with CXF dependencies, so here is my pom.xml:

<dependency>
    <groupId>org.apache.camel</groupId>
    <artifactId>camel-cxf</artifactId>
    <version>${camel-version}</version>
</dependency>
<!-- Apache CXF -->
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-frontend-jaxws</artifactId>
    <version>${cxf.version}</version>
</dependency>
<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-transports-http</artifactId>
    <version>${cxf.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-transports-http-jetty</artifactId>
    <version>${cxf.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-rt-bindings-soap</artifactId>
    <version>${cxf.version}</version>
</dependency>   
<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>jsr311-api</artifactId>
    <version>1.1.1</version>
</dependency>
<!-- End of Apache CXF -->

You can design a new processor or a bean to get the body message with the response (not the pure SOAP message, but the POJO used before) from the web service.

The answer provided by @Namphibian is OK and can fit to my purpose as well.

2

2 Answers

2
votes

You are referring to contract first or top-down development of Web-services. In this approach you generate stub code from the WSDL definitions and use these classes etc in your development. I have done this a lot and I have used WSDL's from service created in .Net, Java, PHP and even Delphi(though Delphi breaks WSI compliance don't even go there).

CXF will generate classes from just about any WSDL you can point the library at. First thing is that you need to add an entry to your Maven POM file which tell Maven to generate classes from the WSDL for you.

Add the following to your POM file:

<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>get the latest version or the version you want</version>
    <executions>
        <execution>
        <id>generate-sources</id>
        <phase>generate-sources</phase>
        <configuration>

            <sourceRoot>
            ${basedir}/target/generated/src/main/java
            </sourceRoot>
            <wsdlOptions>
                <wsdlOption>
                <wsdl>
                URI/FILE PATH OF WSDL HERE
                </wsdl>
                <extraargs>
                <extraarg>-impl</extraarg> <-- use this if you want to implement the service i.e. create it
                <extraarg>-client</extraarg> <-- us this if you want to generate a client for the service.
                </extraargs>
                </wsdlOption>
            </wsdlOptions>
        </configuration>
        <goals>
        <goal>wsdl2java</goal>
        </goals>
        </execution>
    </executions>
</plugin>

You can now runt he maven goal mvn generate-source to generate the needed stub classes to use this file.

I normally drop into a processor/bean to do the actual implementation as most of the web-services I produce and or consume have rather complex data structures. However this really depends on the service.

So in short you can use CXF to generate the stub classes for almost(Delphi guys are you listening?) web-service then use these generated classes in a processor to implement the client and or server.

UPDATE:

Based on your example above you are on the right track. First I want to just discuss something about CXF in camel which is an important concept. The CXF beans are a little different than the other beans for example when you see the following code:

<from uri="file://....."/>
<to uri="mock"/>

The file component is a producer it produces files after all and the mock component is a consumer as it takes data produced by the file component and consumes it to do its task.

Web-services twists this concept slightly. When you have a route such as:

The cxf bean is called a producer as it calls a web service. It gets interesting when you use a cxf bean as a consumer or in the <from> part of your route.

Here is an example of a web-service consumer:

<from uri="cxf:bean:someService" />

The CXF bean is consuming the web-service call and then sending the message on to various other parts. This allows you to expose something very complex such as downloading a file from an FTP server, enriching the contents to with a JDBC call and then processing the enriched data against a SAP system as web-service. Your route exposes the cxf bean in the <from> as the service the rest of the route can do all the integration but it is exposed as a web-service. VERY powerful concept.

In your case your web-service bean is a producer or a client. I typically don't use cxf beans for clients as te services I consume can be rather complex and i need the full power of Java to handle these cases. So I will show you how to do it this way.

So in my world I would do the following camel route:

<routeContext id="osvWebServiceInvoke" xmlns="http://camel.apache.org/schema/spring">
    <route>
        <from uri="quartz2://test?cron=0/10+*+*+*+*+?"/>
        <process ref="osvWebServiceProcessor" />
        <to uri="log:live?level=INFO" />
    </route>
</routeContext>

And my processor bean will change to this:

public class OsvWebServiceProcessor implements Processor
{
    @Override
    public void process(Exchange exchange) throws Exception
    {
        Message inMessage = exchange.getIn();



       /*
              SInce i dont have access to the WSDL and XSD I cant say how the following code will look but essentially you would need to call the web-service here in Java code and get the result back.

       */
      outSOAPMsg=  siemens_hiq8000.SiemensHiq8000PortType.OperationName(inSOAPMsg)
      //set the reply into the inbody and send onto other components for processing.
      inMessage.setBody(outSOAPMsg);

  }
}

Looking at your code I realised that it appears that your stub classes are not generated correctly. I strongly urge you to add the items described in my post to your POM file. If you could post some of the WSDL and XSD files I could give you more descriptive answer.

1
votes

One of my main responsibilities is to consume different web services using camel as a client. This is the approach I generally use, never ran into any problems it works like a charm everytime.

1) Define the endpoint in your camel-config.xml file:

< bean id="service_name_CXFEndpoint" class="org.apache.camel.component.cxf.CxfEndpoint" />

2) Use the Endpoint Defined in the config file in the route for web service consumption:

private String CXF_SERVICE_ENDPOINT = "cxf:bean:service_name_CXFEndpoint?address=wsdl_uri_location&serviceClass=service_name_from_the_stubs&loggingFeatureEnabled=true";

For the service name, you need to copy the contents of the wsdl, paste it in a .wsdl file and generate webservice client. To do this, you need to right click on the wsdl file>webservices>Generate Client. Then you need to select JbossWS for the runtime( You have to set it up first in the preferences). Once the stubs, have been generated, look for the main service class. Then copy its entire location.(for example, for a class called WebServiceClass located in com.test would be serviceClass=com.test.WebService.class)

3) Define the routing for consumption:

from("direct:web_service")
.routeId("service_name")
.bean(ServiceWSProcessor,"processRequest")
.to(CXF_SERVICE_ENDPOINT)
.bean(ServiceWSProcessor,"processResponse")
.end();

Now, you send a request to this processor, which will go to the web service endpoint and give you a response.

4) Write a processor for the request response(in this case serviceWS processor).

@Component(value="serviceWSProcessor")
public class ServiceWSProcessor {

  private static final Logger LOGGER = LoggerFactory.getLogger(ServiceWSProcessor.class);

  public void processRequest(Exchange exchange) throws Exception {

    Message in = exchange.getIn();
    Message out = exchange.getOut();

    try {

      LOGGER.info("Service - START");

      Request Request = in.getBody(Request.class);

      //This depends on your WSDL File. Test the process in SOAP UI and see how the request looks like and code
      //accordingly.

      List<Object> list = new ArrayList<Object>();
      list.add(header);
      list.add(body);
      //for this, you need to check the service for what are the parameters for that method and what response its expecting.

      out.setHeader(CxfConstants.OPERATION_NAME, "method_name");
      out.setBody(list);

    } 
    catch (Exception e) {
      e.printStackTrace();
    }

  }

  public void processResponse(Exchange exchange) throws Exception {

    Message in = exchange.getIn();
    Message out = exchange.getOut();

    try {
      Response response = null; //Code this based on what response you should set to the final body. check the message
      //contents list in debug mode to see what response you are getting.

      try {
        MessageContentsList result = (MessageContentsList) in.getBody();
        if (result != null) {
          response = (Response)result.get(0);

          out.setHeader(Constants.HEADER_SERVICE_RESPONSE_SUCCESS, Constants.SERVICE_RESPONSE_SUCCESS_Y);
        } else {
          out.setHeader(Constants.HEADER_SERVICE_RESPONSE_SUCCESS, Constants.SERVICE_RESPONSE_SUCCESS_N);
        }

      } catch (Exception e) {
        e.printStackTrace();
        out.setHeader(Constants.HEADER_SERVICE_RESPONSE_SUCCESS, Constants.SERVICE_RESPONSE_SUCCESS_N);
      }
      LOGGER.info("Service - END");
      out.setBody(response));
    } 
    catch (Exception e) {
      LOGGER.error("Service - ERROR");
      out.setBody(e.getMessage());
    } 

  }

}

For the list of dependencies required for camel and spring-ws, check out this page.

https://github.com/gauthamhs/Java/blob/master/JavaEE/Dependencies-Important-Camel-Spring.xml

Let me know if you need additional help or if you have any concerns.

Regards, Gautham