spacer spacer spacer
spacer spacer spacer
spacer
NASA Jet Propulsion Laboratory, California Institute of Technology + View the NASA Portal

+ NASA en Español

+ Contact NASA
Search the API    

Developing a Real Profile Handler

In the basic profile handler tutorial , we developed a profile handler that answered every query with a list of exactly zero profile objects. It also responded to requests to retrieve a profile by a specific ID with null , meaning "not found by this handler." Useful, huh? Not really.

But it did get our profile server ready for this tutorial, where we'll write a real profile handler that analyzes incoming queries, consults a local "database", constructs a set of matching profile results, and responds to requests to profiles by ID. Get some fresh coffee, because this is going to be a tough one.

And yes, you'll need to have gone through all of the following before proceeding:

The Music Database

Let's say you've got an OODT product server already running that serves up your favorite music files. All you have to do is pass in the URI to a track and it spits back the MP3 data which can run into your favorite media player. You've set it up so your URIs are all unique for each track, and you just have to pass in an unparsed XMLQuery like urn:sk:tr5B7E.mp3 and you get the matching data.

But since you're not in the habit of memorizing hexadecimal numbers inside of URIs, let's write a profile ser ver who's job it is to take queries for specific artists, genres, albums, ratings, track titles, and so forth, and spit out the matching profiles. The profiles have a places for a URI (in the Identifier field) that you can then pass to the hypothetical product server to get the track data. (In fact, this is a common OODT pattern: profile query to do resource location, product query to do resource retrieval.) While you're listening to the track, you can read all sorts of other juicy metadata about it by examining the returned profile.

The Metadata

For this demonstration, we'll just focus on three kinds of metadata instead of going all-out, iTunes style:

  • Artist name
  • Album name
  • Track name

Each profile will describe a single track. The resource attributes will have:

  • The URI of the track as the Identifier .
  • The name of the track as the Title .
  • The name of the artist as the Creator .

In addition, we'll put in two profile elements:

  • The name of the album as an EnumeratedProfileElement .
  • The name of the artist as an EnumeratedProfileElement . Yes, this is redundant with the artist named as the Creator in the resource attributes; but one profile element by itself would get too lonely!

Query Style

Both product handlers and profile handlers get to choose whether they want unparsed query expressions in their XMLQuery objects or if they want parsed query expressions. Parsed query expressions generate the "where" boolean stack. While the product server that this profile server is meant to work with wants unparsed ones, we'll use parsed expressions for this profile handler. Why? Well, having a well-defined query language and a way to operate on it will save a little trouble from us having to generate a parser.

The queries will use element names artist , album , and track only, to match what we'll save in our music database. Here are a couple example queries:

artist = Beatles AND album = Revolver
track = 'Blue Suede Shoes'

Developing the Handler

We'll develop the handler in parts, so we can discuss each section, and then show the entire source file.

Making the "Database"

Our music database will be nothing more than Java objects kept in memory. We'll create separate objects of three classes:

  • Artist . Artist objects represent people or groups who create music. Artist s will have zero or more Track s.
  • Album . Album objects are collections of Track s.
  • Track . Track objects appear on one Album and are made by one Artist . They have the URN necessary to pass to the hypothetical music product server in order to actually play music.

A better music model would probably separate out artists and composers, account for remixes, compilation albums, re-issues, multiple renditions, and so forth, but this is government work, and it'll do.

Here's class Artist :

class Artist {
  public Artist(String name) {
    this.name = name;
    tracks = new ArrayList();
  }
  public String getName() {
    return name;
  }
  public List getTracks() {
    return tracks;
  }
  public int hashCode() {
    return name.hashCode();
  }
  public boolean equals(Object obj) {
    if (obj == this) return true;
    if (!(obj instanceof Artist)) return false;
    Artist rhs = (Artist) obj;
    return name.equals(rhs.name);
  }
  private String name;
  private List tracks;
}

As you can see, Artist s have a name and a List of Track s they've made. Now, here's class Album :

