Programming Recipes

Table of Contents

1. Service Scope and Lifetime
1.1. How can I force my service to initialize when the container starts?
1.2. How can I have Service methods execute at lifetime (Startup/Shutdown) events?
1.3. How can I have ResourceHome (or any JNDI resource) methods execute at lifetime events?
1.4. How do I set the scope of my service?
2. Operation Providers
2.1. How do I use Operation Providers?
2.2. How can I expose my own class as an Operation Provider for other services to use?
3. Connecting to Services
3.1. How do I connect to a service via WSRF?
3.2. How do I connect to a service in the same Java Virtual Machine in memory?
4. Recipes about working with Endpoint Addressing
4.1. How can my service object obtain information about its Endpoint Address?
4.2. How do I use WS-Addressing with low level API's such as javax.xml.rpc.Call?
4.3. How do I use ReferenceParameters to specify resource identity for my service?
5. Object Serialization
5.1. How can I handle information defined as xsd:any in my service?
5.2. How can I serialize my Java Object to a file?
6. Notifications
6.1. How can I set up my client/service to receive notifications
6.2. How can I use a notification topic that is not tied to a ResourceProperty?
7. JNDI, Resources and Objects
7.1. How do I access other objects from my service?
7.2. How can I store my own resources in JNDI?
8. Asynchronous Work and Threads
8.1. How can I run work asynchronously from my service?
8.2. How do I configure WorkManagers for my service?
8.3. How can I run a "Daemon" process from my service?
8.4. How can I create a Thread from my service?
9. Querying Properties
9.1. How can I query resource properties using XPath
9.2. How can I add a new query/topic expression evaluator?
10. Build Recipes
10.1. How can I process my wsdl using an Ant build file?
10.2. How can I create a GAR?
10.3. How can I deploy my GAR?
10.4. How can I generate a laucher for my client?
10.5. How do I create a client distribution for my service?
11. Controlling client connection settings
11.1. How can I set the timeout on my client?
11.2. How can I reuse my connection?
11.3. How can I control HTTP chunked encoding
11.4. How can I control the HTTP protocol version?
12. Writing and running tests
12.1. What is JUnit and how does it relate to Java WS Core?
12.2. How do I write JUnit tests for services?
12.3. How do I run my JUnit tests for Java WS Core and/or my services?
13. Miscellaneous Recipes
13.1. How do I obtain Version Information about Java WS Core
13.2. How can I use SOAP Attachments with my service?
13.3. How can I make sure my WSDL/SOAP documents are valid for GT 4?
13.4. How can I retrieve service parameters from my WSDD file?
13.5. How do I get standard MessageContext properties?

1. Service Scope and Lifetime

1.1. How can I force my service to initialize when the container starts?

By default, a service is initialized on the first invocation of a method in that service. However, there are frequently times when a service needs to run some time-consuming, global initialization. In this situation, it is better to have the service initialize when the container starts, so as to not impact the performance of the first method invocation. To have the service initialize on container starup, add the following to your deploy-service.wsdd descriptor:

                    
<parameter name="loadOnStartup" value="true"/>                                        
                    
                

1.2. How can I have Service methods execute at lifetime (Startup/Shutdown) events?

If the service implements the javax.xml.rpc.server.ServiceLifecycle interface, the lifecycle methods will be called according to the scope setting as a service instance is created and destroyed.

For example, in Application scope, destroy() will be called on container shutdown, and in Request scope it will be called after the service method is called. (See next question)

1.3. How can I have ResourceHome (or any JNDI resource) methods execute at lifetime events?

A ResourceHome will be activated either on the first service invocation or, if "loadOnStartup" parameter is set to "true", during container startup. Both mechanisms trigger actual activation by looking up the ResourceHome in the JNDI directory. This initial lookup causes a proper MessageContext and/or JAAS subject to be associated with the current thread, instantiation of the object implementing the ResourceHome and, if the ResourceHome implements the org.globus.wsrf.jndi.Initializable interface, the invocation of the initialize() function.

In fact, the same steps are performed upon initial lookup of any JNDI resource entry that uses the org.globus.wsrf.jndi.BeanFactory class for its factory and is defined directly under a service entry in a jndi-config.xml file.

1.4. How do I set the scope of my service?

The scope setting of the service dictates when and how service instances are created. It is generally recommended that services use the Application scope (even though it is not currently the default) because it associates the security credentials with the thread. To set the scope, you add the following parameters to your deploy-server.wsdd:


<parameter name="scope" value="Application"/>
<parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/>
    
                    

This will specify to use the globus supplied RPCProvider and to set the scope to Application. This is the recommended setting for most services. If you have any question about what to use, set this.

There are two scope options:

Table 1. Service Scopes

Application Scope

Service/provider instances are created either on first invocation or on container startup. The behavior is determined by the value of the "loadOnStartup" parameter. This will work in the same way in both the stand-alone container and in Tomcat.

If the service or the container is configured with a security descriptor, the appropriate credentials will be associated with the thread during activation (using JAAS). Also, during activation a basic Axis MessageContext will be associated with the thread with only Constants.MC_HOME_DIR, Constants.MC_CONFIGPATH, and the right target service properties set (see “Obtaining standard MessageContext properties” for details). If service or providers implement the javax.xml.rpc.server.ServiceLifecycle interface, the lifecycle methods will be called accordingly.

Request Scope

One instance is created per invocation. This is the default (if scope parameter is not set in the deployment descriptor).

Service/Provider instances are created per invocation, ServiceLifecycle methods called right before and after service method invocation, no JAAS credentials during ServiceLifecycle methods

2. Operation Providers

2.1. How do I use Operation Providers?

GT3 introduced the concept of operation providers where a service could be composed of different parts/classes. Java WS Core also supports this functionality. In GT3 operation providers had to implement a specific interface. In Java WS Core no such interface is required. In fact, an operation provider is not in any way different from a standard web service. That means that any web service implementation can automatically be used as an operation provider (as long as it uses common or standard interfaces to operate on resources).

To enable operation provider support for your service, make the following changes to the service deployment descriptor:

  1. Change the value of the provider attribute to Handler.
  2. Add a handleClass parameter with a value of org.globus.axis.providers.RPCProvider.
  3. Specify providers in the providers parameter.

    The value of the parameter is a space-separated list of either provider names or class names.

  4. Add or change the value of the scope parameter to Application or Request.

The following is an example of a modified service deployment descriptor:

<service name="SubscriptionManagerService"  provider="Handler" 
        use="literal" style="document"> <parameter name="allowedMethods" 
        value="*"/>
    <parameter name="scope" value="Application"/>
    <parameter name="providers" 
            value=" GetRPProvider org.globus.wsrf.impl.lifetime.SetTerminationTimeProvider PauseSubscriptionProvider"/>
            
    <parameter name="handlerClass" 
            value="org.globus.axis.providers.RPCProvider"/>
    <parameter name="className"
            value="org.globus.wsrf.impl.notification.ResumeSubscriptionProvider"/>
    <wsdlFile>share/schema/core/notification/subscription_manager_service.wsdl</wsdlFile>
</service>
                    

[Note]Note

The operations defined in the className service always overwrite the providers' operations. That is, if one provider defines the same method as the service specified in the className parameter, the operation will be invoked on the service. Also, if two providers define the same method, the first one specified in the providers parameter will be invoked.

2.2. How can I expose my own class as an Operation Provider for other services to use?

Any service can provide an object for other service to include as a operation provider. The consuming service simply needs to add the fully qualified classname (FQCN) to the list of providers. However, if you wish to allow users to refer to your class by a friendly name instead of FQCN, you need to add an entry to the container's main deployment file element of the main deployment descriptor (etc\globus_wsrf_core\server-config.wsdd).

For example:

                   
<globalConfiguration> 

    ... 
    
    <parameter name="GetRPProvider" value="org.globus.wsrf.impl.properties.GetResourcePropertyProvider"/>
    
    ... 

</globalConfiguration>

               

3. Connecting to Services

These recipes are all about connecting to services running in a Java WS Core container.

3.1. How do I connect to a service via WSRF?

There are several ways to connect to a service. The simplest is to use the AddressingLocator that is created as part of the WSDL2Java execution. The AddressingLocator class can be used to get a stub for a service by passing the Apache Addressing EndpointReferenceType parameter. Each service has it's own AddressingLocator which can be used to acquire a port for the remote service. The port can then be used to access the service. If an EndpointReferenceType is returned from a method on that service, it can be used to acquire the port from the AddressingLocator and will be associated with the resource context created by the service.

