Difference between revisions of "The Development Cycle"

From GCube System
Jump to: navigation, search
(The Deployment Descriptor)
(The Deployment Descriptor)
Line 305: Line 305:
 
=== The Deployment Descriptor ===
 
=== The Deployment Descriptor ===
  
We configure deployment so as to tell the gHN how to manage a running instance of the service, particularly how to instantiate the service and how to recognise and correctly dispatch calls to its port-types. Here is the so-called ''deployment descriptor'' for ''SampleService'':
+
We configure deployment so as to tell the gHN how to manage a Running Instance of the service, i.e. how to instantiate the port-type implementations and how to recognise and correctly dispatch calls to them. Here is the so-called ''deployment descriptor'' for ''SampleService'':
  
 
<pre>
 
<pre>
Line 325: Line 325:
 
</pre>
 
</pre>
  
The deployment descriptor includes a ''service''  section for each port-type we wish to deploy. The following is worth noticing:
+
The deployment descriptor ought to include a ''service''  section for each port-type. The following is worth noticing:
  
* the ''name'' of the section tells the gHN how to distinguish calls to the port-type from calls to other port-types. It is the relative part of the URI at which the port-type will be accessible, its ''endpoint''. The endpoint is formed by concatenating the ''name'' of the port-type with the endpoint of the gHN. The endpoint of the gHN is ''http:// <hostname>:<port>/wsrf/services'' so that the endpoint of our port-type is:
+
* the ''name'' of the section tells the gHN how to distinguish calls to the port-type from calls to other port-types. It is the relative part of the URI at which the port-type is accessible, its ''endpoint''. In particular, the port-type endpoint is formed by concatenating the ''name'' parameter with the endpoint of the gHN. Given that the endpoint of the gHN is  
  
<pre>http://<hostname>:<port>/wsrf/services/acme/sample/stateless</pre>
+
      <pre>http:// <hostname>:<port>/wsrf/services</pre>
 +
 
 +
then the endpoint of our stateless port-type is:
 +
 
 +
      <pre>http://<hostname>:<port>/wsrf/services/acme/sample/stateless</pre>
  
 
* the ''className'' parameter names the class which implements the port-type. This is used by the gHN to  create an instance of that class and forward to it all the calls for the port-type that it receives.
 
* the ''className'' parameter names the class which implements the port-type. This is used by the gHN to  create an instance of that class and forward to it all the calls for the port-type that it receives.
 +
 
* the ''wsdlFile'' parameter specifies the location of the port-type interface ''after'' deployment. We will be able to explain its value later on.
 
* the ''wsdlFile'' parameter specifies the location of the port-type interface ''after'' deployment. We will be able to explain its value later on.
* the ''scope'' parameter tells the gHN how many instances of the port-type implementation it ought to create in order to serve requests. A value of ''scope'' indicates that we wish all requests to be handled by single object of the implementation class.
 
