ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Playing Together Nicely: Getting REST and SOAP to Share Each Other's Toys
Pages: 1, 2, 3, 4

SOAP Interface #2: Missing Schemas

In creating the SOAP "mapping," we need to take into account some fundamental characteristics that will be generally true across most REST resources:



  1. A GET request will not contain a body. Therefore, we need to create an XML schema for the body content of the associated SOAP request (of course, we don't need to create the schema for the response, because we will use the response from the GET).
  2. A PUT request will contain a body, and may return a response, in which case default schemas would be required for neither requests nor responses.
  3. A DELETE request will not contain a body, but may return a response (the contents of the item deleted), so will require a separate schema for the SOAP requests.

For the purposes of this article, neither my DELETE nor PUT methods will return a response--hence, default schemas will be required.

That aside, the main difficulty we have mapping a SOAP call to a REST resource method is that of data identification (or naming). A REST resource is identified by its URI: /order/123A, /stock/ORCL. A SOAP web service can also be identified by a URI (for example, /GetLocalReservations?code=ABCD) but may just as likely be identified within the SOAP message itself. Hence, the URI will have no identifying characteristics other than that of distinguishing the service method itself. Hopefully, the reason for including a Reference element in both the BasketRQ and BasketRS definitions has now become clear. For a REST resource, the Reference isn't used and will be ignored. For a SOAP service, it is a necessary part of identifying the data element in question.

Consequently, the definitions of GenericRQ and GenericRS, which are used where a REST resource does not require content for a request or for responses (but a SOAP request does need this data), also include the Reference element, although in these cases it is no longer optional:

<xs:complexType name="Reference">
    <xs:sequence>
        <xs:element name="Name" minOccurs="0" maxOccurs="1">
            <xs:simpleType>
                <xs:restriction base="xs:string">
                    <xs:minLength value='1'/>
                </xs:restriction>
            </xs:simpleType>
        </xs:element>
        <xs:element name="Parameter" minOccurs="0" maxOccurs="unbounded">
            <xs:complexType>
                <xs:sequence>
                    <xs:element name="Param" type="xs:string" />
                    <xs:element name="Value" type="xs:string" />
                </xs:sequence>
            </xs:complexType>
        </xs:element>
    </xs:sequence>
</xs:complexType>

<xs:element name="GenericRQ">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="Reference" type="Reference" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

<xs:element name="GenericRS">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="Reference" type="Reference" />
            <xs:element name="Status" type="xs:positiveInteger" />
            <xs:element name="Message" type="xs:string" />
        </xs:sequence>
    </xs:complexType>
</xs:element>

A GenericRQ identifies data by its Reference, while the response adds the HTTP status code and message. A Reference is, in turn, made up of a name and any number of Parameter elements--required in order to reproduce the information that is included in a typical REST resource URI (where parameters may be used to filter the data returned, uniquely identify subsets of data, and so on).

The doPut method of the SOAP interface (found in the Jython script soap.py), is used for all SOAP requests to the system, and is (excerpted) below:

def doPost(self, request, response):
    path = request.getPathInfo()[1:]
    
    # use jaxb to parse the soap content
    s = pyutils.read(request)
    soapobj = pyutils.unmarshal('testbeans', s)
    obj = soapobj.getBody().getAny().get(0)
    
    sw = soapservices[path]
    
    body = ''
    
    # build the URI from the context path, service name
    uri = '%s/%s' % (request.getContextPath(), sw.name)
    
    ref = obj.getReference()
    
    # if the reference has a name element then add it to the uri
    if ref.getName():
        uri = uri + '/' + obj.getReference().getName()
        
    # if the reference has parameters, add them as the query string
    if ref.getParameter().size() > 0:
        uri = uri + '?'
        length = ref.getParameter().size()
        for x in xrange(0, length):
            p = ref.getParameter().get(x)
            uri = uri + p.getParam() + '=' + p.getValue()
            
            if x &lt; length-1:
                uri = uri + '&'
                
    # if the REST method is a PUT, turn the data obj back into xml format
    if sw.httpmethod == 'PUT':
        body = pyutils.marshal('testbeans', obj)
    
    # send the request to the REST resource, using the correct HTTP method
    (status, reason, headers, content) = pyutils.httprequest('%s:%s' 
                % (request.getServerName(), request.getServerPort()), 
                   sw.httpmethod, uri, body, {}) 
    
    response.setContentType('text/xml')
    w = response.getWriter()
    
    if status == 200 and content != '':
        # return successful results
        content = xmldecl_re.sub('', content)
        w.write(soapcontent % content)
    else:
        # errors are returned as a GenericRS
        rs = objfactory.createGenericRS()
        
        rs.setReference(obj.getReference())
        rs.setMessage(urllib.unquote_plus(reason))
        rs.setStatus(BigInteger('%s' % status))

        xml = pyutils.marshal('testbeans', rs)
        xml = xmldecl_re.sub('', xml)
        w.write(soapcontent % xml)

As you can see, the basic process for this method is:

  1. Parse the content into a Java object.
  2. Get the reference from the object and then turn this into a URI for calling the REST resource.
  3. Call the resource, and in the event of a successful result with content, write this back to the client.
  4. In the case of an unsuccessful result, or no content, write a GenericRS back to the client including the status and message from the REST resource.

What is perhaps not immediately obvious from this code is the fact that JAXB is unmarshalling the SOAP envelope (soapobj = pyutils.unmarshal('testbeans', s)), while the body content (retrieved via obj = soapobj.getBody().getAny().get(0)) is then remarshalled to send on to the REST resource. This is accomplished by way of the XML schema for SOAP envelopes (envelope.xsd), large chunks of which have been hacked out in order to get it working successfully with the version of JAXB I'm using (you will also find this somewhat-mangled version in the source code for this article).

Pages: 1, 2, 3, 4

Next Pagearrow