//Specify the service address
String url = "http://localhost:8080/wsrf/services/Version";
//Create the EndpointReferenceType
EndpointReferenceType epr = new EndpointReferenceType(); 
epr.setAddress(new Address(url)); 
//Create the AddressingLocator for the service
VersionServiceAddressingLocator locator = new VersionServiceAddressingLocator();
//Get the port for the service
VersionServicePortType port = locator.getVersionPort(epr); 
port.getVersion();
                

3.2. How do I connect to a service in the same Java Virtual Machine in memory?

Services in the container can be invoked locally. Local invocations work just like remote invocations (all handlers are called, messages get serialized/deserialized) but messages do not travel over the network - everything happens in memory.

Local invocations can only be made on the server side. URLs with "local" protocol name are used for local invocations.

To invoke a service locally, do the following:

  1. Create a service URL with "local" protocol:

    URL url = new URL("local:///wsrf/services/MyService");
                                
  2. And as normal make the call:

    MyServiceAddressingLocator locator = new MyServiceAddressingLocator();
    MyService port = locator.getMyServicePort(url); 
    port.hello(); 
                            

That's all. By default the local invocations are made using the default instance of the AxisServer engine. It is possible to make local invocations using a different AxisServer engine instance if there is a MessageContext associated with the current thread (the MessageContext should have a reference to the desired AxisServer engine instance). To make a local invocation using non-default AxisServer engine add the following (using above example):

    
import org.globus.axis.transport.local.LocalTransportUtils;  
...
MyService port = ...
LocalTransportUtils.enableLocalTransport((Stub)port);
port.hello();
                

The "local" protocol URL handler is automatically registered by Java WS Core. However, sometimes it might be necessary to install the handler explicitly. To register the "local" URL protocol hander do the following:

import org.globus.axis.transport.local.LocalTransportUtils; 
...
LocalTransportUtils.init(); 
... 
                    

This step must be done before creating URLs with "local" protocol. Also, make sure that axis-local.jar is accessible from the system classloader.

4. Recipes about working with Endpoint Addressing

4.1. How can my service object obtain information about its Endpoint Address?

In most cases, a service will need to return the endpoint information of the container to a client. Unfortunately, getting that information might not be easy. The only reliable way of getting the container endpoint information is to extract it from the MessageContext.TRANS_URL property of the MessageContext/ResourceContext associated with the current thread.

To obtain base container endpoint information use the ServiceHost API. For example:

import org.globus.wsrf.container.ServiceHost; 

... 

URL containerBaseUrl = ServiceHost.getBaseURL(); 

... 
                

The above will return the base container URL such as http://localhost:8080/wsrf/services/.

To obtain service endpoint information use the ResourceContext API. For example:

 import org.globus.wsrf.ResourceContext; ... URL serviceUrl =
                                            ResourceContext.getResourceContext().getServiceURL(); ... 

The above will return the service URL such as http://localhost:8080/wsrf/services/MyService.

To obtain WS-Addressing endpoint for the service use the AddressingUtils API. For example:

import org.apache.axis.message.addressing.EndpointReferenceType; 
import org.globus.wsrf.utils.AddressingUtils; 

... 

EndpointReferenceType containerEndpoint = AddressingUtils.createEndpointReference(null); 

... 
                

The above will create a EndpointReferenceType object initialized with the Address field set to the service URL (as before) and empty reference properties. Also, you can pass a non-null ResourceKey instance to the createEndpointReference() function to create an endpoint for a specific resource. The reference properties field of the created EndpointReferenceType object will be set to the given ResourceKey.

[Note]Note

The ServiceHost API will return the correct information and AddressingUtils API will work correctly only if called from the same thread as the service method was invoked from.

4.2. How do I use WS-Addressing with low level API's such as javax.xml.rpc.Call?

If you are using the javax.xml.rpc.Call object directly, you can pass the addressing information by setting a Constants.ENV_ADDRESSING_REQUEST_HEADERS property on the call object.

For example:

Service service = new Service(); 
Call call = (Call) service.createCall(); 
String url = "http://localhost:8080/axis/services/Version"; 
AddressingHeaders headers = new AddressingHeaders(); 
headers.setTo(new To(url)); 
// pass the addressing info to the addressing handler
call.setProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS, headers);
call.setTargetEndpointAddress(new URL(url)); 
call.setOperationName(new QName(url, "getVersion")); // url here is just a namespace 
String ret = (String) call.invoke(new Object[]);
                    

4.3. How do I use ReferenceParameters to specify resource identity for my service?

In the WS-RF and WS-N specifications, the WS-Addressing ReferenceParameters are used to carry resource identity information. In previous versions of the WSRF/WSN specification, ReferenceProperties was used for the same purpose. The resource identity can be anything as long as it serializes as a XML element. The ReferenceParameters are serialized as separate SOAP headers in the SOAP envelope.

The Apache Addressing library only allows a DOM Element or a SOAPElement to be a reference parameters.

For example, create ReferenceParametersType and fill it with resource key info:

// create a reference property 
QName keyName = new QName("http://axis.org", "VersionKey"); 
String keyValue = "123"; 
SimpleResourceKey key = new SimpleResourceKey(keyName, keyValue); 
ReferenceParametersType props = new ReferenceParametersType();
// convert to SOAPElement and add to the list
props.add(key.toSOAPElement());

...
                

Then pass it to AddressingHeaders:

... 
                    
Service service = new Service(); 
Call call = (Call) service.createCall();
String url = "http://localhost:8080/axis/services/Version";
AddressingHeaders headers = new AddressingHeaders(); headers.setTo(new To(url));
headers.setReferenceParameters(props); // pass the addressinginfo to the addressing handler
call.setProperty(Constants.ENV_ADDRESSING_REQUEST_HEADERS, headers);
call.setTargetEndpointAddress(new URL(url)); 
call.setOperationName(new QName(url, "getVersion")); // url here is just a namespace 
String ret = (String) call.invoke(new Object[]);
                

Or set it on EndpointReferenceType:

... 

String url = "http://localhost:8080/axis/services/Version"; 
EndpointReferenceType epr = new EndpointReferenceType(); 
epr.setAddress(new Address(url));
epr.setParameters(props); 
VersionServiceAddressingLocator locator = new VersionServiceAddressingLocator(); 
VerionServicePortType port = locator.getVersionPort(epr); 
port.getVersion();
                

5. Object Serialization

5.1. How can I handle information defined as xsd:any in my service?

Java Beans generated by Apache Axis that represent a XML type with the xsd:any content implement the org.apache.axis.encoding.AnyContentType interface and have get_any() and set_any() methods. There are several API to help you deal with the AnyContentType content.

To convert AnyContentType content to a Java object use the ObjectDeserializer API. For example:

AnyContentType bean = ...; 
Integer value = (Integer) ObjectDeserializer.getFirstAsObject( bean), Integer.class);
                

To convert a Java object into a type that can be used with AnyContentType content use the ObjectSerializer API. For example:

// convert Java object into SOAPElement EndpointReferenceType object = ...; 
QName elementName = new QName("http://example.com", "EPR"); 
SOAPElement element = ObjectSerializer.toSOAPElement(object, elementName); 
// set the SOAPlement as Any content 
AnyContentType bean = ...; 
AnyHelper.setAny(bean, element);
                

To examine the raw AnyContentType content use the AnyHelper API to serialize it as XML. For example:

AnyContentType bean = ...; 
String contents = AnyHelper.toSingleString(bean);
System.out.println("Contents: " + contents);
                

5.2. How can I serialize my Java Object to a file?

You can use the ObjectSerializer API to serialize the Java object into a file (in XML format):

// object to serialize EndpointReferenceType epr = ...; 
// root element name
QName elementName = new QName("http://example.com", "EPR"); 
FileWriter out = null; 
try {
    out = new FileWriter("epr.xml"); 
    ObjectSerializer.serialize(out, epr, elementName);
    out.write('\n'); 
} catch (IOException e) { 
    System.err.println("Error: " +
        e.getMessage()); 
} finally { 
    if (out != null) { 
        try { 
            out.close(); 
        } catch (Exception ee) {} 
    } 
} 

Similarly, use can use the ObjectDeserializer API to deserialize a file containing XML data into a Java object:

                    // deserialized object EndpointReferenceType epr = ...; FileInputStream in =
                                null; try { in = new FileInputStream("epr.xml"); epr =
                                (EndpointReferenceType)ObjectDeserializer.deserialize( new InputSource(in),
                                EndpointReferenceType.class); } catch (IOException e) { System.err.println("Error: " +
                                e.getMessage()); } finally { if (in != null) { try { in.close(); } catch (Exception ee)
                                {} } } 

