You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 18 Next »

Tutorial for using AXIS 1.4 with Apache Camel

Introduction

Apache AXIS is/was widely used as a webservice framework. So in line with some of the other tutorials to demonstrate how Camel is not an invasive framework but is flexible and integrates well with existing solution.

We have an existing solution that exposes a webservice using AXIS 1.4 deployed as web applications. This is a common solution. We use contract first so we have AXIS generated source code from an existing wsdl file. Then we show how we introduce Spring and Camel to integrate with AXIS.

This tutorial uses the following frameworks:

  • Maven 2.0.9
  • Apache Camel 1.4.0
  • Apache AXIS 1.4
  • Spring 2.5.5

Setting up the project to run AXIS

This first part is about getting the project up to speed with Axis. We are not touching Camel or Spring at this time.

Maven 2

AXIS dependencies is available for maven 2 so we configure our pom.xml as:

        <dependency>
            <groupId>org.apache.axis</groupId>
            <artifactId>axis</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.axis</groupId>
            <artifactId>axis-jaxrpc</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>org.apache.axis</groupId>
            <artifactId>axis-saaj</artifactId>
            <version>1.4</version>
        </dependency>

	<dependency>
	    <groupId>axis</groupId>
	    <artifactId>axis-wsdl4j</artifactId>
	    <version>1.5.1</version>
	</dependency>

	<dependency>
	    <groupId>commons-discovery</groupId>
	    <artifactId>commons-discovery</artifactId>
	    <version>0.4</version>
	</dependency> 

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.14</version>
        </dependency>

Then we need to configure maven to use Java 1.5 and the axis maven plugin that generates the source code based on the wsdl file:

<!-- to compile with 1.5 -->
	<plugin>
		<groupId>org.apache.maven.plugins</groupId>
		<artifactId>maven-compiler-plugin</artifactId>
		<configuration>
			<source>1.5</source>
			<target>1.5</target>
		</configuration>
	</plugin>

            <plugin>
               <groupId>org.codehaus.mojo</groupId>
               <artifactId>axistools-maven-plugin</artifactId>
               <configuration>
	          <sourceDirectory>src/main/resources/</sourceDirectory>
                  <packageSpace>com.mycompany.myschema</packageSpace>
                  <testCases>false</testCases>
                  <serverSide>true</serverSide>
                  <subPackageByFileName>false</subPackageByFileName>
               </configuration>
               <executions>
                 <execution>
                   <goals>
                     <goal>wsdl2java</goal>
                   </goals>
                 </execution>
               </executions>
            </plugin>

wsdl

We use the same .wsdl file as the Tutorial-Example-ReportIncident and copy it to src/main/webapp/WEB-INF/wsdl

