Package org.apache.marmotta.platform.sparql.webservices

Source Code of org.apache.marmotta.platform.sparql.webservices.SparqlWebService

/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.marmotta.platform.sparql.webservices;

import org.apache.marmotta.platform.core.api.templating.TemplatingService;
import org.apache.marmotta.platform.core.exception.MarmottaException;
import org.apache.marmotta.platform.sparql.api.sparql.SparqlService;
import org.apache.marmotta.platform.sparql.services.sparql.SparqlWritersHelper;
import org.apache.marmotta.platform.sparql.services.sparqlio.rdf.SPARQLGraphResultWriter;
import org.apache.marmotta.platform.sparql.services.sparqlio.sparqlhtml.SPARQLBooleanHTMLWriter;
import org.apache.marmotta.platform.sparql.services.sparqlio.sparqlhtml.SPARQLResultsHTMLWriter;
import com.google.common.collect.Lists;
import com.google.common.io.CharStreams;
import org.apache.marmotta.platform.core.api.config.ConfigurationService;
import org.apache.marmotta.platform.core.exception.InvalidArgumentException;
import org.apache.marmotta.platform.core.util.WebServiceUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.marmotta.commons.http.ContentType;
import org.apache.marmotta.commons.http.LMFHttpUtils;
import org.openrdf.query.MalformedQueryException;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.QueryLanguage;
import org.openrdf.query.UpdateExecutionException;
import org.openrdf.query.resultio.BooleanQueryResultFormat;
import org.openrdf.query.resultio.BooleanQueryResultWriter;
import org.openrdf.query.resultio.QueryResultIO;
import org.openrdf.query.resultio.TupleQueryResultFormat;
import org.openrdf.query.resultio.TupleQueryResultWriter;
import org.slf4j.Logger;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;
import javax.ws.rs.core.UriBuilder;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Execute SPARQL query (both query and update) on the LMF triple store
* according the SPARQL 1.1 Protocol
*
* @link http://www.w3.org/TR/sparql11-protocol/
*
* @author Sebastian Schaffert
* @author Sergio Fernández
*/
@ApplicationScoped
@Path("/" + SparqlWebService.PATH)
public class SparqlWebService {
 
    public final static String PATH = "sparql";
    public static final String SELECT = "/select";
    public static final String UPDATE = "/update";
    public static final String SNORQL = "/snorql";

    @Inject
    private Logger log;

    @Inject
    private SparqlService sparqlService;

    @Inject
    private ConfigurationService configurationService;

    @Inject
    private TemplatingService templatingService;
   
    /**
     * Single SPARQL endpoint, redirecting to the actual select endpoint
     * when possible
     *
     * @param query
     * @param update
     * @param request
     * @return
     * @throws URISyntaxException
     */
    @GET
    public Response get(@QueryParam("query") String query, @QueryParam("update") String update, @Context HttpServletRequest request) throws URISyntaxException {
      if (StringUtils.isNotBlank(update)) {
        String msg = "update operations are not supported through get"; //or yes?
        log.error(msg);
      return Response.status(Response.Status.BAD_REQUEST).entity(msg).build();
      } else {
        UriBuilder builder = UriBuilder.fromPath(PATH + SELECT);
        if (StringUtils.isNotBlank(query)) {       
          builder.replaceQuery(request.getQueryString());
        }
      return Response.seeOther(builder.build()).build();
    }
    }
   
    /**
     * Single endpoint for direct post queries (not yet implemented)
     *
     * @param request
     * @return
     */
    @POST
    public Response post(@Context HttpServletRequest request) {
      //String query = CharStreams.toString(request.getReader());       
      //TODO: introspect the query to determine the operation type
      String msg = "impossible to determine which type of operation (query/update) the request contains";
      log.error(msg);
    return Response.status(Response.Status.CONFLICT).entity(msg).build();
    }

