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 Query Handler

In the last tutorial , we started a product server. But this wasn't a very useful product server; it could answer queries but always respond with no results. That's because it had no query handlers. Query handlers have the responsibility of actually handling product queries. In this tutorial, we'll develop a query handler, install it into our product server, and query it to see if it works.

To do this tutorial, you'll need mastery of two things:

  • Using the XMLQuery class. Follow the query expression tutorail now if you're not familiar with it.
  • Running and querying a product server. Follow the Your First Product Server tutorial to get your product server up and running. In this tutorial, we'll build on that product server, so it's especially important to have it in good shape.

Serving Up Constants

Product servers delegate to query handlers. It's the job of query handlers to interpret incoming queries (expressed as XMLQuery objects), search for, retrieve, convert, or synthesize matching product results, adorn the XMLQuery object with Result objects, and return the modified query. At that point the OODT framework takes over again and tries other installed query handlers, eventually returning the completed XMLQuery back to the product client that made the query in the first place.

We'll make a query handler that serves mathematical constants. Have you ever been in a position where you needed, say, the value of the third Fla jolet number or perhaps Zeta(9)? No? Well, just pretend for now you did. What we'll do is develop a query handler for a product server that will serve values of various mathematical constants.

The approach we'll take has three simple steps:

  1. Get some handy constants.
  2. Define the query expression.
  3. Write a query handler. The query handler will:
    1. Examine the query expression to see if it's a request for a constant, and if so, what constant is requested.
    2. Examine the query's list of acceptable MIME types.
    3. If both check out, look up the desired constant's value.
    4. If found, add it as a Result in the XMLQuery .

Writing the Code

In this section, we'll build up the query handler source code in pieces, examining each piece thoroughly. We'll then present the entire source file.

Gathering Handy Constants

The wonderful world of science and mathematics is replete with useful constant values. For this example, let's just pick three:

  • pi = 3.14159265...
  • e = 2.7182818285...
  • gamma = 0.577215664...

In Java code, we can set up those values as a Map in a static field. Thus we start forming our source file, ConstantHandler.java :

import java.util.HashMap;
import java.util.Map;
import jpl.eda.product.QueryHandler;
public class ConstantHandler
  implements QueryHandler {
  private static final Map CONSTANTS = new HashMap();
  static {
    CONSTANTS.put("pi",    "3.14159265...");
    CONSTANTS.put("e",     "2.7182818285...");
    CONSTANTS.put("gamma", "0.577215664...");
  }
}

As you can see, we're storing both the constant name and its value as java.lang.String objects.

Defining the Query Expression

Recall that the XMLQuery class can use parsed queries (where it generates postfix boolean stacks) or unparsed ones. While unparsed ones are easier, we'll go with parsed ones to demonstrate how on the server-side you deal with those postfix stacks.

Using the XMLQuery's expression language, we'll look for queries of the form:

constant = name

where name is the name of a constant. That will form a postfix "where" stack with exactly three QueryElement objects on it:

  1. The first (top) QueryElement will have role = elemName and value = constant .
  2. The second (middle) QueryElement will have role = LITERAL and a value equal to the constant name .
  3. The third (bottom) QueryElement will have role = RELOP and value = EQ .

If we get any other kind of stack, we'll reject it and return no matching results. That's reasonable behavior; after all, a query for donutsEaten > 5 AND RETURN = episodeNumber may be handled by a SimpsonsEpisodeQueryHandler that's also installed in the same product server.

We'll define a utility method, getConstantName , that will take the XMLQuery , check for the postfix "where" stack as described, and return the matching constant name . If it gets a stack whose structure doesn't match, it will return null . We'll add this method to our ConstantHandler.java file:

import java.util.List;
import jpl.eda.xmlquery.XMLQuery;
import jpl.eda.xmlquery.QueryElement;
...
public class ConstantHandler
  implements QueryHandler {
  ...
  private static String getConstantName(XMLQuery q) {
    List stack = q.getWhereElementSet();
    if (stack.size() != 3) return null;
    QueryElement e = (QueryElement) stack.get(0);
    if (!"elemName".equals(e.getRole())
      || !"constant".equals(e.getValue()))
      return null;
    e = (QueryElement) stack.get(2);
    if (!"RELOP".equals(e.getRole())
      || !"EQ".equals(e.getValue()))
      return null;
    e = (QueryElement) stack.get(1);
    if (!"LITERAL".equals(e.getRole()))
	return null;
    return e.getValue();
  }
}

