Combined SOAP and REST Web Service

The soaprest project is a framework for developing web service endpoints that accept both SOAP and REST invocations. One of the design problems in web services is that one must decide whether to use SOAP or REST. Existing frameworks limit a particular endpoint to be either SOAP or REST. A soaprest endpoint is specified as a SOAP web service using WSDL, but will also accept REST invocations, and will respond according to how a request was invoked. This gives one the advantages of both SOAP and REST in a single web service.

This is an experimental project, and it does not support all of the features of Java Web Services. It also maps from SOAP to REST, not the other way around, so only a few of the REST parameter styles are used. The mapping from SOAP to REST is the following:

  1. An operation can be invoked RESTfully using either the GET or POST methods. PUT and DELETE are supported only for operations that do not have a return value.
  2. A RESTful invocation using POST is nearly identical to the invocation using SOAP. The only difference is that the SOAP envelope and body elements must not be used. If they are used, then the invocation cannot be distinguished from a SOAP invocation, and will be treated as such.
  3. RESTful invocation using GET is possible only if the parameters of the SOAP request are all of type String or convertible from String. Conversion is discussed in more detail later.
  4. To invoke a web service using GET, the operation is specified by the last path segment of the URL. The parameters are specified as name-value pairs in the query part of the URL. Each parameter has at most one value. Unspecified parameters will be given the default value if there is one for the parameter. Otherwise, they will be null. A default value may be specified using the soaprest.DefaultValue annotation. Default values are specified as strings even when the actual default value has another type.
  5. For either GET or POST, the return value of a RESTful invocation is the same as the body of the SOAP envelope that would be produced by a SOAP invocation.
  6. Operation names of an SEI can never be overloaded. One cannot have two operations with the same name in the same SEI even when they have different signatures.
  7. The web service implementation need not be a class that implements the SEI. The soaprest framework acquires all of its annotations from the web service implementation, and never examines the SEI. This allows methods of the implementation class to have overloaded names and to throw exceptions that are not declared in the SEI. When overloading names, one must specify different operation names with the WebMethod annotations. However, remember that the client uses the SEI, not the implementation.