    /**
     * Execute a SPARQL 1.1 tuple query on the LMF triple store using the query passed as query parameter to the
     * GET request. Result will be formatted using the result type passed as argument (either "html", "json" or "xml").
     * <p/>
     * see SPARQL 1.1 Query syntax at http://www.w3.org/TR/sparql11-query/
     *
     * @param query       the SPARQL 1.1 Query as a string parameter
     * @param resultType  the format for serializing the query results ("html", "json", or "xml")
     * @HTTP 200 in case the query was executed successfully
     * @HTTP 500 in case there was an error during the query evaluation
     * @return the query result in the format passed as argument
     */
    @GET
    @Path(SELECT)
    public Response selectGet(@QueryParam("query") String query, @QueryParam("output") String resultType, @Context HttpServletRequest request) {
        if(resultType == null) {
            List<ContentType> acceptedTypes = LMFHttpUtils.parseAcceptHeader(request.getHeader("Accept"));
            List<ContentType> offeredTypes  = LMFHttpUtils.parseStringList(Lists.newArrayList("application/sparql-results+xml","application/sparql-results+json","text/html", "application/rdf+xml", "text/csv"));

            ContentType bestType = LMFHttpUtils.bestContentType(offeredTypes,acceptedTypes);

            if(bestType != null) {
                resultType = bestType.getMime();
            }
        }

        try {
            if(resultType != null) {
                if (StringUtils.isNotBlank(query))
                    return buildQueryResponse(resultType, query);
                else {
                    if (parseSubType(resultType).equals("html"))
                        return Response.seeOther(new URI(configurationService.getServerUri() + "sparql/admin/snorql.html")).build();
                    else
                        return Response.status(Response.Status.BAD_REQUEST).entity("no SPARQL query specified").build();
                }
            } else
                return Response.status(Response.Status.BAD_REQUEST).entity("no result format specified or unsupported result format").build();
        } catch (InvalidArgumentException ex) {
            return Response.status(Response.Status.BAD_REQUEST).entity(ex.getMessage()).build();
        } catch(Exception e) {
            log.error("query execution threw an exception",e);

            return Response.serverError().entity("query not supported").build();
        }
    }

    /**
     * For CORS operations TODO: make it more fine grained (maybe user dependent)
     * + TODO filter chain do not work properly
     *
     * @param reqHeaders
     * @return responde

    @OPTIONS
    @Path(UPDATE)
    public Response optionsResourceRemote(@HeaderParam("Access-Control-Request-Headers") String reqHeaders) {
        if(reqHeaders == null) {
            reqHeaders = "Accept, Content-Type";
        }
        return Response.ok()
                .header("Allow", "POST")
                .header("Access-Control-Allow-Methods", "POST")
                .header("Access-Control-Allow-Headers", reqHeaders)
                .header("Access-Control-Allow-Origin", configurationService.getStringConfiguration("sparql.allow_origin","*"))
                .build();

    }
    */
   
    /**
     * Execute a SPARQL 1.1 tuple query on the LMF triple store using the query passed as form parameter to the
     * POST request. Result will be formatted using the result type passed as argument (either "html", "json" or "xml").
     * <p/>
     * see SPARQL 1.1 Query syntax at http://www.w3.org/TR/sparql11-query/
     *
     * @param query       the SPARQL 1.1 Query as a string parameter
     * @param resultType  the format for serializing the query results ("html", "json", or "xml")
     * @HTTP 200 in case the query was executed successfully
     * @HTTP 500 in case there was an error during the query evaluation
     * @return the query result in the format passed as argument
     */
    @POST
    @Consumes({"application/x-www-url-form-urlencoded", "application/x-www-form-urlencoded"})
    @Path(SELECT)
    public Response selectPostForm(@FormParam("query") String query, @QueryParam("output") String resultType, @Context HttpServletRequest request) {
        try {
            if(resultType == null) {
                List<ContentType> acceptedTypes = LMFHttpUtils.parseAcceptHeader(request.getHeader("Accept"));
                List<ContentType> offeredTypes  = LMFHttpUtils.parseStringList(Lists.newArrayList("application/sparql-results+xml","application/sparql-results+json","text/html", "application/rdf+xml", "text/csv"));

                ContentType bestType = LMFHttpUtils.bestContentType(offeredTypes,acceptedTypes);

                if(bestType != null) {
                    resultType = bestType.getMime();
                }
            }
            if(resultType != null) {
                if (StringUtils.isNotBlank(query))
                    return buildQueryResponse(resultType, query);
                else
                    return Response.status(Response.Status.BAD_REQUEST).entity("no SPARQL query specified").build();
            } else
                return Response.status(Response.Status.BAD_REQUEST).entity("no result format specified or unsupported result format").build();
        } catch (InvalidArgumentException ex) {
            return Response.status(Response.Status.BAD_REQUEST).entity(ex.getMessage()).build();
        } catch(Exception e) {
            log.error("query execution threw an exception",e);

            return Response.serverError().entity("query not supported").build();
        }
    }