Here, we first check to make sure there's exactly three elements, returning null if not. There's no need to go further.

Assuming there's three elements, the code then checks the topmost element. For an expression constant = name , the topmost element will have role elemName and value constant . If neither condition is true, we return null right away. No need to check further.

If the topmost element checks out, we then check the bottommost element. For constant = name , the bottom element is generated from the equals sign. It will have role RELOP (relational operator) and value EQ , meaning "equals".

If it checks out, all we have to do is check the middle element. The infix expression constant = name generates a postfix middle element of name as the value, with a role of LITERAL . We make sure it's LITERAL . If not, we're done; it's not a valid expression for our query handler.

But if so, then the value of that query element is the name of the desired constant. So we return it, regardless of what it is.

Checking for Acceptable MIME Types

Since all of our mathematical constants are strings, we'll say that the result MIME type of our products is text/plain . That means that any incoming XMLQuery must include any of the following MIME types:

  1. text/plain
  2. text/*
  3. */*

All of these match text/plain , which is the only product type we're capable of serving. (In your own product servers, you might have more complex logic; for example, you could write code to draw the numbers into an image file if the requested type is image/jpeg ... but I wouldn't want to.)

To support this in our query handler, we'll write another utility method. It'll be called isAcceptableType , and it will take the XMLQuery and examine it to see what MIME types are acceptable to the caller. If it finds any of the ones in the above list, it will return true , and the caller can continue to process the query. If not, it will return false , and the query handler will stop processing and return the XMLQuery unadorned with any results.

Here's the code:

import java.util.Iterator;
...
public class ConstantHandler
  implements QueryHandler {
  ...
  private static boolean isAcceptableType(XMLQuery q) {
    List mimes = q.getMimeAccept();
    if (mimes.isEmpty()) return true;
    for (Iterator i = mimes.iterator(); i.hasNext();) {
      String type = (String) i.next();
      if ("text/plain".equals(type)
        || "text/*".equals(type)
        || "*/*".equals(type)) return true;
    }
    return false;
  }
}

Here, we check if the list of acceptable MIME types is empty. An empty list is the same as saying */* , so that automatically says we've got an acceptable type. For a non-empty list, we go t hrough each type one-by-one. If it's any of the strings text/plain , text/* , or */* , then that's an acceptable type.

However, if we get through the entire list and we don't find any type that the user wants that we can provide, we return false . The query handler will check for a false value and return early from handling the query, leaving the XMLQuery untouched.

Inserting the Result

Assuming the query handler has found an acceptable MIME type, and has found a valid query and the name of the desired constant, it can lookup the constant in the CONSTANTS map. And assuming it finds a matching constant in that map, it can insert the value as a Result object.

To insert the constant's value, we'll develop yet another utility method, this time called insert . This method will take the name of the constant, its value, and the XMLQuery . It will add a Result object to the XMLQuery . When the query handler returns this modified XMLQuery object, the framework will return it to the product client, which can then display the matching result.

