How to implement web services?

We will go through creating and implementating web services in our openhub-example module.

WSDL and XSD implementation

  • WSDL and XSD is defined in Contract-First design approach - firstly define web service interfaces, secondly implement them in specific language.
  • there are two files (in package resources/org.openhubframework.openhub.modules.in.hello.ws.v1_0): 
    • WSDL definition (e.g. hello-v1.0.wsdl) 
    • XSD with requests/responses definition (helloOperations-v1.0.xsd). XSD contains request definitions, imports commonTypes-v1.0.xsd with common types from openhub-core module

Versioning

Use versions in format <major>.<minor>. 

Minor version is for compatible changes, major version indicates non-compatible changes.

Versioning should be explicit - it means to present version number in elements, URLs etc.:

  • add major and minor version to WSDL name: MyService-v1.2.wsdl
  • add major version to targetNamespace of WSDL: <definition
  targetNamespace="http://openhubframework.org/ws/MyService-v1"
  xmlns="http://schemas.xmlsoap.org/wsdl/">
  • add major and minor version to portType element: 
<portType name="MyServicePort-v1.2">
  • add major and minor version to  service element: 
<service name="MyService-v1.2">
  • add major and minor version to endpoint: <soap:address
  location="http://openhubframework.org/myService/v1"/>

If there is change in WSDL's version then change versions of XSDs as well.

See Best practices for more development tips.

hello-v1.0.wsdl:

  • traceHeader is mandatory for asynchronnous messages
  • it's good practice to adhere some name conventions, for example those presented in Best practices


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
                  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
                  xmlns:xs="http://www.w3.org/2001/XMLSchema"
                  xmlns:cc="http://openhubframework.org/ws/Common-v1"
                  xmlns:tns="http://openhubframework.org/ws/HelloService-v1"
                  targetNamespace="http://openhubframework.org/ws/HelloService-v1">

    <wsdl:types>
        <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified">
            <xs:import namespace="http://openhubframework.org/ws/HelloService-v1" schemaLocation="helloOperations-v1.0.xsd"/>
        </xs:schema>
    </wsdl:types>

    <wsdl:message name="traceHeaderMsg">
        <wsdl:part element="cc:traceHeader" name="traceHeader"/>
    </wsdl:message>

    <wsdl:message name="syncHelloRequestMsg">
        <wsdl:part element="tns:syncHelloRequest" name="syncHelloRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="syncHelloResponseMsg">
        <wsdl:part element="tns:syncHelloResponse" name="syncHelloResponse">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="asyncHelloRequestMsg">
        <wsdl:part element="tns:asyncHelloRequest" name="asyncHelloRequest">
        </wsdl:part>
    </wsdl:message>
    <wsdl:message name="asyncHelloResponseMsg">
        <wsdl:part element="tns:asyncHelloResponse" name="asyncHelloResponse">
        </wsdl:part>
    </wsdl:message>


    <wsdl:portType name="hello-v1.0">
        <wsdl:operation name="syncHello">
            <wsdl:input message="tns:syncHelloRequestMsg" name="syncHelloRequest">
            </wsdl:input>
            <wsdl:output message="tns:syncHelloResponseMsg" name="syncHelloResponse">
            </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="asyncHello">
            <wsdl:input message="tns:asyncHelloRequestMsg" name="asyncHelloRequest">
            </wsdl:input>
            <wsdl:output message="tns:asyncHelloResponseMsg" name="asyncHelloResponse">
            </wsdl:output>
        </wsdl:operation>
    </wsdl:portType>


    <wsdl:binding name="helloBindingSoap11-v1.0" type="tns:hello-v1.0">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>

        <wsdl:operation name="syncHello">
            <soap:operation soapAction=""/>
            <wsdl:input name="syncHelloRequest">
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="syncHelloResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
        <wsdl:operation name="asyncHello">
            <soap:operation soapAction=""/>
            <wsdl:input name="asyncHelloRequest">
                <soap:header message="tns:traceHeaderMsg" part="traceHeader" use="literal"/>
                <soap:body use="literal"/>
            </wsdl:input>
            <wsdl:output name="asyncHelloResponse">
                <soap:body use="literal"/>
            </wsdl:output>
        </wsdl:operation>
    </wsdl:binding>


    <wsdl:service name="helloService-v1.0">
        <wsdl:port binding="tns:helloBindingSoap11-v1.0" name="helloSoap11-v1.0">
            <soap:address location="/ws/hello/v1"/>
        </wsdl:port>
    </wsdl:service>

