[Mulgara-svn] r1266 - trunk/src/jar/querylang/java/org/mulgara/protocol/http

pag at mulgara.org pag at mulgara.org
Tue Sep 23 04:32:15 UTC 2008


Author: pag
Date: 2008-09-22 21:32:14 -0700 (Mon, 22 Sep 2008)
New Revision: 1266

Added:
   trunk/src/jar/querylang/java/org/mulgara/protocol/http/MimeMultiNamedPart.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/http/ServletDataSource.java
Modified:
   trunk/src/jar/querylang/java/org/mulgara/protocol/http/ProtocolServlet.java
   trunk/src/jar/querylang/java/org/mulgara/protocol/http/SparqlServlet.java
Log:
Now allows RDF files to be uploaded with an HTTP PUT operation. TODO: RDF/XML files with a missing newline at the end will erroneously give an error.

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/http/MimeMultiNamedPart.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/http/MimeMultiNamedPart.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/http/MimeMultiNamedPart.java	2008-09-23 04:32:14 UTC (rev 1266)
@@ -0,0 +1,129 @@
+/*
+ * 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.protocol.http;
+
+import java.io.IOException;
+
+import javax.activation.DataSource;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMultipart;
+
+/**
+ * This class extends multipart MIME objects to lookup parameter values.
+ *
+ * @created Sep 17, 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 MimeMultiNamedPart extends MimeMultipart {
+
+  /**
+   * @param src The data source to retrieve the MIME data from
+   * @throws MessagingException If the source cannot be parsed as valid MIME data.
+   */
+  public MimeMultiNamedPart(DataSource arg0) throws MessagingException {
+    super(arg0);
+  }
+
+
+  /**
+   * Goes through MIME data to look for a parameter.
+   * @param paramName The name of the parameter to retrieve
+   * @return The value for the parameter.
+   * @throws MessageException If the MIME data could not be parsed.
+   */
+  public Object getParameter(String param) throws MessagingException, IOException {
+    BodyPart part = getNamedPart(param);
+    return part == null ? null : part.getContent();
+  }
+
+
+  /**
+   * Goes through MIME data to look for a string parameter.
+   * @param paramName The name of the parameter to retrieve
+   * @return The string value for the parameter, converting if needed.
+   * @throws MessageException If the MIME data could not be parsed.
+   */
+  public String getParameterString(String param) throws MessagingException, IOException {
+    Object obj = getParameter(param);
+    return obj == null ? null : obj.toString();
+  }
+
+
+  /**
+   * Finds a body part that has the requested name.
+   * @param paramName The name of the part to get.
+   * @return The body part with the requested name, or null if not found.
+   * @throws MessagingException If the MIME object could not be scanned.
+   */
+  public BodyPart getNamedPart(String paramName) throws MessagingException {
+    for (int p = 0; p < getCount(); p++) {
+      BodyPart bpart = getBodyPart(p);
+      if (paramName.equalsIgnoreCase(getPartName(bpart))) return bpart;
+    }
+    return null;
+  }
+
+
+  /**
+   * Look up the name of a part by index.
+   * @param partNr The index of the part to look up.
+   * @return The name of the part, or null if not available.
+   * @throws MessagingException If the MIME object could not be scanned.
+   */
+  public String getPartName(int partNr) throws MessagingException {
+    return getPartName(getBodyPart(partNr));
+  }
+
+
+  /**
+   * Gets the name of a body part.
+   * @param part The body part to get the name of.
+   * @return The name of this part, or <code>null</code> if no name can be found.
+   * @throws MessagingException The part could not be accessed.
+   */
+  public static String getPartName(BodyPart part) throws MessagingException {
+    String[] cds = part.getHeader("Content-Disposition");
+    if (cds == null) return null;
+    // probably only has one Content-Disposition header, but check all anyway
+    for (String header: cds) {
+      for (String kv: header.split("; ")) {
+        int eq = kv.indexOf('=');
+        if (eq >= 0) {
+          // a key=value element
+          String key = kv.substring(0, eq);
+          if ("name".equalsIgnoreCase(key)) {
+            String value = kv.substring(eq + 1);
+            return stripQuotes(value);
+          }
+        }
+      }
+    }
+    return null;
+  }
+
+
+  /**
+   * Removes quote characters from around a string.
+   * @param str The string to remove quotes from.
+   * @return The part of str that was between quotes, or all of str if there were no quotes.
+   */
+  private static String stripQuotes(String str) {
+    int l = str.length() - 1;
+    if (str.charAt(0) == '"' && str.charAt(l) == '"') str = str.substring(1, l);
+    return str;
+  }
+
+}

