diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl index c6e9a3523..a84b13707 100644 --- a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl @@ -16,7 +16,7 @@ ACTIVE 1 0 - + @@ -28,7 +28,7 @@ ACTIVE 1 0 - + @@ -40,7 +40,7 @@ ACTIVE 1 0 - + @@ -52,7 +52,7 @@ ACTIVE 1 0 - + diff --git a/webapp/rdf/auth/everytime/permission_config.n3 b/webapp/rdf/auth/everytime/permission_config.n3 index c4a88018a..e0b1c1960 100644 --- a/webapp/rdf/auth/everytime/permission_config.n3 +++ b/webapp/rdf/auth/everytime/permission_config.n3 @@ -12,6 +12,7 @@ auth:ADMIN # ADMIN-only permissions auth:hasPermission simplePermission:AccessSpecialDataModels ; + auth:hasPermission simplePermission:EnableDeveloperPanel ; auth:hasPermission simplePermission:LoginDuringMaintenance ; auth:hasPermission simplePermission:ManageMenus ; auth:hasPermission simplePermission:ManageProxies ; @@ -23,8 +24,12 @@ auth:ADMIN auth:hasPermission simplePermission:UseAdvancedDataToolsPages ; auth:hasPermission simplePermission:UseMiscellaneousAdminPages ; auth:hasPermission simplePermission:UseSparqlQueryPage ; - auth:hasPermission simplePermission:PageViewableAdmin ; - auth:hasPermission simplePermission:EnableDeveloperPanel ; + auth:hasPermission simplePermission:PageViewableAdmin ; + + # Uncomment the following permission line to enable the SPARQL update API. + # Before enabling, be sure that the URL api/sparqlUpdate is secured by SSH, + # so passwords will not be sent in clear text. +# auth:hasPermission simplePermission:UseSparqlUpdateApi ; # permissions for CURATOR and above. auth:hasPermission simplePermission:EditOntology ; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java index 33c609c5b..0484638bd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java @@ -38,6 +38,8 @@ public class SimplePermission extends Permission { NAMESPACE + "EditOwnAccount"); public static final SimplePermission EDIT_SITE_INFORMATION = new SimplePermission( NAMESPACE + "EditSiteInformation"); + public static final SimplePermission ENABLE_DEVELOPER_PANEL = new SimplePermission( + NAMESPACE + "EnableDeveloperPanel"); public static final SimplePermission LOGIN_DURING_MAINTENANCE = new SimplePermission( NAMESPACE + "LoginDuringMaintenance"); public static final SimplePermission MANAGE_MENUS = new SimplePermission( @@ -76,9 +78,8 @@ public class SimplePermission extends Permission { NAMESPACE + "UseAdvancedDataToolsPages"); public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission( NAMESPACE + "UseSparqlQueryPage"); - public static final SimplePermission ENABLE_DEVELOPER_PANEL = new SimplePermission( - NAMESPACE + "EnableDeveloperPanel"); - + public static final SimplePermission USE_SPARQL_UPDATE_API = new SimplePermission( + NAMESPACE + "UseSparqlUpdateApi"); // ---------------------------------------------------------------------- // These instances are "catch all" permissions to cover poorly defined diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java index d8a606efb..b1250a855 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java @@ -83,11 +83,11 @@ public class PolicyHelper { } try{ - Authenticator basicAuth = new BasicAuthenticator(req); - UserAccount user = basicAuth.getAccountForInternalAuth( email ); + Authenticator auth = Authenticator.getInstance(req); + UserAccount user = auth.getAccountForInternalAuth( email ); log.debug("userAccount is " + user==null?"null":user.getUri() ); - if( ! basicAuth.isCurrentPassword( user, password ) ){ + if( ! auth.isCurrentPassword( user, password ) ){ log.debug(String.format("UNAUTHORIZED, password not accepted for %s, account URI: %s", user.getEmailAddress(), user.getUri())); return false; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiController.java new file mode 100644 index 000000000..60a0e6af0 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiController.java @@ -0,0 +1,201 @@ +/* $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_OK; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.update.GraphStore; +import com.hp.hpl.jena.update.GraphStoreFactory; +import com.hp.hpl.jena.update.UpdateAction; +import com.hp.hpl.jena.update.UpdateFactory; +import com.hp.hpl.jena.update.UpdateRequest; + +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.beans.UserAccount; +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.search.indexing.IndexBuilder; + +/** + * This extends HttpServlet instead of VitroHttpServlet because we want to have + * full control over the response: + * + * + * So these responses will be produced: + * + *
+ * 200 Success
+ * 400 Failed to parse SPARQL update
+ * 400 SPARQL update must specify a GRAPH URI.
+ * 403 username/password combination is not valid
+ * 403 Account is not authorized
+ * 405 Method not allowed
+ * 500 Unknown error
+ * 
+ */ +public class SparqlUpdateApiController extends HttpServlet { + private static final Log log = LogFactory + .getLog(SparqlUpdateApiController.class); + + private static final Actions REQUIRED_ACTIONS = SimplePermission.USE_SPARQL_UPDATE_API.ACTIONS; + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + try { + checkAuthorization(req); + UpdateRequest parsed = parseUpdateString(req); + executeUpdate(req, parsed); + do200response(resp); + } catch (AuthException e) { + do403response(resp, e); + } catch (ParseException e) { + do400response(resp, e); + } catch (Exception e) { + do500response(resp, e); + } + } + + 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) + throws ParseException { + String update = req.getParameter("update"); + if (StringUtils.isBlank(update)) { + log.debug("No update parameter."); + throw new ParseException("No 'update' parameter."); + } + + if (!StringUtils.containsIgnoreCase(update, "GRAPH")) { + if (log.isDebugEnabled()) { + log.debug("No GRAPH uri in '" + update + "'"); + } + throw new ParseException("SPARQL update must specify a GRAPH URI."); + } + + try { + return UpdateFactory.create(update); + } catch (Exception e) { + log.debug("Problem parsing", e); + throw new ParseException("Failed to parse SPARQL update", e); + } + } + + private void executeUpdate(HttpServletRequest req, UpdateRequest parsed) { + ServletContext ctx = req.getSession().getServletContext(); + VitroRequest vreq = new VitroRequest(req); + + IndexBuilder.getBuilder(ctx).pause(); + try { + Dataset ds = new RDFServiceDataset(vreq.getUnfilteredRDFService()); + GraphStore graphStore = GraphStoreFactory.create(ds); + UpdateAction.execute(parsed, graphStore); + } finally { + IndexBuilder.getBuilder(ctx).unpause(); + } + } + + private void do200response(HttpServletResponse resp) throws IOException { + doResponse(resp, SC_OK, "SPARQL update accepted."); + } + + private void do403response(HttpServletResponse resp, AuthException e) + throws IOException { + doResponse(resp, SC_FORBIDDEN, e.getMessage()); + } + + private void do400response(HttpServletResponse resp, ParseException e) + throws IOException { + if (e.getCause() == null) { + doResponse(resp, SC_BAD_REQUEST, e.getMessage()); + } else { + doResponse(resp, SC_BAD_REQUEST, e.getMessage(), e.getCause()); + } + } + + private void do500response(HttpServletResponse resp, Exception e) + throws IOException { + doResponse(resp, SC_INTERNAL_SERVER_ERROR, "Unknown error", e); + } + + private void doResponse(HttpServletResponse resp, int statusCode, + String message) throws IOException { + resp.setStatus(statusCode); + PrintWriter writer = resp.getWriter(); + writer.println("

" + statusCode + " " + message + "

"); + } + + private void doResponse(HttpServletResponse resp, int statusCode, + String message, Throwable e) throws IOException { + resp.setStatus(statusCode); + PrintWriter writer = resp.getWriter(); + writer.println("

" + statusCode + " " + message + "

"); + writer.println("
");
+		e.printStackTrace(writer);
+		writer.println("
"); + } + + // ---------------------------------------------------------------------- + // Helper classes + // ---------------------------------------------------------------------- + + private static class AuthException extends Exception { + private AuthException(String message) { + super(message); + } + } + + private static class ParseException extends Exception { + private ParseException(String message) { + super(message); + } + + private ParseException(String message, Throwable cause) { + super(message, cause); + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ExternalIdRetryController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ExternalIdRetryController.java deleted file mode 100644 index 51509e580..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/ExternalIdRetryController.java +++ /dev/null @@ -1,120 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.controller.edit; - -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; - -import javax.servlet.RequestDispatcher; -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.vedit.beans.EditProcessObject; -import edu.cornell.mannlib.vedit.beans.FormObject; -import edu.cornell.mannlib.vedit.beans.Option; -import edu.cornell.mannlib.vedit.controller.BaseEditController; -import edu.cornell.mannlib.vedit.util.FormUtils; -import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; -import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement; -import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatementImpl; -import edu.cornell.mannlib.vitro.webapp.beans.Individual; -import edu.cornell.mannlib.vitro.webapp.controller.Controllers; -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyDao; -import edu.cornell.mannlib.vitro.webapp.dao.DataPropertyStatementDao; -import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; - -public class ExternalIdRetryController extends BaseEditController { - - private static final Log log = LogFactory.getLog(ExternalIdRetryController.class.getName()); - - public void doPost (HttpServletRequest request, HttpServletResponse response) { - if (!isAuthorizedToDisplayPage(request, response, - SimplePermission.DO_BACK_END_EDITING.ACTIONS)) { - return; - } - - VitroRequest vreq = new VitroRequest(request); - - //create an EditProcessObject for this and put it in the session - EditProcessObject epo = super.createEpo(request); - - String action = "insert"; - - DataPropertyDao dpDao = vreq.getUnfilteredWebappDaoFactory().getDataPropertyDao(); - DataPropertyStatementDao edDao = vreq.getUnfilteredWebappDaoFactory().getDataPropertyStatementDao(); - epo.setDataAccessObject(edDao); - epo.setBeanClass(DataPropertyStatement.class); - - IndividualDao eDao = vreq.getUnfilteredWebappDaoFactory().getIndividualDao(); - - DataPropertyStatement eidForEditing = null; - if (!epo.getUseRecycledBean()){ - eidForEditing = new DataPropertyStatementImpl(); - populateBeanFromParams(eidForEditing,vreq); - if (vreq.getParameter(MULTIPLEXED_PARAMETER_NAME) != null) { - action = "update"; - } - epo.setOriginalBean(eidForEditing); - } else { - eidForEditing = (DataPropertyStatement) epo.getNewBean(); - } - - //make a simple mask for the class's id - Object[] simpleMaskPair = new Object[2]; - simpleMaskPair[0]="Id"; - //simpleMaskPair[1]=Integer.valueOf(eidForEditing.getId()); - epo.getSimpleMask().add(simpleMaskPair); - - //set up any listeners - - FormObject foo = new FormObject(); - HashMap OptionMap = new HashMap(); - List entityList = new LinkedList(); - if (eidForEditing.getIndividualURI() != null) { - Individual individual = eDao.getIndividualByURI(eidForEditing.getIndividualURI()); - entityList.add(new Option(individual.getURI(),individual.getName(),true)); - } else { - entityList.add(new Option ("-1", "Error: the entity must be specified", true)); - } - OptionMap.put("IndividualURI",entityList); - // TOOD change following DAO call to getAllExternalIdDataProperties once implemented - List allExternalIdDataProperties = dpDao.getAllExternalIdDataProperties(); - Collections.sort(allExternalIdDataProperties); - OptionMap.put("DatapropURI", FormUtils.makeOptionListFromBeans(allExternalIdDataProperties, "URI", "PublicName", eidForEditing.getDatapropURI(),"")); - foo.setOptionLists(OptionMap); - foo.setErrorMap(epo.getErrMsgMap()); - - epo.setFormObject(foo); - - FormUtils.populateFormFromBean(eidForEditing,action,foo,epo.getBadValueMap()); - - RequestDispatcher rd = request.getRequestDispatcher(Controllers.BASIC_JSP); - request.setAttribute("bodyJsp","/templates/edit/formBasic.jsp"); - request.setAttribute("formJsp","/templates/edit/specific/externalIds_retry.jsp"); - request.setAttribute("scripts","/templates/edit/formBasic.js"); - request.setAttribute("title","External Id Editing Form"); - request.setAttribute("_action",action); - request.setAttribute("unqualifiedClassName","External Id"); - setRequestAttributes(request,epo); - - try { - rd.forward(request, response); - } catch (Exception e) { - log.error("ExternalIdRetryController could not forward to view."); - log.error(e.getMessage()); - log.error(e.getStackTrace()); - } - - } - - public void doGet (HttpServletRequest request, HttpServletResponse response) { - doPost(request, response); - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java index 83ac3b051..69960f546 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassEditController.java @@ -143,24 +143,25 @@ public class VclassEditController extends BaseEditController { // if supported, we want to show only the asserted superclasses and subclasses. VClassDao vcDao = ModelAccess.on(getServletContext()).getBaseWebappDaoFactory().getVClassDao(); + VClassDao displayVcDao = ModelAccess.on(getServletContext()).getWebappDaoFactory().getVClassDao(); List superVClasses = getVClassesForURIList( - vcDao.getSuperClassURIs(vcl.getURI(),false), vcDao); + vcDao.getSuperClassURIs(vcl.getURI(),false), displayVcDao); sortForPickList(superVClasses, request); request.setAttribute("superclasses",superVClasses); List subVClasses = getVClassesForURIList( - vcDao.getSubClassURIs(vcl.getURI()), vcDao); + vcDao.getSubClassURIs(vcl.getURI()), displayVcDao); sortForPickList(subVClasses, request); request.setAttribute("subclasses",subVClasses); List djVClasses = getVClassesForURIList( - vcDao.getDisjointWithClassURIs(vcl.getURI()), vcDao); + vcDao.getDisjointWithClassURIs(vcl.getURI()), displayVcDao); sortForPickList(djVClasses, request); request.setAttribute("disjointClasses",djVClasses); List eqVClasses = getVClassesForURIList( - vcDao.getEquivalentClassURIs(vcl.getURI()), vcDao); + vcDao.getEquivalentClassURIs(vcl.getURI()), displayVcDao); sortForPickList(eqVClasses, request); request.setAttribute("equivalentClasses",eqVClasses); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java index 785bb031b..fe08c7d95 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java @@ -58,15 +58,16 @@ public class PageController extends FreemarkerHttpServlet{ if( pageActs == null && dgActs == null){ return Actions.AUTHORIZED; - }else if( pageActs == null && dgActs != null ){ + }else if( pageActs == null ){ return dgActs; + }else if( dgActs == null ){ + return pageActs; }else{ - return pageActs; + return pageActs.and(dgActs); } } catch (Exception e) { - // TODO Auto-generated catch block - log.debug(e); + log.warn(e); return Actions.UNAUTHORIZED; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java index 23e300013..fd333560b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java @@ -537,13 +537,14 @@ public class PageDaoJena extends JenaBaseDao implements PageDao { List actions = new ArrayList(); Model dModel = getOntModelSelector().getDisplayModel(); + dModel.enterCriticalSection(false); try{ QueryExecution qe = QueryExecutionFactory.create( requiredActionsQuery, dModel, initialBindings); actions = executeQueryToList( qe ); qe.close(); }finally{ - dModel.enterCriticalSection(false); + dModel.leaveCriticalSection(); } return actions; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java index 11cf0210d..4d29a23d1 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java @@ -112,7 +112,7 @@ public class DefaultObjectPropertyFormGenerator implements EditConfigurationGene Individual subject = EditConfigurationUtils.getSubjectIndividual(vreq); String predicateUri = EditConfigurationUtils.getPredicateUri(vreq); String rangeUri = EditConfigurationUtils.getRangeUri(vreq); - if (rangeUri != null) { + if (rangeUri != null && !rangeUri.isEmpty()) { VClass rangeVClass = ctxDaoFact.getVClassDao().getVClassByURI(rangeUri); if (!rangeVClass.isUnion()) { types.add(rangeVClass); @@ -125,18 +125,23 @@ public class DefaultObjectPropertyFormGenerator implements EditConfigurationGene } WebappDaoFactory wDaoFact = vreq.getWebappDaoFactory(); //Get all vclasses applicable to subject - List vClasses = subject.getVClasses(); - HashMap typesHash = new HashMap(); - for(VClass vclass: vClasses) { - List rangeVclasses = wDaoFact.getVClassDao().getVClassesForProperty(vclass.getURI(),predicateUri); - if(rangeVclasses != null) { - for(VClass range: rangeVclasses) { - //a hash will keep a unique list of types and so prevent duplicates - typesHash.put(range.getURI(), range); + if(subject != null) { + List vClasses = subject.getVClasses(); + HashMap typesHash = new HashMap(); + for(VClass vclass: vClasses) { + List rangeVclasses = wDaoFact.getVClassDao().getVClassesForProperty(vclass.getURI(),predicateUri); + if(rangeVclasses != null) { + for(VClass range: rangeVclasses) { + //a hash will keep a unique list of types and so prevent duplicates + typesHash.put(range.getURI(), range); + } } - } - } - types.addAll(typesHash.values()); + } + types.addAll(typesHash.values()); + } else { + log.error("Subject individual was null for"); + } + return types; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java index 617a09f6c..7d2c05c9d 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfigurationImpl.java @@ -3,6 +3,7 @@ package edu.cornell.mannlib.vitro.webapp.freemarker.config; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -35,28 +36,52 @@ import freemarker.template.utility.DeepUnwrap; * Extend the Freemarker Configuration class to include some information that is * particular to the current request. * - * Takes advantage of the fact that each servlet request runs in a separate - * thread. Stores the request-based information in a ThreadLocal. Override any - * methods that should return that information instead of (or in addition to) - * the common info. + * A reference to the current request is not always available to the Freemarker + * Configuration, so we take advantage of the fact that each request runs in a + * separate thread, and store a reference to that request in a ThreadLocal + * object. + * + * Then, we override any methods that should return that request-based + * information instead of (or in addition to) the common info. * * Only the getters are overridden, not the setters. So if you call * setAllSharedVariables(), for example, it will have no effect on the * request-based information. + * + * Notice that the reference to the current request is actually stored through a + * WeakReference. This is because the ThreadLocal will not be cleared when the + * webapp is stopped, so none of the references from that ThreadLocal are + * eligible for garbage collection. If any of those references is an instance of + * a class that is loaded by the webapp, then the webapp ClassLoader is not + * eligible for garbage collection. This would be a huge memory leak. + * + * Thanks to the WeakReference, the request is eligible for garbage collection + * if nothing else refers to it. In theory, this means that the WeakReference + * could return a null, but if the garbage collector has taken the request, then + * who is invoking this object? */ public class FreemarkerConfigurationImpl extends Configuration { private static final Log log = LogFactory .getLog(FreemarkerConfigurationImpl.class); - private final ThreadLocal rbiRef = new ThreadLocal<>(); + private static final String ATTRIBUTE_NAME = RequestBasedInformation.class + .getName(); + + private final ThreadLocal> reqRef = new ThreadLocal<>(); void setRequestInfo(HttpServletRequest req) { - rbiRef.set(new RequestBasedInformation(req, this)); + reqRef.set(new WeakReference<>(req)); + req.setAttribute(ATTRIBUTE_NAME, new RequestBasedInformation(req, this)); + } + + private RequestBasedInformation getRequestInfo() { + HttpServletRequest req = reqRef.get().get(); + return (RequestBasedInformation) req.getAttribute(ATTRIBUTE_NAME); } @Override public Object getCustomAttribute(String name) { - Map attribs = rbiRef.get().getCustomAttributes(); + Map attribs = getRequestInfo().getCustomAttributes(); if (attribs.containsKey(name)) { return attribs.get(name); } else { @@ -66,13 +91,13 @@ public class FreemarkerConfigurationImpl extends Configuration { @Override public String[] getCustomAttributeNames() { - Set rbiNames = rbiRef.get().getCustomAttributes().keySet(); + Set rbiNames = getRequestInfo().getCustomAttributes().keySet(); return joinNames(rbiNames, super.getCustomAttributeNames()); } @Override public TemplateModel getSharedVariable(String name) { - Map vars = rbiRef.get().getSharedVariables(); + Map vars = getRequestInfo().getSharedVariables(); if (vars.containsKey(name)) { return vars.get(name); } else { @@ -82,7 +107,7 @@ public class FreemarkerConfigurationImpl extends Configuration { @Override public Set getSharedVariableNames() { - Set rbiNames = rbiRef.get().getSharedVariables().keySet(); + Set rbiNames = getRequestInfo().getSharedVariables().keySet(); @SuppressWarnings("unchecked") Set superNames = super.getSharedVariableNames(); @@ -94,7 +119,7 @@ public class FreemarkerConfigurationImpl extends Configuration { @Override public Locale getLocale() { - return rbiRef.get().getReq().getLocale(); + return getRequestInfo().getReq().getLocale(); } private String[] joinNames(Set nameSet, String[] nameArray) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java index 2ae556862..0bef38b30 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java @@ -285,7 +285,7 @@ public class FreemarkerTemplateLoader implements TemplateLoader { } public boolean fileQualifies(Path path) { - return Files.isRegularFile(path) && Files.isReadable(path); + return Files.isReadable(path) && !Files.isDirectory(path); } public SortedSet getMatches() { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java index 6b917c43f..5ccea3bfd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java @@ -73,8 +73,8 @@ public class ThemeInfoSetup implements ServletContextListener { ApplicationBean.themeInfo = new ThemeInfo(themesBaseDir, defaultThemeName, themeNames); - ss.info(this, ", current theme: " + currentThemeName - + "default theme: " + defaultThemeName + ", available themes: " + ss.info(this, "current theme: " + currentThemeName + + ", default theme: " + defaultThemeName + ", available themes: " + themeNames); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java deleted file mode 100644 index 7bb33194b..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java +++ /dev/null @@ -1,96 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.utils.dataGetter; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.query.Dataset; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.update.GraphStore; -import com.hp.hpl.jena.update.GraphStoreFactory; -import com.hp.hpl.jena.update.UpdateAction; - -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.ifaces.RequiresActions; -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; -import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder; - -/** - * Handle a SPARQL Update request. This uses Jena ARQ and the RDFServiceDataset to - * evaluate a SPARQL Update with the RDFService. - * - * The reason to make this a DataGettere was to allow configuration in RDF of this - * service. - */ -public class SparqlUpdate implements DataGetter, RequiresActions{ - - private static final Log log = LogFactory.getLog(SparqlUpdate.class); - - VitroRequest vreq; - ServletContext context; - - public SparqlUpdate( - VitroRequest vreq, Model displayModel, String dataGetterURI ) { - if( vreq == null ) - throw new IllegalArgumentException("VitroRequest may not be null."); - this.vreq = vreq; - this.context = vreq.getSession().getServletContext(); - } - - - /** - * Gets the update from the request and then executes it on - * the RDFService. - */ - @Override - public Map getData( Map valueMap ) { - HashMap data = new HashMap(); - - String update = vreq.getParameter("update"); - - if( update != null && !update.trim().isEmpty()){ - try{ - IndexBuilder.getBuilder(context).pause(); - Dataset ds = new RDFServiceDataset( vreq.getUnfilteredRDFService() ); - GraphStore graphStore = GraphStoreFactory.create(ds); - log.warn("The SPARQL update is '"+vreq.getParameter("update")+"'"); - UpdateAction.parseExecute( vreq.getParameter("update") , graphStore ); - }finally{ - IndexBuilder.getBuilder(context).unpause(); - } - - } - - data.put("bodyTemplate", "page-sparqlUpdateTest.ftl"); - return data; - } - - - /** - * Check if this request is authorized by the email/password. - * If not do normal authorization. - */ - @Override - public Actions requiredActions(VitroRequest vreq) { - String email = vreq.getParameter("email"); - String password = vreq.getParameter("password"); - - boolean isAuth = PolicyHelper.isAuthorizedForActions(vreq, - email, password, SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS); - - if( isAuth ) - return Actions.AUTHORIZED; - else - return SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS; - } - -} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java index 169491372..f883281f9 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettings.java @@ -179,7 +179,7 @@ public class DeveloperSettings { // The factory // ---------------------------------------------------------------------- - private static final String ATTRIBUTE_NAME = DeveloperSettings.class + protected static final String ATTRIBUTE_NAME = DeveloperSettings.class .getName(); public static DeveloperSettings getBean(HttpServletRequest req) { @@ -203,7 +203,7 @@ public class DeveloperSettings { private final Map settings = new EnumMap<>(Keys.class); - private DeveloperSettings(ServletContext ctx) { + protected DeveloperSettings(ServletContext ctx) { updateFromFile(ctx); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java index 8a3e273ca..651cfb6b0 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/edit/EditConfigurationTemplateModel.java @@ -547,11 +547,14 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { rangeVClasses.addAll(rangeClass.getUnionComponents()); } for(VClass rangeVClass : rangeVClasses) { - vclasses.add(rangeVClass); + if(rangeVClass.getGroupURI() != null) { + vclasses.add(rangeVClass); + } List subURIs = wdf.getVClassDao().getAllSubClassURIs(rangeVClass.getURI()); for (String subClassURI : subURIs) { VClass subClass = wdf.getVClassDao().getVClassByURI(subClassURI); - if (subClass != null) { + //if the subclass exists and also belongs to a particular class group + if (subClass != null && subClass.getGroupURI() != null) { vclasses.add(subClass); } } @@ -567,7 +570,8 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { //add range vclass to hash if(rangeVclasses != null) { for(VClass v: rangeVclasses) { - if(!vclassesURIs.contains(v.getURI())) { + //Need to make sure any class added will belong to a class group + if(!vclassesURIs.contains(v.getURI()) && v.getGroupURI() != null) { vclassesURIs.add(v.getURI()); vclasses.add(v); } @@ -577,7 +581,13 @@ public class EditConfigurationTemplateModel extends BaseTemplateModel { } //if each subject vclass resulted in null being returned for range vclasses, then size of vclasses would be zero if(vclasses.size() == 0) { - vclasses = wdf.getVClassDao().getAllVclasses(); + List allVClasses = wdf.getVClassDao().getAllVclasses(); + //Since these are all vclasses, we should check whether vclasses included are in a class group + for(VClass v:allVClasses) { + if(v.getGroupURI() != null) { + vclasses.add(v); + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/GroupedPropertyList.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/GroupedPropertyList.java index 1240285c3..8235202a6 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/GroupedPropertyList.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/individual/GroupedPropertyList.java @@ -81,7 +81,7 @@ public class GroupedPropertyList extends BaseTemplateModel { // save applicable ranges before deduping to filter later populatedObjectPropertyList = dedupe(populatedObjectPropertyList); - + Collection additions = ApplicationConfigurationOntologyUtils .getAdditionalFauxSubpropertiesForList( populatedObjectPropertyList, subject, vreq); @@ -121,13 +121,7 @@ public class GroupedPropertyList extends BaseTemplateModel { if (editing) { mergeAllPossibleDataProperties(propertyList); } - -// Not currently necessary since the language-specific version is now added -// during the merge -// if (editing) { -// propertyList = correctLanguageForProperties(propertyList); -// } - + sort(propertyList); // Put the list into groups @@ -176,35 +170,6 @@ public class GroupedPropertyList extends BaseTemplateModel { return filteredAdditions; } - // Use the language-filtering WebappDaoFactory to get the right version of - // each property. When editing, the methods that add to the property list - // are blissfully (and intentionally) language-unaware. - private List correctLanguageForProperties(List properties) { - List languageCorrectedProps = new ArrayList(); - for (Property p : properties) { - Property correctedProp = null; - if (p instanceof ObjectProperty) { - ObjectProperty op = (ObjectProperty) p; - correctedProp = wdf.getObjectPropertyDao() - .getObjectPropertyByURIs(op.getURI(), - op.getDomainVClassURI(), op.getRangeVClassURI()); - } else if (p instanceof DataProperty) { - correctedProp = wdf.getDataPropertyDao() - .getDataPropertyByURI(((DataProperty) p).getURI()); - } else { - log.warn("Ignoring " + p.getURI() + " which is neither an " + - "ObjectProperty nor a DatatypeProperty."); - } - if (correctedProp != null) { - languageCorrectedProps.add(correctedProp); - } else { - log.error("Unable to retrieve property " + p.getURI() + - " using the WebappDaoFactory associated with the request."); - } - } - return languageCorrectedProps; - } - // It's possible that an object property retrieved in the call to getPopulatedObjectPropertyList() // is now empty of statements, because if not editing, some statements without a linked individual // are not retrieved by the query. (See elements in queries.) @@ -281,16 +246,17 @@ public class GroupedPropertyList extends BaseTemplateModel { continue; } boolean addToList = true; - int opIndex = 0; for(ObjectProperty op : populatedObjectPropertyList) { - if(redundant(op, piOp)) { + RedundancyReason reason = redundant(op, piOp); + if(reason != null) { addToList = false; - if (moreRestrictiveRange(piOp, op, wadf)) { - propertyList = replaceOpWithPiOpInList(piOp, op, opIndex, propertyList); + if (reason == RedundancyReason.LABEL_AND_URI_MATCH + && moreRestrictiveRange(piOp, op, wadf)) { + op.setRangeVClassURI(piOp.getRangeVClassURI()); + op.setRangeVClass(piOp.getRangeVClass()); } break; } - opIndex++; } if(addToList) { propertyList.add(piOp); @@ -315,6 +281,10 @@ public class GroupedPropertyList extends BaseTemplateModel { return propertyList; } + private enum RedundancyReason { + LABEL_AND_URI_MATCH, LABEL_URI_DOMAIN_AND_RANGE_MATCH + } + private boolean moreRestrictiveRange(ObjectProperty piOp, ObjectProperty op, WebappDaoFactory wadf) { if(piOp.getRangeVClassURI() == null) { @@ -327,25 +297,9 @@ public class GroupedPropertyList extends BaseTemplateModel { } } - private List replaceOpWithPiOpInList(ObjectProperty piOp, - ObjectProperty op, int opIndex, List propertyList) { - - List returnList = new ArrayList(); - int index = 0; - for(Property p : propertyList) { - if(index == opIndex /* p.equals(op) */) { - returnList.add(piOp); - } else { - returnList.add(p); - } - index++; - } - return returnList; - } - - private boolean redundant(ObjectProperty op, ObjectProperty op2) { + private RedundancyReason redundant(ObjectProperty op, ObjectProperty op2) { if (op2.getURI() == null) { - return false; + return null; } boolean uriMatches = (op.getURI() != null && op.getURI().equals(op2.getURI())); @@ -360,7 +314,7 @@ public class GroupedPropertyList extends BaseTemplateModel { labelMatches = true; } if(uriMatches && labelMatches) { - return true; + return RedundancyReason.LABEL_AND_URI_MATCH; } if(op.getDomainVClassURI() == null) { if(op2.getDomainVClassURI() == null) { @@ -377,9 +331,9 @@ public class GroupedPropertyList extends BaseTemplateModel { rangeMatches = true; } if (uriMatches && domainMatches && rangeMatches) { - return true; + return RedundancyReason.LABEL_URI_DOMAIN_AND_RANGE_MATCH; } - return false; + return null; } private void addObjectPropertyToPropertyList(String propertyUri, String domainUri, String rangeUri, diff --git a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettingsStub.java b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettingsStub.java new file mode 100644 index 000000000..6e2871596 --- /dev/null +++ b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/utils/developer/DeveloperSettingsStub.java @@ -0,0 +1,33 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package stubs.edu.cornell.mannlib.vitro.webapp.utils.developer; + +import javax.servlet.ServletContext; + +import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings; + +/** + * Do everything that a standard DeveloperSettings would do, except loading from + * a properties file. + * + * That way, we don't require ConfigurationProperties to find the Vitro home + * directory, so we don't throw errors if there is no ConfigurationProperties. + */ +public class DeveloperSettingsStub extends DeveloperSettings { + /** + * Factory method. Create the stub and set it into the ServletContext. + */ + public static void set(ServletContext ctx) { + ctx.setAttribute(ATTRIBUTE_NAME, new DeveloperSettingsStub(ctx)); + } + + protected DeveloperSettingsStub(ServletContext ctx) { + super(ctx); + } + + @Override + protected void updateFromFile(ServletContext ctx) { + // Don't bother. + } + +} diff --git a/webapp/test/stubs/javax/servlet/ServletContextStub.java b/webapp/test/stubs/javax/servlet/ServletContextStub.java index 46e592d60..eb1e5eab7 100644 --- a/webapp/test/stubs/javax/servlet/ServletContextStub.java +++ b/webapp/test/stubs/javax/servlet/ServletContextStub.java @@ -21,6 +21,8 @@ import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import stubs.edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettingsStub; + /** * A simple stand-in for the {@link ServletContext}, for use in unit tests. */ @@ -36,6 +38,11 @@ public class ServletContextStub implements ServletContext { private final Map mockResources = new HashMap(); private final Map realPaths = new HashMap(); + public ServletContextStub() { + // Assume that unit tests won't want to use Developer mode. + DeveloperSettingsStub.set(this); + } + public void setContextPath(String contextPath) { if (contextPath == null) { throw new NullPointerException("contextPath may not be null."); diff --git a/webapp/web/WEB-INF/web.xml b/webapp/web/WEB-INF/web.xml index 3a5377bda..1d393a7b9 100644 --- a/webapp/web/WEB-INF/web.xml +++ b/webapp/web/WEB-INF/web.xml @@ -1025,6 +1025,16 @@ /admin/sparqlquery + + SparqlUpdateApi + edu.cornell.mannlib.vitro.webapp.controller.api.SparqlUpdateApiController + + + + SparqlUpdateApi + /api/sparqlUpdate + + primitiveRdfEdit edu.cornell.mannlib.vitro.webapp.controller.edit.PrimitiveRdfEdit diff --git a/webapp/web/templates/edit/specific/ents_edit.jsp b/webapp/web/templates/edit/specific/ents_edit.jsp index 10e1764cb..f6f6ba9b2 100644 --- a/webapp/web/templates/edit/specific/ents_edit.jsp +++ b/webapp/web/templates/edit/specific/ents_edit.jsp @@ -99,11 +99,6 @@
- - - -
-
diff --git a/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl b/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl deleted file mode 100644 index 17274f829..000000000 --- a/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl +++ /dev/null @@ -1,12 +0,0 @@ -<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> - -

SPARQL Update Test

- -

This is an expermental SPARQL update service.

- - -

- - -

-
\ No newline at end of file