Difference between revisions of "Adding State"

From GCube System
Jump to: navigation, search
(WS-Resources and The Implied Resource Pattern)
(Home Sweet Home)
 
(119 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
= Adding State =
 
= Adding State =
 +
 
The service we defined in the [[The_Development_Cycle|first part]] of this tutorial was stateless: its responses to client requests depended solely on the requests. This is all well, but in practice services may need to maintain some form of ''state'' that pre-exists, persists, and - most importantly - ''changes'' as a result of client invocations. Many gCube services are indeed ''stateful''.
 
The service we defined in the [[The_Development_Cycle|first part]] of this tutorial was stateless: its responses to client requests depended solely on the requests. This is all well, but in practice services may need to maintain some form of ''state'' that pre-exists, persists, and - most importantly - ''changes'' as a result of client invocations. Many gCube services are indeed ''stateful''.
  
Line 6: Line 7:
  
 
* a <code>Factory</code> port-type that allows clients to log on and thus creates state within the service . In particular, we plan a single operation <code>logon()</code> for this port-type.
 
* a <code>Factory</code> port-type that allows clients to log on and thus creates state within the service . In particular, we plan a single operation <code>logon()</code> for this port-type.
* a <code>Stateful</code> port-type that allows clients to visit the service and thus consults and updates the state of the the service. In particular, we plan a single operation <code>aboutSF()</code> for this port-type.
 
  
(It will be [[#WS-Resources and The Implied Resource Pattern|soon]] clear why we prefer two port-types with a single operation each rather than a single port-type with two operations.)
+
* a <code>Stateful</code> port-type that allows clients to visit the service and thus consults and updates the state of the service. In particular, we plan a single operation <code>aboutSF()</code> for this port-type which will behave similarly to the operation <code>about()</code> in the <code>Stateless</code> port-type whilst recognising visits from clients that have previously logged on.
 +
 
 +
(It will be clear [[#WS-Resources and The Implied Resource Pattern|soon]] why we prefer two port-types with a single operation each rather than a single port-type with two operations.)
 
   
 
   
 
For both port-types, we need to repeat the [[The_Development_Cycle#A_PortType_Interface|steps]] already shown for the <code>Stateless</code> port-type: add the port-types descriptions to the service profile, define the WSDL interface of each port-type, and provide the Java implementation of the two port-types. Additional steps will then be required for state management.
 
For both port-types, we need to repeat the [[The_Development_Cycle#A_PortType_Interface|steps]] already shown for the <code>Stateless</code> port-type: add the port-types descriptions to the service profile, define the WSDL interface of each port-type, and provide the Java implementation of the two port-types. Additional steps will then be required for state management.
Line 16: Line 18:
 
The overall state of our <code>SampleService</code> will be comprised of many 'pieces', one per client that logs on and then visits. We refer to these pieces somewhat more technically as ''stateful resources''.  
 
The overall state of our <code>SampleService</code> will be comprised of many 'pieces', one per client that logs on and then visits. We refer to these pieces somewhat more technically as ''stateful resources''.  
  
How should we go about creating and accessing stateful resources?   
+
How should we go about creating and accessing stateful resources?  One approach could be as follows:
  
One approach could be as follows:
+
* take some credentials from clients when they invoke the <code>logon()</code> operation on the <code>Factory</code> port-type. A simple name would surely do for our purposes.  
 
+
* create stateful resources as Java objects that contain the count of client visits, and identify such resources with the name of the associated client.  
* take some credentials from clients when they invoke the <code>logon()</code> operation on the <code>Factory</code> port-type. A simple name would surely do for our purposes. The create stateful resources that contain the count of client visits and identify such resources with the name of the associated client.  
+
 
* when a client comes back to visit the service and invokes the <code>aboutSF()</code> operation on the <code>Stateful</code> port-type, ask it to provide his name so that we can identify the corresponding stateful resource and update the count of its visits.
 
* when a client comes back to visit the service and invokes the <code>aboutSF()</code> operation on the <code>Stateful</code> port-type, ask it to provide his name so that we can identify the corresponding stateful resource and update the count of its visits.
  
In this approach, clients need to explicitly identify the state they wish to target. Unfortunately, identifiers are service-specific: here we need a name, elsewhere we may need something else. The use that we make of identifiers is also specific: here we hinted at one parameter in the <code>aboutSF()</code> operation, elsewhere could be two or three parameters in one or more operations. This variability makes it impossible to build generic clients that can transparently access state across different services.  
+
In this approach, clients need to explicitly identify the state they wish to target. Unfortunately, identifiers are service-specific: here we need a name, elsewhere we may need something different. The use that we make of identifiers is also specific: here we hinted at one parameter in the <code>aboutSF()</code> operation, elsewhere could be two or three parameters in one or more operations. This variability makes it impossible to build generic clients that can transparently access state across different services.
 +
 
 +
You may find this observation rather strange: what could a client do that does not require specific knowledge of the target service? Well, it turns out that if we find a general way to access stateful resources, then we can build enough conventions on how we describe them to enable a range of very useful and yet generic clients. For example, we can define clients that can query and change the stateful resources of any service that complies with the conventions. We can define clients that can uniformly destroy stateful resources, either immediately or based on some renewable expiry time. We can even define clients that allow others to subscribe for changes to the stateful resources. These are all key features in gCube, and we shall be directly concerned with some of them in this very Primer.  
  
You may find this observation rather strange: what could a client do that does not require specific knowledge of the service it is talking to? It turns out that, if we can build enough conventions on how stateful resources are represented, we can define generic clients capable of querying and changing the stateful resources of any service that complies with the conventions. We can also define generic clients that can uniformly destroy stateful resources, either immediately or based or some renewable expiry time. We can even define generic clients that allow others to subscribe for changes to the stateful resources. These are all key features in gCube, and we shall be directly concerned with some of them in this very Primer. All we need to support generic clients is:
+
What we need to promote generic clients is then:
  
* a uniform pattern to access stateful resources which does not change from service to service.  
+
* a uniform pattern to identify and access stateful resources which does not change from service to service.  
* a standard that codifies this access pattern and builds other conventions on top of it.
+
* a standard that codifies this pattern and builds useful conventions on top of it.
  
Now, the [http://www.oasis-open.org/committees/wsrf Web Services Resource Framework (WSRF)] is precisely one such standard, and gCube adopts it. To access stateful resources, WSRF says: forget passing identifiers explicitly in operations such as <code>aboutSF()</code>, which vary from port-type to port-type and from service to service. Let us pass them instead ''implicitly'', as part of the address of the port-type that exposes those operations. An invocation of <code>aboutSF()</code> would thus be addressed to the <code>Stateful</code> port-type, but the address would include also the identifier of the stateful resource that is the target of the request. No need to explicitly parameterise <code>aboutSF()</code> with it. This is the access pattern that WSRF calls, for obvious reasons, the ''implied resource pattern''.
+
The [http://www.oasis-open.org/committees/wsrf Web Services Resource Framework (WSRF)] is precisely one such standard, and gCube adopts it. When it comes to identifying and accessing stateful resources, WSRF says: forget passing identifiers explicitly in operations such as <code>aboutSF()</code>, which vary from port-type to port-type and from service to service; let us pass them instead ''implicitly'', as part of the address of the port-type that exposes those operations. An invocation of <code>aboutSF()</code> would thus be addressed to the <code>Stateful</code> port-type, but the address would include also the identifier of the stateful resource that is the target of the request. The port-type implementation would then extract such identifier and use it to locally access the stateful resource. No need to explicitly parameterise <code>aboutSF()</code> with it. This is the access pattern that WSRF calls the ''implied resource pattern''.
  
If you think about it, this annotated address - or more appropriately, this ''qualified endpoint reference'' - identifies a pair (port-type,stateful resource), which WSRF calls a ''WS-Resource''. We can then think of a 'qualified endpoint reference' to the port-type more simply as the endpoint reference of the WS-resource itself. Similarly, invoking the operations of the port-type can now be seen as invoking operations of the WS-Resource. Conversely, the port-type itself can be thought of as the uniform interface of potentially many WS-Resources.
+
If you think about it, this annotated address - or more appropriately, this ''qualified endpoint reference'' - identifies a pair (port-type,stateful resource). WSRF calls this pair a ''WS-Resource''. We can then think of the qualified endpoint reference as the endpoint reference of the WS-resource itself. Similarly, we can think of the operations available at that endpoint as the operations of the WS-Resource. In this sense, the port-type becomes the uniform interface of potentially many WS-Resources.
  
With the implied resource pattern and the corresponding terminology, we can now think of our new port-types as follows:
+
With the implied resource pattern and the corresponding terminology, we can now describe our new port-types as follows:
  
 
* a <code>Factory</code> port-type that allows users to create WS-Resources. In particular, we plan a single operation for this port-type, <code>logon()</code>, which takes the name of the client and returns the endpoint reference of a WS-Resource 'dedicated' to the client.
 
* a <code>Factory</code> port-type that allows users to create WS-Resources. In particular, we plan a single operation for this port-type, <code>logon()</code>, which takes the name of the client and returns the endpoint reference of a WS-Resource 'dedicated' to the client.
* a <code>Stateful</code> port-type that allows clients to consume WS-Resources. In particular, we plan a single operation for this port-type, <code>aboutSF()</code>, which takes nothing and returns nothing but it is invoked with the endpoint reference of a WS-Resource.
+
* a <code>Stateful</code> port-type that defines the interface of the WS-Resources. We plan a single operation for this port-type too, <code>aboutSF()</code>, which takes nothing but it is invoked with the endpoint reference of a WS-Resource.
  
A client will then invoke <code>logon()</code> on the <code>Factory</code> port-type and use the resulting WS-Resource endpoint reference to invoke <code>aboutSF()</code> on it. A couple of things to notice:
+
A client may then invoke <code>logon()</code> on the <code>Factory</code> port-type and then use the resulting WS-Resource endpoint reference to invoke <code>aboutSF()</code> on it. As we shall see, a client may also ''discover'' and use the endpoint of a WS-Resource of interest without having previously created it. A couple of things to notice:
  
[[Image:blue.jpg]] The client does not need to know about the stateful resource identifier embedded in the endpoint reference returned by the <code>Factory</code> port-type. The endpoint reference identifies a WS-Resource, the inner structure of this WS-Resource as a pair (port-type,stateful resource) remains opaque to the client.
+
[[Image:blue.jpg]] The client does not need to know about the stateful resource identifier embedded in the endpoint reference returned by the <code>Factory</code>, or otherwise 'found'. The endpoint reference identifies a WS-Resource but the inner structure of this WS-Resource as a pair (port-type,stateful resource) remains opaque to the client.
  
[[Image:blue.jpg]] Placing <code>logon()</code> and <code>aboutSF()</code> in different port-types makes even more sense retrospectively. The first creates WS-Resources while the second defines their operations. Informally, we say that <code>Factory</code> is, like <code>Stateless</code>, a 'stateless port-type'it does not serve as the interface of WS-Resources. In contrast, we say that <code>Stateful</code> is a 'stateful port-type', for it is meant to be used as the interface of WS-Resources.
+
[[Image:blue.jpg]] Placing <code>logon()</code> and <code>aboutSF()</code> in different port-types makes sense. The first creates WS-Resources while the second defines their operations. Informally, we say that the <code>Factory</code> is, like <code>Stateless</code>, a ''stateless port-type'' because it does not serve as the interface of WS-Resources. For the opposite reason, we say that <code>Stateful</code> is a ''stateful port-type''.
  
== Extending the Profile ==
+
=== Extending the Profile ===
  
 
We now enrich the service profile to reflect the existence of two new port-types.  
 
We now enrich the service profile to reflect the existence of two new port-types.  
Line 99: Line 102:
 
Not much to comment about here, we just added two new <code>PortType</code> elements in the description of the <code>Main</code> package.
 
Not much to comment about here, we just added two new <code>PortType</code> elements in the description of the <code>Main</code> package.
  
== Defining the port-type interfaces ==
+
=== More Port-Type Interfaces ===
 
+
=== WSDL interface for the factory service ===
+
Define a new WSDL interface for the Factory service:
+
  
 +
The WSDL that describes the interface of the <code>Factory</code> port-type holds few surprises at this stage:
 +
 
<pre>
 
<pre>
 
<definitions name="Factory"
 
<definitions name="Factory"
Line 117: Line 119:
 
  <types>
 
  <types>
 
<xsd:schema targetNamespace="http://acme.org/sample">
 
<xsd:schema targetNamespace="http://acme.org/sample">
+
 
<xsd:import namespace="http://schemas.xmlsoap.org/ws/2004/03/addressing"  
+
<xsd:import namespace="http://schemas.xmlsoap.org/ws/2004/03/addressing"  
schemaLocation="../ws/addressing/WS-Addressing.xsd" />
+
                                                    schemaLocation="../ws/addressing/WS-Addressing.xsd" />
+
 
 
   <xsd:element name="logon" type="xsd:string" />
 
   <xsd:element name="logon" type="xsd:string" />
 
<xsd:element name="logonResponse" type="wsa:EndpointReferenceType"/>
 
<xsd:element name="logonResponse" type="wsa:EndpointReferenceType"/>
 +
 
</xsd:schema>
 
</xsd:schema>
 
</types>
 
</types>
Line 148: Line 151:
 
</pre>
 
</pre>
  
Notes:
+
Just notice the following:
* the interface imports the ''WS-Addressing.xsd'' to make use of the WS-Adressing types' definitions
+
* the interface exposes one single operation allowing to create a new stateful resource, the ''logon'' operation
+
* the operation takes a string as input parameter
+
* the operation returns an ''EndpointReferenceType'' that points to the stateful resource
+
  
 +
[[Image:blue.jpg ]] We use the <code>EndpointReferenceType</code> type to describe endpoint references of WS-Resources. This type is defined by the [http://www.w3.org/Submission/ws-addressing WS-Addressing] standard to describe endpoint references (qualified or not), and we need to  import its definition from a file that ships with gCore (<code>WS-Addressing.xsd</code>); note in particular the <code>import</code> directive and how it is resolved relatively to the location of the interface ''after'' service deployment ([[The_Development_Cycle#Building & Deploying the Service|remember?]]).
  
Save the file as '''Factory.wsdl''' in the schema folder under the service location:
+
[[Image:blue.jpg ]] At this stage, there is little point in looking into the schema definition of this type. gCore will offer Java objects to model and serialise qualified endpoint references, in accordance with the schema definition. We just notice here that the stateful resource identifier that qualifies an endpoint reference of a WS-Resource is an XML document with an arbitrary payload. In jargon, we speak of this element as the ''key'' of the stateful resource. In gCF, in particular, the payload of resource keys is always a plain string, such as the 'name' of clients we expect to use in the design of our <code>Sample</code> service.
<pre>
+
|-SampleService
+
|--etc
+
|---profile.xml
+
|---deploy-jndi-config.xml
+
|---deploy-server.wsdd
+
|---build.properties
+
|
+
|--src
+
|---org
+
|----acme
+
|-----sample
+
|------ServiceContext.java
+
|------stateless
+
|-------Stateless.java
+
|------tests
+
|-------StatelessTest.java
+
|
+
|--schema
+
|---Stateless.wsdl
+
|---Factory.wsdl
+
|
+
|
+
|--build.xml
+
|
+
|-Dependencies
+
|--SampleService
+
</pre>
+
  
=== WSDL interface for the service instance ===
+
 
Define a new WSDL interface for the service instance:
+
The WSDL for the <code>Stateful</code> port-type is also straightforward, at least for the time being.
 +
 
<pre>
 
<pre>
 
<definitions name="Stateful"
 
<definitions name="Stateful"
Line 196: Line 169:
 
     xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults">
 
     xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults">
 
      
 
      
     <import namespace="http://gcube-system.org/namespaces/common/core/faults"  
+
     <import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
+
 
      
 
      
 
  <types>
 
  <types>
 
<xsd:schema targetNamespace="http://acme.org/sample">   
 
<xsd:schema targetNamespace="http://acme.org/sample">   
    <xsd:import namespace="http://gcube-system.org/namespaces/common/core/types"  
+
     
schemaLocation="../gcube/common/core/types/GCUBETypes.xsd"/>
+
            <xsd:import namespace="http://gcube-system.org/namespaces/common/core/types"  
 +
                                        schemaLocation="../gcube/common/core/types/GCUBETypes.xsd"/>
 
   
 
   
  <xsd:element name="aboutSF" type="coretypes:VOID" />
+
      <xsd:element name="aboutSF" type="coretypes:VOID" />
<xsd:element name="aboutSFResponse" type="xsd:string" />          
+
    <xsd:element name="aboutSFResponse" type="xsd:string" />          
 
                      
 
                      
 
</xsd:schema>
 
</xsd:schema>
Line 230: Line 203:
 
</pre>
 
</pre>
  
Notes:
+
Just notice the following:
* the port-type exposes a single operation, the ''aboutSF'' operation
+
 
* the operation does not take any input, in these cases it must be passed an element ''coretypes:VOID'' to avoid SOAP issues
+
[[Image:blue.jpg ]] The operation <code>aboutSF()</code> takes an instance of type <code>VOID</code> as a way to say that it takes nothing of interest. In particular, notice how the operation does ''not'' need any explicit information to identify the stateful resource that corresponds to the requesting client; as discussed [[#WS-Resources and The Implied Resource Pattern |earlier]] the identifier will be implicitly carried by the request, in accordance with the implied resource access pattern. As to the type <code>VOID</code>, this is imported from a schema document that ships with gCore (<code>GCUBETypes.xsd</code>), again relatively to the location of the interface ''after'' service deployment. The schema defines <code>VOID</code> and other common types, for your convenience and to promote uniform conventions in gCube.
* the operation returns a string
+
 
 +
Now store the new interfaces in the schema folder under the service location. Remember that file names and port-types must coincide:
  
Save the interface in a file '''Stateful.wsdl''' in the schema folder under the service location:
 
 
<pre>
 
<pre>
 
|-SampleService
 
|-SampleService
Line 256: Line 229:
 
|--schema
 
|--schema
 
|---Stateless.wsdl
 
|---Stateless.wsdl
|---Factory.wsdl
+
|---Factory.wsdl         [new]
|---Stateful.wsdl
+
|---Stateful.wsdl         [new]
 
|
 
|
 
|
 
|
Line 264: Line 237:
 
|-Dependencies
 
|-Dependencies
 
|--SampleService
 
|--SampleService
 
 
</pre>
 
</pre>
  
== Delving into the implementation ==
+
=== Stateful Resources ===
The implementation of the stateful part of the SampleService is a bit more complex than the [[The_Development_Cycle#A_Simple_Implementation|stateless implementation]] and requires to code:
+
# the stateful context,
+
# the stateful resource,
+
# the resource home,
+
# and the two new port-types.
+
All the new classes implemented in this part will be placed in the new <code>org.acme.sample.stateful</code> package.
+
  
'''Important note''': since we are leaving the stateless port-type as part of our example, we do not need to extend again the GCUBEStartupPortType (in charge of the initialisation of the GCUBEServiceContext) in one of the two new port-types. Instead, if we would start from scratch a complete stateful service, it is mandatory that one port-type extends the GCUBEStartupPortType (ideally, the Factory port-type, but it's up to developer.
+
Time to model stateful resources then. Inspiringly, we shall do this with objects of a <code>Resource</code> class. The minimal requirements on this class are indeed minimal:
  
===='''The Stateful Context'''====
+
[[Image:red.jpg]] it '''must''' extend a class provided by gCF, <code>GCUBEWSResource</code>.
----
+
We begin by implementing the Stateful Context. This context models the configuration of the ''acme/sample/stateful'' port-type. It transparently adds a lot of facilities to access the statefulness of the port-type.
+
<pre>
+
package org.acme.sample.stateful;
+
  
import org.acme.sample.ServiceContext;
+
[[Image:red.jpg]] it '''must''' implement <code>initialise()</code>, the only abstract method of <code>GCUBEWSResource</code>.
import org.gcube.common.core.contexts.GCUBEServiceContext;
+
import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext;
+
  
public class StatefulContext extends GCUBEStatefulPortTypeContext {
+
Here's a glorious Java class that does just that:
  
    public static String FREQUENT_USER_LIMIT_JNDI_NAME = "frequentUserLimit";
+
<pre>
   
+
package org.acme.sample.stateful;
    private static GCUBEStatefulPortTypeContext cache = new StatefulContext();
+
import ...
   
+
    @Override
+
    public String getJNDIName() {return "acme/sample/stateful";}
+
  
    @Override
+
public class Resource extends GCUBEWSResource {
    public String getNamespace() {return "http://acme.org/sample";}
+
  
     @Override
+
     /** Client visits.*/
     public GCUBEServiceContext getServiceContext() {
+
    private int visits;
return ServiceContext.getContext();
+
    /** Client name. */
 +
    private String name;
 +
 +
    /**{@inheritDoc}*/
 +
     public void initialise(Object... args) throws Exception {
 +
        if (args == null || args.length>1) throw new IllegalArgumentException();
 +
        this.setName((String) args[0]);
 
     }
 
     }
  
     public static GCUBEStatefulPortTypeContext getPortTypeContext() {
+
     public String getName() {return name;}
return cache;
+
    public void setName(String name) {this.name=name;}
     }      
+
 
 +
    public synchronized int getVisits() {return visits;}
 +
     protected synchronized void addVisit() {this.visits++;}
 +
   
 
}
 
}
 
</pre>
 
</pre>
  
Notes:
+
As you can see, there is little that you ''have'' to do to extend <code>GCUBEWSResource</code>. gCF will invoke <code>initialise()</code> in the process of creating a new WS-Resource. The single parameter is an array of zero or more parameters that might be required to initialise a stateful resource (here modelled as an optional parameter for the convenience of clients that have no parameters to pass). Here we expect the name of the client associated with the resource (the client whose visits the resource will track). We perform some checks on the input parameters (there must be exactly one), and then use it to initialise the resource in the obvious way.
* the class extends the <code>GCUBEStatefulPortTypeContext</code>
+
* the class adopts the singleton pattern as suggested for any context
+
* the <code>getJNDIName()</code> method returns the JNDI name of the port-type
+
* the <code>getServiceContext()</code> method connects the port-type to the service context
+
* the <code>getNamespace()</code> method returns the namespace of the port-type as defined in the [[Adding_State#WSDL_interface_for_the_service_instance|WSDL interface]]
+
  
===='''The Resource'''====
 
----
 
The Resource class is the core of the statefulness. It models the state of a port-type and the way in which it can be accessed and modified. Our Resource will maintain the state in 2 properties:
 
* the name of the user
 
* a counter about how many times it accesses the resource
 
  
<pre>
+
[[Image:blue.jpg]] <code>initialise()</code> works a bit like the <code>main()</code> method of standard a Java application, except that it takes <code>Object</code>s rather than <code>String</code>s. In the latter case, the parameters are typically specified on the command line, here you will pass them in from some other part of the code, where the decision to create a WS-Resource is first made. Remember that according to our [[#WS-Resources and The Implied Resource Pattern|plans]] we will do it from the implementation of the <code>Factory</code> port-type and in response to explicit client requests.
package org.acme.sample.stateful;
+
  
import org.gcube.common.core.state.GCUBEWSResource;
+
[[Image:blue.jpg]]  <code>Resource</code> inherits far more state and behaviour than it declares. We will unveil a small part of this heirloom as we go along. For now, we only notice that the stateful resource inherits an identifier and that the inherited method <code>getID()</code> is available to show it. The precise nature of this identifier depends on how the WS-Resource is created, and we will discuss it later when implementing the <code>Factory</code> port-type.
import org.globus.wsrf.ResourceException;
+
  
public class Resource extends GCUBEWSResource {
+
[[Image:blue.jpg]] Even the simplest of stateful resources must be ready for concurrent access. Although not exactly likely for our <code>Sample</code> service, many clients may target the same WS-Resource at the same time. Concurrent client visits will be assigned different threads by gCore and different threads might concurrently access the same stateful resource through the <code>addVisit()</code> and <code>getVisits()</code> methods. If we did not synchronise access to either of these methods our counter may then become inconsistent. Concurrency is of course a main concern when implementing a service and it is not something gCF can entirely abstract away for you.
  
    protected static final String RP_NAME = "Name";
+
Save <code>Resource</code> in accordance with the suggested package:
    protected static final String RP_VISITS = "Visits";
+
    protected static String[] RPNames = { RP_NAME, RP_VISITS };
+
  
    @Override
+
<pre>
    protected void initialise(Object... args) throws ResourceException {
+
|-SampleService
if (args.length!=1) throw new IllegalArgumentException();
+
|--etc
+
|---profile.xml
try {
+
|---deploy-jndi-config.xml
String name = (String) args[0];
+
|---deploy-server.wsdd
this.setName(name);     
+
|---build.properties
this.setVisits(0);     
+
|
}
+
|--src
catch (Exception e) {
+
|---org
throw new ResourceException(e);
+
|----acme
}
+
|-----sample
    }
+
|------ServiceContext.java
 +
|------stateless
 +
|-------Stateless.java
 +
|------stateful
 +
|-------Resource.java          [new]
 +
|------tests
 +
|-------StatelessTest.java
 +
|
 +
|--schema
 +
|---Stateless.wsdl
 +
|---Factory.wsdl
 +
|---Stateful.wsdl
 +
|
 +
|
 +
|--build.xml
 +
|
 +
|-Dependencies
 +
|--SampleService
 +
</pre>
  
    /**
+
=== Home Sweet Home ===
    * {@inheritDoc}
+
    */
+
    public String[] getPropertyNames() {
+
return RPNames;
+
    }
+
  
    /**
+
According to our [[#WS-Resources and The Implied Resource Pattern|plans]], the <code>Factory</code> port-type will create <code>Resource</code>s and the <code>Stateful</code> port-type will find them, use them, and change them.  
    * Returns the name of the RP stored on this Resource.
+
    *
+
    * @return the name.
+
    */
+
    public String getName() {
+
return (String) this.getResourcePropertySet().get(RP_NAME).get(0);
+
    }
+
  
    /**
+
Find them from where, exactly? We could hold a repository of <code>Resource</code>s in the implementation of the <code>Stateful</code> port-type, but a cleaner and more general approach is to have a dedicated manager of <code>Resource</code>s that can be accessed from multiple port-types and for different purposes. In gCF, managers of stateful resources are called ''resource homes''.
    *  Returns the number of visits for the user
+
    * @return
+
    */
+
    public Integer getVisits() {
+
return (Integer) this.getResourcePropertySet().get(RP_VISITS).get(0);
+
    }
+
  
    /**
+
Writing a simple resource home is as simple as writing a simple stateful resource. However, we cannot do it as incrementally. Along with the resource home we have to introduce a context for the associated port-type, <code>Stateful</code> in our case. Only so will gCF be able to link all the pieces required for state management on our behalf. In particular:
    * Sets the name of the property.
+
    *
+
    * @throws Exception
+
    */
+
    protected synchronized void setName(String name) throws Exception {
+
this.getResourcePropertySet().get(RP_NAME).clear();
+
this.getResourcePropertySet().get(RP_NAME).add(name);
+
  
    }
+
[[Image:red.jpg]] Stateful port-types '''must''' have an associated context in gCF.
  
    /**
+
Let us start from the home anyway:
    * Sets the name of the property.
+
    *
+
    * @throws Exception
+
    */
+
    protected synchronized void setVisits(Integer visits) throws Exception {
+
this.getResourcePropertySet().get(RP_VISITS).clear();
+
this.getResourcePropertySet().get(RP_VISITS).add(visits);
+
    }
+
   
+
}
+
 
+
</pre>
+
 
+
Notes:
+
* the class overrides the <code>initialise()</code> method to perform a custom initialisation of the properties
+
* the two properties are not maintained as private members of the class, they are transparently managed by the superclass in a <code>GCUBEWSResourcePropertySet</code> instance. In order to do that the class:
+
*# declares the name of the two properties
+
*# overrides the <code>getPropertyNames()</code> method to return the property names
+
* the getters and setters implementation of the properties reflects the <code>GCUBEWSResourcePropertySet</code> management of such properties
+
 
+
===='''The Resource Home'''====
+
----
+
The implementation of the Resource Home is very simple, since most of the work is performed by the superclass behind the scene. All it is needed is to override a method to connect the Home to the port-type context.
+
  
 
<pre>
 
<pre>
 
package org.acme.sample.stateful;
 
package org.acme.sample.stateful;
 
+
import ...
import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext;
+
import org.gcube.common.core.state.GCUBEWSHome;
+
  
 
public class Home extends GCUBEWSHome {
 
public class Home extends GCUBEWSHome {
  
     @Override
+
     /** {@inheritDoc} */
     public GCUBEStatefulPortTypeContext getPortTypeContext() {
+
     public GCUBEStatefulPortTypeContext getPortTypeContext() {return StatefulContext.getContext();}
return StatefulContext.getPortTypeContext();
+
    }
+
  
}
+
}  
 
</pre>
 
</pre>
Notes:
 
* the class extends the <code>GCUBEWSHome</code> class
 
* the <code>getPortTypeContext()</code> method connects the class to the stateful port-type
 
  
===='''The Service instance port-type'''====
+
Notice the requirements:
----
+
 
The <code>Service</code> class provides an implementation of the Service instance port-type defined in the [[Adding_State#WSDL_interface_for_the_service_instance|WSDL interface]].
+
[[Image:red.jpg]] The home implementation '''must''' extend a class provided by gCF, <code>GCUBEWSHome</code>.
 +
 
 +
[[Image:red.jpg]] The home implementation '''must''' implement <code>getPortTypeContext()</code>, the only abstract method of <code>GCUBEWSHome</code>.
 +
 
 +
Here we return the singleton instance of <code>StatefulContext</code>, where <code>StatefulContext</code> is defined as follows:
 +
 
 
<pre>
 
<pre>
 
package org.acme.sample.stateful;
 
package org.acme.sample.stateful;
 +
import ...
  
import org.acme.sample.ServiceContext;
+
public class StatefulContext extends GCUBEStatefulPortTypeContext {
import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext;
+
import org.gcube.common.core.contexts.GHNContext;
+
import org.gcube.common.core.faults.GCUBEFault;
+
import org.gcube.common.core.faults.GCUBEUnrecoverableException;
+
import org.gcube.common.core.types.VOID;
+
import org.globus.wsrf.ResourceException;
+
  
public class Service {
+
    /** Singleton instance. */
 +
    private static GCUBEStatefulPortTypeContext cache = new StatefulContext();
  
     public String aboutSF(VOID voidType) throws GCUBEFault {
+
     /**Creates an instance, privately. */
+
    private StatefulContext(){}
StringBuilder output = new StringBuilder();
+
GHNContext nctx = GHNContext.getContext();
+
ServiceContext sctx = ServiceContext.getContext();
+
GCUBEStatefulPortTypeContext pctx = StatefulContext.getPortTypeContext();
+
  
try {
+
    /** Returns the singleton context.
    final Resource resource = this.getResource();
+
    /* @return the context.*/
    resource.setVisits(resource.getVisits() + 1);
+
    public static GCUBEStatefulPortTypeContext getContext() {return cache;}
   
+
 
    output.append("Hello " + resource.getName()).append(", you have invoked porttype ").
+
    /** {@inheritDoc} */
    append(pctx.getName() + " \nof service " + sctx.getName()).
+
    public String getJNDIName() {return "acme/sample/stateful";}
    append(", \nwhich you found at ").
+
 
    append(pctx.getEPR() + "in the gCube infrastructure " + nctx.getGHN().getInfrastructure()).      
+
     /** {@inheritDoc} */
    append( " \nand you are in the Scope " + sctx.getScope()).
+
    public String getNamespace() {return "http://acme.org/sample";}
append(" \nThis is your invocation N." + resource.getVisits() + "\n");
+
   
+
    /** {@inheritDoc} */
    if (resource.getVisits() >= (Integer)pctx.getProperty(StatefulContext.FREQUENT_USER_LIMIT_JNDI_NAME, true)) {
+
     public GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}
    output.append("welcome in the frequent user club!");
+
    }      
+
} catch (Exception e) {
+
throw new GCUBEUnrecoverableException(e).toFault();
+
}
+
return output.toString();
+
    }
+
  
    /**
 
    *
 
    * @return the stateful resource
 
    * @throws ResourceException if no resource was found in the current context
 
    */
 
    private Resource getResource() throws ResourceException {
 
return (Resource) StatefulContext.getPortTypeContext().getWSHome().find();
 
    }   
 
 
}
 
}
 
</pre>
 
</pre>
Notes:
 
* the class implements the ''aboutSF'' operation in the <code>aboutSF()</code> method
 
* the method retrieves various contexts to use when producing the output string
 
* the most interesting part of the class is the <code>getResource()</code> method demonstrating the way to access from the stateful context the actual <code>Resource</code> instance
 
  
===='''The Factory port-type'''====
+
There is not much we have not [[Refining the implementation#Node & Port-Type Contexts|already seen]] here:
----
+
The <code>Factory</code> class provides an implementation of the Factory port-type defined in the [[Adding_State#WSDL_interface_for_the_factory_service|WSDL interface]].
+
<pre>
+
package org.acme.sample.stateful;
+
  
import org.apache.axis.message.addressing.EndpointReferenceType;
+
* we follow the usual singleton pattern by returning always a single, eagerly created instance.  
import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext;
+
* we follow the usual template pattern to indicate the JNDI configuration entry-point for the associated port-type (<code>getJNDIName()</code>), the namespace in which the interface of the port-type was declared (<code>getNamespace()</code>), and the context of the associated service (<code>getServiceContext()</code>).  
import org.gcube.common.core.faults.GCUBEFault;
+
import org.gcube.common.core.faults.GCUBEUnrecoverableException;
+
  
public class Factory {   
+
The only novel requirement is the following:
 +
 
 +
[[Image:red.jpg]] The contexts of stateful port-types '''must''' extend the <code>GCUBEStatefulPortTypeContext</code>, rather than the more general <code>GCUBEPortTypeContext</code>. The latter gCF class will provide our context with the additional behaviour which is required by the underlying assumption of state.
 +
 
 +
 
 +
Now gCF can link our home to our port-type. However, gCF will also need to do the opposite, i.e. identify the home from the port-type context. In fact, when <code>Sample</code> will start up, gCF will look into the configuration of the port-type first and will need to find in it enough information to instantiate the home. We must then dedicate a new section of the JNDI file to the configuration of this port-type, as we have done [[Refining the implementation#Node & Port-Type Contexts|earlier]]. In addition, we have to point to the associated resource home from there:
 +
 
 +
<pre>
 +
<service name="acme/sample/stateful">
 
      
 
      
    public EndpointReferenceType logon(String name) throws GCUBEFault {
+
<resource name="home" type="org.acme.sample.stateful.Home">
//create/reuse the resource
+
<resourceParams>                
try {
+
        <parameter>
GCUBEStatefulPortTypeContext ptcxt = StatefulContext.getPortTypeContext();
+
              <name>factory</name>
return ptcxt.getWSHome().create(ptcxt.makeKey(name), name).getEPR();
+
              <value>org.globus.wsrf.jndi.BeanFactory</value>
} catch (Exception e) {  
+
          </parameter>
    throw new GCUBEUnrecoverableException(e).toFault();
+
          <parameter>
}
+
                <name>resourceClass</name>
    }
+
                <value>org.acme.sample.stateful.Resource</value>
}
+
          </parameter>
 +
</resourceParams>
 +
</resource>
 +
     
 +
        <environment name="frequentUserThreshold" value="3" type="java.lang.Integer" override="false" />
 +
 
 +
</service>
 
</pre>
 
</pre>
Notes:
 
* the class implements the ''logon'' operation in the <code>logon</code> method
 
* the method retrieves the Stateful context and from it retrieves also the <code>Home</code> instance
 
* by invoking the <code>create</code> method, the stateful resource is created with the given key (first input parameter) and with the given list of parameters (only the <code>name</code> variable here). The list is then passed to the <code>initialise()</code> method of the <code>Resource</code> instance
 
* the <code>makeKey()</code> method of the <code>StatefulContext</code> instance creates a new <code>GCUBEWSResourceKey</code>
 
* the <code>getEPR()</code> method of the <code>Resource</code> instance return the EndPointReference to use to access the new state from clients
 
  
===='''Service folder tree after the implementation'''====
+
Notice the following:
All the java files described has to be placed in the src/org/acme/sample/stateful folder under the service location:
+
 
 +
[[Image:red.jpg]] The JNDI section contains a distinguished <code>resource</code> element that configures the resource home associated with the port-type. The terminology is a bit unfortunate here: this <code>resource</code> is a 'JNDI resource', nothing to do with a stateful resource! Along with the <code>environment</code> elements you have seen so far, it is one of the [[Configuration Components#The JNDI Configuration|two modelling primitives]] that we can use to structure information within JNDI fields. Hopefully, this will not be too confusing.
 +
 
 +
[[Image:red.jpg]] The <code>resource</code> element '''must''' have a <code>name</code> attribute with value <code>home</code>. Only so, can this JNDI resource be identified as the configuration of the resource home associated with the port-type. Then it '''must''' have a <code>type</code> that specifies the fully qualified name of the resource home implementation, <code>org.acme.sample.stateful.Home</code> in our case;
 +
 
 +
[[Image:red.jpg]] The <code>resource</code> element '''must''' be configured with at least a small number of <code>resourceParams</code>. The first <code>resourceParam</code> specifies a <code>factory</code> that can create an instance of the resource home we are configuring. The value of this <code>resourceParam</code> is normally always the same, a pre-defined bean factory that ships with gCore. This factory expects the resource home implementation to expose setters for all the other <code>resourceParam</code>s that occur under <code>resourceParams</code>. The <code>GCUBEWSHome</code> derived by our <code>Home</code> guarantees that this is the case for all the parameters that are pre-defined in gCore (if you were to add ad-hoc configuration parameters in your resource home, then your home implementation would have to include setters for these too). In most cases, you will stick with this factory implementation. We will surely do in this Primer!
 +
 
 +
[[Image:red.jpg]] The <code>resourceParams</code> '''must''' include a <code>resourceParam</code> called <code>resourceClass</code> that indicates the fully qualified name of the class that implements the stateful resources managed by the resource home. This allows your home to create your stateful resource objects reflectively. Here we specify <code>org.acme.sample.stateful.Resource</code> of course but we do not need to worry about using it directly. The <code>GCUBEWSHome</code> we inherited from gCF will do it for us at the right time.
 +
 
 +
[[Image:blue.jpg]] Since we are at it, we are throwing in some service-specific configuration, here a numeric threshold beyond which clients will belong to a Frequent User club. This is rather silly but reminds you that port-type contexts are there for your own configuration as well as the configuration required by gCF. We will use this configuration [[#The Stateful Port-Type|soon]] from the implementation of the <code>Stateful</code> port-type.
 +
 +
 
 +
In conclusion, we have added two more pieces to the implementation of <code>Sample</code>, a <code>Home</code> to manage <code>Resource</code>s and a <code>StatefulContext</code> for the <code>Stateful</code>. We have also added JNDI configuration for the <code>Stateful</code> port-type, particularly information about the associated <code>Home</code>. The implementation of <code>Sample</code> now look as follows:
 +
 
 
<pre>
 
<pre>
 
|-SampleService
 
|-SampleService
 
|--etc
 
|--etc
 
|---profile.xml
 
|---profile.xml
|---deploy-jndi-config.xml
+
|---deploy-jndi-config.xml     [changed]
 
|---deploy-server.wsdd
 
|---deploy-server.wsdd
 
|---build.properties
 
|---build.properties
Line 535: Line 442:
 
|-------Stateless.java
 
|-------Stateless.java
 
|------stateful
 
|------stateful
|-------StatefulContext.java
+
|-------Resource.java        
|-------Resource.java
+
|-------Home.java             [new]
|-------Home.java
+
|-------StatefulContext.java   [new]
|-------Service.java
+
|-------Factory.java
+
 
|------tests
 
|------tests
 
|-------StatelessTest.java
 
|-------StatelessTest.java
Line 555: Line 460:
 
</pre>
 
</pre>
  
== JNDI configuration ==
+
=== The Factory Port-Type ===
The '''deploy-jndi-config.xml''' file must be updated in order to add the configuration of the stateful port-type. The stateful configuration has to declare the "home" resource that instructs gCore about how to create a new stateful resource.
+
 
This configuration is automatically loaded by the gCore in the <code>[[Adding_State#The_Stateful_Context|StatefulContext]]</code> instance from which the configuration can be accessed programmatically.
+
With <code>Resource</code>, <code>ResourceHome</code>, and <code>StatefulPortTypeContext</code> our back-end for state management is in place. What we are left with is now to make co-ordinate use of these classes in the implementation of the <code>Factory</code> and <code>Stateful</code> port-types.
 +
 
 +
Starting with the <code>Factory</code> port-type:
  
 
<pre>
 
<pre>
<?xml version="1.0" encoding="UTF-8"?>
+
package org.acme.sample.stateful;
<jndiConfig xmlns="http://wsrf.globus.org/jndi/config">
+
import...
  
 +
public class Factory extends GCUBEPortType {   
  
<service name="acme/sample">
+
GCUBELog logger = new GCUBELog(this);
<environment
+
   
name="configDir"
+
/** {@inheritDoc} */
value="@config.dir@"
+
protected ServiceContext getServiceContext() {return ServiceContext.getContext();}
type="java.lang.String"
+
override="false" />
+
+
</service>
+
  
 +
   
 +
        public EndpointReferenceType logon(String name) throws GCUBEFault {
 +
 +
          try {
 +
                GCUBEStatefulPortTypeContext ptcxt = StatefulContext.getContext();
 +
                GCUBEWSHome home = ptcxt.getWSHome();
 +
                GCUBEWSResourceKey key = ptcxt.makeKey(name);
 +
                GCUBEWSResource resource = home.create(key,name);
 +
                return resource.getEPR();
 +
          } catch (Exception e) {
 +
                logger.error("unable to logon", e);
 +
                throw new GCUBEUnrecoverableException(e).toFault();
 +
          }
 +
        }
 +
}
 +
</pre>
  
<service name="acme/sample/stateful">  
+
As we have [[The Development Cycle#A Simple Implementation|already seen]] for the <code>Stateless</code> port-type, our <code>Factory</code> implementation extends <code>GCUBEPortType</code> and implements the <code>getServiceContext()</code> method.
                <resource name="home" type="org.acme.sample.stateful.Home">
+
 
<resourceParams>                
+
As to the method <code>logon()</code>, notice what follows:
        <parameter>
+
 
              <name>factory</name>
+
[[Image:blue.jpg]] In accordance with its [[More Port-Type Interfaces|interface]], <code>logon()</code> takes a <code>String</code> and returns an <code>EndpointReferenceType</code>, which is a Java model for the WS-Addressing's schema type mentioned in the WSDL of the port-type. <code>EndpointReferenceType</code> Java type ships with gCore.
              <value>org.globus.wsrf.jndi.BeanFactory</value>
+
 
          </parameter>
+
[[Image:blue.jpg]] We obtain the singleton instance of the <code>StatefulContext</code> port-type and use it to retrieve the associated resource home. The method <code>getHome()</code> is defined in <code>GCUBEStatefulPortType</code> and our <code>StatefulContext</code> has simply inherited it. The JNDI configuration of the port-type has provided enough information to gCF to implement that method on our behalf. Note also that since our <code>Home</code> does not add any behaviour to the generic <code>GCUBEWSHome</code> (nor would we need it here), we do not need to cast the return value of <code>getWSHome()</code> to <code>Home</code> but can work directly with the supertype <code>GCUBEWSHome</code>.
          <parameter>
+
 
                <name>resourceClass</name>
+
[[Image:blue.jpg]] We ask the home to create a stateful resource by invoking the method <code>create()</code> on it. This method is predefined in <code>GCUBEWSHome</code> (and thus in our <code>Home</code> that inherits from it). It appears to return a generic <code>GCUBEWSResource</code> but there is a more specific <code>Resource</code> underneath. The home has looked into the JNDI configuration of the port-type to know which class to instantiate reflectively (remember the <code>resourceClass</code> configuration parameter?). In any case, we do not need to do anything specific with this resource, so we can again leave it at that without needing to cast down to the more specific <code>Resource</code>.
                <value>org.acme.sample.stateful.Resource</value>
+
 
          </parameter>
+
[[Image:blue.jpg]] We invoke <code>create()</code> with the identifier that we wish to give to the stateful resource and the parameters required to initialise it, here only the name of the client. This identifier is based on the <code>name</code> parameter provided by the client but it's actually a <code>GCUBEWSresourceKey</code> wrapper that we can ask the <code>StatefulContext</code> to produce for us. The reason of wrapping our identifier into a 'key' is because we need to return it to the client and thus it needs to serialise on the wire in accordance with WS-Addressing requirements for endpoint reference qualifications. Notice that we have [[#WS-Resources and The Implied Resource Pattern|already introduced]] the notion of a WS-Resource key, check it out.  
        </resourceParams>
+
 
        </resource>
+
[[Image:blue.jpg]] Finally, we invoke the method <code>getEPR()</code> on our resource. The method is predefined in <code>GCUBEWSResource</code> and returns the endpoint reference of the WS-Resource that encapulates our resource. Then we simply return the endpoint reference to the client.
+
 
  <environment
+
A couple of extra commonents on <code>create()</code>:
name="frequentUserLimit"  
+
 
value="3"
+
[[Image:blue.jpg]] <code>create()</code> can accept an arbitrary number of resource initialisation parameters. If more were required, we would pass them all after the key, comma-separating them (or as an array). As we have seen [[Stateful_Resources|earlier]] these parameters will end up into the <code>initialise()</code> method of the <code>Resource</code> class. Similarly, we could pass zero initialisation parameters (i.e. only the key) if the resource did not need any at all to initialise.
type="java.lang.Integer"
+
 
override="false" />
+
[[Image:blue.jpg]] the <code>create()</code> could have also ''not'' taken a key at all (only parameters or absolutely nothing). In this case, the home would have automatically generated a key for the resource. The decision of whether to specify an identifier or not is our own to make. If we specify one, then the home will avoid creating a fresh resource ''if'' there is already one with that identifier. So, we specify a key any time we wish to ''reuse'' resources across 'semantically equivalent' requests. If this is not the required behaviour, i.e. we do not want reuse of stateful resources, then we do not pass a key to <code>create()</code>. This depends of course on the semantics of our service; if reuse is a sensible option we should strive for it, as creating stateful resources might be in principle arbitrarily expensive processes. Here, rather artificially, we assume that the name is an unambiguous identifier for clients (...) and decide that if we receive many requests with the same name then we will associate all of them with the same WS-Resource.
</service>
+
 
 
+
=== The Stateful Port-Type ===
</jndiConfig>
+
 
 +
Finally we come to the consumption of WS-Resources and thus to the implementation of the <code>Stateful</code>:
 +
 
 +
<pre>
 +
package org.acme.sample.stateful;
 +
import ...
 +
 
 +
public class Stateful extends GCUBEPortType {
 +
 
 +
    private final String THRESHOLD_JNDI_NAME="frequentUserThreshold";
 +
   
 +
    ...
 +
 
 +
    /** {@inheritDoc} */
 +
    protected ServiceContext getServiceContext() {return ServiceContext.getContext();}
 +
 
 +
    public String aboutSF(VOID voidType) throws GCUBEFault {
 +
 
 +
          ServiceContext sctx = ServiceContext.getContext();
 +
          StatefulContext pctx = StatefulContext.getContext();
 +
 
 +
          try {
 +
            Resource resource = this.getResource()
 +
            String name = resource.getName();
 +
            StringBuilder output = new StringBuilder();
 +
 
 +
                  ...build output as in Stateless.about()...
 +
 
 +
            output.append("\nThis is your invocation N." + resource.getVisits() + "\n");
 +
            int threshold = (Integer) pctx.getProperty(THRESHOLD_JNDI_NAME);
 +
            if (resource.getVisits() >= threshold) output.append("welcome in the frequent user club!");
 +
            resource.addVisit();
 +
            return output.toString();
 +
 
 +
          }
 +
          catch (GCUBEException e) {throw e.toFault();}
 +
          catch (Exception e) {throw sctx.getDefaultException(e).toFault();}
 +
    }
 +
 
 +
 
 +
    private Resource getResource() throws ResourceException {
 +
    return (Resource) StatefulContext.getContext().getWSHome().find();
 +
    }
 +
 
 +
    ...
 +
 
 +
}
 
</pre>
 
</pre>
  
The following is worth noticing:  
+
There are essentially two things to notice here:
* the JNDI still included the mandatory <code>service</code> element entirely dedicated to the SampleService as a whole
+
 
* the <code>service</code> element named ''acme/sample/stateful'' groups the configuration of our stateful port-type; here it only includes the Home resource and a custom property. In the section [[Managing_State#Publication_Profile|How to publish the state ]] we will see other configuration elements to add in this service element allowing to publish the service state
+
[[Image:blue.jpg]] the method <code>aboutSF()</code> behaves like <code>about()</code> in the implementation of the <code>Stateless</code> port-type (check any of the versions we have looked at before). The significant difference is that now we are acting in the context of a WS-Resource and thus upon an underlying stateful resource. We know by now that this resource will be implicitly identified in the endpoint reference use to call this WS-Resource, and we delegate the task to retrieve it to a private helper method <code>getResource()</code>. Assuming we get one back, we increment its count of client visits and proceed with satisfying the client request. In particular, we append the current count of visits at the end of the message, consult port-type configuration via its context to decide whether we should also welcome the client to the frequent user club, and finally  return the message to the client.
* the <code>frequentUserLimit</code> environment element shows an example of custom property that can be programmatically read within the service code
+
 
 +
[[Image:blue.jpg]] in <code>getResource()</code>, we delegate in turn the task to retrieve the stateful resource to the resource home, which we obtain from the context of the <code>Stateful</code> port-type. We then ask the home to find the required resource, by invoking a method <code>find()</code> that our <code>Home</code> inherits from <code>GCUBEWSHome</code>. As the invocation specifies no parameters, the home resolves it by automatically extracting the endpoint reference with which the current call was made by the client. It will then look into that reference and extract the stateful resource key and use the key to lookup the required resource. As this operation is a general one defined by <code>GCUBEWSHome</code>, its return type is as generic as <code>GCUBEWSResource</code> and, since we plan to invoke <code>Resource</code>-specific methods, we need to down cast it before we can return a specific <code>Resource</code>.
 +
 
 +
[[Image:blue.jpg]] in the process of implied resource access, a few things could go wrong. The call may have been made with an unqualified endpoint reference, i.e. might have been addressed to the port-type rather than a WS-Resource that encapsulates the port-type. Even if the endpoint reference is qualified with a key, the home may still fail to find a resource with that key. This could be because one such resource never existed (the endpoint reference was somehow incorrectly built), or because it existed but it has somehow been removed. We will return later to this possibility.
 +
 
 +
 
 +
With our two new port-type implementations, the service implementation ought to look as follows:
  
Save the new configuration in the file called '''deploy-jndi-config.xml''' and place it in the etc folder under the service location:
 
 
<pre>
 
<pre>
 
|-SampleService
 
|-SampleService
Line 620: Line 591:
 
|-------Stateless.java
 
|-------Stateless.java
 
|------stateful
 
|------stateful
|-------StatefulContext.java
+
|-------Resource.java        
|-------Resource.java
+
|-------Home.java            
|-------Home.java
+
|-------StatefulContext.java  
|-------Service.java
+
|-------Factory.java           [new]
|-------Factory.java
+
|-------Stateful.java         [new]
 
|------tests
 
|------tests
 
|-------StatelessTest.java
 
|-------StatelessTest.java
Line 641: Line 612:
 
</pre>
 
</pre>
  
== Building & Deploying ==
+
=== Building & Deploying ===
 +
 
 +
The service implementation is almost complete. What we are left with is to reflect the new port-types in the [[The Development Cycle#The Deployment Descriptor|deployment descriptor]] and in the [[The Development Cycle#The Build Properties|build properties]].
 +
 
 +
In <code>build.properties</code> we add two more <code>wsdl</code> properties, though we don't need to add any <code>namespace</code> property since we defined all the new interfaces in the same namespace as the first (<code>http://acme.org/sample</code>):
 +
 
 +
<pre>
 +
package = org.acme.sample
 +
lib.dir = Dependencies/SampleService
 +
wsdl.1 = Stateless
 +
wsdl.2 = Stateful
 +
wsdl.3 = Factory
 +
namespace.1=http://acme.org/sample
 +
</pre>
 +
 
 +
In <code>deploy-server.wsdd</code> we add two more <code>service</code> sections, essentially following the same pattern used for the <code>Stateless</code> port-type:
  
=== Towards Deployment: the WSDD descriptor ===
 
The two new port-types have to be declared in the deployment descriptor as follows.
 
 
<pre>
 
<pre>
 
<?xml version="1.0" encoding="UTF-8"?>
 
<?xml version="1.0" encoding="UTF-8"?>
<deployment name="defaultServerConfig"  
+
<deployment name="defaultServerConfig" xmlns="http://xml.apache.org/axis/wsdd/"
    xmlns="http://xml.apache.org/axis/wsdd/"  
+
                        xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"  
+
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+
  
 
     <service name="acme/sample/stateless" provider="Handler" use="literal" style="document">
 
     <service name="acme/sample/stateless" provider="Handler" use="literal" style="document">
Line 659: Line 641:
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="loadOnStartup" value="true"/>
 
         <parameter name="loadOnStartup" value="true"/>
        <parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/>
+
    </service>
    </service>
+
 
 
+
    <service name="acme/sample/factory" provider="Handler" use="literal" style="document">
  <service name="acme/sample/stateful" provider="Handler" use="literal" style="document">
+
         <parameter name="className" value="org.acme.sample.stateful.Factory"/>
         <parameter name="className" value="org.acme.sample.stateful.Service"/>
+
         <wsdlFile>share/schema/org.acme.sample/Factory_service.wsdl</wsdlFile>
         <wsdlFile>share/schema/org.acme.sample/Stateful_service.wsdl</wsdlFile>
+
 
         <parameter name="allowedMethods" value="*"/>
 
         <parameter name="allowedMethods" value="*"/>
 
         <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
 
         <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="scope" value="Application"/>
        <parameter name="providers" value="GCUBEProvider"/>
 
 
         <parameter name="loadOnStartup" value="true"/>
 
         <parameter name="loadOnStartup" value="true"/>
      <parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/>
 
 
     </service>
 
     </service>
 
    
 
    
  <service name="acme/sample/factory" provider="Handler" use="literal" style="document">
+
    <service name="acme/sample/stateful" provider="Handler" use="literal" style="document">
         <parameter name="className" value="org.acme.sample.stateful.Factory"/>
+
         <parameter name="className" value="org.acme.sample.stateful.Stateful"/>
         <wsdlFile>share/schema/org.acme.sample/Factory_service.wsdl</wsdlFile>
+
         <wsdlFile>share/schema/org.acme.sample/Stateful_service.wsdl</wsdlFile>
 
         <parameter name="allowedMethods" value="*"/>
 
         <parameter name="allowedMethods" value="*"/>
 
         <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
 
         <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="loadOnStartup" value="true"/>
 
         <parameter name="loadOnStartup" value="true"/>
      <parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/>
 
 
     </service>
 
     </service>
 
+
 
 
</deployment>
 
</deployment>
 
 
</pre>
 
</pre>
The '''deploy-server.wsdd''' file has to be updated with the above content in the etc folder under the service location:
+
 
 +
Now [[The Development Cycle#Building & Deploying|build and deploy]] service and stubs. As to the service implementation, the latest snapshot is as follows:
 +
 
 
<pre>
 
<pre>
 
|-SampleService
 
|-SampleService
Line 692: Line 671:
 
|---profile.xml
 
|---profile.xml
 
|---deploy-jndi-config.xml
 
|---deploy-jndi-config.xml
|---deploy-server.wsdd
+
|---deploy-server.wsdd       [changed]
|---build.properties
+
|---build.properties         [changed] 
 
|
 
|
 
|--src
 
|--src
Line 703: Line 682:
 
|-------Stateless.java
 
|-------Stateless.java
 
|------stateful
 
|------stateful
|-------StatefulContext.java
+
|-------Resource.java        
|-------Resource.java
+
|-------Home.java            
|-------Home.java
+
|-------StatefulContext.java  
|-------Service.java
+
|-------Factory.java        
|-------Factory.java
+
|-------Stateful.java        
 
|------tests
 
|------tests
 
|-------StatelessTest.java
 
|-------StatelessTest.java
Line 721: Line 700:
 
|-Dependencies
 
|-Dependencies
 
|--SampleService
 
|--SampleService
 +
</pre>
 +
 +
=== A Quick Test ===
 +
 +
Ok, it's time to make sure that all the pieces developed so far come together nicely. For this purpose, we will write a couple of simple test clients. The first client, <code>CreateResource</code>, will invoke the <code>logon()</code> operation of the <code>Factory</code> port-type to create a WS-Resource. The second client, <code>StatefulTest</code>  will invoke the method <code>aboutSF()</code> of that WS-Resource. The two clients will exchange the endpoint reference of the WS-Resource that the first creates and the second consumes. The exchange will be based on the file system: <code>CreateResource</code> will serialise on file the endpoint reference of the WS-Resource it creates; <code>StatefulTest</code> will deserialise the endpoint reference from that file and use it.
 +
 +
Starting with <code>CreateResource</code>:
 +
 +
<pre>
 +
package org.acme.sample.tests;
 +
import ...
 +
 +
public class CreateResource {
 +
   
 +
    static GCUBEClientLog logger = new GCUBEClientLog(CreateResource.class);
 +
 +
    public static void main(String[] args) throws Exception {
 +
   
 +
      logger.info("creating WS-Resource...");
 +
     
 +
      try {
 +
       
 +
        EndpointReferenceType factoryEPR = new EndpointReferenceType(new AttributedURI(args[0]));
 +
        FactoryPortType stub = new FactoryServiceAddressingLocator().getFactoryPortTypePort(endpoint);
 +
        stub=GCUBERemotePortTypeContext.getProxy(stub,GCUBEScope.getScope(args[1]));
 +
     
 +
        EndpointReferenceType resourceEpr = stub.logon(args[2]);
 +
        logger.trace("created resource at endpoint " + resourceEpr);
 +
 +
        FileWriter writer = new FileWriter("/tmp/resource.epr");
 +
        ObjectSerializer.serialize(writer,resourceEpr,new QName("http://acme.org/sample","statefulEPR"));
 +
        writer.close();
 +
     
 +
      }
 +
     
 +
    }
 +
}
  
 
</pre>
 
</pre>
 +
 +
The first part of the client has been [[The Development Cycle#A Minimal Client|already discussed]] when we tested the <code>Stateless</code> port-type, including [[Refining the implementation#Logging_it!|logging practices]]. We expect three
 +
arguments: the endpoint reference of a running <code>Factory</code>, a scope in which to make the call which is compatible with the targeted <code>Factory</code>, and a name for the client.
 +
 +
We then invoke the <code>logon()</code> operation and get back an endpoint reference to a WS-Resource. To store it, we invoke the <code>serialize()</code> method of a utility class that ships with gCore, <code>ObjectSerializer</code>, passing a <code>FileWriter</code> 'open' on a given file, the WS-Resource endpoint reference to serialise, and a <code>QName</code> of our own invention that wraps the endpoint reference in well-formed XML.
 +
 +
Moving on to <code>StatefulTest</code>:
  
=== Configuring the build process ===
 
The build properties configuration file has now to declare the two new port-types to build.
 
 
<pre>
 
<pre>
package = org.acme.sample
+
package org.acme.sample.tests;
lib.dir = Dependencies/SampleService
+
import ...
wsdl.1 = Stateless
+
 
wsdl.2 = Stateful
+
public class StatefulTest {
wsdl.3 = Factory
+
   
namespace.1=http://acme.org/sample
+
    static GCUBEClientLog logger = new GCUBEClientLog(StatefulTest.class);
 +
 +
    public static void main(String[] args) throws Exception {
 +
   
 +
      logger.info("visiting WS-Resource...");
 +
     
 +
      try {
 +
 
 +
        FileReader reader = new FileReader("/tmp/resource.epr");
 +
        EndpointReferenceType resourceEPR=  
 +
              (EndpointReferenceType) ObjectDeserializer.deserialize(newInputSource(reader),EndpointReferenceType.class);
 +
        reader.close();
 +
       
 +
        StatefulPortType stub = new StatefulServiceAddressingLocator().getStatefulPortTypePort(resourceEPR);
 +
        stub = GCUBERemotePortTypeContext.getProxy(stub, GCUBEScope.getScope(args[0]));
 +
       
 +
        logger.trace(stub.aboutSF(new VOID()));
 +
     
 +
      }
 +
     
 +
    }
 +
}
 +
 
 
</pre>
 
</pre>
  
The '''build.properties''' file has to be updated with the above content in the etc folder under the service location:
+
Here, we deserialise the resource following a process inverse to its serialisation (just take this code as boiler plate, really). The rest ought to be familiar by now, including the expectation of a scope as a test parameter.
 +
 
 +
Now, run <code>CreateResource</code> once and then <code>StatefulTest</code> multiple times to see the count of visits growing at each call.
 +
 
 +
With the two test clients, the service implementation ought to look as follows:
 +
 
 
<pre>
 
<pre>
 
|-SampleService
 
|-SampleService
 
|--etc
 
|--etc
 
|---profile.xml
 
|---profile.xml
|---deploy-jndi-config.xml
+
|---deploy-jndi-config.xml    
 
|---deploy-server.wsdd
 
|---deploy-server.wsdd
 
|---build.properties
 
|---build.properties
Line 752: Line 800:
 
|-------Stateless.java
 
|-------Stateless.java
 
|------stateful
 
|------stateful
|-------StatefulContext.java
+
|-------Resource.java        
|-------Resource.java
+
|-------Home.java            
|-------Home.java
+
|-------StatefulContext.java  
|-------Service.java
+
|-------Factory.java  
|-------Factory.java
+
|-------Stateful.java  
 
|------tests
 
|------tests
 
|-------StatelessTest.java
 
|-------StatelessTest.java
 +
|-------CreateResource.java      [new]
 +
|-------StatefulTest.java        [new]
 
|
 
|
 
|--schema
 
|--schema
Line 769: Line 819:
 
|
 
|
 
|-Dependencies
 
|-Dependencies
|--SampleService
 
 
</pre>
 
</pre>
  
=== Building the stubs and the service ===
+
== WS-Resources and Standard Interfaces ==
The building process does not change. The same steps seen in the Stateless part of the tutorial have to be performed for building the [[The_Development_Cycle#Building_.26_Deploying_Stubs|stubs]] and the [[The_Development_Cycle#Building_.26_Deploying_the_Service|service]].
+
  
=== (Un)Deploying the service ===
+
At this stage, we have succesfully implemented the implied resource pattern promoted by WSRF. This is of value in itself, as a clean design approach to state identification and access. As discussed [[#WS-Resources and The Implied Resource Pattern|earlier on]], however, the pattern opens possibilities for generic manipulations of state oriented towards discovery, lifetime management, and notification management. To move in that direction, we need to expose a uniform description of the WS-Resources and also augment their interfaces and implementations with generic operations. If we don't do this, the state will be uniformly accessible in principle, but it will also remain utterly opaque and inaccessible to generic clients.
The (un)deploying process does not change. The same steps seen in the Stateless part of the tutorial have to be performed for deploying and undeploying the [[The_Development_Cycle#Building_.26_Deploying_the_Service|service]].
+
 
 +
=== WS-Resource Properties ===
 +
 
 +
WSRF proposes a standard for exposing state as an unordered set of [http://www.ibm.com/developerworks/library/specification/ws-resource/ws-resourceproperties.pdf Resource Properties]. A Resource Property, or RP for short, is any piece of information that we wish to make ''public'' about our WS-Resources. A RP may be statically or dynamically computed, it may be constant or else change over time or not, it may occur once or many times, and it may or may not have internal structure. What it does matter is that a RP has a ''name'' that clients can use to refer to it.
 +
 
 +
In our example, we may decide to consider the name of the client associated with a WS-Resource as a single-valued and constant RP. Instead, we may decide to consider the number of client visits as a piece of private state and not expose it as a RP at all. In fact, unknown to us, our WS-Resources already include some 'hidden' state modelled as RPs. gCF augmented them implicitly with RPs that describe various systemic properties of the WS-Resources (e.g. the scope in which they operate, the identifier of the gHN in which they are hosted, the name and class of the service to which they belong, etc.). Collectively, we refer to these pre-defined RPs as the ''gCube RPs''.
 +
 
 +
gCube RPs and service-specific RPs collectively form the public state of our WS-Resources, something that generic clients can target without knowing anything more specific about our WS-Resources. To actually act upon our WS-Resource, WSRF also defines standard interfaces with which clients can access, query, and change RPs. gCube has in turn implemented many key infrastructure-wide mechanisms that make use of these interfaces, and thus is important that our WS-Resources implement them. We don't need to worry too much about the cost of compliance, however, because gCF offers pre-defined implementations that can be 'plugged' into our services. Effectively, these implementations ''extend'' the stateful port-types with support for the operations defined in the WSRF interfaces. In particular, gCF defines one such implementation, called the ''gCube Provider'', that supports the minimal set of WSRF operations that are mandatory for the WS-Resource of ''all'' gCube services.
 +
 
 +
To extend a stateful port-type with the gCube Provider, we need to perform three steps:
 +
 
 +
* extend the interface of the stateful port-type with the interface of the gCube Provider.
 +
* extend the implementation of the stateful port-type with the implementation of the gCube provider.
 +
* model the state of stateful resources as RPs.
 +
 
 +
After completing these three steps we will have added the operations of the gCube Provider to the stateful port-type and will have linked them to the implementation of our stateful resources. In other words, clients will be able to invoke the operations of the gCube Provider and the implementations of these operations will be able to access our stateful resources.
 +
As a net result, our WS-Resources will have become compliant with WSRF and thus with gCube. No small feat.
 +
 
 +
Let us now go through these steps for our <code>Sample</code> service.
 +
 
 +
=== Extending WS-Resource Interface & Implementation ===
 +
 
 +
We start by extending the interface of the <code>Stateful</code> port-type with the interface of the gCube Provider. In the WSDL of the <code>Stateful</code> port-type we must proceed as follows:
 +
 
 +
[[Image:red.jpg]] we '''must''' first include a schema description of the available RPs in the WSDL of the port-type, where a generic client that obtains the WSDL can in principle discover them. This effectively amounts to describing the public state of our WS-Resources as a well-formed XML document, what WSRF calls the ''Resource Property Document'' (RPD). The RPD description is always a global, complex element which we are otherwise free to name. In our case, the element is called <code>statefulRPD</code> and includes the description of a single-valued RP called <code>Name</code>:
  
== A Test Client ==
 
 
<pre>
 
<pre>
package org.acme.sample.tests;
+
<xsd:element name="statefulRPD">
 +
<xsd:complexType>
 +
  <xsd:sequence>
 +
    <xsd:element ref="tns:Name" minOccurs="1" maxOccurs="1"/>
 +
  </xsd:sequence>
 +
</xsd:complexType>
 +
</xsd:element>
 +
</pre>
  
import org.acme.sample.stubs.FactoryPortType;
 
import org.acme.sample.stubs.StatefulPortType;
 
import org.acme.sample.stubs.service.FactoryServiceAddressingLocator;
 
import org.acme.sample.stubs.service.StatefulServiceAddressingLocator;
 
import org.apache.axis.message.addressing.AttributedURI;
 
import org.apache.axis.message.addressing.EndpointReferenceType;
 
import org.gcube.common.core.scope.GCUBEScope;
 
import org.gcube.common.core.types.VOID;
 
import org.gcube.common.core.utils.logging.GCUBEClientLog;
 
import org.gcube.common.core.contexts.GCUBERemotePortTypeContext;
 
  
public class StatefulTest {
+
[[Image:red.jpg]] we '''must''' add two special attributes to the <code>portType</code> element of the WSDL. The first points generic clients to the <code>statefulRPD</code> element, so that they can identify it within the WSDL. The second indicates our will to extend the port-type interface with the operations defined in the WSDL of the gCube Provider. Clearly, we need also to import the new namespaces that come into play and bind them to some suffixes in the top-level <code>definitions</code> element of the WSDL:
   
+
    static GCUBEClientLog logger = new GCUBEClientLog(StatefulTest.class);
+
       
+
public static void main(String[] args) throws Exception {
+
   
+
    logger.debug("Stateful client is running...");        
+
    EndpointReferenceType factory_endpoint = new EndpointReferenceType();
+
    factory_endpoint.setAddress(new AttributedURI(args[0]));
+
FactoryPortType factoryPT = new FactoryServiceAddressingLocator().getFactoryPortTypePort(factory_endpoint);
+
  
//we proxy the factory PT with the desired scope
+
<pre>
factoryPT = GCUBERemotePortTypeContext.getProxy(factoryPT, GCUBEScope.getScope(args[2]));
+
<definitions name="Stateful"
EndpointReferenceType service_endpoint = factoryPT.logon(args[1]);
+
    ....
StatefulPortType statefulPT = new StatefulServiceAddressingLocator().getStatefulPortTypePort(service_endpoint);
+
    xmlns:wsdlpp="http://www.globus.org/namespaces/2004/10/WSDLPreprocessor"
 +
    xmlns:wsrp="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.xsd"
 +
    xmlns:provider="http://gcube-system.org/namespaces/common/core/porttypes/GCUBEProvider">
  
// then we have to proxy also the stateful PT...
+
    ...
statefulPT = GCUBERemotePortTypeContext.getProxy(statefulPT, GCUBEScope.getScope(args[2]));
+
System.out.println(statefulPT.aboutSF(new VOID()));
+
  <import namespace="http://gcube-system.org/namespaces/common/core/porttypes/GCUBEProvider" location="../gcube/common/core/providers/GCUBEProvider.wsdl"/>
 +
 
 +
  ...
  
//... but only once, if we use it again, there is no need to proxy again for subsequent uses
+
  <portType name="StatefulPortType" wsdlpp:extends="provider:GCUBEProvider" wsrp:ResourceProperties="tns:statefulRPD">
System.out.println(statefulPT.aboutSF(new VOID()));
+
 
}
+
    ...
 +
</pre>
 +
 
 +
We now move to extending the implementation of the <code>Stateful</code> port-type with the implementation of the gCube Provider. This will not require us to change any code, only the deployment descriptor of the <code>Stateful</code> port-type. In particular:
 +
 
 +
[[Image:red.jpg]] we '''must''' add the following <code>parameter</code> to the <code>service</code> dedicated to the port-type (in no particular place):
 +
 
 +
<pre>
 +
<parameter name="providers" value="GCUBEProvider"/>
 +
</pre>
 +
 
 +
That's it. Now when the operations of the gCube Provider are invoked on our <code>Stateful</code> port-type their original implementations will be found and executed.
 +
 
 +
=== Modelling State as Resource Properties ===
 +
 
 +
One last step to complete the integration of the gCube Provider in our service implementation: when an operation of the gCube Provider is invoked on our <code>Stateful</code> port-type, its implementation will try to access our stateful resources. The problem is that it will do so in terms of the abstractions it knows about, namely RPs.
 +
Although our <code>Resource</code> class defines a <code>String</code>-valued field that corresponds to the <code>Name</code> RP we have declared in the WSDL, the gCube Provider has no idea that this field corresponds to the RP, nor does it know what fields correspond to RPs and what fields are instead meant to be private state.
 +
 
 +
The cleanest and most general solution here is to comply with the provider's expectations and move from a <code>String</code>-based model of client names to a more explicit RP-based model.
 +
gCF offers classes to model RPs as well as a class to group RPs into a single container, the so-called ''Resource Property Set'' of the WS-Resource. The gCube Provider will then expect our <code>Resource</code> class to expose the RP Set and then it will work with the RP objects it finds within it. In practice, the <code>GCUBEWSResource</code> class extended by our <code>Resource</code> class takes care of most requirements. All we have to do is:
 +
 
 +
[[Image:blue.jpg]] indicate the names of the RPs. We do so by overriding the method <code>getPropertyNames()</code> inherited from <code>GCUBEWSResource</code> and returning the names into an array of <code>String</code>s:
 +
 
 +
<pre>
 +
private static final String NAME_RP_NAME = "Name";
 +
...
 +
 
 +
/** {@inheritDoc} */
 +
protected String[] getPropertyNames() {return new String[]{NAME_RP_NAME};}
 +
</pre>
 +
 
 +
[[Image:blue.jpg]] refactor accessor methods in terms of RP objects:
 +
 
 +
<pre>
 +
public synchronized String getName() {
 +
  return (String) this.getResourcePropertySet().get(NAME_RP_NAME).get(0);
 
}
 
}
 +
   
 +
public synchronized void setName(String name) {
 +
  ResourceProperty property = this.getResourcePropertySet().get(NAME_RP_NAME);
 +
  property.clear();
 +
  property.add(name);
 +
}
 +
</pre>
  
 +
As you can see, we first obtain the whole RP Set (inherited method <code>getResourcePropertySet()</code>), and then use it to lookup by name the required RP (method <code>get()</code>).
 +
As RPs may be multi-valued, working with RP objects is similar to working with lists; we obtain the single-value of our <code>Name</code> property as the first element of the list (method <code>get()</code>), and we set it by first emptying the list (method <code>clear()</code>)) and then adding to it in the next place available, which is again the first (method <code>set()</code>). As the accessors are no longer atomic operations we also synchronise them. Once the accessors have been re-factored in terms of RP objects, the rest of the code can remain unchanged.
 +
 +
 +
After all these changes, our service implementation will look as follows:
 +
 +
<pre>
 +
|-SampleService
 +
|--etc
 +
|---profile.xml
 +
|---deploy-jndi-config.xml   
 +
|---deploy-server.wsdd          [changed]
 +
|---build.properties
 +
|
 +
|--src
 +
|---org
 +
|----acme
 +
|-----sample
 +
|------ServiceContext.java
 +
|------stateless
 +
|-------Stateless.java
 +
|------stateful
 +
|-------Resource.java          [changed] 
 +
|-------Home.java             
 +
|-------StatefulContext.java 
 +
|-------Factory.java 
 +
|-------Stateful.java       
 +
|------tests
 +
|-------StatelessTest.java
 +
|-------CreateResource.java     
 +
|-------StatefulTest.java       
 +
|
 +
|--schema
 +
|---Stateless.wsdl
 +
|---Factory.wsdl
 +
|---Stateful.wsdl              [changed]
 +
|
 +
|
 +
|--build.xml               
 +
|
 +
|-Dependencies
 
</pre>
 
</pre>
Save the file '''StatefulTest.java''' under the src/org/acme/test folder under the service location.
 
  
== Final structure of the service location ==
+
=== More Testing ===
At the end of this section, the service location tree should look as follows:  
+
 
 +
After a new build and deployment of the service, we are ready to test the novelties. Here, we pretend to be generic clients and check whether the RPs of our WS-Resources can be accessed using the generic WSRF interfaces. In practice, we may well never need to act as generic clients. And yet some of the services we might interact with may take advantage of the standard interfaces and offer no other means to explore the state of their WS-Resources; in other words, they may treat specific and generic clients in the same way (no methods like <code>getName()</code> on their port-type interfaces!). In any case, we need to test that we are truly compliant with WSRF and gCube requirements.
 +
 
 +
There are two ways to approach testing here. We can go down the usual programmatic route, or else use some pre-defined clients that ship with gCore.
 +
In the first case, our testing client for the <code>Stateful</code> port-type may contain code such as the following:
 +
 
 +
<pre>
 +
StatefulPortType statefulPT = new StatefulServiceAddressingLocator().getStatefulPortTypePort(..some-endpoint-reference..);
 +
logger.info(statefulPT.getResourceProperty(new QName("http://acme.org/sample","Name")).get_any()[0]);
 +
</pre>
 +
 
 +
where <code>getResourceProperty</code> is just one of the standard operations defined by the WSDLs interface that our <code>Stateful</code> port-type 'inherits' by the gCubeProvider.
 +
The operation returns the RP named in input, although it is rather unimportant here to get into the details of its signature. For testing purposes, it suffices to confirm that we get back something logged as:
 +
 
 +
<pre>
 +
2009-04-22 16:16:17,816 INFO  tests.StatefulTest [main,info:78] StatefulTest:
 +
              <ns1:Name xmlns:ns1="http://acme.org/sample">...the current value of the RP...</ns1:Name>
 +
</pre>
 +
 
 +
The second and simplest way to test our port-type's compliance with WSRF is through scripts that launch pre-defined WSRF clients, one for each operation of the WSRF interfaces that the gCube Provider, and now our <code>Stateful</code> port-type, implements. In what follows, for example, we show the invocation of a script that queries all the RPs of a WS-Resource whose endpoint is stored in a given file (incidentally, the same file produced by our test client <code>CreateResource</code>). Do notice that our <code>Name</code> RP co-exists with all the gCube RPs added by gCF.
 +
 
 +
<pre>
 +
...> wsrf-query -e /tmp/stateful.epr
 +
 
 +
<ns1:statefulRPD xmlns:ns0="http://acme.org/sample" xmlns:ns1="http://gcube-system.org/namespaces/common/core/porttypes/GCUBEProvider"
 +
    xmlns:ns2="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd"
 +
    xmlns:ns3="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.xsd"
 +
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 +
 
 +
<ns1:RI>6feeeed0-2f37-11de-92c8-a326c1a4b1b6</ns1:RI>
 +
 
 +
<ns1:ServiceID>d202da90-2f38-11de-9805-a99465896164</ns1:ServiceID>
 +
 
 +
<ns1:ServiceName>SampleService</ns1:ServiceName>
 +
 
 +
<ns1:GHN>12994620-2e80-11de-b393-a6908a641d8e</ns1:GHN>
 +
 
 +
<ns1:ServiceClass>Samples</ns1:ServiceClass>
 +
 
 +
<ns1:Scope>/gcube/devsec</ns1:Scope>
 +
 
 +
<ns1:CurrentTime xmlns:ns1="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd">
 +
                                                                            2009-04-22T15:17:20.426Z</ns1:CurrentTime>
 +
 
 +
<ns1:TerminationTime xsi:nil="true" xmlns:ns1="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd"/>
 +
 
 +
<ns1:FixedTopicSet
 +
      xmlns:ns1="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-1.xsd">false</ns1:FixedTopicSet>
 +
 
 +
<ns1:TopicExpressionDialects xmlns:ns1="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.xsd">
 +
                                        http://docs.oasis-open.org/wsn/2004/06/TopicExpression/Simple</ns1:TopicExpressionDialects>
 +
 
 +
<ns1:Name xmlns:ns1="http://acme.org/sample">...the current value of the RP...</ns1:Name>
 +
 
 +
</ns1:statefulRPD>
 +
 
 +
</pre>
 +
 
 +
[[Image:blue.jpg]] You can find more WSRF scripts junder <code>$GLOBUS_LOCATION/bin</code>. You can also find more information about the operations of RP-oriented WSRF interfaces in the definition of the [http://www.ibm.com/developerworks/library/specification/ws-resource/ws-resourceproperties.pdf corresponding standard].
 +
 
 +
== The Lifetime of WS-Resources ==
 +
 
 +
Besides operations that deal directly with RPs, WSRF defines also an [http://docs.oasis-open.org/wsrf/wsrf-ws_resource_lifetime-1.2-spec-os.pdf interface] for the destruction of entire WS-Resources. This means that an important aspect of their lifetime management can now be handled remotely. Needless to say, gCube requires compliance with this interface too. The gCube provider implements the interface, however, so we have already done everything we need to do to comply with this further requirement.
 +
 
 +
There are two forms of destruction contemplated by WSRF, immediate and scheduled. To destroy a WS-Resource immediately it suffices to invoke a <code>destroy()</code> operation on it, as shown in the following example:
 +
 
 +
<pre>
 +
StatefulPortType statefulPT = new StatefulServiceAddressingLocator().getStatefulPortTypePort(epr);
 +
statefulPT = GCUBERemotePortTypeContext.getProxy(statefulPT, ...some scope of the WS-Resource..."));
 +
statefulPT.destroy(new Destroy());
 +
</pre>
 +
 
 +
Two things to notice here:
 +
 
 +
[[Image:red.jpg]] The destruction of a WS-Resource '''must''' be relative to a scope, as the WS-Resource might operate in multiple scopes. We must then set a scope on the proxied stub of the <code>Stateful</code> port-type prior to invoking the <code>destroy()</code> operation on it. Furthermore, the scope must be compatible with those in which the WS-Resource operates, or the invocation will result in a fault.
 +
 
 +
[[Image:blue.jpg]] At the server's side, the gCube Provider executes the operation by invoking a method <code>remove()</code> that our <code>Home</code> inherits from <code>GCUBEWSHome</code>. You can also invoke this method from within the service implementation if, for some conditions that are specific to the semantics of your WS-Resources, you can conclude that it's time to remove any given one from one or more of its scopes.
 +
 
 +
The second form of destruction occurs implicitly when WS-Resources pass their 'expiry date'. By default, the are all born without a time to live, but you can rectify things if the semantics of your WS-Resources requires it. To do this, just define a <code>lifeTime</code> environment in the JNDI configuration of your stateful port-type. In the following example, WS-Resources are granted a time to live of 30 seconds:
 +
 
 +
<pre>
 +
<environment name="lifeTime" value="30" type="java.lang.Integer" override="false"/>
 +
</pre>
 +
 
 +
When a WS-Resource is created, it first checks for the existence of a <code>lifeTime</code> environment in the configuration of the associated stateful port-type. If it finds it, it adds the lifetime value to the current time and stores the resulting ''termination time'' in a dedicated gCube RP, called unsurprisingly <code>TerminationTime</code>. This is behaviour that all your WS-Resources inherit from gCF's <code>GCUBEWSResource</code> class.
 +
 
 +
The termination time of a WS-Resource can then be acted upon in a number of ways. First and foremost, the resource home will periodically check the termination time of all the WS-Resources that it manages. It will then remove those that it finds expired, and it will do so from ''all'' the scopes in which they currently operate. Again, this is behaviour that all resource homes inherit from gCF's <code>GCUBEWSHome</code> class.
 +
 
 +
Besides the resource home, you can also act upon the termination time of WS-Resources, typically to give them a new lease of life. Interestingly, remote clients can do the same! The same WSRF interface that offers the <code>destroy</code> operation, offers also operations to extend the lifetime of WS-Resources. As usual, the gCube provider implements these operations and so do all the stateful port-types that extend the gCube Provider. Again, the details of these operations are beyond the scope of this Primer, so check out the [http://docs.oasis-open.org/wsrf/wsrf-ws_resource_lifetime-1.2-spec-os.pdf standard] for precise instructions.
 +
 
 +
== Persistence for WS-Resources ==
 +
 
 +
How does a resource home keep track of the stateful resources it manages? Where does it retrieve them from when we ask for those required to satisfy client requests?
 +
 
 +
By default, all resource homes keep their stateful resources in good old memory. Our <code>Home</code> was no exception in this respect.
 +
This ''transient'' mode of operation guarantees the fastest access to state, of course, but it does not take long to see shortcomings too:
 +
 
 +
* the resources vanish as soon as the gHN experiences some downtime. This will bite at development time, when you are likely to start and stop the gHN at the rhythm of changes to the service implementation. It will then bite ''much'' more in production, where gHNs may be brought temporarily down for maintenance (e.g. upgrades), and for all sorts of failures of course. If our stateful resources are transient, all these scenarios will translate into the need for re-staging your service. For serious gCube services, this is a lenghty, error-prone, and costly process.
 +
 
 +
* the resources may ultimately clog your memory, depending on their size and number, and the overall performance of your service may degrade accordingly.
 +
 
 +
For these reasons, it is important that resource homes can operate in a ''persistent'' mode, i.e. rely on some longer-term storage medium to store their stateful resources.
 +
Implementing persistence is not exactly a trivial task, because it impacts on virtually all other faces of state management. Fortunately, gCF hides most of the complexity from you.
 +
It can do it all by itself, however, so you need to help in the way of configuration and, yes, some implementation too. We consider these requirements in the process of adding persistence for the WS-Resources of the <code>Sample</code> service.
 +
 
 +
=== Persistence Delegates ===
 +
 
 +
Resource homes will rely on a separate object to store and load their stateful resources from long-term storage. This object is dedicated to persistence and is called the ''persistence delegate''. gCF defines a whole framework for the design of persistence delegates, where different types of delegates can target different types of persistent stores. As long as you are happy with the file system as the persistence store, then you do not have to design a persistence delegate from scratch but can derive it from gCF's <code>GCUBEWSFilePersistenceDelegate</code>. To do this, you must do the following:
 +
 
 +
[[Image:red.jpg]] you must override the <code>onStore()</code> method to explicitly store in a <code>ObjectOutputStream</code> the properties of a stateful resource that you care to persist.
 +
 
 +
[[Image:red.jpg]] you must override the <code>onLoad()</code> method to explicitly restore from a <code>ObjectInputStream</code> the properties of a stateful resource, in the exact order in which you persisted them in <code>onStore()</code> of course.
 +
 
 +
The following example illustrates the point for our <code>Resource</code>s, where both client name and visit count need to be persisted:
 +
 
 +
<pre>
 +
package org.acme.sample.stateful;
 +
import...
 +
 
 +
public class ResourcePersistenceDelegate extends GCUBEWSFilePersistenceDelegate<Resource> {
 +
 
 +
  protected void onLoad(Resource resource,ObjectInputStream ois) throws Exception {
 +
 
 +
  /** {@inheritDoc} */
 +
  super.onLoad(resource,ois);
 +
  resource.setName((String)ois.readObject());
 +
  int visits = (Integer)ois.readObject();
 +
  for (int i =0; i<visits; i++) resource.addVisit();
 +
 
 +
  }
 +
 +
  /** {@inheritDoc} */
 +
  protected void onStore(Resource resource,ObjectOutputStream oos) throws Exception {
 +
 +
  super.onStore(resource,oos);
 +
  oos.writeObject(resource.getName());
 +
  oos.writeObject(resource.getVisits());
 +
 +
  }
 +
}
 +
</pre>
 +
 
 +
Notice the following:
 +
 
 +
[[Image:blue.jpg]] <code>GCUBEWSFilePersistenceDelegate</code> is parametric in the type of stateful resource that it handles. Accordingly, <code>ResourcePersistenceDelegate</code> instantiates the type parameter to <code>Resource</code>:
 +
 
 +
<pre>
 +
public class ResourcePersistenceDelegate extends GCUBEWSFilePersistenceDelegate<Resource> {...}
 +
</pre>
 +
 
 +
[[Image:red.jpg]] <code>onStore()</code> and <code>onLoad()</code> '''must''' delegate to the methods they override in <code>GCUBEWSFilePersistenceDelegate</code>, so as to ensure that the gCube RPs are stored/loaded before more specific properties of the stateful resource. You can do this at the beginning or at the end of the methods, as long as you are consistent across the two methods. The usual convention is to do it straight away.
 +
 
 +
[[Image:blue.jpg]] <code>onStore()</code> and <code>onLoad()</code> invoke the accessors of the <code>Resource</code> they are storing and loading. As we have seen [[#Modelling State as Resource Properties|earlier]], these accessors may well encapsulate access to RPs. You can here happily abstract over the way in which stateful resources store their state in memory.
 +
 
 +
[[Image:blue.jpg]] Synchronisation issues are taken care of by gCF, in the context in which callbacks to your methods are issued. So no need to worry about that.
 +
 
 +
[[Image:blue.jpg]] Where are our <code>Resource</code>s actually stored on the file system? Stateful resources are always stored under your home directory, in a file named with the plain identifier of the resource (not the key), with a <code>wsresource</code> suffix, and in a directory with the following path:
 +
 
 +
<pre>
 +
<homedir>/.gcore/persisted/<hostname>-<port>/<servicename>/<resourceclassname>
 +
</pre>
 +
 
 +
In our example, a <code>Resource</code> with identifier <code>johndoe</code> may be stored in <code>~/.gcore/persisted/myhost.org-8080/SampleService/Resource/johndoe.wsresource</code>.
 +
 
 +
 
 +
The next requirement is to let the resource home know about the existence of <code>ResourcePersistenceDelegate</code>. We do this by adding a new <code>parameter</code> element to the JNDI configuration of our <code>Home</code>, as follows:
 +
 
 +
<pre>
 +
<resource name="home" type="org.acme.sample.stateful.Home">
 +
  <resourceParams>                
 +
    <parameter>
 +
      <name>factory</name>
 +
      <value>org.globus.wsrf.jndi.BeanFactory</value>
 +
    </parameter>
 +
    <parameter>
 +
      <name>resourceClass</name>
 +
      <value>org.acme.sample.stateful.Resource</value>
 +
    </parameter>
 +
    <parameter>
 +
      <name>persistenceDelegateClass</name>
 +
      <value>org.acme.sample.stateful.ResourcePersistenceDelegate</value>
 +
    </parameter>
 +
</resource>
 +
</pre>
 +
 
 +
just notice that:
 +
 
 +
[[Image:red.jpg]] we '''must''' name the parameter <code>persistenceDelegateClass</code> and specify the fully qualified name of the <code>ResourcePersistenceDelegate</code> class as its value.
 +
 
 +
 
 +
There is of course one question that still needs to be answered. Who calls the <code>onStore()</code> and <code>onLoad()</code> methods of our <code>ResourcePersistenceDelegate</code>? And when does this happen?
 +
 
 +
[[Image:blue.jpg]] <code>onLoad()</code> is invoked transparently by our <code>Home</code> whenever a <code>Resource</code> is needed and yet cannot be found in memory. Before giving up and concluding that the <code>Resource</code> does not exist (or never did), the resource home will check for its existence on the persistence store targeted by our <code>ResourcePersistenceDelegate</code>. 
 +
 
 +
[[Image:blue.jpg]] When is a resource not found in memory then? The first and foremost case is when the gHN has been restarted of course. We call this first-time load a ''hard load''. All your stateful resources will be automatically reloaded at gHN startup. The other important case will become clearer in a [[#Persistence Modes|moment]], and it won't assume a gHN restart. We call these ''soft loads''.
 +
 
 +
[[Image:red.jpg]] <code>onStore()</code> is invoked by a related method <code>store()</code> that our <code>Resource</code> class inherits from <code>GCUBEWSResource</code>. However, ''we are entirely responsible'' to invoke <code>store()</code> on our <code>Resource</code>s. Why is down to us? Well, because no one else knows when the state of our <code>Resource</code>s changes and thus when they become stale in the persistent store. Typically, we will want to do this right at the end of any process that causes or is very likely to cause changes. In our example, we will want to store a <code>Resource</code> after it has been created and right after each client visit. We will then need to change the implementation of the <code>logon()</code> method of our <code>Factory</code> port-type implementation as follows:
 +
 
 +
<pre>
 +
package org.acme.sample.stateful;
 +
import...
 +
 
 +
public class Factory extends GCUBEPortType {   
 +
 
 +
    ...       
 +
    public EndpointReferenceType logon(String name) throws GCUBEFault {
 +
 +
          try {
 +
                ...
 +
                GCUBEWSResource resource = home.create(key,name);
 +
                resource.store();
 +
                return resource.getEPR();
 +
          } catch (Exception e) {
 +
                ...
 +
          }
 +
        }
 +
}
 +
 
 +
</pre>
 +
 
 +
We will also need to revise the implementation of the method <code>aboutSF()</code> in our <code>Stateful</code> port-type implementation, as follows:
 +
 
 +
<pre>
 +
package org.acme.sample.stateful;
 +
import...
 +
 
 +
public class Stateful extends GCUBEPortType {
 +
 
 +
    ...
 +
 
 +
  public String aboutSF(VOID voidType) throws GCUBEFault {
 +
 
 +
          ...
 +
 
 +
          try {
 +
            Resource resource = this.getResource()
 +
            String name = resource.getName();
 +
            ...
 +
            resource.addVisit();
 +
            resource.store();
 +
            return output.toString();
 +
 
 +
          }
 +
          catch (GCUBEException e) {...}
 +
          catch (Exception e) {...}
 +
    }
 +
 
 +
    ...
 +
 
 +
}
 +
</pre>
 +
 
 +
A word of caution now:
 +
 
 +
[[Image:red.jpg]] You may be tempted to add an invocation of <code>store()</code> inside the <code>addVisit()</code> of the <code>Resource</code> class. More generally, you may consider to concentrate storing operations as close as possible to the point of change, in the accessors of the mutable properties of your stateful resources. '''Don't do it'''. This is a bad idea, for a number of reasons. First, it is potentially inefficient, as you may end up storing multiple times in a process that makes many fine-grained changes simultaneously. Second, the accessors are invoked during the loading of your stateful resources, as you have seen in the implementation of <code>onLoad()</code>. This means that your resources would be stored ''as'' they are been loaded, which may be disastrous and is surely inefficient. Store instead as late possible, just before closing the process (typically, but not always, before returning to the client).
 +
 
 +
=== Persistence Modes ===
 +
 
 +
Given the pros and cons of memory-based storage, the interpretation of the persistence requirement may actually vary:
 +
 
 +
* we may want to guard our services from gHN downtime ''and'' retain maximum speed of access. In this case, we would like to rely on a persistent store to ''backup'' our resources, while keeping them in memory all the time. Perhaps we do not have huge resources, perhaps they are short-lived, perhaps we have arranged for deployment on memory-rich machines. What we really cannot loose is performance. In all these cases, we say that the requirement is for a resource home that operates in a ''hard persistence'' mode.
 +
 
 +
* alternatively, we may be happier with a more balanced compromise between memory consumption and access speed. We may accept that, while all stateful resources are still persistently stored to cope with gHN downtime, any of them may exist ''only'' in the persistent store. In particular, we may accept that a stateful resource remains in memory for a time that is proportional to the amount of available memory. The resource would then be transferred back to memory only and precisely when it is needed by clients, clearly with some delay.  In this case, we say that the requirement is for a resource home that operates in a ''soft persistence'' mode.
 +
 
 +
* as a more refined compromise between memory consumption and access speed, we may wish that stateful resources remain in memory for a time that is proportional to the amount of available memory ''as well as'' the frequency with which they are used. In other words, we may require that stateful resources that are consumed often stay in memory for longer, and thus are more likely to be accessed quickly. In this case, we say that the requirement is for a resource home that operates in a ''cached persistence'' mode because the resource home keeps a cache of frequently used resources that forces them to stay in memory.
 +
 
 +
Resource homes in gCF can support all three modes of persistence. By default, configuring a persistence delegate for the resource home will make it operate in soft persistence mode.
 +
The other two modes can be easily configured as follows:
 +
 
 +
[[Image:blue.jpg]] To configure a cached persistence mode, add a <code>cacheTimeout</code> parameter to the JNDI resource that configures the resource home, specifying the time in milliseconds after which a resource which has not been needed is kicked out of the cache:
 +
 
 +
<pre>
 +
<resource name="home" type="org.acme.sample.stateful.Home">
 +
  <resourceParams>                
 +
    <parameter>
 +
      <name>factory</name>
 +
      <value>org.globus.wsrf.jndi.BeanFactory</value>
 +
    </parameter>
 +
    <parameter>
 +
      <name>resourceClass</name>
 +
      <value>org.acme.sample.stateful.Resource</value>
 +
    </parameter>
 +
    <parameter>
 +
      <name>persistenceDelegateClass</name>
 +
      <value>org.acme.sample.stateful.ResourcePersistenceDelegate</value>
 +
    </parameter>
 +
    <parameter>
 +
      <name>cacheTimeout</name>
 +
      <value>60000</value>
 +
    </parameter>
 +
</resource>
 +
</pre>
 +
 
 +
[[Image:blue.jpg]] To configure a ''hard persistence'' mode, simply specify a value of 0 for the <code>cacheTimeout</code> parameter just discussed above.
 +
 
 +
 
 +
After the changes induced by persistence, the snapshot of your service implementation ought to be as follows:
 +
 
 
<pre>
 
<pre>
 
|-SampleService
 
|-SampleService
 
|--etc
 
|--etc
 
|---profile.xml
 
|---profile.xml
|---deploy-jndi-config.xml
+
|---deploy-jndi-config.xml               [changed]
|---deploy-server.wsdd
+
|---deploy-server.wsdd        
 
|---build.properties
 
|---build.properties
 
|
 
|
Line 839: Line 1,281:
 
|-------Stateless.java
 
|-------Stateless.java
 
|------stateful
 
|------stateful
|-------StatefulContext.java
+
|-------Resource.java          
|-------Resource.java
+
|-------Home.java            
|-------Home.java
+
|-------StatefulContext.java  
|-------Service.java
+
|-------Factory.java  
|-------Factory.java
+
|-------Stateful.java
 +
|-------ResourcePersistentDelegate.java   [new]       
 
|------tests
 
|------tests
 
|-------StatelessTest.java
 
|-------StatelessTest.java
|-------StatefulTest.java
+
|-------CreateResource.java     
 +
|-------StatefulTest.java        
 
|
 
|
 
|--schema
 
|--schema
Line 854: Line 1,298:
 
|
 
|
 
|
 
|
|--build.xml
+
|--build.xml              
 
|
 
|
 
|-Dependencies
 
|-Dependencies
|--SampleService
+
</pre>
  
 +
== Publishing WS-Resources ==
 +
 +
The first and foremost motivations for adopting WSRF standards in gCube concerns the publication of WS-Resources. Publishing a WS-Resource means to publish the values of its RPs with the gCube Information Services. The reason for doing so is to allow clients to dynamically discover the WS-Resource by querying the Information Services for the existence or current value of RPs. The Information Services themselves are thus a distinguished example of the class of generic clients for which we have bothered to adopt WSRF standards in the first place. We deal with WS-Resource publication here and discuss discovery later and in a much broader context.
 +
 +
gCF takes care of WS-Resource publication. All home resources, in particular, inherit from the <code>GCUBEWSHome</code> the ability to publish a WS-Resource right after its initialisation. In response, the Information Services will come back at regular intervals and poll the WS-Resource via its WSRF interfaces to check that it is still alive and to get the latest value of its RPs. If the WS-Resource is not available after a number of attempts, its publication expires. When persistence is enabled, however, the unavailability of a WS-Resource may not be due to its actual destruction, but simply a gHN that is temporarily down. For this reason, the resource home will republish at start-up all the WS-Resources that it manages persistently. Furthermore, the resource home will take the initiative and signal the destruction of a WS-Resource to the Information Services as soon as this happens.
 +
 +
There is still something that is left to us, however. We need to tell the resource home:
 +
 +
* ''what'' RPs we wish to actually publish (typically all those we cared to define);
 +
* ''how often'' we wish our RPs to be polled by the Information Services (depending on the frequency with which we expect our RPs to change).
 +
 +
We do both things via configuration. First we fill a dedicated 'registration' template and store it in a file in the <etc> folder of our service implementation, say <code>registration.xml</code>. For the WS-Resources that encapsulate our <code>Stateful</code> port-type, for example, the registration file may look as follows:
 +
 +
<pre>
 +
<ServiceGroupRegistrationParameters
 +
    xmlns:sgc="http://mds.globus.org/servicegroup/client"
 +
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
 +
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 +
    xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
 +
    xmlns:agg="http://mds.globus.org/aggregator/types"
 +
    xmlns="http://mds.globus.org/servicegroup/client">
 +
   
 +
    <RefreshIntervalSecs>60</RefreshIntervalSecs>
 +
   
 +
    <Content xsi:type="agg:AggregatorContent">
 +
       
 +
        <agg:AggregatorConfig>
 +
            <agg:GetMultipleResourcePropertiesPollType xmlns:sample="http://acme.org/sample">
 +
                <agg:PollIntervalMillis>60000</agg:PollIntervalMillis>               
 +
                <agg:ResourcePropertyNames>sample:Name</agg:ResourcePropertyNames>
 +
                <agg:ResourcePropertyNames>sample:Visits</agg:ResourcePropertyNames>
 +
            </agg:GetMultipleResourcePropertiesPollType>
 +
        </agg:AggregatorConfig>
 +
       
 +
        <agg:AggregatorData/>
 +
       
 +
    </Content>
 +
</pre>
 +
 +
This is mostly a boiler plate format defined by the technologies that underlie gCube publication mechanisms. We just notice the following:
 +
 +
[[Image:red.jpg]] we '''must''' specify the polling interval as the value in milliseconds of the <code>PollIntervalMillis</code> element. Here we opted for a minute.
 +
 +
[[Image:red.jpg]] we '''must''' explicitly list the RPs that wish to have published as the values of the <code>ResourcePropertyNames</code>. Do note that the values include the namespace of the <code>Stateful</code> port-type, and that this namespace is bound to the local default namespace.
 +
 +
The second piece of configuration we need is in the JNDI configuration of the <code>Stateful</code> port-type. We have seen an example of JNDI <code>resource</code> [[#Home Sweet Home|before]], here is another one:
 +
 +
<pre>
 +
<resource name="publicationProfile" type="org.gcube.common.core.state.GCUBEPublicationProfile">
 +
  <resourceParams>
 +
    <parameter>
 +
      <name>factory</name>
 +
      <value>org.globus.wsrf.jndi.BeanFactory</value>
 +
    </parameter>
 +
    <parameter>
 +
      <name>fileName</name>
 +
      <value>registration.xml</value>
 +
    </parameter>
 +
  </resourceParams>
 +
</resource>
 +
</pre>
 +
 +
[[Image:red.jpg]] we '''must''' name the configuration <code>publicationProfile</code> and we '''must''' give it a type of <code>org.gcube.common.core.state.GCUBEPublicationProfile</code>. This is the class of the object that will model this piece of configuration at runtime.
 +
 +
[[Image:red.jpg]] we '''must''' specify a <code>param</code> called <code>factory</code> that specifies the class responsible for creating the <code>GCUBEPublicationProfile</code> object that models this piece of configuration at runtime. As usual, this is not expected to change from service to service.
 +
 +
[[Image:red.jpg]] finally, we '''must''' specify a <code>param</code> called <code>fileName</code> that specifies the name of the registration file.
 +
 +
Finally, notice the following:
 +
 +
[[Image:blue.jpg]] WS-Resource publication is ''not'' mandatory. If WS-Resources are meant to be generated and consumed by a single client and for the purposed of some fairly short-lived task, then sharing them within the infrastructure may simply not be a requirement. In these cases, simply avoid any configuration (the <code>publicationProfile</code> and, subsequently, the registration file) and gCF will understand your intentions. Of course you can also temporarily disable publication by keeping all your configuration but commenting out the <code>publicationProfile</code>.
 +
 +
With publication configured, your service implementation ought to look as follows:
 +
 +
<pre>
 +
|-SampleService
 +
|--etc
 +
|---profile.xml
 +
|---deploy-jndi-config.xml      [changed]
 +
|---deploy-server.wsdd         
 +
|---build.properties
 +
|---registration.xml            [new]
 +
|
 +
|--src
 +
|---org
 +
|----acme
 +
|-----sample
 +
|------ServiceContext.java
 +
|------stateless
 +
|-------Stateless.java
 +
|------stateful
 +
|-------Resource.java           
 +
|-------Home.java             
 +
|-------StatefulContext.java 
 +
|-------Factory.java 
 +
|-------Stateful.java       
 +
|-------ResourcePersistenceDelegate.java
 +
|------tests
 +
|-------StatelessTest.java
 +
|-------CreateResource.java     
 +
|-------StatefulTest.java       
 +
|
 +
|--schema
 +
|---Stateless.wsdl
 +
|---Factory.wsdl
 +
|---Stateful.wsdl
 +
|
 +
|
 +
|--build.xml               
 +
|
 +
|-Dependencies
 
</pre>
 
</pre>

Latest revision as of 21:21, 27 April 2009

Adding State

The service we defined in the first part of this tutorial was stateless: its responses to client requests depended solely on the requests. This is all well, but in practice services may need to maintain some form of state that pre-exists, persists, and - most importantly - changes as a result of client invocations. Many gCube services are indeed stateful.

In this second part of the tutorial we will learn how to add state to our SampleService. In the spirit of a dumb service, the idea is to keep track of the number of visits of any client that has previously 'logged on' with the service. The identity of logged clients and the number of their subsequent visits will then form the state of our augmented SampleService. In particular, we will add two new port-types:

  • a Factory port-type that allows clients to log on and thus creates state within the service . In particular, we plan a single operation logon() for this port-type.
  • a Stateful port-type that allows clients to visit the service and thus consults and updates the state of the service. In particular, we plan a single operation aboutSF() for this port-type which will behave similarly to the operation about() in the Stateless port-type whilst recognising visits from clients that have previously logged on.

(It will be clear soon why we prefer two port-types with a single operation each rather than a single port-type with two operations.)

For both port-types, we need to repeat the steps already shown for the Stateless port-type: add the port-types descriptions to the service profile, define the WSDL interface of each port-type, and provide the Java implementation of the two port-types. Additional steps will then be required for state management.

WS-Resources and The Implied Resource Pattern

The overall state of our SampleService will be comprised of many 'pieces', one per client that logs on and then visits. We refer to these pieces somewhat more technically as stateful resources.

How should we go about creating and accessing stateful resources? One approach could be as follows:

  • take some credentials from clients when they invoke the logon() operation on the Factory port-type. A simple name would surely do for our purposes.
  • create stateful resources as Java objects that contain the count of client visits, and identify such resources with the name of the associated client.
  • when a client comes back to visit the service and invokes the aboutSF() operation on the Stateful port-type, ask it to provide his name so that we can identify the corresponding stateful resource and update the count of its visits.

In this approach, clients need to explicitly identify the state they wish to target. Unfortunately, identifiers are service-specific: here we need a name, elsewhere we may need something different. The use that we make of identifiers is also specific: here we hinted at one parameter in the aboutSF() operation, elsewhere could be two or three parameters in one or more operations. This variability makes it impossible to build generic clients that can transparently access state across different services.

You may find this observation rather strange: what could a client do that does not require specific knowledge of the target service? Well, it turns out that if we find a general way to access stateful resources, then we can build enough conventions on how we describe them to enable a range of very useful and yet generic clients. For example, we can define clients that can query and change the stateful resources of any service that complies with the conventions. We can define clients that can uniformly destroy stateful resources, either immediately or based on some renewable expiry time. We can even define clients that allow others to subscribe for changes to the stateful resources. These are all key features in gCube, and we shall be directly concerned with some of them in this very Primer.

What we need to promote generic clients is then:

  • a uniform pattern to identify and access stateful resources which does not change from service to service.
  • a standard that codifies this pattern and builds useful conventions on top of it.

The Web Services Resource Framework (WSRF) is precisely one such standard, and gCube adopts it. When it comes to identifying and accessing stateful resources, WSRF says: forget passing identifiers explicitly in operations such as aboutSF(), which vary from port-type to port-type and from service to service; let us pass them instead implicitly, as part of the address of the port-type that exposes those operations. An invocation of aboutSF() would thus be addressed to the Stateful port-type, but the address would include also the identifier of the stateful resource that is the target of the request. The port-type implementation would then extract such identifier and use it to locally access the stateful resource. No need to explicitly parameterise aboutSF() with it. This is the access pattern that WSRF calls the implied resource pattern.

If you think about it, this annotated address - or more appropriately, this qualified endpoint reference - identifies a pair (port-type,stateful resource). WSRF calls this pair a WS-Resource. We can then think of the qualified endpoint reference as the endpoint reference of the WS-resource itself. Similarly, we can think of the operations available at that endpoint as the operations of the WS-Resource. In this sense, the port-type becomes the uniform interface of potentially many WS-Resources.

With the implied resource pattern and the corresponding terminology, we can now describe our new port-types as follows:

  • a Factory port-type that allows users to create WS-Resources. In particular, we plan a single operation for this port-type, logon(), which takes the name of the client and returns the endpoint reference of a WS-Resource 'dedicated' to the client.
  • a Stateful port-type that defines the interface of the WS-Resources. We plan a single operation for this port-type too, aboutSF(), which takes nothing but it is invoked with the endpoint reference of a WS-Resource.

A client may then invoke logon() on the Factory port-type and then use the resulting WS-Resource endpoint reference to invoke aboutSF() on it. As we shall see, a client may also discover and use the endpoint of a WS-Resource of interest without having previously created it. A couple of things to notice:

Blue.jpg The client does not need to know about the stateful resource identifier embedded in the endpoint reference returned by the Factory, or otherwise 'found'. The endpoint reference identifies a WS-Resource but the inner structure of this WS-Resource as a pair (port-type,stateful resource) remains opaque to the client.

Blue.jpg Placing logon() and aboutSF() in different port-types makes sense. The first creates WS-Resources while the second defines their operations. Informally, we say that the Factory is, like Stateless, a stateless port-type because it does not serve as the interface of WS-Resources. For the opposite reason, we say that Stateful is a stateful port-type.

Extending the Profile

We now enrich the service profile to reflect the existence of two new port-types.

<Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
	<ID></ID>
	<Type>Service</Type>
	<Profile>
		<Description>A very simple gCube Service</Description>
		<Class>Samples</Class>
		<Name>SampleService</Name>
		<Packages>
			<Main>
				<Description>Describes port-types</Description>
				<Name>Main</Name>
				<Dependencies>
					<Dependency>
						<Service>
							<Class>Samples</Class>
							<Name>SampleService</Name>
						</Service>
						<Package>Stubs</Package>
						<Version>1.0</Version>
						<Scope level="GHN"/>
						<Optional>false</Optional>
					</Dependency>
				</Dependencies>
				<GARArchive>org.acme.sample.gar</GARArchive>
				<PortType>
					<Name>acme/sample/stateless</Name>
					<WSDL/>
				</PortType>
				<PortType>
					<Name>acme/sample/stateful</Name>
					<WSDL/>
				</PortType>
				<PortType>
					<Name>acme/sample/factory</Name>
					<WSDL/>
				</PortType>
			</Main>
			<Software>
				<Description>Describes port-type stubs</Description>
				<Name>Stubs</Name>
				<Files><File>org.acme.sample.stubs.jar</File></Files>
			</Software>
		</Packages>
	</Profile>
</Resource>

Not much to comment about here, we just added two new PortType elements in the description of the Main package.

More Port-Type Interfaces

The WSDL that describes the interface of the Factory port-type holds few surprises at this stage:

<definitions name="Factory"
    targetNamespace="http://acme.org/sample"
    xmlns:tns="http://acme.org/sample"
  	xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults"
    xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" >
    
    <import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
   
 	<types>
	<xsd:schema targetNamespace="http://acme.org/sample">

		<xsd:import namespace="http://schemas.xmlsoap.org/ws/2004/03/addressing" 
                                                    schemaLocation="../ws/addressing/WS-Addressing.xsd" />

  		<xsd:element name="logon" type="xsd:string" />		
		<xsd:element name="logonResponse" type="wsa:EndpointReferenceType"/>

	</xsd:schema>
	</types>

	<message name="logonInputMessage">
		<part name="request" element="tns:logon"/>
	</message>
	<message name="logonOutputMessage">
		<part name="response" element="tns:logonResponse"/>
	</message>

	<portType name="FactoryPortType">
	
		<operation name="logon">
			<input message="tns:logonInputMessage"/>
			<output message="tns:logonOutputMessage"/>
			<fault name="fault" message="corefaults:GCUBEFaultMessage"></fault>
			<fault name="fault" message="corefaults:GCUBEUnrecoverableFaultMessage"></fault>
		</operation>
	
	</portType>

</definitions>

Just notice the following:

Blue.jpg We use the EndpointReferenceType type to describe endpoint references of WS-Resources. This type is defined by the WS-Addressing standard to describe endpoint references (qualified or not), and we need to import its definition from a file that ships with gCore (WS-Addressing.xsd); note in particular the import directive and how it is resolved relatively to the location of the interface after service deployment (remember?).

Blue.jpg At this stage, there is little point in looking into the schema definition of this type. gCore will offer Java objects to model and serialise qualified endpoint references, in accordance with the schema definition. We just notice here that the stateful resource identifier that qualifies an endpoint reference of a WS-Resource is an XML document with an arbitrary payload. In jargon, we speak of this element as the key of the stateful resource. In gCF, in particular, the payload of resource keys is always a plain string, such as the 'name' of clients we expect to use in the design of our Sample service.


The WSDL for the Stateful port-type is also straightforward, at least for the time being.

<definitions name="Stateful"
    targetNamespace="http://acme.org/sample"
    xmlns:tns="http://acme.org/sample"
  	xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:coretypes="http://gcube-system.org/namespaces/common/core/types"
    xmlns:corefaults="http://gcube-system.org/namespaces/common/core/faults">
    
    <import namespace="http://gcube-system.org/namespaces/common/core/faults" location="../gcube/common/core/faults/GCUBEFaults.wsdl"/>
    
 	<types>
	<xsd:schema targetNamespace="http://acme.org/sample">    	
	    
             <xsd:import namespace="http://gcube-system.org/namespaces/common/core/types" 
                                         schemaLocation="../gcube/common/core/types/GCUBETypes.xsd"/>
	    	
  	     <xsd:element name="aboutSF" type="coretypes:VOID" />
	     <xsd:element name="aboutSFResponse" type="xsd:string" />				        
                    
	</xsd:schema>
	</types>

	<message name="aboutSFInputMessage">
		<part name="request" element="tns:aboutSF"/>
	</message>
	<message name="aboutSFOutputMessage">
		<part name="response" element="tns:aboutSFResponse"/>
	</message>

	<portType name="StatefulPortType">        
	
		<operation name="aboutSF">
			<input message="tns:aboutSFInputMessage"/>
			<output message="tns:aboutSFOutputMessage"/>
			<fault name="fault" message="corefaults:GCUBEFaultMessage"></fault>
		</operation>	
	
	</portType>

</definitions>

Just notice the following:

Blue.jpg The operation aboutSF() takes an instance of type VOID as a way to say that it takes nothing of interest. In particular, notice how the operation does not need any explicit information to identify the stateful resource that corresponds to the requesting client; as discussed earlier the identifier will be implicitly carried by the request, in accordance with the implied resource access pattern. As to the type VOID, this is imported from a schema document that ships with gCore (GCUBETypes.xsd), again relatively to the location of the interface after service deployment. The schema defines VOID and other common types, for your convenience and to promote uniform conventions in gCube.

Now store the new interfaces in the schema folder under the service location. Remember that file names and port-types must coincide:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|---deploy-server.wsdd
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------tests
|-------StatelessTest.java
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl          [new]
|---Stateful.wsdl         [new]
|
|
|--build.xml
|
|-Dependencies
|--SampleService

Stateful Resources

Time to model stateful resources then. Inspiringly, we shall do this with objects of a Resource class. The minimal requirements on this class are indeed minimal:

Red.jpg it must extend a class provided by gCF, GCUBEWSResource.

Red.jpg it must implement initialise(), the only abstract method of GCUBEWSResource.

Here's a glorious Java class that does just that:

package org.acme.sample.stateful;
import ...

public class Resource extends GCUBEWSResource {

    /** Client visits.*/ 
    private int visits;
    /** Client name. */
    private String name;
	
     /**{@inheritDoc}*/
    public void initialise(Object... args) throws Exception {
         if (args == null || args.length>1) throw new IllegalArgumentException();
         this.setName((String) args[0]);
    }

    public String getName() {return name;}
    public void setName(String name) {this.name=name;}

    public synchronized int getVisits() {return visits;}
    protected synchronized void addVisit() {this.visits++;}
    
}

As you can see, there is little that you have to do to extend GCUBEWSResource. gCF will invoke initialise() in the process of creating a new WS-Resource. The single parameter is an array of zero or more parameters that might be required to initialise a stateful resource (here modelled as an optional parameter for the convenience of clients that have no parameters to pass). Here we expect the name of the client associated with the resource (the client whose visits the resource will track). We perform some checks on the input parameters (there must be exactly one), and then use it to initialise the resource in the obvious way.


Blue.jpg initialise() works a bit like the main() method of standard a Java application, except that it takes Objects rather than Strings. In the latter case, the parameters are typically specified on the command line, here you will pass them in from some other part of the code, where the decision to create a WS-Resource is first made. Remember that according to our plans we will do it from the implementation of the Factory port-type and in response to explicit client requests.

Blue.jpg Resource inherits far more state and behaviour than it declares. We will unveil a small part of this heirloom as we go along. For now, we only notice that the stateful resource inherits an identifier and that the inherited method getID() is available to show it. The precise nature of this identifier depends on how the WS-Resource is created, and we will discuss it later when implementing the Factory port-type.

Blue.jpg Even the simplest of stateful resources must be ready for concurrent access. Although not exactly likely for our Sample service, many clients may target the same WS-Resource at the same time. Concurrent client visits will be assigned different threads by gCore and different threads might concurrently access the same stateful resource through the addVisit() and getVisits() methods. If we did not synchronise access to either of these methods our counter may then become inconsistent. Concurrency is of course a main concern when implementing a service and it is not something gCF can entirely abstract away for you.

Save Resource in accordance with the suggested package:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|---deploy-server.wsdd
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java          [new]
|------tests
|-------StatelessTest.java
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml
|
|-Dependencies
|--SampleService

Home Sweet Home

According to our plans, the Factory port-type will create Resources and the Stateful port-type will find them, use them, and change them.

Find them from where, exactly? We could hold a repository of Resources in the implementation of the Stateful port-type, but a cleaner and more general approach is to have a dedicated manager of Resources that can be accessed from multiple port-types and for different purposes. In gCF, managers of stateful resources are called resource homes.

Writing a simple resource home is as simple as writing a simple stateful resource. However, we cannot do it as incrementally. Along with the resource home we have to introduce a context for the associated port-type, Stateful in our case. Only so will gCF be able to link all the pieces required for state management on our behalf. In particular:

Red.jpg Stateful port-types must have an associated context in gCF.

Let us start from the home anyway:

package org.acme.sample.stateful;
import ...

public class Home extends GCUBEWSHome {

    /** {@inheritDoc} */
    public GCUBEStatefulPortTypeContext getPortTypeContext() {return StatefulContext.getContext();}

} 

Notice the requirements:

Red.jpg The home implementation must extend a class provided by gCF, GCUBEWSHome.

Red.jpg The home implementation must implement getPortTypeContext(), the only abstract method of GCUBEWSHome.

Here we return the singleton instance of StatefulContext, where StatefulContext is defined as follows:

package org.acme.sample.stateful;
import ...

public class StatefulContext extends GCUBEStatefulPortTypeContext {

    /** Singleton instance. */
    private static GCUBEStatefulPortTypeContext cache = new StatefulContext();

    /**Creates an instance, privately. */
    private StatefulContext(){}

    /** Returns the singleton context.
    /* @return the context.*/
    public static GCUBEStatefulPortTypeContext getContext() {return cache;}

    /** {@inheritDoc} */
    public String getJNDIName() {return "acme/sample/stateful";}

    /** {@inheritDoc} */
    public String getNamespace() {return "http://acme.org/sample";}
    
    /** {@inheritDoc} */
    public GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}

}

There is not much we have not already seen here:

  • we follow the usual singleton pattern by returning always a single, eagerly created instance.
  • we follow the usual template pattern to indicate the JNDI configuration entry-point for the associated port-type (getJNDIName()), the namespace in which the interface of the port-type was declared (getNamespace()), and the context of the associated service (getServiceContext()).

The only novel requirement is the following:

Red.jpg The contexts of stateful port-types must extend the GCUBEStatefulPortTypeContext, rather than the more general GCUBEPortTypeContext. The latter gCF class will provide our context with the additional behaviour which is required by the underlying assumption of state.


Now gCF can link our home to our port-type. However, gCF will also need to do the opposite, i.e. identify the home from the port-type context. In fact, when Sample will start up, gCF will look into the configuration of the port-type first and will need to find in it enough information to instantiate the home. We must then dedicate a new section of the JNDI file to the configuration of this port-type, as we have done earlier. In addition, we have to point to the associated resource home from there:

<service name="acme/sample/stateful">
    
	 <resource name="home" type="org.acme.sample.stateful.Home">
		<resourceParams>		                
		         <parameter>
		              <name>factory</name>
		              <value>org.globus.wsrf.jndi.BeanFactory</value>
		          </parameter>
		          <parameter>
		                <name>resourceClass</name>
		                <value>org.acme.sample.stateful.Resource</value>
		          </parameter>
		 </resourceParams>
	 </resource>
      
         <environment name="frequentUserThreshold" value="3" type="java.lang.Integer" override="false" />

</service>

Notice the following:

Red.jpg The JNDI section contains a distinguished resource element that configures the resource home associated with the port-type. The terminology is a bit unfortunate here: this resource is a 'JNDI resource', nothing to do with a stateful resource! Along with the environment elements you have seen so far, it is one of the two modelling primitives that we can use to structure information within JNDI fields. Hopefully, this will not be too confusing.

Red.jpg The resource element must have a name attribute with value home. Only so, can this JNDI resource be identified as the configuration of the resource home associated with the port-type. Then it must have a type that specifies the fully qualified name of the resource home implementation, org.acme.sample.stateful.Home in our case;

Red.jpg The resource element must be configured with at least a small number of resourceParams. The first resourceParam specifies a factory that can create an instance of the resource home we are configuring. The value of this resourceParam is normally always the same, a pre-defined bean factory that ships with gCore. This factory expects the resource home implementation to expose setters for all the other resourceParams that occur under resourceParams. The GCUBEWSHome derived by our Home guarantees that this is the case for all the parameters that are pre-defined in gCore (if you were to add ad-hoc configuration parameters in your resource home, then your home implementation would have to include setters for these too). In most cases, you will stick with this factory implementation. We will surely do in this Primer!

Red.jpg The resourceParams must include a resourceParam called resourceClass that indicates the fully qualified name of the class that implements the stateful resources managed by the resource home. This allows your home to create your stateful resource objects reflectively. Here we specify org.acme.sample.stateful.Resource of course but we do not need to worry about using it directly. The GCUBEWSHome we inherited from gCF will do it for us at the right time.

Blue.jpg Since we are at it, we are throwing in some service-specific configuration, here a numeric threshold beyond which clients will belong to a Frequent User club. This is rather silly but reminds you that port-type contexts are there for your own configuration as well as the configuration required by gCF. We will use this configuration soon from the implementation of the Stateful port-type.


In conclusion, we have added two more pieces to the implementation of Sample, a Home to manage Resources and a StatefulContext for the Stateful. We have also added JNDI configuration for the Stateful port-type, particularly information about the associated Home. The implementation of Sample now look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml     [changed]
|---deploy-server.wsdd
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java          
|-------Home.java              [new]
|-------StatefulContext.java   [new] 
|------tests
|-------StatelessTest.java
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml
|
|-Dependencies
|--SampleService

The Factory Port-Type

With Resource, ResourceHome, and StatefulPortTypeContext our back-end for state management is in place. What we are left with is now to make co-ordinate use of these classes in the implementation of the Factory and Stateful port-types.

Starting with the Factory port-type:

package org.acme.sample.stateful;
import...

public class Factory extends GCUBEPortType {    

	GCUBELog logger = new GCUBELog(this);
    
	/** {@inheritDoc} */
	protected ServiceContext getServiceContext() {return ServiceContext.getContext();}

    
        public EndpointReferenceType logon(String name) throws GCUBEFault {		
	
           try {
                GCUBEStatefulPortTypeContext ptcxt = StatefulContext.getContext();
                GCUBEWSHome home = ptcxt.getWSHome();
                GCUBEWSResourceKey key = ptcxt.makeKey(name);
                GCUBEWSResource resource = home.create(key,name);
                return resource.getEPR();
           } catch (Exception e) {
                logger.error("unable to logon", e);
                throw new GCUBEUnrecoverableException(e).toFault();
           } 
        }
}

As we have already seen for the Stateless port-type, our Factory implementation extends GCUBEPortType and implements the getServiceContext() method.

As to the method logon(), notice what follows:

Blue.jpg In accordance with its interface, logon() takes a String and returns an EndpointReferenceType, which is a Java model for the WS-Addressing's schema type mentioned in the WSDL of the port-type. EndpointReferenceType Java type ships with gCore.

Blue.jpg We obtain the singleton instance of the StatefulContext port-type and use it to retrieve the associated resource home. The method getHome() is defined in GCUBEStatefulPortType and our StatefulContext has simply inherited it. The JNDI configuration of the port-type has provided enough information to gCF to implement that method on our behalf. Note also that since our Home does not add any behaviour to the generic GCUBEWSHome (nor would we need it here), we do not need to cast the return value of getWSHome() to Home but can work directly with the supertype GCUBEWSHome.

Blue.jpg We ask the home to create a stateful resource by invoking the method create() on it. This method is predefined in GCUBEWSHome (and thus in our Home that inherits from it). It appears to return a generic GCUBEWSResource but there is a more specific Resource underneath. The home has looked into the JNDI configuration of the port-type to know which class to instantiate reflectively (remember the resourceClass configuration parameter?). In any case, we do not need to do anything specific with this resource, so we can again leave it at that without needing to cast down to the more specific Resource.

Blue.jpg We invoke create() with the identifier that we wish to give to the stateful resource and the parameters required to initialise it, here only the name of the client. This identifier is based on the name parameter provided by the client but it's actually a GCUBEWSresourceKey wrapper that we can ask the StatefulContext to produce for us. The reason of wrapping our identifier into a 'key' is because we need to return it to the client and thus it needs to serialise on the wire in accordance with WS-Addressing requirements for endpoint reference qualifications. Notice that we have already introduced the notion of a WS-Resource key, check it out.

Blue.jpg Finally, we invoke the method getEPR() on our resource. The method is predefined in GCUBEWSResource and returns the endpoint reference of the WS-Resource that encapulates our resource. Then we simply return the endpoint reference to the client.

A couple of extra commonents on create():

Blue.jpg create() can accept an arbitrary number of resource initialisation parameters. If more were required, we would pass them all after the key, comma-separating them (or as an array). As we have seen earlier these parameters will end up into the initialise() method of the Resource class. Similarly, we could pass zero initialisation parameters (i.e. only the key) if the resource did not need any at all to initialise.

Blue.jpg the create() could have also not taken a key at all (only parameters or absolutely nothing). In this case, the home would have automatically generated a key for the resource. The decision of whether to specify an identifier or not is our own to make. If we specify one, then the home will avoid creating a fresh resource if there is already one with that identifier. So, we specify a key any time we wish to reuse resources across 'semantically equivalent' requests. If this is not the required behaviour, i.e. we do not want reuse of stateful resources, then we do not pass a key to create(). This depends of course on the semantics of our service; if reuse is a sensible option we should strive for it, as creating stateful resources might be in principle arbitrarily expensive processes. Here, rather artificially, we assume that the name is an unambiguous identifier for clients (...) and decide that if we receive many requests with the same name then we will associate all of them with the same WS-Resource.

The Stateful Port-Type

Finally we come to the consumption of WS-Resources and thus to the implementation of the Stateful:

package org.acme.sample.stateful;
import ...

public class Stateful extends GCUBEPortType {

    private final String THRESHOLD_JNDI_NAME="frequentUserThreshold";
    
    ...

    /** {@inheritDoc} */	
    protected ServiceContext getServiceContext() {return ServiceContext.getContext();}

    public String aboutSF(VOID voidType) throws GCUBEFault {

          ServiceContext sctx = ServiceContext.getContext();
          StatefulContext pctx = StatefulContext.getContext();

          try {
             Resource resource = this.getResource()
             String name = resource.getName();
             StringBuilder output = new StringBuilder();

                  ...build output as in Stateless.about()...

             output.append("\nThis is your invocation N." + resource.getVisits() + "\n");
             int threshold = (Integer) pctx.getProperty(THRESHOLD_JNDI_NAME);
             if (resource.getVisits() >= threshold) output.append("welcome in the frequent user club!");
             resource.addVisit();
             return output.toString();

          }
          catch (GCUBEException e) {throw e.toFault();}
          catch (Exception e) {throw sctx.getDefaultException(e).toFault();}
    }


    private Resource getResource() throws ResourceException {
    	return (Resource) StatefulContext.getContext().getWSHome().find();
    }

    ...

}

There are essentially two things to notice here:

Blue.jpg the method aboutSF() behaves like about() in the implementation of the Stateless port-type (check any of the versions we have looked at before). The significant difference is that now we are acting in the context of a WS-Resource and thus upon an underlying stateful resource. We know by now that this resource will be implicitly identified in the endpoint reference use to call this WS-Resource, and we delegate the task to retrieve it to a private helper method getResource(). Assuming we get one back, we increment its count of client visits and proceed with satisfying the client request. In particular, we append the current count of visits at the end of the message, consult port-type configuration via its context to decide whether we should also welcome the client to the frequent user club, and finally return the message to the client.

Blue.jpg in getResource(), we delegate in turn the task to retrieve the stateful resource to the resource home, which we obtain from the context of the Stateful port-type. We then ask the home to find the required resource, by invoking a method find() that our Home inherits from GCUBEWSHome. As the invocation specifies no parameters, the home resolves it by automatically extracting the endpoint reference with which the current call was made by the client. It will then look into that reference and extract the stateful resource key and use the key to lookup the required resource. As this operation is a general one defined by GCUBEWSHome, its return type is as generic as GCUBEWSResource and, since we plan to invoke Resource-specific methods, we need to down cast it before we can return a specific Resource.

Blue.jpg in the process of implied resource access, a few things could go wrong. The call may have been made with an unqualified endpoint reference, i.e. might have been addressed to the port-type rather than a WS-Resource that encapsulates the port-type. Even if the endpoint reference is qualified with a key, the home may still fail to find a resource with that key. This could be because one such resource never existed (the endpoint reference was somehow incorrectly built), or because it existed but it has somehow been removed. We will return later to this possibility.


With our two new port-type implementations, the service implementation ought to look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|---deploy-server.wsdd
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java          
|-------Home.java              
|-------StatefulContext.java   
|-------Factory.java           [new]
|-------Stateful.java          [new] 
|------tests
|-------StatelessTest.java
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml
|
|-Dependencies
|--SampleService

Building & Deploying

The service implementation is almost complete. What we are left with is to reflect the new port-types in the deployment descriptor and in the build properties.

In build.properties we add two more wsdl properties, though we don't need to add any namespace property since we defined all the new interfaces in the same namespace as the first (http://acme.org/sample):

package = org.acme.sample
lib.dir = Dependencies/SampleService
wsdl.1 = Stateless
wsdl.2 = Stateful
wsdl.3 = Factory
namespace.1=http://acme.org/sample

In deploy-server.wsdd we add two more service sections, essentially following the same pattern used for the Stateless port-type:

<?xml version="1.0" encoding="UTF-8"?>
<deployment name="defaultServerConfig" xmlns="http://xml.apache.org/axis/wsdd/"  
                         xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <service name="acme/sample/stateless" provider="Handler" use="literal" style="document">
        <parameter name="className" value="org.acme.sample.stateless.Stateless"/>
        <wsdlFile>share/schema/org.acme.sample/Stateless_service.wsdl</wsdlFile>
        <parameter name="allowedMethods" value="*"/>
        <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
        <parameter name="scope" value="Application"/>
        <parameter name="loadOnStartup" value="true"/>
     </service>

    <service name="acme/sample/factory" provider="Handler" use="literal" style="document">
        <parameter name="className" value="org.acme.sample.stateful.Factory"/>
        <wsdlFile>share/schema/org.acme.sample/Factory_service.wsdl</wsdlFile>
        <parameter name="allowedMethods" value="*"/>
        <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
        <parameter name="scope" value="Application"/>
        <parameter name="loadOnStartup" value="true"/>
    </service>
  
    <service name="acme/sample/stateful" provider="Handler" use="literal" style="document">
        <parameter name="className" value="org.acme.sample.stateful.Stateful"/>
        <wsdlFile>share/schema/org.acme.sample/Stateful_service.wsdl</wsdlFile>
        <parameter name="allowedMethods" value="*"/>
        <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
        <parameter name="scope" value="Application"/>
        <parameter name="loadOnStartup" value="true"/>
    </service>
	  
</deployment>

Now build and deploy service and stubs. As to the service implementation, the latest snapshot is as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|---deploy-server.wsdd       [changed]
|---build.properties         [changed]  
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java          
|-------Home.java              
|-------StatefulContext.java   
|-------Factory.java          
|-------Stateful.java          
|------tests
|-------StatelessTest.java
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml
|
|-Dependencies
|--SampleService

A Quick Test

Ok, it's time to make sure that all the pieces developed so far come together nicely. For this purpose, we will write a couple of simple test clients. The first client, CreateResource, will invoke the logon() operation of the Factory port-type to create a WS-Resource. The second client, StatefulTest will invoke the method aboutSF() of that WS-Resource. The two clients will exchange the endpoint reference of the WS-Resource that the first creates and the second consumes. The exchange will be based on the file system: CreateResource will serialise on file the endpoint reference of the WS-Resource it creates; StatefulTest will deserialise the endpoint reference from that file and use it.

Starting with CreateResource:

package org.acme.sample.tests;
import ...

public class CreateResource {
    	
    static GCUBEClientLog logger = new GCUBEClientLog(CreateResource.class);
	
    public static void main(String[] args) throws Exception {
	    	
      logger.info("creating WS-Resource...");
      
      try {
        
        EndpointReferenceType factoryEPR = new EndpointReferenceType(new AttributedURI(args[0]));
        FactoryPortType stub = new FactoryServiceAddressingLocator().getFactoryPortTypePort(endpoint);
        stub=GCUBERemotePortTypeContext.getProxy(stub,GCUBEScope.getScope(args[1]));
      
        EndpointReferenceType resourceEpr = stub.logon(args[2]);
        logger.trace("created resource at endpoint " + resourceEpr);

        FileWriter writer = new FileWriter("/tmp/resource.epr");
        ObjectSerializer.serialize(writer,resourceEpr,new QName("http://acme.org/sample","statefulEPR"));
        writer.close();
      
      }
      		
    }
}

The first part of the client has been already discussed when we tested the Stateless port-type, including logging practices. We expect three arguments: the endpoint reference of a running Factory, a scope in which to make the call which is compatible with the targeted Factory, and a name for the client.

We then invoke the logon() operation and get back an endpoint reference to a WS-Resource. To store it, we invoke the serialize() method of a utility class that ships with gCore, ObjectSerializer, passing a FileWriter 'open' on a given file, the WS-Resource endpoint reference to serialise, and a QName of our own invention that wraps the endpoint reference in well-formed XML.

Moving on to StatefulTest:

package org.acme.sample.tests;
import ...

public class StatefulTest {
    	
    static GCUBEClientLog logger = new GCUBEClientLog(StatefulTest.class);
	
    public static void main(String[] args) throws Exception {
	    	
      logger.info("visiting WS-Resource...");
      
      try {

        FileReader reader = new FileReader("/tmp/resource.epr");
        EndpointReferenceType resourceEPR= 
               (EndpointReferenceType) ObjectDeserializer.deserialize(newInputSource(reader),EndpointReferenceType.class);
        reader.close();
        
        StatefulPortType stub = new StatefulServiceAddressingLocator().getStatefulPortTypePort(resourceEPR);
        stub = GCUBERemotePortTypeContext.getProxy(stub, GCUBEScope.getScope(args[0]));
        
        logger.trace(stub.aboutSF(new VOID()));
      
      }
      		
    }
}

Here, we deserialise the resource following a process inverse to its serialisation (just take this code as boiler plate, really). The rest ought to be familiar by now, including the expectation of a scope as a test parameter.

Now, run CreateResource once and then StatefulTest multiple times to see the count of visits growing at each call.

With the two test clients, the service implementation ought to look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml     
|---deploy-server.wsdd
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java          
|-------Home.java              
|-------StatefulContext.java   
|-------Factory.java   
|-------Stateful.java    
|------tests
|-------StatelessTest.java
|-------CreateResource.java       [new]
|-------StatefulTest.java         [new] 
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml
|
|-Dependencies

WS-Resources and Standard Interfaces

At this stage, we have succesfully implemented the implied resource pattern promoted by WSRF. This is of value in itself, as a clean design approach to state identification and access. As discussed earlier on, however, the pattern opens possibilities for generic manipulations of state oriented towards discovery, lifetime management, and notification management. To move in that direction, we need to expose a uniform description of the WS-Resources and also augment their interfaces and implementations with generic operations. If we don't do this, the state will be uniformly accessible in principle, but it will also remain utterly opaque and inaccessible to generic clients.

WS-Resource Properties

WSRF proposes a standard for exposing state as an unordered set of Resource Properties. A Resource Property, or RP for short, is any piece of information that we wish to make public about our WS-Resources. A RP may be statically or dynamically computed, it may be constant or else change over time or not, it may occur once or many times, and it may or may not have internal structure. What it does matter is that a RP has a name that clients can use to refer to it.

In our example, we may decide to consider the name of the client associated with a WS-Resource as a single-valued and constant RP. Instead, we may decide to consider the number of client visits as a piece of private state and not expose it as a RP at all. In fact, unknown to us, our WS-Resources already include some 'hidden' state modelled as RPs. gCF augmented them implicitly with RPs that describe various systemic properties of the WS-Resources (e.g. the scope in which they operate, the identifier of the gHN in which they are hosted, the name and class of the service to which they belong, etc.). Collectively, we refer to these pre-defined RPs as the gCube RPs.

gCube RPs and service-specific RPs collectively form the public state of our WS-Resources, something that generic clients can target without knowing anything more specific about our WS-Resources. To actually act upon our WS-Resource, WSRF also defines standard interfaces with which clients can access, query, and change RPs. gCube has in turn implemented many key infrastructure-wide mechanisms that make use of these interfaces, and thus is important that our WS-Resources implement them. We don't need to worry too much about the cost of compliance, however, because gCF offers pre-defined implementations that can be 'plugged' into our services. Effectively, these implementations extend the stateful port-types with support for the operations defined in the WSRF interfaces. In particular, gCF defines one such implementation, called the gCube Provider, that supports the minimal set of WSRF operations that are mandatory for the WS-Resource of all gCube services.

To extend a stateful port-type with the gCube Provider, we need to perform three steps:

  • extend the interface of the stateful port-type with the interface of the gCube Provider.
  • extend the implementation of the stateful port-type with the implementation of the gCube provider.
  • model the state of stateful resources as RPs.

After completing these three steps we will have added the operations of the gCube Provider to the stateful port-type and will have linked them to the implementation of our stateful resources. In other words, clients will be able to invoke the operations of the gCube Provider and the implementations of these operations will be able to access our stateful resources. As a net result, our WS-Resources will have become compliant with WSRF and thus with gCube. No small feat.

Let us now go through these steps for our Sample service.

Extending WS-Resource Interface & Implementation

We start by extending the interface of the Stateful port-type with the interface of the gCube Provider. In the WSDL of the Stateful port-type we must proceed as follows:

Red.jpg we must first include a schema description of the available RPs in the WSDL of the port-type, where a generic client that obtains the WSDL can in principle discover them. This effectively amounts to describing the public state of our WS-Resources as a well-formed XML document, what WSRF calls the Resource Property Document (RPD). The RPD description is always a global, complex element which we are otherwise free to name. In our case, the element is called statefulRPD and includes the description of a single-valued RP called Name:

<xsd:element name="statefulRPD">
 <xsd:complexType>
   <xsd:sequence>
     <xsd:element ref="tns:Name" minOccurs="1" maxOccurs="1"/>
   </xsd:sequence>
 </xsd:complexType>
</xsd:element>


Red.jpg we must add two special attributes to the portType element of the WSDL. The first points generic clients to the statefulRPD element, so that they can identify it within the WSDL. The second indicates our will to extend the port-type interface with the operations defined in the WSDL of the gCube Provider. Clearly, we need also to import the new namespaces that come into play and bind them to some suffixes in the top-level definitions element of the WSDL:

<definitions name="Stateful"
    ....
    xmlns:wsdlpp="http://www.globus.org/namespaces/2004/10/WSDLPreprocessor"
    xmlns:wsrp="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.xsd"
    xmlns:provider="http://gcube-system.org/namespaces/common/core/porttypes/GCUBEProvider">

    ...
 
   <import namespace="http://gcube-system.org/namespaces/common/core/porttypes/GCUBEProvider" location="../gcube/common/core/providers/GCUBEProvider.wsdl"/>
   
   ...

   <portType name="StatefulPortType" wsdlpp:extends="provider:GCUBEProvider" wsrp:ResourceProperties="tns:statefulRPD">
  
     ...

We now move to extending the implementation of the Stateful port-type with the implementation of the gCube Provider. This will not require us to change any code, only the deployment descriptor of the Stateful port-type. In particular:

Red.jpg we must add the following parameter to the service dedicated to the port-type (in no particular place):

 <parameter name="providers" value="GCUBEProvider"/>

That's it. Now when the operations of the gCube Provider are invoked on our Stateful port-type their original implementations will be found and executed.

Modelling State as Resource Properties

One last step to complete the integration of the gCube Provider in our service implementation: when an operation of the gCube Provider is invoked on our Stateful port-type, its implementation will try to access our stateful resources. The problem is that it will do so in terms of the abstractions it knows about, namely RPs. Although our Resource class defines a String-valued field that corresponds to the Name RP we have declared in the WSDL, the gCube Provider has no idea that this field corresponds to the RP, nor does it know what fields correspond to RPs and what fields are instead meant to be private state.

The cleanest and most general solution here is to comply with the provider's expectations and move from a String-based model of client names to a more explicit RP-based model. gCF offers classes to model RPs as well as a class to group RPs into a single container, the so-called Resource Property Set of the WS-Resource. The gCube Provider will then expect our Resource class to expose the RP Set and then it will work with the RP objects it finds within it. In practice, the GCUBEWSResource class extended by our Resource class takes care of most requirements. All we have to do is:

Blue.jpg indicate the names of the RPs. We do so by overriding the method getPropertyNames() inherited from GCUBEWSResource and returning the names into an array of Strings:

private static final String NAME_RP_NAME = "Name";
...

/** {@inheritDoc} */
protected String[] getPropertyNames() {return new String[]{NAME_RP_NAME};}

Blue.jpg refactor accessor methods in terms of RP objects:

public synchronized String getName() {
   return (String) this.getResourcePropertySet().get(NAME_RP_NAME).get(0);
}
    
public synchronized void setName(String name) {
  ResourceProperty property = this.getResourcePropertySet().get(NAME_RP_NAME);
  property.clear();
  property.add(name);
}

As you can see, we first obtain the whole RP Set (inherited method getResourcePropertySet()), and then use it to lookup by name the required RP (method get()). As RPs may be multi-valued, working with RP objects is similar to working with lists; we obtain the single-value of our Name property as the first element of the list (method get()), and we set it by first emptying the list (method clear())) and then adding to it in the next place available, which is again the first (method set()). As the accessors are no longer atomic operations we also synchronise them. Once the accessors have been re-factored in terms of RP objects, the rest of the code can remain unchanged.


After all these changes, our service implementation will look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml     
|---deploy-server.wsdd          [changed]
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java           [changed]  
|-------Home.java              
|-------StatefulContext.java   
|-------Factory.java   
|-------Stateful.java        
|------tests
|-------StatelessTest.java
|-------CreateResource.java       
|-------StatefulTest.java         
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl               [changed]
|
|
|--build.xml                
|
|-Dependencies

More Testing

After a new build and deployment of the service, we are ready to test the novelties. Here, we pretend to be generic clients and check whether the RPs of our WS-Resources can be accessed using the generic WSRF interfaces. In practice, we may well never need to act as generic clients. And yet some of the services we might interact with may take advantage of the standard interfaces and offer no other means to explore the state of their WS-Resources; in other words, they may treat specific and generic clients in the same way (no methods like getName() on their port-type interfaces!). In any case, we need to test that we are truly compliant with WSRF and gCube requirements.

There are two ways to approach testing here. We can go down the usual programmatic route, or else use some pre-defined clients that ship with gCore. In the first case, our testing client for the Stateful port-type may contain code such as the following:

StatefulPortType statefulPT = new StatefulServiceAddressingLocator().getStatefulPortTypePort(..some-endpoint-reference..);
logger.info(statefulPT.getResourceProperty(new QName("http://acme.org/sample","Name")).get_any()[0]);

where getResourceProperty is just one of the standard operations defined by the WSDLs interface that our Stateful port-type 'inherits' by the gCubeProvider. The operation returns the RP named in input, although it is rather unimportant here to get into the details of its signature. For testing purposes, it suffices to confirm that we get back something logged as:

2009-04-22 16:16:17,816 INFO  tests.StatefulTest [main,info:78] StatefulTest: 
              <ns1:Name xmlns:ns1="http://acme.org/sample">...the current value of the RP...</ns1:Name>

The second and simplest way to test our port-type's compliance with WSRF is through scripts that launch pre-defined WSRF clients, one for each operation of the WSRF interfaces that the gCube Provider, and now our Stateful port-type, implements. In what follows, for example, we show the invocation of a script that queries all the RPs of a WS-Resource whose endpoint is stored in a given file (incidentally, the same file produced by our test client CreateResource). Do notice that our Name RP co-exists with all the gCube RPs added by gCF.

...> wsrf-query -e /tmp/stateful.epr 

<ns1:statefulRPD xmlns:ns0="http://acme.org/sample" xmlns:ns1="http://gcube-system.org/namespaces/common/core/porttypes/GCUBEProvider" 
     xmlns:ns2="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd" 
     xmlns:ns3="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.xsd" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

 <ns1:RI>6feeeed0-2f37-11de-92c8-a326c1a4b1b6</ns1:RI>

 <ns1:ServiceID>d202da90-2f38-11de-9805-a99465896164</ns1:ServiceID>

 <ns1:ServiceName>SampleService</ns1:ServiceName>

 <ns1:GHN>12994620-2e80-11de-b393-a6908a641d8e</ns1:GHN>

 <ns1:ServiceClass>Samples</ns1:ServiceClass>

 <ns1:Scope>/gcube/devsec</ns1:Scope>

 <ns1:CurrentTime xmlns:ns1="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd">
                                                                             2009-04-22T15:17:20.426Z</ns1:CurrentTime>

 <ns1:TerminationTime xsi:nil="true" xmlns:ns1="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceLifetime-1.2-draft-01.xsd"/>

 <ns1:FixedTopicSet 
      xmlns:ns1="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-1.xsd">false</ns1:FixedTopicSet>

 <ns1:TopicExpressionDialects xmlns:ns1="http://docs.oasis-open.org/wsn/2004/06/wsn-WS-BaseNotification-1.2-draft-01.xsd">
                                        http://docs.oasis-open.org/wsn/2004/06/TopicExpression/Simple</ns1:TopicExpressionDialects>

 <ns1:Name xmlns:ns1="http://acme.org/sample">...the current value of the RP...</ns1:Name>

</ns1:statefulRPD>

Blue.jpg You can find more WSRF scripts junder $GLOBUS_LOCATION/bin. You can also find more information about the operations of RP-oriented WSRF interfaces in the definition of the corresponding standard.

The Lifetime of WS-Resources

Besides operations that deal directly with RPs, WSRF defines also an interface for the destruction of entire WS-Resources. This means that an important aspect of their lifetime management can now be handled remotely. Needless to say, gCube requires compliance with this interface too. The gCube provider implements the interface, however, so we have already done everything we need to do to comply with this further requirement.

There are two forms of destruction contemplated by WSRF, immediate and scheduled. To destroy a WS-Resource immediately it suffices to invoke a destroy() operation on it, as shown in the following example:

StatefulPortType statefulPT = new StatefulServiceAddressingLocator().getStatefulPortTypePort(epr);
statefulPT = GCUBERemotePortTypeContext.getProxy(statefulPT, ...some scope of the WS-Resource..."));
statefulPT.destroy(new Destroy());

Two things to notice here:

Red.jpg The destruction of a WS-Resource must be relative to a scope, as the WS-Resource might operate in multiple scopes. We must then set a scope on the proxied stub of the Stateful port-type prior to invoking the destroy() operation on it. Furthermore, the scope must be compatible with those in which the WS-Resource operates, or the invocation will result in a fault.

Blue.jpg At the server's side, the gCube Provider executes the operation by invoking a method remove() that our Home inherits from GCUBEWSHome. You can also invoke this method from within the service implementation if, for some conditions that are specific to the semantics of your WS-Resources, you can conclude that it's time to remove any given one from one or more of its scopes.

The second form of destruction occurs implicitly when WS-Resources pass their 'expiry date'. By default, the are all born without a time to live, but you can rectify things if the semantics of your WS-Resources requires it. To do this, just define a lifeTime environment in the JNDI configuration of your stateful port-type. In the following example, WS-Resources are granted a time to live of 30 seconds:

<environment name="lifeTime" value="30" type="java.lang.Integer" override="false"/>

When a WS-Resource is created, it first checks for the existence of a lifeTime environment in the configuration of the associated stateful port-type. If it finds it, it adds the lifetime value to the current time and stores the resulting termination time in a dedicated gCube RP, called unsurprisingly TerminationTime. This is behaviour that all your WS-Resources inherit from gCF's GCUBEWSResource class.

The termination time of a WS-Resource can then be acted upon in a number of ways. First and foremost, the resource home will periodically check the termination time of all the WS-Resources that it manages. It will then remove those that it finds expired, and it will do so from all the scopes in which they currently operate. Again, this is behaviour that all resource homes inherit from gCF's GCUBEWSHome class.

Besides the resource home, you can also act upon the termination time of WS-Resources, typically to give them a new lease of life. Interestingly, remote clients can do the same! The same WSRF interface that offers the destroy operation, offers also operations to extend the lifetime of WS-Resources. As usual, the gCube provider implements these operations and so do all the stateful port-types that extend the gCube Provider. Again, the details of these operations are beyond the scope of this Primer, so check out the standard for precise instructions.

Persistence for WS-Resources

How does a resource home keep track of the stateful resources it manages? Where does it retrieve them from when we ask for those required to satisfy client requests?

By default, all resource homes keep their stateful resources in good old memory. Our Home was no exception in this respect. This transient mode of operation guarantees the fastest access to state, of course, but it does not take long to see shortcomings too:

  • the resources vanish as soon as the gHN experiences some downtime. This will bite at development time, when you are likely to start and stop the gHN at the rhythm of changes to the service implementation. It will then bite much more in production, where gHNs may be brought temporarily down for maintenance (e.g. upgrades), and for all sorts of failures of course. If our stateful resources are transient, all these scenarios will translate into the need for re-staging your service. For serious gCube services, this is a lenghty, error-prone, and costly process.
  • the resources may ultimately clog your memory, depending on their size and number, and the overall performance of your service may degrade accordingly.

For these reasons, it is important that resource homes can operate in a persistent mode, i.e. rely on some longer-term storage medium to store their stateful resources. Implementing persistence is not exactly a trivial task, because it impacts on virtually all other faces of state management. Fortunately, gCF hides most of the complexity from you. It can do it all by itself, however, so you need to help in the way of configuration and, yes, some implementation too. We consider these requirements in the process of adding persistence for the WS-Resources of the Sample service.

Persistence Delegates

Resource homes will rely on a separate object to store and load their stateful resources from long-term storage. This object is dedicated to persistence and is called the persistence delegate. gCF defines a whole framework for the design of persistence delegates, where different types of delegates can target different types of persistent stores. As long as you are happy with the file system as the persistence store, then you do not have to design a persistence delegate from scratch but can derive it from gCF's GCUBEWSFilePersistenceDelegate. To do this, you must do the following:

Red.jpg you must override the onStore() method to explicitly store in a ObjectOutputStream the properties of a stateful resource that you care to persist.

Red.jpg you must override the onLoad() method to explicitly restore from a ObjectInputStream the properties of a stateful resource, in the exact order in which you persisted them in onStore() of course.

The following example illustrates the point for our Resources, where both client name and visit count need to be persisted:

package org.acme.sample.stateful;
import...

public class ResourcePersistenceDelegate extends GCUBEWSFilePersistenceDelegate<Resource> {

  protected void onLoad(Resource resource,ObjectInputStream ois) throws Exception {

   /** {@inheritDoc} */		
   super.onLoad(resource,ois);
   resource.setName((String)ois.readObject());
   int visits = (Integer)ois.readObject();
   for (int i =0; i<visits; i++) resource.addVisit();
   
  }
	
  /** {@inheritDoc} */
  protected void onStore(Resource resource,ObjectOutputStream oos) throws Exception {
		
   super.onStore(resource,oos);
   oos.writeObject(resource.getName());
   oos.writeObject(resource.getVisits());
		
  }
}

Notice the following:

Blue.jpg GCUBEWSFilePersistenceDelegate is parametric in the type of stateful resource that it handles. Accordingly, ResourcePersistenceDelegate instantiates the type parameter to Resource:

public class ResourcePersistenceDelegate extends GCUBEWSFilePersistenceDelegate<Resource> {...}

Red.jpg onStore() and onLoad() must delegate to the methods they override in GCUBEWSFilePersistenceDelegate, so as to ensure that the gCube RPs are stored/loaded before more specific properties of the stateful resource. You can do this at the beginning or at the end of the methods, as long as you are consistent across the two methods. The usual convention is to do it straight away.

Blue.jpg onStore() and onLoad() invoke the accessors of the Resource they are storing and loading. As we have seen earlier, these accessors may well encapsulate access to RPs. You can here happily abstract over the way in which stateful resources store their state in memory.

Blue.jpg Synchronisation issues are taken care of by gCF, in the context in which callbacks to your methods are issued. So no need to worry about that.

Blue.jpg Where are our Resources actually stored on the file system? Stateful resources are always stored under your home directory, in a file named with the plain identifier of the resource (not the key), with a wsresource suffix, and in a directory with the following path:

<homedir>/.gcore/persisted/<hostname>-<port>/<servicename>/<resourceclassname>

In our example, a Resource with identifier johndoe may be stored in ~/.gcore/persisted/myhost.org-8080/SampleService/Resource/johndoe.wsresource.


The next requirement is to let the resource home know about the existence of ResourcePersistenceDelegate. We do this by adding a new parameter element to the JNDI configuration of our Home, as follows:

<resource name="home" type="org.acme.sample.stateful.Home">
  <resourceParams>		                
    <parameter>
      <name>factory</name>
      <value>org.globus.wsrf.jndi.BeanFactory</value>
    </parameter>
    <parameter>
      <name>resourceClass</name>
      <value>org.acme.sample.stateful.Resource</value>
    </parameter>
    <parameter>
      <name>persistenceDelegateClass</name>
      <value>org.acme.sample.stateful.ResourcePersistenceDelegate</value>
    </parameter>
</resource>

just notice that:

Red.jpg we must name the parameter persistenceDelegateClass and specify the fully qualified name of the ResourcePersistenceDelegate class as its value.


There is of course one question that still needs to be answered. Who calls the onStore() and onLoad() methods of our ResourcePersistenceDelegate? And when does this happen?

Blue.jpg onLoad() is invoked transparently by our Home whenever a Resource is needed and yet cannot be found in memory. Before giving up and concluding that the Resource does not exist (or never did), the resource home will check for its existence on the persistence store targeted by our ResourcePersistenceDelegate.

Blue.jpg When is a resource not found in memory then? The first and foremost case is when the gHN has been restarted of course. We call this first-time load a hard load. All your stateful resources will be automatically reloaded at gHN startup. The other important case will become clearer in a moment, and it won't assume a gHN restart. We call these soft loads.

Red.jpg onStore() is invoked by a related method store() that our Resource class inherits from GCUBEWSResource. However, we are entirely responsible to invoke store() on our Resources. Why is down to us? Well, because no one else knows when the state of our Resources changes and thus when they become stale in the persistent store. Typically, we will want to do this right at the end of any process that causes or is very likely to cause changes. In our example, we will want to store a Resource after it has been created and right after each client visit. We will then need to change the implementation of the logon() method of our Factory port-type implementation as follows:

package org.acme.sample.stateful;
import...

public class Factory extends GCUBEPortType {    

     ...        
     public EndpointReferenceType logon(String name) throws GCUBEFault {		
	
           try {
                ...
                GCUBEWSResource resource = home.create(key,name);
                resource.store();
                return resource.getEPR();
           } catch (Exception e) {
                ...
           } 
        }
}

We will also need to revise the implementation of the method aboutSF() in our Stateful port-type implementation, as follows:

package org.acme.sample.stateful;
import...

public class Stateful extends GCUBEPortType {

    ...

   public String aboutSF(VOID voidType) throws GCUBEFault {

          ...

          try {
             Resource resource = this.getResource()
             String name = resource.getName();
             ...
             resource.addVisit();
             resource.store();
             return output.toString();

          }
          catch (GCUBEException e) {...}
          catch (Exception e) {...}
    }

    ...

}

A word of caution now:

Red.jpg You may be tempted to add an invocation of store() inside the addVisit() of the Resource class. More generally, you may consider to concentrate storing operations as close as possible to the point of change, in the accessors of the mutable properties of your stateful resources. Don't do it. This is a bad idea, for a number of reasons. First, it is potentially inefficient, as you may end up storing multiple times in a process that makes many fine-grained changes simultaneously. Second, the accessors are invoked during the loading of your stateful resources, as you have seen in the implementation of onLoad(). This means that your resources would be stored as they are been loaded, which may be disastrous and is surely inefficient. Store instead as late possible, just before closing the process (typically, but not always, before returning to the client).

Persistence Modes

Given the pros and cons of memory-based storage, the interpretation of the persistence requirement may actually vary:

  • we may want to guard our services from gHN downtime and retain maximum speed of access. In this case, we would like to rely on a persistent store to backup our resources, while keeping them in memory all the time. Perhaps we do not have huge resources, perhaps they are short-lived, perhaps we have arranged for deployment on memory-rich machines. What we really cannot loose is performance. In all these cases, we say that the requirement is for a resource home that operates in a hard persistence mode.
  • alternatively, we may be happier with a more balanced compromise between memory consumption and access speed. We may accept that, while all stateful resources are still persistently stored to cope with gHN downtime, any of them may exist only in the persistent store. In particular, we may accept that a stateful resource remains in memory for a time that is proportional to the amount of available memory. The resource would then be transferred back to memory only and precisely when it is needed by clients, clearly with some delay. In this case, we say that the requirement is for a resource home that operates in a soft persistence mode.
  • as a more refined compromise between memory consumption and access speed, we may wish that stateful resources remain in memory for a time that is proportional to the amount of available memory as well as the frequency with which they are used. In other words, we may require that stateful resources that are consumed often stay in memory for longer, and thus are more likely to be accessed quickly. In this case, we say that the requirement is for a resource home that operates in a cached persistence mode because the resource home keeps a cache of frequently used resources that forces them to stay in memory.

Resource homes in gCF can support all three modes of persistence. By default, configuring a persistence delegate for the resource home will make it operate in soft persistence mode. The other two modes can be easily configured as follows:

Blue.jpg To configure a cached persistence mode, add a cacheTimeout parameter to the JNDI resource that configures the resource home, specifying the time in milliseconds after which a resource which has not been needed is kicked out of the cache:

<resource name="home" type="org.acme.sample.stateful.Home">
  <resourceParams>		                
    <parameter>
      <name>factory</name>
      <value>org.globus.wsrf.jndi.BeanFactory</value>
    </parameter>
    <parameter>
      <name>resourceClass</name>
      <value>org.acme.sample.stateful.Resource</value>
    </parameter>
    <parameter>
      <name>persistenceDelegateClass</name>
      <value>org.acme.sample.stateful.ResourcePersistenceDelegate</value>
    </parameter>
    <parameter>
       <name>cacheTimeout</name>
       <value>60000</value>
    </parameter>
</resource>

Blue.jpg To configure a hard persistence mode, simply specify a value of 0 for the cacheTimeout parameter just discussed above.


After the changes induced by persistence, the snapshot of your service implementation ought to be as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml                [changed]
|---deploy-server.wsdd          
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java            
|-------Home.java              
|-------StatefulContext.java   
|-------Factory.java   
|-------Stateful.java
|-------ResourcePersistentDelegate.java   [new]        
|------tests
|-------StatelessTest.java
|-------CreateResource.java       
|-------StatefulTest.java         
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml                
|
|-Dependencies

Publishing WS-Resources

The first and foremost motivations for adopting WSRF standards in gCube concerns the publication of WS-Resources. Publishing a WS-Resource means to publish the values of its RPs with the gCube Information Services. The reason for doing so is to allow clients to dynamically discover the WS-Resource by querying the Information Services for the existence or current value of RPs. The Information Services themselves are thus a distinguished example of the class of generic clients for which we have bothered to adopt WSRF standards in the first place. We deal with WS-Resource publication here and discuss discovery later and in a much broader context.

gCF takes care of WS-Resource publication. All home resources, in particular, inherit from the GCUBEWSHome the ability to publish a WS-Resource right after its initialisation. In response, the Information Services will come back at regular intervals and poll the WS-Resource via its WSRF interfaces to check that it is still alive and to get the latest value of its RPs. If the WS-Resource is not available after a number of attempts, its publication expires. When persistence is enabled, however, the unavailability of a WS-Resource may not be due to its actual destruction, but simply a gHN that is temporarily down. For this reason, the resource home will republish at start-up all the WS-Resources that it manages persistently. Furthermore, the resource home will take the initiative and signal the destruction of a WS-Resource to the Information Services as soon as this happens.

There is still something that is left to us, however. We need to tell the resource home:

  • what RPs we wish to actually publish (typically all those we cared to define);
  • how often we wish our RPs to be polled by the Information Services (depending on the frequency with which we expect our RPs to change).

We do both things via configuration. First we fill a dedicated 'registration' template and store it in a file in the <etc> folder of our service implementation, say registration.xml. For the WS-Resources that encapsulate our Stateful port-type, for example, the registration file may look as follows:

<ServiceGroupRegistrationParameters
    xmlns:sgc="http://mds.globus.org/servicegroup/client"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
    xmlns:agg="http://mds.globus.org/aggregator/types"
    xmlns="http://mds.globus.org/servicegroup/client">
    
    <RefreshIntervalSecs>60</RefreshIntervalSecs>
    
    <Content xsi:type="agg:AggregatorContent">
        
        <agg:AggregatorConfig>
            <agg:GetMultipleResourcePropertiesPollType xmlns:sample="http://acme.org/sample">
                <agg:PollIntervalMillis>60000</agg:PollIntervalMillis>                
                <agg:ResourcePropertyNames>sample:Name</agg:ResourcePropertyNames>
                <agg:ResourcePropertyNames>sample:Visits</agg:ResourcePropertyNames>
            </agg:GetMultipleResourcePropertiesPollType>
        </agg:AggregatorConfig>
        
        <agg:AggregatorData/>
        
    </Content>

This is mostly a boiler plate format defined by the technologies that underlie gCube publication mechanisms. We just notice the following:

Red.jpg we must specify the polling interval as the value in milliseconds of the PollIntervalMillis element. Here we opted for a minute.

Red.jpg we must explicitly list the RPs that wish to have published as the values of the ResourcePropertyNames. Do note that the values include the namespace of the Stateful port-type, and that this namespace is bound to the local default namespace.

The second piece of configuration we need is in the JNDI configuration of the Stateful port-type. We have seen an example of JNDI resource before, here is another one:

<resource name="publicationProfile" type="org.gcube.common.core.state.GCUBEPublicationProfile">	
  <resourceParams>
    <parameter>
       <name>factory</name>
       <value>org.globus.wsrf.jndi.BeanFactory</value>
    </parameter>
    <parameter>
       <name>fileName</name>
       <value>registration.xml</value>
    </parameter>
  </resourceParams>
</resource>

Red.jpg we must name the configuration publicationProfile and we must give it a type of org.gcube.common.core.state.GCUBEPublicationProfile. This is the class of the object that will model this piece of configuration at runtime.

Red.jpg we must specify a param called factory that specifies the class responsible for creating the GCUBEPublicationProfile object that models this piece of configuration at runtime. As usual, this is not expected to change from service to service.

Red.jpg finally, we must specify a param called fileName that specifies the name of the registration file.

Finally, notice the following:

Blue.jpg WS-Resource publication is not mandatory. If WS-Resources are meant to be generated and consumed by a single client and for the purposed of some fairly short-lived task, then sharing them within the infrastructure may simply not be a requirement. In these cases, simply avoid any configuration (the publicationProfile and, subsequently, the registration file) and gCF will understand your intentions. Of course you can also temporarily disable publication by keeping all your configuration but commenting out the publicationProfile.

With publication configured, your service implementation ought to look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml      [changed]
|---deploy-server.wsdd          
|---build.properties
|---registration.xml            [new]
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|------stateful
|-------Resource.java            
|-------Home.java              
|-------StatefulContext.java   
|-------Factory.java   
|-------Stateful.java        
|-------ResourcePersistenceDelegate.java
|------tests
|-------StatelessTest.java
|-------CreateResource.java       
|-------StatefulTest.java         
|
|--schema
|---Stateless.wsdl
|---Factory.wsdl
|---Stateful.wsdl
|
|
|--build.xml                
|
|-Dependencies