Modified: trunk/src/jar/querylang/java/org/mulgara/protocol/http/ProtocolServlet.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/http/ProtocolServlet.java	2008-09-23 04:30:12 UTC (rev 1265)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/http/ProtocolServlet.java	2008-09-23 04:32:14 UTC (rev 1266)
@@ -14,13 +14,20 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
+import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
 
 import org.mulgara.connection.Connection;
 import org.mulgara.connection.SessionConnection;
@@ -31,8 +38,12 @@
 import org.mulgara.query.QueryException;
 import org.mulgara.query.TuplesException;
 import org.mulgara.query.operation.Command;
+import org.mulgara.query.operation.CreateGraph;
+import org.mulgara.query.operation.Load;
 import org.mulgara.server.SessionFactory;
 import org.mulgara.server.SessionFactoryProvider;
+import org.mulgara.util.functional.C;
+import org.mulgara.util.functional.Fn1E;
 import org.mulgara.util.functional.Fn2;
 
 /**
@@ -78,6 +89,19 @@
   /** The default output type to use. */
   protected static final String DEFAULT_OUTPUT_TYPE = OUTPUT_XML;
 
+  /** The parameter identifying the graph. */
+  protected static final String DEFAULT_GRAPH_ARG = "default-graph-uri";
+
+  /** The parameter identifying the graph. We don't set these in SPARQL yet. */
+  @SuppressWarnings("unused")
+  protected static final String NAMED_GRAPH_ARG = "named-graph-uri";
+
+  /** The name of the default graph. This is a null graph. */
+  protected static final URI DEFAULT_NULL_GRAPH = URI.create("sys:null");
+
+  /** An empty graph for those occasions when no graph is set. */
+  protected static final List<URI> DEFAULT_NULL_GRAPH_LIST = Collections.singletonList(DEFAULT_NULL_GRAPH);
+
   /** The content type of the results. */
   protected static final String CONTENT_TYPE = "application/sparql-results+xml";
 
@@ -87,6 +111,21 @@
   /** Session value for interpreter. */
   protected static final String INTERPRETER = "session.interpreter";
 
+  /** Posted RDF data content type. */
+  protected static final String POSTED_DATA_TYPE = "multipart/form-data;";
+
+  /** The name of the posted data. */
+  protected static final String GRAPH_DATA = "graph";
+
+  /** The header used to indicate a statement count. */ //HDR_CANNOT_LOAD
+  protected static final String HDR_STMT_COUNT = "Statements-Loaded";
+
+  /** The header used to indicate a part that couldn't be loaded. */
+  protected static final String HDR_CANNOT_LOAD = "Cannot-Load";
+
+  /** A made-up scheme for data uploaded through http-put, since http means "downloaded". */
+  protected static final String HTTP_PUT_NS = "http-put://upload/";
+
   /** The server for finding a session factory. */
   private SessionFactoryProvider server;
 
@@ -146,19 +185,10 @@
    * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
    */
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
-    String queryStr = req.getParameter(QUERY_ARG);
+    String type = req.getContentType();
     try {
-      Command cmd = getCommand(queryStr, req);
-  
-      Object result = executeCommand(cmd, req);
-
-      String outputType = req.getParameter(OUTPUT_ARG);
-      if (result instanceof Answer) {
-        sendAnswer((Answer)result, outputType, resp);
-      } else {
-        sendStatus(result, outputType, resp);
-      }
-
+      if (type != null && type.startsWith(POSTED_DATA_TYPE)) handleDataUpload(req, resp);
+      else handleUpdateQuery(req, resp);
     } catch (ServletException e) {
       e.sendResponseTo(resp);
     }
