Difference between revisions of "The Development Cycle"

From GCube System
Jump to: navigation, search
(JNDI configuration)
(Building & Deploying)
 
(29 intermediate revisions by 4 users not shown)
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></ID>
+
    <ID/>
<Type>Service</Type>
+
    <Type>Service</Type>
<Profile>
+
    <Profile>
<Description>A very simple gCube Service</Description>
+
        <Description>A very simple gCube Service</Description>
<Class>Samples</Class>
+
        <Class>Samples</Class>
<Name>SampleService</Name>
+
        <Name>SampleService</Name>
<Version>1.0</Version>
+
        <Version>1.0.0</Version>
<Packages>
+
        <Packages>
<Main>
+
            <Main>
<Name>Main</Name>
+
                <Name>SampleService-service</Name>
<Version>1.0</Version>
+
                <Version>1.0.0</Version>
<Dependencies>
+
                <Dependencies>
<Dependency>
+
                    <Dependency>
<Service>
+
                        <Service>
<Class>Samples</Class>
+
                            <Class>Samples</Class>
<Name>SampleService</Name>
+
                            <Name>SampleService</Name>
</Service>
+
                            <Version>1.0.0</Version>
<Package>Stubs</Package>
+
                        </Service>
<Version>1.0</Version>
+
                        <Package>SampleService-stubs</Package>
<Scope level="GHN"/>
+
                        <Version>1.0.0</Version>
<Optional>false</Optional>
+
                        <Scope level="GHN"/>
</Dependency>
+
                        <Optional>false</Optional>
</Dependencies>
+
                    </Dependency>
<GARArchive>org.acme.sample.gar</GARArchive>
+
                </Dependencies>
<PortType>
+
                <GARArchive>org.acme.sample.gar</GARArchive>
<Name>acme/sample/stateless</Name>
+
                <PortType>
<WSDL>...</WSDL>
+
                    <Name>acme/sample/stateless</Name>
</PortType>
+
                    <WSDL/>
</Main>
+
                </PortType>
<Software>
+
            </Main>
<Description>Describes port-type stubs</Description>
+
            <Software>
<Name>Stubs</Name>
+
                <Description>Describes port-type stubs</Description>
<Version>1.0</Version>
+
                <Name>SampleService-stubs</Name>
<Files><File>org.acme.sample.stubs.jar</File></Files>
+
                <Version>1.0.0</Version>
</Software>
+
                <Files><File>org.acme.sample.stubs.jar</File></Files>
</Packages>
+
            </Software>
</Profile>
+
        </Packages>
 +
    </Profile>
 
</Resource>
 
</Resource>
 
</pre>
 
</pre>
Line 58: Line 59:
 
* Another <code>Software</code> element identifies the secondary build artefact of the service, its own stubs. The <code>Main</code> package depends always upon its stubs and this dependency must be explicit.
 