Result objects can also have optional Header objects that serve as "column headings" for tabular like results. Our result isn't tabular, it's just a single value, but we'll add a heading anyway just to demonstrate how it's done. (You could argue that it's a one-by-one table, too!) The header's name will be the same as the constant's name; the data type will be real and the units will be none .

Here's the code:

import java.util.Collections;
import jpl.eda.xmlquery.Header;
import jpl.eda.xmlquery.Result;
...
public class ConstantHandler
  implements QueryHandler {
  ...
  private static void insert(String name,
    String value, XMLQuery q) {
    Header h = new Header(name, "real", "none");
    Result r = new Result(name, "text/plain",
      /*profileID*/null, /*resourceID*/null,
      Collections.singletonList(h),
      value, /*classified*/false, Result.INFINITE);
    q.getResults().add(r);
  }
}

In this method, we first create the header. Then we create the result; the result's ID (which differentiates it from other results in the same XMLQuery is just the name of the constant. Its MIME type is text/plain . We set the profile ID and resource ID fields to null , as recommended back in the XMLQuery Tutorial . Then we add our sole header. Then we add the mathematical constant's value. Finally, this constant isn't classified, so we set the classified flag to false . Also, these mathematical constants should be valid forever, so we set the validity period to Result.INFINITE , a special value that means a never-ending validity period.

Handling the Query

With all of these utility methods in hand, it's easy to handle the query now. The jpl.eda.product.QueryHandler interface specifies a single method that we must implement, query . This method accepts an XMLQuery object and returns an XMLQuery object. The returned one may or may not be adorned with matching results.

Here's what we have to do:

  1. Get the constant name with getConstantName . If we get null , it means the query's not of the form constant = name , so we ignore it.
  2. See if the user's willing to accept a text/plain MIME type. If not, we ignore this query.
  3. Find the constant in our CONSTANTS map. If it's not there, we ignore this query.
  4. Insert the constant's value into the XMLQuery .
  5. Returned the modified XMLQuery .

The source:

public class ConstantHandler
  implements QueryHandler {
  ...
  public XMLQuery query(XMLQuery q) {
    String name = getConstantName(q);
    if (name == null) return q;
    if (!isAcceptableType(q)) return q;
    String value = (String) CONSTANTS.get(name);
    if (value == null) return q;
    insert(name, value, q);
    return q;
  }
}

Complete Source Code

Here is the complete source file, ConstantHandler.java :

import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import jpl.eda.product.QueryHandler;
import jpl.eda.xmlquery.Header;
import jpl.eda.xmlquery.Result;
import jpl.eda.xmlquery.QueryElement;
import jpl.eda.xmlquery.XMLQuery;

public class ConstantHandler
  implements QueryHandler {
  private static final Map CONSTANTS = new HashMap();
  static {
    CONSTANTS.put("pi",    "3.14159265...");
    CONSTANTS.put("e",     "2.7182818285...");
    CONSTANTS.put("gamma", "0.577215664...");
  }
  private static String getConstantName(XMLQuery q) {
    List stack = q.getWhereElementSet();
    if (stack.size() != 3) return null;
    QueryElement e = (QueryElement) stack.get(0);
    if (!"elemName".equals(e.getRole())
      || !"constant".equals(e.getValue()))
      return null;
    e = (QueryElement) stack.get(2);
    if (!"RELOP".equals(e.getRole())
      || !"EQ".equals(e.getValue()))
      return null;
    e = (QueryElement) stack.get(1);
    if (!"LITERAL".equals(e.getRole()))
	return null;
    return e.getValue();
  }
  private static boolean isAcceptableType(XMLQuery q) {
    List mimes = q.getMimeAccept();
    if (mimes.isEmpty()) return true;
    for (Iterator i = mimes.iterator(); i.hasNext();) {
      String type = (String) i.next();
      if ("text/plain".equals(type)
        || "text/*".equals(type)
        || "*/*".equals(type)) return true;
    }
    return false;
  }
  private static void insert(String name,
    String value, XMLQuery q) {
    Header h = new Header(name, "real", "none");
    Result r = new Result(name, "text/plain",
      /*profileID*/null, /*resourceID*/null,
      Collections.singletonList(h),
      value, /*classified*/false, Result.INFINITE);
    q.getResults().add(r);
  }
  public XMLQuery query(XMLQuery q) {
    String name = getConstantName(q);
    if (name == null) return q;
    if (!isAcceptableType(q)) return q;
    String value = (String) CONSTANTS.get(name);
    if (value == null) return q;
    insert(name, value, q);
    return q;
  }
}

How should you go about compiling this and installing it in a product server? Read on!

Compiling the Code

We'll compile this code using the J2SDK command-line tools, but if you're more comfortable with some kind of Integrated Development Environment (IDE), adjust as necessary.

First, let's go back to the $PS_HOME directory we made earlier and make directories to hold both the source code and classes that we'll compile from it:

% cd $PS_HOME
% mkdir classes src

Then, create $PS_HOME/src/ConstantHandler.java using your favorite text editor (or by cutting and pasting the source from this page, or whatever). Finally, compile the file as follows:

% javac -extdirs lib \
  -d classes src/ConstantHandler.java
% ls -l classes
total 4
-rw-r--r--  1 kelly  kelly  2524 25 Feb 15:46 ConstantHandler.class

The javac command is the Java compiler. The -extdirs lib arguments tell the compiler where to find extension jars. In this case, the code references things defined in edm-query-2.0.2.jar and grid-product-3.0.3.jar. The -d classes tells where compiled classes should go.

Next, make a jar file that contains your compiled class:

% jar -cf lib/my-handlers.jar \
  -C classes ConstantHandler.class
% jar -tf lib/my-handlers.jar
META-INF/
META-INF/MANIFEST.MF
ConstantHandler.class

We now have a new jar file of our own creation in the $PS_HOME/lib directory; this means that the product server will be able to find out new query handler. All we have to do now is tell our product server about it.

Specfying the Query Handler

Query handlers aren't really installed into product servers. What you do is tell the product server what query handlers you want it to use by naming their classes. The product server will instantiate an object of each class and, as queries come in, it will delegate queries to each instantiated query handler.

To tell a product server what query handlers to instantiate, you specify a system property called handlers . You set this property to a comma-separated list of class names. These should be fully-qualified class names (with package prefixes, if you used packages when making your query handlers), separated by commas. In this tutorial, we made just one query handler, and we didn't put it into a package, so we'll just use ConstantHandler .

First, stop any product server you have running now by pressing CTRL+C (or whatever your interrupt key is) in the window that was running the product server. Next, modify the $PS_HOME/bin/ps file so it reads as follows:

#!/bin/sh
exec java -Djava.ext.dirs=$PS_HOME/lib \
    -Dhandlers=ConstantHandler \
    jpl.eda.ExecServer \
    jpl.eda.product.rmi.ProductServiceImpl \
    urn:eda:rmi:MyProductService

We specified a system property on the command line using Java's -D option. This defines the system property handlers as having the value ConstantHandler . Finally, start the product server again by running $PS_HOME/bin/ps .

Querying for Constants

Once again, edit the $PS_HOME/bin/pc script and change -xml back to -out so that instead of the XML output we'll see the raw product data. Then run it and see what happens:

% $PS_HOME/bin/pc 'constant = pi'
3.14159265...% 

Because the raw product data was the string 3.14159265... without any trailing newline, the shell's prompt appeared right at the end of the product result. You might try piping the output of the above command through a pager like more or less to avoid this.

Here's what happened when we ran this command:

  1. The product client created an XMLQuery object out of the string query constant = pi .
  2. It asked the RMI Registry to tell it where (network address) it could find the product service named MyProductService .
  3. 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.
  4. The product service had only one query handler, the ConstantHandler , to which to delegate, so it passed the XMLQuery to it.
  5. The ConstantHandler 's query method was called. It checked if the query was the kind it wanted, extracted the desired mathematical constant's name, checked for an acceptable requested MIME type, looked up the constant's value, inserted it as a Result into the XMLQuery , and returned the modified query.
  6. The product service, seeing it had no other handlers to try, returned the modified XMLQuery to the product client over the network connection.
  7. The product client took the first Result out of the XMLQuery , called Result.getInputStream , and copied each byte of the result to the standard output. This wrote 3.14159265... to your window.

If you change the $PS_HOME/bin/pc script again so that instead of -out it's -xml , you'll again see the XMLQuery as an XML document. The interesting part is the < queryResultSet > :

<queryResultSet>
  <resultElement classified="false" validity="-1">
    <resultId>pi</resultId>
    <resultMimeType>text/plain</resultMimeType>
    <profId/>
    <identifier/>
    <resultHeader>
      <headerElement>
	<elemName>pi</elemName>
	<elemType>real</elemType>
	<elemUnit>none</elemUnit>
      </headerElement>
    </resultHeader>
    <resultValue xml:space="preserve">3.14159265...</resultValue>
  </resultElement>
</queryResultSet>

I'll let you figure out how this maps to the Result object we created in the code. OK, so is this really interesting? Not really, except to note that the actual result data, 3.1415265... , appears in the XML document. In the OODT framework, we call this a "small" result, because the product data was embedded in the XMLQuery object. Text data like text/plain products appear just as text. Binary d ata like image/jpeg and application/octet-stream get base-64 encoded into text.

As you can guess, there's a point at which encoded data goes from being nice and small and tidy to just too large to contain in a single object. In the OODT framework, we can use a special query handler for such large products, but that's another tutorial.

Conclusion

In this tutorial, we learned how to write a complete query handler for a product server, including handling of postfix boolean "where" stacks, lists of acceptable MIME types, and result headers. We compiled the query handler, put it into a jar file, and specified it to our product server. And we could even query it and get data back.

Don't toss out this product server yet, though. Another tutorial will use it and cover the infamous LargeProductQueryHandler .

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: 13 June 2007

+ Contact NASA
spacer
spacer spacer spacer
spacer spacer spacer