package org.gazzax.labs.solrdf.handler.search.handler; import static org.gazzax.labs.solrdf.F.readCommandFromIncomingStream; import java.security.Principal; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.gazzax.labs.solrdf.Strings.*; import javax.servlet.http.HttpServletRequest; import org.apache.jena.riot.WebContent; import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.core.SolrCore; import org.apache.solr.handler.RequestHandlerBase; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestHandler; import org.apache.solr.response.SolrQueryResponse; import org.apache.solr.schema.IndexSchema; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.util.RTimer; import org.gazzax.labs.solrdf.Names; /** * A RequestHandler that dispatches SPARQL 1.1 Query and Update requests across dedicated handlers. * * @author Andrea Gazzarini * @since 1.0 */ public class Sparql11SearchHandler extends RequestHandlerBase { static final String MISSING_QUERY_IN_GET_MESSAGE = "SPARQL Protocol violation: query or q parameter is mandatory in GET requests. " + "(see http://www.w3.org/TR/sparql11-protocol/#query-via-get)"; static final String MISSING_QUERY_OR_UPDATE_IN_POST_MESSAGE = "SPARQL Protocol violation: POST with URL encoded parameters request without \"query\", \"q\" or \"update\" parameter. " + "(see http://www.w3.org/TR/sparql11-protocol/#query-via-post-urlencoded and http://www.w3.org/TR/sparql11-protocol/#update-via-post-urlencoded)"; static final String MISSING_QUERY_IN_POST_BODY = "SPARQL Protocol violation: Query request via POST directly seems to have an empty body"; static final String MISSING_UPDATE_IN_POST_BODY = "SPARQL Protocol violation: Update request via POST directly seems to have an empty body."; static final String BAD_POST_REQUEST = "SPARQL Protocol violation: Cannot determine the POST request type you sent. Content-type must be " + WebContent.contentTypeSPARQLQuery + " or " + WebContent.contentTypeSPARQLUpdate + " and body must contains a valid query or update command."; static final String INVALID_HTTP_METHOD = "SPARQL Protocol violation: request method must be GET or POST."; static final String SEARCH_HANDLER_PARAMETER_NAME = "s"; static final String DEFAULT_SEARCH_HANDLER_NAME = "/sparql-query"; static final String UPDATE_HANDLER_PARAMETER_NAME = "u"; static final String DEFAULT_UPDATE_HANDLER_NAME = "/sparql-update"; @Override public void handleRequestBody(final SolrQueryRequest request, final SolrQueryResponse response) throws Exception { final SolrParams parameters = request.getParams(); if (isUsingGET(request)) { if (containsQueryParameter(parameters)) { requestHandler( request, parameters.get(SEARCH_HANDLER_PARAMETER_NAME, DEFAULT_SEARCH_HANDLER_NAME)) .handleRequest(request, response); } else { throw new SolrException( ErrorCode.BAD_REQUEST, MISSING_QUERY_IN_GET_MESSAGE); } } else if (isUsingPOST(request)) { if (isUsingURLEncodedParameters(request)) { if (containsUpdateParameter(parameters)) { requestHandler( request, parameters.get(UPDATE_HANDLER_PARAMETER_NAME, DEFAULT_UPDATE_HANDLER_NAME)) .handleRequest(new SparqlUpdateSolrQueryRequest(request), response); } else if (containsQueryParameter(parameters)) { requestHandler( request, parameters.get(SEARCH_HANDLER_PARAMETER_NAME, DEFAULT_SEARCH_HANDLER_NAME)) .handleRequest(request, response); } else { throw new SolrException( ErrorCode.BAD_REQUEST, MISSING_QUERY_OR_UPDATE_IN_POST_MESSAGE); } } else if (isSparqlQueryContentType(request)) { if (isBodyNotEmpty(request)) { request.setParams(new ModifiableSolrParams(parameters).set(Names.QUERY, readCommandFromIncomingStream(request.getContentStreams().iterator().next()))); requestHandler( request, parameters.get(SEARCH_HANDLER_PARAMETER_NAME, DEFAULT_SEARCH_HANDLER_NAME)) .handleRequest(new SparqlQuerySolrQueryRequest(request), response); } else { throw new SolrException( ErrorCode.BAD_REQUEST, MISSING_QUERY_IN_POST_BODY); } } else if (isSparqlUpdateContentType(request)){ if (isBodyNotEmpty(request)) { requestHandler( request, parameters.get(UPDATE_HANDLER_PARAMETER_NAME, DEFAULT_UPDATE_HANDLER_NAME)) .handleRequest(request, response); } else { throw new SolrException( ErrorCode.BAD_REQUEST, MISSING_UPDATE_IN_POST_BODY); } } else { throw new SolrException( ErrorCode.BAD_REQUEST, BAD_POST_REQUEST); } } else { throw new SolrException( ErrorCode.BAD_REQUEST, INVALID_HTTP_METHOD); } } /** * Checks if the current (HTTP) request contains a valid body. * * @param request the Solr request. * @return true if the current (HTTP) request contains a valid body, false otherwise. */ boolean isBodyNotEmpty(final SolrQueryRequest request) { return request.getContentStreams() != null && request.getContentStreams().iterator().hasNext(); } /** * Returns true if the method associated with the current HTTP request is GET. * * @param request the current Solr request. * @return true if the method associated with the current HTTP request is GET. */ boolean isUsingGET(final SolrQueryRequest request) { return "GET".equals(((HttpServletRequest) request.getContext().get(Names.HTTP_REQUEST_KEY)).getMethod()); } /** * Returns true if the method associated with the current HTTP request is POST. * * @param request the current Solr request. * @return true if the method associated with the current HTTP request is POST. */ boolean isUsingPOST(final SolrQueryRequest request) { return "POST".equals(((HttpServletRequest) request.getContext().get(Names.HTTP_REQUEST_KEY)).getMethod()); } @Override public String getDescription() { return "SPARQL 1.1 Search Handler"; } @Override public String getSource() { return "https://github.com/agazzarini/SolRDF"; } /** * Returns the {@link SolrRequestHandler} associated with the given name. * * @param request the current Solr request. * @param name the {@link SolrRequestHandler} name. * @return the {@link SolrRequestHandler} associated with the given name. */ SolrRequestHandler requestHandler(final SolrQueryRequest request, final String name) { return request.getCore().getRequestHandler(name); } /** * Returns true if the current request is using POST method and URL-encoded parameters. * * @param request the current Solr request. * @return true if the current request is using POST method and URL-encoded parameters. */ boolean isUsingURLEncodedParameters(final SolrQueryRequest request) { return contentType(request).startsWith(WebContent.contentTypeHTMLForm); } /** * Returns true if the current request has a "application/sparql-update" content type. * * @param request the current Solr request. * @return true if the current request has a "application/sparql-update" content type. */ boolean isSparqlUpdateContentType(final SolrQueryRequest request) { return contentType(request).startsWith(WebContent.contentTypeSPARQLUpdate); } /** * Returns true if the current request has a "application/sparql-query" content type. * * @param request the current Solr request. * @return true if the current request has a "application/sparql-query" content type. */ boolean isSparqlQueryContentType(final SolrQueryRequest request) { return contentType(request).startsWith(WebContent.contentTypeSPARQLQuery); } /** * Returns true if the current request contains the "query" or "q" parameter. * * @param parameters the parameters associated with the current request. * @return true if the current request contains the "query" or "q" parameter. */ boolean containsQueryParameter(final SolrParams parameters) { return parameters.get(Names.QUERY) != null || parameters.get(CommonParams.Q) != null; } /** * Returns true if the current request contains the "update" parameter. * * @param parameters the parameters associated with the current request. * @return true if the current request contains the "update" parameter. */ boolean containsUpdateParameter(final SolrParams parameters) { return parameters.get(Names.UPDATE_PARAMETER_NAME) != null; } /** * Returns the content type associated with the current request. * * @param request the current request. * @return the content type associated with the current request or an empty string. */ String contentType(final SolrQueryRequest request) { final String incomingContentType = ((HttpServletRequest) request.getContext().get(Names.HTTP_REQUEST_KEY)).getContentType(); return isNotNullOrEmptyString(incomingContentType) ? incomingContentType : EMPTY_STRING; } /** * A simple wrapper around a {@link SolrQueryRequest} for marking SPARQL Queries (with POST Directly requests). * * @author Andrea Gazzarini * @since 1.0 */ static class SparqlQuerySolrQueryRequest implements SolrQueryRequest { final SolrQueryRequest request; /** * Builds a new {@link SparqlUpdateSolrQueryRequest} with the given wrapped request. * * @param request the current Solr request. */ public SparqlQuerySolrQueryRequest(final SolrQueryRequest request) { this.request = request; } @Override public void updateSchemaToLatest() { request.updateSchemaToLatest(); } @Override public void setParams(final SolrParams params) { request.setParams(params); } @Override public long getStartTime() { return request.getStartTime(); } @Override public SolrIndexSearcher getSearcher() { return request.getSearcher(); } @Override public IndexSchema getSchema() { return request.getSchema(); } @Override public SolrParams getParams() { return request.getParams(); } @Override public String getParamString() { return request.getParamString(); } @Override public SolrParams getOriginalParams() { return request.getOriginalParams(); } @Override public SolrCore getCore() { return request.getCore(); } @Override public Map<Object, Object> getContext() { return request.getContext(); } @Override public Iterable<ContentStream> getContentStreams() { return null; } @Override public void close() { request.close(); } @Override public RTimer getRequestTimer() { return request.getRequestTimer(); } @Override public Map<String, Object> getJSON() { return request.getJSON(); } @Override public void setJSON(final Map<String, Object> json) { request.setJSON(json); } @Override public Principal getUserPrincipal() { return request.getUserPrincipal(); } } /** * A simple wrapper around a {@link SolrQueryRequest} for marking SPARQL Updates (with URL encoded parameters). * * @author Andrea Gazzarini * @since 1.0 */ static class SparqlUpdateSolrQueryRequest implements SolrQueryRequest { final static List<ContentStream> DUMMY_STREAMS = new ArrayList<ContentStream>(1); static { DUMMY_STREAMS.add(new ContentStreamBase.ByteArrayStream(new byte[0], "dummy") { @Override public String getContentType() { return WebContent.contentTypeHTMLForm; } }); } final SolrQueryRequest request; /** * Builds a new {@link SparqlUpdateSolrQueryRequest} with the given wrapped request. * * @param request the current Solr request. */ public SparqlUpdateSolrQueryRequest(final SolrQueryRequest request) { this.request = request; } @Override public void updateSchemaToLatest() { request.updateSchemaToLatest(); } @Override public void setParams(final SolrParams params) { request.setParams(params); } @Override public long getStartTime() { return request.getStartTime(); } @Override public SolrIndexSearcher getSearcher() { return request.getSearcher(); } @Override public IndexSchema getSchema() { return request.getSchema(); } @Override public SolrParams getParams() { return request.getParams(); } @Override public String getParamString() { return request.getParamString(); } @Override public SolrParams getOriginalParams() { return request.getOriginalParams(); } @Override public SolrCore getCore() { return request.getCore(); } @Override public Map<Object, Object> getContext() { return request.getContext(); } @Override public Iterable<ContentStream> getContentStreams() { return DUMMY_STREAMS; } @Override public void close() { request.close(); } @Override public RTimer getRequestTimer() { return request.getRequestTimer(); } @Override public Map<String, Object> getJSON() { return request.getJSON(); } @Override public void setJSON(final Map<String, Object> json) { request.setJSON(json); } @Override public Principal getUserPrincipal() { return request.getUserPrincipal(); } } }