<?xml version="1.0" encoding="ISO-8859-1"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
	xmlns:tns="http://reportincident.example.camel.apache.org"
	xmlns:xs="http://www.w3.org/2001/XMLSchema"
	xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
	xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
	targetNamespace="http://reportincident.example.camel.apache.org">

	<!-- Type definitions for input- and output parameters for webservice -->
	<wsdl:types>
	<xs:schema targetNamespace="http://reportincident.example.camel.apache.org">
			<xs:element name="inputReportIncident">
				<xs:complexType>
					<xs:sequence>
						<xs:element type="xs:string"  name="incidentId"/>
						<xs:element type="xs:string"  name="incidentDate"/>
						<xs:element type="xs:string"  name="givenName"/>
						<xs:element type="xs:string"  name="familyName"/>
						<xs:element type="xs:string"  name="summary"/>
						<xs:element type="xs:string"  name="details"/>
						<xs:element type="xs:string"  name="email"/>
						<xs:element type="xs:string"  name="phone"/>
					</xs:sequence>
				</xs:complexType>
			</xs:element>
			<xs:element name="outputReportIncident">
				<xs:complexType>
					<xs:sequence>
						<xs:element type="xs:string" name="code"/>
					</xs:sequence>
				</xs:complexType>
			</xs:element>
		</xs:schema>
	</wsdl:types>

	<!-- Message definitions for input and output -->
	<wsdl:message name="inputReportIncident">
		<wsdl:part name="parameters" element="tns:inputReportIncident"/>
	</wsdl:message>
	<wsdl:message name="outputReportIncident">
		<wsdl:part name="parameters" element="tns:outputReportIncident"/>
	</wsdl:message>

	<!-- Port (interface) definitions -->
	<wsdl:portType name="ReportIncidentEndpoint">
		<wsdl:operation name="ReportIncident">
			<wsdl:input message="tns:inputReportIncident"/>
			<wsdl:output message="tns:outputReportIncident"/>
		</wsdl:operation>
	</wsdl:portType>

	<!-- Port bindings to transports and encoding - HTTP, document literal encoding is used -->
	<wsdl:binding name="ReportIncidentBinding" type="tns:ReportIncidentEndpoint">
		<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
		<wsdl:operation name="ReportIncident">
			<soap:operation
				soapAction="http://reportincident.example.camel.apache.org/ReportIncident"
				style="document"/>
			<wsdl:input>
				<soap:body parts="parameters" use="literal"/>
			</wsdl:input>
			<wsdl:output>
				<soap:body parts="parameters" use="literal"/>
			</wsdl:output>
		</wsdl:operation>
	</wsdl:binding>

	<!-- Service definition -->
	<wsdl:service name="ReportIncidentService">
		<wsdl:port name="ReportIncidentPort" binding="tns:ReportIncidentBinding">
			<soap:address location="http://reportincident.example.camel.apache.org"/>
		</wsdl:port>
	</wsdl:service>

</wsdl:definitions>

Configuring AXIS

Okay we are now setup for the contract first development and can generate the source file. For now we are still only using standard AXIS and not Spring nor Camel. We still need to setup AXIS as a web application so we configure the web.xml in src/main/webapp/WEB-INF/web.xml as:

	<servlet>
		<servlet-name>axis</servlet-name>
		<servlet-class>org.apache.axis.transport.http.AxisServlet</servlet-class>
	</servlet>

	<servlet-mapping>
		<servlet-name>axis</servlet-name>
		<url-pattern>/services/*</url-pattern>
	</servlet-mapping>

The web.xml just registeres AXIS servlet that is handling the incoming web requests to its servlet mapping. We still need to configure AXIS itself and this is done using its special configuration file server-config.wsdd. We nearly get this file for free if we let Axis generate the source code so we run the maven goal:

mvn axistools:wsdl2java

The tool will generate the source code based on the wsdl and save the files to the following folder:

.\target\generated-sources\axistools\wsdl2java\org\apache\camel\example\reportincident
deploy.wsdd
InputReportIncident.java
OutputReportIncident.java
ReportIncidentBindingImpl.java
ReportIncidentBindingStub.java
ReportIncidentService_PortType.java
ReportIncidentService_Service.java
ReportIncidentService_ServiceLocator.java
undeploy.wsdd

This is standard AXIS and so far no Camel or Spring has been touched. To implement our webservice we will add our code, so we create a new class AxisReportIncidentService that implements the port type interface where we can implement our code logic what happens when the webservice is invoked.

package org.apache.camel.example.axis;

import org.apache.camel.example.reportincident.InputReportIncident;
import org.apache.camel.example.reportincident.OutputReportIncident;
import org.apache.camel.example.reportincident.ReportIncidentService_PortType;

import java.rmi.RemoteException;

/**
 * Axis webservice
 */
public class AxisReportIncidentService implements ReportIncidentService_PortType {

    public OutputReportIncident reportIncident(InputReportIncident parameters) throws RemoteException {
        System.out.println("Hello AxisReportIncidentService is called from " + parameters.getGivenName());

        OutputReportIncident out = new OutputReportIncident();
        out.setCode("OK");
        return out;
    }

}

Now we need to configure AXIS itself and this is done using its server-config.wsdd file. We nearly get this for for free from the auto generated code, we copy the stuff from deploy.wsdd and made a few modifications:

