From 10d692d4113861b7972fb039c106c134eaf189e6 Mon Sep 17 00:00:00 2001 From: Jim Blake Date: Tue, 6 May 2014 15:09:27 -0400 Subject: [PATCH] VIVO-734 implement listrdf by delegating to SparqlQueryApiExecutor. It formerly retrieved the data from Solr. --- .../IndividualListRdfController.java | 174 +++++++++++------- .../controller/api/VitroApiServlet.java | 25 ++- .../api/sparqlquery/RdfResultMediaType.java | 9 +- .../webapp/utils/http/ContentTypeUtil.java | 5 +- 4 files changed, 139 insertions(+), 74 deletions(-) diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/IndividualListRdfController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/IndividualListRdfController.java index d9433f7a9..3cd56ce9d 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/IndividualListRdfController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/IndividualListRdfController.java @@ -1,6 +1,14 @@ /* $This file is distributed under the terms of the license in /doc/license.txt$ */ package edu.cornell.mannlib.vitro.webapp.controller; + +import static edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.RdfResultMediaType.RDF_XML; +import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.RDF_TYPE; +import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_NOT_ACCEPTABLE; + import java.io.IOException; +import java.util.Collection; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -9,76 +17,118 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.hp.hpl.jena.query.QueryParseException; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.ModelFactory; -import com.hp.hpl.jena.rdf.model.RDFNode; -import com.hp.hpl.jena.rdf.model.Resource; -import com.hp.hpl.jena.rdf.model.ResourceFactory; -import com.hp.hpl.jena.vocabulary.RDF; -import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils; -import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngine; -import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery; -import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse; -import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocument; -import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocumentList; -import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames; +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.publish.PublishObjectPropertyStatement; +import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty; +import edu.cornell.mannlib.vitro.webapp.controller.api.VitroApiServlet; +import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.InvalidQueryTypeException; +import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.RdfResultMediaType; +import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.SparqlQueryApiExecutor; +import edu.cornell.mannlib.vitro.webapp.dao.ModelAccess; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService; +import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException; +import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils; +import edu.cornell.mannlib.vitro.webapp.utils.http.AcceptHeaderParsingException; +import edu.cornell.mannlib.vitro.webapp.utils.http.NotAcceptableException; -public class IndividualListRdfController extends VitroHttpServlet { +public class IndividualListRdfController extends VitroApiServlet { + private static final Log log = LogFactory + .getLog(IndividualListRdfController.class); - private static final long serialVersionUID = 1L; - private static final Log log = LogFactory.getLog(IndividualListRdfController.class.getName()); - - public static final int ENTITY_LIST_CONTROLLER_MAX_RESULTS = 30000; - - @Override - public void doGet (HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { - SearchEngine search = ApplicationUtils.instance().getSearchEngine(); - - // Make the query - String vclassUri = req.getParameter("vclass"); - String queryStr = VitroSearchTermNames.RDFTYPE + ":\"" + vclassUri + "\""; - SearchQuery query = search.createQuery(queryStr); - query.setStart(0) - .setRows(ENTITY_LIST_CONTROLLER_MAX_RESULTS) - .addFields(VitroSearchTermNames.URI); - // For now, we're only displaying the url, so no need to sort. - //.addSortField(VitroSearchTermNames.NAME_LOWERCASE_SINGLE_VALUED); + private static final String QUERY_TEMPLATE = "CONSTRUCT { ?s a ?vclass . } WHERE { ?s a ?vclass . }"; - // Execute the query - SearchResponse response = null; - - try { - response = search.query(query); - } catch (Throwable t) { - log.error(t, t); - } + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + try { + String vclassUri = getVClassParameter(req); + RdfResultMediaType mediaType = parseAcceptHeader(req); + log.debug("Requested class is '" + vclassUri + "', media type is " + + mediaType); - if ( response == null ) { - throw new ServletException("Could not run search in IndividualListRdfController"); - } + if (isVclassRestricted(vclassUri, req)) { + sendEmptyModel(mediaType, resp); + } else { + executeQuery(buildQuery(vclassUri), mediaType.getContentType(), + getRdfService(req), resp); + } + } catch (BadParameterException e) { + sendShortResponse(SC_BAD_REQUEST, e.getMessage(), resp); + } catch (NotAcceptableException | AcceptHeaderParsingException e) { + sendShortResponse(SC_NOT_ACCEPTABLE, + "The accept header does not include any " + + "available content type.", e, resp); + } catch (Exception e) { + sendShortResponse(SC_INTERNAL_SERVER_ERROR, + "Failed to obtain the list.", e, resp); + } + } - SearchResultDocumentList docs = response.getResults(); - - if (docs == null) { - throw new ServletException("Could not run search in IndividualListRdfController"); - } + private String getVClassParameter(HttpServletRequest req) + throws BadParameterException { + String vclass = req.getParameter("vclass"); + if (vclass == null) { + throw new BadParameterException( + "vclass parameter was not supplied."); + } + if (vclass.trim().isEmpty()) { + throw new BadParameterException("vclass parameter is empty."); + } + return vclass; + } - Model model = ModelFactory.createDefaultModel(); - for (SearchResultDocument doc : docs) { - String uri = doc.getStringValue(VitroSearchTermNames.URI); - Resource resource = ResourceFactory.createResource(uri); - RDFNode node = ResourceFactory.createResource(vclassUri); - model.add(resource, RDF.type, node); - } + private RdfResultMediaType parseAcceptHeader(HttpServletRequest req) + throws NotAcceptableException, AcceptHeaderParsingException { + String defaultType = RDF_XML.getContentType(); + Collection availableTypes = RdfResultMediaType.contentTypes(); + String contentType = parseAcceptHeader(req, availableTypes, defaultType); + return RdfResultMediaType.fromContentType(contentType); + } + + private boolean isVclassRestricted(String vclassUri, HttpServletRequest req) { + ObjectProperty property = new ObjectProperty(); + property.setURI(RDF_TYPE); + RequestedAction dops = new PublishObjectPropertyStatement(ModelAccess + .on(req).getBaseOntModel(), RequestedAction.SOME_URI, property, + vclassUri); + return !PolicyHelper.isAuthorizedForActions(req, dops); + } + + private void sendEmptyModel(RdfResultMediaType mediaType, + HttpServletResponse resp) throws IOException { + resp.setContentType(mediaType.getContentType()); + Model m = ModelFactory.createDefaultModel(); + m.write(resp.getOutputStream(), mediaType.getJenaResponseFormat()); + } + + private String buildQuery(String vclassUri) { + return QUERY_TEMPLATE.replace("?vclass", "<" + vclassUri + ">"); + } + + private RDFService getRdfService(HttpServletRequest req) { + return RDFServiceUtils.getRDFService(new VitroRequest(req)); + } + + private void executeQuery(String query, String contentType, + RDFService rdfService, HttpServletResponse resp) + throws QueryParseException, NotAcceptableException, + InvalidQueryTypeException, AcceptHeaderParsingException, + RDFServiceException, IOException { + SparqlQueryApiExecutor executor = SparqlQueryApiExecutor.instance( + rdfService, query, contentType); + resp.setContentType(executor.getMediaType()); + executor.executeAndFormat(resp.getOutputStream()); + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse res) + throws IOException, ServletException { + doGet(req, res); + } - res.setContentType(RDFXML_MIMETYPE); - model.write(res.getOutputStream(), "RDF/XML"); - } - - @Override - public void doPost (HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException{ - doGet(req,res); - } - } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/VitroApiServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/VitroApiServlet.java index 44e96830b..3731040f1 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/VitroApiServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/VitroApiServlet.java @@ -4,6 +4,7 @@ package edu.cornell.mannlib.vitro.webapp.controller.api; import java.io.IOException; import java.io.PrintWriter; +import java.util.Collection; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -16,6 +17,9 @@ import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator; +import edu.cornell.mannlib.vitro.webapp.utils.http.AcceptHeaderParsingException; +import edu.cornell.mannlib.vitro.webapp.utils.http.ContentTypeUtil; +import edu.cornell.mannlib.vitro.webapp.utils.http.NotAcceptableException; /** * The base class for Vitro servlets that implement the API. @@ -56,6 +60,17 @@ public class VitroApiServlet extends HttpServlet { log.debug("Authorized for '" + email + "'"); } + protected String parseAcceptHeader(HttpServletRequest req, + Collection availableTypes, String defaultType) + throws AcceptHeaderParsingException, NotAcceptableException { + String acceptHeader = req.getHeader("Accept"); + if (acceptHeader == null) { + return defaultType; + } + acceptHeader += "," + defaultType + ";q=0.1"; + return ContentTypeUtil.bestContentType(acceptHeader, availableTypes); + } + protected void sendShortResponse(int statusCode, String message, HttpServletResponse resp) throws IOException { resp.setStatus(statusCode); @@ -63,8 +78,8 @@ public class VitroApiServlet extends HttpServlet { writer.println("

" + statusCode + " " + message + "

"); } - protected void sendShortResponse(int statusCode, String message, Throwable e, - HttpServletResponse resp) throws IOException { + protected void sendShortResponse(int statusCode, String message, + Throwable e, HttpServletResponse resp) throws IOException { sendShortResponse(statusCode, message, resp); PrintWriter writer = resp.getWriter(); writer.println("
");
@@ -77,15 +92,15 @@ public class VitroApiServlet extends HttpServlet {
 	// ----------------------------------------------------------------------
 
 	protected static class AuthException extends Exception {
-		protected AuthException(String message) {
+		public AuthException(String message) {
 			super(message);
 		}
 	}
 
 	protected static class BadParameterException extends Exception {
-		protected BadParameterException(String message) {
+		public BadParameterException(String message) {
 			super(message);
 		}
 	}
-	
+
 }
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/sparqlquery/RdfResultMediaType.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/sparqlquery/RdfResultMediaType.java
index 85839bbf5..f2f98b76d 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/sparqlquery/RdfResultMediaType.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/sparqlquery/RdfResultMediaType.java
@@ -12,11 +12,11 @@ import java.util.Map;
  * and DESCRIBE).
  */
 public enum RdfResultMediaType {
-	TEXT("text/plain", true, "NTRIPLE", null),
+	TEXT("text/plain", true, "NTRIPLE", "N-TRIPLE"),
 
-	RDF_XML("application/rdf+xml", true, "RDFXML", null),
+	RDF_XML("application/rdf+xml", true, "RDFXML", "RDF/XML"),
 
-	N3("text/n3", true, "N3", null),
+	N3("text/n3", true, "N3", "N3"),
 
 	TTL("text/turtle", false, "N3", "TTL"),
 
@@ -73,8 +73,7 @@ public enum RdfResultMediaType {
 	private final String serializationFormat;
 
 	/**
-	 * What format shall we ask the resulting OntModel to write? (Applies only
-	 * to non-native formats)
+	 * What format shall we ask the resulting OntModel to write? 
 	 */
 	private final String jenaResponseFormat;
 
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/http/ContentTypeUtil.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/http/ContentTypeUtil.java
index 7292c5524..99518911f 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/http/ContentTypeUtil.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/http/ContentTypeUtil.java
@@ -62,8 +62,9 @@ public class ContentTypeUtil {
 
 		if (bestMatch == null) {
 			throw new NotAcceptableException(
-					"No available type matches the Accept header: "
-							+ acceptHeader);
+					"No available type matches the Accept header: '"
+							+ acceptHeader + "'; available types are "
+							+ availableTypeNames);
 		} else {
 			return bestMatch.getName();
 		}