Difference between revisions of "Common-gcore-stubs"

From Gcube Wiki
Jump to: navigation, search
(Wrapped or Bare?)
Line 221: Line 221:
 
=== Wrapped or Bare? ===
 
=== Wrapped or Bare? ===
  
An important way in which operations differ within and across services is in the general 'pattern' adopted in the WSDL to define their request and response elements. We've [[#The Service Endpoint Interface|already]] discussed briefly the difference between the <code>BARE</code> and <code>WRAPPED</code> patterns:
+
An important way in which operations differ within and across services is in the 'pattern' adopted in the WSDL to define their request and response elements. For our document/literal services, the well-known patterns are <code>BARE</code> and <code>WRAPPED</code>. For gCore services the difference between the two boils down to the following:
  
* in the <code>BARE</code> pattern, request and response elements are the actual payloads of the operation, i.e. the inputs and outputs.  
+
* in the <code>BARE</code> pattern, request and responses have simple content.  
* In the <code>WRAPPED</code> pattern, request and response elements serve solely as operation-specific containers for the actual payloads.
+
* In the <code>WRAPPED</code> pattern, they have complex context.
  
In the example [[#A Simple Service|above]], Acme used the <code>BARE</code> pattern for its payloads made of single atomic values:
+
Clearly, the difference arises only when operations take or return simple types, otherwise the pattern is necessarily <code>WRAPPED</code>.
 +
 
 +
In the example [[#A Simple Service|above]], Acme used the <code>BARE</code> pattern:
  
 
<source lang="xml">
 
<source lang="xml">
Line 235: Line 237:
 
</source>
 
</source>
  
In the SEI, <code>op</code> was then modelled as follows:
+
In the SEI <code>op</code> was then modelled as follows:
  
 
<source lang="java5">
 
<source lang="java5">
Line 244: Line 246:
 
</source>
 
</source>
  
The JAX-WS runtime generates a request element called after the operation, <code>op</code>, and includes in it the input string as simple content. Similarly, it expects a response element called <code>opResponse</code> with simple content.
+
In response, the JAX-WS runtime generates a request element called after the operation, <code>op</code>, and includes in it the input string as simple content. Similarly, it expects a response element with simple content.
  
 
+
Had Acme used the <code>WRAPPED</code> pattern, its request and response elements would have looked like this:  
Had Acme used the <code>WRAPPED</code> pattern, its WSDL would have included something like this:  
+
  
 
<source lang="xml">
 
<source lang="xml">
Line 265: Line 266:
 
</source>
 
</source>
  
A service may choose this pattern to make its clients more resilient to future changes in the remote API. If <code>op</code> evolves to take further inputs, perhaps optional ones, clients that used to send a single string may still interact with the service.
+
The pattern is verbose in this case, but services choose it to make clients more resilient to future changes in the remote API. If <code>op</code> evolves to take further inputs, perhaps optional ones, clients that used to send a single string may still interact with the service. Similarly, if <code>op</code> evolves to return multiple values, clients that used to expect a single string may still process the response.
  
In this case, we have two ways of modelling <code>op</code> in the SEI. In the first, we first define bean classes that bind to request and response messages. At their simplest, they may look as follows:
+
In this case, we have two ways of modelling <code>op</code> in the SEI. In the first, we first define JAXB-annotated bean classes that bind to request and response messages. At their simplest, they may look as follows:
  
 
<source lang="java5">
 
<source lang="java5">
Line 284: Line 285:
 
</source>
 
</source>
  
'''Note''': there is not strong incentive to make these beans more complex than they are, e.g. add accessors and mutators over private fields. The act solely ad data carriers.
+
'''Note''': since they act solely ad data transfer objects (DTOs) there is little incentive to make these beans more complex than they are, e.g. add accessors and mutators over private fields.
  
 
We then model <code>op</code> in the SEI as follows:
 
We then model <code>op</code> in the SEI as follows:
Line 295: Line 296:
 
</source>
 
</source>
  
Here, the JAX-WS runtime prepares an <code>op</code> request element but it delegates to the JAXB runtime the task of creating its children. The JAXB runtime creates the <code>param</code> child accordingly. Similarly for the response element.
+
Here, the JAX-WS runtime prepares an <code>op</code> request element and looks into the <code>Op</code> bean for instructions on how to create its children. The JAXB annotations indicate that request element should have a <code>param</code> child. Similarly for the response element.
  
  
Alternatively, we can signal to the JAX-WS runtime that the parameter style is <code>WRAPPED</code> (the default) and add annotations that instruct JAX-WS how to generate the wrapping request and response elements for us:
+
Alternatively, we can signal to the JAX-WS runtime that the parameter style is <code>WRAPPED</code> (which is the default) and add annotations in the SEI that instruct JAX-WS how to generate the wrapping request and response elements for us:
  
 
<source lang="java5">
 
<source lang="java5">
  
@WebResult(name="return")
 
 
String op(@WebParameter(name="param") String s);
 
String op(@WebParameter(name="param") String s);
  
 
</source>
 
</source>
  
<code>@WebResult</code> tells the JAX-WS runtime the name of the element in <code>opResponse</code> that contains the string we declare to return. Similarly, <code>WebParameter</code> indicates to it the child element of the <code>op</code> request that contains the string that we declare to take.
+
<code>@WebResult</code> tells the JAX-WS runtime the name of the element in <code>opResponse</code> which contains the string that we declare to return. Similarly, <code>WebParameter</code> names the child element of the <code>op</code> request which should contains the string that we declare to take.
  
 
When we manually generate the SEI, this latter approach reduces the number of bean classes that we need to produce. It is accordingly the approach we prefer.
 
When we manually generate the SEI, this latter approach reduces the number of bean classes that we need to produce. It is accordingly the approach we prefer.
  
Finally, note that it is often the case that an operation is defined with a mixture of patterns, e.g. the input of the operation is modelled with one pattern and the output with the other. In this case, the corresponding method of the SEI will need to use a <code>BARE</code> parameter style and define a bean class for the wrapped input or the wrapped output. In the following, we avoid showing how to write SEIs for all the possible cases: pure <code>BARE</code>, pure <code>WRAPPED</code>, or mixed. Rather, we present <code>BARE</code> and <code>WRAPPED</code> solutions for method inputs alone, with the understanding that the former will be required in the mixed case.
+
Finally, note that we cannot use a <code>WRAPPED</code> parameter style only for the input parameters or only for the return value. If we tell JAX_WS to wrap the input parameters in a request element, then it will unwrap the response element, and vice-versa. We are thus forced to use the <code>BARE</code> style when the operation:
 +
 
 +
* follows one pattern for the input and the other for the output, or vice-versa;
 +
* has a response element with multiple children.  
 +
 
 +
'''Note''': In the latter case, we ''could'' still use the <code>WRAPPED</code> style and <code>Holder</code> input parameters(cf. Sect. 2.3.2 and Sect. 2.3.3 of the JAX-WS [http://jcp.org/aboutJava/communityprocess/pfd/jsr224 specifications]), but we find that doing so is more trouble than worth.
 +
 
 +
In summary:
 +
 
 +
* we ''can'' use a <code>WRAPPED</code> parameter style if the request element has a complex type;
 +
* we ''must'' use a <code>BARE</code> parameters style if the request element or the response element have a simple type, or if the response element has more than one child element.
 +
 
 +
Since we will most often have to use a <code>BARE</code> parameter style in the SEI, we can move the <code>@SOAPBinding</code> annotation on the class declaration itself, and override this default on individual methods when we can and wish to use the <code>WRAPPED</code>style.
 +
 
 +
In the following, we avoid showing how to write SEIs for all the possible pattern usage cases: pure <code>BARE</code>, pure <code>WRAPPED</code>, and mixed cases. Rather, we show solutions based on <code>BARE</code> and <code>WRAPPED</code> parameter style for inputs alone, with the understanding that the former will be required in the mixed case.
  
 
=== The Empty Case ===
 
=== The Empty Case ===

Revision as of 11:12, 29 November 2012

common-gcore-stubs is a client-library that interacts with the JAX-WS runtime of the Java platform to generate dynamic JAX-WS proxies of remote gCore services. Architecturally, it operates at the lowest layer of the Featherweight Stack for gCube clients.

common-gcore-stubs is available through our Maven repositories with the following coordinates:

<artifactId>common-gcore-stubs</artifactId>
<groupId>org.gcube.core</groupId>

Quick Tour

At the time of writing, most gCube services are JSR101 (JAX-RPC) services implemented and running on the gCore stack inside a gCube Hosting Node. common-gcore-stubs allows us to invoke such services without dependencies on that stack, hence from within arbitrary client environments. It does so by interacting on our behalf with the JSR224 (JAX-WS) runtime, which is part of the Java platform since version 1.6 as the standard for SOAP-based Web Services and Web Service clients. With common-gcore-stubs, we use a modern standard to call services that align with a legacy standard.

We provide the library with:

  • information about the target service, such as its gCube coordinates (service class, service name) and its WSDL coordinates (namespace, porttype name);
  • the address of a target endpoint of the services;
  • the Service Endpoint Interface (SEI) of the service, i.e. the local Java interface that models the remote API of the service and provides additional information about its endpoint through JSR-181 annotations.

The library gives us back a dynamically generated proxy implementation of the SEI, which is first synthesised by the JAX-WS runtime and then appropriately configured by the library to issue gCube calls to the target endpoint (i.e. propagate the call scope, target service coordinates, client identity, etc.).

The generated proxy can serve as a local stub for Acme endpoints. Typically, we use this stub in the context of higher-level proxying facilities, such as gCube Client Libraries. The SEI and the other required information may be distributed as a stand-alone component, like for JAX-RPC stubs. Alternatively, they may be integral part of the higher-level Client Library which uses them with common-gcore-stubs. The minimal footprint of these 'stubs', the fact that they do not need to be manually generated (though they can), and the fact that they serve a client-only role (as opposed to JAX-RPC stubs, which we use also service-side) makes the embedding option natural and appealing.

In the following, we run through a simple example to illustrate the process and relevant APIs.

A Sample Service

For the sake of simplicity, let us illustrate how to use common-gcore-stubs to call a fictional gCore Acme service. Let us assume that the remote API of Acme is defined by the following WSDL:

<definitions name="Acme"
    targetNamespace="http://acme.org" xmlns:tns="http://acme.org" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 
 	<types>
	<xsd:schema targetNamespace="http://acme.org">
 
               <xsd:element name="foo" type="xsd:string" />
	       <xsd:element name="fooResponse" type="xsd:string" />
 
	</xsd:schema>
	</types>
 
	<message name="foo">
		<part name="request" element="tns:foo"/>
	</message>
	<message name="fooResponse">
		<part name="response" element="tns:fooResponse"/>
	</message>
 
	<portType name="AcmePortType">
 
		<operation name="foo">
			<input message="tns:foo"/>
			<output message="tns:fooResponse"/>
		</operation>
 
	</portType>
 
      <binding name="binding:AcmePortTypeSOAPBinding" type="tns:AcmePortType" xlmns:binding=""http://acme.org/bindings"">
           <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
           <operation name="foo">
             <soap:operation soapAction="http://acme.org/StatelessPortType/fooRequest"/>
             <input>
                 <soap:body use="literal"/>
            </input>
           <output>
              <soap:body use="literal"/>
           </output>
         </operation>
       </binding>
 
       <service name="service:AcmeService" xlmns:service="http://acme.org/service">
         <port name="AcmePortTypePort" binding="binding:StatelessPortTypeSOAPBinding">
             <soap:address location="...some address..."/>
         </port>
       </service>
 
</definitions>

Like most gCore services, Acme:

  • defines a single porttype, AcmePortType. For Acme, the porttype includes a single operation, foo that takes and returns a string;
  • foo can be invoked through SOAP over HTTP in a document/literal style (essentially, messages are made directly of elements defined in the <types> section)

Note: since the WSDL describes an Acme endpoint, it includes also its address. However, this address is largely irrelevant to our purposes because we use the WSDL to call different endpoints, whose addresses we discover only at call time.

Note: gCore services are normally developed WSDL-first and use tooling to derive a WSDL complete with binding information from a partial WSDL that includes only logical definitions (types, port-types, operations). By design, the tooling spreads the derived WSDL across a number of files that follow a chain of imports. It also defines ad-hoc namespaces for the information it derives (e.g. http://acme.org/service for the <service> definitions, or http://acme.org/bindings for <binding> definitions). Here, we present the WSDL as a whole but keep using different namespaces for port-types, bindings, and service definitions so as to facilitate mapping the example to the WSDLs of real gCube services.

The WSDL provides the following coordinates about Acme, which we capture in a class of constants to use later with common-gcore-stubs:

import ....
 
public class AcmeConstants {
 
  public static final String serviceNS = "http://acme.org/service";
  public static final String serviceLocalName = "AcmeService";
  public static final QName serviceName = new QName(serviceNS,serviceLocalName);
 
  public static final String porttypeNS = "http://acme.org";
  static final String porttypeLocalName = "AcmePortType";
}

The Service Endpoint Interface

In JAX-WS terminology, the SEI is a local Java interface that mirrors the remote API of the Acme service. Its declaration includes annotations that provide the JAX-WS runtime with (some of the) information required to generate an implementation of the interface which can correctly call an Acme endpoint.

One way to obtain a SEI is to derive it from the WSDL with tooling, such as the wsimport utility which ships with the JDK. For this example, however, we produce the SEI manually, which gives us more control and makes for cleaner code.

import .............AcmeConstants.*; 
...
 
@WebService(name=porttypeLocalName,targetNamespace=porttypeNS)
public interface AcmeStub {
 
  @SOAPBinding(parameterStyle=ParameterStyle.BARE)
  String foo(String s);
 
}

We name the SEI to reflect that it acts as a stub of the Acme service. As required by JAX-WS, we annotate the SEI with @WebService, providing the coordinates of the porttype that includes the operations to be proxied through the SEI. For this, we use the constants of the AcmeConstants class defined above, which we statically import for improved legibility of the code.

We then declare the method foo with the expected signature. We also annotate the method with @SOAPBinding, to indicate that the input and output strings should be put/found in elements directly under the SOAP body, rather than inside some 'wrapper' element of sorts. In other words, specifying the "bare" parameter style tells the JAX-WS runtime that the WSDL declaration for foo does not follow the so-called wrapped pattern, which is otherwise assumed by default.

Note: For operations that take and return a single primitive type, such as foo the bare pattern is the natural choice (provided that we name the elements in such a way that the service runtime can easily dispatch requests).

The Service Descriptor

With the SEI under our belt, we're left with providing common-gcore-stubs with a descriptor for the Acme service, i.e. the remaining pieces of information which are needed to call it.We provide the descriptor as an instance of the GCoreService class, which we build in a fluent style with the help of a GCoreServiceBuilder. A convenient place to do this is directly the AcmeConstants class first introduced above.

The following example illustrates:

import static org.gcube.common.clients.stubs.jaxws.GCoreServiceBuilder.*;
...
public class AcmeConstants {
 
 ...
 
 public static String service_class="....";
 public static String service_name="...";
 
 public static final GCoreService<AcmeStub> acme = service().withName(serviceName)
                                                            .coordinates(service_class,service_name)
                                                            .andInterface(AcmeStub.class); 
 
 
}

Since we will not need more than one instance of the descriptor, we create it once and for all as a constant named after the service. We use the static method GCoreServiceBuilder#service() to kick off the process and the follow the type system to provide the remaining information, using the constants already available within the class, pus the gCube coordinates of the service. The static 'star' import is just a convenience to improve further the legibility of the code.

Stubbed Calls

When it's finally time to call an Acme endpoint, we use the SEI and the GCoreService descriptor to obtain a an implementation of the SEI configured for the target endpoint:

import ......AcmeConstants.*;
import static org.gcube.common.clients.stubs.jaxws.StubFactory.*;
 
...
 
String address = 
 
AcmeStub stub = stubFor(acme).at(address);
 
String response = stub.foo("...");

We build our AcmeStub using the static method stubFor(GCoreService) of the StubFactory class, which as usual we statically import for convenience. We provide the factory with the acme descriptor that we've defined earlier in the AcmeConstants</code?> class, which we also statically import. We also provide the address of the target endpoint, here modelled as a string in the assumption that the service is stateless. We shall see later how to model reference instances of stateful services.

Finally, we use the stub returned by the <code>StubFactory to call the endpoint through the SEI.

Note: as we're calling a gCube service, we need to do so in a given scope or the stub will refuse to go ahead. The classic way to associate the call with a scope is to set it on the current thread:

ScopeProvider.instance.set('..some scope...");

Writing SEIs

The example above considers the simplest case of remote operation, where inputs and outputs are single atomic values. The range of remote operations we can encounter across gCore services is in fact quite wide. While the ultimate reference to writing and annotating SEIs is provided by the JAX-WS specifications and the [http://jcp.org/aboutJava/communityprocess/mrel/jsr222 specifications (JAXB), we consider some of the common cases below, for convenience.

In all the cases, we ask the same question:

how do we define and annotate the methods of the SEI so that the implementation generated by the JAX-WS runtime: a) sends requests which are understood by the service, and b) understands the responses produced by the service?

In what follows we assume that, like for the Acme service of our earlier example:

  • the WSDL follows a document/literal style for its SOAP/HTTP binding, with requests and responses defined by a single element in accordance to WS-I profile;
  • the request element is named after the corresponding operation. This convention follows best practices, as it allows SOAP engines to unambiguously dispatch requests. The response element is more freely named, though the convention is to suffix "Response" to the name of the operation.

The definitions of <message>, <portType>, <binding> and <service> elements become then boilerplate for our discussion, and we can omit it to concentrate solely on the definitions of request and response elements. In fact, we can simplify the presentation further if we agree to omit namespace prefixes altogether. Then we can illustrate each case by instantiating the following template:

<element name="op">
   ....
</lement>
 
<element name="opResponse">
   ....
</element>

Wrapped or Bare?

An important way in which operations differ within and across services is in the 'pattern' adopted in the WSDL to define their request and response elements. For our document/literal services, the well-known patterns are BARE and WRAPPED. For gCore services the difference between the two boils down to the following:

  • in the BARE pattern, request and responses have simple content.
  • In the WRAPPED pattern, they have complex context.

Clearly, the difference arises only when operations take or return simple types, otherwise the pattern is necessarily WRAPPED.

In the example above, Acme used the BARE pattern:

<element name="op" type="string" />
<element name="opResponse" type="string" />

In the SEI op was then modelled as follows:

@SOAPBinding(parameterStyle=ParameterStyle.BARE)
String op(String s);

In response, the JAX-WS runtime generates a request element called after the operation, op, and includes in it the input string as simple content. Similarly, it expects a response element with simple content.

Had Acme used the WRAPPED pattern, its request and response elements would have looked like this:

<element name="op" >
  <complexType>
         <element name="param" type="string">  
  </complexType>
</element>
 
<element name="opResponse">
  <complexType>
         <element name="return" type="string">  
  </complexType>
</element>

The pattern is verbose in this case, but services choose it to make clients more resilient to future changes in the remote API. If op evolves to take further inputs, perhaps optional ones, clients that used to send a single string may still interact with the service. Similarly, if op evolves to return multiple values, clients that used to expect a single string may still process the response.

In this case, we have two ways of modelling op in the SEI. In the first, we first define JAXB-annotated bean classes that bind to request and response messages. At their simplest, they may look as follows:

public class Op {
 
  @XmlElement
   public param; 
}
 
public class OpResponse {
 
  @XmlElement
   public return; 
 
}

Note: since they act solely ad data transfer objects (DTOs) there is little incentive to make these beans more complex than they are, e.g. add accessors and mutators over private fields.

We then model op in the SEI as follows:

@SOAPBinding(parameterStyle=ParameterStyle.BARE)
OpResponse op(Op s);

Here, the JAX-WS runtime prepares an op request element and looks into the Op bean for instructions on how to create its children. The JAXB annotations indicate that request element should have a param child. Similarly for the response element.


Alternatively, we can signal to the JAX-WS runtime that the parameter style is WRAPPED (which is the default) and add annotations in the SEI that instruct JAX-WS how to generate the wrapping request and response elements for us:

String op(@WebParameter(name="param") String s);

@WebResult tells the JAX-WS runtime the name of the element in opResponse which contains the string that we declare to return. Similarly, WebParameter names the child element of the op request which should contains the string that we declare to take.

When we manually generate the SEI, this latter approach reduces the number of bean classes that we need to produce. It is accordingly the approach we prefer.

Finally, note that we cannot use a WRAPPED parameter style only for the input parameters or only for the return value. If we tell JAX_WS to wrap the input parameters in a request element, then it will unwrap the response element, and vice-versa. We are thus forced to use the BARE style when the operation:

  • follows one pattern for the input and the other for the output, or vice-versa;
  • has a response element with multiple children.

Note: In the latter case, we could still use the WRAPPED style and Holder input parameters(cf. Sect. 2.3.2 and Sect. 2.3.3 of the JAX-WS specifications), but we find that doing so is more trouble than worth.

In summary:

  • we can use a WRAPPED parameter style if the request element has a complex type;
  • we must use a BARE parameters style if the request element or the response element have a simple type, or if the response element has more than one child element.

Since we will most often have to use a BARE parameter style in the SEI, we can move the @SOAPBinding annotation on the class declaration itself, and override this default on individual methods when we can and wish to use the WRAPPEDstyle.

In the following, we avoid showing how to write SEIs for all the possible pattern usage cases: pure BARE, pure WRAPPED, and mixed cases. Rather, we show solutions based on BARE and WRAPPED parameter style for inputs alone, with the understanding that the former will be required in the mixed case.

The Empty Case

Let us now consider an operation that expects no input:

<element name="op" >
  <complexType />
</element>

If op uses the WRAPPED pattern also for its output, then we can model the operation in the SEI as follows:

@WebResult(name="...")
... op();

which is of course just a specialisation of the previous example.

If instead op uses the BARE pattern for its output, then we're forced to adopt the same pattern in the SEI and use an empty bean class as the input. For convenience, an Empty bean class is included JAXWSUtils class of common-gcore-clients:

@SOAPBinding(parameterStyle=ParameterStyle.BARE)
void op(Empty empty);

The SEI method may then be invoked as follows:

import static ....JAXWSUtils.*;
 
...
 
stub.op(empty);

where empty is a constant of type Empty provided by JAXWSUtils.

Collections

Operations often expects collections of values,e.g:

<element name="op" >
  <complexType>
      <sequence>
          <element name="element"  maxOccurs="unbounded" type="string">
      </sequence>
  </complexType>
</element>

If op follows the WRAPPED pattern for its output, then we can model it in the SEI as follows:

@WebResult(name="...")
... op(@WebParam(name="element") List<String> elements);

where we have chosen a List type for the richness of the interface, though we could have chosen an array or a Set if the intended usage or knowledge of data semantics had made this choice preferable.

If op follows instead the BARE pattern for its output, then we can model it in the SEI as follows:

@SOAPBinding(parameterStyle=ParameterStyle.BARE)
... op(OpInput input);

where OpInput is the bean class defined as:

public class OpInput {
 
 @XmlElement
  public List<String> element;
}

Complex Parameters

Assume Acme exposes an operation complex that takes and returns complex objects. Omitting the declaration of operation and messages, the WSDL will contain these new element declarations:

....
 
 <xsd:element name="complex">
  <xsd:complexType>
     <xsd:sequence>
         <xsd:element name="in1" type="xsd:string"/>
         <xsd:element name="in2" type="xsd:integer"/>
     </xsd:sequence>
  </xsd:complexType>		
</xsd:element>
 
<xsd:element name="complexResponse">
  <xsd:complexType>
	<xsd:sequence>
           <xsd:element name="out" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/>
	</xsd:sequence>
  </xsd:complexType>		
</xsd:element>
 
...

In this case, our SEI could declare the following method:

@SOAPBinding(parameterStyle=ParameterStyle.BARE)
ComplexOutput complex(ComplexInput input);

where ComplexInput and ComplexOutput may be as simple as the following field-only, JAXB-annotated beans:

@XmlRootElement
public class ComplexInput {
 
   @XMLElement
   public String in1;
 
   @XMLElement
   public int in2;
 
}
 
...
 
@XmlRootElement
public class ComplexOutput {
 
   @XMLElement
   public List<String> out;
 
}


Alternatively, the SEI could declare the following method:

@SOAPBinding(parameterStyle=ParameterStyle.WRAPPED)
@WebResult(name="out") 
List<String> complex(@WebParam(name="in1") String in1, @WebParam(name="in2") int in2);

i.e. use the WRAPPED mapping style, with the onus