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