Copyright © 2008-2009 Roland Kübert
| Revision History | ||
|---|---|---|
| Revision 0.3 | 27.11.2009 | rk - kuebert@hlrs.de |
| Third draft. | ||
| Revision 0.2 | 16.01.2009 | rk - kuebert@hlrs.de |
| Second draft. | ||
| Revision 0.1 | 14.01.2009 | rk - kuebert@hlrs.de |
| First draft. | ||
Abstract
This is a follow-up tutorial to My first WSRFLiter service and describes how to develop a full-fledge WSRF web service for WSRFLite, the lightweight hosting environment for services using the Web Services Resource Framework (WSRF) and bundeled with UNICORE.
This article explains how to develop stateful web services with WSRFLite by showing how the WS-Agreement specification can be implemented.
In this article, we will show how to
The complete source code of the finished project is available for download.
This document is licensed under the Creative Commons Attribution-Noncommercial 2.0 Germany license, which can be found at http://creativecommons.org/licenses/by-nc/3.0/de/deed.en.
Table of Contents
In this section, we shortly introduce the Web Services Resource Framework and the WS-Agreement specification.
Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
Lorem ipsum dolor sit amet, consectetuer sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
WSRFLite can be obtained via the Download page at Sourceforge.net (direct download). Don't bother looking at Unicore.eu because you will finally be redirected to Sourceforge anyway.
As of the writing of this document, v1.8.6 is the current stable release; it has been released on 23. January 2008. The name of the archive file is wsrflite-1.8.6-all.zip.
Saying installation is perhaps a bit to exaggerated, since what you really have to do is simply unzip the downloaded archive. It does not really matter where you unzip it though. Under Windows, I recommend something like c:/dev/wsrflite-1.8.6, while under Linux you may just unzip it to your home directory so that you get ~/wsrflite-1.8.6 or, if you have the privileges, do as I do and put it under /opt/wsrflite-1.8.6.
Anyways, from now on I will refer to the installation directory just as $WSRFLite. And in case that you wondered: yes, that was the whole installation. ;)
The included start script for Linux has a bug and for Windows there is simply no script. So depending on which OS you work with, perform the necessary steps.
At first, make $WSRFLite/bin/start.sh executable by running chmod u+x $WSRFLite/bin/start.sh.
The original start.sh contains the following code which, when run, will give you the following exception: java.lang.NoClassDefFoundError: de/fzj/unicore/wsrflite/Kernel:
#
#put all jars in lib/ on the classpath
#
JARS=lib/*.jar
CP=.
for JAR in $JARS ; do
CP=$CP:$JAR
done
echo Reading code from $CP
cd $INST
The problem is that the change to the $INST directory is performed after the classpath is built, but it needs to happen before so just put cd $INST before the classpath creation like this:
cd $INST
#
#put all jars in lib/ on the classpath
#
JARS=lib/*.jar
CP=.
for JAR in $JARS ; do
CP=$CP:$JAR
done
echo Reading code from $CP
Save the file and then you're ready to contiune to the next section.
No start script for Windows is supplied with WSRFLite. You can use the
following code and save it to $WSRFLite/start.bat in the
WSRFLite base directory.
java -Xmx128m -classpath ./lib/xmlParserAPIs-2.6.2.jar;./lib/xfire-xmlbeans-1.2.4.jar;./lib/xfire-jsr181-api-1.0-M1.jar;./lib/xfire-core-1.2.4.jar;./lib/xfire-aegis-1.2.4.jar;./lib/xercesImpl-2.6.2.jar;./lib/xbean_xpath-2.2.0.jar;./lib/xbean-2.2.0.jar;./lib/wstx-asl-3.2.0.jar;./lib/wsrftypes-1.4.jar;./lib/wsrflite-1.8.6.jar;./lib/wsrflite-1.8.6-sources.jar;./lib/wsrflite-1.8.6-javadoc.jar;./lib/wsdl4j-1.6.1.jar;./lib/stax-utils-snapshot-20040917.jar;./lib/stax-api-1.0.1.jar;./lib/servlet-api-2.5-6.1.3.jar;./lib/saxon-xpath-8.6.1.jar;./lib/saxon-dom-8.6.1.jar;./lib/saxon-8.6.1.jar;./lib/mysql-connector-java-5.1.5.jar;./lib/mail-1.4.jar;./lib/junit-3.8.1.jar;./lib/jetty-util-6.1.3.jar;./lib/jetty-sslengine-6.1.3.jar;./lib/jetty-6.1.3.jar;./lib/jdom-1.0.jar;./lib/jaxen-1.1-beta-9.jar;./lib/hsqldb-1.8.0.5.jar;./lib/ehcache-1.2.jar;./lib/commons-logging-1.0.4.jar;./lib/commons-httpclient-3.1-rc1.jar;./lib/commons-collections-2.1.jar;./lib/commons-codec-1.2.jar;./lib/activation-1.1.jar;./lib/XmlSchema-1.1.jar;. de.fzj.unicore.wsrflite.Kernel
![]() | |
Please note that the Windows batch file is not as sophisticated as the shell
script already supplied with WSRFLite. Calling the batch script from arbitrary
directories will most likely not work, you have to call it from
|
The central configuration file for WSRFLite is $WSRFLITE/conf/wsrflite.xml.
It contains generic parameters, for example if SSL should be used and where the containers
keystore is located, and service descriptions, which detail on the deployed services.
For using the example shop service, you have to change the service description; you also
have to change the path to the keystore and, if you do not necessarily need SSL, you should
disable it.
To disable SSL, just change the property unicore.wsrflite.ssl from true
to false.
This gives us the possibility to monitor packages as they are going over the wire.
The keystore specified in wsrflite.xml is wrong; technically,
when you disable SSL you do not need to correct it, but since you will sooner or later
trip over this error, there's no point in not adding 5 letters to the config file. ;)
Change the value of the property unicore.wsrflite.ssl.keystore from
demo_keystore to conf/demo_keystore. As already said, if you
start the server with SSL disabled, this property is never read; if you start it with
SSL enabled, you get an exception:
SCHWERWIEGEND: Could not start server.
java.io.FileNotFoundException: /opt/wsrflite-1.8.6/demo_keystore (No such file or directory)
at java.io.FileInputStream.open(Native Method)
If the keystore is specified correctly, you get this in the logging statements:
INFO: Security Settings: SSL is enabled Using keystore conf/demo_keystore, type JKS.
Okay, that's all, you have successfully installed and configured WSRFLite and can now go on to develop your service.
We want to use Eclipse when writing our implementation code, so that we are able to use its fancy features (code completion etc.). This sections shows what has to be done to set up an Eclipse project for convenient development of WSRFLite services.
Open Eclipse and click File->New->Java Project, which opens the following dialog. Name your project (wsrflite-wsag, for exmaple), then click finish.

Create a new Java project with default settings.
Basically, we need 4 folders, of which one (src) has already been created during project setup:
Just create these folder by right-clicking on the project root and selecting New->Folder.
You probably will build more services for WSRFLite, therefore it is recommended that you create a user library containing all necessary jar files. This saves the burden of having to import all .jar files seperately. It is the easiest to just add all .jar files in the $WSRFLite/lib directory to the user library, than add the user library to the class path.
Right click on the project folder, then click on Build Path->Add Libraries.
Select User Library, then click Next.

Creating a new user library.
Click User libraries, then New

Select User libraries here.

Select New here.
Enter a name, for example wsrflite-1.8.6, then click Ok.

Name your library.
Click Add Jars and select all .jar files from the $WSRFLite/lib directory.
Select your newly created library, then click finish.

Selected libraries are marked with a check.
The library is now part of your build path and can also be reused in other projects.
We have several tasks, which we need to perform, before we can deploy our service, once it is ready, into WSRFLite. Although we do not even have prepared all of these steps, we pretty much know already what we have to accomplish:
Basically, we only need the abovementioned four tasks, but for convenience, the following build.xml file, which will be put into the project root directory, has the additional tasks init, clean, compileTests and test. The presented build file is far from perfect, but simple enough to be easily understood and can be easily extended, if you wish to do so.
Example 1. The basic build.xml file
<project name="UNICORE6-WS-Agreement" basedir="." default="all">
<description>Build file for a WSRFLite service</description>
<property environment="env" />
<property file="build.properties" />
<!-- Classes for runnign XMLBeans -->
<path id="beans.classes">
<!-- distribution jars from WSRFlite which must include xbean-2.2.0.jar -->
<fileset dir="${WSRFLITE_LOCATION}/lib/">
<include name="**/*.jar" />
</fileset>
</path>
<!-- Classes for building the service -->
<path id="build.classes">
<fileset dir="${WSRFLITE_LOCATION}/lib/">
<include name="**/*.jar" />
</fileset>
<fileset dir="${LOCAL_LIB_DIR}">
<include name="**/*.jar" />
</fileset>
</path>
<target name="init">
<mkdir dir="${BUILD_DIR}" />
</target>
<target name="clean">
<delete dir="${BUILD_DIR}" />
<delete>
<fileset dir="lib/" includes="*.jar" />
</delete>
</target>
<target name="xmlbeans" description="create the XMLBeans classes for the xsd and wsdl files">
<echo>Creating XMLBeans types for the example schema</echo>
<java classname="org.apache.xmlbeans.impl.tool.SchemaCompiler" classpathref="beans.classes" fork="true">
<arg value="-compiler" />
<arg value="${env.JAVA_HOME}/bin/javac" />
<arg value="-dl" />
<arg value="-noupa" />
<arg value="-out" />
<arg value="${LOCAL_LIB_DIR}/${XMLBEANS_TYPES_JAR}" />
<!-- files to XMLBeanify follow ... -->
<arg value="schema/ws-agreement/AgreementPortType.wsdl" />
<arg value="schema/ws-agreement/agreement_types.xsd" />
</java>
</target>
<target name="compile" depends="init, xmlbeans">
<javac srcdir="${SRC_DIR}" destdir="${BUILD_DIR}" classpathref="build.classes" debug="on" verbose="false" />
</target>
<target name="compileTests" depends="jarService">
<javac srcdir="${TEST_DIR}" destdir="${BUILD_DIR}" classpathref="build.classes" debug="on" verbose="false" excludes="de/hlrs/wsag/service/impl/benchmark/*.java" />
<echo>Test files now end up in the source folder...</echo>
</target>
<target name="jarService" depends="compile">
<jar jarfile="${LOCAL_LIB_DIR}/${JAR_NAME}" basedir="build/">
<include name="**/*" />
</jar>
</target>
<target name="deploy" depends="jarService">
<echo>Deploying JARs to WSRFLite lib dir at ${WSRFLITE_LOCATION}/lib</echo>
<copy todir="${WSRFLITE_LOCATION}/lib" file="${LOCAL_LIB_DIR}/${JAR_NAME}" />
<copy todir="${WSRFLITE_LOCATION}/lib" file="${LOCAL_LIB_DIR}/${XMLBEANS_TYPES_JAR}" />
<echo>Do not forget to adapt the wsrflite.conf file in ${WSRFLITE_LOCATION}/lib</echo>
</target>
<target name="test" depends="compile, compileTests">
<junit dir="build/" fork="true" printsummary="withOutAndErr" showoutput="true">
<formatter type="plain" usefile="false" />
<classpath>
<path refid="build.classes" />
</classpath>
<test name="de.hlrs.wsag.service.impl.AgreementFactoryTest" />
<test name="de.hlrs.wsag.service.impl.AgreementTest" />
</junit>
</target>
<target name="all" depends="clean,init,xmlbeans,compile,jarService" />
</project>
build.properties file; if we use the build file for another
service, we only need to adapt the build.properties and can
leave the build.xml file untouched. The following properties have to be
defined:
c:/dev/wsrflite-1.8.6wsag_xmlbeans.jarwsag_wsrflite.jarbuildsrctestlibschema/ws-agreementbuild.properties, which you have to put into the project
root (alongside build.xml):
We don't need all files provided by WS-Agreement, just the basic
agreement types (like
Agreements, Offers, Terms, ...), agreement state types and the agreement port type (to get the
resource properties element).
Import these two files, either directly into the schema folder or
(as I prefer) create a sub-folder named
ws-agreement (if you put the files
in a different folder, you need to adapt the build.properties file).
You can either exract both these files from the
WS-Agreement specification
document or download theme directly from my home page (
agreement types,
agreement state types,
agreement port type).
An important note: we made a small change to the schema files, as they
need to have attributeFormDefault="unqualified" in the root
element, otherwise they don't work.
Before we start declaring our interface and implementing our service, let's already use XMLBeans to automatically build the classes for the schema files we previously imported.
Ensure that you have create the lib directory
(or whatever LOCAL_LIB_DIR points to), then run the
ant task xmlbeans. You should get something like the
following output:
xmlbeans:
[echo] Creating XMLBeans types for the example schema
[java] Time to build schema type system: 0.943 seconds
[java] Time to generate code: 1.014 seconds
[java] Time to compile code: 2.609 seconds
[java] Compiled types to: lib\wsag_xmlbeans.jar
BUILD SUCCESSFUL
Total time: 6 seconds
Now, add the resulting library file to your build path by right-clicking on it and selecting Build Path->Add to Build Path. If you don't see the file, select your build folder and press F5 to refresh.
Now that we have created the Java types for everything we need from WS-Agreement, we can start building our service interfaces. Service interfaces are, in WSRFLite, simple Java interfaces. We create two interfaces, one for the AgreementFactory, one for the Agreementitself, like this:
Example 2. The factory interface
package de.hlrs.wsag.service;
import javax.jws.WebMethod;
import javax.jws.WebService;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementTemplateType;
import org.w3.x2005.x08.addressing.EndpointReferenceDocument;
/**
* Factory interface that allows to create an agreement (WS-Agreement style).
*/
@WebService(targetNamespace="http://schemas.ggf.org/graap/2007/03/ws-agreement", portName="AgreementFactory")
public interface AgreementFactory {
/**
* Creates an agreement and returns the epr document which contains the
* endpoint reference pointing to the document.
*/
@WebMethod(action="http://schemas.ggf.org/graap/2007/03/ws-agreement/createAgreement")
public EndpointReferenceDocument createAgreement(AgreementTemplateType offer);
}
As you can see, the factory only provides WS-Agreement's createAgreement operation, defined with the namespaces from WS-Agreement.
Next comes the interface to our resource. We extend from WSResource and don't add any other operations:
package de.hlrs.wsag.service;
import javax.jws.WebService;
import de.fzj.unicore.wsrflite.xmlbeans.WSResource;
@WebService(targetNamespace="http://schemas.ggf.org/graap/2007/03/ws-agreement", portName="Agreement")
public interface Agreement extends WSResource {
}
![]() | |
Hint: You can just copy the code from above and paste it into eclipse, which should automatically generate the package and file for you. |
Our factory implementation looks like this:
package de.hlrs.wsag.service.impl;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementTemplateType;
import org.w3.x2005.x08.addressing.AttributedURIType;
import org.w3.x2005.x08.addressing.EndpointReferenceDocument;
import org.w3.x2005.x08.addressing.EndpointReferenceType;
import de.fzj.unicore.wsrflite.Kernel;
import de.hlrs.wsag.service.AgreementFactory;
/**
* WS-IAgreement factory class used for creating agreements.
*
* @author Roland Kuebert
*/
public class AgreementFactoryImpl implements AgreementFactory {
/**
* Receives an offer and decides on acceptance. <p/> If the agreement is
* accepted, an EndpointReferenceDocument is returned, otherwise the
* <code>null</code> value is returned.
*
* @param offer the agreement offer
*
* @return {@link EndpointReferenceDocument} an EndpointReferenceDocument
* if the offer is accepted, otherwise <code>null</code>.
*/
public EndpointReferenceDocument createAgreement(AgreementTemplateType offer) {
return null;
}
}
This class just inherits from the factory and implements the inherited method - at the moment just returning null. We will implement a test for this method before we implement it.
Before we start with the real impementation, let's create a JUnit test
for the factory. To do this, first create another source folder called
test, then create the following
class in there:
package de.hlrs.wsag.service.impl;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementOfferDocument;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementType;
import org.w3.x2005.x08.addressing.EndpointReferenceDocument;
import de.fzj.unicore.wsrflite.Kernel;
import de.fzj.unicore.wsrflite.persistence.HsqldbPersist;
import de.fzj.unicore.wsrflite.persistence.IPersistenceProperties;
import de.fzj.unicore.wsrflite.xfire.JettyTestCase;
import de.fzj.unicore.wsrflite.xfire.XFireClientFactory;
import de.fzj.unicore.wsrflite.xfire.XFireKernel;
import de.hlrs.wsag.service.AgreementFactory;
/**
* Test case for the <code>AgreementFactoryImpl</code> class.
*
* @author Roland Kuebert
*/
public class AgreementFactoryTest extends JettyTestCase {
/** If the setup has been done before. */
static boolean haveInit = false;
/** Default Id used for created agreements. */
private final static String AGREEMENT_ID = "abcdefg-12345";
/** Agreement factory proxy. */
private AgreementFactory agreementFactory;
/**
* Sets up the agreement factory proxy and performs service
* registration with the kernel.
*
* @throws Exception
*/
@Override
protected void setUp() throws Exception {
if (haveInit == false) {
System.setProperty(HsqldbPersist.clearDBOnStartup, "true");
super.setUp();
addServices();
// Logging
LogManager.getLogManager().reset();
ConsoleHandler c = new ConsoleHandler();
c.setLevel(Level.OFF);
Logger.getLogger("de").addHandler(c);
// Create a proxy for the agreement factory.
agreementFactory = (AgreementFactory) (new XFireClientFactory())
.createPlainWSProxy(AgreementFactory.class, getBaseurl()
+ "/AgreementFactoryImpl", null);
}
}
/**
* Adds the factory and instance services to the kernel.
*/
@Override
protected void addServices() throws Exception {
Kernel.getKernel().setProperty(
IPersistenceProperties.WSRF_PERSIST_CLASSNAME,
HsqldbPersist.class.getName());
Kernel.getKernel().setProperty(
IPersistenceProperties.WSRF_PERSIST_STORAGE_DIRECTORY, ".");
XFireKernel.exposeAsService("AgreementFactoryImpl",
AgreementFactory.class, AgreementFactoryImpl.class, false);
XFireKernel.exposeAsService("Agreement", AgreementImpl .class,
AgreementHomeImpl.class, true);
}
/**
* Tests the factory's <code>createAgreement</code> operation.
*
* @throws Exception
*/
public void testCreateAgreement() throws Exception {
try {
EndpointReferenceDocument document = agreementFactory
.createAgreement(createOffer());
assertNotNull("EndpointReferenceDocument must not be null", document);
System.out.println("Received EPR: " + document);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
public AgreementOfferDocument createOffer() {
AgreementOfferDocument offerDoc = AgreementOfferDocument.Factory.newInstance();
AgreementType offer = offerDoc.addNewAgreementOffer();
offer.setAgreementId(AGREEMENT_ID);
System.out.println(offerDoc.xmlText());
return offerDoc;
}
}
This operation sets up our whole environment. Here, we
| |
This operation does the actual work of programmatically deploying the services. | |
This is our test. We call the createAgreement operation and ensure that we are getting an EndpointReference to the created agreement. | |
This operation just creates a minimal offer which we can send to the agreement factory. |
What you notice immediately: we reference a class AgreementHomeImpl, but we haven't created it yet, so let's just do it:
package de.hlrs.wsag.service.impl;
import java.util.logging.Logger;
import de.fzj.unicore.wsrflite.xmlbeans.impl.WSResourceHomeImpl;
import de.fzj.unicore.wsrflite.xmlbeans.impl.WSResourceImpl;
public class AgreementHomeImpl extends WSResourceHomeImpl {
protected static Logger logger=Logger.getLogger(AgreementHomeImpl.class.getName());
@Override
protected WSResourceImpl doCreateInstance(){
return new AgreementImpl();
}
}
Ouch, tricked again - where's AgreementImpl coming from? We have to create that as well (explanation will follow in good time):
package de.hlrs.wsag.service.impl;
import javax.xml.namespace.QName;
import de.fzj.unicore.wsrflite.xmlbeans.impl.WSResourceImpl;
import de.hlrs.wsag.service.Agreement;
public class AgreementImpl extends WSResourceImpl implements Agreement {
@Override
public QName getResourcePropertyDocumentQName() {
// TODO Auto-generated method stub
return null;
}
}
If you run the the test, it should fail and you should get the following console output:
2009-01-19 16:31:38.706::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2009-01-19 16:31:38.741::INFO: jetty-6.1.3 2009-01-19 16:31:38.888::INFO: Started SocketConnector @ 0.0.0.0:65321 createAgreement called
As you can clearly see, the test failed because we returned null in the implementation, but expected something else in the test - an endpoint reference to an agreement, obviously. Once we have this correct, our test will no longer fail. Furthermore, you can see from the output createAgreement called that our implementation is really called.
We now replace the code in AgreementFactoryImpl to really create a service instance and return it's epr. Substitute the code with this:
/**
* Receives an offer and decides on acceptance. <p/> If the agreement is
* accepted, an EndpointReferenceDocument is returned, otherwise the
* <code>null</code> value is returned.
*
* @param offer
* the agreement offer
*
* @return {@link EndpointReferenceDocument} an EndpointReferenceDocument if
* the offer is accepted, otherwise <code>null</code>.
*/
public EndpointReferenceDocument createAgreement(
AgreementOfferDocument offer) {
EndpointReferenceDocument response = EndpointReferenceDocument.Factory
.newInstance();
try {
response.addNewEndpointReference();
// create the service wsa:To address
String addr = Kernel.getKernel().getProperty(Kernel.WSRF_BASEURL)
+ "/Agreement?res=" + makeNewInstance(m);
EndpointReferenceType eprt = EndpointReferenceType.Factory
.newInstance();
AttributedURIType address = eprt.addNewAddress();
address.setStringValue(addr);
response.setEndpointReference(eprt);
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
/**
* create a new WSResource on the associated WSRF service
*
* @return
*/
protected String makeNewInstance() throws Exception {
return Kernel.getKernel().getServiceHome("Agreement")
.createWSRFServiceInstance(null);
}
We use the Kernel class to get the containers base URL and add the path to the instance using the makeNewInstance operation. | |
The address is added to an endpoint reference and returned to the caller. | |
The Kernel is used to obtain the service home of the Agreement service. Using the service home, we can create and instance and return it's unique ID to the caller. |
If you run the test again, you will get something like this:
2009-01-19 17:13:12.021::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2009-01-19 17:13:12.053::INFO: jetty-6.1.3 2009-01-19 17:13:12.089::INFO: Started SocketConnector @ 0.0.0.0:65321 http://localhost:65321/services/Agreement?res=b0e06126-5580-4333-bf63-bd2b9598e027 Received EPR: <add:EndpointReference xmlns:add="http://www.w3.org/2005/08/addressing" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <add:Address>http://localhost:65321/services/Agreement?res=b0e06126-5580-4333-bf63-bd2b9598e027</add:Address> </add:EndpointReference>
This shows that a resource has been created and its address is http://localhost:65321/services/Agreement?res=b0e06126-5580-4333-bf63-bd2b9598e027 .
Now the only thing that is missing is the implementation of our instance service, which we will be doing in the next section.
As we have done before for the agreement factory, we prepare a unit test to test the implementation we will be doing. The following class is the basis for our tests and has two - yet empty - test methods in it, along with the skeleton that helps to create an agreement instance.
package de.hlrs.wsag.service.impl;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementIdDocument;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementOfferDocument;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementType;
import org.oasisOpen.docs.wsrf.rp2.GetResourcePropertyDocumentResponseDocument;
import org.w3.x2005.x08.addressing.EndpointReferenceDocument;
import org.w3.x2005.x08.addressing.EndpointReferenceType;
import de.fzj.unicore.wsrflite.Kernel;
import de.fzj.unicore.wsrflite.persistence.HsqldbPersist;
import de.fzj.unicore.wsrflite.persistence.IPersistenceProperties;
import de.fzj.unicore.wsrflite.utils.Utilities;
import de.fzj.unicore.wsrflite.xfire.JettyTestCase;
import de.fzj.unicore.wsrflite.xfire.XFireClientFactory;
import de.fzj.unicore.wsrflite.xfire.XFireKernel;
import de.fzj.unicore.wsrflite.xmlbeans.client.BaseWSRFClient;
import de.hlrs.wsag.service.Agreement;
import de.hlrs.wsag.service.AgreementFactory;
/**
* Test case for the <code>AgreementTest</code> class.
*
* @author Roland Kuebert
*/
public class AgreementTest extends JettyTestCase {
/** Agreement factory proxy. */
private AgreementFactory agreementFactory;
/** Agreement WSRF client. */
private BaseWSRFClient baseWsrfAgreementClient;
/** Default Id used for created agreements. */
private final static String AGREEMENT_ID = "abcdefg-12345";
/**
* Sets up the agreement factory proxy and performs service
* registration with the kernel.
*
* @throws Exception
*/
@Override
protected void setUp() throws Exception {
System.setProperty(HsqldbPersist.clearDBOnStartup, "true");
super.setUp();
addServices();
// Logging
LogManager.getLogManager().reset();
ConsoleHandler c = new ConsoleHandler();
c.setLevel(Level.OFF);
Logger.getLogger("de").addHandler(c);
// Create a proxy for the agreement factory.
agreementFactory = (AgreementFactory) (new XFireClientFactory())
.createPlainWSProxy(AgreementFactory.class, getBaseurl()
+ "/AgreementFactoryImpl", null);
}
/**
* Adds the factory and instance services to the kernel.
*/
@Override
protected void addServices() throws Exception {
Kernel.getKernel().setProperty(
IPersistenceProperties.WSRF_PERSIST_CLASSNAME,
HsqldbPersist.class.getName());
Kernel.getKernel().setProperty(
IPersistenceProperties.WSRF_PERSIST_STORAGE_DIRECTORY, ".");
XFireKernel.exposeAsService("AgreementFactoryImpl",
AgreementFactory.class, AgreementFactoryImpl.class, false);
XFireKernel.exposeAsService("Agreement", Agreement.class,
AgreementHomeImpl.class, true);
}
/**
* Creates an agreement.
*
* @throws Exception
*/
public void createAgreement() throws Exception {
try {
AgreementOfferDocument offerDoc = AgreementOfferDocument.Factory
.newInstance();
AgreementType offer = offerDoc.addNewAgreementOffer();
offer.setAgreementId(AGREEMENT_ID);
EndpointReferenceDocument document = agreementFactory
.createAgreement(offerDoc);
assertNotNull("EndpointReferenceDocument must not be null", document);
EndpointReferenceType epr = document.getEndpointReference();
String agreementUrl = getBaseurl()+"/Agreement";
baseWsrfAgreementClient = new BaseWSRFClient(agreementUrl, epr);
// Do not cache stuff at the client.
baseWsrfAgreementClient.setUpdateInterval(-1);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}
As you can see, this test sets up a BaseWSRFClient, which is a generic WSRF client. Since there are no tests, there's nothing you can run.
The BaseWSRFClient has the following operations:
// TODO Add operations list here.
The first thing we want to try is to use the BaseWSRFClient to obtain a resource property document. We add the operation testGetResourcePropertyDocument():
/**
* Queries the service for the resource property document and asserts that
* a non-null value is returned.
*/
public void testGetResourcePropertyDocument() {
try {
createAgreement();
} catch (Exception e) {
e.printStackTrace();
fail();
}
try {
String rpString = baseWsrfAgreementClient.getResourcePropertyDocument();
assertNotNull(rpString);
System.out.println(rpString);
GetResourcePropertyDocumentResponseDocument rpDoc
= GetResourcePropertyDocumentResponseDocument.Factory.parse(rpString);
assertNotNull(rpDoc);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
The output of the program should be like this:
2009-01-19 18:14:32.261::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2009-01-19 18:14:32.293::INFO: jetty-6.1.3 2009-01-19 18:14:32.327::INFO: Started SocketConnector @ 0.0.0.0:65321 http://localhost:65321/services/Agreement?res=52a1ccbe-17c0-41ae-b50a-ba30569a4595 <rp:GetResourcePropertyDocumentResponse xmlns:rp="http://docs.oasis-open.org/wsrf/rp-2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <rl:CurrentTime xmlns:rl="http://docs.oasis-open.org/wsrf/rl-2"> 2009-01-19T18:14:32.964+01:00 </rl:CurrentTime> <rl:TerminationTime xmlns:rl="http://docs.oasis-open.org/wsrf/rl-2"> 2009-01-20T18:14:32.724+01:00 </rl:TerminationTime> </rp:GetResourcePropertyDocumentResponse>
This shows us that the resource already has resource properties: the current time and the termination. What we now want is to add resource properties from WS-Agreement, which we will then hopefully be able to see in the output of this test.
So how do we add the resource properties we want to have an agreement, like Id, Context and Terms? In principle, this is easy, even though it is the most cumbersome thing we have to do until now.
For each resource property we want our service to have, we need to have a corressponding class that extends de.fzj.unicore.wsrflite.xmlbeans.ResourceProperty. This class encapsulates the ...Document, created by XMLBeans, that corresponds to what we want to expose as a property.
So, for the AgreementId, this is how our property class looks:
package de.hlrs.wsag.service.properties;
import org.ggf.schemas.graap.x2007.x03.wsAgreement.AgreementIdDocument;
import de.fzj.unicore.wsrflite.WSRFInstance;
import de.fzj.unicore.wsrflite.xmlbeans.ResourceProperty;
public class AgreementIdProperty extends ResourceProperty<Object> {
private AgreementIdDocument agreementIdDocument;
public AgreementIdProperty(WSRFInstance res) {
// reference to the resource is needed in order to access the entry resource property
this.parentWSResource=res;
AgreementIdDocument doc = AgreementIdDocument.Factory.newInstance();
doc.setAgreementId(null);
agreementIdDocument = doc;
}
@Override
public AgreementIdDocument[] getXml() {
return new AgreementIdDocument[] { agreementIdDocument };
}
/**
* Gets the current value of <code>agreementIdDocument</code>
*
* @return AgreementIdDocument the current value of agreementIdDocument
*/
public AgreementIdDocument getAgreementIdDocument() {
return agreementIdDocument;
}
/**
* Sets the agreementIdDocument to <code>agreementIdDocument</code>.
*
* @param agreementIdDocument the agreementIdDocument to set
*/
public void setAgreementIdDocument(AgreementIdDocument agreementIdDocument) {
this.agreementIdDocument = agreementIdDocument;
}
}
TODO explain this...
Okay, now that we have the correct class, we still need to adapt AgreementImpl. We need to override the initialise method and our AgreementId property to the list of properties:
public void initialise(String serviceName, Map<String, Object> initobjs)throws Exception{
super.initialise(serviceName, initobjs);
AgreementIdProperty idProperty = new AgreementIdProperty(this);
AgreementIdDocument idDocument = idProperty.getAgreementIdDocument();
idDocument.setAgreementId("Foo");
idProperty.setAgreementIdDocument(idDocument);
properties.put(Utilities.toQName(AgreementIdDocument.type), idProperty);
}
Running the test now should give you something like this:
2009-01-19 18:52:21.013::INFO: Logging to STDERR via org.mortbay.log.StdErrLog 2009-01-19 18:52:21.046::INFO: jetty-6.1.3 2009-01-19 18:52:21.086::INFO: Started SocketConnector @ 0.0.0.0:65321 http://localhost:65321/services/Agreement?res=cbf07fbf-c548-4ed9-93d0-65266321dedd <rp:GetResourcePropertyDocumentResponse xmlns:rp="http://docs.oasis-open.org/wsrf/rp-2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <rl:CurrentTime xmlns:rl="http://docs.oasis-open.org/wsrf/rl-2"> 2009-01-19T18:52:21.776+01:00 </rl:CurrentTime> <ws:AgreementId xmlns:ws="http://schemas.ggf.org/graap/2007/03/ws-agreement"> Foo </ws:AgreementId> <rl:TerminationTime xmlns:rl="http://docs.oasis-open.org/wsrf/rl-2"> 2009-01-20T18:52:21.514+01:00</rl:TerminationTime> </rp:GetResourcePropertyDocumentResponse>
It worked, nice job - although we have the value hard coded in the resource, which is definitely something we need to change.
Now that we have a property, we basically want to set it by software, not fixed like we did. At first, let's adapt the test to set the ID to some string, so after
AgreementTemplateType offer = AgreementTemplateType.Factory.newInstance();
add
offer.setAgreementId("someAgreementId");
to set the agreement id in the offer to something more or less sensible.
Let's see what we do with this now. In the factory, we can obviously extract it from the document passed in:
String agreementId = offer.getAgreementOffer().getAgreementId()
That's fair enough, but how do we pass it to the resource? Easy. Maybe
you have noticed before, that we pass a null reference to
createWSRFServiceInstsance:
protected String makeNewInstance() throws Exception {
return Kernel.getKernel().getServiceHome("Agreement").createWSRFServiceInstance(null);
}
createWSRFServiceInstance takes an object of type
Map<String, Object> as an argument and the resource
obtains this object in its initialise method,
inhertied from WSResourceImpl, since it has
the following signature:
public void initialise(String serviceName, Map initobjs) throws Exception
Obviously, we only need to create a map in our factory implementation, put the value from the offer in there and remove it in the instance implementation. We put it in a HashMap like this
Map<String, Object> m = new HashMap<String, Object>();
m.put("agreement_id", offer.getAgreementOffer().getAgreementId());
and then implement a new version of makeNewInstance like
this:
protected String makeNewInstance(Map<String, Object> m) throws Exception {
return Kernel.getKernel().getServiceHome("Agreement")createWSRFServiceInstance(m);
}
In the factory, we only need to change one more line, from
String addr = Kernel.getKernel().getProperty(Kernel.WSRF_BASEURL) + "/Agreement?res=" + makeNewInstance();
to
String addr = Kernel.getKernel().getProperty(Kernel.WSRF_BASEURL) + "/Agreement?res=" + makeNewInstance(m);
Adapt the initalise method in AgreementImpl
to look like this:
public void initialise(String serviceName, Map<String, Object> initobjs)throws Exception{
super.initialise(serviceName, initobjs);
AgreementIdProperty idProperty = new AgreementIdProperty(this);
AgreementIdDocument idDocument = idProperty.getAgreementIdDocument();
if ( initobjs.containsKey("agreement_id") ) {
String id = (String) initobjs.get("agreement_id");
idDocument.setAgreementId(id);
} else {
idDocument.setAgreementId(null);
}
idProperty.setAgreementIdDocument(idDocument);
properties.put(Utilities.toQName(AgreementIdDocument.type), idProperty);
}
We want to have a test for our agreement id resource property, so let's add the following to AgreementTest:
/**
* Queries the resource property AgreementId and compares it with the value
* from the agreement offer. Fails if the values are not equal.
*/
public void testGetAgreementIdResourceProperty() {
try {
createAgreement();
} catch (Exception e) {
e.printStackTrace();
fail();
}
try {
String rpString
= baseWsrfAgreementClient.getResourceProperty(Utilities.toQName(AgreementIdDocument.type));
assertNotNull(rpString);
AgreementIdDocument agreementIdDoc = AgreementIdDocument.Factory.parse(rpString);
String id = agreementIdDoc.getAgreementId();
assertEquals("Agreement Id should be '" + AGREEMENT_ID + "', is '"
+ id + "'", AGREEMENT_ID, id);
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
Running this test should succeed, as the property gets correctly set and will, therefore, be equal to the value that was given the offer.
Now, the missing resource properties can be added in the same way. WS-Agreement foresees
These properties can be added in the same way as described above for
AgreementId, so we won't go into detail on these. You can
download the whole Eclipse project with the completed implementation
here if you want to have a look at the finished
implementation.
The configuration file that holds the service information is called
wsrflite.xml and can be found in the directory
$WSRFLite/conf.
Each service - factory and instance - needs an entry like this:
<service name="Name you wish your service to have" wsrf="false or true"> <interface class="name of interface class (with package)" /> <implementation class="name of implementation class (with package)"/> </service>
For our services, we add:
<service name="AgreementFactory" wsrf="false"> <interface class="de.hlrs.wsag.service.AgreementFactory" /> <implementation class="de.hlrs.wsag.service.impl.AgreementFactoryImpl"/> </service> <service name="Agreement" wsrf="true"> <interface class="de.hlrs.wsag.service.Agreement" /> <implementation class="de.hlrs.wsag.service.impl.AgreementHomeImpl"/> </service>
Apart from specifying the service's classes, we obviously need to make
them accessible to WSRFLite. Therefore, all necessary libraries we use
in our service - for our example, only
wsag_wsrflite.jar and
wsag_xmlbeans.jar - have to be copied to the
$WSRFLite/lib directory.
You can either use the task deploy from the
build.xml files or copy the files manually to the
$WSRFLite/lib directory.
![]() | |
If you are running under Windows, you need to specify the library files
in the class path; the Linux |
You can now start the container via your start script and have a look at the deployed services. Normally, the container binds to localhost, port 7777. If you connect to http://localhost:7777/services/, you should see the Agreement and AgreementFactory services, which will look something like this:
Available Services:
* Agreement [wsdl]
* AgreementFactory [wsdl]
Generated by XFire ( http://xfire.codehaus.org )
You can retrieve the WSDL description of your service by clicking on the corresponding [wsdl] link. With the WSDL file, you can, for example, generate stubs for Axis and access the service this way.
In this article, we have demonstrated on how to develop a full-fledged WS-RF service according to the WS-RF Factory/Instance pattern by showing how one can implement the WS-Agreement specification in WSRFLite. We have used Eclipse to develop, test and deploy our service, which makes the whole process very convenient.
Based on what has been demonstrated in this article, you should be able to
develop your own services for WSRFLite, regardless of them being stateless or
stateful web service and make use of the files we have presented here, like,
for example, build.xml.
This work has been supported by the IRMOS project and has been partly funded by the EC Seventh Framework Programme FP7/2007-2011 under grant agreement n° 214777. This work expresses the opinions of the authors and not necessarily those of the European Commission. The European Commission is not liable for any use that may be made of the information contained in this work.
This document has been inspired by Borja Sotomayor's The Globus Toolkit 4 Programmer's Tutorial and has reused parts of its docbook sources and stylesheets.