Service Oriented Architecture
Inside the subversion repository you will find MyProxyWebService. In here are three sub-folders which demonstrate how to construct a web service and call it from within Java.
The three folders are:
Each of these folders contains a self-contained Maven project which will compile and install into your local repository upon typing (once per folder whilst inside that folder):
If you wish to reuse the 'api' module inside your own project, you can copy the repository and dependency definitions from the 'ws-server' and 'ws-client' pom.xml files. The 'api' module and all its special dependencies can be found here.
You will need to compile and install the api module before you can compile and install the ws-server module.
The ws-server folder contains a functioning Java web-service which when compiled will result in a ws-server.war file inside the target/ folder. You can deploy this war file to any Tomcat installation. It requires that Tomcat has been set up to allow SSL connections. If you don't want to use SSL/https, modify the web.xml file and comment out the <security-constraint> tag (and its contents).
The service works by making use of annotations to get the web service context injected into it. This is a JAXWS feature. The service makes use of the Axis2 framework, which complies with the JAXWS standards. You could potentially swap Axis2 for CXF or any other JAXWS compliant framework without having to modify any of the source code.
When called, the service uses the api module of MyProxyWebService (described below) to parse out the SOAP headers into a HeaderDetails block, which is a simple data structure unique to MyProxyWebService. Once parsed this block will contain all the information you need to submit jobs to the grid.
The HeaderDetails class is described below in the section on the api.
The example service, HelloService (method sayHello()) provided does nothing more than return the header strings and a Hello World message back as the response. It is useful for checking that the SOAP header strings are being received correctly by the service.
There are two prerequisites to the service installation:
- that Java on the machine the service is running on must have been given the SSL public certificates of the WMS server to which the service will be submitting jobs, the VOMS server to which it will delegate certificates, and the CA server which signed the above two. You need to follow these instructions with the details of the WMS machine inserted where appropriate.
- that a folder exists containing the above CA certificates with the name 'ca.0' or something else ending in '.0'. This by default points to ~/.globus/certificates/
Demonstration of ws-server
A demonstration service which uses the 'ws-server' HelloService as-is without any modification can be found here.
Random number generation
The /dev/urandom device on the machine running the service needs to be providing sufficient amounts of randomness. If it isn't, then attempts to call services will result in 'randomStream == null' errors on the client side. The following command should return >= 3500 immediately before attempting to deploy the service, or start up an application server containing an existing service:
If you don't get >= 3500, you'll need to restart the application server itself, not just stop/redeploy/restart the service.
To increase the value returned, do a Google search for increasing the urandom entropy on your particular OS.
You will need to compile and install the api module before you can compile and install the ws-client module.
The client demonstrates how to embed headers for the grid into a SOAP call, by calling the demonstration sayHello() method in ws-server. The headers are embedded by constructing a HeaderDetails object then passing it to an api method to embed it into the header. The client uses the Axis2/AXIOM technique for calling web services, and this is currently the only supported method for embedding headers via the api.
The HeaderDetails class is described below in the section on the api.
Before compiling, modify the HelloClient.java code so that it points to the correct URL for your local Tomcat installation where you deployed ws-server.war. The service is set up to require HTTPS. If you haven't got a trusted certificate for your local Tomcat server, you'll need to manually trust it before you can run the ws-client code. Don't forget that if your server is not using SSL, you'll need to change the URL for it from https to just http, and also change the port number (probably to 8080 instead of 8443, depending on your local setup).
After compiling and installing the ws-client code, you will find a jar file called ws-client-0.0.1-SNAPSHOT.jar in the target folder. You will also find a subfolder there called dependency which is full of other jar files.
To run the client, you must always have all the jars in the dependency folder in your system classpath, OR you must have the dependency folder in the same location as the jar file when you run it.
For the purposes of the client demo, you must also have your user certificate and private key stored in ~/.globus/usercert.pem and ~/.globus/userkey.pem. It will delegate them to MyProxy for you before running a job. To run it, type:
java -jar sync ws-client-0.0.1-SNAPSHOT.jar "MyPassphrase"
where MyPassphrase is your private passphrase for unlocking your private key. It can't delegate your certificate to MyProxy without it.
If all goes to plan, you will see a SOAP envelope displayed which is the response from the service being called. In a real application you would then parse this message for a response. Simple single-string responses can be obtained using getReturnValue() from WMSUtils on the returned envelope.
To use the asynchronous version, which will submit the job, check progress and retrieve results over a series of separate calls to the server instead of all in one, type this instead:
java -jar async ws-client-0.0.1-SNAPSHOT.jar "MyPassphrase"
If you need to submit input files to the job, use the addInputFilename() method in WMSUtils to modify the JDL before submitting it. You'll need to gather all your input files into one directory, or subdirectories of one directory, and pass that directory in to the submitJob() call. The filenames entered using addInputFilename() must be relative to the input directory and not include any portion of the path up to or including that directory. NOTE: ALL files in the input directory will be uploaded, but only those referred to by calling addInputFilename() will actually be available in the job on the grid. Also, you cannot include any subdirectories inside the input directory as this will cause exceptions.
If your job produces more than just stdout and stderr, then you will need to use addOutputFilename() to register the additional output filenames relative to the working directory of the job on the grid. These will then be copied back and will be available locally relative to the directory returned by getJobOutput(). In the same way as for the input directory you can't have any subdirectories in the output directory as this will cause exceptions.
The API contains the core methods required to work with SOAP headers for grid in an easy way.
The main part of it is the HeaderDetails interface. This provides a convenient way to group together all the information you'll need to pass to grid-enabled SOAP services. It has two sections - MyProxyDetails and VOMSDetails. They're both pretty self-explanatory and contain the ability to store username, passphrase, server URIs (including port numbers), and VO/DN.
The useful bits of the API are to do with creating and parsing SOAP headers.
To create a SOAP header, the HeaderWriter interface is provided. There is currently just one implementation of this, AXIOMHeaderWriter, which knows how to create SOAP headers and insert them into Apache Axis2/AXIOM SOAP envelopes. The constructor requires an instance of AXIOM's SOAPEnvelope object to work with.
You don't use the HeaderWriter directly. Instead, you pass an instance of it alongside your HeaderDetails data object to one of three methods in MyProxyUtils - all called writeHeader(). The most basic form uses defaults and is fine for most purposes. The other two forms allow you to change the namespace and prefix used in the header tags, and even the names of the header tags themselves. Unless you've got something special you want to do, this is unlikely to be useful and should be avoided.
The single call to MyProxyUtils.writeHeader() is all you need in order to embed your HeaderDetails data into the SOAP call.
The HeaderParser interface provides methods for extracting SOAP headers. The JAXWSHeaderParser implementation does this by interrogating a WebServiceContext object provided by JAXWS. The XMLHeaderParser does it by parsing raw XML containing the entire SOAP message.
You shouldn't use HeaderParser directly. Instead, pass an instance of it to MyProxyUtils.parseHeader(), of which there are three variants available. The default one searches for header data using default namespaces, prefixes, and tag names. The other two allow you to modify the namespace or prefix or tag names depending on your own local setup. They are unlikely to be useful unless you have very complicated requirements. They all return a HeaderDetails block containing the parsed data.
The single call to MyProxyUtils.parseHeader() is all you need in order to parse your HeaderDetails data from the SOAP call.
The GLite/JLite libraries are not thread-safe. They rely on setting system-wide environment variables to work. This means that special measures had to be taken within the API implementation to allow them to be used in a multi-threaded environment such as a web service running on an application server. The downside is that only one GLite operation can ever be running at any point in time, which in effect makes all services inside one application server into a single serial service rather than a number of parallel ones. Until GLite/JLite are made thread-safe themselves, there's not much that can be done about this.