6. Notifications

6.1. How can I set up my client/service to receive notifications

There are a few steps involved in setting up and receiving notifications:

6.1.1. Step 1: Implement the callback

The notification consumer application must provide an implementation of the NotifyCallback interface. The deliver function of the interface will be invoked whenever a notification for that consumer arrives.

[Note]Note

The deliver function should be thread-safe as multiple notifications might come at once. Notifications might also come unordered and some might even be lost (due to network failures).

6.1.1.1. Resource property notifications

For resource property notifications the message parameter will usually be of ResourcePropertyValueChangeNotificationElementType type. From that type you can retrieve the real notification message which contains the new and optionally the old value of the resource property. Example:

import org.globus.wsrf.encoding.DeserializationException;
import org.globus.wsrf.utils.NotificationUtil;
import org.oasis.wsrf.properties.ResourcePropertyValueChangeNotificationType;
...
// Notification callback
public void deliver(List topicPath, EndpointReferenceType producer, Object message) {
                                               
    ResourcePropertyValueChangeNotificationType changeMessage = null;
    try {
        changeMessage = NotificationUtil.getRPValueChangeNotification(message);
    } catch (DeserializationException e) {
        // handle exception
    }
                        
    Integer newValue = (Integer)ObjectDeserializer.getFirstAsObject( changeMessage.getNewValue(), Integer.class); 
    System.out.println("New value: " + newValue); 
    if(changeMessage.getOldValue() != null) { 
        Integer oldValue = (Integer)ObjectDeserializer.getFirstAsObject(changeMessage.getNewValue(), Integer.class); 
        System.out.println("Old value: " + oldValue); 
    } 
} 
                    

The resource property values are of AnyContentType type. Please see the Section 5.1, “How can I handle information defined as xsd:any in my service?” section for more information on how to deal with such types.

6.1.1.2. Other notifications

For other non-resource property notifications the message parameter will either be of the type into which the message type maps into (if there is an appropriate type mapping defined) or of org.w3c.dom.Element type if there is no appropriate type mapping defined. Example:

public void deliver(List topicPath, EndpointReferenceType producer, Object message) { 
    EndpointReferenceType epr = null; 
    if (message instanceof Element) { 
        //type mapping not defined, try to deserialize into right Java 
        // type using ObjectDeserializer API. 
        epr = (EndpointReferenceType)ObjectDeserializer.toObject((Element)message, EndpointReferenceType.class); 
    } else if (message instanceof EndpointReferenceType) { 
        // type mapping defined 
        epr = (EndpointReferenceType)message; 
    } else { 
        // some other type 
    } 
}
                    

The custom notification message type mappings can be defined in a client-server-config.wsdd file. This file can be deployed with your service (it must be placed directly under the etc/ directory in the GAR file). Please see the $GLOBUS_LOCATION/etc/globus_wsrf_core/client-server-config.wsdd file for an example. If your callback implementation will be used on the server-side, you might also need to define the type mappings in your server-config.wsdd.

6.1.2. Step 2: Start NotificationConsumerManager

In order to facilitate the receipt of notifications, start a NotificationConsumerManager by doing the following:

import org.globus.wsrf.NotificationConsumerManager; 

...

NotificationConsumerManager consumer = null; 

try { 
    consumer = NotificationConsumerManager.getInstance(); 
    consumer.startListening(); 
    ... 
} catch (...) { 
    ... 
} 
                
[Important]Important

On the client when the consumer.startListening() is called an embedded container is actually started in the background. That embedded container is the same as the standalone container but configured with only one or two services needed to handle the notifications. Therefore, any client using notification consumer API will have the same dependencies on the libraries and configurations files as the basic standalone container code. Also, please check the Failed to acquire notification consumer home instance from registry error in Table 1, “Java WS Core Errors” if the consumer.startListening() call failed on the client.

On the server when the consumer.startListening() is called the container in which the service is running in is used to receive the notifications. Therefore, there are no extra dependencies.

6.1.3. Step 3: Register the callback

Register the callback implementation with the NotificationConsumerManager (once it is started) using the createNotificationConsumer function.

The createNotificationConsumer function returns an endpoint for this notification consumer.

Example:

import org.globus.wsrf.NotifyCallback; 
import org.apache.axis.message.addressing.EndpointReferenceType; 
                        
... 
                        
MyCallback callback = new MyCallback(); 
EndpointReferenceType consumerEPR = consumer.createNotificationConsumer(callback); 
... 
class MyCallback implements NotifyCallback { 
    .... 
} 
                    

6.1.4. Step 4: Subscribe to the callback

Pass the endpoint returned by the createNotificationConsumer function to the subscribe call.

Example:

import org.oasis.wsn.TopicExpressionType; 
import org.oasis.wsn.Subscribe; 
import org.oasis.wsn.SubscribeResponse; 
import org.globus.wsrf.WSNConstants; 
import org.globus.wsrf.WSRFConstants; 

...

Subscribe request = new Subscribe();
request.setConsumerReference(consumerEPR);
TopicExpressionType topicExpression = new TopicExpressionType();
topicExpression.setDialect(WSNConstants.SIMPLE_TOPIC_DIALECT);
topicExpression.setValue(Counter.VALUE);
MessageElement element = (MessageElement)ObjectSerializer.toSOAPElement(topicExpression, WSNConstants.TOPIC_EXPRESSION);
FilterType filter = new FilterType();
filter.set_any(new MessageElement[] { element });
request.setFilter(filter);
port.subscribe(request); 

... 
                        

6.1.5. Step 5: Clean up

Once done with the notifications, do the following clean up tasks.

Step 5a: Destroy subscriptions resource. Make sure to explicitly destroy the subscription resource or set its termination time. Example:

import org.globus.wsrf.core.notification.SubscriptionManager; 
import org.globus.wsrf.core.notification.service.SubscriptionManagerServiceAddressingLocator;
import org.oasis.wsrf.lifetime.Destroy; 
                        
...
                        
SubscriptionManagerServiceAddressingLocator sLocator = new SubscriptionManagerServiceAddressingLocator(); 
SubscriptionManager manager = sLocator.getSubscriptionManagerPort( subResponse.getSubscriptionReference());
manager.destroy(new Destroy()); 
                                
... 
                            

Step 5b: Un-register the callback.  Make sure to call (especially in error cases) the NotificationConsumerManager.removeNotificationConsumer() function to unregister the callback from the NotificationConsumerManager.

Step 5c: Release resources. In addition, make sure to always call the NotificationConsumerManager.stopListening() function when finished using the NotificationConsumerManager. Otherwise, some resources might not be released. Example:

... 
} catch(Exception e) { 
... 
}
finally { 
    if (consumer != null) { 
        try { 
            consumer.stopListening(); 
        }catch (Exception ee) {
            //Handle Exception
        } 
    } 
}
                            

6.2. How can I use a notification topic that is not tied to a ResourceProperty?

While the typical usage of WS-Notification within WSRF is to notify subscribers when a ResourceProperty changes, this is not a requirement. In fact, it is easy to set up a WS-Notification topic to support general messages. The Resource which will handle your topic should implement org.globus.wsrf.TopicListAccessor. This means your Resource needs to implement the TopicList getTopicList() method. The simplest way to do this, is to create an instance of SimpleTopicList in your resource like such:

public class MyResource implements ResourceProperties, TopicListAccessor{
    private TopicList topicList;
    
    private ResourcePropertySet propertySet;
    
    private QName qname = new QName("http://www.globus.org/MyService", "MyResource");
    public MyResource(){
        topicList = new SimpleTopicList(this);
        propertySet = new ResourcePropertySet(qname);
    }
    
    public TopicList getTopicList(){
        return topicList;
    }

    public ResourcePropertySet getResourcePropertySet(){
        return propertySet;
    }
}
                

Once you have this, your resource is ready to work with topics. To create a new Topic in thisresource, you need to define the Topic and add it to your TopicList like so (this is in the same class as above):

import org.globus.wsrf.impl.SimpleTopic;

. . . 
    private static final QName topicName = new QName("http://www.globus.org/MyService", "MyTopic");

    private void init(){
       Topic myTopic = new SimpleTopic(topicName);
       this.getTopicList().addTopic(myTopic);
    }
                

