[Mulgara-svn] r1194 - in trunk/src/jar: . webquery webquery/java webquery/java/org webquery/java/org/mulgara webquery/java/org/mulgara/webquery

pag at mulgara.org pag at mulgara.org
Thu Aug 28 23:36:04 UTC 2008


Author: pag
Date: 2008-08-28 16:36:04 -0700 (Thu, 28 Aug 2008)
New Revision: 1194

Added:
   trunk/src/jar/webquery/
   trunk/src/jar/webquery/java/
   trunk/src/jar/webquery/java/org/
   trunk/src/jar/webquery/java/org/mulgara/
   trunk/src/jar/webquery/java/org/mulgara/webquery/
   trunk/src/jar/webquery/java/org/mulgara/webquery/QueryResponsePage.java
Log:
trying to fix svn

Added: trunk/src/jar/webquery/java/org/mulgara/webquery/QueryResponsePage.java
===================================================================
--- trunk/src/jar/webquery/java/org/mulgara/webquery/QueryResponsePage.java	                        (rev 0)
+++ trunk/src/jar/webquery/java/org/mulgara/webquery/QueryResponsePage.java	2008-08-28 23:36:04 UTC (rev 1194)
@@ -0,0 +1,483 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.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.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.webquery;
+
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.jrdf.graph.BlankNode;
+import org.jrdf.graph.Literal;
+import org.jrdf.graph.Node;
+import org.jrdf.graph.URIReference;
+import org.mulgara.query.Answer;
+import org.mulgara.query.TuplesException;
+import org.mulgara.query.Variable;
+import org.mulgara.query.operation.Command;
+import org.mulgara.util.functional.Pair;
+import org.mulgara.webquery.html.Anchor;
+import org.mulgara.webquery.html.HtmlElement;
+import org.mulgara.webquery.html.Span;
+import org.mulgara.webquery.html.Strong;
+import org.mulgara.webquery.html.Table;
+import org.mulgara.webquery.html.TableData;
+import org.mulgara.webquery.html.TableHeader;
+import org.mulgara.webquery.html.TableRow;
+import org.mulgara.webquery.html.Text;
+import org.mulgara.webquery.html.HtmlElement.Attr;
+import org.mulgara.webquery.html.HtmlElement.Entity;
+
+import static org.mulgara.webquery.Template.*;
+
+/**
+ * Constructs and emits the response page for a set of queries.
+ *
+ * @created Aug 4, 2008
+ * @author Paul Gearon
+ * @copyright &copy; 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class QueryResponsePage {
+
+  /** The name of the system property that can override the page size. */
+  public static final String PAGE_SIZE_PROP = "webui.page.size";
+
+  /** The default number of results per page or per cell. */
+  public static final int DEFAULT_RESULTS_PER_PAGE = 250;
+
+  /** The number of results per page or per cell. */
+  static int resultsPerPage;
+
+    /** The request that asked for this page. */
+  HttpServletRequest request;
+
+  /** The structure for sending the page back to the client. */
+  HttpServletResponse response;
+
+  /** A map of tags to the values that should replace them. */
+  Map<String,String> tagMap;
+
+  /** The object for accepting the output stream. */
+  PrintWriter out = null;
+
+  /** The set of unfinished results to render, mapped to the remaining sizes. */
+  Map<Answer,Pair<Long,Command>> unfinishedResults;
+
+
+  static {
+    try {
+      resultsPerPage = Integer.parseInt(System.getProperty(PAGE_SIZE_PROP));
+    } catch (Exception e) {
+      resultsPerPage = DEFAULT_RESULTS_PER_PAGE;
+    }
+  }
+
+
+  /**
+   * Construct this page for responsing to a particular request environment.
+   * @param reqThe request that asked for this page.
+   * @param resp The structure for sending the page back to the client.
+   * @param tagMap A map of tags to the values that should replace them
+   */
+  @SuppressWarnings("unchecked")
+  public QueryResponsePage(HttpServletRequest req, HttpServletResponse resp, Map<String,String> tagMap) {
+    this.request = req;
+    this.response = resp;
+    this.tagMap = tagMap;
+    this.unfinishedResults = (Map<Answer,Pair<Long,Command>>)req.getSession().getAttribute(UNFINISHED_RESULTS);
+  }
+
+  
+  /**
+   * Send a result page to the client without any stats. This starts with a form template, and then
+   * follows up with a single result.
+   * @param cmd The command that was run.
+   * @param result The result returned from running the command.
+   * @throws IOException There was an error writing to the client, or reading resources.
+   */
+  public void writeResult(Command cmd, Answer result) throws IOException {
+    List<Command> commands = Collections.singletonList(cmd);
+    List<Object> results = Collections.singletonList((Object)result);
+    writeResults(-1L, commands, results);
+  }
+
+
+  /**
+   * Send a result page to the client. This starts with a form template, and then
+   * follows up with the results.
+   * @param time The time take to execute the commands to get these results.
+   * @param cmds The commands that were run.
+   * @param results The result list returned from running each command.
+   * @throws IOException There was an error writing to the client, or reading resources.
+   */
+  public void writeResults(long time, List<Command> cmds, List<Object> results) throws IOException {
+    response.setContentType("text/html");
+    response.setHeader("pragma", "no-cache");
+    PrintWriter output = getOutput();
+    // write the head of the page
+    new ResourceTextFile(TEMPLATE_HEAD, tagMap).sendTo(output);
+
+    // summarise the results first
+    writeResultSummary(time, results.size());
+
+    // write the results
+    Iterator<Command> c = cmds.iterator();
+    Iterator<Object> r = results.iterator();
+    while (c.hasNext()) writeResult(c.next(), r.next());
+
+    // Check that we exhausted the results, like we were supposed to
+    if (r.hasNext()) response.sendError(SC_INTERNAL_SERVER_ERROR, "Internal error: results do not match queries.");
+
+    // write the tail of the page
+    new ResourceTextFile(TEMPLATE_TAIL).sendTo(output);
+
+    output.close();
+  }
+
+
+  /**
+   * Write a summary for the results of all the executed commands.
+   * @param out The stream to write the results to.
+   * @param time The time taken to execute the commands, in milliseconds.
+   * @param size The number of commands executed.
+   */
+  private void writeResultSummary(long time, int nrCommands) throws IOException {
+    // short circuit if we don't have a summary
+    if (time < 0) return;
+    HtmlElement row = new TableRow(ROW_INDENT);
+    row.add(new Span(CSS_LARGE, "Results:"));
+    Text t = new Text("(" + nrCommands);
+    t.append(nrCommands == 1 ? " query, " : " queries, ");
+    t.append(String.format("%.3f", (time / 1000.0))).append(" seconds)");
+    row.add(t);
+
+    emitTopLevelElement(row);
+  }
+
+
+  /**
+   * Write a single result into a row of an existing table.
+   * @param cmd The command executed to give this result.
+   * @param result The result for the command cmd.
+   * @throws IOException Occurs for an error writing the response.
+   */
+  private void writeResult(Command cmd, Object result) throws IOException {
+    if (result instanceof Answer) writeAnswerResult(cmd, (Answer)result);
+    else writeSimpleResult(cmd);
+  }
+
+
+  /**
+   * Write a simple result into a row of an existing table.
+   * @param cmd The command executed to give this result.
+   * @param result The result for the command cmd.
+   * @throws IOException Occurs for an error writing the response.
+   */
+  private void writeSimpleResult(Command cmd) throws IOException {
+    HtmlElement row = new TableRow(ROW_INDENT);
+    row.add(new Span(CSS_LARGE, "Query Executed:"));
+    row.add(new TableData(cmd.getText()).addAttr(Attr.VALIGN, "top").addAttr(Attr.WIDTH, "100%"));
+    
+    emitTopLevelElement(row);
+
+    row = new TableRow(ROW_INDENT);
+    row.add(new Span(CSS_LARGE, "Result Message:"));
+    row.add(new TableData(cmd.getResultMessage()).addAttr(Attr.VALIGN, "top").addAttr(Attr.WIDTH, "100%"));
+
+    emitTopLevelElement(row);
+  }
+
+
+  /**
+   * Write a complex result into a row of an existing table.
+   * @param cmd The command executed to give this result.
+   * @param result The result for the command cmd.
+   * @throws IOException Occurs for an error writing the response.
+   */
+  private void writeAnswerResult(Command cmd, Answer result) throws IOException {
+    // create a row with the query that was executed
+    TableRow row = new TableRow(ROW_INDENT);
+    row.add(new Span(CSS_LARGE, "Query Executed:"));
+    row.add(new TableData(cmd.getText()).addAttr(Attr.VALIGN, "top").addAttr(Attr.WIDTH, "100%"));
+
+    emitTopLevelElement(row);
+
+    // create the result table, and put it in a cell
+    TableData resultData = new TableData(createResultTable(result, cmd));
+    resultData.addAttr(Attr.COLSPAN, 2).addAttr(Attr.CLASS, CSS_RESULT_TABLE_CELL);
+    // create a new row to hold the result table
+    row = new TableRow(ROW_INDENT, resultData);
+
+    emitTopLevelElement(row);
+  }
+
+
+  /**
+   * Convert a result into an HTML table.
+   * @param result The result to convert.
+   * @return A new table containing all the requested results.
+   * @throws IOException If there was an error reading the results
+   */
+  private Table createResultTable(Answer result, Command cmd) throws IOException {
+    // create the table for the results
+    Table resultTable = new Table();
+    resultTable.addAttr(Attr.CLASS, CSS_RESULT_TABLE);
+
+    // Get the columns to be displayed
+    Variable[] vars = result.getVariables();
+
+    // add the headers for the table
+    TableRow headerRow = new TableRow();
+    for (Variable v: vars) headerRow.add(new TableHeader(new Strong(v.getName())));
+    resultTable.add(headerRow);
+
+    // add the result data
+    try {
+      int rowsLeft = resultsPerPage;
+      // if this is the first time we've seen this result, then go to the start
+      if (!isResuming(result)) result.beforeFirst();
+
+      while (result.next()) {
+        TableRow row = new TableRow();
+        for (int c = 0; c < vars.length; c++) row.add(createEmbeddableElement(result.getObject(c), cmd));
+        resultTable.add(row);
+        // exit early if this is too big
+        if (--rowsLeft == 0) {
+          addUnfinishedResult(result, resultsPerPage, cmd);
+          appendNextPageLink(result, resultTable);
+          return resultTable;
+        }
+      }
+      // got to the end of this result, so clean it up
+      resultFinished(result);
+    } catch (TuplesException e) {
+      throw new IOException("Error accessing the results of the query: " + e.getMessage());
+    }
+    return resultTable;
+  }
+
+
+  /**
+   * Converts an object returned from a query into an element in a table.
+   * If the object is a simple type, then it returns text linked to a query
+   * for that element. For Answer object, it returns a new table.
+   * @param obj The object to convert into a table element.
+   * @param cmd The command that gave rise to the object.
+   * @return A new table data element.
+   * @throws IOException If the data could not be read.
+   */
+  private TableData createEmbeddableElement(Object obj, Command cmd) throws IOException {
+    HtmlElement result = null;
+    if (obj instanceof Answer) result = createResultTable((Answer)obj, cmd);
+    if (obj instanceof BlankNode) result = new Text(obj.toString());
+    if (obj instanceof URIReference || obj instanceof Literal) result = getElementQuery((Node)obj);
+
+    // only null should make it this far without setting the result
+    if (result == null) {
+      if (obj != null) throw new IllegalArgumentException("Unknown type in result data");
+      result = new TableData(new Text(Entity.NBSP));
+    }
+
+    if (!(result instanceof TableData)) result = new TableData(result);
+    result.addAttr(Attr.CLASS, "rtd");  // set the style to Result-Table-Data
+    return (TableData)result;
+  }
+
+
+  /**
+   * Gets the URL used for the sample data.
+   * @return A URL for the sample data, expressed in the template as:
+   *         rmi://@@hostname@@/@@servername@@#sampledata
+   */
+  private Anchor getElementQuery(Node n) throws IOException {
+    try {
+      URI graphUri = new URI("rmi", tagMap.get(HOSTNAME_TAG), "/" + tagMap.get(SERVERNAME_TAG), "sampledata");
+      QueryParams params = new QueryParams();
+      params.add(MODEL_ARG, graphUri.toString());
+      String text;
+      if (n instanceof URIReference) {
+        params.add(QUERY_RESOURCE_ARG, n.toString());
+        text = n.toString();
+      } else {
+        params.add(QUERY_LITERAL_ARG, parameterizeLiteral((Literal)n));
+        text = ((Literal)n).getEscapedForm();
+      }
+      return new Anchor(new URI(EXECUTE_LINK + "?" + params), text);
+    } catch (URISyntaxException e) {
+      throw new IOException("Bad data returned from server");
+    }
+  }
+
+
+  /**
+   * Adds on a row without borders that links to the next page in this result.
+   * @param result The result being displayed.
+   * @param resultTable The existing page of data representing the result.
+   * @return The table with a new row appended to it, containing the required link.
+   * @throws IOException If there was an error structuring the table data.
+   */
+  private Table appendNextPageLink(Answer result, Table resultTable) throws IOException {
+    if (isResuming(result)) {
+      TableRow tr = createNextLinkRow(result, resultTable.getWidth());
+      tr.addAttr(Attr.CLASS, "borderLess");
+      resultTable.add(tr);
+    }
+    return resultTable;
+  }
+
+
+  /**
+   * Creates a link that will take the user to the next page for this data.
+   * @param result The result to create the link for.
+   * @param width The width the row has to fill.
+   * @return A table row containing the link to the next unfinished result.
+   * @throws IOException If there was an error structuring the table data.
+   */
+  private TableRow createNextLinkRow(Answer result, int width) throws IOException {
+    try {
+      QueryParams param = new QueryParams(RESULT_ORD_ARG, getNrToBeResumed(result));
+      Anchor a = new Anchor(new URI(EXECUTE_LINK + "?" + param), "Next page >");
+      a.addAttr(Attr.TITLE, "Forward to next page of results");
+      return new TableRow(new TableData(a).addAttr(Attr.COLSPAN, width));
+    } catch (URISyntaxException e) {
+      throw new IOException("Unabled to emit a relative URL: " + e.getMessage());
+    }
+  }
+
+
+  /**
+   * Writes a top-level element to the output stream.
+   * @param elt The element to write.
+   * @throws IOException If there was an error getting the stream or using it.
+   */
+  private void emitTopLevelElement(HtmlElement elt) throws IOException {
+    PrintWriter output = getOutput();
+    elt.sendTo(output);
+    output.append("\n");
+  }
+
+
+  /**
+   * Encode a Literal for use as a parameter in a query.
+   * @param l The literal to encode.
+   * @return An form of the literal that is safe to use in a parameter.
+   */
+  private String parameterizeLiteral(Literal l) {
+    StringBuilder p = new StringBuilder("'");
+    p.append(l.getLexicalForm().replaceAll("'", "\\\\'")).append('\'');
+
+    URI datatype = l.getDatatypeURI();
+    String language = l.getLanguage();
+
+    if (datatype != null) {
+      p.append("^^<").append(datatype).append(">");
+    } else if (language != null && !language.equals("")) {
+      p.append("@").append(language);
+    }
+
+    return p.toString();
+  }
+
+
+  /**
+   * Gets the PrintWriter for this page.
+   * @return The print writer to use for this page.
+   * @throws IOException If there was a problem getting the object.
+   */
+  private PrintWriter getOutput() throws IOException {
+    if (out == null) out = response.getWriter();
+    return out;
+  }
+
+
+  /**
+   * Determines how many results are to be resumed.
+   * @return <code>true</code> if this result has already been partly resumed.
+   */
+  private int getNrToBeResumed(Answer ans) {
+    if (unfinishedResults == null) throw new IllegalStateException("Should not be creating a link to a result when that result was not saved.");
+    Iterator<Answer> answers = unfinishedResults.keySet().iterator();
+    // should either be the first one added, or the last one
+    return (answers.next() == ans) ? 1 : unfinishedResults.size();
+  }
+
+
+  /**
+   * Tests a result to see it is being resumed.
+   * @param result The Answer to test.
+   * @return <code>true</code> if this result has already been partly resumed.
+   */
+  private boolean isResuming(Answer result) {
+    return (unfinishedResults != null) && unfinishedResults.keySet().contains(result);
+  }
+
+
+  /**
+   * Marks a result as finished.
+   * @param result The Answer that just finished.
+   * @throws TuplesException Error while cleaning up the result.
+   */
+  private void resultFinished(Answer result) throws TuplesException {
+    if (unfinishedResults != null) unfinishedResults.remove(result);
+    result.close();
+  }
+
+
+  /**
+   * Adds an unfinished Answer to the current session for later display. The Answer is not
+   * added if all the rows have been displayed.
+   * @param result The Answer to add to the session.
+   * @param numDisplayed The number of rows that have just been displayed from the result.
+   * @param cmd The Command associated with the Answer in <var>result</var>.
+   * @throws TuplesException If it is not possible to get the number of remaining rows.
+   */
+  private void addUnfinishedResult(Answer result, int numDisplayed, Command cmd) throws TuplesException {
+    // Determine how many results have yet to be displayed from this Answer
+    long remainingRows;
+    if (unfinishedResults == null) remainingRows = result.getRowCount();
+    else {
+      // get the remaining from the session, or use the full value if not in the session
+      Pair<Long,Command> lastRemainingRows = unfinishedResults.get(result);
+      remainingRows = (lastRemainingRows == null) ? result.getRowCount() : lastRemainingRows.first();
+    }
+    // just diplayed a page, so decrement by a page
+    remainingRows -= numDisplayed;
+    if (remainingRows < 0) throw new IllegalStateException("Cannot display more rows than are available.");
+
+    // No more to display, so clean up and leave
+    if (remainingRows == 0) {
+      result.close();
+      unfinishedResults.remove(result);
+      return;
+    }
+
+    // Remember that there is more for this result
+    if (unfinishedResults == null) {
+      unfinishedResults = new LinkedHashMap<Answer,Pair<Long,Command>>();
+      request.getSession().setAttribute(UNFINISHED_RESULTS, unfinishedResults);
+    }
+    unfinishedResults.put(result, new Pair<Long,Command>(remainingRows, cmd));
+  }
+
+}




More information about the Mulgara-svn mailing list