VIVO-731 Implement the SPARQL query API.
Create a base class for API controller servlets.
This commit is contained in:
parent
de32d53791
commit
8892da74b0
5 changed files with 217 additions and 65 deletions
|
@ -59,7 +59,7 @@ public class SimplePermission extends Permission {
|
||||||
public static final SimplePermission REFRESH_VISUALIZATION_CACHE = new SimplePermission(
|
public static final SimplePermission REFRESH_VISUALIZATION_CACHE = new SimplePermission(
|
||||||
NAMESPACE + "RefreshVisualizationCache");
|
NAMESPACE + "RefreshVisualizationCache");
|
||||||
public static final SimplePermission SEE_CONFIGURATION = new SimplePermission(
|
public static final SimplePermission SEE_CONFIGURATION = new SimplePermission(
|
||||||
NAMESPACE + "SeeConfiguration");
|
NAMESPACE + "SeeConfiguration");
|
||||||
public static final SimplePermission SEE_INDVIDUAL_EDITING_PANEL = new SimplePermission(
|
public static final SimplePermission SEE_INDVIDUAL_EDITING_PANEL = new SimplePermission(
|
||||||
NAMESPACE + "SeeIndividualEditingPanel");
|
NAMESPACE + "SeeIndividualEditingPanel");
|
||||||
public static final SimplePermission SEE_REVISION_INFO = new SimplePermission(
|
public static final SimplePermission SEE_REVISION_INFO = new SimplePermission(
|
||||||
|
@ -76,6 +76,8 @@ public class SimplePermission extends Permission {
|
||||||
NAMESPACE + "UseIndividualControlPanel");
|
NAMESPACE + "UseIndividualControlPanel");
|
||||||
public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission(
|
public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission(
|
||||||
NAMESPACE + "UseSparqlQueryPage");
|
NAMESPACE + "UseSparqlQueryPage");
|
||||||
|
public static final SimplePermission USE_SPARQL_QUERY_API = new SimplePermission(
|
||||||
|
NAMESPACE + "UseSparqlQueryApi");
|
||||||
public static final SimplePermission USE_SPARQL_UPDATE_API = new SimplePermission(
|
public static final SimplePermission USE_SPARQL_UPDATE_API = new SimplePermission(
|
||||||
NAMESPACE + "UseSparqlUpdateApi");
|
NAMESPACE + "UseSparqlUpdateApi");
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.controller.api;
|
||||||
|
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||||
|
import static javax.servlet.http.HttpServletResponse.SC_NOT_ACCEPTABLE;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.hp.hpl.jena.query.QueryParseException;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.InvalidQueryTypeException;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.api.sparqlquery.SparqlQueryApiExecutor;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process SPARQL queries as an API.
|
||||||
|
*
|
||||||
|
* Supports GET or POST requests. May produce these responses:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 200 Success
|
||||||
|
* 400 Failed to parse SPARQL query
|
||||||
|
* 400 SPARQL query type is not SELECT, ASK, CONSTRUCT, or DESCRIBE.
|
||||||
|
* 403 username/password combination is not valid
|
||||||
|
* 403 Account is not authorized
|
||||||
|
* 406 Accept header does not include any available result formats
|
||||||
|
* 500 Unknown error
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
public class SparqlQueryApiController extends VitroApiServlet {
|
||||||
|
|
||||||
|
private static final Actions REQUIRED_ACTIONS = SimplePermission.USE_SPARQL_QUERY_API.ACTIONS;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
doPost(req, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
RDFService rdfService = RDFServiceUtils.getRDFServiceFactory(
|
||||||
|
getServletContext()).getRDFService();
|
||||||
|
|
||||||
|
String acceptHeader = req.getHeader("Accept");
|
||||||
|
String queryString = req.getParameter("query");
|
||||||
|
try {
|
||||||
|
confirmAuthorization(req, REQUIRED_ACTIONS);
|
||||||
|
confirmQueryIsPresent(queryString);
|
||||||
|
|
||||||
|
SparqlQueryApiExecutor core = SparqlQueryApiExecutor.instance(
|
||||||
|
rdfService, queryString, acceptHeader);
|
||||||
|
resp.setContentType(core.getMediaType());
|
||||||
|
core.executeAndFormat(resp.getOutputStream());
|
||||||
|
} catch (AuthException e) {
|
||||||
|
sendShortResponse(SC_FORBIDDEN, e.getMessage(), resp);
|
||||||
|
} catch (BadParameterException e) {
|
||||||
|
sendShortResponse(SC_BAD_REQUEST, e.getMessage(), resp);
|
||||||
|
} catch (InvalidQueryTypeException e) {
|
||||||
|
sendShortResponse(SC_BAD_REQUEST,
|
||||||
|
"Query type is not SELECT, ASK, CONSTRUCT, "
|
||||||
|
+ "or DESCRIBE: '" + queryString + "'", resp);
|
||||||
|
} catch (QueryParseException e) {
|
||||||
|
sendShortResponse(SC_BAD_REQUEST, "Failed to parse query: '"
|
||||||
|
+ queryString + "'", e, resp);
|
||||||
|
} catch (NotAcceptableException | AcceptHeaderParsingException e) {
|
||||||
|
sendShortResponse(SC_NOT_ACCEPTABLE,
|
||||||
|
"The accept header does not include any "
|
||||||
|
+ "available content type.", e, resp);
|
||||||
|
} catch (RDFServiceException e) {
|
||||||
|
sendShortResponse(SC_INTERNAL_SERVER_ERROR,
|
||||||
|
"Problem executing the query.", e, resp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendShortResponse(SC_INTERNAL_SERVER_ERROR, "Unrecognized error.",
|
||||||
|
e, resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void confirmQueryIsPresent(String queryString)
|
||||||
|
throws BadParameterException {
|
||||||
|
if (queryString == null) {
|
||||||
|
throw new BadParameterException("Query string was not supplied.");
|
||||||
|
}
|
||||||
|
if (queryString.trim().isEmpty()) {
|
||||||
|
throw new BadParameterException("Query string is empty.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -8,11 +8,9 @@ import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
|
||||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
|
||||||
|
|
||||||
import javax.servlet.ServletContext;
|
import javax.servlet.ServletContext;
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@ -28,24 +26,15 @@ import com.hp.hpl.jena.update.UpdateFactory;
|
||||||
import com.hp.hpl.jena.update.UpdateRequest;
|
import com.hp.hpl.jena.update.UpdateRequest;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
|
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
|
||||||
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
|
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset;
|
import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset;
|
||||||
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
|
import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This extends HttpServlet instead of VitroHttpServlet because we want to have
|
* Process SPARQL Updates, as an API.
|
||||||
* full control over the response:
|
|
||||||
* <ul>
|
|
||||||
* <li>No redirecting to the login page if not authorized</li>
|
|
||||||
* <li>No redirecting to the home page on insufficient authorization</li>
|
|
||||||
* <li>No support for GET or HEAD requests, only POST.</li>
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* So these responses will be produced:
|
* Supports only POST requests, not GET or HEAD. May produce these responses:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
* 200 Success
|
* 200 Success
|
||||||
|
@ -57,7 +46,7 @@ import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder;
|
||||||
* 500 Unknown error
|
* 500 Unknown error
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
public class SparqlUpdateApiController extends HttpServlet {
|
public class SparqlUpdateApiController extends VitroApiServlet {
|
||||||
private static final Log log = LogFactory
|
private static final Log log = LogFactory
|
||||||
.getLog(SparqlUpdateApiController.class);
|
.getLog(SparqlUpdateApiController.class);
|
||||||
|
|
||||||
|
@ -67,7 +56,7 @@ public class SparqlUpdateApiController extends HttpServlet {
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
try {
|
try {
|
||||||
checkAuthorization(req);
|
confirmAuthorization(req, REQUIRED_ACTIONS);
|
||||||
UpdateRequest parsed = parseUpdateString(req);
|
UpdateRequest parsed = parseUpdateString(req);
|
||||||
executeUpdate(req, parsed);
|
executeUpdate(req, parsed);
|
||||||
do200response(resp);
|
do200response(resp);
|
||||||
|
@ -80,27 +69,6 @@ public class SparqlUpdateApiController extends HttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAuthorization(HttpServletRequest req)
|
|
||||||
throws AuthException {
|
|
||||||
String email = req.getParameter("email");
|
|
||||||
String password = req.getParameter("password");
|
|
||||||
|
|
||||||
Authenticator auth = Authenticator.getInstance(req);
|
|
||||||
UserAccount account = auth.getAccountForInternalAuth(email);
|
|
||||||
if (!auth.isCurrentPassword(account, password)) {
|
|
||||||
log.debug("Invalid: '" + email + "'/'" + password + "'");
|
|
||||||
throw new AuthException("email/password combination is not valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PolicyHelper.isAuthorizedForActions(req, email, password,
|
|
||||||
REQUIRED_ACTIONS)) {
|
|
||||||
log.debug("Not authorized: '" + email + "'");
|
|
||||||
throw new AuthException("Account is not authorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Authorized for '" + email + "'");
|
|
||||||
}
|
|
||||||
|
|
||||||
private UpdateRequest parseUpdateString(HttpServletRequest req)
|
private UpdateRequest parseUpdateString(HttpServletRequest req)
|
||||||
throws ParseException {
|
throws ParseException {
|
||||||
String update = req.getParameter("update");
|
String update = req.getParameter("update");
|
||||||
|
@ -139,55 +107,32 @@ public class SparqlUpdateApiController extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void do200response(HttpServletResponse resp) throws IOException {
|
private void do200response(HttpServletResponse resp) throws IOException {
|
||||||
doResponse(resp, SC_OK, "SPARQL update accepted.");
|
sendShortResponse(SC_OK, "SPARQL update accepted.", resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void do403response(HttpServletResponse resp, AuthException e)
|
private void do403response(HttpServletResponse resp, AuthException e)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
doResponse(resp, SC_FORBIDDEN, e.getMessage());
|
sendShortResponse( SC_FORBIDDEN, e.getMessage(), resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void do400response(HttpServletResponse resp, ParseException e)
|
private void do400response(HttpServletResponse resp, ParseException e)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (e.getCause() == null) {
|
if (e.getCause() == null) {
|
||||||
doResponse(resp, SC_BAD_REQUEST, e.getMessage());
|
sendShortResponse( SC_BAD_REQUEST, e.getMessage(), resp);
|
||||||
} else {
|
} else {
|
||||||
doResponse(resp, SC_BAD_REQUEST, e.getMessage(), e.getCause());
|
sendShortResponse( SC_BAD_REQUEST, e.getMessage(), e.getCause(), resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void do500response(HttpServletResponse resp, Exception e)
|
private void do500response(HttpServletResponse resp, Exception e)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
doResponse(resp, SC_INTERNAL_SERVER_ERROR, "Unknown error", e);
|
sendShortResponse(SC_INTERNAL_SERVER_ERROR, "Unknown error", e, resp);
|
||||||
}
|
|
||||||
|
|
||||||
private void doResponse(HttpServletResponse resp, int statusCode,
|
|
||||||
String message) throws IOException {
|
|
||||||
resp.setStatus(statusCode);
|
|
||||||
PrintWriter writer = resp.getWriter();
|
|
||||||
writer.println("<H1>" + statusCode + " " + message + "</H1>");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void doResponse(HttpServletResponse resp, int statusCode,
|
|
||||||
String message, Throwable e) throws IOException {
|
|
||||||
resp.setStatus(statusCode);
|
|
||||||
PrintWriter writer = resp.getWriter();
|
|
||||||
writer.println("<H1>" + statusCode + " " + message + "</H1>");
|
|
||||||
writer.println("<pre>");
|
|
||||||
e.printStackTrace(writer);
|
|
||||||
writer.println("</pre>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// Helper classes
|
// Helper classes
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
private static class AuthException extends Exception {
|
|
||||||
private AuthException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ParseException extends Exception {
|
private static class ParseException extends Exception {
|
||||||
private ParseException(String message) {
|
private ParseException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.controller.api;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for Vitro servlets that implement the API.
|
||||||
|
*
|
||||||
|
* We don't want the API servlets to extend VitroHttpServlet, because we want
|
||||||
|
* the following behavior:
|
||||||
|
* <ul>
|
||||||
|
* <li>No redirecting to the login page if not authorized</li>
|
||||||
|
* <li>No redirecting to the home page on insufficient authorization</li>
|
||||||
|
* <li>GET and POST requests are not necessarily equivalent.</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public class VitroApiServlet extends HttpServlet {
|
||||||
|
private static final Log log = LogFactory.getLog(VitroApiServlet.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If they have not provided an email/password combo that will authorize
|
||||||
|
* them for this action, throw an AuthException.
|
||||||
|
*/
|
||||||
|
protected void confirmAuthorization(HttpServletRequest req,
|
||||||
|
Actions requiredActions) throws AuthException {
|
||||||
|
String email = req.getParameter("email");
|
||||||
|
String password = req.getParameter("password");
|
||||||
|
|
||||||
|
Authenticator auth = Authenticator.getInstance(req);
|
||||||
|
UserAccount account = auth.getAccountForInternalAuth(email);
|
||||||
|
if (!auth.isCurrentPassword(account, password)) {
|
||||||
|
log.debug("Invalid: '" + email + "'/'" + password + "'");
|
||||||
|
throw new AuthException("email/password combination is not valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PolicyHelper.isAuthorizedForActions(req, email, password,
|
||||||
|
requiredActions)) {
|
||||||
|
log.debug("Not authorized: '" + email + "'");
|
||||||
|
throw new AuthException("Account is not authorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("Authorized for '" + email + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendShortResponse(int statusCode, String message,
|
||||||
|
HttpServletResponse resp) throws IOException {
|
||||||
|
resp.setStatus(statusCode);
|
||||||
|
PrintWriter writer = resp.getWriter();
|
||||||
|
writer.println("<H1>" + statusCode + " " + message + "</H1>");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendShortResponse(int statusCode, String message, Throwable e,
|
||||||
|
HttpServletResponse resp) throws IOException {
|
||||||
|
sendShortResponse(statusCode, message, resp);
|
||||||
|
PrintWriter writer = resp.getWriter();
|
||||||
|
writer.println("<pre>");
|
||||||
|
e.printStackTrace(writer);
|
||||||
|
writer.println("</pre>");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// Helper classes
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
protected static class AuthException extends Exception {
|
||||||
|
protected AuthException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class BadParameterException extends Exception {
|
||||||
|
protected BadParameterException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1034,6 +1034,16 @@
|
||||||
<url-pattern>/admin/sparqlquery</url-pattern>
|
<url-pattern>/admin/sparqlquery</url-pattern>
|
||||||
</servlet-mapping>
|
</servlet-mapping>
|
||||||
|
|
||||||
|
<servlet>
|
||||||
|
<servlet-name>SparqlQueryApi</servlet-name>
|
||||||
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.api.SparqlQueryApiController</servlet-class>
|
||||||
|
</servlet>
|
||||||
|
|
||||||
|
<servlet-mapping>
|
||||||
|
<servlet-name>SparqlQueryApi</servlet-name>
|
||||||
|
<url-pattern>/api/sparqlQuery</url-pattern>
|
||||||
|
</servlet-mapping>
|
||||||
|
|
||||||
<servlet>
|
<servlet>
|
||||||
<servlet-name>SparqlUpdateApi</servlet-name>
|
<servlet-name>SparqlUpdateApi</servlet-name>
|
||||||
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.api.SparqlUpdateApiController</servlet-class>
|
<servlet-class>edu.cornell.mannlib.vitro.webapp.controller.api.SparqlUpdateApiController</servlet-class>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue