Access Keys:
Skip to content (Access Key - 0)

Introduce

Introduce 1.2 Developers Guide

Contents


Introduce Service Developers


Deserialization and Serialization Examples

Introduce comes with helper tools for serializing and deserializing objects.

Deserialization

The example below if the the cagrid transfer service. This example is deserializing an xml file into a TranasferServiceContextResourceProperties object.

        TransferServiceContextResourceProperties props = null;
        try {
            props = (TransferServiceContextResourceProperties) Utils.deserializeObject(new FileReader(persistenceDir
                + File.separator + requestedID + ".xml"), TransferServiceContextResourceProperties.class);
        } catch (Exception e) {
            logger.info("Cannot find or deserialize the resource properties describing this transfer object: "
                + requestedID);
            e.printStackTrace();
        }

Serialization

Write the object out that we serialized above. This will use the QName of the schema element that this object adheres to and write the object out to the test.xml file.

        try {
            Utils.serializeDocument("test.xml", props, new QName("http://transfer.cagrid.org/TransferService/Context", "TransferServiceContextResourceProperties"));
        } catch (Exception e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

Debugging my service

[caGrid debugging article]

Using the Client

Introduce generates a client API for the service which is exactly as described within the graphical editing environment (see Auto-boxing/Unboxing or Service Operations). This client API can be used in order to leverage this type of service from another application or service. The API contains four constructors which can use used, each of which is different depending on having a handle or just an address and the need for security to be used.

 /**
  * Takes in the url of the service to connect to as a string
  */
 HelloWorldClient(String url)

 /**
  * Takes in the url of the service to connect to as a string and
  * a proxy to be used to represent the credentials or the caller
  */
 HelloWorldClient(String url, GlobusCredential proxy)

 /**
  * Takes in the epr which refers to the service or resource
  */
 HelloWorldClient(EndpointReferenceType epr)

 /**
  * Takes in the epr which refers to the service or resource and
  * a proxy to be used to represent the credentials or the caller
  */
 HelloWorldClient(EndpointReferenceType epr, GlobusCredential proxy)


Once a client handle is constructed each of the operations which were created in the service are available as operations to this newly constructed client instance. Below is an example snippet of code which creates a new client handle to a service called "HelloWorld" and calls the "echo" operation.

try {
 HelloWorldClient client = new HelloWorldClient("http://localhost:8080/wsrf/services/HelloWorld");
 client.echo("Testing)";
} catch (Exception e) {
 System.out.println("Problem creating handle to or calling service" + e.getMessage(), e);
}

Advance Client Usage

Printing Detailed Faults

The example below shows how a client can print a more detailed stack trace from an exception that is received from the service.

     try {
       ...
     } catch (Exception e) {
	gov.nih.nci.cagrid.common.FaultUtil.printFault(e);
     }

Using Subscription and Notification

If the Introduce service you are using supports the Notification resource framework option than the client will be enabled for subscribing to that service and listening for particular changes to resource properties. An example below shows how to perform the subscription:

     IntroduceTestNotificationServiceClient client = new IntroduceTestNotificationServiceClient("http://localhost:8080/wsrf/services/IntroduceTestNotificationServiceClient");
     client.subscribe(new QName("http://testing.org","TestResourceProperty");

In the above code we have an example client that is subscribing to a service to listen for changes to the TestResourceProperty resource property of the service. If the value of this resource property gets changed in the resource a notification will be sent out to all registered subscribers. Now we need to implement some code so that we receive these notifications. The introduce client generated for this service will automatically be able to listen for these changes. For us to be able to perform our own actions when we receive a notification we must overload the deliver operation in our client:

    public void deliver(List topicPath, EndpointReferenceType producer, Object message) {
        org.oasis.wsrf.properties.ResourcePropertyValueChangeNotificationType changeMessage = ((org.globus.wsrf.core.notification.ResourcePropertyValueChangeNotificationElementType) message)
            .getResourcePropertyValueChangeNotification();

        if (changeMessage != null) {
            recievedNotificationCount++;
            try {
                System.out.println("GOT NOTIFICATION: " + changeMessage.getNewValue().get_any()[0].getAsString());
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

Loading a Proxy from a File

If you want to use a specific proxy file, other than the locations that Globus will automatically look. The ProxyUtil class can be used to load the proxy and create a GlobusCredential.

 GlobusCredential creds = null;
        try {
           creds = gov.nih.nci.cagrid.common.security.ProxyUtil.loadProxy("user.proxy");
           System.out.println("Using proxy with id= " + creds.getIdentity() + " and lifetime "
                            + creds.getTimeLeft());
        } catch (Exception e1) {
           System.out.println("No proxy file loaded so running with no credentials");
        }

Implementing the Service

When an operation is added in the Introduce GDE and hte save button is clicked. Introduce will add the new stubbed method into the <service package>.service.<service name>Impl.java class. The developer is then responsible for implementing the method prior to deployment. For example the snippet below would be generated in the <service package>.service.<service name>Impl.java if the developer added an add operation to the service with Introduce that took in two integers and returned and integer.

  public int add(java.math.BigInteger a,java.math.BigInteger b) throws RemoteException {
    //TODO: Implement this autogenerated method
    throw new RemoteException("Not yet implemented");
  }

The developer would then have to edit this method to implement the logic the service would execute on invokation.

  public int add(java.math.BigInteger a,java.math.BigInteger b) throws RemoteException {
    return a + b;
  }

Advanced Service Development Topic

Obtaining the Callers Identity

In the service it is sometime required to get the identity of the caller. The identity is carried in the context of the message which was passed into the service. In order to get this information the following lines will be needed.

     String userDN = gov.nih.nci.cagrid.introduce.servicetools.security.SecurityUtils.getCallerIdentity()

Accessing Service Properties

Service properties are the properties that the main service can be configured with that all service contexts deployed with that service can access. These properties are configured at deploy time and will likely need to be accessed in the service. Below is example code for how to access these properties where the main service was named HelloWorld and the name of the service property was testing.

     HelloWorldConfiguration.getConfiguration().getTesting();

Getting a Handle to a Resource

When developing in the service context you must remember that the ServiceImpl class is just the service interface but the service should act on the addressed resource as there may be more than one resource instance being used by the service at a time. An example of how to get a handle to the resource which is being addressed by the current caller (exmaple from the counter service context as described in in the section on Utilizing the factory patter):

public void incrementCounter() throws RemoteException {
      int currentCounter;
      try {
          currentCounter = getResourceHome().getAddressedResource().getIntValue();

          currentCounter++;
          getResourceHome().getAddressedResource().setIntValue(currentCounter);
      } catch (Exception e) {
          e.printStackTrace();
      }
  }

Programmatically Accessing and/or Editing Resource Properties (Metadata)

From the resource there will be getters and setters automatically generated for the resource properties of your service. For instance, if you look at the above example, you can see that the counter service has an "int" resource property. This enables introduce to automatically generated getters and setters for that resource property that the service implementer can use to access and modify values in the resource properties.

Creating a Security Descriptor for a Resource

Sometimes it is necessary to set a security descriptor on an instance of a resource. This might be to protect resource instances so that only the creator of the resource can use it. In order to do this you have to make sure that when the context was generated in introduce that you had selected the "secure" resource framework option. This will enable the resource to have a setSecurityDescriptor() method enabled on it. When the resource is being created, as in the example below, the security descriptor should be generated and set on the resource. Once set it will be utilized on any access to the resource. Below is an example method from the counter service used in the utilizing a factory patter section. This is the method that Introduce generated to create the counter instance for the caller. You can see in this code that there is a place documenting how to set the security descriptor on the resource.

public example.counter.context.stubs.types.CounterContextReference createCounter() throws RemoteException {
		org.apache.axis.message.addressing.EndpointReferenceType epr = new org.apache.axis.message.addressing.EndpointReferenceType();
		example.counter.context.service.globus.resource.BaseResourceHome home = null;
		org.globus.wsrf.ResourceKey resourceKey = null;
		org.apache.axis.MessageContext ctx = org.apache.axis.MessageContext.getCurrentContext();
		String servicePath = ctx.getTargetService();
		String homeName = org.globus.wsrf.Constants.JNDI_SERVICES_BASE_NAME + servicePath + "/" + "counterContextHome";

		try {
			javax.naming.Context initialContext = new javax.naming.InitialContext();
			home = (example.counter.context.service.globus.resource.BaseResourceHome) initialContext.lookup(homeName);
			resourceKey = home.createResource();

			//  Grab the newly created resource
			example.counter.context.service.globus.resource.CounterContextResource thisResource = (example.counter.context.service.globus.resource.CounterContextResource)home.find(resourceKey);

			//  This is where the creator of this resource type can set whatever needs
			//  to be set on the resource so that it can function appropriatly  for instance
			//  if you want the resouce to only have the query string then there is where you would
			//  give it the query string.


			// sample of setting creator only security.  This will only allow the caller that created
			// this resource to be able to use it.
	                //thisResource.setSecurityDescriptor(gov.nih.nci.cagrid.introduce.servicetools.security.SecurityUtils.createCreatorOnlyResourceSecurityDescriptor());



			String transportURL = (String) ctx.getProperty(org.apache.axis.MessageContext.TRANS_URL);
			transportURL = transportURL.substring(0,transportURL.lastIndexOf('/') +1 );
			transportURL += "CounterContext";
			epr = org.globus.wsrf.utils.AddressingUtils.createEndpointReference(transportURL,resourceKey);
		} catch (Exception e) {
			throw new RemoteException("Error looking up CounterContext home:" + e.getMessage(), e);
		}

		//return the typed EPR
		example.counter.context.stubs.types.CounterContextReference ref = new example.counter.context.stubs.types.CounterContextReference();
		ref.setEndpointReference(epr);

		return ref;
  }

Setting the Termination Time on a Resource

When the lifetime resource framework option is set on a service, the resources of that service will be able to have tier lifetime managed. From the client side a SetTerminationTime() or Destroy() grid call can be made to the service either setting the time to terminate or destroying the addressed resource. You can also call the set termination time directly on the resource inside the service. An example below, utilizing the same code as above will enable us create counters for users that will only be alive for 5 minutes:

public example.counter.context.stubs.types.CounterContextReference createCounter() throws RemoteException {
		org.apache.axis.message.addressing.EndpointReferenceType epr = new org.apache.axis.message.addressing.EndpointReferenceType();
		example.counter.context.service.globus.resource.BaseResourceHome home = null;
		org.globus.wsrf.ResourceKey resourceKey = null;
		org.apache.axis.MessageContext ctx = org.apache.axis.MessageContext.getCurrentContext();
		String servicePath = ctx.getTargetService();
		String homeName = org.globus.wsrf.Constants.JNDI_SERVICES_BASE_NAME + servicePath + "/" + "counterContextHome";

		try {
			javax.naming.Context initialContext = new javax.naming.InitialContext();
			home = (example.counter.context.service.globus.resource.BaseResourceHome) initialContext.lookup(homeName);
			resourceKey = home.createResource();

			//  Grab the newly created resource
			example.counter.context.service.globus.resource.CounterContextResource thisResource = (example.counter.context.service.globus.resource.CounterContextResource)home.find(resourceKey);

			//  This is where the creator of this resource type can set whatever needs
			//  to be set on the resource so that it can function appropriatly  for instance
			//  if you want the resouce to only have the query string then there is where you would
			//  give it the query string.


			// sample of setting creator only security.  This will only allow the caller that created
			// this resource to be able to use it.
			//thisResource.setSecurityDescriptor(gov.nih.nci.cagrid.introduce.servicetools.security.SecurityUtils.createCreatorOnlyResourceSecurityDescriptor());

			//set the termination time of this resource
                        Calendar cal = new GregorianCalendar();
                        cal.add(Calendar.MINUTE,5);
                        thisResource.setTerminationTime(cal);

			String transportURL = (String) ctx.getProperty(org.apache.axis.MessageContext.TRANS_URL);
			transportURL = transportURL.substring(0,transportURL.lastIndexOf('/') +1 );
			transportURL += "CounterContext";
			epr = org.globus.wsrf.utils.AddressingUtils.createEndpointReference(transportURL,resourceKey);
		} catch (Exception e) {
			throw new RemoteException("Error looking up CounterContext home:" + e.getMessage(), e);
		}

		//return the typed EPR
		example.counter.context.stubs.types.CounterContextReference ref = new example.counter.context.stubs.types.CounterContextReference();
		ref.setEndpointReference(epr);

		return ref;
  }

Utilizing the Factory Pattern

Link to the entire example service source code described below

Utilizing Grid Service Factory Pattern


In order to create instances of a resource something must tell the resource home of the service to create them. This is typically done using the factory pattern. A service or method is responsible for telling the resource to create a new instance of a resource type for the service. An example of the factory pattern in action is show it the image above. A factory service, the Counter Grid Service, contains a create operation which is exposed as a grid service method. When the client invokes this method the Counter Grid Service will locate the resource home of the Counter Context Grid Service and ask it to create a new resource instance. Once this resource intance is created the resource home will return a pointer or address to this resource instance called an EndPointReference (EPR). This EPR is then returned from the create method back to the client so that it has a pointer to these new resource that it can act on.
In order to act on this resource it will need to construct a client which can talk to the grid service which represents this resource type. The Counter Context Client will be constructed using the EPR to address the specific resource context for the service. Next the user would like to call the incrementCounter() operation on the Counter Context Grid Service. The client will make the call to the grid service and the service will then look at the EPR sent over by the client. The service will then take the EPR and give it to the resource home and ask for the resource instance back which is represented by the particular EPR. The the service will run it's logic to increment the counter, which in this case is represented as a resource property of the resource.
Invoking a Statefull Grid Services


Below shows an example of the code that would be required to implement the incrementCounter() method in the CounterContextServiceImpl.java.

package example.counter.context.service;

import java.rmi.RemoteException;


/**
 * TODO:I am the service side implementation class. IMPLEMENT AND DOCUMENT ME
 *
 * @created by Introduce Toolkit version 1.1
 */
public class CounterContextImpl extends CounterContextImplBase {

    public CounterContextImpl() throws RemoteException {
        super();
    }


    public void incrementCounter() throws RemoteException {
        int currentCounter;
        try {
            currentCounter = getResourceHome().getAddressedResource().getIntValue();

            currentCounter++;
            getResourceHome().getAddressedResource().setIntValue(currentCounter);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

In Introduce 1.1, the "incrementCounter()" operation shown above is implemented in the service context implementation class (CounterContextImpl). The context operations implemented in this class are called each time a context client calls an operation. If the service context you are writing (as in the counter case above) maintains state for each context instance between calls, then that state can be accessed via the "getResourceHome().getAddressedResource()" call shown above. This call returns the specific instance of a CounterContextResource class that holds the state for the context client. In summary, the CounterContextImpl class is a stateless class that simply implements the context operations. The CounterContextImpl class uses the CounterContextResource instance specific to the EPR used by the client to access and maintain state for the client who is currently calling the context operation. In the above example, the state in the CounterContextResource is the current count retrieved in the impl via the "getIntValue" method (which is a service context property added via Introduce GDE).

Introduce Toolkit Developers


Creating an Update Site

Providing an update site is one way to enable Introduce users to be able to obtain your extensions or modified versions of Introduce. An update site is simply an address which points to a web folder that contains a software.xml file adhering to the schema. The software.xml file describes the packages available at the particular update site and the release zip which contains those packages.

<ns1:Software
    xmlns:ns1="gme://gov.nih.nci.cagrid.introduce/1/Software">
   <ns1:Introduce version="1.0" zipFileURL="http://bmi.osu.edu/~hastings/introduce/software/introduce_1-0.zip" />
   <ns1:Introduce version="1.1" zipFileURL="http://bmi.osu.edu/~hastings/introduce/software/introduce_1-1.zip" />
   <ns1:Extension version="1.1" compatibleIntroduceVersions="1.1" displayName="caGrid Data Service Extension" name="data" zipFileURL="http://bmi.osu.edu/~hastings/introduce/software/data.zip"/>
	<ns1:Extension version="1.1" compatibleIntroduceVersions="1.1" displayName="caGrid Bulk Data Transfer" name="bdt" zipFileURL="http://bmi.osu.edu/~hastings/introduce/software/bdt.zip"/>
	<ns1:Extension version="1.1" compatibleIntroduceVersions="1.0,1.1" displayName="caGrid Extensions Package" name="cabigextensions" zipFileURL="http://bmi.osu.edu/~hastings/introduce/software/cabigextensions.zip"/>
	<ns1:Extension version="1.1" compatibleIntroduceVersions="1.0,1.1" displayName="caGrid Cancer Data Standards Repository" name="cadsr" zipFileURL="http://bmi.osu.edu/~hastings/introduce/software/cadsr.zip"/>
</ns1:Software>

Writing an Extension

When writing an extension, it is first important to chose which type of extension that you want to write. A description of the extension framework and the important extension types is documented here There are extensions that add functionality to introduce and there are extensions designed to add functionality to a service. The most common introduce extension type is the service extension. This type of extension is designed to add functionality to a service. The best place to learn about extension is just to look at one. Here are a list of the service extension that can be found in the cagrid repository that are great places to start.

  • cagrid/projects/data
  • cagrid/projects/cabigextensions
  • cagrid/projects/bdt
  • cagrid/projects/transfer

Every extension revolves around its extension.xml. The extension.xml describes what type the extension is, and what are the classes that need to be invoked in order to execute the extension.

Bundling an Extension

In order to bundle up an extension so that it can be deployed to introduce the extension directory structure must be laid out in the following way:

  • <top of zip file>
    • lib (any jars required to "execute" your extension
    • <your extension name (not display name)>
      • extension.xml (the extension.xml describing your extension)

Once this layout is complete, the next step is to create the extension zip file. In order to do this, all that is required is to create a zip file with no top level directory and the lib dir and <your extension name> dir as the only directories in the zip. So for instance, if the zip file were extracted there would be the two directories with no top level directory. Now that you have bundled up your extension so that it can be delivered and used by Introduce.

Last edited by
Knowledge Center (1519 days ago) , ...
Adaptavist Theme Builder Powered by Atlassian Confluence