class Album {
  public Album(String name) {
    this.name = name;
    tracks = new ArrayList();
  }
  public String getName() {
    return name;
  }
  public List getTracks() {
    return tracks;
  }
  public int hashCode() {
    return name.hashCode();
  }
  public boolean equals(Object obj) {
    if (obj == this) return true;
    if (!(obj instanceof Album)) return false;
    Album rhs = (Album) obj;
    return name.equals(rhs.name);
  }
  private String name;
  private List tracks;
}

As with Artist s, Album s (or should that be Alba ?) have names and collections of Track s. Finally, here's class Track :

class Track {
  public Track(String name, URI id, Artist artist,
    Album album) {
    this.name = name;
    this.id = id;
    this.artist = artist;
    this.album = album;
    artist.getTracks().add(this);
    album.getTracks().add(this);
  }
  public String getName() { return name; }
  public URI getID() { return id; }
  public Artist getArtist() { return artist; }
  public Album getAlbum() { return album; }
  public int hashCode() {
    return name.hashCode() ^ id.hashCode();
  }
  public boolean equals(Object obj) {
    if (obj == this) return true;
    if (!(obj instanceof Track)) return false;
    Track rhs = (Track) obj;
    return id.equals(rhs.id);
  }
  private String name;
  private URI id;
  private Artist artist;
  private Album album;
}

As you can see from the code, a track belongs to an Artist and to an Album and has a URI which you can use to get to the track's MP3 data. Finally, with these three "entity" classes in hand, we can create a music database:

class DB {
  public static Set ARTISTS = new HashSet();
  public static Set ALBUMS = new HashSet();
  public static Set TRACKS = new HashSet();
  static {
    Artist bach = new Artist("Bach");
    Album brandenburg123
      = new Album("Brandenburg Concerti 1, 2, 3");
    Album brandenburg456
      = new Album("Brandenburg Concerti 4, 5, 6");
    Track brandenburg1
      = new Track("Brandenburg Concerto #1",
      URI.create("urn:sk:tr91BC.mp3"), bach,
      brandenburg123);
    Track brandenburg2
      = new Track("Brandenburg Concerto #2",
      URI.create("urn:sk:tr311E.mp3"), bach,
      brandenburg123);
    Track brandenburg3
      = new Track("Brandenburg Concerto #3",
      URI.create("urn:sk:trA981.mp3"), bach,
      brandenburg123);
    Track brandenburg4
      = new Track("Brandenburg Concerto #4",
      URI.create("urn:sk:tr233A.mp3"), bach,
      brandenburg456);
    Track brandenburg5
      = new Track("Brandenburg Concerto #5",
      URI.create("urn:sk:trA6E5.mp3"), bach,
      brandenburg456);

     Track brandenburg6
      = new Track("Brandenburg Concerto #6",
      URI.create("urn:sk:tr01E9.mp3"), bach,
      brandenburg456);

    Artist delerium = new Artist("Delerium");
    Album semantic = new Album("Semantic Spaces");
    Album poem = new Album("Poem");
    Track flowers
      = new Track("Flowers Become Screens",
      URI.create("urn:sk:tr3A5E.mp3"), delerium,
      semantic);
    Track metaphor = new Track("Metaphor",
      URI.create("urn:sk:tr0E13.mp3"), delerium,
      semantic);
    Track innocente = new Track("Innocente",
      URI.create("urn:sk:tr004A.mp3"), delerium,
      poem);
    Track aria = new Track("Aria",
      URI.create("urn:sk:tr004A.mp3"), delerium,
      poem);

    ARTISTS.add(bach);
    ARTISTS.add(delerium);
    ALBUMS.add(brandenburg123);
    ALBUMS.add(brandenburg456);
    ALBUMS.add(semantic);
    ALBUMS.add(poem);
    TRACKS.add(brandenburg1);
    TRACKS.add(brandenburg2);
    TRACKS.add(brandenburg3);
    TRACKS.add(brandenburg4);
    TRACKS.add(brandenburg5);
    TRACKS.add(brandenburg6);
    TRACKS.add(flowers);
    TRACKS.add(metaphor);
    TRACKS.add(innocente);
    TRACKS.add(aria);
  }
}