@@ -317,6 +347,92 @@
 
 
   /**
+   * Uploads data into a graph.
+   * @param req The object containing the client request to upload data.
+   * @param resp The object to respond to the client with.
+   * @throws IOException If an error occurs when communicating with the client.
+   */
+  protected void handleDataUpload(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
+    try {
+      // parse in the data to be uploaded
+      MimeMultiNamedPart mime = new MimeMultiNamedPart(new ServletDataSource(req, GRAPH_DATA));
+
+      // validate the request
+      if (mime.getCount() == 0) throw new BadRequestException("Request claims to have posted data, but none was supplied.");
+
+      // Get the destination graph, and ensure it exists
+      URI destGraph = getRequestedDefaultGraph(req, mime);
+      Connection conn = getConnection(req);
+      try {
+        new CreateGraph(destGraph).execute(conn);
+      } catch (QueryException e) {
+        throw new InternalErrorException("Unable to create graph: " + e.getMessage());
+      }
+
+      // upload the data
+      for (int partNr = 0; partNr < mime.getCount(); partNr++) {
+        BodyPart part = mime.getBodyPart(partNr);
+        String partName = mime.getPartName(partNr);
+        try {
+          if (!knownParam(partName)) resp.addHeader(HDR_STMT_COUNT, Long.toString(loadData(destGraph, part, conn)));
+        } catch (QueryException e) {
+          resp.addHeader(HDR_CANNOT_LOAD, partName);
+        }
+      }
+    } catch (MessagingException e) {
+      throw new BadRequestException("Unable to process received MIME data: " + e.getMessage());
+    }
+    resp.setStatus(SC_NO_CONTENT);
+  }
+
+
+  /**
+   * Respond to a request for a query that may update the data.
+   * @param req The query request object.
+   * @param resp The HTTP response object.
+   * @throws IOException If an error occurs when communicating with the client.
+   * @throws ServletException 
+   */
+  protected void handleUpdateQuery(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
+    String queryStr = req.getParameter(QUERY_ARG);
+    Command cmd = getCommand(queryStr, req);
+
+    Object result = executeCommand(cmd, req);
+
+    String outputType = req.getParameter(OUTPUT_ARG);
+    if (result instanceof Answer) {
+      sendAnswer((Answer)result, outputType, resp);
+    } else {
+      sendStatus(result, outputType, resp);
+    }
+  }
+
+
+  /**
+   * Load MIME data into a graph.
+   * @param graph The graph to load into.
+   * @param data The data to be loaded, with associated meta-data.
+   * @param cxt The connection to the database.
+   * @return The number of statements loaded.
+   * @throws IOException error reading from the client.
+   * @throws BadRequestException Bad data passed to the load request.
+   * @throws InternalErrorException A query exception occurred during the load operation.
+   */
+  protected long loadData(URI graph, BodyPart data, Connection cxt) throws IOException, ServletException, QueryException {
+    try {
+      URI absoluteUri = new URI(HTTP_PUT_NS + data.getFileName());
+      Load loadCmd = new Load(absoluteUri, graph, true);
+      loadCmd.setOverrideInputStream(data.getInputStream());
+      return (Long)loadCmd.execute(cxt);
+    } catch (MessagingException e) {
+      throw new BadRequestException("Unable to process data for loading: " + e.getMessage());
+    } catch (URISyntaxException e) {
+      throw new BadRequestException("Illegal filename: " + e.getInput());
+    }
+  }
+
+
+  /**
    * Gets the SPARQL interpreter for the current session,
    * creating it if it doesn't exist yet.
    * @param req The current request environment.
@@ -326,6 +442,59 @@
 
 
   /**
+   * Gets the default graphs the user requested.
+   * @param req The request object from the user.
+   * @return A list of URIs for graphs. This may be null if no URIs were requested.
+   * @throws BadRequestException If a graph name was an invalid URI.
+   */
+  protected List<URI> getRequestedDefaultGraphs(HttpServletRequest req) throws BadRequestException {
+    String[] defaults = req.getParameterValues(DEFAULT_GRAPH_ARG);
+    if (defaults == null) return DEFAULT_NULL_GRAPH_LIST;
+    try {
+      return C.map(defaults, new Fn1E<String,URI,URISyntaxException>(){public URI fn(String s)throws URISyntaxException{return new URI(s);}});
+    } catch (URISyntaxException e) {
+      throw new BadRequestException("Invalid URI. " + e.getMessage());
+    }
+  }
+
+
+  /**
+   * Gets the default graphs the user requested.
+   * @param req The request object from the user.
+   * @return A list of URIs for graphs. This may be null if no URIs were requested.
+   * @throws BadRequestException If a graph name was an invalid URI.
+   */
+  protected URI getRequestedDefaultGraph(HttpServletRequest req, MimeMultiNamedPart mime) throws ServletException {
+    // look in the parameters
+    String[] defaults = req.getParameterValues(DEFAULT_GRAPH_ARG);
+    if (defaults != null) {
+      if (defaults.length != 1) throw new BadRequestException("Multiple graphs requested.");
+      try {
+        return new URI(defaults[0]);
+      } catch (URISyntaxException e) {
+        throw new BadRequestException("Invalid URI. " + e.getInput());
+      }
+    }
+    // look in the mime data
+    if (mime != null) {
+      try {
+        String result = mime.getParameterString(DEFAULT_GRAPH_ARG);
+        if (result != null) {
+          try {
+            return new URI(result);
+          } catch (URISyntaxException e) {
+            throw new BadRequestException("Bad graph URI: " + result);
+          }
+        }
+      } catch (Exception e) {
+        throw new BadRequestException("Bad MIME data: " + e.getMessage());
+      }
+    }
+    return DEFAULT_NULL_GRAPH;
+  }
+
+
+  /**
    * Gets the connection for the current session, creating it if it doesn't exist yet.
    * @param req The current request environment.
    * @return A connection that is tied to this HTTP session.
@@ -359,4 +528,15 @@
     return cachedSessionFactory;
   }
 
+
+  /**
+   * Compare a parameter name to a set of known parameter names.
+   * @param name The name to check.
+   * @return <code>true</code> if the name is known. <code>false</code> if not known or <code>null</code>.
+   */
+  private boolean knownParam(String name) {
+    final String[] knownParams = new String[] { DEFAULT_GRAPH_ARG, NAMED_GRAPH_ARG, GRAPH_DATA };
+    for (String p: knownParams) if (p.equalsIgnoreCase(name)) return true;
+    return false;
+  }
 }