    /**
     * Execute a SPARQL 1.1 tuple query on the LMF triple store using the query passed in the body of the
     * POST request. Result will be formatted using the result type passed as argument (either "html", "json" or "xml").
     * <p/>
     * see SPARQL 1.1 Query syntax at http://www.w3.org/TR/sparql11-query/
     *
     * @param request     the servlet request (to retrieve the SPARQL 1.1 Query passed in the body of the POST request)
     * @param resultType  the format for serializing the query results ("html", "json", or "xml")
     * @HTTP 200 in case the query was executed successfully
     * @HTTP 500 in case there was an error during the query evaluation
     * @return the query result in the format passed as argument
     */
    @POST
    @Path(SELECT)
    public Response selectPost(@QueryParam("output") String resultType, @Context HttpServletRequest request) {
        try {
            if(resultType == null) {
                List<ContentType> acceptedTypes = LMFHttpUtils.parseAcceptHeader(request.getHeader("Accept"));
                List<ContentType> offeredTypes  = LMFHttpUtils.parseStringList(Lists.newArrayList("application/sparql-results+xml","application/sparql-results+json","text/html", "application/rdf+xml", "text/csv"));

                ContentType bestType = LMFHttpUtils.bestContentType(offeredTypes,acceptedTypes);

                if(bestType != null) {
                    resultType = bestType.getMime();
                }
            }

            if(resultType != null) {
                String query = CharStreams.toString(request.getReader());
                if (query != null && !query.equals(""))
                    return buildQueryResponse(resultType, query);
                else
                    return Response.status(Response.Status.BAD_REQUEST).entity("no SPARQL query specified").build();
            } else
                return Response.status(Response.Status.BAD_REQUEST).entity("no result format specified or unsupported result format").build();
        } catch (InvalidArgumentException ex) {
            return Response.status(Response.Status.BAD_REQUEST).entity(ex.getMessage()).build();
        } catch(Exception e) {
            log.error("query execution threw an exception",e);

            return Response.serverError().entity("query not supported").build();
        }
    }

    /**
     * Execute a SPARQL 1.1 tuple query on the LMF triple store using the query passed as form parameter to the
     * POST request. Result will be formatted using the result type passed as argument (either "html", "json" or "xml").
     * <p/>
     * see SPARQL 1.1 Query syntax at http://www.w3.org/TR/sparql11-query/
     *
     * @param query       the SPARQL 1.1 Query as a string parameter
     * @param resultType  the format for serializing the query results ("html", "json", or "xml")
     * @HTTP 200 in case the query was executed successfully
     * @HTTP 500 in case there was an error during the query evaluation
     * @return the query result in the format passed as argument
     */
    @POST
    @Path(SNORQL)
    public Response snorqlPost(@FormParam("output") String resultType, @FormParam("query") String query) {
        try {
            return buildQueryResponse(resultType, query);
        } catch(Exception e) {
            log.error("query execution threw an exception",e);

            return Response.serverError().entity("query not supported").build();
        }
    }