(Please don't judge this limited collection as the breadth of my listening tastes. It's actually much narrower now!) In this small database, we've got two artists, Bach and Delerium, with four albums: Brandenburg Concerti 1, 2, 3 and 4, 5, 6 ; and Semantic Spaces and Poem . And we've got 10 tracks: 3 belonging to one album, 3 belonging to another, 2 belonging to yet another, and the last 2 belonging to the last album. Six are by Bach, and four by Delerium. Each track has

Querying our Database

Recall that the XMLQuery 's query language uses triples of the form (element, relation, literal) like album != Poem . The relations include =, !=, < , > , < =, > =, LIKE, and NOTLIKE. The triples are linked with AND, OR, and NOT. For this tutorial, we'll do the = and != cases. The rest you can fill in for your own edification. Our approach will be to examine the postfix "where" boolean stack and convert it into an infix boolean expression tree. We'll ask the tree to evaluate itself into a matching set of Track s. Then all we have to do is descibe the matching Track s as Profile objects.

Let's start by defining a node in our expression tree:

interface Expr {
  Set evaluate();
}

The evaluate method means "evaluate into a Set of matching Track objects." With this interface, we can then define classes that make up different flavors of tree nodes. One of the easier ones is a constant tree node that either matches every track available (constant true) or none of them (constant false):

class Constant implements Expr {
  public Constant(boolean value) {
    this.value = value;
  }
  public Set evaluate() {
    return value? DB.TRACKS
      : Collections.EMPTY_SET;
  }
  private boolean value;
}

Next, let's do negation. This takes the set complement of an existing tree node:

class Not implements Expr {
  public Not(Expr expr) {
    this.expr = expr;
  }
  public Set evaluate() {
    Set matches = expr.evaluate();
    Set inverse = new HashSet();
    for (Iterator i = DB.TRACKS.iterator();
      i.hasNext();) {
      Track t = (Track) i.next();
      if (!matches.contains(t))
        inverse.add(t);
    }
    return inverse;
  }
  private Expr expr;
}

As you can see, this node is constructed with another tree node expression. To evaluate this node, we evaluate the expression passed in. Then we take its inverse by iterating through each track in the database and adding it to the matching set if it doesn't occur in the expression's matching set.

The union tree node takes two expressions and adds the two sets of matching tracks together:

class Or implements Expr {
  public Or(Expr lhs, Expr rhs) {
    this.lhs = lhs;
    this.rhs = rhs;
  }
  public Set evaluate() {
    Set left = lhs.evaluate();
    Set right = rhs.evaluate();
    left.addAll(right);
    return left;
  }
  private Expr lhs;
  private Expr rhs;
}

The intersection tree node evaluates to Track s that occur only in both expressions' tracks:

class And implements Expr {
  public And(Expr lhs, Expr rhs) {
    this.lhs = lhs;
    this.rhs = rhs;
  }
  public Set evaluate() {
    Set left = lhs.evaluate();
    Set right = rhs.evaluate();
    left.retainAll(right);
    return left;
  }
  private Expr lhs;
  private Expr rhs;
}

With these nodes, we can cover the logical operators AND, OR, and NOT that appear in a postfix "where" stack, as well as an empty "where" stack, which, by convention, is meant to be a constant "true", matching all available resources. Now we just have to handle triples (element, relation, literal). First up, comparisons against Artist s:

class ArtistExpr implements Expr {
  public ArtistExpr(String op, String value) {
    this.op = op;
    this.value = value;
  }
  public Set evaluate() {
    Set tracks = new HashSet();
    if ("EQ".equals(op)) {
      for (Iterator i = DB.ARTISTS.iterator();
        i.hasNext();) {
        Artist a = (Artist) i.next();
        if (a.getName().equals(value))
          tracks.addAll(a.getTracks());
      }
    } else if ("NE".equals(op)) {
      for (Iterator i = DB.ARTISTS.iterator();
        i.hasNext();) {
        Artist a = (Artist) i.next();
        if (!a.getName().equals(value))
          tracks.addAll(a.getTracks());
      }
    } else throw new
      UnsupportedOperationException("NYI");
    return tracks;
  }
  private String op;
  private String value;
}