Added: trunk/src/jar/querylang/java/org/mulgara/protocol/http/ServletDataSource.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/http/ServletDataSource.java	                        (rev 0)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/http/ServletDataSource.java	2008-09-23 04:32:14 UTC (rev 1266)
@@ -0,0 +1,76 @@
+/*
+ * 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.protocol.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.activation.DataSource;
+import javax.servlet.http.HttpServletRequest;
+
+/**
+ * Wraps a servlet request to provide as a parameter to a MimeMultipart handler.
+ *
+ * @created Sep 16, 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 ServletDataSource implements DataSource {
+
+  /** The wrapped request to present as data. */
+  private final HttpServletRequest req;
+
+  /** The name of this data source/ */
+  private final String name;
+
+  /**
+   * Create a data source that presents a servlet request.
+   * @param req The servlet request to wrap.
+   */
+  public ServletDataSource(HttpServletRequest req, String name) {
+    this.req = req;
+    this.name = name;
+  }
+
+  /**
+   * @see javax.activation.DataSource#getContentType()
+   */
+  public String getContentType() {
+    return req.getContentType();
+  }
+
+  /**
+   * @see javax.activation.DataSource#getInputStream()
+   */
+  public InputStream getInputStream() throws IOException {
+    return req.getInputStream();
+  }
+
+  /**
+   * @see javax.activation.DataSource#getName()
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * @see javax.activation.DataSource#getOutputStream()
+   * @throws UnsupportedOperationException This is a read-only resource.
+   */
+  public OutputStream getOutputStream() throws IOException {
+    throw new UnsupportedOperationException("This is a read-only data source.");
+  }
+
+}

Modified: trunk/src/jar/querylang/java/org/mulgara/protocol/http/SparqlServlet.java
===================================================================
--- trunk/src/jar/querylang/java/org/mulgara/protocol/http/SparqlServlet.java	2008-09-23 04:30:12 UTC (rev 1265)
+++ trunk/src/jar/querylang/java/org/mulgara/protocol/http/SparqlServlet.java	2008-09-23 04:32:14 UTC (rev 1266)
@@ -14,10 +14,6 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.List;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpSession;
@@ -30,8 +26,6 @@
 import org.mulgara.query.Answer;
 import org.mulgara.server.SessionFactoryProvider;
 import org.mulgara.sparql.SparqlInterpreter;
-import org.mulgara.util.functional.C;
-import org.mulgara.util.functional.Fn1E;
 
 /**
  * A query gateway for SPARQL.
@@ -46,20 +40,6 @@
   /** Serialization ID */
   private static final long serialVersionUID = 5047396536306099528L;
 
-  /** The parameter identifying the graph. */
-  private static final String DEFAULT_GRAPH_ARG = "default-graph-uri";
-
-  /** The parameter identifying the graph. We don't set these in SPARQL yet. */
-  @SuppressWarnings("unused")
-  private static final String NAMED_GRAPH_ARG = "named-graph-uri";
-
-  /** An empty graph for those occasions when no graph is set. */
-  private static final List<URI> DEFAULT_NULL_GRAPH_LIST = Collections.singletonList(URI.create("sys:null"));
-
-  static {
-  }
-
-
   /**
    * Creates the servlet for communicating with the given server.
    * @param server The server that provides access to the database.
@@ -119,21 +99,4 @@
     return interpreter;
   }
 
-
-  /**
-   * Gets the default graphs the user requested.
-   * @param req The request object from the user.
-   * @return A list of URIs for graphs. This may be null if no URIs were requested.
-   * @throws BadRequestException If a graph name was an invalid URI.
-   */
-  private List<URI> getRequestedDefaultGraphs(HttpServletRequest req) throws BadRequestException {
-    String[] defaults = req.getParameterValues(DEFAULT_GRAPH_ARG);
-    if (defaults == null) return DEFAULT_NULL_GRAPH_LIST;
-    try {
-      return C.map(defaults, new Fn1E<String,URI,URISyntaxException>(){public URI fn(String s)throws URISyntaxException{return new URI(s);}});
-    } catch (URISyntaxException e) {
-      throw new BadRequestException("Invalid URI. " + e.getMessage());
-    }
-  }
-
 }




More information about the Mulgara-svn mailing list