* the ''loadOnStartup'' parameter indicates that the single object of the implementation class should be create when the gHN starts up and when it dispatches the first request to the port-type.
 
  
'''Note:''' the ''name'' of the section must match that reported in the [[#My First Profile|service profile]].
+
* the ''scope'' parameter tells the gHN how many port-type instances it ought to create in order to serve requests. A value of ''Application'' indicates that we wish all requests to be handled by single object of the implementation class. Alternatively, a value of ''Request" indicates that we want a new port-type instance for each request.
'''Note:''' other possibilities for ''scope'' are [http://globus.org available].
+
 
'''Note:''' ''loadOnStartup'' must be set to ''true'' for the [[A Simple Implementation|startup port-type]].
+
* the ''loadOnStartup'' parameter indicates that the gHN should create the port-type instance when it starts up, rather than when it receives the first request for it.
 +
 
 +
These two notes are ''very'' important:
 +
 
 +
'''Note:''' the ''name'' of the ''service'' section here must match that reported in the [[#My First Profile|service profile]] and the [[#A Tiny JNDI|JNDI configuration]].
 +
'''Note:''' ''loadOnStartup'' '''must''' be set to ''true'' for the [[#A Simple Implementation|startup port-type]].
  
 
Save the deployment descriptor in a file called '''deploy-server.wsdd''' under the ''etc'' folder of the service location:
 
Save the deployment descriptor in a file called '''deploy-server.wsdd''' under the ''etc'' folder of the service location:

Revision as of 20:01, 12 April 2008

Let us go through a quick tour of development with SampleService.

My First Profile

A good starting point with service development is its configuration. And a good starting point when configuring a service is the definition of its profile. The role and structure of gCube service profiles is described in detail elsewhere. Here we just show one of the smallest and yet perfectly well-formed profile a service may have:

<?xml version="1.0" encoding="UTF-8"?>
<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>
		<Version>1.0</Version>
		<Packages>
			<Main>
				<Name>Main</Name>
				<Version>1.0</Version>
				<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>...</WSDL>
				</PortType>
			</Main>
			<Software>
				<Description>Describes port-type stubs</Description>
				<Name>Stubs</Name>
				<Version>1.0</Version>
				<Files><File>org.acme.sample.stubs.jar</File></Files>
			</Software>
		</Packages>
	</Profile>
</Resource>

By necessity, this profile reveals things yet to come. For the time being, be happy with a conceptual summary:

  • SampleService is a a particular type of gCube Resource, one of type Service. Like all gCube Resources has an ID, which it receives when it is formally registered with the infrastructure. Until then, SampleService does not have a proper identifier. No worries though, a temporary one will be generated by gCF at runtime.
  • The profile of SampleService specifies its Name and specifies its membership to a Class of functionally related gCube services. Classes are not pre-defined, and here we settle on Samples. A free-form Description and a Version complement the preliminary description of SampleService.
  • SampleService is physically comprised of Packages. A Main package identifies the main artefact to emerge from the build of the service, a GARArchive, and provides information about the service port-types, including their WSDLs' (here omitted for mercy). The profile unveils our initial plans for a single stateless port-type, which we name by identifying the relative part of the URI at which it will be accessible on the gHN (more on this soon).
  • Another Software component identifies the secondary build artefact of the service, its own stubs. The Main package depends always upon its stubs and this dependency must be explicitated.

Note: If the notion of build artefact is not obvious to you, bide your time for a little longer or jump ahead.

Save the profile in a file called profile.xml and place it in the etc folder of the service implementation:

|-SampleService
|--etc
|---profile.xml
|--src
|--schema
|
|-Dependencies
|--SampleService

A Tiny JNDI

Next we move to the configuration of the code itself, for which we use JNDI technology. Again, the details and granularity of JNDI configuration are discussed elsewhere. Here we simply notice that, while the JNDI configuration might contain any information which the code needs to consult at runtime, the gCF raises precise requirements against the existence and shape of some of that information. In particular, the configuration should at least contain the following information:

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

	<service name="acme/sample">
		<environment  name="configDir" value="@config.dir@"  type="java.lang.String" override="false" />
	</service>

</jndiConfig>

Essentially, the configuration must have a service section dedicated to information conceptually associated with the entire implementation, rather than more specific components yet to come. Furthermore, the service section must:

  • specify the name of the service. This is not particularly constrained but must distinguish SampleService from any other which may be deployed on the same gHN. As we already do with Java and URI namespaces, we can approach uniqueness through a hierarchical structure, here of /-separated strings. In our case, the name acme/sample will suffice as a unique identifier.
  • include an environment element which specifies the absolute path to the configuration folder of SampleService. Contrary to expectations, this is not our etc folder under the service location. It is in fact another configuration folder allocated to SampleService at the point of service deployment. This will become clearer later. For the time being, we use the wildcard @config.dir@ to abstract over the precise location of this yet-to-be folder, and leave to gCore the task of resolving it at the point of deployment.

Note: The wildcard @config.dir is surely convenient at this stage. It is in fact necessary for any real gCube service. This is because the service may be dynamically deployed on any gHN and the absolute path of the configuration folder depends on the file system of that gHN. During service development, that location is an absolute unknown.

So far, there is little in our configuration which is specific to SampleService. Indeed, the JNDI above can be considered as boilerplate for any gCube service. We will see shortly how to inject more information into the JNDI configuration, but until that moment this minimal configuration will suffice.

Save the configuration in a file called deploy-jndi-config.xml and place it in the etc folder of the service implementation:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|--src
|--schema
|
|-Dependencies
|--SampleService


A PortType Interface

Enough configuration for now. Let us move to the definition of the interface for the first port-type we plan to add to SampleService. The general expectation is that all your interfaces will have this high-level structure:

<?xml version="1.0" encoding="UTF-8"?>
<definitions 
               xmlns:tns="..YOUR NAMESPACE.." targetNamespace="...YOUR NAMESPACE..." name="..SOME_NAME... "
               xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" >

	<types>
	   <xsd:schema targetNamespace=".....">
			<!-- REQUEST & RESPONSE TYPE DEFINITIONS -->
	    </xsd:schema>
	</types>
	
	<!-- MESSAGE DEFINITIONS  -->

	<portType name="...PORT_TYPE NAME...portType">
		<!--OPERATIONS DEFINITIONS -->
	</portType>

</definitions>

All pretty standard WSDL, of course. And yet notice what follows:

  • we dedicate the WSDL to a single port-type (even though multiple port-types are admitted by the WSDL specs). This simplifies our building configuration and suggests than any form of sharing across port-types (type definition, messages, faults, etc.) be achieved through imports/includes.
  • the name of the WSDL definitions does not play much of a role during development. Use anything that looks appropriate. Given that there is a one-to-one correspondence between WSDLs and port-types use, any informal name you may wish to give to the port-type.
  • the name of the portType also refers to the port-type, though this has a very important role during the build of the service. In particular, it must end in portType.

The WSDL of our first port-type illustrate these conventions:

<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns:tns="http://acme.org/sample" name="Stateless" targetNamespace="http://acme.org/sample"
    xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"  >

	<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"/>			
		</operation>

	</portType>
</definitions>

As you can see:

  • we have used the namespace http://acme.org/sample for our port-type.
  • we have settled on the name Stateless for it, and used it twice. The first time to name the whole interface definition and a second time in the name StatelessPortType of the portType definition.
  • we have defined a single operation about for Stateless, which takes and returns a string. The idea is to expect the name of the caller as input and produce some information about the port-type as output.

Hard to imagine a simpler port-type, really. Save the interface in a file Stateless" under the schema folder of the service, as agreed:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|
|--src
|
|--schema
|---Stateless.wsdl
|
|-Dependencies
|--SampleService

A Simple Implementation

So far, the development process has been dominated by simple but numerous conventions upon the use of not necessarily simple but equally numerous formats. Frankly, all very unexciting and error-prone. Welcome (or welcome back) to the grim reality of web service development. gCF cannot take much of this pain away, unfortunately. In fact, gCF has only added to those conventions with no visible returns. Delving into the implementation of SampleService hopefully will give you the first glimpse of rewards to come.

A natural place to start the implementation is right at its centre, with a service context. Among other things, a service context manages configuration, scoping, security, and lifetime on behalf of the rest of the code. In gCF, you can have your own wih a few lines of code:

package org.acme.sample;
import ...

public class ServiceContext extends GCUBEServiceContext {
		
	/** {@inheritDoc} */
	protected String getJNDIName() {
                  return "acme/sample";
        }
	
}

As you can see, it is enough to:

  • inherit your service context from GCUBEServiceContext.
  • let the inherited code know how to act on your behalf by implementing getJNDIName() and returning the name of the JNDI section dedicated to the service ( remember?).

Note: This is a first instance of the template pattern: you complete the interface by exposing service-specific information which the inherited code uses to customise the work it carries out for you. It is simple, and it's used a lot within gCF.

The rest of the implementation could use the ServiceContext by instantiating it. For efficiency and exchange of data, however, it is more appealing to force the use of a single ServiceContext throughout the implementation. A simple way of doing this is as follows:

/** Single context instance, created eagerly */
private static ServiceContext singleton = new ServiceContext();
		
/** Prevents accidental creation of more instances */
private ServiceContext(){};

/** Returns cached instance */
public static ServiceContext getContext() {return singleton;}
  • a single context object is created the first time the class is loaded (singleton field declaration).
  • no other such object can be created by clients (private constructor).
  • the object is available on demand (getContext()).

Note: This is an instance of the singleton pattern and gCF cannot 'force' it as a contract upon your code without taking away too much of its simplicity. It just recommends it.

We will go back to the service context in the rest of the Primer, as its feature will be needed. Even If we didn't, it would already carry out important work during service startup, as you will see later on. More information on the role and features of a service context in gCF is here.


Next, the implementation of the port-type interface. This is also straightforward:

package org.acme.sample.stateless;
import ...

public class Stateless extends GCUBEStartupPortType {

	/** {@inheritDoc} */
	protected GCUBEServiceContext getServiceContext() {return ServiceContext.getContext();}
	
	public String about(String name) {		
		return ("Hello " + name + ", you have invoked service ")+this.getServiceContext().getName() + " ("+this.getServiceContext().getServiceClass() + ")";				
	}
}

but do please note:

  • the implementation extends GCUBEStartupPortType, which initialises the service context when the port-type is acivated in turn at container startup. One service port-type must always assume the role of 'fire-starter', in case you have many. We will refer to it as the startup port-type from now onwards.
  • the name and class of the service as defined in the service profile can be obtained from the service context, as can any piece of information in the profile if it proves useful for dynamic introspection.

By now the service location should look as follows:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|
|--schema
|---Stateless.wsdl
|
|-Dependencies
|--SampleService

Building & Deploying

By now, the definition of the service is completed: configuration, interfaces, and interface implementations are in place. We are ready to build executable artefacts from that definition and to deploy them within the gHN. There are two main artefacts we wish to produce: (i) a library of stubs through which clients can conveniently invoke the service, and (ii) a package of implementation components which is suitable for deployment. Both deployment and building require configuration. And although we will build the artefacts and then deploy them, we must first configure the deployment because this configuration must be included in the artefacts we build.

The Deployment Descriptor

We configure deployment so as to tell the gHN how to manage a Running Instance of the service, i.e. how to instantiate the port-type implementations and how to recognise and correctly dispatch calls to them. Here is the so-called deployment descriptor for SampleService:

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

The deployment descriptor ought to include a service section for each port-type. The following is worth noticing:

  • the name of the section tells the gHN how to distinguish calls to the port-type from calls to other port-types. It is the relative part of the URI at which the port-type is accessible, its endpoint. In particular, the port-type endpoint is formed by concatenating the name parameter with the endpoint of the gHN. Given that the endpoint of the gHN is
http:// <hostname>:<port>/wsrf/services

then the endpoint of our stateless port-type is:

http://<hostname>:<port>/wsrf/services/acme/sample/stateless
  • the className parameter names the class which implements the port-type. This is used by the gHN to create an instance of that class and forward to it all the calls for the port-type that it receives.
  • the wsdlFile parameter specifies the location of the port-type interface after deployment. We will be able to explain its value later on.
  • the scope parameter tells the gHN how many port-type instances it ought to create in order to serve requests. A value of Application indicates that we wish all requests to be handled by single object of the implementation class. Alternatively, a value of Request" indicates that we want a new port-type instance for each request.
  • the loadOnStartup parameter indicates that the gHN should create the port-type instance when it starts up, rather than when it receives the first request for it.

These two notes are very important:

Note: the name of the service section here must match that reported in the service profile and the JNDI configuration. Note: loadOnStartup must be set to true for the startup port-type.

Save the deployment descriptor in a file called deploy-server.wsdd under the etc folder of the service location:

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|---deploy-server.wsdd
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|
|--schema
|---Stateless.wsdl
|
|-Dependencies
|--SampleService

Build Properties

Build in gCore uses Ant and requires additional configuration. First, the location from which we will build the artefacts, the build location. The absolute path to this location is expected as the value of the environment variable BUILD_LOCATION, which for simplicity we take to be the location of the Eclipse workspace:

export BUILD_LOCATION = ...workspace location...

Then a small set of build properties:

package = org.acme.sample
lib.dir = SERVICELIBS/SampleService
wsdl.1 = Stateless
namespace.1=http://acme.org/sample

where:

  • package is the 'root package' of the service implementation, i.e. the first package in the implementation hierarchy which identifies the implementation unambiguously. org.acme.sample is assumed to be a unique identifierof implementations within ACME and, through Java package conventions, pretty much everywhere.
  • lib.dir is the path to your external custom dependency folder, relative to the build location.
  • wsdl.1 is the name of the service port-type, as used in the portType element of its WSDL.
  • namespace.1 is the namespace of the port-type, as specified in its WSDL.

Note: If you had many port-types, their order would have no arbitrary. It's only used to distinguish build properties in the configuration. Note: If you had different namespaces across WSDLS or imported/included XSD you would list them all, again in arbitrary order.

Save the configuration in a build.properties file and place it in the service configuration folder.

Finally, the ANT definition of the build process, the buildfile which makes use of the configuration just discussed. There is one tailored the structure of the service location adopted above and ships with gCore ($GLOBUS_LOCATION/share/gcore_tools/build-sample.xml). Just copy it to your service location and rename it to the standard build.xml.

|-SampleService
|--etc
|---profile.xml
|---deploy-jndi-config.xml
|---build.properties
|
|--src
|---org
|----acme
|-----sample
|------ServiceContext.java
|------stateless
|-------Stateless.java
|
|--schema
|---Stateless.wsdl
|
|
|- build.xml
|-Dependencies
|--SampleService


Build time.

This is normally a two-step process. We build first the service stubs and then the service ]implementation. In both cases, the building process will create temporary artefacts in a build folder under BUILD_LOCATION, though this folder will be erased before each new build.

Building the stubs

From the BUILD_LOCATION, type:

${BUILD_LOCATION}> ant -f <service root>/build.xml stubs

The result is the creation and deployment of a ${package}.stubs.jar file. The file is deployed in:

  • ${GLOBUS_LOCATION}/lib, where it will be found as a run-time dependency.
  • ${BUILD__LOCATION}SERVICELIBS/SampleService folder where it will be found as a compile-time dependency.

Note: You should now add ${BUILD__LOCATION}/SERVICELIBS/SampleService/org.acme.sample.stubs.jar to the build path of your Eclipse project (Project -> Properties -> Java Build Path -> Libraries -> Add Jars...). This allows Eclipse to resolve the compile-time dependencies to your own stubs.



Towards Deployment: the WSDD descriptor

We are finally ready to build. This is normally a two-step process. We build first the service stubs and then the service implementation. In both cases, the building process will create temporary artefacts in a build folder under BUILD_LOCATION, though this folder will be erased before each new build.

Building the stubs

From the BUILD_LOCATION, type:

${BUILD_LOCATION}> ant -f <service root>/build.xml stubs

The result is the creation and deployment of a ${package}.stubs.jar file. The file is deployed in:

  • ${GLOBUS_LOCATION}/lib, where it will be found as a run-time dependency.
  • ${BUILD__LOCATION}SERVICELIBS/SampleService folder where it will be found as a compile-time dependency.

Note: You should now add ${BUILD__LOCATION}/SERVICELIBS/SampleService/org.acme.sample.stubs.jar to the build path of your Eclipse project (Project -> Properties -> Java Build Path -> Libraries -> Add Jars...). This allows Eclipse to resolve the compile-time dependencies to your own stubs.

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

Logging it

A Test Client