Difference between revisions of "Common-gcore-stubs"

From Gcube Wiki
Jump to: navigation, search
(Stubbed Calls)
(The Service Descriptor)
Line 136: Line 136:
 
===The Service Descriptor ===
 
===The Service Descriptor ===
  
With the SEI under our belt, we're left with providing <code>common-gcore-stubs</code> with a descriptor for the Acme service, i.e. the remaining pieces of information which are needed to call it.
+
With the SEI under our belt, we're left with providing <code>common-gcore-stubs</code> 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 <code>GCoreService</code> class, which we build in a fluent style with the help of a <code>GCoreServiceBuilder</code>. A convenient place to do this is directly the <code>AcmeConstants</code> class first introduced [[#A Sample Service|above]].
We provide the descriptor as an instance of the <code>GCoreService</code> class, which we build in a fluent style with the help of a <code>GCoreServiceBuilder</code>. A convenient place to do this is directly the <code>AcmeConstants</code> class first introduced [[#A Sample Service|above]].
+
  
 
The following example illustrates:
 
The following example illustrates:
Line 146: Line 145:
 
...
 
...
 
public class AcmeConstants {
 
public class AcmeConstants {
 
  
 
  ...
 
  ...
 
   
 
   
 +
public static String service_class="....";
 +
public static String service_name="...";
 +
 
  public static final GCoreService<AcmeStub> acme = service().withName(serviceName)
 
  public static final GCoreService<AcmeStub> acme = service().withName(serviceName)
 
                                                             .coordinates(service_class,service_name)
 
                                                             .coordinates(service_class,service_name)
Line 159: Line 160:
 
</source>
 
</source>
  
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 <code>GCoreServiceBuilder#service()</code> to kick off the process and the follow the type system to provide the remaining information, using the constants already available within the class. The static 'star' import is just a convenience to improve further the legibility of the code.
+
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 <code>GCoreServiceBuilder#service()</code> 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 ===
 
=== Stubbed Calls ===

Revision as of 14:37, 27 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="fooInputMessage">
		<part name="request" element="tns:foo"/>
	</message>
	<message name="fooOutputMessage">
		<part name="response" element="tns:fooResponse"/>
	</message>
 
	<portType name="AcmePortType">
 
		<operation name="foo">
			<input message="tns:fooInputMessage"/>
			<output message="tns:fooOutputMessage"/>
		</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. The porttype includes a single operation, foo that takes and returns a string;
  • foo can be invoked at a given Acme endpoint through SOAP over HTTP in a document/literal style. In particular, foo request and response messages contain a single part and this part is an element declared in the <types> section and named in such a way that the request can be easily dispatched to the service implementation.

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 endpoint, 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(namespace,localname);
 
  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=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.