Introduction

This is a guide to developing applications to host in an InstantSOAP services. It will describe a range of ways that you can expose your buisness logic as an InstantSOAP application. By the end of this tutorial, you should be able to write services exposing simple string transformations and complex map to map transformations.

This guide assumes that you have run through the Getting Started guide and have deployed the echo-test war at the default location on the current machine. We also assume that you will be developing your client using CXF as your web-services toolkit.

A brief introduction to the server-side architecture

The InstantSOAP web service can be configured in a multitude of ways. In the project instantsoap-ws-cxf, the details are specified by a Spring context. In this guide we will assume the default deployment is being used.

The default configuration mounts an instance of WebServiceDispatcherImpl at the endpoint /instantsoap/applications relative to the WAR mountpoint. This object is configured with a stack of handlers and delegates that implement asynchronous handling of requests, queing and forwarding requests to strategies that can actually process them.

When a request has been pushed all the way down the stack, it will eventually hit an object implementing StrategyDispatcher. These are responsible for binding the name of the application to the behavior. In the default deployment, there are three dispatchers mounted. The first one exposes an interface that directly transforms the inputs map into the responses map. The second one requries that there is a single input and output, and provides an interface that transforms this input string into the output string. The third one takes the inputs and combines them with a description of a command-line. It then executes that command-line with those inputs and returns the response.

To avoid you needing to edit this spring configuration, the default stack is also configured for automatic discoverfy of service providers. For the string and map processors, this can be achieved by either using the standard Java service provider interface mechanism, or by a spring-based method. For command-lines, this is done by providing the command-line template information and listing the location of this file in a well-known resource.

After the service spring context has been made but before the service is invoked, all of the jars forming the classpath of the webapp will be searched for buisness-logic providers. So, as you work through the examples here, you can test them out by simply making sure they are in the WEB-INF/lib directrory of a copy of instantsoap-ws-cxf.war or instantsoap-ws-echoTest.war.

String Transformation

In this section, we will implement some buisness logic as simple string transformations.

Firstly, we need to set up the pom.xml dependencies. To implement the string provider API, you need to make sure that instantsoap-string-processor is available.

<dependencies>
  <dependency>
    <groupId>uk.ac.ncl.cs.instantsoap</groupId>
    <artifactId>instantsoap-string-processor</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

Next, we will write the boilerplate needed to wrap our application.

package example;

import org.bjv2.util.serviceprovider.Spi;
import uk.ac.ncl.cs.instantsoap.stringprocessor.StringProcessor;
import uk.ac.ncl.cs.instantsoap.wsapi.InvalidJobSpecificationException;
import uk.ac.ncl.cs.instantsoap.wsapi.JobExecutionException;
import uk.ac.ncl.cs.instantsoap.wsapi.MetaData;
import static uk.ac.ncl.cs.instantsoap.wsapi.Wsapi.metaData;

@Spi
public class BobAppender
        implements StringProcessor
{
... your code goes here
}

The class is annotated with @Spi. This will ensure that the correct META-INF/services entries are written so that this StringProcessor can be discovered at run-time. The StringProcessor interface declares several methods.

StringProcessor Methods
Name Description
describeApplication The MetaData describing this service.
getInput The MetaData describing the single input parameter.
getOutput The MetaData describing the single output parameter.
validate Check that the input is well-formed.
process Return an output, given the input. This is the real implementation of the service.

Our BobAppender processor is going to append the string BOB on to the end of each input value. So that the user can introspect this, we say as much in our implementation of describeApplication.

public MetaData describeApplication()
{
    return metaData("bobAppender",
                    "Append the string 'BOB' to the end of the input");
}

We will name the input input and the output output.

    public MetaData getInput()
    {
        return metaData("input",
                        "The input string to process");
    }

    public MetaData getOutputs()
    {
        return metaData("output",
                        "The input string with 'BOB' appended");
    }

In this example, all input strings are eligable for processing, so validate doesn't have any work to do.

public void validate(String data)
        throws InvalidJobSpecificationException
{
    // nothing to do
}

Finally, we can implement process to do the actual work.

