blob: 91bfc007dd229a87550735eaf61530c86c5c56bd [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<document>
<properties>
<title>Your First Product Service</title>
<author email="Sean.Kelly@jpl.nasa.gov">Sean Kelly</author>
</properties>
<body>
<section name="Your First Product Service">
<p>This tutorial introduces starting a basic product server.
This product server will be capable of accepting queries, but
will not actually respond with any data. By completing this
tutorial, you'll have a working product server in which you
can install more complex logic to actually handle product
requests.
</p>
</section>
<section name="The Product Service">
<p>The OODT Product Service is a remotely accessible software
component that enables you to retrieve products, which can be
any kind of data. In OODT, a <em>product client</em> passes a
<em>query</em> into a known product server. The product
server delegates that query to its installed <em>query
handlers</em>; each one gets a chance at satisfying the query
with requested data. Query handlers are the interfaces between
the generic OODT framework and your system-specific data
stores. They have the job of understanding the passed in
query, finding or even synthesizing the matching product,
applying conversions to Internet-standard formats, and
returning results. The product service then collects all the
matching products from the query handlers and returns them to
the product client.
</p>
<p>To deploy a product server, you need to come up with query
handlers that interface to your site- or discipline-specific
data stores. Then, start a product server and inform it of
what query handlers to use.
</p>
<subsection name="Delegation Model">
<p>The OODT product service <em>delegates</em> incoming
queries to zero or more query handlers. In the case of zero
query handlers, the product service always replies to every
query with "zero matches." Otherwise, the query handlers
get a chance to satisfy the query, and may or may not add
matching products to the result.
</p>
<p>The following class diagram demonstrates this delegation model:</p>
<img src="../../images/delegation.png" alt="Delegation Class Diagram" />
<p>Here, a product client calls a server to process a query
for products. The server delegates to query handlers, which
are Java objects that implement the
<code>QueryHandler</code> interface. Two query handlers in
this diagram, <code>MyHandler</code> and
<code>MyOtherHandler</code> can both try to satisfy the
query by adding matching products to the total result. They
can each add more than one matching product, just one, or
none at all. The server then returns the matches, if any,
to the client.
</p>
</subsection>
<subsection name="Large Products">
<p>In OODT, a query contains its matching products. When a
client passes a query to a product server, the query object
returns to the client <em>with matching products embedded in
it</em>. This can, however, make query objects too large to
be comfortably passed around a network of OODT services (query
objects must reside completely in memory). In this case, a
special extension of a <code>QueryHandler</code>, a
<code>LargeProductQueryHandler</code>, can instead place a
<em>reference</em> to the product data in the query.
</p>
<p>To product clients, the difference is invisible: the
product data is still accessed from the query object the
same way. As a developer of product services, though, you
may need to decide which kind of query handler to make:
regular or large.
</p>
</subsection>
<subsection name="Communicating with a Product Service">
<p>The product service is a remotely accessible object.
Therefore, product clients access it with a remote object
access protocol. Currently, OODT supports RMI and CORBA. You
can also access product services with HTTP; in this case, a
proxy object provides the HTTP interface while internally it
accesses a product service with RMI or CORBA.
</p>
<p>For this tutorial, we'll use RMI because it's enormously
less complex than CORBA.
</p>
</subsection>
</section>
<section name="Making the Staging Area">
<p>To start a product service, we'll create a directory
structure that will hold software components (jar files) as
well as scripts that will simplify the usually over-long Java
command lines. (Note that these examples are for Mac OS X,
Linux, or other Unix-like systems. Windows users will have to
adapt.)
</p>
<p>Let's start by making a directory hierarchy for our product
service called <code>ps</code> (this example uses a C-style
shell <code>csh</code>, if you're using <code>bash</code> or
another style shell, substitute the appropriate commands).
</p>
<source>% <b>mkdir ps</b>
% <b>cd ps</b>
% <b>setenv PS_HOME `pwd`</b>
% <b>mkdir bin lib</b>
% <b>ls -RF $PS_HOME</b>
bin/ lib/
/Users/kelly/tmp/ps/bin:
/Users/kelly/tmp/ps/lib:</source>
<p>Note that we're using an environment variable
<code>PS_HOME</code> to contain the path of the directory
we're using to hold everything. We'll use this environment
variable as we develop the scripts to launch the product service.
</p>
</section>
<section name="The RMI Registry">
<p>Since we're using Remote Method Invocation (RMI) for this
tutorial, we'll need to start an RMI Registry. An RMI
Registry serves as a catalog that maps between named objects,
such as your product server, to the appropriate network
address and port where the object can be located. Your
product client will use the RMI registry to locate the product
server so it can connect to the product server and communicate with it.
</p>
<subsection name="Collecting the RMI Registry Components">
<p>To start an RMI Registry, you'll need the following components:</p>
<ul>
<li><a href="/edm-commons/">EDM Common Components</a>.
These are common utilities used by every OODT
service.</li> <li><a href="/grid-product/">Grid Product
Service</a>. This is the product service, product client,
query handler interface, and related classes.</li> <li><a
href="/rmi-registry/">OODT RMI Registry</a>. This is the
actual RMI registry.</li>
</ul>
<p>Download each component's binary distribution, unpack each
one, and take collect the jar files into the
<code>lib</code> directory. For example:
</p>
<source>% <b>cp /tmp/edm-commons-2.2.5/*.jar $PS_HOME/lib</b>
% <b>cp /tmp/grid-product-3.0.3/*.jar $PS_HOME/lib</b>
% <b>cp /tmp/rmi-registry-1.0.0/*.jar $PS_HOME/lib</b>
% <b>ls -l $PS_HOME/lib</b>
total 312
-rw-r--r-- 1 kelly kelly 149503 24 Feb 14:06 edm-commons-2.2.5.jar
-rw-r--r-- 1 kelly kelly 120844 24 Feb 14:07 grid-product-3.0.3.jar
-rw-r--r-- 1 kelly kelly 8055 24 Feb 14:07 rmi-registry-1.0.0.jar</source>
</subsection>
<subsection name="Writing the RMI Script">
<p>To keep from having to type long Java command lines, we'll
create a simple shell script that will start the RMI
registry. We'll call it <code>rmi-reg</code> and stick it
in the <code>bin</code> directory.
</p>
<p>Here's the <code>rmi-reg</code> script:</p>
<source>#!/bin/sh
exec java -Djava.ext.dirs=$PS_HOME/lib \
gov.nasa.jpl.oodt.rmi.RMIRegistry</source>
<p>This script tells the Java virtual machine to find
extension jars in the directory <code>$PS_HOME/lib</code>. It
then says that the main class to execute is
<code>gov.nasa.jpl.oodt.rmi.RMIRegistry</code>.
</p>
<p>Go ahead and make this script executable and start the RMI
Registry. In another window (with the appropriate setting of
<code>PS_HOME</code>), run
<code>$PS_HOME/bin/rmi-reg</code>. You should see output
similar to the following:
</p>
<source>% <b>chmod 755 $PS_HOME/bin/rmi-reg</b>
% <b>$PS_HOME/bin/rmi-reg</b>
Thu Feb 24 14:10:25 CST 2005: no objects registered</source>
<p>The RMI Registry is now running. Every two minutes it will
display an update of all registered objects. Naturally, we
don't have any product service running right now, so it will
say <code>no objects registered</code>. Go ahead and ignore
this window for now. It's time to start our product server.
</p>
</subsection>
</section>
<section name="The Product Server">
<p>With an RMI Registry in place, we're ready to start our
product server. As with the RMI Registry, we'll need the
software components and to make a script to launch it.
</p>
<subsection name="Collecting the Product Server Components">
<p>We already have two of the components needed to start the
product server, <code>edm-commons</code> and
<code>grid-product</code>. We need two more:
</p>
<ul>
<li><a href="/edm-query/">EDM Query Expression</a>. This
component encapsulates the implementation of an OODT query
and also contains some product retrieval utilities.</li>
<li><a href="http://ws.apache.org/xmlrpc">Apache
XML-RPC</a>. This is used internally by OODT services.
Download version 1.1, not a later version! If you prefer,
you can <a
href="http://ibiblio.org/maven/xmlrpc/jars/xmlrpc-1.1.jar">fetch
the jar file directly</a>.</li>
</ul>
<p>As before, put these jars into the
<code>$PS_HOME/lib</code> directory:
</p>
<source>% <b>ls -l $PS_HOME/lib</b>
total 376
-rw-r--r-- 1 kelly kelly 149503 24 Feb 14:06 edm-commons-2.2.5.jar
-rw-r--r-- 1 kelly kelly 43879 24 Feb 14:35 edm-query-2.0.2.jar
-rw-r--r-- 1 kelly kelly 120844 24 Feb 14:07 grid-product-3.0.3.jar
-rw-r--r-- 1 kelly kelly 8055 24 Feb 14:07 rmi-registry-1.0.0.jar
-rw-r--r-- 1 kelly kelly 53978 24 Feb 14:35 xmlrpc-1.1.jar</source>
</subsection>
<subsection name="Writing the Product Server Script">
<p>To launch the product server, we'll create a script called
<code>ps</code> in the <code>$PS_HOME/bin</code> directory.
Here's its contents:
</p>
<source>#!/bin/sh
exec java -Djava.ext.dirs=$PS_HOME/lib \
jpl.eda.ExecServer \
jpl.eda.product.rmi.ProductServiceImpl \
urn:eda:rmi:MyProductService</source>
<p>Like with the RMI server, this tells Java where to find
extension jars (<code>$PS_HOME/lib</code>). The main class
is <code>jpl.eda.ExecServer</code>, this is a framework
class from <code>edm-commons</code> that provides basic
start-up functions for a variety of services. In this case,
the service is
<code>jpl.eda.product.rmi.ProductServiceImpl</code>; this is
the name of the class that provides the RMI version of the
OODT product service. We then pass in one final
command-line argument,
<code>urn:eda:rmi:MyProductService</code>. This names the product service.
</p>
</subsection>
<subsection name="What's in a Name?">
<p>The product service registers itself using a name provided
on the command-line, in this case,
<code>urn:eda:rmi:MyProductService</code>. Let's take apart
the name and see how it works.
</p>
<p>If you're familiar with web standards, you can see that the
name is a Uniform Resource Name (URN), since it starts with
<code>urn:</code>. The OODT Framework uses URNs to identify
services and other objects. The <code>eda:</code> 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
<code>eda:</code> in your URNs.)
</p>
<p>Next comes <code>rmi:</code>. 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.
</p>
<p>Finally is <code>MyProductService</code>. This is the
actual name used in the RMI Registry. You can call your
product service anything you want. For example, suppose you
have three product servers; one in the US, one in Canada,
and one in Australia. You might name them:
</p>
<ul>
<li><code>urn:eda:rmi:US</code></li>
<li><code>urn:eda:rmi:Canada</code></li>
<li><code>urn:eda:rmi:Australia</code></li>
</ul>
<p>Or you might prefer to use ISO country codes. Or you might
name them according to the kinds of products they serve,
such as <code>urn:eda:rmi:Biomarkers</code> or
<code>urn:eda:rmi:BusniessForecasts</code>.
</p>
<p>The RMI Registry will happily re-assign a name if one's
already in use, so when deploying your own product servers,
be sure to give each one a unique name.
</p>
</subsection>
<subsection name="Launching the Product Server">
<p>Make the <code>ps</code> script executable and start the
product server at this time. Do this in a separate window
with the appropriate setting of <code>PS_HOME</code>:
</p>
<source>% <b>chmod 755 $PS_HOME/bin/ps</b>
% <b>$PS_HOME/bin/ps</b>
Object context ready; delegating to: [jpl.eda.object.jndi.RMIContext@94257f]</source>
<p>The product service is now running and ready to accept
product queries. Since we didn't tell it what query
handlers to use, it will always respond with zero matching
products. That may not be interesting, but it's a good test
to see if we can at least launch a product server. Now,
let's launch a product client and query it.
</p>
</subsection>
</section>
<section name="Querying the Product Server">
<p>To query the product server, we use a product client. The
Java class <code>jpl.eda.product.ProductClient</code> provides
the API for your own programs to query for and retrieve
products. But it's also an executable class, so we can run it
from the command-line in order to test our product server.
However, let's again make a script to make invoking it a bit
easier.
</p>
<p>We'll call the script <code>pc</code> for "product client,"
and it will take a single command line argument, which will be
the <em>query expression</em> to pass into the product server.
Query expressions define the constraints on the kinds of
products we want to achieve. Since the product server we've
set up will always respond with zero products, though, we can
pass in any syntactically valid query expression.
</p>
<p>Here's the script:</p>
<source>#!/bin/sh
if [ $# -ne 1 ]; then
echo "Usage: `basename $0` &lt;query-expression&gt;" 1>&amp;2
exit 1
fi
exec java -Djava.ext.dirs=$PS_HOME/lib \
jpl.eda.product.ProductClient \
-out \
urn:eda:rmi:MyProductService \
"$1"</source>
<p>This script checks to make sure there's exactly one
command-line argument, the query expression. If there isn't,
it prints a helpful usage message to the standard error
stream, as is Unix tradition. Otherwise, it will execute the
<code>jpl.eda.product.ProductClient</code> class with Java.
When executed, this class expects three command-line arguments:
</p>
<ol>
<li><code>-out</code> or <code>-xml</code>. OODT uses XML to
represent the query that it passes to and receives back from
a product server. With <code>-out</code>, the product
client will write to the standard output the raw product
data. With <code>-xml</code>, you'll instead see the XML
representation of the query (with any embedded matching
products) instead.
</li>
<li>The name of the product service to contact. In this case,
we're using the one we started earlier, registered under the
name <code>urn:eda:rmi:MyProductService</code>.
</li>
<li>The query expression.</li>
</ol>
<p>Now we can make this script executable and run it:</p>
<source>% <b>chmod 755 $PS_HOME/bin/pc</b>
<b>$PS_HOME/bin/pc "x = 3"</b>
Object context ready; delegating to: [jpl.eda.object.jndi.RMIContext@c79809]
No matching results</source>
<p>Although not terribly exciting, this is good news. Here's
what happened:
</p>
<ol>
<li>The product client created a query object (of class
<code>jpl.eda.xmlquery.XMLQuery</code> from the
<code>edm-query</code> component) out of the string query
<code>x = 3</code>.
</li>
<li>It asked the RMI Registry to tell it where (network
address) it could find the product service named
<code>MyProductService</code>.
</li>
<li>After getting the response back from the RMI Registry, it
then contacted the product service over a network connection
(even if to the same local system) and asked it to handle
the query, passing the query object.
</li>
<li>The product service, having no query handlers to which to
delegate, merely returned the query object unmodified over
the network connection.
</li>
<li>The product client, having no product to write to the
standard output (as indicated by the <code>-out</code>
argument), wrote the diagnostic message <code>No matching
results</code>.
</li>
</ol>
<p>You can make this example slightly more interesting by
changing the <code>-out</code> in the <code>pc</code> script
to <code>-xml</code>. Now, when you run it, you'll see an XML
document describing the query. One of the pertinent sections
to note is:
</p>
<source>...&lt;queryResultSet/&gt;...</source>
<p>This empty XML element means that there were no results.</p>
</section>
<section name="Conclusion">
<p>By following this tutorial, you've started both an RMI
Registry and a basic product server. You've queried that
product server to insure that you can communicate with it. In
later tutorials, you'll build on this product server by adding
a query handler to it and returning actual product data.
</p>
</section>
</body>
</document>