<?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>Serving Large Products</title>
    <author email="Sean.Kelly@jpl.nasa.gov">Sean Kelly</author>
  </properties>

  <body>
    <section name="Serving Large Products">
      <p>In the <a href="../qh/">last tutorial</a>, we created a query
	handler and "installed" it in a product server.  We could
	query it for products (mathematical constants) using the
	XMLQuery's postfix boolean stacks.  The handler would return
	results by embedding them in the returned XMLQuery.  Now we'll
	return larger products that live outside of the XMLQuery.
      </p>
    </section>

    <section name="What's Large?">
      <p>There's a <a
	  href="http://www.worldslargestthings.com/california/clam.htm">giant
	  clam</a> at Pismo Beach, a <a
	  href="http://www.worldslargestthings.com/kansas/cawkercity.htm">giant
	  ball of twine</a> in Kansas, and for those who drive SUVs, a
	  <a
	  href="http://www.worldslargestthings.com/missouri/gaspump.htm">giant
	  gas pump</a>.  For the OODT framework, large is similarly hard to define.
      </p>

      <p>One of the original architects of the OODT framework thought
	that putting a products result in with the query meant that
	you'd never lose the separation between product and the query
	that generated it.  I'm not sure I see the value in that, but
	regardless, it posed a practical challenge: an
	<code>XMLQuery</code> object in memory with one or two large
	results in it will exhaust the Java virtual machine's available
	memory.
      </p>

      <p>It's even worse in when the XMLQuery is expressed as a
	textual XML document.  In this case, a binary product must be
	encoded in a text format (we use <a
	href="ftp://ftp.rfc-editor.org/in-notes/rfc2045.txt">Base64</a>),
	making the XMLQuery in XML format even larger than as a Java
	object.  Moreover, those XML documents must be parsed at some
	time to reconstitute them as Java objects.  We use a DOM-based
	parser, which holds the entire document in memory.  Naturally,
	things tend to explode at this rate.
      </p>

      <p>There is a way out of the quagmire, though.  Instead of
	writing a <code>QueryHandler</code>, write a
	<code>LargeProductQueryHandler</code>.  A
	<code>QueryHandler</code> puts <code>Result</code> objects
	into the <code>XMLQuery</code> which hold the entire product.
	A <code>LargeProductQueryHandler</code> puts
	<code>LargeResult</code> objects which hold <em>a reference to
	the product</em>.
      </p>
    </section>

    <section name="Large Handlers and Large Results">
      <p>The OODT framework provides an extension to the
	<code>QueryHandler</code> interface called
	<code>jpl.eda.product.LargeProductQueryHandler</code>.  This
	interface adds two methods that you must implement:
      </p>

      <ul>
	<li><code>retrieveChunk</code>.  This method returns a byte
	  array representing a chunk of the product.  The OODT
	  framework calls this method repeatedly to gather chunks of
	  the product for the product client.  It takes a <em>product
	  ID</em> (a string) that identifies which product is being
	  retrieved.  It also takes an byte offset into the product
	  data and a size of the byte chunk to return.  You return the
	  matching chunk.
	</li>

	<li><code>close</code>.  This method is called by the OODT
	  framework to tell the query handler it's done getting a
	  product.  It takes a <em>product ID</em> that tells which
	  product is no longer being retrieved.  You use this method
	  to perform any cleanup necessary.
	</li>
      </ul>
	  
      <p>Because it extends the <code>QueryHandler</code> interface,
	you still have to implement the <code>query</code> method.
	However, as a <code>LargeProductQueryHandler</code>, you can
	add <code>LargeResult</code> objects to the
	<code>XMLQuery</code> passed in.  <code>LargeResult</code>s
	identify the <em>product ID</em> (string) that the OODT
	framework will later use when it calls
	<code>retrieveChunk</code> and <code>close</code>.
      </p>

      <p>For example, suppose you're serving large images by
	generating them from various other data sources:
      </p>

      <ol>
	<li>The <code>query</code> method would examine the user's
	  query, consult the various data sources, and generate the
	  image, storing it in a temporary file.  It would also assign
	  a string <em>product ID</em> to this file, use that product
	  ID in a <code>LargeResult</code> object, add the
	  <code>LargeResult</code> to the <code>XMLQuery</code>, and
	  return the modified <code>XMLQuery</code>.
	</li>

	<li>Shortly afterward, the OODT framework will repeatedly call
	  the <code>retrieveChunk</code> method.  This method would
	  check the <em>product ID</em> passed in and locate the
	  corresponding temporary file generated earlier by the
	  <code>query</code> method.  It would index into the file by
	  the offset requested by the framework, read the number of
	  bytes requested by the framework, package that up into a
	  byte array, and return it.  Eventually, the OODT framework
	  will have read the entire product this way.
	</li>

	<li>Lastly, the OODT framework will call the
	  <code>close</code> method.  This method would check the
	  <em>product ID</em> and locate and delete the temporary
	  file.
	</li>
      </ol>

      <p>To put this into practice, let's create a
	<code>LargeProductQueryHandler</code> that serves files out of
	the product server's filesystem.
      </p>
    </section>

    <section name="Writing the Handler">
      <p>We'll develop a <code>FileHandler</code> that will serve
	files out of the product server's filesystem.  Providing
	filesystem access through the OODT framework in this way is
	probably not a very good idea (after all, product clients
	could request copies of sensitive files), but for a
	demonstration it'll do.
      </p>

      <p>Because files can be quite large, we'll use a
	<code>LargeProductQueryHandler</code>.  It will serve queries
	of the form
      </p>

      <p><code>file = <var>path</var></code></p>

      <p>where <var>path</var> is the full path of the file the user
	wants.  The handler will add <code>LargeResult</code>s to the
	XMLQuery, and the <em>product ID</em> will just simply be the
	<var>path</var> of the requested file.  The
	<code>retrieveChunk</code> method will open the file with the
	given product ID (which is just the path to the file) and
	return a block of data out of it.  The <code>close</code>
	method won't need to do anything, since we're not creating
	temporary files or making network conncetions or anything;
	there's just nothing to clean up.
      </p>

      <subsection name="Getting the Path">
	<p>First, let's create a utility method that takes the
	  <code>XMLQuery</code> and returns a <code>java.io.File</code>
	  that matches the requested file.  Because the query takes the form
	</p>

	<p><code>file = <var>path</var></code></p>

	<p>there should be three <code>QueryElement</code>s on the "where" stack:</p>

	<ol>
	  <li>The zeroth (topmost) has role = <code>elemName</code>
	    and value = <code>file</code>.
	  </li>
	  <li>The first (middle) has role = <code>LITERAL</code> and
	    value = the <var>path</var> of the file the user wants.
	  </li>
	  <li>The last (bottom) has role = <code>RELOP</code> and
	    value = <code>EQ</code>.
	  </li>
	</ol>

	<p>We'll reject any other query by returning <code>null</code>
	  from this method.  Further, if the file named by the
	  <var>path</var> doesn't exist, or if it's not a file (for
	  example, it's a directory or a socket), we'll return <code>null</code>.
	</p>

	<p>Here's the start of our <code>FileHandler.java</code>:</p>

	<source>import java.io.File;
import java.util.List;
import jpl.eda.product.LargeProductQueryHandler;
import jpl.eda.xmlquery.QueryElement;
import jpl.eda.xmlquery.XMLQuery;
public class FileHandler
  implements LargeProductQueryHandler {
  private static File getFile(XMLQuery q) {
    List stack = q.getWhereElementSet();
    if (stack.size() != 3) return null;
    QueryElement e = (QueryElement) stack.get(0);
    if (!"elemName".equals(e.getRole())
      || !"file".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;
    File file = new File(e.getValue());
    if (!file.isFile()) return null;
    return file;
  }
}</source>
      </subsection>
      <subsection name="Checking the MIME Type">
	<p>Recall that the user can say what MIME types of products
	  are acceptable by specifying the preference list in the
	  XMLQuery.  This lets a product server that serves, say,
	  video clips, convert them to <code>video/mpeg</code>
	  (MPEG-2), <code>video/mpeg4-generic</code> (MPEG-4),
	  <code>video/quicktime</code> (Apple Quicktime), or some
	  other format, in order to better serve its clients.
	</p>

	<p>Since our product server just serves <em>files of any
	    format</em>, we won't really bother with the list of
	    acceptable MIME types.  After all, the
	    <code>/etc/passwd</code> file <em>could</em> be a JPEG
	    image on some systems.  (Yes, we could go through the
	    extra step of determining the MIME type of a file by
	    looking at its extension or its contents, but this is an
	    OODT tutorial, not a something-else-tutorial!)
	</p>

	<p>However, we will honor the user's wishes by labeling the
	  result's MIME type based on what the user specifies in the
	  acceptable MIME type list.  So, if the product client says
	  that <code>image/jpeg</code> is acceptable and the file is
	  <code>/etc/passwd</code>, we'll call
	  <code>/etc/passwd</code> a JPEG image.  However, we won't
	  try to read the client's mind: if the user wants
	  <code>image/*</code>, then we'll just say it's a binary
	  file, <code>application/octet-stream</code>.
	</p>

	<p>Here's the code:</p>

	<source>import java.util.Iterator;
...
public class FileHandler
  implements LargeProductQueryHandler {
  ...
  private static String getMimeType(XMLQuery q) {
    for (Iterator i = q.getMimeAccept().iterator();
      i.hasNext();) {
      String t = (String) i.next();
      if (t.indexOf('*') == -1) return t;
    }
    return "application/octet-stream";
  }
}</source>
      </subsection>

      <subsection name="Inserting the Result">
	<p>Once we've got the file that the user wants and the MIME
	  type to call it, all we have to do is insert the
	  <code>LargeResult</code>.  Remember that it's the
	  <code>LargeResult</code> that tells the OODT framework what
	  the <em>product ID</em> is for later
	  <code>retrieveChunk</code> and <code>close</code> calls.
	  The <em>product ID</em> is passed as the first argument to
	  the <code>LargeResult</code> constructor.
	</p>

	<p>We'll write a utility method to insert the <code>LargeResult</code>:</p>

	<source>import java.io.IOException;
import java.util.Collections;
import jpl.eda.xmlquery.LargeResult;
...
public class FileHandler
  implements LargeProductQueryHandler {
  ...
  private static void insert(File file, String type,
    XMLQuery q) throws IOException {
    String id = file.getCanonicalPath();
    long size = file.length();
    LargeResult lr = new LargeResult(id, type,
      /*profileID*/null, /*resourceID*/null,
      /*headers*/Collections.EMPTY_LIST, size);
    q.getResults().add(lr);
  }
}</source>

      </subsection>

      <subsection name='Handling the Query'>
	<p>With our three utility methods in hand, writing the
	  required <code>query</code> method is a piece of cake.  Here
	  it is:
	</p>

	<source>import jpl.eda.product.ProductException;