The SOAP web service is relatively general, but a few restrictions are necessary for the mapping to REST to be possible:

  1. One must develop two classes: an implementation class and a provider (endpoint) class. The provider class is the one that is deployed. All other classes and files can be generated.
  2. The WSDL and schema documents can either be developed manually or by using wsgen. The location of the WSDL must be set when the provider class is constructed.
  3. The classes that reify the web service operations (i.e., the request and response classes) must be in the classpath. The ObjectFactory classes (one per package) must also be in the classpath. These classes can either be developed manually or by using wsimport. Unlike JWS, one cannot use a jaxb.index file in lieu of an ObjectFactory class in a package. The generated interface and the generated Service class do not have to be in the classpath. However, SOAP clients will need the interface.
  4. The implementation class must be annotated. Here is an example of such an annotation:
    @WebService(name = "Plastering", targetNamespace = "http://xyz.org")
    public class PlasteringImpl implements PlasteringInterface {
    
    Some annotations can be omitted, but one should not depend on this behavior. More information about the annotations is shown below. The soaprest code tries to find default values, but they might not be the same ones that JWS would use.
  5. The web methods and web parameters of web methods of the implementation class should be annotated.
  6. The implementation class can be deployed in a container such as glassfish that supports web services, but when this is done, the web service will be accessible only by means of SOAP. In order to support both SOAP and REST, one must deploy the provider class.

The web service may be deployed either as a stand-alone service or as a component in a servlet container. Note that the provider must be deployed, not the implementation class. The container can be any servlet container. It is not necessary to have a container that supports web services. The endpoint (provider) class must have the following properties:

  1. It must be a subclass of {@link soaprest.DualProvider}.
  2. If it is to be deployed only as a stand-alone service, then it must be annotated as a WebServiceProvider and the ServiceMode must be PAYLOAD. Such a provider cannot be deployed in a container.
  3. If it is to be deployed either as a stand-alone service or in a servlet container, then it must be annotated as a SoapRestProvider.
  4. The constructor must invoke the superclass constructor with the implementation object and the list of package names of the packages that have JAXB serializable classes. These packages must have ObjectFactory classes.
  5. If it is to be deployed only as a stand-alone service, then the constructor may have parameters.
  6. If it is to be deployed in a servlet container, then the constructor must be parameterless. Any parameters are normally specified in the web.xml file and extracted using the getInitParameter method after the endpoint has been deployed. This is normally done by overriding the init() method.
  7. To deploy the service in a stand-alone manner, the main program must construct an endpoint object and then deploy it.
  8. The test service shows how all of the above are accomplished. For more information about the test service, see the documentation in the test package.

The following are the annotations used by the soaprest package:

Location of annotationAnnotationDiscussion
The endpoint classWebServiceProviderRequired for stand-alone deployment
ServiceModeRequired for stand-alone deployment and must have value Service.Mode.PAYLOAD
SoapRestProviderRequired for servlet container deployment
The implementation classWebServiceRequired and should specify the name of the web service.
DocOptional documentation for the REST API.
An operation (web method)WebMethodRequired and may specify an operationName which overrides the name of the method
RequestWrapperRequired for specifying the className
ResponseWrapperRequired for specifying the className
OnewayOptional specification of a web service operation that does not return. A one-way operation may only be RESTfully invoked using the HTTP PUT and DELETE methods. A two-way operation may only be RESTfully invoked using the HTTP GET and POST methods. SOAP requests are invoked with the HTTP POST method whether they are one-way or two-way.
DocOptional documentation for the REST API.
HTTPMethodsOptional restriction on the HTTP methods that may be used on the operation. See the discussion of the Oneway annotation for the defaults. Note that one can restrict the defaults, but one cannot remove any of the default restrictions.
The request wrapper of a web methodXmlTypeRequired for specifying the propOrder
The response wrapper of a web methodXmlTypeRequired for specifying the propOrder. There must be exactly one property in a response wrapper.
A parameter of a web methodWebParamRequired for specifying the parameter name
WebParam.modeMust be IN
DefaultValueOptional specification of the default value for a parameter convertible from a string
DocOptional documentation for the REST API. The title is used to explain the parameter in the HTTP signature.

Conversions are a large topic, but one special case is provided by the soaprest package. A class is convertible from String if the class has a constructor with a single String parameter and does not throw any exceptions. Conversion to String uses the toString method. Primitive types are also convertible from String by means of autoboxing and unboxing. However, primitive types do not accept null arguments.

When an operation is invoked with a GET, the parameters will automatically be converted to the target types by the soaprest package. The same is true for primitive types. When an operation that has non-primitive parameters that are convertible from String is invoked with a POST (for example, when it is invoked using SOAP), then one must modify the request wrapper classes that are generated by wsimport so that the parameters have the target types. For a return value, one must modify the response wrapper classes in a similar way.

The modification of the wrapper classes can be specified using JAXB annotations in the XSD part of the WSDL. Here is how this is done:


 <xsd:simpleType name="ConvertibleType">
  <xsd:annotation> 
   <xsd:appinfo>
    <jaxb:javaType name="org.project.MyConveribleType"/>
   </xsd:appinfo>
  </xsd:annotation>  
  <xsd:restriction base="xsd:string"/>
 </xsd:simpleType> 

The annotation tells wsimport (or more precisely, xjc) to use org.project.MyConveribleType instead of String in the generated code.

One would think that the classes URL and URI would be convertible from String, but their constructors throw an exception if the argument is not correctly formed. However, if one modifies the generated code manually, then the web service works fine.

As with any experimental project,

Copyright (c) 2012 Ken Baclawski. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY KEN BACLAWSKI ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEN BACLAWSKI OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.