<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
  <!-- global configuration -->
	<globalConfiguration>
		<parameter name="sendXsiTypes" value="true"/>
		<parameter name="sendMultiRefs" value="true"/>
		<parameter name="sendXMLDeclaration" value="true"/>
		<parameter name="axis.sendMinimizedElements" value="true"/>
	</globalConfiguration>
	<handler name="URLMapper" type="java:org.apache.axis.handlers.http.URLMapper"/>

  <!-- this service is from deploy.wsdd -->
  <service name="ReportIncidentPort" provider="java:RPC" style="document" use="literal">
      <parameter name="wsdlTargetNamespace" value="http://reportincident.example.camel.apache.org"/>
      <parameter name="wsdlServiceElement" value="ReportIncidentService"/>
      <parameter name="schemaUnqualified" value="http://reportincident.example.camel.apache.org"/>
      <parameter name="wsdlServicePort" value="ReportIncidentPort"/>
      <parameter name="className" value="org.apache.camel.example.reportincident.ReportIncidentBindingImpl"/>
      <parameter name="wsdlPortType" value="ReportIncidentService"/>
      <parameter name="typeMappingVersion" value="1.2"/>
      <operation name="reportIncident" qname="ReportIncident" returnQName="retNS:outputReportIncident" xmlns:retNS="http://reportincident.example.camel.apache.org"
                 returnType="rtns:>outputReportIncident" xmlns:rtns="http://reportincident.example.camel.apache.org"
                 soapAction="http://reportincident.example.camel.apache.org/ReportIncident" >
        <parameter qname="pns:inputReportIncident" xmlns:pns="http://reportincident.example.camel.apache.org"
                 type="tns:>inputReportIncident" xmlns:tns="http://reportincident.example.camel.apache.org"/>
      </operation>
      <parameter name="allowedMethods" value="reportIncident"/>

      <typeMapping
        xmlns:ns="http://reportincident.example.camel.apache.org"
        qname="ns:>outputReportIncident"
        type="java:org.apache.camel.example.reportincident.OutputReportIncident"
        serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
        deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
        encodingStyle=""
      />
      <typeMapping
        xmlns:ns="http://reportincident.example.camel.apache.org"
        qname="ns:>inputReportIncident"
        type="java:org.apache.camel.example.reportincident.InputReportIncident"
        serializer="org.apache.axis.encoding.ser.BeanSerializerFactory"
        deserializer="org.apache.axis.encoding.ser.BeanDeserializerFactory"
        encodingStyle=""
      />
  </service>

  <!-- part of Axis configuration -->
	<transport name="http">
		<requestFlow>
			<handler type="URLMapper"/>
			<handler type="java:org.apache.axis.handlers.http.HTTPAuthHandler"/>
		</requestFlow>
	</transport>
</deployment>

The globalConfiguration and transport is not in the deploy.wsdd file so you gotta write that yourself. The service is a 100% copy from deploy.wsdd. Axis has more configuration to it than shown here, but then you should check the AXIS documentation.

What we need to do now is important, as we need to modify the above configuration to use our webservice class than the default one, so we the classname parameter to:

<parameter name="className" value="org.apache.camel.example.axis.AxisReportIncidentService"/>

Running the Example

Now we are ready to run our example for the first time, so we use Jetty as the quick web container using its maven command:

mvn jetty:run

Then we can hit the webbrowser and enter this URL: http://localhost:8080/camel-example-axis/services and you should see the famous AXIS page with the text And now... Some Services.

Clicking on the .wsdl link shows the wsdl file, but what. It's an autogenerated one and not our original .wsdl file. So we need to fix this ASAP and this is done by configuring AXIS in the server-config.wsdd file:

  <service name="ReportIncidentPort" provider="java:RPC" style="document" use="literal">
    <wsdlFile>/WEB-INF/wsdl/report_incident.wsdl</wsdlFile>
    ...

We do this by adding the wsdlFile tag in the service element where we can point to the real .wsdl file.

Integrating Spring

First we need to add its dependencies to the pom.xml.

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>2.5.5</version>
        </dependency>

Spring is integrated just as it would like to, we add its listener to the web.xml and a context parameter to be able to configure precisely what spring xml files to use:

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:axis-example-context.xml
        </param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

