Difference between revisions of "The Development Cycle"
(→The Deployment Descriptor) |
m (Some fix) |
||
Line 8: | Line 8: | ||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||
<Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | <Resource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
− | + | <ID/> | |
− | + | <Type>Service</Type> | |
− | + | <Profile> | |
− | + | <Description>A very simple gCube Service</Description> | |
− | + | <Class>Samples</Class> | |
− | + | <Name>SampleService</Name> | |
− | + | <Version>1.0.0</Version> | |
− | + | <Packages> | |
− | + | <Main> | |
− | + | <Name>SampleService-service</Name> | |
− | + | <Version>1.0.0</Version> | |
− | + | <Dependencies> | |
− | + | <Dependency> | |
− | + | <Service> | |
− | + | <Class>Samples</Class> | |
− | + | <Name>SampleService</Name> | |
− | + | <Version>1.0.0</Version> | |
− | + | </Service> | |
− | + | <Package>SampleService-stubs</Package> | |
− | + | <Version>1.0.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>SampleService-stubs</Name> | |
− | + | <Version>1.0.0</Version> | |
− | + | <Files><File>org.acme.sample.stubs.jar</File></Files> | |
− | + | </Software> | |
− | + | </Packages> | |
+ | </Profile> | ||
</Resource> | </Resource> | ||
</pre> | </pre> | ||
Line 95: | Line 96: | ||
* include an <code>environment</code> element which specifies the absolute path to the configuration folder of <code>SampleService</code>. Contrary to expectations, this is not our <code>etc</code> folder under the service location. It is in fact another configuration folder allocated to <code>SampleService</code> at the point of service deployment. This will become clearer [[The Development Cycle#Building & Deploying the Service|later]]. For the time being, we use the wildcard <code>@config.dir@</code> 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. | * include an <code>environment</code> element which specifies the absolute path to the configuration folder of <code>SampleService</code>. Contrary to expectations, this is not our <code>etc</code> folder under the service location. It is in fact another configuration folder allocated to <code>SampleService</code> at the point of service deployment. This will become clearer [[The Development Cycle#Building & Deploying the Service|later]]. For the time being, we use the wildcard <code>@config.dir@</code> 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 <code>@config.dir</code> 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. | + | '''Note:''' The wildcard <code>@config.dir@</code> 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 <code>SampleService</code>. 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. | So far, there is little in our configuration which is specific to <code>SampleService</code>. 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. | ||
Line 264: | Line 265: | ||
public class Stateless { | public class Stateless { | ||
− | |||
public String about(String name) { | public String about(String name) { | ||
return ("Hello " + name + ", you have invoked service ")+ServiceContext.getContext().getName() + | return ("Hello " + name + ", you have invoked service ")+ServiceContext.getContext().getName() + | ||
− | + | " ("+ServiceContext.getServiceContext().getServiceClass() + ")"; | |
} | } | ||
+ | |||
} | } | ||
</pre> | </pre> | ||
Line 333: | Line 334: | ||
<parameter name="scope" value="Application"/> | <parameter name="scope" value="Application"/> | ||
<parameter name="loadOnStartup" value="true"/> | <parameter name="loadOnStartup" value="true"/> | ||
− | + | </service> | |
− | + | ||
</deployment> | </deployment> | ||
Line 399: | Line 399: | ||
<pre> | <pre> | ||
package = org.acme.sample | package = org.acme.sample | ||
− | lib.dir = Dependencies/SampleService | + | lib.dir = Dependencies/SampleService |
wsdl.1 = Stateless | wsdl.1 = Stateless | ||
namespace.1=http://acme.org/sample | namespace.1=http://acme.org/sample |
Revision as of 11:25, 18 September 2008
Let us go through a quick tour of development with SampleService.
Contents
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/> <Type>Service</Type> <Profile> <Description>A very simple gCube Service</Description> <Class>Samples</Class> <Name>SampleService</Name> <Version>1.0.0</Version> <Packages> <Main> <Name>SampleService-service</Name> <Version>1.0.0</Version> <Dependencies> <Dependency> <Service> <Class>Samples</Class> <Name>SampleService</Name> <Version>1.0.0</Version> </Service> <Package>SampleService-stubs</Package> <Version>1.0.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>SampleService-stubs</Name> <Version>1.0.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 gCubeResource
, one of typeService
. Like all gCube Resources has anID
, 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 itsName
and specifies its membership to aClass
of functionally related gCube services. Classes are not pre-defined, and here we settle onSamples
. A free-formDescription
and aVersion
complement the preliminary description ofSampleService
.
-
SampleService
is physically comprised ofPackages
. AMain
package identifies the main artefact to emerge from the build of the service, aGARArchive
, 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 later).
- Another
Software
element identifies the secondary build artefact of the service, its own stubs. TheMain
package depends always upon its stubs and this dependency must be explicit.
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 under the service location:
|-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
element entirely dedicated to SampleService
, rather than more specific components yet to come. Furthermore, the element must:
- specify the
name
of the service. This is not particularly constrained but must distinguishSampleService
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 nameacme/sample
will suffice as a unique identifier.
- include an
environment
element which specifies the absolute path to the configuration folder ofSampleService
. Contrary to expectations, this is not ouretc
folder under the service location. It is in fact another configuration folder allocated toSampleService
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 under the service location:
|-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 (up to the choice of namespace prefixes and other stylistic matters of course):
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns:tns="<PORT-TYPE NAMESPACE>" targetNamespace="<PORT-TYPE NAMESPACE>" name="<PORT-TYPE 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 (as it really should).
- 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, we suggest you decide upon some name for the port-type and use it here.
- the
name
of theportType
element 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'
. We suggest you append this suffix to the name of the port-type you have chosen one bullet above.
The WSDL of our first port-type illustrates 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 nameStatelessPortType
of theportType
element. - we have defined a single operation
about
forStateless
, 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. Now the last important convention:
Note: The file in which you save the interface must have the same name as the port-type.
Accordingly, save the interface in a file Stateless.wsdl in the schema
folder under the service location:
|-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 theservice
element dedicated to the service in the JNDI configuration file.
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 service context by instantiating ServiceContext
. For efficiency and data sharing, however, it is more appealing to force the use of a single ServiceContext
object 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 (see
singleton
field declaration). - no other such object can be created by clients (see private constructor).
- the object is available on demand (see
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 can be found here.
Next, the implementation of the port-type interface. This is also straightforward:
package org.acme.sample.stateless; import ... public class Stateless { public String about(String name) { return ("Hello " + name + ", you have invoked service ")+ServiceContext.getContext().getName() + " ("+ServiceContext.getServiceContext().getServiceClass() + ")"; } }
but do please note:
- 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 nearly completed: most configuration, interfaces, and implementation components are all in place.
Some Java code is still missing actually. This is the code that we wish to generate automatically from the port-type interface so as to hide away the low-level details of communication between service and its clients. In a word, we need the service stubs.
Stubs are key offerings for clients, which can bind to instances of our service and invoke its methods with the simplicity (but not costs!) of local method calls. Stubs are key for the implementation of the service as well, which can also use them as high-level models of inputs and outputs.
Note: Of course, this is not the case for our Stateless
port-type, which manipulates plain strings and so does not need to depend on its own stubs. The port-types we will consider later on, however, will illustrate this dependency more obviously.
Now, assume for a moment we have somehow generated the stub code. Would we be done then? Well, we would have to compile both stub and service code, of course. And then we would have to hand things over to the gHN, i.e. deploy them in key locations under the gCore location. Code generation, packaging, and deployment are the main steps of an overall process of build which makes the service operational within the gHN.
In web service development, the build process is far from trivial and requires the coordinated use of multiple utilities for code generation, compilation, packaging, and deployment. Fortunately, we can simplify the execution of the build process using Ant as our build technology. Ant lets us define the build process declaratively in a buildfile, and to outsource its execution to the generic ANT engine. Even better news, there is a pre-defined buildfile which ships with gCore ($GLOBUS_LOCATION/share/gcore_tools/build-sample.xml
) and has been designed to work seamlessly for any gCube service which follows the structure recommended above.
All you need to do is configure it, of course.
The Deployment Descriptor
A first piece of configuration to produce concerns deployment. We configure deployment so as to tell the gHN how to manage a Running Instance of the service. In particular, we need to tell the gHN how to instantiate the port-type implementations and how to recognise and correctly dispatch calls to them. The result is a deployment descriptor. It will be embedded in the build artefacts to be deployed in the gHN.
Here is a 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/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> </deployment>
The deployment descriptor ought to include a service
element for each port-type. The following is worth noticing:
- the
name
attribute 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 value of thename
parameter, i.e. its relative endpoint, with the endpoint of the gHN itself. Given that the endpoint of the gHN has always the following form:
http:// <hostname>:<port>/wsrf/services
then the endpoint of the Stateless
port-type will be the following:
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 ofApplication
indicates that we wish all requests to be handled by single object of the implementation class. Alternatively, a value ofRequest
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 as soon as it starts up, rather than when it receives the first request for it. This is usually the preferred option as it does not force startup delays upon the first client. Most importantly: gCF requires that at least one of its port-types starts up along with the gHN, i.e. thatloadOnStartup
is set totrue
for it. Otherwise, the service will not initialise correctly and will not be able to serve requests correctly.
Now, notice the following conventions:
- the
name
of theservice
element must match that in thePortType
element of the service profile.
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
The Build Properties
Once you have copied the pre-defined buildfile ($GLOBUS_LOCATION/share/gcore_tools/build-sample.xml
) into the service location and renamed it to the classic build.xml
, you have to configure it.
First of all, the buildfile needs to know the absolute path to the location from which you will build the artefacts, the build location. This is expected as the value of the environment variable BUILD_LOCATION
. For convenience, we use the location of the Eclipse workspace as our build location:
export BUILD_LOCATION = ...workspace location...
Second, the buildfile requires you to specify a small set of build properties:
package = org.acme.sample lib.dir = Dependencies/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. The buildfile uses this property to guarantee unique names to the artefacts it produces. As we shall see, this guarantees the they can be safely deployed without any clashes with other service implementations. Let us assume that
org.acme.sample
identifies uniquely the implementation ofSampleService
within ACME and, through Java package conventions, pretty much everywhere.
- lib.dir is the path to the custom dependency folder, relative to the build location. The buildfile needs to know where are your custom dependencies so as to put them on the classpath during compilation.
- wsdl.1 is the name of the file which containes the WSDL interface of the first port-type. The buildfile uses this property to locate the interface under the service location, process it, and generate stubs from it.
- namespace.1 is the namespace of the port-type, as specified in its WSDL. The buildfile uses this property to determine the Java package of the stubs generated from the WSDL interface.
Note: If you had many port-types, you would list them all in arbitrary order (wsdl.2,wsdl.3,...).
Note: If you had different namespaces across WSDLs and/or auxiliary XSDs, you would also list them all in arbitrary order.
Note: By default, all namespaces are mapped to ${package}/stubs
. In our case, they would be placed org.gcube.sample.stubs
. If you wish to map different namespaces onto different packages, you can do so with optional properties package.<n>
. The buildfile will consider its value when generating stubs for interface elements in the namspace namespace.<n>
. For example, if you specify the property:
package.1=stateless
then the buildfile would create stubs for the Stateless
port-type under org.ample.stubs.stateless
.
Save the configuration in a build.properties file and place it in the etc
folder under the service location.
|-SampleService |--etc |---profile.xml |---deploy-jndi-config.xml |---deploy-server.wsdd |---build.properties | |--src |---org |----acme |-----sample |------ServiceContext.java |------stateless |-------Stateless.java | |--schema |---Stateless.wsdl | | |--build.xml | |-Dependencies |--SampleService
Building & Deploying Stubs
Build time. From the build location, type:
ant -f SampleService/build.xml stubs
This invokes ANT and tells it to process the buildfile of SampleService
, specifically the instructions for building the stub library which are grouped under the target stubs
.
If all goes according to plans, the buildfile:
- generates stubs source code from the port-type interfaces, compiles it, and packages it in a
org.acme.sample.stubs.jar
file. - deploys
org.acme.sample.stubs.jar
in two locations:${GLOBUS_LOCATION}/lib
under the gCore location, where it will be found as a run-time dependency of the service. Everything in here is on the classpath when the gHN is running.${BUILD_LOCATION}/Dependencies/SampleService
, where it can be found as a compile-time dependency of the service. This is why we specified the custom dependency location in the build properties.
Note: The build process generates also three additional port-type interfaces from the original one: Stateless_bindings.wsdl
, Stateless_flattened.wsdl
, and Stateless_service.wsdl
. For the time being, you do not need to be overly concerned with these, but simply notice that the latter, Stateless_service.wsdl
, is the port-type interface pointed at from the deployment descriptor of the service.
Note: The build process creates a build folder under the build location, clearing it if it already exists. It then uses this folder to organise all the elements required for the build (code, configuration, interfaces). It copies most elements from the service location, of course, but it complements them with other elements which are cross-service and ship with gCore. In most cases, you should not concern yourself with the contents of the build folder but it is good to know who creates it and why.
Note: Sometimes you might want to generate the stubs or go as far as to package them, but not deploy them. Say you want to make sure they are generated correctly, and perhaps have a peek inside the jar file, but are not ready to replace the stub library previously deployed. In these cases, you can invoke the buildStubs
or the jarStubs
targets:
ant -f SampleService/build.xml buildStubs ...generates and compile stubs but does not package them... ant -f SampleService/build.xml jarStubs ...generates and packages stubs but does not deploy them...
Note: When we will build the service, the buildfile will automatically resolve the dependency of SampleService
to its own stubs from the custom dependency folder. Eclipse needs instead to be told explicitly. For this, you should add $BUILD__LOCATION/Dependencies/SampleService/org.acme.sample.stubs.jar
to the build path of your Eclipse project (Properties -> Java Build Path -> Libraries -> Add Jars...). The same is true for any other custom dependency, of course. For stubs, however, the good news is that we do not have to manually copy the library into the custom dependency folder.
Note: When need arises, you can undeploy the stubs by typing:
ant -f SampleService/build.xml undeployStubs
Building & Deploying the Service
From the build location, type:
ant -f SampleService/build.xml
which points implicitly to the build instructions grouped under the target deployService
. If all goes according to plans, the buildfile:
- compiles the service implementation and packages it in a
org.acme.sample.jar
file - further packages
org.acme.sample.jar
along with service configuration and port-type interfaces - both manually and automatically generated - in aorg.acme.sample.gar
file. - deploys
org.acme.sample.gar
in the gHN, primarily by copying:-
org.acme.sample.jar
- and any other dependency$BUILD_LOCATION/Dependencies/SampleService
, but not the service's own stub library - in the$GLOBUS_LOCATION/lib
folder under the gCore location. - all the service configuration under the service location in a
$GLOBUS_LOCATION/etc/org.acme.sample
folder under the gCore location. - all the port-type interfaces in the GAR file in a
$GLOBUS_LOCATION/share/schema/org.acme.sample
folder under the gCore location.
-
Note: As with the stubs (and in fact more commonly), sometimes you might want to compile the service implementation, perhaps package it, and even go as far as to produce the GAR file, but not deploy it. In these cases, you can invoke the buildService
, jarService
, or garService
targets:
ant -f SampleService/build.xml buildService ...compiles the service implementation, but does not package it ant -f SampleService/build.xml jarService ...compiles and packages the service implementation but does create a GAR ant -f SampleService/build.xml garService ...compiles and packages the service implementation, creates a GAR but does not deploy it
If you then want to deploy a still undeployed GAR, you can pass it to a dedicated gCore script. From the build location, type:
gcore-deploy-service SampleService/org.acme.sample.gar
Note: When need arises, you can also undeploy the service by typing:
ant -f SampleService/build.xml undeployService.
Alternatively, you can undeploy the service by passing the name of its GAR to a dedicated gCore script:
gcore-undeploy-service org.acme.sample
Starting the gHN
Believe it or not, our 'quick' tour of service development is over.
We can now start the gHN and make sure our SampleService
is alive and kicking in it.
Making sure of it means to look into the logs emitted by the gHN and the code which is deployed in it.
There are essentially two logfiles you should be prepared to look into:
-
nohup.out
offers a brief report of the endpoints of the services which are managed by the gHN and highlights any early startup problem.SampleService
's single endpoint ought to appear in the list and no obvious error message should be visible.
-
container.log
is the main gHN log and contains a trace of the activities of all the services and components which have cared to leave it. Now, by inheriting from core gCF classes, the implementation ofSampleService
will leave a trace of its main activities in the gHN log. However, you need to configure logging in order to show it.
gCore uses Log4j as its logging technology and expects its configuration in $GLOBUS_LOCATION/container-log4j.properties
. For now, you need only to specify that all the logs which are produced by code in the org.acme.sample
package ought to be revealed. This is easily done:
# Display any warnings generated by our code log4j.category.org.globus=WARN log4j.category.org.gcube=DEBUG ... log4j.category.org.acme.sample=DEBUG
To start the gHN, now type:
gcore-start-container
or, to enable full log in case of early startup problems:
gcore-start-container -debug
In both cases, any previous running instance of the gHN will automatically killed.
If all goes well, your nohup.out
log should match this template:
Starting SOAP server at: http://<hostname>:<port>/wsrf/services/ With the following services: [1] .... ... ... [...]: http://<hostname>:<port>/wsrf/services/acme/sample/stateless ... ...
In particular, it should show the full endpoint of the Stateless
port-type of SampleService
.
Similarly, container.log
should include the trace of the startup activities of SampleService
:
2008-04-11 16:25:41,562 INFO contexts.GHNContext [main,info:108] GHNContext: Initialising GHN 2008-04-11 16:25:41,588 INFO contexts.GHNContext [main,info:108] GHNContext: Creating the resource 2008-04-11 16:25:41,616 DEBUG contexts.GHNContext [main,debug:84] GHNContext: Parsing GHN from /Users/fabio/workspace/gcube/GCORE_DEV/config/GHNProfile.xml 2008-04-11 16:25:41,794 DEBUG builders.GHNBuilder [main,debug:84] GHNBuilder: Updating GHN 2008-04-11 16:25:41,795 DEBUG contexts.GHNContext [main,debug:84] GHNContext: Analysing memory status... ... ... 2008-04-11 16:25:43,146 INFO sample.ServiceContext [main,info:108] SampleService: Initialising Running Instance 2008-04-11 16:25:43,149 DEBUG sample.ServiceContext [main,debug:84] SampleService: Parsing RI from ....(omitted) 2008-04-11 16:25:43,155 DEBUG builders.RIBuilder [main,debug:84] SampleService: Updating Running instance 2008-04-11 16:25:43,164 INFO sample.ServiceContext [main,info:108] SampleService: Running Instance has been updated 2008-04-11 16:25:43,165 INFO sample.ServiceContext [main,info:108] SampleService: Managing security with a GCUBEServiceSecurityManagerImpl 2008-04-11 16:25:43,166 INFO contexts.GHNContext [main,info:108] GHNContext: Registering service Samples SampleService... ... ... 2008-04-11 16:25:43,168 INFO sample.ServiceContext [main,info:108] SampleService: Running Instance has moved to status: initialised 2008-04-11 16:25:43,179 INFO sample.ServiceContext [Thread-24,info:108] SampleService: Running Instance has been updated ... ... 2008-04-11 16:25:43,271 INFO sample.ServiceContext [Thread-27,info:108] SampleService: ready event invoked on SampleService 2008-04-11 16:25:43,272 INFO sample.ServiceContext [Thread-27,info:108] SampleService: Running Instance has moved to status: ready 2008-04-11 16:25:43,281 INFO sample.ServiceContext [Thread-27,info:108] SampleService: Running Instance has been updated ... ...
Without going unnecessarily into details, the logs show the various stages of initialisation undergone by SampleService
, from its initialisation and activation to the point in which the service is ready to receive requests at its port-types. Incidentally, this log shows the amount of work which gCF has already carried out on your behalf.
A Minimal Client
We conclude the tour with an invocation of SampleService
. The simple client below exemplifies the use of stubs to interact with a Running Instance of SampleService
and invoke the about()
method of its Stateless
port-type:
package org.acme.sample.tests; import ... public class StatelessTest { public static void main(String[] args) throws Exception { EndpointReferenceType endpoint = new EndpointReferenceType(); endpoint.setAddress(new AttributedURI(args[0])); StatelessPortType stub = new StatelessServiceAddressingLocator().getStatelessPortTypePort(endpoint); stub=GCUBERemotePortTypeContext.getProxy(stub,GCUBEScope.getScope(args[1])); System.out.println(stub.about(args[2])); } }
Most code in StatelessTest
is fairly standard, but we comment it here in order to highlight what is instead less conventional.
- We first model the endpoint of the
Stateless
port-type of a particular Running Instance ofSampleService
:
EndpointReferenceType endpoint = new EndpointReferenceType(); endpoint.setAddress(new AttributedURI(args[0]));
EndpointReferenceType
is our model of the endpoint and we expect it to be initialised with a command-line argument of the form:
http://<hostname>:<port>/wsrf/services/acme/sample/stateless
- Next, we obtain a stub of the remote port-type at that endpoint:
StatelessPortType stub = new StatelessServiceAddressingLocator().getStatelessPortTypePort(endpoint);
StatelessPortType
and ServiceAddressingLocator
are both part of the stubs library of SampleService
. The second returns instances of the first bound to specific endpoints.
- Next, we uses gCF facilities to obtain a proxy for the stub which knows how to make gCube calls, i.e. annotate calls with information which identifies the target service of future calls and the scope in which they will be made:
stub=GCUBERemotePortTypeContext.getProxy(stub,GCUBEScope.getScope(args[1]));
This is an unusual task in web service development, and it is offered in gCF as a transparency over gCube-specific requirements. All you need to know at this stage is that the resulting proxy is indistinguishable from the original stub, just more 'intelligent' in its behaviour because it can identify the target service from information included in the stub library. The intended scope is of course something we need to specify and we do so with a GCUBEScope
object obtained by parsing a further command-line argument (e.g. /gcube/devsec
).
- Finally, we invoke the
about()
method of the remote port-type against the stub proxy, using a third command-line argument for the expected input (e.g.John Doe
) and displaying the oputput.
System.out.println(stub.about(args[2]));
StatelessTest
depends on standard gCore libraries (e.g. for EndpointReferenceType
, GCUBERemotePortTypeContext
, and GCUBEScope
) as well as on the stub library of SampleService
. Of course, these dependencies must be on the classpath during compilation and execution. As extra requirements for client, gCF requires that the gCore location be also on the classpath and that the environment variable GLOBUS_LOCATION
be set to it. How you accommodate these requirement depends on whether you wish to compile and run StatelessTest
from the shell or from within Eclipse.
If you wish to compile and run StatelessTest
from the shell - and you are working with the gHN which hosts SampleService
- then all you need to do is to 'source' a gCore script which does it for you:
source $GLOBUS_LOCATION/bin/gcore-load-env
If instead you wish to compile and run from an Eclipse project, then things get a bit more convoluted. First of all, you need to make sure the project has dependencies to the user libraries GCORELIBS
and SAMPLESERVICEDEPS
defined above. Of course, you can always place StatelessTest
under the service location, where dependencies to those libraries are already defined:
|-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 | | |--build.xml | |-Dependencies |--SampleService
Next you have to configure the run configuration of your client by specifying the gCore location both as the value of the GLOBUS_LOCATION
environment variable and as a classpath element. The most reusable way of approaching this task is to:
- define once and for all a string variable
GLOBUS_LOCATION
at the workspace level and set it to the gCore location (Preferences->Run/Debug->String Substitution->New...). - add an environment variable
GLOBUS_LOCATION
to the run configuration of all your clients and set it to the string variable just defined (Open Run Dialog...->Environment->New...->Variables) - add the value of the string variable as a classpath element to the run configuration of all your clients (Open Run Dialog...->Classpath->User Entries->Advanced...->Add Variable String)
Whether you are running the client from the shell or from within Eclipse, the output to expect (under default gCore settings) is the following:
Hello John Doe, you have invoked service SampleService (Samples)