    /**
     * Execute a SPARQL 1.1 Update request passed in the query parameter of the GET. The update will
     * be carried out
     * on the LMF triple store.
     * <p/>
     * see SPARQL 1.1 Update syntax at http://www.w3.org/TR/sparql11-update/
     *
     * @param update the update query in SPARQL 1.1 syntax
     * @param query the update query in SPARUL syntax
     * @HTTP 200 in case the update was carried out successfully
     * @HTTP 500 in case the update was not successful
     * @return empty content in case the update was successful, the error message in case an error occurred
     */
    @GET
    @Path(UPDATE)
    public Response updateGet(@QueryParam("update") String update, @QueryParam("query") String query, @QueryParam("output") String resultType,
            @Context HttpServletRequest request) {
        try {
            String q = getUpdateQuery(update, query);
            if (StringUtils.isNotBlank(q)) {
                sparqlService.update(QueryLanguage.SPARQL, q);
                return Response.ok().build();
            } else {
                if (resultType == null) {
                    List<ContentType> acceptedTypes = LMFHttpUtils.parseAcceptHeader(request.getHeader("Accept"));
                    List<ContentType> offeredTypes = LMFHttpUtils.parseStringList(Lists.newArrayList("*/*", "text/html"));
                    ContentType bestType = LMFHttpUtils.bestContentType(offeredTypes, acceptedTypes);
                    if (bestType != null) {
                        resultType = bestType.getMime();
                    }
                }
                if (parseSubType(resultType).equals("html"))
                    return Response.seeOther(new URI(configurationService.getServerUri() + "sparql/admin/update.html")).build();
                else
                    return Response.status(Response.Status.BAD_REQUEST).entity("no SPARQL query specified").build();
            }
        } catch (MalformedQueryException ex) {
            return Response.status(Response.Status.BAD_REQUEST).entity(WebServiceUtil.jsonErrorResponse(ex)).build();
        } catch(UpdateExecutionException e) {
            log.error("update execution threw an exception",e);
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch (MarmottaException e) {
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch (URISyntaxException e) {
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        }
    }

    /**
     * Get right update query from both possible parameters, for keeping
     * backward compatibility with the old parameter
     *
     * @param update update parameter
     * @param query query parameter
     * @return
     */
    private String getUpdateQuery(String update, String query) {
        if (StringUtils.isNotBlank(update))
            return update;
        else if (StringUtils.isNotBlank(query)) {
            log.warn("Update query still uses the old 'query' parameter");
            return query;
        } else
            return null;
    }

    /**
     * Execute a SPARQL 1.1 Update request using update via POST directly;
     * see details at http://www.w3.org/TR/sparql11-protocol/\#update-operation
     *
     * @param request the servlet request (to retrieve the SPARQL 1.1 Update query passed in the
     *            body of the POST request)
     * @HTTP 200 in case the update was carried out successfully
     * @HTTP 400 in case the update query is missing or invalid
     * @HTTP 500 in case the update was not successful
     * @return empty content in case the update was successful, the error message in case an error
     *         occurred
     */
    @POST
    @Path(UPDATE)
    @Consumes("application/sparql-update")
    public Response updatePostDirectly(@Context HttpServletRequest request) {
        try {
            String query = CharStreams.toString(request.getReader());
            if (StringUtils.isNotBlank(query)) {
                sparqlService.update(QueryLanguage.SPARQL, query);
                return Response.ok().build();
            } else
                return Response.status(Response.Status.BAD_REQUEST).entity("no SPARQL query given").build();
        } catch (MalformedQueryException e) {
            return Response.status(Response.Status.BAD_REQUEST).entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch(UpdateExecutionException e) {
            log.error("update execution threw an exception", e);
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch (MarmottaException e) {
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch (IOException e) {
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        }
    }
   
    /**
     * Execute a SPARQL 1.1 Update request using update via URL-encoded POST;
     * see details at http://www.w3.org/TR/sparql11-protocol/\#update-operation
     *
     * @param request the servlet request (to retrieve the SPARQL 1.1 Update query passed in the
     *            body of the POST request)
     * @HTTP 200 in case the update was carried out successfully
     * @HTTP 400 in case the update query is missing or invalid
     * @HTTP 500 in case the update was not successful
     * @return empty content in case the update was successful, the error message in case an error
     *         occurred
     */
    @POST
    @Path(UPDATE)
    @Consumes({"application/x-www-url-form-urlencoded", "application/x-www-form-urlencoded"})
    public Response updatePostUrlEncoded(@Context HttpServletRequest request) {
        try {
            Map<String,String> params = parseEncodedQueryParameters(CharStreams.toString(request.getReader()));          
            if (params.containsKey("update") && StringUtils.isNotBlank(params.get("update"))) {
                sparqlService.update(QueryLanguage.SPARQL, params.get("update"));
                return Response.ok().build();
            } else
                return Response.status(Response.Status.BAD_REQUEST).entity("no SPARQL query given").build();
        } catch (MalformedQueryException e) {
            return Response.status(Response.Status.BAD_REQUEST).entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch(UpdateExecutionException e) {
            log.error("update execution threw an exception", e);
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch (MarmottaException e) {
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        } catch (IOException e) {
            return Response.serverError().entity(WebServiceUtil.jsonErrorResponse(e)).build();
        }
    }   

    /**
     * Parse the encoded query parameters
     *
     * @todo this should be somewhere already implemented
     * @param body
     * @return parameters
     */
    private Map<String,String> parseEncodedQueryParameters(String body) {
      Map<String,String> params = new HashMap<String,String>();
        for (String pair : body.split("&")) {
            int eq = pair.indexOf("=");
            try {
              if (eq < 0) {
                  // key with no value
                  params.put(URLDecoder.decode(pair, "UTF-8"), "");
              } else {
                  // key=value
                  String key = URLDecoder.decode(pair.substring(0, eq), "UTF-8");
                  String value = URLDecoder.decode(pair.substring(eq + 1), "UTF-8");
                  params.put(key, value);
              }
            } catch (UnsupportedEncodingException e) {
              log.error("Query parameter cannot be decoded: {}", e.getMessage(), e);
            }
        }
    return params;
  }

  private Response buildQueryResponse(final String resultType, final String query) throws Exception {
        StreamingOutput entity = new StreamingOutput() {
            @Override
            public void write(OutputStream output) throws IOException, WebApplicationException {
                try {
                    sparqlService.query(QueryLanguage.SPARQL,query,getTupleResultWriter(resultType,output),getBooleanResultWriter(resultType,output), getGraphResultWriter(resultType,output), configurationService.getIntConfiguration("sparql.timeout", 60));
                } catch (MarmottaException ex) {
                    throw new WebApplicationException(ex.getCause(), Response.status(Response.Status.BAD_REQUEST).entity(WebServiceUtil.jsonErrorResponse(ex)).build());
                } catch (QueryEvaluationException e) {
                    throw new WebApplicationException(e.getCause(), Response.status(Response.Status.BAD_REQUEST).entity(WebServiceUtil.jsonErrorResponse(e)).build());
                } catch (MalformedQueryException e) {
                    throw new WebApplicationException(e.getCause(), Response.status(Response.Status.BAD_REQUEST).entity(WebServiceUtil.jsonErrorResponse(e)).build());
                } catch (TimeoutException e) {
                    throw new WebApplicationException(e.getCause(), Response.status(Response.Status.GATEWAY_TIMEOUT).entity(WebServiceUtil.jsonErrorResponse(e)).build());
                }
            }
        };

        //set returntype
        String s = "";
        if(resultType ==null) {
            s = "application/sparql-results+xml;charset=utf-8";
        } else if(parseSubType(resultType).equals("html") ) {
            s = "text/html;charset=utf-8";
        } else if(parseSubType(resultType).equals("json") ) {
            s = "application/sparql-results+json;charset=utf-8";
        } else if(parseSubType(resultType).equals("rdf+xml") ) {
            s = "application/rdf+xml;charset=utf-8";
        } else if(parseSubType(resultType).equals("rdf+n3") ) {
            s = "text/rdf+n3;charset=utf-8";
        } else if(parseSubType(resultType).equals("n3") ) {
            s = "text/rdf+n3;charset=utf-8";
        } else if(parseSubType(resultType).equals("csv") ) {
            s = "text/csv;charset=utf-8";
        } else {
            s = "application/sparql-results+xml;charset=utf-8";
        }

        Response r = Response.ok().entity(entity).header("Content-Type", s).build();
        return r;
    }

    private static Pattern subTypePattern = Pattern.compile("[a-z]+/([a-z0-9-._]+\\+)?([a-z0-9-._]+)(;.*)?");
    private String parseSubType(String mimeType) {
        Matcher matcher = subTypePattern.matcher(mimeType);
        if (matcher.matches())
            return matcher.group(2);
        else
            return mimeType;
    }


    private TupleQueryResultWriter getTupleResultWriter(String format, OutputStream os) {
        //build outputwriter
        final TupleQueryResultWriter out;
        if(format == null) {
            out = QueryResultIO.createWriter(TupleQueryResultFormat.SPARQL, os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("html")) {
            out = new SPARQLResultsHTMLWriter(os, templatingService);
        } else if(SparqlWritersHelper.parseSubType(format).equals("json")) {
            out = QueryResultIO.createWriter(TupleQueryResultFormat.JSON, os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("xml")) {
            out = QueryResultIO.createWriter(TupleQueryResultFormat.SPARQL, os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("csv")) {
            out = QueryResultIO.createWriter(TupleQueryResultFormat.CSV, os);
        } else throw new InvalidArgumentException("could not produce format "+format);
        return out;
    }

    private BooleanQueryResultWriter getBooleanResultWriter(String format, OutputStream os) {
        //build outputwriter
        final BooleanQueryResultWriter out;
        if(format == null) {
            out = QueryResultIO.createWriter(BooleanQueryResultFormat.SPARQL, os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("html")) {
            out = new SPARQLBooleanHTMLWriter(os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("json")) {
            out = QueryResultIO.createWriter(BooleanQueryResultFormat.JSON, os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("xml")) {
            out = QueryResultIO.createWriter(BooleanQueryResultFormat.SPARQL, os);
        } else if(SparqlWritersHelper.parseSubType(format).equals("csv")) {
            out = QueryResultIO.createWriter(BooleanQueryResultFormat.TEXT, os);
        } else throw new InvalidArgumentException("could not produce format "+format);
        return out;
    }

    protected SPARQLGraphResultWriter getGraphResultWriter(String format, OutputStream os) {
        return new SPARQLGraphResultWriter(os,format);
    }

}
TOP

Related Classes of org.apache.marmotta.platform.sparql.webservices.SparqlWebService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.