Difference between revisions of "Adding State"

From GCube System
Jump to: navigation, search
(Building the stubs and the service)
((Un)Deploying the Service)
Line 553: Line 553:
  
 
=== (Un)Deploying the Service===
 
=== (Un)Deploying the Service===
 +
The (un)deploying process does not change. The same steps seen in the Stateless part of the tutorial have to be performed for deploying the [[Single_Port-Type#.28Un.29Deploying_the_Service|service]].
  
 
== A Test Client ==
 
== A Test Client ==
  
 
--[[User:Manuele.simi|Manuele.simi]] 00:41, 27 March 2008 (EET)
 
--[[User:Manuele.simi|Manuele.simi]] 00:41, 27 March 2008 (EET)

Revision as of 23:56, 26 March 2008

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. We will create a service that recognize a user once it logs on and keeps trace of how many times it accesses the state.

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>
		</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 implementation and requires to code:

  1. the stateful context,
  2. the stateful resource,
  3. the resource home,
  4. and the two new port-types.

All the new classes implemented in this part will be placed in the new org.acme.sample.stateful package.

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. Our Resource will maintain the state in 2 properties:

  • the name of the user
  • a counter about how many times it accesses the resource
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 class overrides the initialise() 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 GCUBEWSResourcePropertySet instance. In order to do that the class:
    1. declares the name of the two properties
    2. overrides the getPropertyNames() method to return the property names
  • the getters and setters implementation of the properties reflects the GCUBEWSResourcePropertySet 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.

package org.acme.sample.stateful;

import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext;
import org.gcube.common.core.state.GCUBEWSHome;

public class Home extends GCUBEWSHome {

    @Override
    public GCUBEStatefulPortTypeContext getPortTypeContext() {	
	return StatefulContext.getPortTypeContext();
    }

}

Notes:

  • the class extends the GCUBEWSHome class
  • the getPortTypeContext() method connects the class to the stateful port-type

The Service instance port-type


The Service class provides an implementation of the Service instance port-type defined in the WSDL interface.

package org.acme.sample.stateful;

import org.acme.sample.ServiceContext;
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 {

    public String aboutSF(VOID voidType) throws GCUBEFault {
	
	StringBuilder output = new StringBuilder();
	GHNContext nctx = GHNContext.getContext();
	ServiceContext sctx = ServiceContext.getContext();
	GCUBEStatefulPortTypeContext pctx = StatefulContext.getPortTypeContext();

	try {
		    final Resource resource = this.getResource();
		    resource.setVisits(resource.getVisits() + 1);
	    
		    output.append("Hello " + resource.getName()).append(", you have invoked porttype ").
		    	append(pctx.getName() + " \nof service " + sctx.getName()).
		    	append(", \nwhich you found at ").
		    	append(pctx.getEPR() + "in the gCube infrastructure " + nctx.getGHN().getInfrastructure()).	    
		    	append( " \nand you are in the Scope " + sctx.getScope()).
			append(" \nThis is your invocation N." + resource.getVisits() + "\n");
	
		    if (resource.getVisits() >= (Integer)pctx.getProperty(StatefulContext.FREQUENT_USER_LIMIT_JNDI_NAME, true)) {
		    	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();
    }    
}

Notes:

  • the class implements the aboutSF operation in the aboutSF() method
  • the method retrieves various contexts to use when producing the output string
  • the most interesting part of the class is the getResource() method demonstrating the way to access from the stateful context the actual Resource instance

The Factory port-type


The Factory class provides an implementation of the Factory port-type defined in the WSDL interface.

package org.acme.sample.stateful;

import org.apache.axis.message.addressing.EndpointReferenceType;
import org.gcube.common.core.contexts.GCUBEStatefulPortTypeContext;
import org.gcube.common.core.faults.GCUBEFault;
import org.gcube.common.core.faults.GCUBEUnrecoverableException;

public class Factory {    
    
    public EndpointReferenceType logon(String name) throws GCUBEFault {		
	//create/reuse the resource
	try {
		GCUBEStatefulPortTypeContext ptcxt = StatefulContext.getPortTypeContext();
		return ptcxt.getWSHome().create(ptcxt.makeKey(name), name).getEPR();
	} catch (Exception e) {	   
	    throw new GCUBEUnrecoverableException(e).toFault();
	} 
    }
}

Notes:

  • the class implements the logon operation in the logon method
  • the method retrieves the Stateful context and from it retrieves also the Home instance
  • by invoking the create method, the stateful resource is created with the given key (first input parameter) and with the given list of parameters (only the name variable here). The list is then passed to the initialise() method of the Resource instance
  • the makeKey() method of the StatefulContext instance creates a new GCUBEWSResourceKey
  • the getEPR() method of the Resource instance return the EndPointReference to use to access the new state from clients

JNDI configuration

The SERVICE/etc/deploy-jndi-config.xml file must be modified in order to add the configuration of the stateful port-type. With respect to the Stateless configuration a 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 StatefulContext instance from which the configuration can be accessed programmatically.

<?xml version="1.0" encoding="UTF-8"?>
<jndiConfig xmlns="http://wsrf.globus.org/jndi/config">

	<service name="acme/sample">	
		<environment 
		  name="profile" 
	 	  value="@config.dir@/profile.xml" 
	 	  type="java.lang.String"
	 	  override="false" />
	 	
	 	<environment 
		  name="displayServiceProfile" 
	 	  value="true" 
	 	  type="java.lang.Boolean"
	 	  override="false" /> 	

		<environment 
		  name="displayNodeProfile" 
	 	  value="true" 
	 	  type="java.lang.Boolean"
	 	  override="false" /> 	
		 	
	</service>
	
	<service name="acme/sample/stateless">
		<environment 
			name="name" 
		 	value="StatelessPortType" 
		 	type="java.lang.String"
		 	override="false" /> 	
	</service>

	<service name="acme/sample/stateful">
	
		<environment 
			name="name" 
		 	value="StatefulPortType" 
		 	type="java.lang.String"
		 	override="false" /> 		
	        	            
	 	<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="frequentUserLimit" 
		   value="3" 
		   type="java.lang.Integer"
		   override="false" />
	</service>
  
</jndiConfig>

Notes:

  • ...
  • ...

Building & Deploying

Towards Deployment: the WSDD descriptor

The two new port-types have to be declared in the deployment descriptor as follows.

<?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/SampleService/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"/>
        <parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/> 
    </service>
  
  	<service name="acme/sample/stateful" provider="Handler" use="literal" style="document">
        <parameter name="className" value="org.acme.sample.stateful.Service"/>
        <wsdlFile>share/schema/SampleService/Stateful_service.wsdl</wsdlFile>
        <parameter name="allowedMethods" value="*"/>
        <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
        <parameter name="scope" value="Application"/>
        <parameter name="providers" value="GCUBEProvider"/>
        <parameter name="loadOnStartup" value="true"/>
       	<parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/> 
    </service>
  
  	<service name="acme/sample/factory" provider="Handler" use="literal" style="document">
        <parameter name="className" value="org.acme.sample.stateful.Factory"/>
        <wsdlFile>share/schema/SampleService/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"/>
       	<parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/> 
    </service>
  
</deployment>

Configuring the build process

The build.properties file has to declare the two new port-types to build.

name = SampleService
package = org.acme.sample
package.dir = org/acme/sample
lib.dir = SERVICELIBS/SampleService
wsdl.1 = Stateless
wsdl.2 = Factory
wsdl.3 = Stateful

Building the stubs and the service

The building process does not change. The same steps seen in the Stateless part of the tutorial have to be performed for building the stubs and the service.

(Un)Deploying the Service

The (un)deploying process does not change. The same steps seen in the Stateless part of the tutorial have to be performed for deploying the service.

A Test Client

--Manuele.simi 00:41, 27 March 2008 (EET)