[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 © 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