There is nothing magical about the names or what method this occurs in. For simplicity's sake let's just assume that our Resource has an init method that will set up the Topic. This creates a new Topic with the supplied identifier and adds it to our list of Topics. Ok, our setup is done.

Java Core has provided a helper class to assist in subscribing to a Topic from a service. The SubscribeHelper provides a simple way for you service to subscribe a client to a Topic. You can use it like this:

private EndpointReferenceType subscribe(QName topicName, ResourceKey key, Resource resource, 
        EndpointReferenceType consumerAddr) throws ServiceException, RemoteException {
    Subscribe request = new Subscribe();
    request.setConsumerReference(consumerAddr);
    try {
        TopicExpressionType topicExpression = new TopicExpressionType();
        topicExpression.setDialect(WSNConstants.SIMPLE_TOPIC_DIALECT);
        topicExpression.setValue(topicName);
        MessageElement element = 
            (MessageElement) ObjectSerializer.toSOAPElement(topicExpression, WSNConstants.TOPIC_EXPRESSION);
        FilterType filter = new FilterType();
        filter.set_any(new MessageElement[]{element});
        request.setFilter(filter);
        ResourceContext ctx = ResourceContext.getResourceContext();
        String homeLocation = ((ResourceContextImpl) ctx).getResourceHomeLocation();
        return new SubscribeHelper(
            ctx, resource, key, homeLocation, request).subscribe();
    } catch (Exception e) {
        throw new RemoteException("Error creating Subscription", e);
    }
}                    
                

This is a little ugly, but it is boilerplate code. Specify your topicName, resourceKey, resource, and the EndpointReferenceType of the client you are subscribing, and this will handle the subscription for you. You return the EndpointReferenceType of the subscription, so that the client can unsubscribe when they are complete.

Finally, once you have all of this set up the last piece is to actually send a message on your topic. This is pretty easy:

LinkedList topicPath = new LinkedList();
topicPath.add(myTopicQName);                    
Topic myTopic = this.getTopicList().getTopic(topicPath);
myTopic.notify(myMessage);                                        
                

This will send the notification to the Topic and, as a result, to all consumers for that topic.

It is not strictly necessary to use all of the ResourceProperties and TopicListAccessor Interfaces as shown above, but this is the scenario for which Java WS Core provides explicit support.

7. JNDI, Resources and Objects

These recipes all relate to using JNDI to store resources and objects to be used by your service

7.1. How do I access other objects from my service?

Services should avoid creating and destroying resources that live beyond the lifetime of a resource. However, this does not mean that shouldn't use objects that have a longer lifetime. Java WS Core uses JNDI to support these objects. Several objects already exist in JNDI, having been put there by Java WS Core.

  • commonj.work.WorkManager -- Constants.DEFAULT_WORK_MANAGER

    This is a WorkManager for handling asynchronous work

  • commonj.timers.TimerManager -- Constants.DEFAULT_TIMER

    This a timer for scheduling future or recurring asynchronous work

  • org.globus.wsrf.query.QueryEngine -- Constants.DEFAULT_QUERY_ENGINE

    This is used to map queries on the service data of a service to the appropriate ExpressionEvaluators and then return the result.

  • org.globus.wsrf.topicexpression.TopicExpressionEngine -- Constants.DEFAULT_TOPIC_EXPRESSION_ENGINE

    The TopicExpressionEngine interface is used to map queries on a topic list to the appropriate TopicExpressionEvaluators and then return the result.

  • javax.sql.DataSource -- Constants.DEFAULT_DATABASE

    This is an in-memory base (Apache Derby), which can be used by services

To access these objects from JNDI, you can use a convenience method provided by Java WS Core.

import org.globus.wsrf.jndi.JNDIUtils;
import org.globus.wsrf.Constants;

...

WorkManager wm = (WorkManager) JNDIUtils.getInitialContext().lookup(Constants.DEFAULT_WORK_MANAGER);
                    

Note, that you need to cast whatever you lookup to the proper type, which is, in this case, WorkManager. The org.globus.wsrf.Constants class has constant fields for looking up each of standard resources provided by Java WS Core

7.2. How can I store my own resources in JNDI?

As stated before, services should avoid creating objects and resources that have a lifetime that extends beyond a single request directly, so service developers should create and store those objects in JNDI. Any object can be stored in JNDI. The simplest way to implement an object as a JavaBean, i.e., having properties with corresponding get/set methods. Let's assume we have a bean that has a timeout property. We can configure this to be put in JNDI by putting the following information in our jndi-config-deploy.xml file.

                        
<resource name="MyBean" type="org.mypackage.MyBean">
    <resourceParams>
        <parameter>
            <name>timeout</name>
            <value>10000</value>
        </parameter>                               
    </resourceParams>
</resource>                

                   

This would set the timeout property to be 10000. All properties which follow the JavaBean pattern can be set this way.

If you want to work with a class that does not follow the JavaBean pattern, or that requires a greater level of set up, you can implement your own factory to be used by JNDI. The interface for ObjectFactory only has one method to be implemented

Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) 
                    

The parameters to this method provide all of the information necessary to create your object. In most situations, you will only work with the first parameter.

if (obj instanceof ResourceRef) {                        
    Reference ref = (Reference) obj;
    int timeout = ref.get("timeout");
    MyBean bean = new MyBean();
    bean.setTimeout(timeout);
    return bean;
}else{
    return null;
}                        
                    

This is the most common way of creating an object factory. Assuming the first parameter is an instance of ResourceRef, we have a simple way of getting the parameters defined in the configuration. The get() method of ResourceRef allows us to access those parameters. Once you have your parameters, create and configure your object, then return it. Now you can use this object just like you could any of the resources that Java WS Core supplies for you. The lookup name is specicified by the name attribute of the resource element in your configuration. To lookup an object you have supplied, you still use the JNDIUtils as before, specifying the proper lookup path.

MyBean bean = (MyBean) JNDIUtils.getInitialContext().lookup(Constants.DEFAULT_WSRF_LOCATION + "MyService/MyBean");
                    

Any object you define in your jndi-config-deploy.xml file can be found at the default wsrf location followed by your service name and the name you specified in the jndi-config-deploy.xml file.

8. Asynchronous Work and Threads

8.1. How can I run work asynchronously from my service?

You can use the container to maintain your threads for you. This is considered the best approach towards dealing with concurrency. The container's thread management is handled by the WorkManager class. The WorkManager is abstraction on top of how a task (or Work) is executed. It also assures that the execution context will be set correctly for the task. The abstract nature of the WorkManager provides a lot of flexibility to do things such as execute work in a pool, or on a remote cluster To use the WorkManager, you need to construct a Work object. Work just extends the Runnable interface to supply a method which allows the Work to gracefully end its processing. Basically, though, you only need to implement a run() method, just like you normally would. One advantage the WorkManager provides is that the caller can get information about the progress of Work which has been submitted to the WorkManager. This includes the state, as well as any information regarding exceptions that might have been thrown. The WorkManager works in a similar way to the ExecutorServices.

import commonj.work.WorkManager;
                        
...
WorkManager manager = ... //acquired from JNDI
manager.scheduleWork(myWork);
                    

The above code will execute the myWork asynchronously in a separate thread. It is also possible to run work synchronously in a separate thread:

import commonj.work.WorkManager;
                        
...
WorkManager manager = ... //acquired from JNDI
manager.doWork(myWork);
                    

This will also execute the myWork in a separate thread, but it will block until the job has completed. The doWork method should be used sparingly.

8.2. How do I configure WorkManagers for my service?

Java WS Core sets up a default WorkManager which can be used by any service. The default Workmanager can be found in JNDI by doing the following:

WorkManager manager = (WorkManager) JNDIUtils.getInitialContext().lookup(Constants.DEFAULT_WORK_MANAGER);                        
                    

(See the section on JNDI Objects for details about looking up the WorkManager)

However, the job queue and thread pool for the default WorkManager is shared amongst all of the services, and, as such, may not be appropriate for time-sensitive jobs. In addition, the default WorkManager has a simple default configuration, which may not meed the needs of a service. Most services will want to configure one or more WorkManagers for use by their service. To configure a WorkManager for your service, you will need to add a resource to your jndi-deploy-config.xml file (see the section on configuring JNDI for an overview)