Next is to add a plain spring XML file named axis-example-context.xml in the src/main/resources folder.

<?xml version="1.0" encoding="UTF-8"?>
<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-2.5.xsd">

</beans>

The spring XML file is currently empty. We hit jetty again with mvn jetty:run just to make sure Spring was setup correctly.

Using Spring

We would like to be able to get hold of the Spring ApplicationContext from our webservice so we can get access to the glory spring, but how do we do this? And our webservice class AxisReportIncidentService is created and managed by AXIS we want to let Spring do this. So we have two problems.

We solve these problems by creating a delegate class that AXIS creates, and this delegate class gets hold on Spring and then gets our real webservice as a spring bean and invoke the service.

First we create a new class that is 100% independent from AXIS and just a plain POJO. This is our real service.

package org.apache.camel.example.axis;

import org.apache.camel.example.reportincident.InputReportIncident;
import org.apache.camel.example.reportincident.OutputReportIncident;

/**
 * Our real service that is not tied to AXIS
 */
public class ReportIncidentService  {

    public OutputReportIncident reportIncident(InputReportIncident parameters) {
        System.out.println("Hello ReportIncidentService is called from " + parameters.getGivenName());

        OutputReportIncident out = new OutputReportIncident();
        out.setCode("OK");
        return out;
    }

}

So now we need to get from AxisReportIncidentService to this one ReportIncidentService using Spring. Well first of all we add our real service to spring XML configuration file so Spring can handle its lifecycle:

<?xml version="1.0" encoding="UTF-8"?>
<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-2.5.xsd">
    
    <bean id="incidentservice" class="org.apache.camel.example.axis.ReportIncidentService"/>

</beans>

And then we need to modify AxisReportIncidentService to use Spring to lookup the spring bean id="incidentservice" and delegate the call. We do this by extending the spring class org.springframework.remoting.jaxrpc.ServletEndpointSupport so the refactored code is:

package org.apache.camel.example.axis;

import org.apache.camel.example.reportincident.InputReportIncident;
import org.apache.camel.example.reportincident.OutputReportIncident;
import org.apache.camel.example.reportincident.ReportIncidentService_PortType;
import org.springframework.remoting.jaxrpc.ServletEndpointSupport;

import java.rmi.RemoteException;

/**
 * Axis webservice
 */
public class AxisReportIncidentService extends ServletEndpointSupport implements ReportIncidentService_PortType {

    public OutputReportIncident reportIncident(InputReportIncident parameters) throws RemoteException {
        // get hold of the spring bean from the application context
        ReportIncidentService service = (ReportIncidentService) getApplicationContext().getBean("incidentservice");

        // delegate to the real service
        return service.reportIncident(parameters);
    }

}

To see if everything is okay we run mvn jetty:run.

Integrating Camel

Again the first step is to add the dependencies to the maven pom.xml file:

        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-core</artifactId>
            <version>1.4.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.camel</groupId>
            <artifactId>camel-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

Now that we have integrated with Spring then we easily integrate with Camel as Camel works well with Spring.

Camel does not require Spring

Camel does not require Spring, we could easily have used Camel without Spring, but most users prefer to use Spring also.

We choose to integrate Camel in the Spring XML file so we add the camel namespace and the schema location:

xmlns:camel="http://activemq.apache.org/camel/schema/spring"
http://activemq.apache.org/camel/schema/spring http://activemq.apache.org/camel/schema/spring/camel-spring.xsd"

CamelContext

CamelContext is the heart of Camel its where all the routes, endpoints, components, etc. is registered. So we setup a CamelContext and the spring XML files looks like:

<?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:camel="http://activemq.apache.org/camel/schema/spring"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
         http://activemq.apache.org/camel/schema/spring http://activemq.apache.org/camel/schema/spring/camel-spring.xsd">

    <bean id="incidentservice" class="org.apache.camel.example.axis.ReportIncidentService"/>

    <camel:camelContext id="camel">
        <!-- TODO: Here we can add Camel stuff -->
    </camel:camelContext>

</beans>