</wsdl:definitions>

helloOperations-v1.0.xsd:

  • this XSD contains requests and responses for each operation
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
           xmlns:cc="http://openhubframework.org/ws/Common-v1"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           targetNamespace="http://openhubframework.org/ws/HelloService-v1">

    <!--
        Note: all XSD/WSDL files are copied together at one place during XJC (Maven) code generation
    -->
    <xs:import namespace="http://openhubframework.org/ws/Common-v1" schemaLocation="commonTypes-v1.0.xsd"/>

    <!-- syncHello -->
    <xs:element name="syncHelloRequest">
        <xs:annotation>
            <xs:documentation>Synchronous calling of hello service</xs:documentation>
        </xs:annotation>

        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string">
                    <xs:annotation>
                        <xs:documentation>Greeting's name</xs:documentation>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="syncHelloResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="greeting" type="xs:string"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>


    <!-- asyncHello -->
    <xs:element name="asyncHelloRequest">
        <xs:annotation>
            <xs:documentation>Asynchronous calling of hello service</xs:documentation>
        </xs:annotation>

        <xs:complexType>
            <xs:sequence>
                <xs:element name="name" type="xs:string">
                    <xs:annotation>
                        <xs:documentation>Greeting's name</xs:documentation>
                    </xs:annotation>
                </xs:element>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

    <xs:element name="asyncHelloResponse">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="confirmAsyncHello" type="cc:callbackResponse"/>
            </xs:sequence>
        </xs:complexType>
    </xs:element>

</xs:schema>

Generating Java sources from WSDL/XSD

We use jaxws-maven-plugin plugin for generating Java classes from WSDL/XSD:

  • see pom.xml in openhub-example module for more details
  • we use two more plugins before generating Java classes
    • maven-dependency-plugin: copies resources (xjb, xsd) from other modules
    • maven-resources-plugin: copies all xjb, wsdl and xsd files to a single directory for generating Java model from WSDL/XSD,allowing WSDL/XSD files to reference each other easily with a simple relative path.
  • we defined two XJB binding files for use (both from core-api module)
    • jaxb_global_bindings.xjb: global Java type bindings, for example org.joda.time.DateTime vs xs:dateTime
    • jaxb_common_bindings.xjb: bindings of common types defined in commonTypes-v1.0.xsd


<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxws-maven-plugin</artifactId>
    <configuration>
        <sourceDestDir>${modules.output.directory}</sourceDestDir>
        <wsdlDirectory>${modules.import.directory}</wsdlDirectory>
        <bindingDirectory>${modules.import.directory}</bindingDirectory>
        <bindingFiles>
            <bindingFile>jaxb_global_bindings.xjb</bindingFile>
        </bindingFiles>
    </configuration>
    <executions>
        <execution>
            <id>WSDL-import-in-hello-model</id>
            <goals>
                <goal>wsimport</goal>
            </goals>
            <configuration>
                <staleFile>${project.build.directory}/jaxws/.in.hello.model</staleFile>
                <packageName>org.openhubframework.openhub.modules.in.hello.model</packageName>
                <wsdlFiles>
                    <wsdlFile>hello-v1.0.wsdl</wsdlFile>
                </wsdlFiles>
                <bindingFiles>
                    <bindingFile>jaxb_global_bindings.xjb</bindingFile>
                    <bindingFile>jaxb_common_bindings.xjb</bindingFile>
                </bindingFiles>
            </configuration>
        </execution>
    </executions>
</plugin>

Publishing WSDL/XSD

Web service configuration for openhub-example module is in org.openhubframework.openhub.modules.ExampleWebServiceConfig:

  • helloWsdl() publishes WSDL
  • helloOperations() publishes XSD with requests and responses
  • publishing WSDL and XSD files have to be published separately. See Spring Web Services reference manual where you find more information because Spring WS is underlying library for web service communication.
@Configuration
@Profile(ExampleProperties.EXAMPLE_PROFILE)
public class ExampleWebServiceConfig {

