Single Port-Type

From GCube System
Revision as of 21:44, 24 March 2008 by Manuele.simi (Talk | contribs) (A Test Client for Stateless Port-Type)

Jump to: navigation, search

From configuration to testing with a single Port-Type

Even if not compulsory, we strongly suggest to adopt the Eclipse 3.3 as development platform. In such an IDE, open a new workspace with a Java 1.5 compiler and create a new Java project by specifying a source folder (named from now on, SERVICE folder). Then, fill a user-library with all JARs in gCore lib and name it CONTAINERLIB.

Structuring the service code

Prepare the SERVICE folder structure as follows:

  1. create a etc folder where to place your configuration files
  2. create a org/acme/sample folder where to place your source code
  3. create a schema folder where to place the remote interface files
  4. copy the share/gcube_tools/build.xml file into your SERVICE folder

Profiling for the infrastructure

Create a new XML file named Profile.xml and place it in the SERVICE/etc folder. This file profiles the service in such a way that the instance of a service can be discovered by others and eventually dynamically deployed in a gCube infrastructure.

<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>
			</Main>
			<Software>
				<Description>Describes port-type stubs</Description>
				<Name>Stubs</Name>
				<Files><File>org.acme.sample.stubs.jar</File></Files>
			</Software>
		</Packages>
	</Profile>
</Resource>

The file identifies our service by assigning it a ServiceClass and a ServiceName. It also describes the service decomposition: it is composed by two packages, the service itself and its stubs and that the first one has a GHN-scoped dependency against the second one. Finally, it indicates that the service has a single Port-Type named acme/sample/stateless.

JNDI configuration

Create a new XML file named deploy-jndi-config.xml and and place it in the SERVICE/etc folder. It will include either the global service configuration and the all the Port-Type ones. The file has a two-fold role:

  1. tells to the gCube framework about the service
  2. makes available to the service at runtime the information included there
<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" />
		 	
	</service>
</jndiConfig>

Notes:

  • at this stage, the file only include a service section reporting the name of the service ("acme/sample") and the name of the profile created
  • the @config.dir@ is a placeholder replaced at deployment time with the real path of the SERVICE/etc folder

Sketching Port-type Interfaces

WSDL structure

The following is the basic skeleton of any WSDL interface:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:tns="...." xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
name="Stateless" targetNamespace=".....">
	<types>
		<xsd:schema targetNamespace=".....">
			<!-- REQUEST AND RESPONS TYPE DEFINITIONS -->
		</xsd:schema>
	</types>
	
	<!-- MESSAGES  -->

	<portType name=".....">

		<!-- OPERATION -->

	</portType>
</definitions>

Defining the Stateless Port-Type

Create a new WSDL file, name it Stateless.wsdl and place the file in the SERVICE/etc folder.

<?xml version="1.0" encoding="UTF-8"?>
<definitions 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" name="Stateless" targetNamespace="http://acme.org/sample">
	<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:element name="about" type="xsd:string"/>
			<xsd:element name="aboutResponse" type="xsd:string"/>
		</xsd:schema>
	</types>
	<message name="aboutInputMessage">
		<part name="request" element="tns:about"/>
	</message>
	<message name="aboutOutputMessage">
		<part name="response" element="tns:aboutResponse"/>
	</message>
	<portType name="StatelessPortType">
		<operation name="about">
			<input message="tns:aboutInputMessage"/>
			<output message="tns:aboutOutputMessage"/>
			<fault name="fault" message="corefaults:GCUBEUnrecoverableFaultMessage"/>
			<fault name="fault" message="corefaults:GCUBERetrySameFaultMessage"/>
			<fault name="fault" message="corefaults:GCUBERetryEquivalentFaultMessage"/>
		</operation>
	</portType>
</definitions>

This file defines the interface of the first stateless Port-Type of the Sample Service. The interface has a single operation, named about that accepts as input a string and returns a string as output. Moreover, the operation can throw three types of fault, that are defined in the imported GCUBEFaults.wsdl. Each fault has a specific semantic within a gCube infrastructure:

  • GCUBEUnrecoverableFaultMessage ...
  • GCUBERetrySameFaultMessage...
  • GCUBERetryEquivalentFaultMessage ...

Namespace mappings

To successfully generate the stub classes from the WSDL inteface, we need to tell to gCore where (i.e. in what Java package) to place the stub classes. This is done with a mappings file, which maps WSDL namespaces to Java packages, named namespace2package.mappings and placed in the SERVICE root folder.

http\://acme.org/sample=org.acme.sample.stubs
http\://acme.org/sample/bindings=org.acme.sample.stubs.bindings
http\://acme.org/sample/service=org.acme.sample.stubs.service

Delving into the implementation

The Service Context

The fist thing to implement is the Service Context. It is a static component which represents the whole service within the GHN and the rest of its implementation. It has multi-fold role:

  • a provider of service-wide information & utilities
  • bootstraps the service within the GHN
  • gives access to JNDI configuration & profile information
  • manages security and scope
  • …and more

The role of the service context it's very complex role, but fortunately it’s all transparently catered for by the gCore Framework. What is needed it's just derive your context it from GCUBEServiceContext class and fill in the blanks.