There is only one parameter currently used for the WorkManager (more will be coming soon). This is the maximumPoolSize. This allows you to set the maximum number of threads that will be running at any given time. To configure a WorkManager for your service, you can specify the following:

                        
<resource name="wm/WorkManager1" type="org.globus.wsrf.impl.work.WorkManagerImpl">
    <resourceParams>
        <parameter>
            <name>maximumPoolSize</name>
            <value>5</value>
        </parameter>                               
    </resourceParams>
</resource>                

                    

This will create a WorkManager that has a maximum size of 5 threads. Note that this is only the maximum number of threads. The WorkManager can still accept an unlimited number of jobs, but they will be queued until a thread is available to service them. This WorkManager can be acquired using the following code:

WorkManager WorkManager1 = (WorkManager) JNDIUtils.getInitialContext().lookup(Constants.DEFAULT_WSRF_LOCATION + "/MyService/wm/WorkManager1");                        
                    

Now the WorkManager can service your Work objects. Your service can use multiple WorkManagers. In fact, it is often advisable to have more than one WorkManager with a fairly small pool of threads (5-10) where each WorkManager processes a particular stage of your job. That way, long running tasks won't block fast tasks from executing.

8.3. How can I run a "Daemon" process from my service?

Sometimes a service will need to run a process that lasts for a very long time (for example, for the lifetime of the container). A typical example, would be a monitoring process that is constantly listening for some event to occur. Clearly, the service won't want one of the Threads from its WorkManager pool to be occupied with this permanent task. To resolve this, you can specify your Work object as a Daemon which will launch it in a new Thread automatically. You should still use the WorkManager for this task, since it sets the context for you and can reuse threads when a Daemon has completed. The only difference is that when you create your Work object, you will return true for the isDaemon() method.

8.4. How can I create a Thread from my service?

Creating and using Java threads is a complicated task because of classloader, security and transction contexts. As such, it is strongly recommended that service developers not launch their own threads. If you, as a developer, feel you must launch your own thread, do so at your own risk. Here are a couple of pieces of advice, however if you are creating your own threads:

  • Use the java.utils.concurrent constructs

    Java 5 has introduced a whole suite of concurrency tools which hide the details of handling threads. You should take advantage of these, specifically, the ExecutorService framework. This provides a way of abstracting thread pools and other thread handling.

    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadPoolExecutor;
    ...
    //Create the Executor
    ExecutorService executor = Executors.newFixedThreadPool(numOfThreads);
    ...
    //Now use the executor
    executor.submit(myRunnable);
                            

    This assumes that myRunnable is an instance of a class that implements Runnable. The above code will submit the Runnable to the executor, and when a thread becomes available, execute the Runnable. There are other options for creating your Executor service and if you have other needs, you should read the javadocs. If you are going to use an ExecutorService, you should either set your service scope to "application", store the ExecutorService in JNDI or both. Otherwise, the ExecutorService will be created everytime your service has a request.

  • Use the ClassLoaderUtils from CoG

    It is difficult to keep track of classloaders when you are working in threads. If you need to load a class using a method like Class.forName(name) or load a resourse like getClass().getResource(resource) you must take special care to ensure that you are using the correct classloader. CoG provides a utility to help ensure that you are using the correct classloader. Instead of calling the above methods, use ClassLoaderUtils.forName(name) or ClassLoaderUtils.getResource(resource). The ClassLoaderUtils has other helper methods as well. The documentation can be found here

Even with these tools, it is possible you may still run into problems with Threads, but these should help get you on the way.

9. Querying Properties

9.1. How can I query resource properties using XPath

Java WS Core currently provides two ways of querying the resource properties using XPath: the standard method and a proprietary method. The standard method is defined by the WS-ResourceProperties specification and all implementations of this specification support it. The proprietary method is a custom solution and therefore only supported by Globus Toolkit. The proprietary method is a new query dialect called TargetedXPath. The TargetedXPath query dialect offers three key advantages over the standard XPath query method:

  • Namespace mappings - a set of explicit namespace mappings to be passed along with the query. With these mappings the query expression can be dramatically simplified as namespace prefixes can be used freely within the query.

  • Single resource property querying - a specific resource property can be queried instead of the entire resource property document.

  • WS-Enumeration support - the query results can be returned as an enumeration.

The globus-xpath-query command line tool can be used to query resource properties with the TargetedXPath query dialect. If the query results were returned as an enumeration they can be retrieved using the ws-enumerate command line tool.

9.1.1. API

Example querying resource properties using the TargetedXPath query dialect:

import org.globus.wsrf.core.query.targetedXPath.TargetedXPathQueryType; 
import org.globus.wsrf.core.query.targetedXPath.NamespaceMappingType; 
import org.globus.wsrf.query.targetedXPath.TargetedXPathConstants; 
                    
... 
                    
TargetedXPathQueryType
targetedQuery = new TargetedXPathQueryType(); 
NamespaceMappingType nsMap[] = new NamespaceMappingType[1]; 
nsMap[0] = new NamespaceMappingType();
nsMap[0].setMappedName("fooPrefix"); nsMap[0].setNamespace(new URI("http://fooNamespace")); 
targetedQuery.setNamespaceMappings(nsMap);
targetedQuery.setQueryString("boolean(//fooPrefix:fooElement)"); 
QueryExpressionType query = new QueryExpressionType();
query.setDialect(TargetedXPathConstants.TARGETED_XPATH_DIALECT);
query.setValue(targetedQuery); 
QueryResourceProperties_Element queryRequest = new QueryResourceProperties_Element(); 
queryRequest.setQueryExpression(query);
QueryResourcePropertiesResponse queryResponse = port.queryResourceProperties(queryRequest); 
                

To query a specific resource property do:

... 
targetedQuery.setNamespaceMappings(nsMap);
targetedQuery.setQueryString("boolean(//fooPrefix:fooElement)"); 
QName rp = new QName("http://foo", "bar"); 
targetedQuery.setResourcePropertyName(rp); 
... 
                

To return query results as an enumeration do:

import org.xmlsoap.schemas.ws._2004._09.enumeration.EnumerationContextType; 
                    
...
                    
targetedQuery.setNamespaceMappings(nsMap);
targetedQuery.setQueryString("boolean(//fooPrefix:fooElement)");
targetedQuery.setEnumerateResults(Boolean.TRUE); 
                    
... 
                    
QueryResourcePropertiesResponse
queryResponse = port.queryResourceProperties(queryRequest); 
EnumerationContextType context = 
        (EnumerationContextType)ObjectDeserializer.getFirstAsObject(queryResponse, EnumerationContextType.class); 
                

9.2. How can I add a new query/topic expression evaluator?

Java WS Core allows for custom query/topic expression evaluators to be plugged in. The process of adding a new query/topic expression evaluator is composed of three steps:

9.2.1. Step 1: Implement the evaluator

Table 2. Evaluator interfaces

If the evaluator is a... then it must implement:
query expression evaluator org.globus.wsrf.query.ExpressionEvaluator
topic expression evaluator org.globus.wsrf.topicexpression.TopicExpressionEvaluator

9.2.2. Step 2: Register the evaluator

The evaluators must be registered in order for Java WS Core to recognize them. The registration is done through the JNDI configuration file. The expression evaluators must be deployed as global resources under a specific subcontext.

9.2.2.1. Registering query expression evaluators

The query expression evaluators must be deployed as global resources under the query/eval/ subcontext in the JNDI configuration file.

Example:

<global> <resource name="query/eval/MyQueryExpressionEval"
                            type="foo.bar.MyQueryExpressionEvaluator">
                            <resourceParams> <parameter>
                            <name>factory</name>
                            <value>org.globus.wsrf.jndi.BeanFactory</value>
                            </parameter> </resourceParams> </resource>
                            </global>

Where the <resource> attribute:

name Specifies the name of the evaluator in JNDI space. The name can be arbitrary as long as it is unique and is in the right subcontext as explained above.
type Specifies the class that implements the expression evaluator.
9.2.2.2. Registering topic expression evaluators

Topic expression evaluators must be deployed as global resources under the topic/eval/ subcontext in the JNDI configuration file.

Example:

                            
                            <global> <resource name="topic/eval/MyTopicExpressionEval"
                            type="foo.bar.MyTopicExpressionEvaluator">
                            <resourceParams> <parameter>
                            <name>factory</name>
                            <value>org.globus.wsrf.jndi.BeanFactory</value>
                            </parameter> </resourceParams> </resource>
                            </global>

Where the <resource> attribute:

name Specifies the name of the evaluator in JNDI space. The name can be arbitrary as long as it is unique and is in the right subcontext as explained above.
type Specifies the class that implements the expression evaluator.