For an expression like artist = Bach or artist != Delerium we use a expression node object of the above class. When it's EQ , we iterate through all the artists in the database and, when the artist's name matches, add all of that artist's tracks to the set of matches. When it's NE , we instead add all of the artists' tracks whose name doesn't match. (The other relational operators, LT , GT , LE , GE , LIKE , and NOTLIKE curr ently throw an exception. You're welcome to try to implement those.)

The AlbumExpr expression node is quite similar:

class AlbumExpr implements Expr {
  public AlbumExpr(String op, String value) {
    this.op = op;
    this.value = value;
  }
  public Set evaluate() {
    Set tracks = new HashSet();
    if ("EQ".equals(op)) {
      for (Iterator i = DB.ALBUMS.iterator();
        i.hasNext();) {
        Album a = (Album) i.next();
        if (a.getName().equals(value))
          tracks.addAll(a.getTracks());
      }
    } else if ("NE".equals(op)) {
      for (Iterator i = DB.ALBUMS.iterator();
        i.hasNext();) {
        Album a = (Album) i.next();
        if (!a.getName().equals(value))
          tracks.addAll(a.getTracks());
      }
    } else throw new
      UnsupportedOperationException("NYI");
    return tracks;
  }
  private String op;
  private String value;
}

(Another exercise for the reader: refactor out common code between these two classes.) Finally, the TrackExpr node is for expressions like track = Poem :

class TrackExpr implements Expr {
  public TrackExpr(String op, String value) {
    this.op = op;
    this.value = value;
  }
  public Set evaluate() {
    Set tracks = new HashSet();
    if ("EQ".equals(op)) {
      for (Iterator i = DB.TRACKS.iterator();
        i.hasNext();) {
        Track t = (Track) i.next();
        if (t.getName().equals(value))
          tracks.add(t);
      }
    } else if ("NE".equals(op)) {
      for (Iterator i = DB.TRACKS.iterator();
        i.hasNext();) {
        Track t = (Track) i.next();
        if (!t.getName().equals(value))
          tracks.add(t);
      }
    } else throw new
      UnsupportedOperationException("NYI");
    return tracks;
  }
  private String op;
  private String value;
}

For EQ , we just iterate through every track in the database and a dd it to the set of matching tracks if the names match the name passed into the user's query. For NE , we add them if their names don't match.

That completes all the code for the expression tree. Now we can start working on the class that implements the ProfileHandler interface, MusicHandler . Here, we'll build that expression tree with the incoming XMLQuery , which provides its "where" element stack as a postfix boolean expression. Here's the approach:

  1. Make a new, empty stack.
  2. For each element in the "where" stack:
    1. If it's an element name ( artist , album , track ), push the name onto the stack.
    2. If it's a literal value ( Bach , Poem , etc.), push it onto the stack.
    3. If it's a relational operator ( EQ , NE , etc.):
      1. Pop two values off.
      2. Push an ArtistExpr , AlbumExpr , or TrackExpr .
    4. It it's a logical operator:
      • For AND , pop two values off and push an And node.
      • For OR , pop two values off and push an Or node.
      • For NOT , pop one value off and push a Not node.

In the end, there will be one element left on the stack, an Expr node representing the root of the expression tree. Here's the method of MusicHandler that implements the algorithm:

private static Expr transform(XMLQuery q) {
  Stack stack = new Stack();
  for (Iterator i = q.getWhereElementSet()
    .iterator(); i.hasNext();) {
    QueryElement e = (QueryElement) i.next();
    String keyword = e.getValue();
    String type = e.getRole();
    if ("elemName".equals(type))
	stack.push(keyword);
    else if ("LITERAL".equals(type))
	stack.push(keyword);
    else if ("RELOP".equals(type))
	addRelational(keyword, (String)stack.pop(),
	  (String)stack.pop(), stack);
    else if ("LOGOP".equals(type))
	addLogical(keyword, stack);
    else throw new
	IllegalArgumentException("Unknown query "
	  + type + " type");
  }
  if (stack.size() == 0)
    return new Constant(true);
  else if (stack.size() > 1)
    throw new IllegalArgumentException("Unbalanced"
      + " query");
  else return (Expr) stack.pop();
}

For relational and logical operators, this method defers to two other utility methods, which we'll see shortly. After iterating through the entire "where" set, we check to see if there's an empty stack. That's the case where the user passes in an empty expression, which by convention we'll take to mean they want everything. Otherwise, there should be just one Expr node on the stack, the root of the expression tree.

To handle adding a RELOP , we pop two values off, the element name ( artist , album , or track ), and the literal value the user wants ( Bach , Poem , etc.), along with the operator and the stack:

private static void addRelational(String op,
  String value, String kind, Stack stack) {
  if ("artist".equals(kind))
    stack.push(new ArtistExpr(op, value));
  else if ("album".equals(kind))
    stack.push(new AlbumExpr(op, value));
  else if ("track".equals(kind))
    stack.push(new TrackExpr(op, value));
  else throw new
    IllegalArgumentException("Unknown profile"
      + " element " + kind);
}

This method then replaces the popped off values with the matching Expr class for artists, albums, or tracks.

To handle adding a LOGOP , we pass the logical operator and the entire stack to this method:

private static void addLogical(String op,
  Stack stack) {
  if ("AND".equals(op))
    stack.push(new And((Expr)stack.pop(),
      (Expr) stack.pop()));
  else if ("OR".equals(op))
    stack.push(new Or((Expr)stack.pop(),
      (Expr) stack.pop()));
  else if ("NOT".equals(op))
    stack.push(new Not((Expr)stack.pop()));
  else throw new
    IllegalArgumentException("Illegal operator "
      + op);
}

With all this code in place we can generate the expression tree. Let's look at an example. Suppose when constructing the XMLQuery , the user passed in

artist = Bach and not album = Poem or track != Aria

The XMLQuery query language generates a postfix stack of QueryElement objects in the "where" list:

Stack

And we then create this tree:

Tree

Calling the root's evaluate method then yields a java.util.Set of Track objects that match that expression.

OK, we've got a set of Track s. But what we want are a set of Profile s . The next step is to describe those tracks using the profile metadata model.

Describing Tracks

Query handlers serve up List s of Profile objects, where Profile s contain metadata descriptions of resources. For this tutorial, the resources we're describing are music tracks, represented by instances of Track objects. When the handler's findProfiles and get methods are called by the OODT framework to service a request, all we have to do is find the matching Track (or Track s) and create matching Profile s.

Recall that we'r e setting up the resource attributes of the profile so that

  • The URI of the track appears in the Identifier .
  • The name of the track appears in the Title .
  • The name of the artist appears the Creator .

In addition, we'll put in two profile elements:

  • The name of the album as an EnumeratedProfileElement .
  • The name of the artist redundantly as an EnumeratedProfileElement .

Now, let's create a utility method describe which takes a java.util.Set of matching Track s and yields a java.util.List of corresponding Profile s:

private static List describe(Set tracks) {
  List profiles = new ArrayList();
  for (Iterator i = tracks.iterator();
    i.hasNext();) {
    Track t = (Track) i.next();
    String id = t.getID().toString();
    String trackName = t.getName();
    String albumName = t.getAlbum().getName();
    String artistName = t.getArtist().getName();
    Profile p = createProfile(id, trackName,
      albumName, artistName);
    profiles.add(p);
  }
  return profiles;
}

We build a list of Profile s by calling another method, createProfile . It takes the track's URI, its name, the name of the album on which it appears, and the name of the artist who created it, and yields a Profile :

private static Profile createProfile(String id,
  String trackName, String albumName,
  String artistName) {
  Profile p = new Profile();
  ProfileAttributes pa=new ProfileAttributes(id,
    "1.0", "profile", "active", "unclassified",
    /*parent*/null, /*children*/EL,
    "1.3.6.1.4.1.7655", /*revNotes*/EL);
  p.setProfileAttributes(pa);
  ResourceAttributes ra=new ResourceAttributes(p,
    id, trackName,
    Collections.singletonList("audio/mpeg"),
    /*desc*/null,
    Collections.singletonList(artistName),
    /*subjects*/EL, /*pubs*/EL, /*contrib*/EL,
    /*dates*/EL, /*types*/EL, /*sources*/EL,
    /*langs*/EL, /*relations*/EL, /*covs*/EL,
    /*rights*/EL,
    Collections.singletonList("SK.Music"),
    "granule", "system.productServer",
    Collections.singletonList("urn:eda:rmi:"
      + "MyProductServer"));
    p.setResourceAttributes(ra);
    EnumeratedProfileElement artistElem =
    new EnumeratedProfileElement(p, "artist",
    "artist", "Name of the artist of a work",
    "string", "name", /*syns*/EL, /*ob*/true,
    /*maxOccur*/1, /*comment*/null,
    Collections.singletonList(artistName));
  p.getProfileElements().put("artist",
    artistElem);
  EnumeratedProfileElement albumElem =
    new EnumeratedProfileElement(p, "album",
    "album", "Name of album where track occurs",
    "string", "name", /*syns*/EL, /*ob*/true,
    /*maxOccur*/1, /*comment*/null,
    Collections.singletonList(albumName));
  p.getProfileElements().put("album",
    albumElem);
  return p;
}

The profile attributes say that

  • The ID of the profile itself is the same as the track's URI.
  • The version of the profile is 1.0.
  • The type is "profile".
  • It's currently active.
  • It's not top-secret, it's "unclassified".
  • It has no parent profile.
  • It has no child profiles.
  • The registration authority has OID 1.3.6.1.4.1.7655
  • There are no revision notes.

The resource attributes say that

  • The Identifier is the track's URI.
  • The Title is the track's title.
  • The sole Format in which the track is available is audio/mpeg .
  • There's no description.
  • The sole Creator is the name of the artist.
  • There are no subject keywords, publishers, contributors, dates, types, sources, languages, relations, coverages, nor rights.
  • The sole resource context is "Tutorial.Music".
  • The resource's aggregation is "granule", meaning this profile is describing a single, discrete resource.
  • The resource's class is "system.productServer", meaning you need to contact a product server at the resource location to retrieve the resource.
  • The resource location is urn:eda:rmi:MyProductServer .

Finally, the two profile elements tell (again) who the artist was and also on what album the track appears.

What's with all the EL s? It's just to save on typing:

private static final List EL
  = Collections.EMPTY_LIST;

Implementng the Interface

The ProfileHandler interface stipulates two methods, one for finding profiles given an XMLQuery and another for retrieving a single profile given its ID. With all of these utility methods in place, these are both easy to write. First, the findProfiles method:

public List findProfiles(XMLQuery q) {
  Expr expr = transform(q);
  Set matches = expr.evaluate();
  List profiles = describe(matches);
  return profiles;
}

The algorithm should be painfully obvious by now: transform the query to a tree, evaluate the tree into a set of matching tracks, and describe the tracks.

The get method takes a profile's ID and returns the matching profile, or null if it's not found. Since we're using the track's ID as the profile's ID as well, we can just iterate through our tracks, find the one with the matching ID, and describe it:

public Profile get(String id) {
  URI uri = URI.create(id);
  for (Iterator i = DB.TRACKS.iterator();
    i.hasNext();) {
    Track t = (Track) i.next();
    if (t.getID().equals(uri))
      return createProfile(t.getID().toString(),
        t.getName(), t.getAlbum().getName(),
        t.getArtist().getName());
  }
  return null;
}

Complete Source Code

Don't feel like cutting and pasting all of those code fragments? No problem. All of the source files are available in a jar .

Compiling the Handler

As with the NullHandler , we'll use the J2SDK command-line tools. And if you've gone through the NullHandler tutorial , you've got all the dependent jars in place already. Just put the MusicHandler.java and all the related source files under $PS_HOME/src , compile, and build the jar:

% ls src
Album.java         Expr.java
AlbumExpr.java     MusicHandler.java
And.java           Not.java
Artist.java        NullHandler.java
ArtistExpr.java    Or.java
Constant.java      Track.java
DB.java            TrackExpr.java
% javac -extdirs lib -d classes src/*.java
% ls classes
Album.class         Expr.class
AlbumExpr.class     MusicHandler.class
And.class           Not.class
Artist.class        NullHandler.class
ArtistExpr.class    Or.class
Constant.class      Track.class
DB.class            TrackExpr.class
% cd classes
% jar -uf ../lib/my-handler.jar *.class
% cd ..
% jar -tf lib/my-handler.jar
META-INF/
META-INF/MANIFEST.MF
NullHandler.class
Album.class
AlbumExpr.class
And.class
Artist.class
ArtistExpr.class
Constant.class
DB.class
Expr.class
MusicHandler.class
Not.class
Or.class
Track.class
TrackExpr.class

We also need to update the $PS_HOME/bin/ps script. Currently, it's instantiating just the NullHandler ; we need it to instantiate the MusicHandler too. Stop any c urrently running profile server by pressing CTRL+C (or whatever your interrupt key is) in the window running the server. Then edit the script so it reads as follows:

#!/bin/sh
exec java -Djava.ext.dirs=$PS_HOME/lib \
    -Dhandlers=NullHandler,MusicHandler \
    jpl.eda.ExecServer \
    jpl.eda.profile.rmi.ProfileServiceImpl \
    urn:eda:rmi:MyProfileService

Now the profile server will delegate to two handlers: the NullHandler and the MusicHandler . With more than one handler, the OODT framework calls each one in turn and collects all of the matching profiles together to return to the ProfileClient . (Of course, the NullHandler never actually generates any matching profiles.)

Querying the Profile Server

Start the profile server by running $PS_HOME/bin/ps in one window (presumably the RMI registry is still running in another window). In yet another window, we'll run our $PS_HOME/bin/pc script to query the profile server:

% $PS_HOME/bin/pc 'artist = Delerium
 AND album != Poem OR artist = Bach'
Object context ready; delegating to:
[jpl.eda.object.jndi.RMIContext@dec8b3]
[<?xml version="1.0" encoding="UTF-8"?>
<profile><profAttributes><profId>urn:sk:tr91BC.mp3</profId>...

Whoa! There's a huge load of XML! In fact what the ProfileClient is printing is a java.util.List of profiles in XML format, each separated by a comma, and the whole list in square brackets. If you search this output carefully, though, you can pick out the < Title > elements and see indeed that we've got six matching tracks:

  • Brandenburg Concerto #1
  • Brandenburg Concerto #2
  • Brandenburg Concerto #3
  • Brandenburg Concerto #4
  • Brandenburg Concerto #5
  • Brandenburg Concerto #6
  • Flowers Become Screens
  • Metaphor

Sure enough, this matches the XMLQuery query language expression we passed in: There are tracks by Delerium but not from the Poem album, and there are all the tracks by Bach.

Conclusion

In this long tutorial we developed a real profile handler that answered queries by transforming them from postfix stacks into expression trees and using those trees to query an in-memory database made of Java objects. We then described matching data by creating Profile s.

You might be thinking that this seems like a lot of work, and there might be some easier ways to go. You could use the LightweightProfileHandler for resources that never change, but only if you don't have too many of them and don't mind managing potentially large XML documents. You could choose to use unparsed XMLQuery expressions and instead make the user query in the same language as your data system, obviating the need for complex expression trees.

However, with the tools presented in this tutorial, you could adapt the expression tree code to generating system-specific queries, and describe those results with as much or as little detail as necessary.

Happy profiling!

FirstGov - Your First Click to the US Governmnet

+ Freedom of Information Act

+ NASA Privacy Statement, Disclaimer,

and Accessibility Certification


+ Freedom to Manage
NASA

Editor: Sean Kelly

NASA Official: Dan Crichton

Last Published: 07 July 2006

+ Contact NASA
spacer
spacer spacer spacer
spacer spacer spacer