Create a ServiceContext.java class in your SERVICE source root folder.

package org.acme.sample;

import org.gcube.common.core.contexts.GCUBEServiceContext;

public class ServiceContext extends GCUBEServiceContext {

	
	/** Single context instance, created eagerly */
	private static ServiceContext cache = new ServiceContext();
	
	/** Returns cached instance */
	public static ServiceContext getContext() {return cache;}
	
	/** Prevents accidental creation of more instances */
	private ServiceContext(){};
		
	/**
	 * {@inheritDoc}
	 */
	public String getJNDIName() {return "acme/sample";}
	
}

Notes:

  • the class adopts the singleton pattern. As best practice, it is strongly suggested to always adopt it for ANY context defined in a gCube service
  • the getJNDIName() method identifies the configuration in the JNDI file and must return the name given to the service there (acme/sample in this example). It MUST be implemented

A first implementation for the Stateless Port-Type

The implementation of the Stateless provides a mean to bootstrap the service by extending the GCUBEStartupPortType and to serve the Stateless Port-Type clients.

package org.acme.sample.stateless;
import org.acme.sample.ServiceContext;
import org.gcube.common.core.contexts.GCUBEPortTypeContext;
import org.gcube.common.core.contexts.GCUBEServiceContext;
import org.gcube.common.core.contexts.GHNContext;
import org.gcube.common.core.faults.GCUBEException;
import org.gcube.common.core.faults.GCUBEFault;
import org.gcube.common.core.faults.GCUBERetryEquivalentException;
import org.gcube.common.core.faults.GCUBERetrySameException;
import org.gcube.common.core.faults.GCUBEUnrecoverableException;
import org.gcube.common.core.porttypes.GCUBEStartupPortType;

public class Stateless extends GCUBEStartupPortType {

	/** {@inheritDoc} */
	protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}
	
	protected static void simulateProcessRequest() throws Exception {
		if (Math.random()<.20) { //simulating an error
			switch ((int) (Math.random()*4+1)) {//randomly choosing error type
				case 1 : throw new GCUBEUnrecoverableException("just give up");
				case 2 : throw new GCUBERetryEquivalentException("maybe someone else?");
				case 3: throw new GCUBERetrySameException("maybe in a bit?");
				case 4: throw new Exception("some problem with unclear semantics");
	}}}
	
	public String about(String name) throws GCUBEFault {
		
		StringBuilder output = new StringBuilder();		
		GHNContext nctx = GHNContext.getContext();
		ServiceContext sctx = ServiceContext.getContext();
		GCUBEPortTypeContext pctx = StatelessContext.getContext();
		try {
			simulateProcessRequest();
			output.append("Hello "+name).append(", you have invoked porttype ").
			append(pctx.getName()+" of service "+sctx.getName()).append(", which you found at ").
			append(pctx.getEPR()+" in the gCube infrastructure "+nctx.getGHN().getInfrastructure());
		}
		catch(GCUBEException e) {throw e.toFault();}
		catch(Exception e) {sctx.getDefaultException("Problem of unknown semantics", e).toFault();}
		return output.toString();
	}

}

Notes:

  • the GCUBEStartupPortType is a class defined in the gCore Framework performing the Service instance initialisation. In any gCube service, there MUST be ONE (and only ONE) Port-Type that extends this class.
  • the getServiceContext() method MUST be implemented. It connects the Port-Type to the Service Context
  • the about(String name) method provides an implementation of the about operation defined in the WSDL interface
  • the simulateProcessRequest() method is just a trick to randomly choose an error type (since there is no way to get a real error inside the about method)

Towards Deployment: the WSDD descriptor

A gCube service is deployed into an Axis message processing node using an XML-based deployment descriptor file known as a Web Service Deployment Descriptor (WSDD). WSDD describes how the various components installed in the Axis node are to be chained together to process incoming and outgoing messages to the service. These chain definitions are (somehow) compiled and made available at runtime through registries. Each service section reports deployment instructions about a Port-Type. In our Sample service, the first version of the WSDD instructs the Axis engine about how to deploy and activate the Stateless Port-Type of the Sample Service. It must be placed in the SERVICE/etc folder and named deploy-server.wsdd.

<?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>
    	  
</deployment>