9.2.3. Step 3: Register the serializer/deserializer for the evaluator

A serializer/deserializer must be registered for the dialect of the evaluator in order for the expression to be properly serialized and deserialized. The serializers/deserializers for the dialect are deployed as almost any other type mapping. In general, each type mapping specifies a type QName. For dialect serializers/deserializers, that type QName takes a slightly different name.

9.2.3.1. Specifying the QName for query expression evaluators

For query expression evaluators, that QName must have the local name part set to QueryExpressionDialect and namespace part set to the dialect of the query expression evaluator.

Example:

<typeMapping encodingStyle=""
                            deserializer="org.apache.axis.encoding.ser.SimpleDeserializerFactory"
                            serializer="org.apache.axis.encoding.ser.SimpleSerializerFactory"
                            type="java:java.lang.String"
                            qname="ns12:QueryExpressionDialect"
                            xmlns:ns12="http://foo.bar/MyQueryDialect"/>

[Note]Note

These type mappings must be deployed both on the client and the server.

9.2.3.2. Specifying the QName for topic expression evaluators

For topic expression evaluators, that QName must have the local name part set to TopicExpressionDialect and namespace part set to the dialect of the topic expression evaluator.

Example:

<typeMapping encodingStyle=""
                            deserializer="org.apache.axis.encoding.ser.SimpleDeserializerFactory"
                            serializer="org.apache.axis.encoding.ser.SimpleSerializerFactory"
                            type="java:java.lang.String"
                            qname="ns12:TopicExpressionDialect"
                            xmlns:ns12="http://foo.bar/MyTopicDialect"/>
[Note]Note

These type mappings must be deployed both on the client and the server.

9.2.4. Step 4: Configuring a helper serializer for GetCurrentMessageProvider

The standard GetCurrentMessageProvider might not know how to properly serialize the notification message currently associated with the specified topic. The GetCurrentMessageProvider can be configured to use a helper serializer for a given notification message type.

To configure such a helper serializer, define the following global resource in your deploy-jndi.xml configuration file:

<global> <resource name="providers/GetCurrentMessageProvider/foo.bar.MyNotificationMessage"
                        type="foo.bar.MyMessageSerializer">
                        <resourceParams> <parameter>
                        <name>factory</name>
                        <value>org.globus.wsrf.jndi.BeanFactory</value>
                        </parameter> </resourceParams> </resource>
                        </global>

Where the <resource> attribute:

name Must start with providers/GetCurrentMessageProvider/ and must end with the full class name of the notification message.
type Specifies the class that implements the org.globus.wsrf.encoding.ObjectConverter interface and is responsible for serializing the notification message. The GetCurrentMessageProvider will use the type of the notification message to find the helper serializer.

10. Build Recipes

10.1. How can I process my wsdl using an Ant build file?

Java WS Core supplies some helper build files to simplify the task of processing wsdl. In order to use the helper build files, you should tell ant about GLOBUS_LOCATION. You can set an Ant property to do this:

                        
<property environment="env"/>
<property name="globus_location" value="${env.GLOBUS_LOCATION}">

                

The first property statement creates a property which holds all of the system environment variables and the second assigns the GLOBUS_LOCATION environment variable to the property globus_location. Obviously, this means you need to have set the GLOBUS_LOCATION variable in your environment. Once you have set this, you can use this property in your target.