public String process(String data)
        throws JobExecutionException
{
    return data + "BOB";
}

If you compile this code into a jar and drop this into a modified instantsoap-ws-echoTest.war then you should now see in addition to the other available services, one called bobAppender.

Springy String Transformation

While some logic is fairly rigid, some can be configured in a wide range of ways. To illustrate this, we will implement annother appender processor but this time make the appended string configurable. This will use Spring to configure the deployment of the service at run-time. In this example, we will only show code that illustrates differences with the previous example, rather than going through everything line-by-line.

Because we are going to be configuring this bean through spring, we do not need to annotate it with @Spi.

public class StringAppender
        implements StringProcessor
{

Each of the configurable aspects of the service must be exposed as bean properties. In this case, it is only the suffix to be appended that is configurable.

    private String suffix = "";

    public String getSuffix()
    {
        return suffix;
    }

    public void setSuffix(String suffix)
    {
        this.suffix = suffix;
    }

We can use the value of the suffix to return customised meta data. For example, we can return an suffix-specific application description.

public MetaData describeApplication()
{
    return metaData("append" + suffix,
                    "Append the string '" + suffix
                    + "' to the end of the input");
}

Also, the process method must now return the input value with the suffix appended, rather than 'BOB'.

public String process(String data)
        throws JobExecutionException
{
    return data + suffix;
}

At this point, we have a beany string appender. The next step is to configure this and make it available to InstantSOAP. This is done by writing a spring configuration file and placing it at the right place in the JAR file. InstantSOAP follows the pattern that for a given class that will build instances from Spring files, there will be spring files under META-INF/spring/${fully-qualified-class-name}. In this case, the class is uk.ac.ncl.cs.instantsoap.strategydp.impl.SpringStringProcessorConfigurator. This file will be loaded by SpringStringProcessorConfigurator, and all of the beans that implement StringProcessor will be mounted as applications in the web application.

We can create any number of beans in this file. We can also create any number of beans that instantiate StringAppender, with different suffixes.

  <bean name="appendSaysHi" class="example.StringAppender">
    <property name="suffix" value=" Says Hi"/>
  </bean>

  <bean name="appendLover" class="example.StringAppender">
    <property name="suffix" value="-lover"/>
  </bean>

If you compile this code into a jar and drop this into a modified instantsoap-ws-echoTest.war then you should now see in addition to the other available services, one called "append Says Hi" and one called "append-lover".

You can obtain the compete source for this, and the previous example, including the pom, in tar.gz or zip formats.

Map transformation

In the general case, your buisness logic will require multiple inputs and will calculate multiple outputs. InstantSOAP provides a convenient way to deploy this kind of calculation via the MapProcessor interface.

Here we will go through this API, and implement processors both using the service provider interface and Spring. The examples given here will be limited to the code that is instructive.

The methods provided by MapProcessor are similar to those in StringProcessor. The major diferences are that firstly, the processor can handle multiple applications by name, or if you prefer, execute different buisness-logic when presented with different names. Because of this, most methods take the name of the application as their first parameter. The other difference is that the inputs and outputs are maps of strings, so the methods describing, validating and processing these are defined in terms of maps.

MapProcessor Methods
Name Description
handlesApplication True if this processor can handle applications by the given name, false otherwise.
listApplications Return a list of the names of all applications handled by this processor. The handlesApplication method returns true for exactly the names in this list.
describeApplication Given the name of an application, return the MetaData describing what it does.
getInputs Given the name of an application, return a Set of MetaData beans describing the inputs.
getInputs Given the name of an application, return a Set of MetaData beans describing the outputs.
validate Given the name of an application and a Map of inputs, check that the inputs match what is required. This will minimally check that all inputs listed by getInputs are present, and may also validate that the values fall within legal ranges.
process Given the name of an application and a Map of inputs, return a Map of outputs. This will minimally contain all of the outputs described by getOutputs.

Our first implementation of this bean will take two strings representing whole numbers, and return the result of dividing the first by the seccond and any remainder. Firstly, we must ensure that the rigth dependencies are being pulled in by maven.

<dependencies>
  <dependency>
    <groupId>uk.ac.ncl.cs.instantsoap</groupId>
    <artifactId>instantsoap-string-processor</artifactId>
    <version>1.0</version>
  </dependency>
</dependencies>

Next, because this class will be discoverd automatically, we must annotate it with @Spi.

@Spi
public class DivMod
        implements MapProcessor

The meta-data methods for this are a little different from those in the strings examples. They throw UnknownApplicationException if invoked with an appliation that they don't recognise.

    public boolean handlesApplication(String application)
    {
        return "divMod".equals(application);
    }

    public List<String> listApplications()
    {
        return Collections.singletonList("divMod");
    }

    public MetaData describeApplication(String application)
            throws UnknownApplicationException
    {
        if("divMod".equals(application))
        {
            return metaData("divMod",
                            "Calculate the division and modulus of two numbers.");
        }
        else
        {
            throw new UnknownApplicationException(
                    "We can't handle the application: " + application);
        }
    }

    public Set<MetaData> getInputs(String application)
            throws UnknownApplicationException
    {
        if ("divMod".equals(application))
        {
            return asSet(
                    metaData("numerator", "Number to be divided"),
                    metaData("denominator", "Number to divide by"));
        }
        else
        {
            throw new UnknownApplicationException(
                    "We can't handle the application: " + application);
        }
    }

    public Set<MetaData> getOutputs(String application)
            throws UnknownApplicationException
    {
        if ("divMod".equals(application))
        {
            return asSet(
                    metaData("quotient", "The whole-number result of the division"),
                    metaData("remainder", "The whole-number remainder of the division"));
        }
        else
        {
            throw new UnknownApplicationException(
                    "We can't handle the application: " + application);
        }
    }

During validation, the procerssor must check that all needed inputs are available, and that they are parseable as integers. It is much cheeper for InstantSOAP to catch errors at validation time than at execution time, so it is good practice to do what validation you can here.

    public void validate(String application, Map<String, String> inputs)
            throws InvalidJobSpecificationException, UnknownApplicationException
    {
        if ("divMod".equals(application))
        {
            for(MetaData i : getInputs(application))
            {
                if(!inputs.containsKey(i.getName()))
                {
                    throw new InvalidJobSpecificationException(
                            "Missing input: " + i.getName());
                }

                try
                {
                    Integer.parseInt(inputs.get(i.getName()));
                }
                catch (NumberFormatException e)
                {
                    throw new InvalidJobSpecificationException(
                            "Input "
                            + i.getName()
                            + " could not be parsed as an integer",
                            e);
                }
            }
        }
        else
        {
            throw new UnknownApplicationException(
                    "We can't handle the application: " + application);
        }
    }

Finally, we reach the code that will do the work. It calculates the division and remainder and returns these.

    public Map<String, String> process(String application, Map<String, String> inputs)
            throws UnknownApplicationException, JobExecutionException
    {
        if ("divMod".equals(application))
        {
            int numerator = Integer.parseInt(inputs.get("numerator"));
            int divisor = Integer.parseInt(inputs.get("divisor"));
            int quotient = numerator / divisor;
            int remainder = numerator % divisor;

            return asMap(pair("quotient", Integer.toString(quotient)),
                         pair("remainder", Integer.toString(remainder)));
        }
        else
        {
            throw new UnknownApplicationException(
                    "We can't handle the application: " + application);
        }
    }

You can expose this functionality as you did for the string examples, by making a modified instantsoap-ws-echoTest WAR.

You can obtain the compete source for this example, including the pom, in tar.gz or zip formats.

Springy Map Transformation

Just as the String transformations can be configured using Spring, so can Map transformations. Exactly the same process is followed. The spring file is named uk.ac.ncl.cs.instantsoap.strategydp.impl.SpringMapProcessorConfigurator in this case. Try to develop a map processor that uses an operation field to store an enumeration of PLUS, MINUS, MULTIPLY, DIVIDE, takes two arguments that it parses as doubles, and returns the result of applying this operator to them in an output named after the operator. Using Spring, deploy all four cases.