...
public class FileHandler
  implements LargeProductQueryHandler {
  ...
  public XMLQuery query(XMLQuery q)
    throws ProductException {
    try {
      File file = getFile(q);
      if (file == null) return q;
      String type = getMimeType(q);
      insert(file, type, q);
      return q;
    } catch (IOException ex) {
      throw new ProductException(ex);
    }
  }
}</source>

	<p>The <code>query</code> method as defined by the
	  <code>QueryHandler</code> interface (and extended into the
	  <code>LargeProductQueryHandler</code> interface) is allowed
	  to throw only one kind of checked exception:
	  <code>ProductException</code>.  So, in case the
	  <code>insert</code> method throws an
	  <code>IOException</code>, we transform it into a
	  <code>ProductException</code>.
	</p>

	<p>Now there are just two more required methods to implement,
	  <code>retrieveChunk</code> and <code>close</code>.
	</p>
      </subsection>

      <subsection name='Blowing Chunks'>
	<p>The OODT framework repeatedly calls handler's
	  <code>retrieveChunk</code> method to get chunks of the
	  product, evenutally getting the entire product (unless the
	  product client decides to abort the transfer).  For our file
	  handler, retrieve chunk just has to
	</p>
	<ol>
	  <li>Make sure the file specified by the <em>product ID</em>
	    still exists (after all, it could be deleted at any time,
	    even before the first <code>retrieveChunk</code> got
	    called).
	  </li>
	  <li>Open the file.</li>
	  <li>Skip into the file by the requested offset.</li>
	  <li>Read the requested number of bytes out of the file.</li>
	  <li>Return those bytes.</li>
	  <li>Close the file.</li>
	</ol>

	<p>We'll write a quick little <code>skip</code> method to skip
	  into a file's input stream:
	</p>

	<source>private static void skip(long offset,
  InputStream in) throws IOException {
  while (offset > 0)
    offset -= in.skip(offset);
}</source>

	<p>And here's another little utility method to read a
	  specified number of bytes out of a file's input stream:
	</p>

	<source>private static byte[] read(int length,
  InputStream in) throws IOException {
  byte[] buf = new byte[length];
  int numRead;
  int index = 0;
  int toRead = length;
  while (toRead > 0) {
    numRead = in.read(buf, index, toRead);
    index += numRead;
    toRead -= numRead;
  }
  return buf;
}</source>

	<p>(By now, you're probably wondering why we just didn't use
	  <code>java.io.RandomAccessFile</code>; I'm wondering that
	  too!)</p>

	<p>Finally, we can implement the required
	  <code>retrieveChunk</code> method:
	</p>

	<source>import java.io.BufferedInputStream;