Store a file backup

We want to store the web service request as a file before we return a response. To do this we want to send the file content as a message to an endpoint that produces the file. So we need to do two steps:

  • configure the file backup endpoint
  • send the message to the endpoint

The endpoint is configured in spring XML so we just add it as:

    <camel:camelContext id="camelContext">
        <!-- endpoint named backup that is configued as a file component -->
        <camel:endpoint id="backup" uri="file://target?append=false"/>
    </camel:camelContext>

In the CamelContext we have defined our endpoint with the id backup and configured it use the URL notation that we know from the internet. Its a file scheme that accepts a context and some options. The contest is target and its the folder to store the file. The option is just as the internet with ? and & for subsequent options. We configure it to not append, meaning than any existing file will be overwritten. See the File component for options and how to use the camel file endpoint.

Next up is to be able to send a message to this endpoint. The easiest way is to use a CamelProducer. A CamelProducer is inspired by Spring template pattern with for instance JmsTemplate or JdbcTemplate in mind. The template that all the grunt work and exposes a simple interface to the end-user where he/she can set the payload to send. Then the template will do proper resource handling and all related issues in that regard. But how do we get hold of such a template? Well the CamelContext is able to provide one. This is done by configuring the template on the camel context in the spring XML as:

    <camel:camelContext id="camelContext">
        <!-- producer template exposed with this id -->
        <camel:template id="camelTemplate"/>

        <!-- endpoint named backup that is configued as a file component -->
        <camel:endpoint id="backup" uri="file://target?append=false"/>
    </camel:camelContext>

Then we can expose a ProducerTemplate property on our service with a setter in the Java code as:

public class ReportIncidentService {

    private ProducerTemplate template;

    public void setTemplate(ProducerTemplate template) {
        this.template = template;
    }

And then let Spring handle the dependency inject as below:

    <bean id="incidentservice" class="org.apache.camel.example.axis.ReportIncidentService">
        <!-- set the producer template to use from the camel context below -->
        <property name="template" ref="camelTemplate"/>
    </bean>

Now we are ready to use the producer template in our service to send the payload to the endpoint. The template has many sendXXX methods for this purpose. But before we send the payload to the file endpoint we must also specify what filename to store the file as. This is done by sending meta data with the payload. In Camel metadata is sent as headers. Headers is just a plain Map<String, Object>. So if we needed to send several metadata then we could construct an ordinary HashMap and put the values in there. But as we just need to send one header with the filename Camel has a convenient send method sendBodyAndHeader so we choose this one.

    public OutputReportIncident reportIncident(InputReportIncident parameters) {
        System.out.println("Hello ReportIncidentService is called from " + parameters.getGivenName());

        String data = parameters.getDetails();

        // store the data as a file
        String filename = parameters.getIncidentId() + ".txt";
        // send the data to the endpoint and the header contains what filename it should be stored as
        template.sendBodyAndHeader("backup", data, "org.apache.camel.file.name", filename);

        OutputReportIncident out = new OutputReportIncident();
        out.setCode("OK");
        return out;
    }

The template in the code above uses 4 parameters:

  • the endpoint name, in this case the id referring to the endpoint defined in Spring XML in the camelContext element.
  • the payload, can be any kind of object
  • the key for the header, in this case a Camel keyword to set the filename
  • and the value for the header

Running the example

We start our integration with maven using mvn jetty:run. Then we open a browser and hit http://localhost:8080. Jetty is so smart that it display a frontpage with links to the deployed application so just hit the link and you get our application. Now we hit append /services to the URL to access the AXIS frontpage. The URL should be http://localhost:8080/camel-example-axis/services.

You can then test it using a web service test tools such as SoapUI.
Hitting the service will output to the console

2008-09-06 15:01:41.718::INFO:  Started SelectChannelConnector @ 0.0.0.0:8080
[INFO] Started Jetty Server
Hello ReportIncidentService is called from Ibsen

And there should be a file in the target subfolder.

dir target /b
123.txt

Unit Testing

TODO: How to embed Jetty and run a unit test by hitting a webservice call and expect the file output

  • No labels