If you have used the wsdlpp:extends (where wsdlpp is http://www.globus.org/namespaces/2004/10/WSDLPreprocessor) property in your wsdl, the first step to preparing your stubs is to flatten your wsdl file. To run this task, use this:

                    
<ant antfile="${build.stubs}" target="flatten">
    <property name="source.flatten.dir"
        location="${schema.src}/myservice"/>
    <property name="target.flatten.dir"
        location="${schema.dest}/myservice"/>
    <property name="wsdl.source"
        value="myservice_port.wsdl"/>
    <property name="wsdl.target" 
        value="myservice_flattened.wsdl"/>
    <property name="wsdl.porttype" value="MyServicePortType"/>
</ant>                 

                    

The arguments here are fairly self-explanatory, but just to clarify the source.flatten.dir represents the directory that your wsdl file is in, the target.flatten.dir is the directory that you want your processed wsdl file to be written to, wsdl.source is the name of the wsdl file that you are processing, wsdl.target is the name that you want the new wsdl file to be called, and wsdl.porttype is the port type that you are processing (as defined in your wsdl file. You'll notice that there are references to properties such as ${build.stubs}, ${schema.src}, etc. These are just properties I set up. You can set these up to whatever you wish (or just use the paths directly instead of using properties).

The next step is to generate the bindings for your service. This is very similar to the previous step:

                        
<target name="binding" depends="flatten">
                            
    <ant antfile="${build.stubs}" target="generateBinding">
        <property name="source.binding.dir"
            location="${schema.dest}/myservice"/>
        <property name="target.binding.dir" location="${schema.dest}/myservice"/>
        <property name="binding.root" value="myservice"/>
        <property name="porttype.wsdl"
            value="myservice_flattened.wsdl"/>
    </ant>
</target>                        

                    

Again, the arguments should be fairly clear. The source.binding.dir is the directory where the wsdl that you want to bind resides (this will almost always be the target.flatten.dir from the previous step). The target.binding.dir is the directory where the bindings will be written. The binding.root is the base name of the outputted wsdl files, and the porttype.wsdl is the name of the wsdl file to bind (This will almost always be the name of the wsdl.target from the previous step).

The final step to prepare your wsdl is to generate the stub classes. These are the java classes to which you will refer in your service and your clients. Similar to the previous two steps:

                        
<target name="stubs" depends="binding">
    <ant antfile="${build.stubs}" target="mergePackageMapping">
        <property name="mapping.src" value="${basedir}/namespace2package.properties"/>
        <property name="mapping.dst" value="${build.dir}/namespace2package.properties"/>
    </ant>
                            
    <ant antfile="${build.stubs}" target="generateStubs">
        <property name="source.stubs.dir"
            location="${schema.dest}/test/performance"/>
        <property name="target.stubs.dir" location="${stubs.src}"/>
        <property name="wsdl.file"
            value="performance_test_service.wsdl"/>
        <property name="mapping.file" value="${build.dir}/namespace2package.properties"/>
        </ant>
</target>

                    

There are actually two Globus-provided tasks in this target. The first one is optional. If you have custom mappings to map namespaces to Java package names, you can create a properties file that lists those mappings. Then use the mergePackageMapping task to merge your mappings with the default mappings. The parameters are: mapping.src -- where your custom mappings live and mapping.dst -- where you want the merged file to be written.

The final task here (generateStubs) actually generates the stub classes. The parameter names here are not entirely intuitive. source.stubs.dir is the directory where your wsdl's live. target.stubs.dir is where you want the Java source files to be written. wsdl.file is the name of your wsdl file, and mapping.file is the location of the (optional) namespace-package mapping file.

Once you have run all of these steps, you should generated all of the java stubs and bindings that you need to build your service.

10.2. How can I create a GAR?

To create a GAR file use the following example:

                    
<property name="build.packages" location="${deploy.dir}/share/globus_wsrf_common/build-packages.xml"/>
... 
<property name="garjars.id" value="garjars"/> 
<fileset dir="lib" id="garjars"/> 
<property name="garetc.id" value="garetc"/>
<fileset dir="etc" id="garetc"/> 
...

<target name="dist" depends="...">
    <ant antfile="${build.packages}"target="makeGar"> 
        <property name="gar.name" value="mygar.gar"/>
        <reference refid="${garjars.id}"/> 
        <reference refid="${garetc.id}"/> 
    </ant>
</target> 

                    

The gar.name property must be passed. That property specifies the gar file to create. The makeGar task will look for deploy-client.wsdd, deploy-server.wsdd, and deploy-jndi-config.xml files in the base directory of the calling Ant process. All of these files are optional and do not have exist. The list of files to be included in the GAR file is passed via Ant references. The makeGar accepts the following references: garjars.id, garschema.id, garetc.id, garshare.id, gardocs.id, and garbin.id. All of these references are optional and do not have to be defined.

In the above example, all files in the etc and lib directories, and the deploy-client.wsdd, deploy-server.wsdd, and deploy-jndi-config.xml files (if they exist) will be included into the GAR file.

10.3. How can I deploy my GAR?

To deploy a GAR file use the following example:

                    
<property name=&quot;build.packages&quot; location=&quot;${deploy.dir}/share/globus_wsrf_common/build-packages.xml&quot;/>
... 
<target name=&quot;deploy&quot; depends=&quot;...&quot;> 
    <ant antfile=&quot;${build.packages}&quot; target=&quot;deployGar&quot;> 
        <property name=&quot;gar.name&quot; value=&quot;mygar.gar&quot;/>
    </ant> 
</target> 

                    

The gar.name property must be passed. That property specifies the gar file to deploy. Optionally, the profile property can be passed to indicate which configuration profile the gar should be deployed under.

10.4. How can I generate a laucher for my client?

Bourne Shell and Windows batch scripts can be automatically generated to hide the details of launching a Java program from the command line.

To generate such a command line script, write a Ant task that calls the generateLauncher target in $GLOBUS_LOCATION/share/globus_wsrf_common/build-launcher.xml. The following properties/parameters must be specified:

  • ${launcher-name} - the base name of script to generate.
  • ${class.name} - the name of Java class the script must call.

For example:


... 
<property name="env.GLOBUS_LOCATION" value="."/> 
<property name="deploy.dir" location="${env.GLOBUS_LOCATION}"/>
<property name="abs.deploy.dir" location="${deploy.dir}"/> 
<property name="build.launcher" location="${abs.deploy.dir}/share/globus_wsrf_common/build-launcher.xml">
                    
... 

<ant antfile="${build.launcher}" target="generateLauncher"> 
    <property name="launcher-name" value="myClient"/>
    <property name="class.name" value="org.mypackage.MyClient"/> 
</ant>

                

It is also possible to specify default JVM options and command line options via the default.jvm.options and default.cmd.line parameters. When passing multiple parameters using default.jvm.options for Unix/Linux scripts the parameters must be separated by ${DELIM} delimiter. For example:


<target name="generateUnixScripts" if="generate.unix" depends="testUnix">
<ant antfile="${build.launcher}" target="generateLauncher"> 

... 

<property name="default.jvm.options" value="-DFOO="$FOO"${DELIM}-DBAR="$BAR"/>
</ant> 
</target>

                    

In general the generation of the command line scripts is done in the post-deploy.xml script during GAR deployment (globus-deploy-gar).

10.5. How do I create a client distribution for my service?

There is no automated process for this, but there are a few simple steps that can provide the pieces that you need. (Note, this is one possible approach. Other methods can work too.) First, when you are building your service, you need to generate your stubs in their own directory. From Ant, this would look like this (from the CounterService example):


<target name="stubs" unless="stubs.present" depends="init">
    <ant antfile="${build.stubs}" target="generateStubs">
        <property name="source.stubs.dir" 
            location="${schema.dest}/core/samples/counter"/>
        <property name="target.stubs.dir" location="${stubs.src}"/>
        <property name="wsdl.file" 
            value="counter_service.wsdl"/>
    </ant>
</target>
                    
                

The source.stubs.dir determines where the stub classes are created. This, in turn, will allow us to compile those classes into their own compile directory like this (also from the CounterService example):


<target name="compileStubs" depends="stubs">
       <javac srcdir="${stubs.src}" destdir="${stubs.dest}" 
            debug="${java.debug}">
            <include name="**/*.java"/>
            <classpath>
                <fileset dir="${deploy.dir}/lib">
                    <include name="common/*.jar"/>
                    <include name="*.jar"/>
                    <exclude name="${stubs.jar.name}"/>
                    <exclude name="${jar.name}"/>
                </fileset>
            </classpath>
        </javac>
        <copy todir="${build.dest}" >
              <fileset dir="src" includes="**/*.properties" />
              <fileset dir="src" includes="**/*.xml" />
        </copy>
</target>
                                        
                

So, now we have our stubs compiled into their own directory and copied all resource files into the same directory. The final step in preparing the stubs is to jar them up:


<target name="jarStubs" depends="compileStubs">
    <jar destfile="${build.lib.dir}/${stubs.jar.name}" 
        basedir="${stubs.dest}"/>
</target>
                    
                

At this point, we have a jar which contains all of our stubs. This will be used on both the server and by the clients. Now, you need to write your client code (probably in it's own src dirctory), compile it and create a jar for it. These two jars are the only jars you need to produce. In addition you need to make sure that you have an installation of Java WS Core available. The root directory of your installation should have both a client-config.wsdd and a log4j.properties file in it. You can combine this with the launcher example above to build a launcher for the client jars which you have just generated.

11. Controlling client connection settings

By default Java WS Core clients will use HTTP 1.1 protocol with chunked encoding. Java WS Core clients will also attempt to reuse HTTP/S connections between the calls. The default timeout for clients is 10 minutes. All of these connection properties can be controlled programmatically using the HTTPUtils API as shown below.

[Note]Note

Please note that once a connection property is set on a given Stub, it is applied to ALL calls made using that Stub.

11.1. How can I set the timeout on my client?

To set connection timeout do (the timeout value is in milliseconds):

 
import org.globus.axis.transport.HTTPUtils; 

... 

MyServiceAddressingLocator locator = new MyServiceAddressingLocator(); 
MyService port = locator.getMyServicePort(url); // set timeout to 2 minutes
HTTPUtils.setTimeout((Stub)port, 1000 * 60 * 2); 
port.hello();
                        

11.2. How can I reuse my connection?

To control connection reuse do:

import org.globus.axis.transport.HTTPUtils; 
                            
... 
                            
MyServiceAddressingLocator locator = new MyServiceAddressingLocator(); 
MyService port = locator.getMyServicePort(url); // close connection after the call
HTTPUtils.setCloseConnection((Stub)port, true); 
port.hello();
// do not close connection - let it be reused
HTTPUtils.setCloseConnection((Stub)port, false); 
port.hello();
                        

11.3. How can I control HTTP chunked encoding

To control whether HTTP chunked encoding should be used do:

import org.globus.axis.transport.HTTPUtils;
                            
... 
                            
MyServiceAddressingLocator locator = new MyServiceAddressingLocator(); 
MyService port = locator.getMyServicePort(url); // disable chunked encoding
HTTPUtils.setChunkedEncoding((Stub)port, false); 
port.hello();
// re-enable chunked encoding 
HTTPUtils.setChunkedEncoding((Stub)port, true); 
port.hello(); 
                        

11.4. How can I control the HTTP protocol version?

To control what HTTP protocol version should be used do:

import org.globus.axis.transport.HTTPUtils; 
                            
... 

MyServiceAddressingLocator locator = new MyServiceAddressingLocator(); 
MyService port = locator.getMyServicePort(url); 
// force HTTP 1.0
HTTPUtils.setHTTP10Version((Stub)port, true); port.hello(); 
//force HTTP 1.1 
HTTPUtils.setHTTP10Version((Stub)port, false);
port.hello(); 
                        

12. Writing and running tests

One of the most important tasks in developing services is to create tests to assure that your code functions correctly. The following recipes discuss creating tests for Java WS Core.

12.1. What is JUnit and how does it relate to Java WS Core?

Tests in the Java WS Core are based on the JUnit API. JUnit must first be installed with Ant. To install JUnit with Ant copy the junit.jar found in JUnit distribution to the $ANT_HOME/lib directory. Alternatively, you can add the junit.jar to your CLASSPATH, or source$GLOBUS_LOCATION/etc/globus-devel-env.sh.

12.2. How do I write JUnit tests for services?

Always make sure to group your tests under the PackageTests.java and/or SecurityTests.java test suites. Put all tests that require any type of credentials in the SecurityTests.java test suite.

If you are writing basic unit tests that do not require a container to run, just use the regular JUnit classes to write such tests.

If you are writing tests that require a container to execute, use the org.globus.wsrf.test.GridTestCase class instead of junit.framework.TestCase as your base class for your tests. Also ensure your PackageTests.java or SecurityTests.java extends the org.globus.wsrf.test.GridTestSuite instead ofjunit.framework.TestSuite.

The org.globus.wsrf.test.GridTestSuite and org.globus.wsrf.test.GridTestCase must be used together. The org.globus.wsrf.test.GridTestCase class exposes a TEST_CONTAINER variable that can be used to obtain the URL of the container (TEST_CONTAINER.getBaseURL()). By default an embedded container will be started for all tests in the test suite. To specify an external container, pass the -Dweb.server.url=<base.url> system property on the java command line.

12.3. How do I run my JUnit tests for Java WS Core and/or my services?

12.3.1. Group testing

To execute all tests contained in a given jar file with an internal container run the following:

$ cd $GLOBUS_LOCATION $ ant -f share/globus_wsrf_test/runtests.xml run
                    -Dtests.jar=<test.jar>

Where <test.jar> is an absolute path to the jar file that contains the tests.

To execute all tests contained in a given jar file with an external container run the following:

$ cd $GLOBUS_LOCATION $ ant -f share/globus_wsrf_test/runtests.xml runServer
                    -Dtests.jar=<test.jar>

By default, the external container is assumed to be running athttp://localhost:8080/wsrf/services/. To specify a different container, use the -Dtest.server.url=<url> property.

By default, all PackageTests and SecurityTests tests will be executed. To execute PackageTests only, specify -DbasicTestsOnly=true option. To execute SecurityTests only, specify -DsecurityTestsOnly=true option.

By default, the test results will be generated in the XML format.

12.3.2. How do I run a single test?

To execute a single test suite with an internal container run the following:

$ cd $GLOBUS_LOCATION $ ant -f share/globus_wsrf_test/runtests.xml runOne
                    -Dtest.class=<test.class>

Where <test.class> is a Java class that contains a test suite.

To execute a single test suite with an external container run the following:

$ cd $GLOBUS_LOCATION $ ant -f share/globus_wsrf_test/runtests.xml runOneServer
                    -Dtest.class=<test.class>

By default, the external container is assumed to be running athttp://localhost:8080/wsrf/services/. To specify a different container, use the -Dtest.server.url=<url> property.

By default, the test results will be generated in the plain text format.

It is also possible to execute a single test case (or a set of test cases) within a test suite by specifying a -Dtests=<testCase1[,testCaseN]> property. However, this will only work with test suites that inherit from org.globus.wsrf.test.FilteredTestSuite or org.globus.wsrf.test.GridTestSuite classes. Example:

$ cd $GLOBUS_LOCATION $ ant -f share/globus_wsrf_test/runtests.xml runOne \
                    -Dtest.class=org.globus.interop.widget.test.PackageTests \
                    -Dtests="testScenario1,testScenario2"

12.3.3. How do I configure test output and options?

The test reports will be put in the $GLOBUS_LOCATION/share/globus_wsrf_test/tests/test-reports directory by default. A different test reports directory can be specified by passing -Djunit.reports.dir=<directory>.

Use -Djunit.test.format property to generate the test results in a specified format ( xml orplain). Example:

$ ant -f share/... -Djunit.test.format=plain

Use -Djunit.jvmarg to pass arbitrary properties to the testing JVM. Example:

$ ant -f share/...
                    -Djunit.jvmarg="-Dorg.globus.wsrf.container.server.id=myServerID"
                

13. Miscellaneous Recipes

13.1. How do I obtain Version Information about Java WS Core

The Version API can be used to obtain Java WS Core version information programmatically. For example to display major, minor and patch version information do:

import org.globus.wsrf.utils.Version;
                        
... 
                        
System.out.println("Major: " + Version.getMajor()); 
System.out.println("Minor: " + Version.getMinor());
System.out.println("Micro: " + Version.getMicro()); 
                    

13.2. How can I use SOAP Attachments with my service?

Java WS Core supports SOAP with Attachments. DIME, MIME, and MTOM formats are supported. This section provides brief sample code. Detailed code can be found in the automated tests for this feature at, http://viewcvs.globus.org/viewcvs.cgi/wsrf/java/core/test/unit/src/org/globus/wsrf/impl/security/ AttachmentTestService.java and AttachmentsTests.java.

To add an attachment to a request do:

import javax.activation.DataHandler; 
import javax.activation.FileDataSource;                            
import javax.xml.rpc.Stub; 
import org.apache.axis.client.Call; 
                    
.... 
                    
File file = new File(..); 
DataHandler dataHandler = new DataHandler(new FileDataSource(file));
((Stub)port)._setProperty( Call.ATTACHMENT_ENCAPSULATION_FORMAT, Call.ATTACHMENT_ENCAPSULATION_FORMAT_MTOM);
((org.apache.axis.client.Stub)port).addAttachment(dataHandler); 
                

To retrieve attachments associated with a request do:

import javax.activation.DataHandler; 
import javax.xml.soap.AttachmentPart; 
import org.apache.axis.Message; 
import org.apache.axis.MessageContext; 
                    
.... 
                    
MessageContext msgContext = MessageContext.getCurrentContext(); 
Message reqMsg = msgContext.getRequestMessage(); 
if (reqMsg.getAttachmentsImpl() == null) { 
    throw new Exception("Attachments are not supported"); 
} 
Iterator it = reqMsg.getAttachments();
while (it.hasNext()) { 
    AttachmentPart part = (AttachmentPart) it.next(); 
    DataHandler dataHandler = part.getDataHandler(); 
} 
                

The SwA support adds a small overhead to overall SOAP processing. To disable SwA support for improved performance delete $GLOBUS_LOCATION/lib/common/geronimo-activation_1.0.2_spec-1.1-SNAPSHOT.jar and $GLOBUS_LOCATION/lib/common/geronimo-javamail_1.3.1_spec-1.1-SNAPSHOT.jar files.

Please note that SOAP attachments can be used with message security but they will not be signed or encrypted.

13.3. How can I make sure my WSDL/SOAP documents are valid for GT 4?

The WSRF and WSN specifications schemas follow the document/literal mode as described in WS-I Basic Profile. The Basic Profile defines certain rules to follow for document/literal and other modes to ensure interoperability.

Java WS Core relies on these restrictions so please keep them in mind when designing your own schema.

13.3.1. Document/literal

In the document/literal mode as defined in the WS-I Basic Profile at most one <wsdl:part> is allowed in the <wsdl:message> element and it must use the 'element' attribute. Also, the wire signatures must be unique (cannot use the same 'element' attribute in <wsdl:part> in two different <wsdl:message> elements).

[Note]Note

Axis' WSDL2Java tool might sometimes incorrectly detect that schema follows the wrapped/literal mode and generate wrong stub and type classes. To ensure that document/literal mode is always used:

  • use Java WS Core's generateStub* Ant tasks in <install>/share/globus_wsrf_tools/build-stubs.xml file
  • if you are using Axis' WSDL2Java tool directly, you can alternatively specify the -W command line option.

Also, with wrapped/literal mode, the element name had to match the operation name in wsdl. This is not necessary with document/literal mode.

13.3.2. SOAP Encoding

Do not use or mix the literal mode with the SOAP encoding mode (R2706). For example, do not use the soapenc:Array type. Please see the 5.2.3 section in the WS-I Basic Profile for details.

13.4. How can I retrieve service parameters from my WSDD file?

While we strongly recommend that you use the JNDI mechanism to provide your service with configuration information, it is sometimes necessary to obtain the value of parameters set in the WSDD file although it is extremely rare. If you aren't sure that you need to do this, you likely do not. Java WS Core provides some helper functions to ease this process:

                    
import org.globus.wsrf.utils.ContextUtils; 
import org.apache.axis.MessageContext;

... 

MessageContext context = MessageContext.getCurrentContext(); 
String sampleProperty = (String) ContextUtils.getServiceProperty(context, "myProperty"); 

... 

                

Note that this function requires that a MessageContext is associated with the current thread, which in general means that the call needs to happen within the context of a web service invocation.

[Note]Note

Specifying parameters using WSDD files depends on Axis and will likely not be supported in future versions of the toolkit.

13.5. How do I get standard MessageContext properties?

The following properties can be obtained from the SOAPContext/MessageContext associated with the current thread:

  • org.apache.axis.Constants.MC_HOME_DIR - the base directory from which the wsdl files are loaded.
  • org.apache.axis.Constants.MC_CONFIGPATH - the base directory from which different configuration files are loaded.
  • org.apache.axis.Constants.MC_REMOTE_ADDR - the IP address of the client.
  • org.apache.axis.MessageContext.TRANS_URL - the URL of the request.

The Constants.MC_CONFIGPATH property should be used to load any type of configuration file. Only Constants.MC_CONFIGPATH and Constants.MC_HOME_DIR are associated with the thread during activation. In the standalone container the Constants.MC_HOME_DIR and Constants.MC_CONFIGPATH properties will usually point to the same directory. However, in Tomcat they will point to two different directories. Since GT 4.0.1, the Constants.MC_HOME_DIR value can be accessed using the org.globus.wsrf.ContainerConfig.getSchemaDirectory() static call, and Constants.MC_CONFIGPATH value via the org.globus.wsrf.ContainerConfig.getBaseDirectory() static call.