import java.io.FileInputStream;
...
public class FileHandler
  implements LargeProductQueryHandler {
  ...
  public byte[] retrieveChunk(String id, long offset,
    int length) throws ProductException {
    BufferedInputStream in = null;
    try {
      File f = new File(id);
      if (!f.isFile()) throw new ProductException(id
        + " isn't a file (anymore?)");
      in = new BufferedInputStream(new FileInputStream(f));
      skip(offset, in);
      byte[] buf = read(length, in);
      return buf;
    } catch (IOException ex) {
      throw new ProductException(ex);
    } finally {
      if (in != null) try {
        in.close();
      } catch (IOException ignore) {}
    }
  }
}</source>

      </subsection>

      <subsection name='Closing Up'>
	<p>Because the OODT framework has no idea what data sources a
	  <code>LargeProductQueryHandler</code> will eventually
	  consult, what temporary files it may need to clean up, what
	  network sockets it might need to shut down, and so forth, it
	  needs some way to indicate to a query handler that's it's
	  done calling <code>retrieveChunk</code> for a certain
	  <em>product ID</em>.  The <code>close</code> method does this.
	</p>

	<p>In our example, <code>close</code> doesn't need to do
	  anything, but we are obligated to implement it:
	</p>

	<source>...
public class FileHandler
  implements LargeProductQueryHandler {
  ...
  public void close(String id) {}
}</source>
      </subsection>

      <subsection name='Complete Source Code'>
	<p>Here's the complete source file, <code>FileHandler.java</code>:</p>
	<source>import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import jpl.eda.product.LargeProductQueryHandler;
import jpl.eda.product.ProductException;
import jpl.eda.xmlquery.LargeResult;
import jpl.eda.xmlquery.QueryElement;
import jpl.eda.xmlquery.XMLQuery;

public class FileHandler
  implements LargeProductQueryHandler {
  private static File getFile(XMLQuery q) {
    List stack = q.getWhereElementSet();
    if (stack.size() != 3) return null;
    QueryElement e = (QueryElement) stack.get(0);
    if (!"elemName".equals(e.getRole())
      || !"file".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;
    File file = new File(e.getValue());
    if (!file.isFile()) return null;
    return file;
  }
  private static String getMimeType(XMLQuery q) {
    for (Iterator i = q.getMimeAccept().iterator();
      i.hasNext();) {
      String t = (String) i.next();
      if (t.indexOf('*') == -1) return t;
    }
    return "application/octet-stream";
  }
  private static void insert(File file, String type,
    XMLQuery q) throws IOException {
    String id = file.getCanonicalPath();
    long size = file.length();
    LargeResult lr = new LargeResult(id, type,
      /*profileID*/null, /*resourceID*/null,
      /*headers*/Collections.EMPTY_LIST, size);
    q.getResults().add(lr);
  }
  public XMLQuery query(XMLQuery q)
    throws ProductException {
    try {
      File file = getFile(q);
      if (file == null) return q;
      String type = getMimeType(q);
      insert(file, type, q);
      return q;
    } catch (IOException ex) {
      throw new ProductException(ex);
    }
  }
  private static void skip(long offset,
    InputStream in) throws IOException {
    while (offset > 0)
      offset -= in.skip(offset);
  }
  private static byte[] read(int length,
    InputStream in) throws IOException {
    byte[] buf = new byte[length];
    int numRead;
    int index = 0;
    int toRead = length;
    while (toRead > 0) {
      numRead = in.read(buf, index, toRead);
      index += numRead;
      toRead -= numRead;
    }
    return buf;
  }
  public byte[] retrieveChunk(String id, long offset,
    int length) throws ProductException {
    BufferedInputStream in = null;
    try {
      File f = new File(id);
      if (!f.isFile()) throw new ProductException(id
        + " isn't a file (anymore?)");
      in = new BufferedInputStream(new FileInputStream(f));
      skip(offset, in);
      byte[] buf = read(length, in);
      return buf;
    } catch (IOException ex) {
      throw new ProductException(ex);
    } finally {
      if (in != null) try {
        in.close();
      } catch (IOException ignore) {}
    }
  }
  public void close(String id) {}
}</source>
      </subsection>
    </section>

    <section name='Compiling the Code'>
      <p>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.
      </p>

      <p>Let's go back again to the <code>$PS_HOME</code> directory we
	made earlier; create the file
	<code>$PS_HOME/src/FileHandler.java</code> with the contents
	shown above.  Then, compile and update the jar file as follows:
      </p>

      <source>% <b>javac -extdirs lib \
  -d classes src/FileHandler.java</b>
% <b>ls -l classes</b>
total 8
-rw-r--r--  1 kelly  kelly  2524 25 Feb 15:46 ConstantHandler.class
-rw-r--r--  1 kelly  kelly  3163 26 Feb 16:15 FileHandler.class
% <b>jar -uf lib/my-handlers.jar \
  -C classes FileHandler.class</b>
% <b>jar -tf lib/my-handlers.jar</b>
META-INF/
META-INF/MANIFEST.MF
ConstantHandler.class
FileHandler.class</source>

      <p>We've now got a jar with the <code>ConstantHandler</code>
	from the <a href="../qh/">last tutorial</a> and our new
	<code>FileHandler</code>.
      </p>
    </section>

    <section name='Specifying and Running the New Query Handler'>
      <p>The <code>$PS_HOME/bin/ps</code> script already has a system
	property specifying the <code>ConstantHandler</code>, so we
	just need to add the <code>FileHandler</code> to that list.
      </p>

      <p>First, stop the product server by hitting CTRL+C (or your
	interrupt key) in the window in which it's currently running.
	Then, modify the <code>$PS_HOME/bin/ps</code> script to read
	as follows:
      </p>

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

      <p>Then start the server by running
	<code>$PS_HOME/bin/ps</code>.  If all goes well, the product
	server will be ready to answer queries again, this time
	passing each incoming <code>XMLQuery</code> to <em>two</em>
	different query handlers.
      </p>

      <p>Edit the <code>$PS_HOME/bin/pc</code> script once more to
	make sure the <code>-out</code> and not the <code>-xml</code>
	command-line argument is being used.  Let's try querying for a
	file:
      </p>

      <source>% <b>$PS_HOME/bin/pc "file = /etc/passwd"</b>
nobody:*:-2:-2:Unprivileged User:/:/usr/bin/false
root:*:0:0:System Administrator:/var/root:/bin/sh
daemon:*:1:1:System Services:/var/root:/usr/bin/false
...</source>

      <p>If you like, you can change the <code>-out</code> to
	<code>-xml</code> again and examine the XML version.  This
	time, the product data isn't in the XMLQuery object.
      </p>
    </section>

    <section name="What's the Difference?">
      <p>On the client side, the interface to get product results in
	<code>LargeResult</code>s versus regular <code>Result</code>s
	is identical.  The client calls <code>getInputStream</code> to
	get a binary stream to read the product data.
      </p>

      <p>There is a speed penalty for large results.  What
	<code>Result.getInputStream</code> returns is an input stream
	to product data already contained in the XMLQuery.  It's a
	stream to a buffer already in the client's address space, so
	it's nice and fast.
      </p>

      <p><code>LargeResult</code> overrides the
	<code>getInputStream</code> method to instead return an input
	stream that repeatedly makes calls back to the product
	server's <code>retrieveChunk</code> method.  Since the product
	is <em>not</em> already in the local address space of the
	client, getting large products is a bit slower.  To
	compensate, the input stream actually starts a background
	thread to start retrieving chunks of the product ahead of the
	product client, up to a certain point (we don't want to run
	out of memory again).
      </p>

      <p>On the server side, the difference is in programming
	complexity.  Creating a <code>LargeProductQueryHandler</code>
	requires implementing three methods instead of just one.  You
	may have to clean up temporary files, close network ports, or
	do other cleanup.  You may even have to guard against clients
	that present specially-crafted product IDs that try to
	circumvent access controls to products.
      </p>

      <p><code>LargeResult</code>s are more general, and will work for
	any size product, from zero bytes on up.  And you can even mix
	and match: a <code>LargeProductQueryHandler</code> can add
	regular <code>Result</code>s to an XMLQuery as well as
	<code>LargeResult</code>s.  You might program some logic that,
	under a certain threshold, to return regular
	<code>Result</code>s for small sized products, and
	<code>LargeResult</code>s for anything bigger than small.
      </p>
    </section>

    <section name='Conclusion'>
      <p>In this tutorial, we implemented a
	<code>LargeProductQueryHandler</code> that served large
	products.  In this case, large could mean zero bytes (empty
	products) up to gargantuan numbers of bytes.  This handler
	queried for files in the product server's filesystem, which is
	a bit insecure so you might want to terminate the product
	server as soon as possible.  We also learned that what the
	advantages and disadvantages were between regular product
	results and large product results, and that
	<code>LargeProductQueryHandler</code>s can use
	<code>LargeResult</code> objects in addition to regular
	<code>Result</code> objects.
      </p>

      <p>If you've also completed the <a href="../ps">Your First
	  Product Service</a> tutorial and the <a
	  href="../qh/">Developing a Query Handler</a> tutorial, you
	are now a master of the OODT Product Service.
	Congratulations!
      </p>
    </section>
  </body>
</document>