    static final ClassPathResource HELLO_OPERATIONS_XSD_RESOURCE = new ClassPathResource(
            "org/openhubframework/openhub/modules/in/hello/ws/v1_0/helloOperations-v1.0.xsd");

    @Bean(name = "helloOperations-v1.0")
    public XsdSchema helloOperations() {
        return new SimpleXsdSchema(HELLO_OPERATIONS_XSD_RESOURCE);
    }

    @Bean(name = "hello")
    public SimpleWsdl11Definition helloWsdl() {
        SimpleWsdl11Definition wsdl = new SimpleWsdl11Definition();
        wsdl.setWsdl(new ClassPathResource(
                "org/openhubframework/openhub/modules/in/hello/ws/v1_0/hello-v1.0.wsdl"));
        return wsdl;
    }
}

Implementation of WebServiceValidatingSources that registers XSD schemas for validation incoming/outgoing messages (traceHeader is mandatory for asynchronous requests only - set synchronnous requests to ignore requests)

@Component
@Profile(ExampleProperties.EXAMPLE_PROFILE)
public class ExampleWebServiceValidatingSources implements WebServiceValidatingSources {

    @Override
    public Resource[] getXsdSchemas() {
        return new Resource[] {ExampleWebServiceConfig.HELLO_OPERATIONS_XSD_RESOURCE};
    }

    @Override
    public String[] getIgnoreRequests() {
        return new String[] {"{http://openhubframework.org/ws/HelloService-v1}syncHelloRequest"};
    }
}

Route implementations

  • Camel has Spring web services (spring-ws) component for definition of incoming endpoint
  • use handy function getInWsUri() from parent class that creates spring-ws endpoint with all necessary settings
  • adhere name conventions with using getRouteId() method
  • classes SyncHelloRequest and SyncHelloResponse are generated by jaxws-maven-plugin mentioned above


@CamelConfiguration(value = SyncHelloRoute.ROUTE_BEAN)
@Profile(ExampleProperties.EXAMPLE_PROFILE)
public class SyncHelloRoute extends AbstractBasicRoute {

    static final String ROUTE_BEAN = "syncHelloRouteBean";

    private static final String OPERATION_NAME = "syncHello";

    private static final String ROUTE_ID_SYNC_HELLO = getRouteId(ServiceEnum.HELLO, OPERATION_NAME);

    static final String HELLO_SERVICE_NS = "http://openhubframework.org/ws/HelloService-v1";

    @Override
    protected void doConfigure() throws Exception {
        from(getInWsUri(new QName(HELLO_SERVICE_NS, "syncHelloRequest")))
                .routeId(ROUTE_ID_SYNC_HELLO)

                .policy(RouteConstants.WS_AUTH_POLICY)

                .to("throttling:sync:" + OPERATION_NAME)

                .unmarshal(jaxb(SyncHelloRequest.class))

                .log(LoggingLevel.DEBUG, "Calling hello service with name: ${body.name}")

                .bean(this, "composeGreeting")

                .marshal(jaxb(SyncHelloResponse.class));
    }

    @Handler
    public SyncHelloResponse composeGreeting(@Body SyncHelloRequest req) {
        Assert.notNull(req, "req must not be null");

        String greeting = "Hello " + req.getName();

        SyncHelloResponse res = new SyncHelloResponse();
        res.setGreeting(greeting);
        return res;
    }
}

Unit test implementations

@ActiveRoutes(classes = SyncHelloRoute.class)
public class SyncHelloRouteTest extends AbstractExampleModuleTest {

    @Produce(uri = TestWsUriBuilder.URI_WS_IN + "syncHelloRequest")
    private ProducerTemplate producer;

    @EndpointInject(uri = "mock:test")
    private MockEndpoint mock;

    /**
     * Checking successful calling.
     */
    @Test
    public void testSyncHelloRoute() throws Exception {
        String xml = "<syncHelloRequest xmlns=\"http://openhubframework.org/ws/HelloService-v1\">"
                + "    <name>Mr. Parker</name>"
                + "</syncHelloRequest>";

        String output = producer.requestBody((Object)xml, String.class);
        SyncHelloResponse res = Tools.unmarshalFromXml(output, SyncHelloResponse.class);

        assertThat(res, notNullValue());
        assertThat(res.getGreeting(), is("Hello Mr. Parker"));
    }
}