/* * Copyright 2008 Fedora Commons, Inc. * * Licensed 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. */ package org.mulgara.protocol.http; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.mulgara.itql.TqlInterpreter; import org.mulgara.parser.Interpreter; import org.mulgara.protocol.StreamedAnswer; import org.mulgara.protocol.StreamedSparqlJSONAnswer; import org.mulgara.protocol.StreamedSparqlJSONObject; import org.mulgara.protocol.StreamedSparqlXMLObject; import org.mulgara.protocol.StreamedTqlXMLAnswer; import org.mulgara.query.Answer; import org.mulgara.query.Query; import org.mulgara.query.TuplesException; import org.mulgara.query.operation.Command; import org.mulgara.server.SessionFactoryProvider; import org.mulgara.util.functional.Pair; /** * A query gateway for TQL. * * @created Sep 14, 2008 * @author Paula Gearon * @copyright © 2008 <a href="http://www.fedora-commons.org/">Fedora Commons</a> */ public class TqlServlet extends ProtocolServlet { /** Serialization ID */ private static final long serialVersionUID = -72714067636720775L; /** * Creates the servlet for communicating with the given server. * @param server The server that provides access to the database. */ public TqlServlet(SessionFactoryProvider server) throws IOException { super(server); } /** * Creates the servlet in a default application server environment. */ public TqlServlet() throws IOException { } /** @see org.mulgara.protocol.http.ProtocolServlet#initializeBuilders() */ protected void initializeBuilders() { // TODO: create a JSON answer and a XML object for TQL. AnswerStreamConstructor jsonBuilder = new AnswerStreamConstructor() { public StreamedAnswer fn(Answer ans, OutputStream s) { return new StreamedSparqlJSONAnswer(ans, s); } }; AnswerStreamConstructor xmlBuilder = new AnswerStreamConstructor() { public StreamedAnswer fn(Answer ans, OutputStream s) { return new StreamedTqlXMLAnswer(ans, s); } }; streamBuilders.put(Output.JSON, jsonBuilder); streamBuilders.put(Output.XML, xmlBuilder); streamBuilders.put(Output.RDFXML, xmlBuilder); // TODO: create an RDF/XML builder streamBuilders.put(Output.N3, xmlBuilder); // TODO: create an N3 builder ObjectStreamConstructor jsonObjBuilder = new ObjectStreamConstructor() { public StreamedAnswer fn(Object o, OutputStream s) { return new StreamedSparqlJSONObject(o, s); } }; ObjectStreamConstructor xmlObjBuilder = new ObjectStreamConstructor() { public StreamedAnswer fn(Object o, OutputStream s) { return new StreamedSparqlXMLObject(o, s); } }; objectStreamBuilders.put(Output.JSON, jsonObjBuilder); objectStreamBuilders.put(Output.XML, xmlObjBuilder); objectStreamBuilders.put(Output.RDFXML, xmlObjBuilder); // TODO: create an RDF/XML object builder objectStreamBuilders.put(Output.N3, xmlObjBuilder); // TODO: create an N3 object builder } /** * Provide a description for the servlet. * @see javax.servlet.GenericServlet#getServletInfo() */ public String getServletInfo() { return "Mulgara TQL Query Endpoint"; } /** * Gets the TQL interpreter 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. */ protected TqlInterpreter getInterpreter(HttpServletRequest req) throws BadRequestException { HttpSession httpSession = req.getSession(); TqlInterpreter interpreter = (TqlInterpreter)httpSession.getAttribute(INTERPRETER); if (interpreter == null) { interpreter = new TqlInterpreter(); httpSession.setAttribute(INTERPRETER, interpreter); } return interpreter; } /** * Respond to a request for the servlet. * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ protected void doResponse(HttpServletRequest req, HttpServletResponse resp, RestParams params) throws ServletException, IOException { RestParams.ResourceType type = params.getType(); if (type != RestParams.ResourceType.QUERY) { // This is a GET on a graph or a statement super.doResponse(req, resp, params); } else { // build a list of queries List<Query> queries = getQueries(params.getQuery(), req); boolean firstItem = true; Output outputType = null; StreamedAnswer output = null; // respond to everything in the list for (Query q: queries) { Answer result = null; try { result = executeQuery(q, req); if (firstItem) { firstItem = false; outputType = getOutputType(req, q); // start the response output = sendDocumentHeader(streamBuilders, outputType, resp); } else { assert outputType != null; if (outputType != getOutputType(req, q)) { throw new BadRequestException("Incompatible mixed response types requested"); } } assert output != null; output.addAnswer(result); } catch (TuplesException e) { throw new InternalErrorException("Error reading answers: " + e.getMessage()); } finally { try { if (result != null) result.close(); } catch (TuplesException e) { logger.warn("Error closing: " + e.getMessage(), e); } } } if (output == null) { OutputStream s = resp.getOutputStream(); if (s != null) s.close(); } else { output.addDocFooter(); output.close(); } } } /** * Sends back a response when a list has been requested. This is separate from the doResponse * method above (which does something similar), because this is processing all results * after they have been completed. This happens during a POST, because many of the operations * may not have been queries, whereas the doResponse above is onyl dealing with a series of * read-only queries. * @param results The list of Answer/Output pairs. * @param resp The response object to send the data on. * @throws BadRequestException More than one type appeared in the request. * @throws InternalErrorException There was a problem processing the request. * @throws IOException There was an error responding to the client. */ protected void doResponseList(List<Pair<Answer,Output>> results, HttpServletResponse resp) throws BadRequestException, InternalErrorException, IOException { // these variables are to remember state throughout the loop boolean firstItem = true; Output outputType = null; StreamedAnswer output = null; // respond to everything in the list for (Pair<Answer,Output> result: results) { try { if (firstItem) { firstItem = false; // start the response outputType = result.second(); output = sendDocumentHeader(streamBuilders, outputType, resp); } else { assert outputType != null; if (outputType != result.second()) { throw new BadRequestException("Incompatible mixed response types requested"); } } assert output != null; output.addAnswer(result.first()); } catch (TuplesException e) { throw new InternalErrorException("Error reading answers: " + e.getMessage()); } finally { try { if (result != null) result.first().close(); } catch (TuplesException e) { logger.warn("Error closing: " + e.getMessage(), e); } } } if (output == null) { OutputStream s = resp.getOutputStream(); if (s != null) s.close(); } else { output.addDocFooter(); output.close(); } } /** * Converts a SPARQL query string into a Query object. This uses extra parameters from the * client where appropriate, such as the default graph. * @param query The query string issued by the client. * @param req The request from the client. * @return A new Query object, built from the query string. * @throws BadRequestException Due to an invalid command string. */ List<Query> getQueries(String query, HttpServletRequest req) throws BadRequestException { if (query == null) throw new BadRequestException("Query must be supplied"); try { Interpreter interpreter = getInterpreter(req); List<Command> cmdList = interpreter.parseCommands(query); List<Query> qList = new ArrayList<Query>(); for (Command c: cmdList) { if (!(c instanceof Query)) throw new BadRequestException("Modifying command used instead of a query: " + c.getText()); qList.add((Query)c); } return qList; } catch (Exception e) { throw new BadRequestException(e.getMessage()); } } StreamedAnswer sendDocumentHeader(Map<Output,? extends StreamConstructor<Answer>> builders, Output type, HttpServletResponse resp) throws IOException, BadRequestException, InternalErrorException { // establish the output type if (type == null) type = DEFAULT_OUTPUT_TYPE; resp.setContentType(type.mimeText); resp.setHeader("pragma", "no-cache"); // get the constructor for the stream outputter StreamConstructor<Answer> constructor = builders.get(type); if (constructor == null) throw new BadRequestException("Unknown result type: " + type); try { OutputStream out = resp.getOutputStream(); StreamedAnswer strAns = constructor.fn(null, out); strAns.initOutput(); strAns.addDocHeader(); return strAns; } catch (IOException ioe) { // There's no point in telling the client if we can't talk to the client throw ioe; } catch (Exception e) { throw new InternalErrorException(e.getMessage()); } } }