Writing the class that handles profile queries and delivers
profile results is easily the hardes
t part in developing a new
kind of profile server. Profile servers' handlers can serve
profiles describing static resources, can synthesize profile
on the fly to describe resources, and can create profile
metadata for resources that change all the time.
Understanding the resources you're trying to describe with
profiles is the most important thing you can do before
beginning to write your profile handler:
-
Do you always have the same set of static resources?
If so, you can write a static profile document to describe
them and use the
LightweightProfileHandler
, thus
avoiding having to write a new handler at all.
-
Do you have resources that never change, but may add to or
remove from that set? If so, you can use the
OracleProfileImpl
handler which uses an Oracle
database to store a set of profiles that you can update.
-
Do you have resources that do change, or that come
from a dynamic set of data? If so, you'll have to write a
handler.
The OODT Framework provides two handler interfaces (one is
an extension of the other):
-
jpl.eda.profile.handlers.ProfileHandler
is the basic profile handler. It defines methods for
handling
searches for profiles.
-
jpl.eda.profile.handlers.ProfileManager
is an
extension that not just
handles
profile queries
but also
manages
the set of profiles maintained
by the server, by providing methods for adding to,
removing from, and updating the set of managed profiles.
For nearly all applications, the
ProfileHandler
interface is sufficient. If you need to provide profile
management capabilities, it still may be handy to start with
the
Profile
Handler
interface, implement and test
its methods, and
then
change to the
ProfileManager
.
The
ProfileHandler
interface is
as follows:
package jpl.eda.profile.handlers;
import java.util.List;
import jpl.eda.profile.Profile;
import jpl.eda.profile.ProfileException;
import jpl.eda.xmlquery.XMLQuery;
public interface ProfileHandler {
List findProfiles(XMLQuery query) throws ProfileException;
Profile get(String profID) throws ProfileException;
}
The two methods are described in detail below.
-
findProfiles
-
This method accepts a query in the form of an
XMLQuery
object and returns a
Java
List
of
Profile
objects that match. If
there are no matches, this method must return an empty
list. If an error occurs, it should throw the
ProfileException
.
This is by far the most used and most important
method, and really is the
raison
d'etre
for profile servers. It's
what the OODT Framework uses to allow clients to ask
your server for resources based on metadata .
-
get
-
This method accepts the ID of a profile in the form
of a Java
String
and returns
either a
Profile
object with that
ID or null if the ID is unknown. This method enables a
client to retrieve a profile using a priori knowledge of
the profile's ID (perhaps from a previous search).
The
ProfileManager
interface
builds on the
ProfileHandler
, and is
listed below:
package jpl.eda.profile.handlers;
import java.util.Collection;
import java.util.Iterator;
import jpl.eda.profile.Profile;
import jpl.eda.profile.ProfileException;
import jpl.eda.xmlquery.XMLQuery;
public interface ProfileManager extends ProfileHandler {
void add(Profile profile) throws ProfileException;
void addAll(Collection collection) throws ProfileException;
void clear() throws ProfileException;
boolean contains(Profile profile) throws ProfileException;
boolean containsAll(Collection collection) throws ProfileException;
Collection getAll() throws ProfileException;
boolean isEmpty() throws ProfileException;
Iterator iterator() throws ProfileException;
boolean remove(String profID, String version) throws ProfileException;
boolean remove(String profID) throws ProfileException;
int size() throws ProfileException;
void replace(Profile profile) throws ProfileException;
}
If you choose to implement a profile manager, please see
the API documentation for the
ProfileManager
class for the
expectations of each method.
Although not terribly useful, a
“
null
”
profile
handler is a good example to start with because it is small and
will make sure your environment is in good working order before
proceeding to a real profile handler.
What's a
“
null
”
profile handler? It's one that
serves no profiles. That is, for any query with
findProfiles
and any retrieval with
get
it never returns any profiles.
For these examples, we'll work on a kind of Unix system
with a
csh
shell. Other shell users or Windows
users will need to adjust. We'll also use the J2SDK
command-line tools. If you're using an Integrated
Development Environment of some sort, please adjust
accordingly.
We'll create a "home" directory for our profile ser
vers
with subdirectories to hold specific components like source
code, jar files, and scripts. We'll call this home
directory by an environment variable,
PS_HOME
(PS for Profile Server), so that scripts won't have to refer
to things by relative paths:
% mkdir ps
% cd ps
% setenv PS_HOME `pwd`
% mkdir bin classes lib src
One of the easier parts is the source itself for the
‘
null
’
profile handler. Here it is:
import java.util.Collections;
import java.util.List;
import jpl.eda.profile.Profile;
import jpl.eda.profile.handlers.ProfileHandler;
import jpl.eda.xmlquery.XMLQuery;
public class NullHandler implements ProfileHandler {
public List findProfiles(XMLQuery query) {
return Collections.EMPTY_LIST;
}
public Profile get(String id) {
return null;
}
}
Note that for every query, the
findProfiles
method returns an empty list
(meaning that no profiles matched), and that for any retrieval
the
get
method returns null, meaning that
the handler believes there's no such profile.
This class should be compiled into a file named
$PS_HOME/src/NullHandler.java
since it is a public class.
Note:
Profile handler classes
must
be
public
and
provide a no-arguments
constructor. You should retrieve any initialization
settings through the System Properties or by other means
specific to your profile handler.
Compiling this profile handler requires the following
dependent components:
-
Profile Service
. This
defines the entire profile model, handler interfaces,
servers, clients, and so forth.
-
Query Expression
. This
defines the
XMLQuery
and related classes.
Download the binary distributions of the above two packages
and copy the jar file from each into the
$PS_HOME/lib
directory. Then you can compile
the
NullHandler.java
file.
% ls
bin classes lib src
% ls -l lib
total 244
-rw-r--r-- 1 kelly kelly 43879 28 Feb 07:05 edm-query-2.0.2.jar
-rw-r--r-- 1 kelly kelly 201453 28 Feb 07:01 grid-profile-3.0.2.jar
% javac -extdirs lib -d classes src/NullHandler.java
% ls -l classes
total 4
-rw-r--r-- 1 kelly kelly 511 28 Feb 07:07 NullHandler.class
% jar -cf lib/my-handler.jar -C classes NullHandler.class
% jar -tf lib/my-handler.jar
META-INF/
META-INF/MANIFEST.MF
NullHandler.class
We now have a new jar file,
my-handler.jar
which contains our
‘
null
’
profile handler,
compiled and ready to go.
Clients access profile servers with an open-ended set of
network protocols. We currently have implementations for
RMI and CORBA. For this tutorial, we'll use RMI, since it's
enormously less complex. Clients of RMI systems first
contact an RMI registry and look up a server object's
network address. The registry maintains mappings from a
server object's name to its network address. When servers
start up, they register with the RMI registry so clients can
later find them.
To start an RMI Registry, you'll need the following components:
Download each component's binary distribution, unpack each
one
, and take collect the jar files into the
lib
directory. The RMI Registry will also need
the
grid-profile
jar file, which we've already
got.
% ls -l $PS_HOME/lib
total 404
-rw-r--r-- 1 kelly kelly 149503 28 Feb 07:28 edm-commons-2.2.5.jar
-rw-r--r-- 1 kelly kelly 43879 28 Feb 07:05 edm-query-2.0.2.jar
-rw-r--r-- 1 kelly kelly 201453 28 Feb 07:01 grid-profile-3.0.2.jar
-rw-r--r-- 1 kelly kelly 796 28 Feb 07:07 my-handler.jar
-rw-r--r-- 1 kelly kelly 8055 28 Feb 07:28 rmi-registry-1.0.0.jar
Now all we need is a convenient script to start the RMI
registry. We'll call it
rmi-reg
and stick it
in the
bin
directory. Here's the
rmi-reg
script:
#!/bin/sh
exec java -Djava.ext.dirs=$PS_HOME/lib \
gov.nasa.jpl.oodt.rmi.RMIRegistry
This script tells the Java virtual machine to find
extension jars in the directory
$PS_HOME/lib
. It
then says that the main class to execute is
gov.nasa.jpl.oodt.rmi.RMIRegistry
.
Go ahead and make this script executable and start the RMI
Registry. In another window (with the appropriate setting of
PS_HOME
), run
$PS_HOME/bin/rmi-reg
. You should see output
similar to the following:
% chmod 755 $PS_HOME/bin/rmi-reg
% $PS_HOME/bin/rmi-reg
Mon Feb 28 07:30:13 CST 2005: no objects registered
The RMI Registry is now running. Every two minutes it will
display an update of all registered objects. Naturally, we
don't have any profile service running right now, so it will
say
no objects registered
. Go ahead and ignore
this window for now. It's time to start our profile server.
With our handler compiled and our RMI registry running,
we're ready t
o start our profile server. As said before,
profile servers delegate to zero or more profile handlers to
actually handle all incoming requests. You tell the profile
server what handlers to instantiate by naming their classes
in a system property. That property is called
handlers
, and its value is a comma-separated
list of fully qualified class names, including the package
name prefixes. Since our
NullHandler
is just
in the default package,
NullHandler
is
its fully-qualified class name.
Profile server processes require the following components
in addition to the ones we've downloaded so far:
Copy these two other jars to the
$PS_HOME/lib
directory. You should now have seven jars there:
% ls -l $PS_HOME/lib
total 1580
-rw-r--r-- 1 kelly kelly 149503 28 Feb 07:28 edm-commons-2.2.5.jar
-rw-r--r-- 1 kelly kelly 43879 28 Feb 07:05 edm-query-2.0.2.jar
-rw-r--r-- 1 kelly kelly 201453 28 Feb 07:01 grid-profile-3.0.2.jar
-rw-r--r-- 1 kelly kelly 1144107 28 Feb 09:23 jena-1.6.1.jar
-rw-r--r-- 1 kelly kelly 796 28 Feb 07:07 my-handler.jar
-rw-r--r-- 1 kelly kelly 8055 28 Feb 07:28 rmi-registry-1.0.0.jar
-rw-r--r-- 1 kelly kelly 53978 28 Feb 09:20 xmlrpc-1.1.jar
Now, create a second shell script to make starting the
profile server convenient and call it
$PS_HOME/bin/ps
. It should look like this:
#!/bin/sh
exec java -Djava.ext.dirs=$PS_HOME/lib \
-Dhandlers=NullHandler \
jpl.eda.ExecServer \
jpl.eda.profile.rmi.ProfileServiceImpl \
urn:eda:rmi:MyProfileService
Make the script executable and start the profile server:
% chmod 755 $PS_HOME/bin/ps
% $PS_HOME/bin/ps
Object context ready; delegating to: [jpl.eda.object.jndi.RMIContext@dec8b3]
The profile server will start, check its
handlers
property, and create an object of each
class named by it. Then it'll register itself with the RMI
registry and wait for requests to come in from profile clients.
The profile server registers itself using a name provided
on the command-line, in this case,
urn:eda:rmi:MyProfileService
. Let's take apart
the name and see how it works.
If you're familiar with web standards, you can see that the
name is a Uniform Resource Name (URN), since it starts with
urn:
. The OODT Framework uses URNs to identify
services and other objects. The
eda:
tells
that the name is part of the Enterprise Data Architecture
(EDA) namespace. (EDA was the name of a project related to
OODT that was merged with OODT. For now, just always use
eda:
in your URNs.)
Next comes
rmi:
. This is a special flag for
the OODT services that tells that we're using a name of an
RMI-accessible object. The OODT framework will know to use
an RMI registry to register the server.
Finally is
MyProfileService
. This is the
actual name used in the RMI registry. You can call your
profile server anything you want. For
example, suppose you
have three profile servers; one in the US, one in Canada,
and one in Australia. You might name them:
-
urn:eda:rmi:US
-
urn:eda:rmi:Canada
-
urn:eda:rmi:Australia
Or you might prefer to use ISO country codes. Or you might
name them according to the kinds of profiles they serve,
such as
urn:eda:rmi:BiomarkerMetadata
or
urn:eda:rmi:BusniessForecastMetadata
.
The RMI registry will happily re-assign a name if one's
already in use, so when deploying your own profile (and
other) servers, be sure to give each one a unique name.
To query a profile server, you use the
ProfileClient
class. It provides methods to
contact a named profile server, performing the lookup in the
RMI registry, contacting the profile server, and passing in
queries and profile retrievals. The
ProfileClient
class is also an
executable
class, making it perfect for testing a
new profile server from the command-line.
Still, we'll make a script, called
$PS_HOME/bin/pc
(for "profile client") to
execute it, though, to save from having to type hugely long
Java command-lines:
#!/bin/sh
if [ $# -ne 1 ]; then
echo "Usage: `basename $0` <query-expression>" 1>&2
exit 1
fi
exec java -Djava.ext.dirs=$PS_HOME/lib \
jpl.eda.profile.ProfileClient \
urn:eda:rmi:MyProfileService \
"$1"
Make this script executable and then run it:
% chmod 755 $PS_HOME/bin/pc
% $PS_HOME/bin/pc "temperature = 37"
Object context ready; delegating to: [jpl.eda.object.jndi.RMIContext@dec8b3]
[]
Although it may not look spetacular, this is a success!
The two square brackets,
[
]
, indicates the list
of matching profiles to our query expression,
temperature = 37
. In this case, there were no
matches, which is exactly what we wanted.