Notes:

  • the value of the name attribute of the service element, together with the base URL of gCore, will form the URI at which our Port-Type listens the incoming requests (in this example, the complete URL will result ''http://localhost:8080/wsrf/services/acme/sample/stateless'' ). This value MUST be the same reported as name of the Port-Type in the service profile.
  • the value of the className parameter is the Port-Type implementation class
  • the loadOnStartup parameter MUST be set to true for the bootstrapping Port-Type (i.e. the one that implements the GCUBEStartupPortType)

Building & Deploying

The overall build and deployment process is done with the facilities offered by the Ant build tool and thanks to the support of the gCore scripts. Before to start the build process, type:

source $GLOBUS_LOCATION/bin/gcore-load-env

This loads all the needed files in your development environment.

Configuring the build process

To configure the build process, create a build.properties file and place it into the SERVICE root folder.

name = SampleService
package = org.acme.sample
package.dir = org/acme/sample
lib.dir = SERVICELIBS/SampleService
wsdl.1 = Stateless

Notes:

  • the name parameter is the name of the service
  • the package parameter is the root Java package
  • the package.dir parameter is the root folder of the implementation files
  • the lib.dir points to external dependencies (relative to the BUILD_LOCATION)
  • the wsdl.X parameters provide a list of the service Port-Types

Before to build, you have to define a BUILD_LOCATION environment variable

export BUILD_LOCATION=<your build location>

The BUILD_LOCATION/lib.dir folder is used to place the produced stubs to be then exploited for compile time dependencies. This is also a simple way to collect all the produced stubs in a single location and to easily share them among developers. The BUILD_LOCATION/build folder is used to store temporary files created during the build process.

Building the stubs

In the BUILD_LOCATION folder, type

${BUILD_LOCATION}> ant -f <SERVICE folder>/build.xml stubs

This performs a temporary build in the BUILD_LOCATION/build folder. Then, the generated Jar is:

  • deployed in the GLOBUS_LOCATION/lib folder for runtime deployment
  • deployed in the SERVICELIBS/SampleService folder to be used as compile-time dependency. From the Java Build Path configuration of the Eclipse project, you should add this file as an External Jar and see resolved the dependencies against the stub classes.

Building the service

In the BUILD_LOCATION folder, type

${BUILD_LOCATION}> ant -f <SERVICE folder>/build.xml

This implicitly uses the service target. The command above performs a temporary build in the BUILD_LOCATION/build folder, a packaging of the service in a GAR archive and a local deployment in the SERVICE folder. If everything works, the contents of the GAR archive should look like the following:

org.acme.sample.gar
|
|-etc
   |
   |-deploy-jndi-config.xml
   |-deploy-server.wsdd
   |-profile.xml
|
|-lib
   |
   |-org.acme.sample.jar
|
|-META-INF
   |
   |-MANIFEST.MF
|
|-schema
   |
   |-Stateless_bindings.wsdl
   |-Stateless_flattened.wsdl
   |-Stateless_service.wsdl
   |-Stateless.wsdl

(Un)Deploying the Service

To deploy the SampleService, type in your BUILD_LOCATION folder:

${BUILD_LOCATION}> gcore-deploy-service SAMPLESERVICE/org.acme.sample.gar

This preforms a runtime deployment of the service by:

  • assigning it the ID org.acme.sample
  • creating a new GLOBUS_LOCATION/etc/org.acme.sample and copying there the service configuration files
  • copying the compiled code in the GLOBUS_LOCATION/lib folder
  • creating a new GLOBUS_LOCATION//share/schema/SampleService and copying there all the service interfaces (WSDLs)

To undeploy the SampleService, just type:

gcore-undeploy-service org.acme.sample

gCore Logging & Restart

Once a service is deployed, one might want to configure the service container to log the service activity. In order to do that, edit the GLOBUS_LOCATION/container-log4j.properties and add the following category line for org.acme

# Display any warnings generated by our code
log4j.category.org.globus=WARN
log4j.category.org.gcube=DEBUG
[...]
log4j.category.org.acme=DEBUG

This enables the debug level for every code under the org.acme package.

Then, by typing the following

${BUILD_LOCATION}> gcore-start-container -nosec 

or

${BUILD_LOCATION}> gcore-start-container -nosec -debug

it is possible to start the gContainer. The second option (-debug) enable the container to run in the debug mode, so it logs as much as possible all the activities. The log output is appended to the container.log file generated in the working folder (i.e. the folder from which you start the container).

A Test Client for Stateless Port-Type

The StatelessTest class is a simple client for the Stateless Port-Type.

package org.acme.sample.tests;


import org.acme.sample.stubs.StatelessPortType;
import org.acme.sample.stubs.service.StatelessServiceAddressingLocator;
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.scope.GCUBEScopeManager;
import org.gcube.common.core.scope.GCUBEScopeManagerImpl;
import org.gcube.common.core.utils.logging.GCUBEClientLog;

public class StatelessTest {

	static GCUBEClientLog logger = new GCUBEClientLog(StatelessTest.class);
	
	public static void main(String[] args) throws Exception {
		
		GCUBEScopeManager manager = new GCUBEScopeManagerImpl();
		manager.setScope(GCUBEScope.getScope(args[1]));
		
		logger.debug("Stateless Client is running...");
		EndpointReferenceType endpoint = new EndpointReferenceType();
		endpoint.setAddress(new AttributedURI(args[0]));
		
		StatelessPortType statelessPT = new StatelessServiceAddressingLocator().getStatelessPortTypePort(endpoint);
		manager.prepareCall(statelessPT, "Samples", "SampleService");
		System.out.println(statelessPT.about(args[2]));
		
	}
}

Notes:

  • the GCUBEScopeManager manages call scope for the client
  • GCUBEScope.getScope(() parses the input scope e creates a GCUBEScope object
  • the usage of EndPointReference and Locator is the standad way to access Axis-based node

Refining the implementation

Manuele.simi 21:23, 24 March 2008 (EET)