Difference between revisions of "Adding State"
Manuele.simi (Talk | contribs) (→Adding State) |
Manuele.simi (Talk | contribs) (→Delving into the implementation) |
||
Line 177: | Line 177: | ||
The new classes will be placed in the new ''org.acme.sample.stateful''. | The new classes will be placed in the new ''org.acme.sample.stateful''. | ||
− | Stateful Context | + | '''The Stateful Context''' |
---- | ---- | ||
− | We begin by implementing the Stateful Context. This context | + | We begin by implementing the Stateful Context. This context models the configuration of the ''acme/sample/stateful'' port-type as the [[Single_Port-Type#Port-Type_Context|StatelessContext]] did for the Stateless port-type. However, it transparently adds a lot of facilities to access the statefulness of the port-type. |
+ | <pre> | ||
+ | package org.acme.sample.stateful; | ||
− | Resource | + | import org.acme.sample.ServiceContext; |
+ | import org.gcube.common.core.contexts.GCUBEServiceContext; | ||
+ | import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext; | ||
+ | |||
+ | public class StatefulContext extends GCUBEStatefulPortTypeContext { | ||
+ | |||
+ | public static String FREQUENT_USER_LIMIT_JNDI_NAME = "frequentUserLimit"; | ||
+ | |||
+ | private static GCUBEStatefulPortTypeContext cache = new StatefulContext(); | ||
+ | |||
+ | @Override | ||
+ | public String getJNDIName() {return "acme/sample/stateful";} | ||
+ | |||
+ | @Override | ||
+ | public String getNamespace() {return "http://acme.org/sample";} | ||
+ | |||
+ | @Override | ||
+ | public GCUBEServiceContext getServiceContext() { | ||
+ | return ServiceContext.getContext(); | ||
+ | } | ||
+ | |||
+ | public static GCUBEStatefulPortTypeContext getPortTypeContext() { | ||
+ | return cache; | ||
+ | } | ||
+ | } | ||
+ | </pre> | ||
+ | |||
+ | Notes: | ||
+ | * 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. | ||
+ | |||
+ | <pre> | ||
+ | package org.acme.sample.stateful; | ||
+ | |||
+ | import org.gcube.common.core.state.GCUBEWSResource; | ||
+ | import org.globus.wsrf.ResourceException; | ||
+ | |||
+ | public class Resource extends GCUBEWSResource { | ||
+ | |||
+ | protected static final String RP_NAME = "Name"; | ||
+ | protected static final String RP_VISITS = "Visits"; | ||
+ | protected static String[] RPNames = { RP_NAME, RP_VISITS }; | ||
+ | |||
+ | @Override | ||
+ | protected void initialise(Object... args) throws ResourceException { | ||
+ | if (args.length!=1) throw new IllegalArgumentException(); | ||
+ | |||
+ | try { | ||
+ | String name = (String) args[0]; | ||
+ | this.setName(name); | ||
+ | this.setVisits(0); | ||
+ | } | ||
+ | catch (Exception e) { | ||
+ | throw new ResourceException(e); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * {@inheritDoc} | ||
+ | */ | ||
+ | public String[] getPropertyNames() { | ||
+ | return RPNames; | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * 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); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * Returns the number of visits for the user | ||
+ | * @return | ||
+ | */ | ||
+ | public Integer getVisits() { | ||
+ | return (Integer) this.getResourcePropertySet().get(RP_VISITS).get(0); | ||
+ | } | ||
+ | |||
+ | /** | ||
+ | * 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); | ||
+ | |||
+ | } | ||
+ | |||
+ | /** | ||
+ | * 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 Resource Home''' | |
---- | ---- | ||
− | Service instance port-type | + | '''The Service instance port-type''' |
---- | ---- | ||
− | Factory port-type | + | '''The Factory port-type''' |
---- | ---- | ||
Revision as of 18:11, 26 March 2008
Contents
Adding State
In the first part of this tutorial we have created a stateless service for simplicity. However, the most common patterns used when developing gCube services are the ones that allow to create stateful service. In this part of the tutorial we will learn how to add a state to the SampleService.
Towards a multi port-type service
To add a state we need firstly to add two new port-types to the SampleService:
- one is dedicated to create a new stateful resource, the so-called Factory service port-type
- the other one is dedicated to access and manage the state, the so-called Service instance port-type
For both, we need to perform the same steps done for the Stateless port-type:
- add the port-types to the profile file
- define the WSDL interface for each port-type
- provide the Java implementation of the two port-types
plus other specific steps that allow to manage stateful services.
Profiling for the infrastructure
The following is the new SERVICE/etc/profile.xml that declares the new two 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>
Defining the port-type interfaces
WSDL interface for the factory service
Create a new WSDL file, name it Factory.wsdl and place the file in the SERVICE/etc folder.
<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> <fault name="fault" message="corefaults:GCUBERetrySameFaultMessage"></fault> <fault name="fault" message="corefaults:GCUBERetryEquivalentFaultMessage"></fault> </operation> </portType> </definitions>
Notes:
- 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
WSDL interface for the service instance
Create a new WSDL file, name it Stateful.wsdl and place the file in the SERVICE/etc folder.
<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>
Notes:
- 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
- the operation returns a string
Delving into the implementation
The implementation of the stateful part of the SampleService is a bit more complex than the stateless one and requires to code the stateful context, the stateful resource, the resource home and the two new port-types. The new classes will be placed in the new org.acme.sample.stateful.
The Stateful Context
We begin by implementing the Stateful Context. This context models the configuration of the acme/sample/stateful port-type as the StatelessContext did for the Stateless port-type. However, it transparently adds a lot of facilities to access the statefulness of the port-type.
package org.acme.sample.stateful; import org.acme.sample.ServiceContext; import org.gcube.common.core.contexts.GCUBEServiceContext; import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext; public class StatefulContext extends GCUBEStatefulPortTypeContext { public static String FREQUENT_USER_LIMIT_JNDI_NAME = "frequentUserLimit"; private static GCUBEStatefulPortTypeContext cache = new StatefulContext(); @Override public String getJNDIName() {return "acme/sample/stateful";} @Override public String getNamespace() {return "http://acme.org/sample";} @Override public GCUBEServiceContext getServiceContext() { return ServiceContext.getContext(); } public static GCUBEStatefulPortTypeContext getPortTypeContext() { return cache; } }
Notes:
- the class extends the
GCUBEStatefulPortTypeContext
- the class adopts the singleton pattern as suggested for any context
- the
getJNDIName()
method returns the JNDI name of the port-type - the
getServiceContext()
method connects the port-type to the service context - the
getNamespace()
method returns the namespace of the port-type as defined in the 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.
package org.acme.sample.stateful; import org.gcube.common.core.state.GCUBEWSResource; import org.globus.wsrf.ResourceException; public class Resource extends GCUBEWSResource { protected static final String RP_NAME = "Name"; protected static final String RP_VISITS = "Visits"; protected static String[] RPNames = { RP_NAME, RP_VISITS }; @Override protected void initialise(Object... args) throws ResourceException { if (args.length!=1) throw new IllegalArgumentException(); try { String name = (String) args[0]; this.setName(name); this.setVisits(0); } catch (Exception e) { throw new ResourceException(e); } } /** * {@inheritDoc} */ public String[] getPropertyNames() { return RPNames; } /** * 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); } /** * Returns the number of visits for the user * @return */ public Integer getVisits() { return (Integer) this.getResourcePropertySet().get(RP_VISITS).get(0); } /** * 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); } /** * 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); } }
Notes:
- ...
The Resource Home
The Service instance port-type
The Factory port-type
Configuring JNDIs & Descriptors
Building & Deploying
A Test Client
--Manuele.simi 03:21, 26 March 2008 (EET)