* Another <code>Software</code> element identifies the secondary build artefact of the service, its own stubs. The <code>Main</code> 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 [[#Building & Deploying |jump ahead]].
+
[[Image:blue.jpg]] If the notion of build artefact is not obvious to you, bide your time for a little longer or [[#Building & Deploying |jump ahead]].
  
 
Save the profile in a file called '''profile.xml''' and place it in the <code>etc</code> folder under the service location:
 
Save the profile in a file called '''profile.xml''' and place it in the <code>etc</code> folder under the service location:
Line 75: Line 76:
 
== A Tiny JNDI ==
 
== A Tiny JNDI ==
  
Next we move to the configuration of the code itself, for which we use [http://java.sun.com/products/jndi/ JNDI] technology. Again, the details and granularity of JNDI configuration are discussed [[Configuration Management#Profiles, JNDIs & Descriptors|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:
+
Next we move to the configuration of the code itself, for which we use [http://java.sun.com/products/jndi/ JNDI] technology. Again, the details and granularity of JNDI configuration are discussed [[Configuration_Components#The_JNDI_Configuration|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:
  
 
<pre>
 
<pre>
Line 89: Line 90:
  
  
Essentially, the configuration must have a <code>service</code> element entirely dedicated to <code>SampleService</code>, rather than more specific components yet to come. Furthermore, the element must:
+
[[Image:red.jpg]] Essentially, the configuration must have a <code>service</code> element entirely dedicated to <code>SampleService</code>, rather than more specific components yet to come. Furthermore, the element:
  
* specify the <code>name</code> of the service. This is not particularly constrained but must distinguish <code>SampleService</code> 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 <code>acme/sample</code> will suffice as a unique identifier.
+
[[Image:red.jpg]] '''must''' specify the <code>name</code> of the service. This is not particularly constrained but must distinguish <code>SampleService</code> 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 <code>acme/sample</code> will suffice as a unique identifier.
  
* 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.  
+
[[Image:red.jpg]] '''must''' 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.
+
[[Image:blue.jpg]] 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 105: Line 106:
 
|--etc
 
|--etc
 
|---profile.xml
 
|---profile.xml
|---deploy-jndi-config.xml
+
|---deploy-jndi-config.xml   [new]
 
|--src
 
|--src
 
|--schema
 
|--schema
Line 145: Line 146:
 
* the <code>name</code> 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 <code>name</code> 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 <code>name</code> of the <code>portType</code> 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 <code>'portType'</code>. We suggest you append this suffix to the name of the port-type you have chosen one bullet above.
+
[[Image:red.jpg]] the <code>name</code> of the <code>portType</code> 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 <code>'portType'</code>. 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:
 
The WSDL of our first port-type illustrates these conventions:
Line 190: Line 191:
 
Hard to imagine a simpler port-type, really. Now the last important convention:
 
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.
+
[[Image:red.jpg]] 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 <code>schema</code> folder under the service location:
 
Accordingly, save the interface in a file '''Stateless.wsdl''' in the <code>schema</code> folder under the service location:
Line 203: Line 204:
 
|
 
|
 
|--schema
 
|--schema
|---Stateless.wsdl
+
|---Stateless.wsdl       [new]
 
|
 
|
 
|-Dependencies
 
|-Dependencies
Line 234: Line 235:
 
* let the inherited code know how to act on your behalf by implementing <code>getJNDIName()</code> and returning the name of the <code>service</code> element dedicated to the service in the [[#A Tiny JNDI |JNDI configuration file]].
 
* let the inherited code know how to act on your behalf by implementing <code>getJNDIName()</code> and returning the name of the <code>service</code> element dedicated to the service in the [[#A Tiny JNDI |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.
+
 
 +
[[Image:blue.jpg]] 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 <code>ServiceContext</code>. For efficiency and data sharing, however, it is more appealing to force the use of a single <code>ServiceContext</code> object throughout the implementation. A simple way of doing this is as follows:
 
The rest of the implementation could use the service context by instantiating <code>ServiceContext</code>. For efficiency and data sharing, however, it is more appealing to force the use of a single <code>ServiceContext</code> object throughout the implementation. A simple way of doing this is as follows:
Line 253: Line 256:
 
* the object is available on demand (see <code>getContext()</code>).
 
* the object is available on demand (see <code>getContext()</code>).
  
'''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.
+
 
 +
[[Image:blue.jpg]] 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 [[#Starting the gHN|later on]]. More information on the role and features of a service context in gCF can be found [[Contexts#Service Contexts|here]].
 
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 [[#Starting the gHN|later on]]. More information on the role and features of a service context in gCF can be found [[Contexts#Service Contexts|here]].
Line 263: Line 268:
 
import ...
 
import ...
  
public class Stateless extends GCUBEStartupPortType {
+
public class Stateless extends GCUBEPortType {
  
 
/** {@inheritDoc} */
 
/** {@inheritDoc} */
Line 277: Line 282:
 
but do please note:
 
but do please note:
  
* the implementation extends <code>GCUBEStartupPortType</code>, 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.
+
[[Image:red.jpg]] the implementation extends <code>GCUBEPortType</code>, which ensures the correct initialisation of the port-type at service startup. All the port-type implementations of your services ought to do the same.
  
* the name and class of the service as defined in the [[#My First Profile|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.
+
[[Image:blue.jpg]] the name and class of the service as defined in the [[#My First Profile|service profile]] can be obtained from the service context, as can any piece of information in the profile that proves useful for dynamic introspection.
  
 
By now the service location should look as follows:
 
By now the service location should look as follows:
Line 293: Line 298:
 
|----acme
 
|----acme
 
|-----sample
 
|-----sample
|------ServiceContext.java
+
|------ServiceContext.java   [new]
|------stateless
+
|------stateless                    
|-------Stateless.java
+
|-------Stateless.java       [new] 
 
|
 
|
 
|--schema
 
|--schema
Line 302: Line 307:
 
|-Dependencies
 
|-Dependencies
 
|--SampleService
 
|--SampleService
</pre>
 
 
== JNDI configuration ==
 
We now extend the JNDI configuration file to add the port-type configuration.
 
<pre>
 
<?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" />
 
</service>
 
 
<service name="acme/sample/stateless">
 
<environment
 
name="name"
 
value="StatelessPortType"
 
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>
 
</jndiConfig>
 
</pre>
 
Notes:
 
* the <code>service</code> element named ''acme/sample/stateless'' groups the configuration of our stateless port-type; the element can be omitted if no configuration is required for the port-type
 
* the ''name'' property is optional and it is used by the framework for logging purposes
 
* the ''displayServiceProfile'' and ''displayNodeProfile'' are two  example of service-specific properties that can be programmatically read within the service code
 
 
Save the new configuration in the file called '''deploy-jndi-config.xml''' and place it in the etc folder under the service location:
 
<pre>
 
|-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
 
 
 
</pre>
 
</pre>
  
 
== Building & Deploying ==
 
== Building & Deploying ==
  
By now, the definition of the service is nearly completed: most configuration, interfaces, and implementation components are all in place.
+
The definition of the service is nearly complete: most configuration, interfaces, and implementation components are 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.  
+
Some Java code is still missing however. 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 ''stubs'' for our service.  
  
'''Note:''' Of course, this is not the case for our <code>Stateless</code> 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.
+
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 conventional 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. Of course, this is not the case for our <code>Stateless</code> 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.  
 
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 [http://ant.apache.org/ 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 (<code>$GLOBUS_LOCATION/share/gcore_tools/build-sample.xml</code>) and has been designed to work seamlessly for any gCube service which follows the structure recommended  [[The_ACME_Project#Structuring the Service|above]].  
+
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 [http://ant.apache.org/ 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 (<code>$GLOBUS_LOCATION/share/gcore_tools/sample-build.xml</code>) and has been designed to work seamlessly for any gCube service which follows the structure recommended  [[The_ACME_Project#Structuring the Service|above]].  
  
 
All you need to do is configure it, of course.
 
All you need to do is configure it, of course.
Line 409: Line 341:
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="scope" value="Application"/>
 
         <parameter name="loadOnStartup" value="true"/>
 
         <parameter name="loadOnStartup" value="true"/>
        <parameter name="securityDescriptor" value="@config.dir@/security_descriptor.xml"/>
+
    </service>
    </service>
+
 
        
 
        
 
</deployment>
 
</deployment>
 
</pre>
 
</pre>
  
The deployment descriptor ought to include a <code>service</code> element for each port-type. The following is worth noticing:
+
[[Image:red.jpg]] The deployment descriptor <code>must</code> include a <code>service</code> element for each port-type.  
 +
 
 +
The following is worth noticing:
  
 
* the <code>name</code> 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 the <code>name</code> 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:  
 
* the <code>name</code> 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 the <code>name</code> 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:  
Line 433: Line 366:
 
* the <code>loadOnStartup</code> 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.
 
* the <code>loadOnStartup</code> 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.
  
 +
Now, notice the following rules:
  
Now, notice the following conventions:
+
[[Image:red.jpg]] the <code>name</code> of the <code>service</code> element '''must''' match that in the <code>PortType</code> element of  the [[#My First Profile|service profile]].
 
+
* the <code>name</code> of the <code>service</code> element '''must''' match that in the <code>PortType</code> element of  the [[#My First Profile|service profile]].
+
 
+
* <code>loadOnStartup</code> '''must''' be set to <code>true</code> for the [[#A Simple Implementation|startup port-type]].
+
  
 +
[[Image:red.jpg]] <code>loadOnStartup</code> '''must''' be set to <code>true</code> for at least one port-type.
  
 
Save the deployment descriptor in a file called '''deploy-server.wsdd''' under the <code>etc</code> folder of the service location:
 
Save the deployment descriptor in a file called '''deploy-server.wsdd''' under the <code>etc</code> folder of the service location:
Line 448: Line 379:
 
|---profile.xml
 
|---profile.xml
 
|---deploy-jndi-config.xml
 
|---deploy-jndi-config.xml
|---deploy-server.wsdd
+
|---deploy-server.wsdd       [new]
 
|
 
|
 
|--src
 
|--src
Line 477: Line 408:
 
<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
Line 484: Line 415:
 
where:
 
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 <code>org.acme.sample</code> identifies uniquely the implementation of <code>SampleService</code> within ACME and, through Java package conventions, pretty much everywhere.
+
* <code>package</code> 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 <code>org.acme.sample</code> identifies uniquely the implementation of <code>SampleService</code> within ACME and, through Java package conventions, pretty much everywhere.
  
* '''lib.dir''' is the path to the [[The ACME Project|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.
+
* <code>lib.dir</code> is the path to the [[The ACME Project|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.  
+
* <code>wsdl.1</code> 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.
  
'''Note:''' If you had many port-types, you would list them all in arbitrary order (''wsdl.2'',''wsdl.3'',...).  
+
* <code>namespace.1</code> 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 different namespaces across WSDLs and/or auxiliary XSDs, you would also list them all in arbitrary order.
+
[[Image:blue.jpg]]  If you had many port-types, you would list them all in arbitrary order (''wsdl.2'',''wsdl.3'',...).  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 <code>${package}/stubs</code>. In our case, they would be placed <code>org.gcube.sample.stubs</code>. If you wish to map different namespaces onto different packages, you can do so with optional properties <code>package.<n></code>. The buildfile will consider its value when generating stubs for interface elements in the namspace <code>namespace.<n></code>. For example, if you specify the property:
+
[[Image:blue.jpg]] By default, all namespaces are mapped to <code>${package}/stubs</code>. In our case, they would be placed <code>org.gcube.sample.stubs</code>. If you wish to map different namespaces onto different packages, you can do so with optional properties <code>package.<n></code>. The buildfile will consider its value when generating stubs for interface elements in the namspace <code>namespace.<n></code>. For example, if you specify the property:
  
 
  package.1=stateless
 
  package.1=stateless
Line 510: Line 439:
 
|---deploy-jndi-config.xml
 
|---deploy-jndi-config.xml
 
|---deploy-server.wsdd
 
|---deploy-server.wsdd
|---build.properties
+
|---build.properties         [new]
 
|
 
|
 
|--src
 
|--src
Line 543: Line 472:
 
**<code>${BUILD_LOCATION}/Dependencies/SampleService</code>, where it can be found as a compile-time dependency of the service. This is why we specified the custom dependency location in the [[#The Build Properties|build properties]].
 
**<code>${BUILD_LOCATION}/Dependencies/SampleService</code>, where it can be found as a compile-time dependency of the service. This is why we specified the custom dependency location in the [[#The Build Properties|build properties]].
  
'''Note:''' The build process generates also three additional port-type interfaces from the original one: <code>Stateless_bindings.wsdl</code>, <code>Stateless_flattened.wsdl</code>, and <code>Stateless_service.wsdl</code>. For the time being, you do not need to be overly concerned with these, but simply notice that the latter, <code>Stateless_service.wsdl</code>, is the port-type interface pointed at from the [[#The Deployment Descriptor|deployment descriptor]] of the service.
+
[[Image:blue.jpg]] The build process generates also three additional port-type interfaces from the original one: <code>Stateless_bindings.wsdl</code>, <code>Stateless_flattened.wsdl</code>, and <code>Stateless_service.wsdl</code>. For the time being, you do not need to be overly concerned with these, but simply notice that the latter, <code>Stateless_service.wsdl</code>, is the port-type interface pointed at from the [[#The Deployment Descriptor|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.
+
[[Image:blue.jpg]] 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 <code>buildStubs</code> or the <code>jarStubs</code> targets:
+
[[Image:blue.jpg]] 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 <code>buildStubs</code> or the <code>jarStubs</code> targets:
  
 
  ant -f SampleService/build.xml ''buildStubs''  ...generates and compile stubs but does not package them...
 
  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...  
 
  ant -f SampleService/build.xml ''jarStubs''      ...generates and packages stubs but does not deploy them...  
  
'''Note:''' When we will [[#Building & Deploying the Service|build the service]], the buildfile will automatically resolve the dependency of <code>SampleService</code> to its own stubs from the custom dependency folder. Eclipse needs instead to be told explicitly. For this, you should add <code>$BUILD__LOCATION/Dependencies/SampleService/org.acme.sample.stubs.jar</code> 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.
+
[[Image:red.jpg]] When we will [[#Building & Deploying the Service|build the service]], the buildfile will automatically resolve the dependency of <code>SampleService</code> to its own stubs from the custom dependency folder. Eclipse needs instead to be told explicitly. For this, you should add <code>$BUILD__LOCATION/Dependencies/SampleService/org.acme.sample.stubs.jar</code> 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:
+
[[Image:blue.jpg]] When need arises, you can undeploy the stubs by typing:
  
 
  ant -f SampleService/build.xml undeployStubs
 
  ant -f SampleService/build.xml undeployStubs
Line 574: Line 503:
  
  
'''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 <code>buildService</code>, <code>jarService</code>, or <code>garService</code> targets:
+
[[Image:blue.jpg]] 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 <code>buildService</code>, <code>jarService</code>, or <code>garService</code> targets:
  
 
  ant -f SampleService/build.xml ''buildService''      ...compiles the service implementation, but does not package it
 
  ant -f SampleService/build.xml ''buildService''      ...compiles the service implementation, but does not package it
Line 584: Line 513:
 
  gcore-deploy-service SampleService/org.acme.sample.gar
 
  gcore-deploy-service SampleService/org.acme.sample.gar
  
'''Note:''' When need arises, you can also undeploy the service by typing:
+
[[Image:blue.jpg]] When need arises, you can also undeploy the service by typing:
  
 
  ant -f SampleService/build.xml undeployService.
 
  ant -f SampleService/build.xml undeployService.
Line 749: Line 678:
 
|-------Stateless.java
 
|-------Stateless.java
 
|------tests
 
|------tests
|-------StatelessTest.java
+
|-------StatelessTest.java       [new]
 
|
 
|
 
|--schema
 
|--schema
Line 763: Line 692:
 
Next you have to configure the ''run configuration'' of your client by specifying the gCore location both as the value of the <code>GLOBUS_LOCATION</code> environment variable and as a classpath element. The most reusable way of approaching this task is to:
 
Next you have to configure the ''run configuration'' of your client by specifying the gCore location both as the value of the <code>GLOBUS_LOCATION</code> 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'' <code>GLOBUS_LOCATION</code> at the workspace level and set it to the gCore location (''Preferences->Run/Debug->String Substitution->New...'').
+
* define once and for all a ''string variable'' <code>GLOBUS_LOCATION</code> at the workspace level and set it to the gCore location. Menu-wise:
* add an environment variable <code>GLOBUS_LOCATION</code> 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'')
+
  
 +
<pre>Preferences->Run/Debug->String Substitution->New...</pre>
 +
 +
* add an environment variable <code>GLOBUS_LOCATION</code> to the run configuration of ''all'' your clients and set it to the string variable just defined. Menu-wise:
 +
 +
<pre>Open Run Dialog...->Environment->New...->Variables</pre>
 +
 +
* add the value of the string variable as a classpath element to the run configuration of ''all'' your clients. Menu-wise:
 +
 +
<pre>Open Run Dialog...->Classpath->User Entries->Advanced...->Add Variable String</pre>
  
 
Whether you are running the client from the shell or from within Eclipse, the output to expect (under default gCore settings) is the following:
 
Whether you are running the client from the shell or from within Eclipse, the output to expect (under default gCore settings) is the following:
Line 773: Line 709:
 
Hello John Doe, you have invoked service SampleService (Samples)
 
Hello John Doe, you have invoked service SampleService (Samples)
 
</pre>
 
</pre>
 +
 +
 +
== Using the Eclipse IDE for for debugging and/or profiling your service ==
 +
 +
=== Degugging ===
 +
Starting the gHN with:
 +
 +
  gcore-start-container-debug
 +
 +
allows you to connect your eclipse debugger with the container following a few easy steps.
 +
 +
* Open your Eclipse IDE and choose from the main menu: Run -> Debug Configurations
 +
* In the "Debug Configurations" window add a new configuration for debugging a "Remote Java Application".
 +
** Give the configuration an appropriate name
 +
** Have it pointing to the project you wish to debug
 +
** Set the connection type to "Standard (Soket attach)"
 +
** Set the host to be the host name where the server runs on and port 8787
 +
** Press Debug
 +
* Open the Debug perspective and you are ready to start the Debugging
 +
[[Image:DbgWin.png|frame|none||Example of Debug Configuration]]
 +
 +
=== Profiling ===
 +
Starting the gHN with:
 +
 +
  gcore-start-container-profile
 +
 +
allows you to connect your eclipse profiler with the container.
 +
 +
The process described process is tested with Eclipse Ganymede and the [http://www.eclipse.org/tptp/ "Eclipse Test and Performance Tools Platform"] (TPTP). After installing the TPTP plugin you have to follow the path of profiling a remote java application.
 +
 +
First, you should manually start the agent that will be picking up messages from the java VM. This agent will have to be in the same system where the container runs. This is done with a command like:
 +
 +
  /home/myusername/.Programs/eclipse/plugins/org.eclipse.tptp.platform.ac.linux_ia32_4.4.100.v200902101418/agent_controller/bin/ACStart.sh
 +
 +
Be sure that there is only one ACServer running, the one created by your call to ACStart.sh script.
 +
You can find more info on setting up this agent [http://help.eclipse.org/help32/index.jsp?topic=/org.eclipse.tptp.platform.doc.user/tasks/teprofsa.htm here]
 +
 +
 +
As soon ACServer and gCore are running
 +
* Open your Eclipse IDE and choose from the main menu: Run -> Profile Configurations
 +
* In the "Profile Configurations" window add a new configuration from category "Attach to Agent".
 +
** Give the configuration an appropriate name
 +
** Have it pointing to the host you run the container along with the agent.
 +
** Set port 10003 (unless you pick a different port from the agents configurations)
 +
** In the Agents tab select what you want to profile
 +
** Press Profile
 +
The Profiling perspective should open and you should be able to profile your service.
 +
 +
[[Image:ProfHost.png|frame|none||Example of Profiling Configuration]]
 +
[[Image:ProfAgent.png|frame|none||Set what you want to profile]]
 +
[[Image:ProfWork.png|frame|none||Profiling in action]]

Latest revision as of 17:14, 21 April 2010

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/>
    <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 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 later).
  • Another Software element identifies the secondary build artefact of the service, its own stubs. The Main package depends always upon its stubs and this dependency must be explicit.

Blue.jpg 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>


Red.jpg Essentially, the configuration must have a service element entirely dedicated to SampleService, rather than more specific components yet to come. Furthermore, the element:

Red.jpg 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.

Red.jpg must 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.

Blue.jpg 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   [new]
|--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.

Red.jpg the name of the portType 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 name StatelessPortType of the portType element.
  • 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. Now the last important convention:

Red.jpg 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       [new]
|
|-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 service element dedicated to the service in the JNDI configuration file.


Blue.jpg 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()).


Blue.jpg 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 extends GCUBEPortType {

	/** {@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:

Red.jpg the implementation extends GCUBEPortType, which ensures the correct initialisation of the port-type at service startup. All the port-type implementations of your services ought to do the same.

Blue.jpg 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 that 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   [new]
|------stateless                      
|-------Stateless.java       [new]   
|
|--schema
|---Stateless.wsdl
|
|-Dependencies
|--SampleService

Building & Deploying

The definition of the service is nearly complete: most configuration, interfaces, and implementation components are in place.

Some Java code is still missing however. 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 stubs for our service.

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 conventional 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. 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/sample-build.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>

Red.jpg The deployment descriptor must 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 the name 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 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 as soon as it starts up, rather than when it receives the first request for it.

Now, notice the following rules:

Red.jpg the name of the service element must match that in the PortType element of the service profile.

Red.jpg loadOnStartup must be set to true for at least one 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        [new]
|
|--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 of SampleService 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.

Blue.jpg If you had many port-types, you would list them all in arbitrary order (wsdl.2,wsdl.3,...). If you had different namespaces across WSDLs and/or auxiliary XSDs, you would also list them all in arbitrary order.

Blue.jpg 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         [new]
|
|--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.

Blue.jpg 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.

Blue.jpg 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.

Blue.jpg 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... 

Red.jpg 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.

Blue.jpg 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 a org.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.


Blue.jpg 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

Blue.jpg 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 of SampleService 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 of SampleService:
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       [new]
|
|--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. Menu-wise:
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. Menu-wise:
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. Menu-wise:
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)


Using the Eclipse IDE for for debugging and/or profiling your service

Degugging

Starting the gHN with:

 gcore-start-container-debug

allows you to connect your eclipse debugger with the container following a few easy steps.

  • Open your Eclipse IDE and choose from the main menu: Run -> Debug Configurations
  • In the "Debug Configurations" window add a new configuration for debugging a "Remote Java Application".
    • Give the configuration an appropriate name
    • Have it pointing to the project you wish to debug
    • Set the connection type to "Standard (Soket attach)"
    • Set the host to be the host name where the server runs on and port 8787
    • Press Debug
  • Open the Debug perspective and you are ready to start the Debugging
Example of Debug Configuration

Profiling

Starting the gHN with:

 gcore-start-container-profile

allows you to connect your eclipse profiler with the container.

The process described process is tested with Eclipse Ganymede and the "Eclipse Test and Performance Tools Platform" (TPTP). After installing the TPTP plugin you have to follow the path of profiling a remote java application.

First, you should manually start the agent that will be picking up messages from the java VM. This agent will have to be in the same system where the container runs. This is done with a command like:

  /home/myusername/.Programs/eclipse/plugins/org.eclipse.tptp.platform.ac.linux_ia32_4.4.100.v200902101418/agent_controller/bin/ACStart.sh

Be sure that there is only one ACServer running, the one created by your call to ACStart.sh script. You can find more info on setting up this agent here


As soon ACServer and gCore are running

  • Open your Eclipse IDE and choose from the main menu: Run -> Profile Configurations
  • In the "Profile Configurations" window add a new configuration from category "Attach to Agent".
    • Give the configuration an appropriate name
    • Have it pointing to the host you run the container along with the agent.
    • Set port 10003 (unless you pick a different port from the agents configurations)
    • In the Agents tab select what you want to profile
    • Press Profile

The Profiling perspective should open and you should be able to profile your service.

Example of Profiling Configuration
Set what you want to profile
Profiling in action