diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index eddaf3e5ad..490309816f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -25,11 +25,39 @@ A description of what steps someone could take to:
Any additional information that you think would be helpful when reviewing this PR.
Example:
-* Does this change require documentation to be updated?
-* Does this change add any new dependencies?
-* Does this change require any other modifications to be made to the repository?
+* Does this change require documentation to be updated?
+* Does this change add any new dependencies?
+* Does this change require any other modifications to be made to the repository?
* Could this change impact execution of existing code?
-* Large pull requests should be avoided. If this PR is large (more than 1,000 lines of codes), please provide short explanation why your contribution can't be decoupled in smaller PRs.
+* Large pull requests should be avoided. If this PR is large (more than 1,000 lines of codes), please provide short explanation why your contribution can't be decoupled in smaller PRs.
# Interested parties
Tag (@ mention) interested parties or, if unsure, @VIVO-project/vivo-committers
+
+# Reviewers' expertise
+**Please add any new expertise in the list which might be needed for reviewing your PR or remove any of the listed if it is not needed.**
+
+Candidates for reviewing this PR should have some of the following expertises:
+1. Java
+1. HTML, CSS, JavaScript
+1. FreeMarker
+1. SPARQL
+1. Ontologies
+1. Docker
+1. Natural language knowledge
+ 1. English
+ 2. German
+ 3. Spanish
+ 4. French
+ 5. Portuguese
+ 6. Russian
+ 7. Serbian
+
+# Reviewers' report template
+**Please update the following template which should be used by reviewers.**
+## General comment
+A reviewer should provide here comments and suggestions for requested changes if any.
+## Testing
+A reviewer should briefly describe here how it was tested
+## Code reviewing
+A reviewer should briefly describe here which part was code reviewed
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 79ebd73739..e4c9f3351f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,8 +19,6 @@ utilities/sdb_to_tdb/.work
**/overlays
*~
-**/.DS_Store
-
# Eclipse artifacts
**/.settings
**/.classpath
diff --git a/api/pom.xml b/api/pom.xml
index deebfb9e14..ae0bed6ff3 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -7,13 +7,13 @@
org.vivoweb
vitro-api
- 1.14.1-SNAPSHOT
+ 1.15.1-SNAPSHOT
jar
org.vivoweb
vitro-project
- 1.14.1-SNAPSHOT
+ 1.15.1-SNAPSHOT
..
@@ -65,6 +65,21 @@
argon2-jvm
2.11
+
+ net.logicsquad
+ nanocaptcha
+ 1.5
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.36
+
+
+ com.google.guava
+ guava
+ 32.0.0-jre
+
org.apache.httpcomponents
fluent-hc
@@ -73,7 +88,7 @@
org.vivoweb
vitro-dependencies
- 1.14.1-SNAPSHOT
+ 1.15.1-SNAPSHOT
pom
diff --git a/api/src/main/java/edu/cornell/mannlib/vedit/beans/LoginStatusBean.java b/api/src/main/java/edu/cornell/mannlib/vedit/beans/LoginStatusBean.java
index 541aa42f65..6785e40989 100644
--- a/api/src/main/java/edu/cornell/mannlib/vedit/beans/LoginStatusBean.java
+++ b/api/src/main/java/edu/cornell/mannlib/vedit/beans/LoginStatusBean.java
@@ -2,7 +2,6 @@
package edu.cornell.mannlib.vedit.beans;
-import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
@@ -100,9 +99,7 @@ public static UserAccount getCurrentUser(HttpSession session) {
if (!getBean(session).isLoggedIn()) {
return null;
}
-
- ServletContext ctx = session.getServletContext();
- WebappDaoFactory wadf = ModelAccess.on(ctx).getWebappDaoFactory();
+ WebappDaoFactory wadf = ModelAccess.getInstance().getWebappDaoFactory();
UserAccountsDao userAccountsDao = wadf.getUserAccountsDao();
if (userAccountsDao == null) {
log.error("No UserAccountsDao");
diff --git a/api/src/main/java/edu/cornell/mannlib/vedit/controller/BaseEditController.java b/api/src/main/java/edu/cornell/mannlib/vedit/controller/BaseEditController.java
index 23a3e6f51e..93a27e0e97 100644
--- a/api/src/main/java/edu/cornell/mannlib/vedit/controller/BaseEditController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vedit/controller/BaseEditController.java
@@ -2,6 +2,9 @@
package edu.cornell.mannlib.vedit.controller;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.NamedKeyComponent.NOT_RELATED;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.NamedKeyComponent.SUPPRESSION_BY_TYPE;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.NamedKeyComponent.SUPPRESSION_BY_URI;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.ReasoningOption.ASSERTIONS_ONLY;
import java.text.Collator;
@@ -12,13 +15,11 @@
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
-import java.util.Set;
import javax.servlet.http.HttpServletRequest;
@@ -27,6 +28,7 @@
import edu.cornell.mannlib.vedit.util.FormUtils;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
+import edu.cornell.mannlib.vitro.webapp.auth.permissions.PermissionSets;
import edu.cornell.mannlib.vitro.webapp.auth.policy.EntityPolicyController;
import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet;
import edu.cornell.mannlib.vitro.webapp.controller.Controllers;
@@ -40,7 +42,18 @@
public class BaseEditController extends VitroHttpServlet {
+ private static final String OPERATIONS_TO_ROLES = "operationsToRoles";
+
+ public static final String URI_SUPPRESSIONS = "uriSuppressions";
+
+ public static final String TYPE_SUPPRESSIONS = "typeSuppressions";
+
+ public static final String TYPE_SUPPRESSIONS_NOT_RELATED = "typeSuppressionsNotRelated";
+
+ public static final String PROPERTY_SUPPRESSIONS_NOT_RELATED = "propertySuppressionsNotRelated";
+
public static final String ENTITY_URI_ATTRIBUTE_NAME = "_permissionsEntityURI";
+
public static final String ENTITY_TYPE_ATTRIBUTE_NAME = "_permissionsEntityType";
public static final boolean FORCE_NEW = true; // when you know you're starting a new edit process
@@ -215,15 +228,13 @@ public String getDefaultLandingPage(HttpServletRequest request) {
protected static void addAccessAttributes(HttpServletRequest req, String entityURI, AccessObjectType aot) {
// Add the permissionsEntityURI (if we are creating a new property, this will be empty)
req.setAttribute(ENTITY_URI_ATTRIBUTE_NAME, entityURI);
-
+ String[] namedKeys = new String[0];
// Get the available permission sets
List permissionSets = buildListOfSelectableRoles(ModelAccess.on(req).getWebappDaoFactory());
List roles = new ArrayList<>();
- List roleUris = new ArrayList<>();
for (PermissionSet permissionSet : permissionSets) {
roles.add(new RoleInfo(permissionSet));
- roleUris.add(permissionSet.getUri());
}
List accessOperations = AccessOperation.getOperations(aot);
// Operation, list of roles>
@@ -242,16 +253,110 @@ protected static void addAccessAttributes(HttpServletRequest req, String entityU
}
}
}
- if (!StringUtils.isEmpty(entityURI)) {
- for (RoleInfo roleInfo : roleInfos) {
- if (roleInfo.isEnabled()) {
- roleInfo.setGranted(
- EntityPolicyController.isGranted(entityURI, aot, operation, roleInfo.getUri()));
- }
+ getRolePolicyInformation(entityURI, aot, namedKeys, operation, roleInfos);
+ }
+ req.setAttribute(OPERATIONS_TO_ROLES, operationsToRoles);
+ }
+
+ private static void getRolePolicyInformation(String entityURI, AccessObjectType aot, String[] namedKeys,
+ AccessOperation operation, List roleInfos) {
+ if (!StringUtils.isEmpty(entityURI)) {
+ for (RoleInfo roleInfo : roleInfos) {
+ if (roleInfo.isEnabled()) {
+ roleInfo.setGranted(
+ EntityPolicyController.isGranted(entityURI, aot, operation, roleInfo.getUri(), namedKeys));
}
}
}
- req.setAttribute("operationsToRoles", operationsToRoles);
+ }
+
+ protected static void addUriSuppressions(HttpServletRequest req, String entityURI, AccessObjectType aot) {
+ AccessOperation operation = AccessOperation.DISPLAY;
+ String[] namedKeys = new String[1];
+ namedKeys[0] = SUPPRESSION_BY_URI.toString();
+ // Get the available permission sets
+ List permissionSets = buildListOfSelectableRoles(ModelAccess.on(req).getWebappDaoFactory());
+ List roles = new ArrayList<>();
+
+ for (PermissionSet permissionSet : permissionSets) {
+ roles.add(new RoleInfo(permissionSet));
+ }
+ Map> uriSuppressionsToRoles = new LinkedHashMap<>();
+ List roleInfos = new LinkedList<>();
+ String operationName = StringUtils.capitalize(operation.toString().toLowerCase());
+ uriSuppressionsToRoles.put(operationName, roleInfos);
+ for (RoleInfo role : roles) {
+ RoleInfo roleCopy = role.clone();
+ roleInfos.add(roleCopy);
+ }
+ getRolePolicyInformation(entityURI, aot, namedKeys, operation, roleInfos);
+ req.setAttribute(URI_SUPPRESSIONS, uriSuppressionsToRoles);
+ req.setAttribute(ENTITY_URI_ATTRIBUTE_NAME, entityURI);
+ }
+
+ protected static void addTypeSuppressions(HttpServletRequest req, String entityURI, AccessObjectType aot) {
+ AccessOperation operation = AccessOperation.DISPLAY;
+ String[] namedKeys = new String[1];
+ namedKeys[0] = SUPPRESSION_BY_TYPE.toString();
+ // Get the available permission sets
+ List permissionSets = buildListOfSelectableRoles(ModelAccess.on(req).getWebappDaoFactory());
+ List roles = new ArrayList<>();
+
+ for (PermissionSet permissionSet : permissionSets) {
+ roles.add(new RoleInfo(permissionSet));
+ }
+ Map> typeSuppressionsToRoles = new LinkedHashMap<>();
+ List roleInfos = new LinkedList<>();
+ String operationName = StringUtils.capitalize(operation.toString().toLowerCase());
+ typeSuppressionsToRoles.put(operationName, roleInfos);
+ for (RoleInfo role : roles) {
+ RoleInfo roleCopy = role.clone();
+ roleInfos.add(roleCopy);
+ }
+ getRolePolicyInformation(entityURI, aot, namedKeys, operation, roleInfos);
+ req.setAttribute(TYPE_SUPPRESSIONS, typeSuppressionsToRoles);
+ }
+
+ protected static void addNotRelatedTypeSuppressions(HttpServletRequest req, String entityURI, AccessObjectType aot) {
+ AccessOperation operation = AccessOperation.DISPLAY;
+ String[] namedKeys = new String[2];
+ namedKeys[0] = SUPPRESSION_BY_TYPE.toString();
+ namedKeys[1] = NOT_RELATED.toString();
+
+ RoleInfo role = getSelfEditorRole(req);
+ Map> typeSuppressionsToRoles = new LinkedHashMap<>();
+ List roleInfos = new LinkedList<>();
+ String operationName = StringUtils.capitalize(operation.toString().toLowerCase());
+ typeSuppressionsToRoles.put(operationName, roleInfos);
+ roleInfos.add(role);
+
+ getRolePolicyInformation(entityURI, aot, namedKeys, operation, roleInfos);
+ req.setAttribute(TYPE_SUPPRESSIONS_NOT_RELATED, typeSuppressionsToRoles);
+ }
+
+ protected static RoleInfo getSelfEditorRole(HttpServletRequest req) {
+ PermissionSet permissionSet = ModelAccess.on(req).getWebappDaoFactory().getUserAccountsDao()
+ .getPermissionSetByUri(PermissionSets.URI_SELF_EDITOR);
+ RoleInfo role = new RoleInfo(permissionSet);
+ return role;
+ }
+
+ protected static void addNotRelatedPropertySuppressions(HttpServletRequest req, String entityURI,
+ AccessObjectType aot) {
+ AccessOperation operation = AccessOperation.DISPLAY;
+ String[] namedKeys = new String[2];
+ namedKeys[0] = SUPPRESSION_BY_URI.toString();
+ namedKeys[1] = NOT_RELATED.toString();
+
+ RoleInfo role = getSelfEditorRole(req);
+ Map> propertySuppressionsToRoles = new LinkedHashMap<>();
+ List roleInfos = new LinkedList<>();
+ String operationName = StringUtils.capitalize(operation.toString().toLowerCase());
+ propertySuppressionsToRoles.put(operationName, roleInfos);
+ roleInfos.add(role);
+
+ getRolePolicyInformation(entityURI, aot, namedKeys, operation, roleInfos);
+ req.setAttribute(PROPERTY_SUPPRESSIONS_NOT_RELATED, propertySuppressionsToRoles);
}
static boolean isPublicForbiddenOperation(AccessOperation operation) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vedit/controller/OperationController.java b/api/src/main/java/edu/cornell/mannlib/vedit/controller/OperationController.java
index 6953d7e343..db98ba2a97 100644
--- a/api/src/main/java/edu/cornell/mannlib/vedit/controller/OperationController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vedit/controller/OperationController.java
@@ -2,10 +2,16 @@
package edu.cornell.mannlib.vedit.controller;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType.CLASS;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType.INDIVIDUAL;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation.DISPLAY;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.NamedKeyComponent.NOT_RELATED;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.NamedKeyComponent.SUPPRESSION_BY_TYPE;
+import static edu.cornell.mannlib.vitro.webapp.auth.attributes.NamedKeyComponent.SUPPRESSION_BY_URI;
+
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
@@ -19,19 +25,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType;
-import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.attributes.OperationGroup;
-import edu.cornell.mannlib.vitro.webapp.auth.policy.EntityPolicyController;
-import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet;
-import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
-import org.apache.commons.lang3.EnumUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
import edu.cornell.mannlib.vedit.beans.EditProcessObject;
-import edu.cornell.mannlib.vedit.controller.BaseEditController.RoleInfo;
import edu.cornell.mannlib.vedit.forwarder.PageForwarder;
import edu.cornell.mannlib.vedit.listener.ChangeListener;
import edu.cornell.mannlib.vedit.listener.EditPreProcessor;
@@ -40,6 +34,15 @@
import edu.cornell.mannlib.vedit.util.OperationUtils;
import edu.cornell.mannlib.vedit.validator.ValidationObject;
import edu.cornell.mannlib.vedit.validator.Validator;
+import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType;
+import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
+import edu.cornell.mannlib.vitro.webapp.auth.policy.EntityPolicyController;
+import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import org.apache.commons.lang3.EnumUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
@WebServlet(name = "OperationController", urlPatterns = {"/doEdit"} )
public class OperationController extends BaseEditController {
@@ -138,39 +141,10 @@ public void doPost (HttpServletRequest request, HttpServletResponse response) {
// If contains restrictions
if (request.getParameter("_permissions") != null) {
- // Get the namespace that we are editing
- String entityUri = request.getParameter(ENTITY_URI_ATTRIBUTE_NAME);
- if (StringUtils.isEmpty(entityUri)) {
- // If we don't have a namespace set, we are creating a new entity so use that namespace
- if (!StringUtils.isEmpty(request.getParameter("Namespace")) && !StringUtils.isEmpty(request.getParameter("LocalName"))) {
- entityUri = "" + request.getParameter("Namespace") + request.getParameter("LocalName");
- }
- }
- String entityType = request.getParameter(ENTITY_TYPE_ATTRIBUTE_NAME);
- List permissionSets = buildListOfSelectableRoles(ModelAccess.on(request).getWebappDaoFactory());
- Set roles = new HashSet<>();
- for (PermissionSet permissionSet : permissionSets) {
- roles.add(new RoleInfo(permissionSet));
- }
- AccessObjectType aot = getAccessObjectType(entityUri, entityType);
- if (aot != null) {
- List operations = AccessOperation.getOperations(aot);
- for (AccessOperation ao : operations) {
- String operationGroupName = ao.toString().toLowerCase().split("_")[0];
- Set selectedRoles = getSelectedRoles(request, operationGroupName);
- for (RoleInfo role : roles) {
- if (role.isPublic() && isPublicForbiddenOperation(ao)) {
- continue;
- }
- if (selectedRoles.contains(role.getUri())) {
- EntityPolicyController.grantAccess(entityUri, aot, ao, role.getUri());
- } else {
- EntityPolicyController.revokeAccess(entityUri, aot, ao, role.getUri());
- }
-
- }
- }
- }
+ updatePermissions(request);
+ }
+ if (isUriSuppressionsPresent(request)) {
+ updateUriSuppressions(request);
}
/* put request parameters and attributes into epo where the listeners can see */
@@ -230,6 +204,155 @@ public void doPost (HttpServletRequest request, HttpServletResponse response) {
}
}
+ private void updateUriSuppressions(HttpServletRequest request) {
+ String entityUri = request.getParameter(ENTITY_URI_ATTRIBUTE_NAME);
+ if (entityUri == null) {
+ return;
+ }
+ String entityType = request.getParameter(ENTITY_TYPE_ATTRIBUTE_NAME);
+ AccessObjectType aot = getAccessObjectType(entityUri, entityType);
+ if (aot == null) {
+ return;
+ }
+ updateUriSuppressions(request, aot, entityUri);
+ }
+
+ private void updatePermissions(HttpServletRequest request) {
+ // Get the namespace that we are editing
+ String entityUri = request.getParameter(ENTITY_URI_ATTRIBUTE_NAME);
+ if (StringUtils.isEmpty(entityUri)) {
+ // If we don't have a namespace set, we are creating a new entity so use that namespace
+ if (!StringUtils.isEmpty(request.getParameter("Namespace")) && !StringUtils.isEmpty(request.getParameter("LocalName"))) {
+ entityUri = "" + request.getParameter("Namespace") + request.getParameter("LocalName");
+ }
+ }
+ String entityType = request.getParameter(ENTITY_TYPE_ATTRIBUTE_NAME);
+ AccessObjectType aot = getAccessObjectType(entityUri, entityType);
+ if (aot == null) {
+ return;
+ }
+ updateEntityPermissions(request, entityUri, aot);
+ updateTypeSuppressions(request, aot, entityUri);
+ updateNotRelatedTypeSuppressions(request, aot, entityUri);
+ updateNotRelatedPropertySuppressions(request, aot, entityUri);
+ }
+
+ private void updateEntityPermissions(HttpServletRequest request, String entityUri, AccessObjectType aot) {
+ Set roles = getAllRoles(request);
+ List operations = AccessOperation.getOperations(aot);
+ for (AccessOperation ao : operations) {
+ String operationGroupName = ao.toString().toLowerCase();
+ Set selectedRoles = getSelectedRoles(request, operationGroupName);
+ for (RoleInfo role : roles) {
+ if (role.isPublic() && isPublicForbiddenOperation(ao)) {
+ continue;
+ }
+ if (selectedRoles.contains(role.getUri())) {
+ EntityPolicyController.grantAccess(entityUri, aot, ao, role.getUri());
+ } else {
+ EntityPolicyController.revokeAccess(entityUri, aot, ao, role.getUri());
+ }
+ }
+ }
+ }
+
+ private Set getAllRoles(HttpServletRequest request) {
+ List permissionSets = buildListOfSelectableRoles(ModelAccess.on(request).getWebappDaoFactory());
+ Set roles = new HashSet<>();
+ for (PermissionSet permissionSet : permissionSets) {
+ roles.add(new RoleInfo(permissionSet));
+ }
+ return roles;
+ }
+
+ private void updateUriSuppressions(HttpServletRequest request, AccessObjectType aot, String entityUri) {
+ if (!AccessObjectType.INDIVIDUAL.equals(aot)) {
+ return;
+ }
+ String[] namedKeys = new String[1];
+ namedKeys[0] = SUPPRESSION_BY_URI.toString();
+ Set roles = getAllRoles(request);
+ String operationGroupName = "uriSuppression" + DISPLAY.toString().toLowerCase();
+ Set selectedRoles = getSelectedRoles(request, operationGroupName);
+ for (RoleInfo role : roles) {
+ if (selectedRoles.contains(role.getUri())) {
+ EntityPolicyController.grantAccess(entityUri, aot, DISPLAY, role.getUri(), namedKeys);
+ } else {
+ EntityPolicyController.revokeAccess(entityUri, aot, DISPLAY, role.getUri(), namedKeys);
+ }
+ }
+ }
+
+ private void updateTypeSuppressions(HttpServletRequest request, AccessObjectType aot, String entityUri) {
+ if (!isTypeSuppressionsPresent(request) || !AccessObjectType.CLASS.equals(aot)) {
+ return;
+ }
+ String[] namedKeys = new String[1];
+ namedKeys[0] = SUPPRESSION_BY_TYPE.toString();
+ Set roles = getAllRoles(request);
+ String operationGroupName = "typeSuppression" + DISPLAY.toString().toLowerCase();
+ Set selectedRoles = getSelectedRoles(request, operationGroupName);
+ for (RoleInfo role : roles) {
+ if (selectedRoles.contains(role.getUri())) {
+ EntityPolicyController.grantAccess(entityUri, INDIVIDUAL, DISPLAY, role.getUri(), namedKeys);
+ } else {
+ EntityPolicyController.revokeAccess(entityUri, INDIVIDUAL, DISPLAY, role.getUri(), namedKeys);
+ }
+ }
+ }
+
+ private void updateNotRelatedTypeSuppressions(HttpServletRequest request, AccessObjectType aot, String entityUri) {
+ if (!isNotRelatedTypeSuppressionsPresent(request) || !CLASS.equals(aot)) {
+ return;
+ }
+ String[] namedKeys = new String[2];
+ namedKeys[0] = SUPPRESSION_BY_TYPE.toString();
+ namedKeys[1] = NOT_RELATED.toString();
+ RoleInfo role = getSelfEditorRole(request);
+ String operationGroupName = "typeSuppressionNotRelated" + DISPLAY.toString().toLowerCase();
+ Set selectedRoles = getSelectedRoles(request, operationGroupName);
+ if (selectedRoles.contains(role.getUri())) {
+ EntityPolicyController.grantAccess(entityUri, INDIVIDUAL, DISPLAY, role.getUri(), namedKeys);
+ } else {
+ EntityPolicyController.revokeAccess(entityUri, INDIVIDUAL, DISPLAY, role.getUri(), namedKeys);
+ }
+ }
+
+ private void updateNotRelatedPropertySuppressions(HttpServletRequest request, AccessObjectType aot,
+ String entityUri) {
+ if (!isNotRelatedPropertySuppressionsPresent(request)) {
+ return;
+ }
+ String[] namedKeys = new String[2];
+ namedKeys[0] = SUPPRESSION_BY_URI.toString();
+ namedKeys[1] = NOT_RELATED.toString();
+ RoleInfo role = getSelfEditorRole(request);
+ String operationGroupName = "propertySuppressionNotRelated" + DISPLAY.toString().toLowerCase();
+ Set selectedRoles = getSelectedRoles(request, operationGroupName);
+ if (selectedRoles.contains(role.getUri())) {
+ EntityPolicyController.grantAccess(entityUri, aot, DISPLAY, role.getUri(), namedKeys);
+ } else {
+ EntityPolicyController.revokeAccess(entityUri, aot, DISPLAY, role.getUri(), namedKeys);
+ }
+ }
+
+
+ private boolean isUriSuppressionsPresent(HttpServletRequest request) {
+ return request.getParameter(URI_SUPPRESSIONS) != null;
+ }
+
+ private boolean isNotRelatedPropertySuppressionsPresent(HttpServletRequest request) {
+ return request.getParameter(PROPERTY_SUPPRESSIONS_NOT_RELATED) != null;
+ }
+
+ private boolean isTypeSuppressionsPresent(HttpServletRequest request) {
+ return request.getParameter(TYPE_SUPPRESSIONS) != null;
+ }
+
+ private boolean isNotRelatedTypeSuppressionsPresent(HttpServletRequest request) {
+ return request.getParameter(TYPE_SUPPRESSIONS_NOT_RELATED) != null;
+ }
+
private Set getSelectedRoles(HttpServletRequest request, String operationGroupName) {
String[] selectedRoles = request.getParameterValues(operationGroupName + "Roles");
if (selectedRoles == null) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AccessObjectType.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AccessObjectType.java
index 48ca429fb7..1358c827a4 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AccessObjectType.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AccessObjectType.java
@@ -15,4 +15,5 @@ public enum AccessObjectType {
FAUX_DATA_PROPERTY,
FAUX_DATA_PROPERTY_STATEMENT,
FAUX_OBJECT_PROPERTY_STATEMENT,
+ INDIVIDUAL,
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueKey.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueKey.java
index 8dfec3aa30..ec65c5f1e0 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueKey.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueKey.java
@@ -1,39 +1,59 @@
package edu.cornell.mannlib.vitro.webapp.auth.attributes;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class AttributeValueKey {
- private AccessOperation ao;
- private AccessObjectType aot;
+ private AccessOperation accessOperation;
+ private AccessObjectType accessObjectType;
private String role;
private String type;
+ private Set namedKeyComponents = new HashSet<>();
+
+ public Set getNamedKeyComponents() {
+ return namedKeyComponents;
+ }
public AttributeValueKey() {
}
- public AttributeValueKey(AccessOperation ao, AccessObjectType aot, String role, String type) {
- this.ao = ao;
- this.aot = aot;
+ public AttributeValueKey(AccessOperation ao, AccessObjectType aot, String role, String type,
+ String... namedKeyComponents) {
+ this.accessOperation = ao;
+ this.accessObjectType = aot;
this.role = role;
this.type = type;
+ this.namedKeyComponents = new HashSet<>(Arrays.asList(namedKeyComponents));
+ }
+
+ public AttributeValueKey(AccessOperation ao, AccessObjectType aot, String role, String type,
+ Set namedKeyComponents) {
+ this.accessOperation = ao;
+ this.accessObjectType = aot;
+ this.role = role;
+ this.type = type;
+ this.namedKeyComponents = namedKeyComponents;
}
public AccessOperation getAccessOperation() {
- return ao;
+ return accessOperation;
}
public void setOperation(AccessOperation ao) {
- this.ao = ao;
+ this.accessOperation = ao;
}
public AccessObjectType getObjectType() {
- return aot;
+ return accessObjectType;
}
public void setObjectType(AccessObjectType aot) {
- this.aot = aot;
+ this.accessObjectType = aot;
}
public String getRole() {
@@ -52,12 +72,16 @@ public void setType(String type) {
this.type = type;
}
+ public void addNamedKey(String key) {
+ namedKeyComponents.add(key);
+ }
+
public AttributeValueKey clone() {
- return new AttributeValueKey(ao, aot, role, type);
+ return new AttributeValueKey(accessOperation, accessObjectType, role, type, namedKeyComponents);
}
public boolean isEmpty() {
- return ao == null && aot == null && role == null && type == null;
+ return accessOperation == null && accessObjectType == null && role == null && type == null;
}
@Override
@@ -75,6 +99,7 @@ public boolean equals(Object object) {
.append(getObjectType(), compared.getObjectType())
.append(getRole(), compared.getRole())
.append(getType(), compared.getType())
+ .append(getNamedKeyComponents(), compared.getNamedKeyComponents())
.isEquals();
}
@@ -85,6 +110,7 @@ public int hashCode() {
.append(getObjectType())
.append(getRole())
.append(getType())
+ .append(getNamedKeyComponents())
.toHashCode();
}
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueSet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueSet.java
index d8902b2509..902175b094 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueSet.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/AttributeValueSet.java
@@ -1,5 +1,7 @@
package edu.cornell.mannlib.vitro.webapp.auth.attributes;
+import java.util.Set;
+
public interface AttributeValueSet {
void add(String value);
@@ -26,4 +28,6 @@ public interface AttributeValueSet {
void setKey(AttributeValueKey key);
+ Set getValues();
+
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/MutableAttributeValueSet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/MutableAttributeValueSet.java
index e00b670632..413ded0957 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/MutableAttributeValueSet.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/MutableAttributeValueSet.java
@@ -55,6 +55,10 @@ public String getSingleValue() {
return "";
}
+ public Set getValues() {
+ return values;
+ }
+
@Override
public boolean isEmpty() {
return values.isEmpty();
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/NamedKeyComponent.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/NamedKeyComponent.java
new file mode 100644
index 0000000000..09a7bc4894
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/NamedKeyComponent.java
@@ -0,0 +1,7 @@
+package edu.cornell.mannlib.vitro.webapp.auth.attributes;
+
+public enum NamedKeyComponent {
+ SUPPRESSION_BY_URI,
+ SUPPRESSION_BY_TYPE,
+ NOT_RELATED,
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/ValueSetFactory.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/ValueSetFactory.java
index dc05586edd..e85d999520 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/ValueSetFactory.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/attributes/ValueSetFactory.java
@@ -16,8 +16,12 @@ public static AttributeValueSet create(String value, QuerySolution qs, Attribute
if (avc == null) {
return createNew(value, qs, dataSetKey, avcKey);
} else {
- return returnFromRegistry(value, avc);
+ Optional setUri = getSetUri(qs);
+ if (!setUri.isPresent() || !setUri.get().equals(avc.getValueSetUri())) {
+ return createNew(value, qs, dataSetKey, avcKey);
+ }
}
+ return returnFromRegistry(value, avc);
}
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AbstractCheck.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AbstractCheck.java
index 2409e02809..ccdf73b98e 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AbstractCheck.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AbstractCheck.java
@@ -11,6 +11,7 @@ public abstract class AbstractCheck implements Check {
private AttributeValueSet values;
private String uri;
private long computationalCost;
+ private String configuration;
private CheckType testType = CheckType.EQUALS;
@@ -36,12 +37,18 @@ public void setType(CheckType testType) {
adjustComputationCost(testType);
}
- @Override
+ public String getConfiguration() {
+ return configuration;
+ }
+
+ public void setConfiguration(String configuration) {
+ this.configuration = configuration;
+ }
+
public void addValue(String value) {
values.add(value);
}
- @Override
public AttributeValueSet getValues() {
return values;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AttributeValueChecker.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AttributeValueChecker.java
index dd5cb7a081..3ad814622b 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AttributeValueChecker.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/AttributeValueChecker.java
@@ -2,19 +2,10 @@
package edu.cornell.mannlib.vitro.webapp.auth.checks;
-import java.util.Arrays;
-import java.util.List;
-
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AttributeValueSet;
-import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jena.rdf.model.Model;
public class AttributeValueChecker {
- private static final Log log = LogFactory.getLog(AttributeValueChecker.class);
static boolean test(Check attr, AuthorizationRequest ar, String... values) {
CheckType testType = attr.getType();
@@ -30,44 +21,14 @@ static boolean test(Check attr, AuthorizationRequest ar, String... values) {
case STARTS_WITH:
return startsWith(attr, values);
case SPARQL_SELECT_QUERY_RESULTS_CONTAIN:
- return sparqlQueryContains(attr, ar, values);
+ return SparqlSelectQueryResultsChecker.sparqlSelectQueryResultsContain(attr, ar, values);
case SPARQL_SELECT_QUERY_RESULTS_NOT_CONTAIN:
- return !sparqlQueryContains(attr, ar, values);
+ return !SparqlSelectQueryResultsChecker.sparqlSelectQueryResultsContain(attr, ar, values);
default:
return false;
}
}
- private static boolean sparqlQueryContains(Check attr, AuthorizationRequest ar, String[] inputValues) {
- AttributeValueSet values = attr.getValues();
- if (!values.containsSingleValue()) {
- log.error("SparqlQueryContains more than one value");
- return false;
- }
- String queryTemplate = values.getSingleValue();
- if (StringUtils.isBlank(queryTemplate)) {
- log.error("SparqlQueryContains template is empty");
- return false;
- }
- AccessObject ao = ar.getAccessObject();
- Model m = ao.getStatementOntModel();
- if (m == null) {
- log.debug("SparqlQueryContains model is not provided");
- return false;
- }
- List personUris = ar.getEditorUris();
- if (personUris.isEmpty()) {
- if (queryTemplate.contains("?personUri")) {
- log.debug("Subject has no person URIs");
- return false;
- } else {
- personUris.add("");
- }
- }
- List resourceUris = Arrays.asList(ao.getResourceUris());
- return ProximityChecker.isAnyRelated(m, resourceUris, personUris, queryTemplate);
- }
-
private static boolean contains(Check attr, String... inputValues) {
AttributeValueSet values = attr.getValues();
for (String inputValue : inputValues) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/Check.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/Check.java
index 99a06bc020..7f6ee08d7b 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/Check.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/Check.java
@@ -26,4 +26,8 @@ public interface Check {
long getComputationalCost();
+ void setConfiguration(String configuration);
+
+ String getConfiguration();
+
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/CheckFactory.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/CheckFactory.java
index 1a18355be8..444ca2d8b0 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/CheckFactory.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/CheckFactory.java
@@ -8,6 +8,7 @@
import edu.cornell.mannlib.vitro.webapp.auth.attributes.ValueSetFactory;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyLoader;
import org.apache.jena.query.QuerySolution;
+import org.apache.jena.rdf.model.RDFNode;
public class CheckFactory {
@@ -48,9 +49,18 @@ public static Check createCheck(QuerySolution qs, AttributeValueKey dataSetKey)
check = null;
}
check.setType(CheckType.valueOf(testId));
+ setConfiguration(qs, check);
return check;
}
+ private static void setConfiguration(QuerySolution qs, Check check) {
+ RDFNode rdfNode = qs.get("config");
+ if (rdfNode == null || !rdfNode.isLiteral()) {
+ return;
+ }
+ check.setConfiguration(rdfNode.asLiteral().toString());
+ }
+
private static String getValue(QuerySolution qs) {
if (!qs.contains(PolicyLoader.LITERAL_VALUE) || !qs.get(PolicyLoader.LITERAL_VALUE).isLiteral()) {
String value = qs.getResource(PolicyLoader.ATTR_VALUE).getURI();
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/ProximityChecker.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/ProximityChecker.java
deleted file mode 100644
index e1f4931008..0000000000
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/ProximityChecker.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/* $This file is distributed under the terms of the license in LICENSE$ */
-
-package edu.cornell.mannlib.vitro.webapp.auth.checks;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jena.query.ParameterizedSparqlString;
-import org.apache.jena.query.Query;
-import org.apache.jena.query.QueryExecution;
-import org.apache.jena.query.QueryExecutionFactory;
-import org.apache.jena.query.QueryFactory;
-import org.apache.jena.query.QuerySolution;
-import org.apache.jena.query.ResultSet;
-import org.apache.jena.rdf.model.Model;
-import org.apache.jena.rdf.model.RDFNode;
-
-public class ProximityChecker {
- private static final Log log = LogFactory.getLog(ProximityChecker.class);
-
- public static boolean isAnyRelated(Model ontModel, List resourceUris, List personUris,
- String query) {
- for (String personUri : personUris) {
- List connectedResourceUris = getRelatedUris(ontModel, personUri, query);
- for (String connectedResourceUri : connectedResourceUris) {
- if (resourceUris.contains(connectedResourceUri)) {
- return true;
- }
- }
- }
- return false;
- }
-
- private static List getRelatedUris(Model model, String personUri, String queryTemplate) {
- HashMap> queryMap = QueryResultsMapCache.get();
- String queryMapKey = createQueryMapKey(personUri, queryTemplate);
- if (queryMap.containsKey(queryMapKey)) {
- return queryMap.get(queryMapKey);
- }
- List results = new ArrayList<>();
- ParameterizedSparqlString pss = new ParameterizedSparqlString();
- pss.setCommandText(queryTemplate);
- pss.setIri("personUri", personUri);
- String queryText = pss.toString();
- debug("queryText: " + queryText);
- Query query = QueryFactory.create(queryText);
- QueryExecution queryExecution = QueryExecutionFactory.create(query, model);
- try {
- ResultSet resultSet = queryExecution.execSelect();
- while (resultSet.hasNext()) {
- QuerySolution qs = resultSet.nextSolution();
- addSolutionValues(results, qs);
- }
- } finally {
- queryExecution.close();
- }
- debug("query results: " + results);
- queryMap.put(queryMapKey, results);
- QueryResultsMapCache.update(queryMap);
- return results;
- }
-
- private static void addSolutionValues(List results, QuerySolution qs) {
- Iterator names = qs.varNames();
- while (names.hasNext()) {
- String name = names.next();
- RDFNode node = qs.get(name);
- if (node.isURIResource()) {
- results.add(node.asResource().getURI());
- } else if (node.isLiteral()) {
- results.add(node.asLiteral().toString());
- }
- }
- }
-
- private static void debug(String queryText) {
- if (log.isDebugEnabled()) {
- log.debug(queryText);
- }
- }
-
- private static String createQueryMapKey(String personUri, String queryTemplate) {
- return queryTemplate + "." + personUri;
- }
-}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/QueryResultsMapCache.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/QueryResultsMapCache.java
index 761131cdbd..7d6fd99691 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/QueryResultsMapCache.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/QueryResultsMapCache.java
@@ -4,7 +4,7 @@
import java.io.IOException;
import java.util.HashMap;
-import java.util.List;
+import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -12,11 +12,11 @@
public class QueryResultsMapCache implements AutoCloseable {
private static final Log log = LogFactory.getLog(QueryResultsMapCache.class);
- private static ThreadLocal>> threadLocal =
- new ThreadLocal>>();
+ private static ThreadLocal>> threadLocal =
+ new ThreadLocal>>();
public QueryResultsMapCache() {
- threadLocal.set(new HashMap>());
+ threadLocal.set(new HashMap>());
log.debug("Query results map cache initialized");
}
@@ -26,10 +26,10 @@ public void close() throws IOException {
log.debug("QueryResultsMapCache is closed");
}
- public static HashMap> get() {
- HashMap> queryResultsMap = threadLocal.get();
+ public static HashMap> get() {
+ HashMap> queryResultsMap = threadLocal.get();
if (queryResultsMap == null) {
- queryResultsMap = new HashMap>();
+ queryResultsMap = new HashMap>();
log.debug("Use a non-cached query results map");
} else {
log.debug("Use cached query results map");
@@ -37,7 +37,7 @@ public static HashMap> get() {
return queryResultsMap;
}
- public static void update(HashMap> queryResultsMap) {
+ public static void update(HashMap> queryResultsMap) {
if (threadLocal.get() != null) {
threadLocal.set(queryResultsMap);
log.debug("Query results map cache has been updated");
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SparqlSelectQueryResultsChecker.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SparqlSelectQueryResultsChecker.java
new file mode 100644
index 0000000000..72087f89fb
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SparqlSelectQueryResultsChecker.java
@@ -0,0 +1,217 @@
+/* $This file is distributed under the terms of the license in LICENSE$ */
+
+package edu.cornell.mannlib.vitro.webapp.auth.checks;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Optional;
+import java.util.Set;
+
+import edu.cornell.mannlib.vitro.webapp.auth.attributes.AttributeValueSet;
+import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
+import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
+import edu.cornell.mannlib.vitro.webapp.beans.SelfEditingConfiguration;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jena.query.ParameterizedSparqlString;
+import org.apache.jena.query.Query;
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.query.QueryExecutionFactory;
+import org.apache.jena.query.QueryFactory;
+import org.apache.jena.query.QuerySolution;
+import org.apache.jena.query.ResultSet;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.RDFNode;
+
+public class SparqlSelectQueryResultsChecker {
+ private static final String PROFILE_URI = "profileUri";
+ private static final String EXTERNAL_AUTH_ID = "externalAuthId";
+ private static final String MATCHING_PROPERTY_URI = "matchingPropertyUri";
+ private static final String OBJECT_URI = "objectUri";
+ private static final Log log = LogFactory.getLog(SparqlSelectQueryResultsChecker.class);
+
+ public static boolean sparqlSelectQueryResultsContain(Check check, AuthorizationRequest ar, String[] inputValues) {
+ String query = check.getConfiguration();
+ if (StringUtils.isBlank(query)) {
+ query = check.getValues().getSingleValue();
+ if (StringUtils.isBlank(query)) {
+ log.error("Sparql query is empty.");
+ return false;
+ }
+ }
+
+ AccessObject ao = ar.getAccessObject();
+ Model m = ao.getModel();
+ if (m == null) {
+ log.error("Model not provided");
+ return false;
+ }
+
+ Set comparedValues = new HashSet<>();
+
+ if (isQueryNotProvidedInConfiguration(check)) {
+ addRelatedUrisToComparedValues(ao, comparedValues);
+ } else {
+ addValuesToComparedValues(check.getValues(), comparedValues);
+ }
+
+ if (isProfileUriRelatedQuery(query)) {
+ return makeProfileUriMatchQuery(ar, query, m, comparedValues);
+ }
+
+ if (query.contains("?" + EXTERNAL_AUTH_ID) && externalAuthIdIsNotAvailable(ar)) {
+ logVariableNotAvailable(EXTERNAL_AUTH_ID);
+ return false;
+ }
+
+ if (query.contains("?" + MATCHING_PROPERTY_URI) && matchingPropertyUriIsNotAvailable()) {
+ logVariableNotAvailable(MATCHING_PROPERTY_URI);
+ return false;
+ }
+
+ Set sparqlResults = getSparqlSelectResults(m, "", query, ar);
+ sparqlResults.retainAll(comparedValues);
+ if (!sparqlResults.isEmpty()) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean makeProfileUriMatchQuery(AuthorizationRequest ar, String queryTemplate, Model m,
+ Set comparedValues) {
+ boolean result = false;
+ Set profileUris = new HashSet(ar.getEditorUris());
+ if (profileUris.isEmpty()) {
+ log.debug("Subject has no person Uri, nothing to substitute.");
+ result = false;
+ }
+ for (String profileUri : profileUris) {
+ Set sparqlResults = getSparqlSelectResults(m, profileUri, queryTemplate, ar);
+ // Return true if intersection is not empty
+ sparqlResults.retainAll(comparedValues);
+ if (!sparqlResults.isEmpty()) {
+ result = true;
+ }
+ }
+ return result;
+ }
+
+ private static void addValuesToComparedValues(AttributeValueSet values, Set comparedValues) {
+ comparedValues.addAll(values.getValues());
+ }
+
+ private static void addRelatedUrisToComparedValues(AccessObject ao, Set comparedValues) {
+ comparedValues.addAll(Arrays.asList(ao.getResourceUris()));
+ }
+
+ private static boolean isQueryNotProvidedInConfiguration(Check check) {
+ return StringUtils.isBlank(check.getConfiguration());
+ }
+
+ private static Set getSparqlSelectResults(Model model, String profileUri, String queryTemplate,
+ AuthorizationRequest ar) {
+ HashMap> queryMap = QueryResultsMapCache.get();
+ String queryMapKey = createQueryMapKey(profileUri, queryTemplate, ar);
+ HashSet results = new HashSet<>();
+ if (queryMap.containsKey(queryMapKey)) {
+ results.addAll(queryMap.get(queryMapKey));
+ return results;
+ }
+ ParameterizedSparqlString pss = new ParameterizedSparqlString();
+ pss.setCommandText(queryTemplate);
+ setVariables(profileUri, ar, pss);
+
+ String queryText = pss.toString();
+ debug("queryText: " + queryText);
+ Query query = QueryFactory.create(queryText);
+ QueryExecution queryExecution = QueryExecutionFactory.create(query, model);
+ try {
+ ResultSet resultSet = queryExecution.execSelect();
+ while (resultSet.hasNext()) {
+ QuerySolution qs = resultSet.nextSolution();
+ addSolutionValues(results, qs);
+ }
+ } catch (Exception e) {
+ log.error(e, e);
+ } finally {
+ queryExecution.close();
+ }
+ debug("query results: " + results);
+ Set queryMapValue = new HashSet<>();
+ queryMapValue.addAll(results);
+ queryMap.put(queryMapKey, queryMapValue);
+ QueryResultsMapCache.update(queryMap);
+ return results;
+ }
+
+ private static void setVariables(String profileUri, AuthorizationRequest ar, ParameterizedSparqlString pss) {
+ pss.setIri(PROFILE_URI, profileUri);
+ AccessObject object = ar.getAccessObject();
+ Optional uri = object.getUri();
+ if (uri.isPresent()) {
+ pss.setIri(OBJECT_URI, uri.get());
+ }
+ String externalAuthId = ar.getExternalAuthId();
+ if (!StringUtils.isBlank(externalAuthId)) {
+ pss.setLiteral(EXTERNAL_AUTH_ID, externalAuthId);
+ }
+ String matchingPropertyUri = SelfEditingConfiguration.getInstance().getMatchingPropertyUri();
+ if (!StringUtils.isBlank(matchingPropertyUri)) {
+ pss.setIri(MATCHING_PROPERTY_URI, matchingPropertyUri);
+ }
+ }
+
+ private static void addSolutionValues(Set results, QuerySolution qs) {
+ Iterator names = qs.varNames();
+ while (names.hasNext()) {
+ String name = names.next();
+ RDFNode node = qs.get(name);
+ if (node.isURIResource()) {
+ results.add(node.asResource().getURI());
+ } else if (node.isLiteral()) {
+ results.add(node.asLiteral().toString());
+ }
+ }
+ }
+
+ private static void debug(String queryText) {
+ if (log.isDebugEnabled()) {
+ log.debug(queryText);
+ }
+ }
+
+ private static String createQueryMapKey(String profileUri, String queryTemplate, AuthorizationRequest ar) {
+ String mapKey = queryTemplate + "." + profileUri;
+ if (queryTemplate.contains("?" + OBJECT_URI)) {
+ AccessObject object = ar.getAccessObject();
+ Optional uri = object.getUri();
+ if (uri.isPresent()) {
+ mapKey += "." + uri.get();
+ }
+ }
+ return mapKey;
+ }
+
+ private static void logVariableNotAvailable(String variable) {
+ if (log.isDebugEnabled()) {
+ log.debug(String.format("Query contains ?%s, but authorization request doesn't provide it.", variable));
+ }
+ }
+
+ private static boolean externalAuthIdIsNotAvailable(AuthorizationRequest ar) {
+ return StringUtils.isBlank(ar.getExternalAuthId());
+ }
+
+ private static boolean matchingPropertyUriIsNotAvailable() {
+ return StringUtils.isBlank(SelfEditingConfiguration.getInstance().getMatchingPropertyUri());
+ }
+
+ private static boolean isProfileUriRelatedQuery(String queryTemplate) {
+ return queryTemplate.contains("?" + PROFILE_URI);
+ }
+
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectRoleCheck.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectRoleCheck.java
index ca5cd89ac1..30653e0e03 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectRoleCheck.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectRoleCheck.java
@@ -2,11 +2,13 @@
package edu.cornell.mannlib.vitro.webapp.auth.checks;
+import java.util.ArrayList;
import java.util.List;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.Attribute;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AttributeValueSet;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
+import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -20,7 +22,10 @@ public SubjectRoleCheck(String uri, AttributeValueSet values) {
@Override
public boolean check(AuthorizationRequest ar) {
- final List inputValues = ar.getRoleUris();
+ List inputValues = new ArrayList(ar.getRoleUris());
+ if (inputValues.isEmpty()) {
+ inputValues.add(VitroVocabulary.ROLE_PUBLIC_URI);
+ }
if (AttributeValueChecker.test(this, ar, inputValues.toArray(new String[0]))) {
log.debug("Attribute match requested '" + inputValues + "'");
return true;
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectTypeCheck.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectTypeCheck.java
index 8cada0556d..94892fad34 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectTypeCheck.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/checks/SubjectTypeCheck.java
@@ -4,8 +4,6 @@
import edu.cornell.mannlib.vitro.webapp.auth.attributes.Attribute;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AttributeValueSet;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.IsRootUser;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -20,8 +18,7 @@ public SubjectTypeCheck(String uri, AttributeValueSet values) {
@Override
public boolean check(AuthorizationRequest ar) {
- IdentifierBundle ac_subject = ar.getIds();
- String inputValue = IsRootUser.isRootUser(ac_subject) ? "ROOT_USER" : "";
+ String inputValue = ar.isRootUser() ? "ROOT_USER" : "";
if (AttributeValueChecker.test(this, ar, inputValue)) {
log.debug("Attribute subject type match requested object type '");
return true;
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObject.java
index 32e003cd5e..ffd0884537 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObject.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObject.java
@@ -15,6 +15,7 @@ public abstract class AccessObject {
public static String SOME_URI = "?SOME_URI";
public static Property SOME_PREDICATE = new Property(SOME_URI);
public static String SOME_LITERAL = "?SOME_LITERAL";
+ private Model model = null;
protected AccessObjectStatement statement;
private DataProperty dataProperty;
@@ -50,16 +51,12 @@ protected void initializeStatement() {
}
}
- public void setStatementOntModel(Model ontModel) {
- initializeStatement();
- statement.setModel(ontModel);
+ public void setModel(Model ontModel) {
+ model = ontModel;
}
- public Model getStatementOntModel() {
- if (statement != null) {
- return statement.getModel();
- }
- return null;
+ public Model getModel() {
+ return model;
}
public void setStatementSubject(String subject) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObjectStatement.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObjectStatement.java
index 91872cd146..db65bfa47a 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObjectStatement.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/AccessObjectStatement.java
@@ -4,23 +4,13 @@
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType;
import edu.cornell.mannlib.vitro.webapp.beans.Property;
-import org.apache.jena.rdf.model.Model;
public class AccessObjectStatement {
- private Model model = null;
private String subject = null;
private Property predicate = null;
private String object = null;
- public Model getModel() {
- return model;
- }
-
- public void setModel(Model model) {
- this.model = model;
- }
-
public String getSubject() {
return subject;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/DataPropertyStatementAccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/DataPropertyStatementAccessObject.java
index 644b71e116..f69731b63e 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/DataPropertyStatementAccessObject.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/DataPropertyStatementAccessObject.java
@@ -15,7 +15,7 @@ public class DataPropertyStatementAccessObject extends AccessObject {
public DataPropertyStatementAccessObject(OntModel ontModel, String subjectUri, String predicateUri,
String dataValue) {
- setStatementOntModel(ontModel);
+ setModel(ontModel);
setStatementSubject(subjectUri);
setStatementPredicate(new Property(predicateUri));
setStatementObject(dataValue);
@@ -24,7 +24,7 @@ public DataPropertyStatementAccessObject(OntModel ontModel, String subjectUri, S
public DataPropertyStatementAccessObject(OntModel ontModel, String subjectUri, Property predicate,
String dataValue) {
- setStatementOntModel(ontModel);
+ setModel(ontModel);
setStatementSubject(subjectUri);
setStatementPredicate(predicate);
setStatementObject(dataValue);
@@ -32,7 +32,7 @@ public DataPropertyStatementAccessObject(OntModel ontModel, String subjectUri, P
}
public DataPropertyStatementAccessObject(OntModel ontModel, DataPropertyStatement dps) {
- setStatementOntModel(ontModel);
+ setModel(ontModel);
setStatementSubject((dps.getIndividual() == null) ? dps.getIndividualURI() : dps.getIndividual().getURI());
setStatementPredicate(new Property(dps.getDatapropURI()));
setStatementObject(dps.getData());
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxDataPropertyStatementAccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxDataPropertyStatementAccessObject.java
index 61dbfcfd1e..383f47dcc9 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxDataPropertyStatementAccessObject.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxDataPropertyStatementAccessObject.java
@@ -12,7 +12,7 @@ public class FauxDataPropertyStatementAccessObject extends AccessObject {
public FauxDataPropertyStatementAccessObject(OntModel ontModel, String subjectUri, FauxProperty predicate,
String dataValue) {
- setStatementOntModel(ontModel);
+ setModel(ontModel);
setStatementSubject(subjectUri);
this.predicate = predicate;
setStatementObject(dataValue);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxObjectPropertyStatementAccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxObjectPropertyStatementAccessObject.java
index 186a00505f..b710ae1722 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxObjectPropertyStatementAccessObject.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/FauxObjectPropertyStatementAccessObject.java
@@ -12,7 +12,7 @@ public class FauxObjectPropertyStatementAccessObject extends AccessObject {
public FauxObjectPropertyStatementAccessObject(Model ontModel, String subjectUri, FauxProperty fauxProperty,
String objectUri) {
- setStatementOntModel(ontModel);
+ setModel(ontModel);
setStatementSubject(subjectUri);
predicate = fauxProperty;
setStatementObject(objectUri);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/IndividualAccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/IndividualAccessObject.java
new file mode 100644
index 0000000000..0df44ff030
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/IndividualAccessObject.java
@@ -0,0 +1,26 @@
+package edu.cornell.mannlib.vitro.webapp.auth.objects;
+
+import java.util.Optional;
+
+import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType;
+
+public class IndividualAccessObject extends NamedAccessObject {
+
+ public IndividualAccessObject(String uri) {
+ super(uri);
+ }
+
+ @Override
+ public AccessObjectType getType() {
+ return AccessObjectType.INDIVIDUAL;
+ }
+
+ public String[] getResourceUris() {
+ Optional optionalUri = getUri();
+ if (optionalUri.isPresent()) {
+ return new String[] { optionalUri.get() };
+ } else {
+ return new String[0];
+ }
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/NamedAccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/NamedAccessObject.java
index 8b9f025ac9..f3a3eecfde 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/NamedAccessObject.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/NamedAccessObject.java
@@ -11,21 +11,13 @@
*/
public class NamedAccessObject extends AccessObject {
private final String uri;
- private AccessObjectType type;
public NamedAccessObject() {
this.uri = "";
- this.type = AccessObjectType.NAMED_OBJECT;
- }
-
- public NamedAccessObject(String uri, AccessObjectType type) {
- this.uri = uri;
- this.type = type;
}
public NamedAccessObject(String uri) {
this.uri = uri;
- this.type = AccessObjectType.NAMED_OBJECT;
}
@Override
@@ -61,7 +53,7 @@ public String toString() {
@Override
public AccessObjectType getType() {
- return type;
+ return AccessObjectType.NAMED_OBJECT;
}
private static String getShortName(String entityUri) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/ObjectPropertyStatementAccessObject.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/ObjectPropertyStatementAccessObject.java
index 87db9869a2..33de4b03ba 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/ObjectPropertyStatementAccessObject.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/objects/ObjectPropertyStatementAccessObject.java
@@ -14,7 +14,7 @@ public class ObjectPropertyStatementAccessObject extends AccessObject {
public ObjectPropertyStatementAccessObject(Model ontModel, String subjectUri, Property predicate,
String objectUri) {
- setStatementOntModel(ontModel);
+ setModel(ontModel);
setStatementSubject(subjectUri);
setStatementPredicate(predicate);
setStatementObject(objectUri);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java
index 3cfbc4a3e1..a86036dd1e 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java
@@ -75,7 +75,7 @@ public String getUri() {
private SimplePermission(String uri) {
this.uri = SimplePermission.NS + uri;
- NamedAccessObject ao = new NamedAccessObject(this.uri, AccessObjectType.NAMED_OBJECT);
+ NamedAccessObject ao = new NamedAccessObject(this.uri);
this.ACTION = new SimpleAuthorizationRequest(ao, AccessOperation.EXECUTE);
}
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/EntityPolicyController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/EntityPolicyController.java
index 98145a6f38..a37191204f 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/EntityPolicyController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/EntityPolicyController.java
@@ -4,6 +4,8 @@
import static edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary.AUTH_VOCABULARY_PREFIX;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -52,8 +54,9 @@ private static AttributeValueSetRegistry getRegistry() {
return AttributeValueSetRegistry.getInstance();
}
- public static void revokeAccess(String entityUri, AccessObjectType aot, AccessOperation ao, String role) {
- AttributeValueKey key = new AttributeValueKey(ao, aot, role, aot.toString());
+ public static void revokeAccess(String entityUri, AccessObjectType aot, AccessOperation ao, String role,
+ String... namedKeyComponents) {
+ AttributeValueKey key = new AttributeValueKey(ao, aot, role, aot.toString(), namedKeyComponents);
AttributeValueSet set = getRegistry().get(key);
if (set != null) {
if (set.contains(entityUri)) {
@@ -62,7 +65,7 @@ public static void revokeAccess(String entityUri, AccessObjectType aot, AccessOp
getLoader().updateAccessControlModel(toRemove, false);
}
} else {
- reduceInactiveValueSet(entityUri, aot, ao, role);
+ reduceInactiveValueSet(entityUri, aot, ao, role, namedKeyComponents);
}
}
@@ -71,14 +74,15 @@ private static PolicyLoader getLoader() {
}
private static void reduceInactiveValueSet(String entityUri, AccessObjectType aot, AccessOperation ao,
- String role) {
+ String role, String... namedKeyComponents) {
StringBuilder removals = new StringBuilder();
- getDataValueStatements(entityUri, aot, ao, Collections.singleton(role), removals);
+ getDataValueStatements(entityUri, aot, ao, Collections.singleton(role), removals, namedKeyComponents);
getLoader().updateAccessControlModel(removals.toString(), false);
}
- public static void grantAccess(String entityUri, AccessObjectType aot, AccessOperation ao, String role) {
- AttributeValueKey key = new AttributeValueKey(ao, aot, role, aot.toString());
+ public static void grantAccess(String entityUri, AccessObjectType aot, AccessOperation ao, String role,
+ String... namedKeyComponents) {
+ AttributeValueKey key = new AttributeValueKey(ao, aot, role, aot.toString(), namedKeyComponents);
AttributeValueSet set = getRegistry().get(key);
if (set != null) {
if (!set.contains(entityUri)) {
@@ -87,14 +91,18 @@ public static void grantAccess(String entityUri, AccessObjectType aot, AccessOpe
getLoader().updateAccessControlModel(toAdd, true);
}
} else {
- extendInactiveValueSet(entityUri, aot, ao, role);
- loadPolicy(aot, ao, role);
+ extendInactiveValueSet(entityUri, aot, ao, role, namedKeyComponents);
+ loadPolicy(aot, ao, role, namedKeyComponents);
}
}
- private static void loadPolicy(AccessObjectType aot, AccessOperation ao, String role) {
- String dataSetUri =
- getLoader().getDataSetUriByKey(new String[] { }, new String[] { ao.toString(), aot.toString(), role });
+ private static void loadPolicy(AccessObjectType aot, AccessOperation ao, String role,
+ String... namedKeyComponents) {
+ String[] ids = Arrays.copyOf(namedKeyComponents, namedKeyComponents.length + 3);
+ ids[ids.length - 1] = ao.toString();
+ ids[ids.length - 2] = aot.toString();
+ ids[ids.length - 3] = role;
+ String dataSetUri = getLoader().getDataSetUriByKey(ids);
if (dataSetUri != null) {
DynamicPolicy policy = getLoader().loadPolicyFromTemplateDataSet(dataSetUri);
if (policy != null) {
@@ -104,18 +112,19 @@ private static void loadPolicy(AccessObjectType aot, AccessOperation ao, String
}
private static void extendInactiveValueSet(String entityUri, AccessObjectType aot, AccessOperation ao,
- String role) {
+ String role, String... namedKeyComponents) {
StringBuilder additions = new StringBuilder();
- getDataValueStatements(entityUri, aot, ao, Collections.singleton(role), additions);
+ getDataValueStatements(entityUri, aot, ao, Collections.singleton(role), additions, namedKeyComponents);
getLoader().updateAccessControlModel(additions.toString(), true);
}
- public static boolean isGranted(String entityUri, AccessObjectType aot, AccessOperation ao, String role) {
+ public static boolean isGranted(String entityUri, AccessObjectType aot, AccessOperation ao, String role,
+ String... namedKeyComponents) {
if (StringUtils.isBlank(entityUri)) {
return false;
}
AttributeValueSetRegistry registry = getRegistry();
- AttributeValueKey key = new AttributeValueKey(ao, aot, role, aot.toString());
+ AttributeValueKey key = new AttributeValueKey(ao, aot, role, aot.toString(), namedKeyComponents);
AttributeValueSet set = registry.get(key);
if (set == null) {
return false;
@@ -138,12 +147,12 @@ public static List getGrantedRoles(String entityUri, AccessOperation ao,
}
public static void getDataValueStatements(String entityUri, AccessObjectType aot, AccessOperation ao,
- Set selectedRoles, StringBuilder sb) {
+ Set selectedRoles, StringBuilder sb, String... namedKeyComponents) {
if (StringUtils.isBlank(entityUri)) {
return;
}
for (String role : selectedRoles) {
- String valueSetUri = getValueSetUri(aot, ao, role);
+ String valueSetUri = getValueSetUri(aot, ao, role, namedKeyComponents);
if (valueSetUri == null) {
log.debug(String.format("Policy value set wasn't found by key:\n%s\n%s\n%s", ao, aot, role));
continue;
@@ -175,13 +184,25 @@ private static boolean isUriInTestDataset(String entityUri, AccessOperation ao,
return values.contains(entityUri);
}
- private static String getValueSetUri(AccessObjectType aot, AccessOperation ao, String role) {
- String key = aot.toString() + "." + ao.toString() + "." + role;
+ private static String getValueSetUri(AccessObjectType aot, AccessOperation ao, String role,
+ String... namedKeyComponents) {
+ String key = generateKey(aot, ao, role, namedKeyComponents);
if (policyKeyToDataValueMap.containsKey(key)) {
return policyKeyToDataValueMap.get(key);
}
- String uri = getLoader().getEntityValueSetUri(ao, aot, role);
+ String uri = getLoader().getEntityValueSetUri(ao, aot, role, namedKeyComponents);
policyKeyToDataValueMap.put(key, uri);
return uri;
}
+
+ private static String generateKey(AccessObjectType aot, AccessOperation ao, String role,
+ String[] namedKeyComponents) {
+ String key = aot.toString() + "." + ao.toString() + "." + role;
+ if (namedKeyComponents.length > 0) {
+ List namedKeys = new ArrayList<>(Arrays.asList(namedKeyComponents));
+ Collections.sort(namedKeys);
+ key = key + String.join(".", namedKeys);
+ }
+ return key;
+ }
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/InvalidSolutionException.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/InvalidSolutionException.java
new file mode 100644
index 0000000000..0cb3bbecff
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/InvalidSolutionException.java
@@ -0,0 +1,9 @@
+package edu.cornell.mannlib.vitro.webapp.auth.policy;
+
+public class InvalidSolutionException extends RuntimeException {
+
+ public InvalidSolutionException(String string) {
+ super(string);
+ }
+
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java
index 435d193eab..9661ffb03b 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionLogger.java
@@ -7,11 +7,11 @@
import java.util.regex.Pattern;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Policy;
import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision;
import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
import org.apache.commons.logging.Log;
@@ -33,7 +33,7 @@ public class PolicyDecisionLogger {
private final DeveloperSettings settings;
private final AccessObject object;
private final AccessOperation operation;
- private final IdentifierBundle whoToAuth;
+ private final UserAccount whoToAuth;
private final boolean enabled;
@@ -43,7 +43,7 @@ public class PolicyDecisionLogger {
public PolicyDecisionLogger(AuthorizationRequest ar) {
this.settings = DeveloperSettings.getInstance();
- this.whoToAuth = ar.getIds();
+ this.whoToAuth = ar.getUserAccount();
this.object = ar.getAccessObject();
this.operation = ar.getAccessOperation();
@@ -67,7 +67,7 @@ private boolean figureEnabled() {
*/
private boolean passesUserRestriction() {
Pattern userRestriction = compilePatternFromSetting(Key.AUTHORIZATION_LOG_DECISIONS_USER_RESTRICTION);
- return userRestriction == null || userRestriction.matcher(String.valueOf(whoToAuth)).find();
+ return userRestriction == null || userRestriction.matcher(String.valueOf(whoToAuth.getUri())).find();
}
/**
@@ -126,7 +126,7 @@ public void log(Policy policy, PolicyDecision pd) {
if (passesRestrictions(String.valueOf(policy), pd)) {
if (this.includeIdentifiers) {
log.info(String.format("Decision on %s %s by %s was %s; user is %s", this.operation, this.object,
- policy.getShortUri(), pd, this.whoToAuth));
+ policy.getShortUri(), pd, getUser()));
} else {
log.info(String.format("Decision on %s %s by %s was %s", this.operation, this.object,
policy.getShortUri(), pd));
@@ -156,10 +156,14 @@ private boolean isInconclusive(PolicyDecision pd) {
public void logNoDecision(PolicyDecision pd) {
if (enabled) {
if (this.includeIdentifiers) {
- log.info(pd.getMessage() + "; user is " + this.whoToAuth);
+ log.info(pd.getMessage() + "; user is " + getUser());
} else {
log.info(pd.getMessage());
}
}
}
+
+ private String getUser() {
+ return whoToAuth.getLastName() + ", " + whoToAuth.getFirstName() + " uri:'" + whoToAuth.getUri() + "'";
+ }
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionPoint.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionPoint.java
index 1e317a7a4a..a90dec6b7c 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionPoint.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyDecisionPoint.java
@@ -40,8 +40,8 @@ public static PolicyDecision decide(AuthorizationRequest ar) {
}
}
- pd = new BasicPolicyDecision(DecisionResult.INCONCLUSIVE,
- "No policy returned a conclusive decision on " + ar.getAccessObject());
+ pd = new BasicPolicyDecision(DecisionResult.INCONCLUSIVE, String.format(
+ "No policy returned a conclusive decision on %s of %s", ar.getAccessOperation(), ar.getAccessObject()));
logger.logNoDecision(pd);
return pd;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java
index b515a4863e..641e146c31 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java
@@ -21,13 +21,8 @@
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.rdf.model.StmtIterator;
-
+import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.ActiveIdentifierBundleFactories;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.HasAssociatedIndividual;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.IdentifierPermissionSetProvider;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.objects.DataPropertyStatementAccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.objects.ObjectPropertyStatementAccessObject;
@@ -46,84 +41,77 @@
* are authorized by current policy.
*/
public class PolicyHelper {
- private static final Log log = LogFactory.getLog(PolicyHelper.class);
+ private static final Log log = LogFactory.getLog(PolicyHelper.class);
+ private static UserAccount nullUserAccount = new UserAccount();
- public static boolean isAuthorizedForActions(HttpServletRequest req, AccessObject ar, AccessOperation operation) {
- IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(req);
- return actionRequestIsAuthorized(ids, ar, operation);
- }
-
- public static boolean isAuthorizedForActions(IdentifierBundle ids, AccessObject ar, AccessOperation op) {
- return actionRequestIsAuthorized(ids, ar, op);
+ public static UserAccount getUserAccount(HttpServletRequest req) {
+ UserAccount userAccount = LoginStatusBean.getCurrentUser(req);
+ if (userAccount == null) {
+ return nullUserAccount ;
+ }
+ return userAccount;
}
- public static boolean isAuthorizedForActions(IdentifierBundle ids, AuthorizationRequest ar) {
- if (ar == null) {
- log.error("AuthorizationRequest is null");
- return false;
- }
- if (ar.getPredefinedDecision() != DecisionResult.INCONCLUSIVE){
- return ar.getPredefinedDecision() == DecisionResult.AUTHORIZED;
- }
- if (ar.getWrapType() != null) {
- return processUnwrappedAuthorizationRequest(ar, ids);
- }
- return actionRequestIsAuthorized(ids, ar.getAccessObject(), ar.getAccessOperation());
+ public static boolean isAuthorizedForActions(UserAccount user, AccessObject ar, AccessOperation op) {
+ return actionRequestIsAuthorized(user, ar, op);
}
public static boolean isAuthorizedForActions(HttpServletRequest req, AuthorizationRequest ar) {
+ UserAccount userAccount = getUserAccount(req);
+ return isAuthorizedForActions(userAccount, ar);
+ }
+
+ public static boolean isAuthorizedForActions(HttpServletRequest req, AccessObject ar, AccessOperation operation) {
+ UserAccount userAccount = getUserAccount(req);
+ return actionRequestIsAuthorized(userAccount, ar, operation);
+ }
+
+ public static boolean isAuthorizedForActions(UserAccount userAccount, AuthorizationRequest ar) {
if (ar == null) {
log.error("AuthorizationRequest is null");
return false;
}
- if (ar.getPredefinedDecision() != DecisionResult.INCONCLUSIVE){
+ if (ar.getPredefinedDecision() != DecisionResult.INCONCLUSIVE) {
return ar.getPredefinedDecision() == DecisionResult.AUTHORIZED;
}
- IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(req);
if (ar.getWrapType() != null) {
- return processUnwrappedAuthorizationRequest(ar, ids);
+ return processUnwrappedAuthorizationRequest(ar, userAccount);
}
- return actionRequestIsAuthorized(ids, ar.getAccessObject(), ar.getAccessOperation());
+ return actionRequestIsAuthorized(userAccount, ar.getAccessObject(), ar.getAccessOperation());
}
- private static boolean processUnwrappedAuthorizationRequest(AuthorizationRequest ar, IdentifierBundle ids) {
+ private static boolean processUnwrappedAuthorizationRequest(AuthorizationRequest ar, UserAccount userAccount) {
List items = ar.getItems();
boolean result = false;
if (WRAP_TYPE.OR == ar.getWrapType()) {
- for (AuthorizationRequest item : items ) {
- result = result || isAuthorizedForActions(ids, item);
- }
+ for (AuthorizationRequest item : items) {
+ result = result || isAuthorizedForActions(userAccount, item);
+ }
} else {
result = true;
- for (AuthorizationRequest item : items ) {
- result = result && isAuthorizedForActions(ids, item);
- }
+ for (AuthorizationRequest item : items) {
+ result = result && isAuthorizedForActions(userAccount, item);
+ }
}
return result;
}
- private static boolean actionRequestIsAuthorized(IdentifierBundle ids, AccessObject ao, AccessOperation operation) {
- if (operation == null) {
- log.error("Opeartion is null, accessObject " + ao );
- return false;
- }
+ private static boolean actionRequestIsAuthorized(UserAccount userAccount, AccessObject ao,
+ AccessOperation operation) {
+ if (operation == null) {
+ log.error("Opeartion is null, accessObject " + ao);
+ return false;
+ }
if (ao == null) {
log.error("Access object is null, operation " + operation);
return false;
}
AuthorizationRequest ar = new SimpleAuthorizationRequest(ao, operation);
- Collection uris = IdentifierPermissionSetProvider.getPermissionSetUris(ids);
- if (uris.isEmpty()) {
- uris.add(VitroVocabulary.VITRO_AUTH + "PUBLIC");
- }
- ar.setRoleUris(new ArrayList(uris));
-
- ar.setIds(ids);
- ar.setEditorUris(new ArrayList(HasAssociatedIndividual.getIndividualUris(ids)));
- PolicyDecision decision = PolicyDecisionPoint.decide(ar);
- debug(ar, decision);
+ ar.setUserAccount(userAccount);
+ PolicyDecision decision = PolicyDecisionPoint.decide(ar);
+ debug(ar, decision);
return decision.getDecisionResult() == DecisionResult.AUTHORIZED;
- }
+ }
private static void debug(AuthorizationRequest ar, PolicyDecision decision) {
if (log.isDebugEnabled()) {
@@ -166,8 +154,7 @@ public static boolean isAuthorizedForActions(HttpServletRequest req, String emai
+ "account URI: %s", email, uri));
// figure out if that account can do the actions
- IdentifierBundle ids = ActiveIdentifierBundleFactories.getUserIdentifierBundle(user);
- return isAuthorizedForActions(ids, ar);
+ return isAuthorizedForActions(user, ar);
} catch (Exception ex) {
log.error("Error while attempting to authorize actions " + ar, ex);
return false;
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyLoader.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyLoader.java
index bc70622e17..90a4fa62ce 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyLoader.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyLoader.java
@@ -7,6 +7,7 @@
import java.io.InputStream;
import java.io.StringWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
@@ -95,38 +96,51 @@ public class PolicyLoader {
private static final String NO_DATASET_RULES_QUERY = ""
+ "prefix auth: \n"
+ "prefix access: \n"
- + "SELECT DISTINCT ?policyUri ?rule ?check ?testId ?typeId ?value ?lit_value ?decision_id \n"
+ + "SELECT DISTINCT ?policyUri ?rule ?check ?config ?attributeValue "
+ + "?testId ?typeId ?value ?lit_value ?decision_id \n"
+ "WHERE {\n"
+ " GRAPH {\n"
- + "?policy a access:Policy .\n"
- + "?policy access:hasRule ?rule . \n"
- + "?rule access:requiresCheck ?check .\n"
- + "OPTIONAL {\n"
- + " ?check access:useOperator ?checkTest .\n"
- + " OPTIONAL {\n"
- + " ?checkTest access:id ?testId . \n"
- + " }\n"
- + "}"
- + "OPTIONAL {\n"
- + " ?check access:hasTypeToCheck ?checkType . \n"
- + " OPTIONAL {\n"
- + " ?checkType access:id ?typeId . \n"
- + " }\n"
- + "}\n"
- + "OPTIONAL {\n"
- + " ?rule access:hasDecision ?decision . \n"
- + " ?decision access:id ?decision_id . \n"
- + "}\n"
- + "?check access:value ?value . \n"
- + "OPTIONAL {?value access:id ?lit_value . }\n"
+ + " ?policy a access:Policy .\n"
+ + " ?policy access:hasRule ?rule . \n"
+ + " ?rule access:requiresCheck ?check .\n"
+ + " OPTIONAL {\n"
+ + " ?check access:useOperator ?checkTest .\n"
+ + " OPTIONAL {\n"
+ + " ?checkTest access:id ?testId . \n"
+ + " }\n"
+ + " }\n"
+ + " OPTIONAL {\n"
+ + " ?check access:hasTypeToCheck ?checkType . \n"
+ + " OPTIONAL {\n"
+ + " ?checkType access:id ?typeId . \n"
+ + " }\n"
+ + " }\n"
+ + " OPTIONAL {\n"
+ + " ?check access:useConfiguration ?configUri . \n"
+ + " ?configUri access:id ?config . \n"
+ + " }\n"
+ + " OPTIONAL {\n"
+ + " ?rule access:hasDecision ?decision . \n"
+ + " ?decision access:id ?decision_id . \n"
+ + " }\n"
+ + " {\n"
+ + " ?check access:values ?attributeValue .\n"
+ + " ?attributeValue access:value ?value .\n"
+ + " OPTIONAL { ?value access:id ?lit_value . }\n"
+ + " }\n"
+ + " UNION \n"
+ + " {\n"
+ + " ?check access:value ?value .\n"
+ + " OPTIONAL {?value access:id ?lit_value . }\n"
+ + " }\n"
+ + " BIND(?policy as ?policyUri)\n"
+ " }\n"
- + "BIND(?policy as ?policyUri)\n"
+ "} ORDER BY ?rule ?check";
private static final String DATASET_RULES_QUERY = ""
+ "prefix auth: \n"
+ "prefix access: \n"
- + "SELECT DISTINCT ?policyUri ?rule ?check ?testId ?typeId ?value ?lit_value ?decision_id "
+ + "SELECT DISTINCT ?policyUri ?rule ?check ?config ?testId ?typeId ?value ?lit_value ?decision_id "
+ " ?dataSetUri ?attributeValue ?setElementsType \n"
+ "WHERE {\n"
+ " GRAPH {\n"
@@ -148,6 +162,10 @@ public class PolicyLoader {
+ " }\n"
+ " }\n"
+ " OPTIONAL {\n"
+ + " ?check access:useConfiguration ?configUri . \n"
+ + " ?configUri access:id ?config . \n"
+ + " }\n"
+ + " OPTIONAL {\n"
+ " ?rule access:hasDecision ?decision .\n"
+ " ?decision access:id ?decision_id .\n"
+ " }\n"
@@ -262,6 +280,10 @@ public class PolicyLoader {
+ " ?keyComponent a access:SubjectRoleUri .\n"
+ " BIND('SUBJECT_ROLE_URI' as ?type)\n"
+ " }\n"
+ + " OPTIONAL {\n"
+ + " ?keyComponent a access:NamedKeyComponent .\n"
+ + " BIND('NAMED_KEY_COMPONENT' as ?type)\n"
+ + " }\n"
+ " }\n"
+ "}\n";
@@ -443,8 +465,7 @@ protected void processQuerySolution(QuerySolution qs) {
public Set getDataSetValues(AccessOperation ao, AccessObjectType aot, String role) {
Set values = new HashSet<>();
long expectedSize = 3;
- String queryText = getDataSetByKeyQuery(new String[] {},
- new String[] { ao.toString(), aot.toString(), role });
+ String queryText = getDataSetByKeyQuery(ao.toString(), aot.toString(), role);
ParameterizedSparqlString pss = new ParameterizedSparqlString(queryText);
pss.setLiteral("setElementsId", aot.toString());
queryText = pss.toString();
@@ -478,9 +499,14 @@ protected void processQuerySolution(QuerySolution qs) {
return values;
}
- public String getEntityValueSetUri(AccessOperation ao, AccessObjectType aot, String role) {
- long expectedSize = 3;
- String queryText = getDataSetByKeyQuery(new String[] { }, new String[] { ao.toString(), aot.toString(), role });
+ public String getEntityValueSetUri(AccessOperation ao, AccessObjectType aot, String role,
+ String... namedKeyComponents) {
+ int expectedSize = 3 + namedKeyComponents.length;
+ String[] ids = Arrays.copyOf(namedKeyComponents, expectedSize);
+ ids[ids.length - 1] = ao.toString();
+ ids[ids.length - 2] = aot.toString();
+ ids[ids.length - 3] = role;
+ String queryText = getDataSetByKeyQuery(ids);
ParameterizedSparqlString pss = new ParameterizedSparqlString(queryText);
pss.setLiteral("setElementsId", aot.toString());
queryText = pss.toString();
@@ -496,7 +522,7 @@ protected void processQuerySolution(QuerySolution qs) {
}
long keySize = qs.getLiteral("keySize").getLong();
if (expectedSize != keySize) {
- log.error("wrong key size. Expected " + expectedSize + ". Actual " + keySize );
+ debug("wrong key size. Expected " + expectedSize + ". Actual " + keySize );
return;
}
uri[0] = qs.getResource("valueSet").getURI();
@@ -579,11 +605,8 @@ private static String getPolicyDataSetValueStatementByKeyQuery(String entityUri,
return query.toString();
}
- private static String getDataSetByKeyQuery(String[] uris, String[] ids) {
+ private static String getDataSetByKeyQuery(String... ids) {
StringBuilder query = new StringBuilder(policyKeyTemplatePrefix);
- for (String uri : uris) {
- query.append(String.format(" ?dataSetKeyUri access:hasKeyComponent <%s> . \n", uri));
- }
int i = 0;
for (String id : ids) {
query.append(String.format(
@@ -626,9 +649,7 @@ private void loadRulesWithoutDataSet(String policyUri, Map r
@Override
protected void processQuerySolution(QuerySolution qs) {
try {
- if (isInvalidPolicySolution(qs)) {
- throw new Exception();
- }
+ isInvalidPolicySolution(qs);
if (isRuleContinues(rules, qs)) {
String ruleUri = qs.getResource("rule").getURI();
populateRule(rules.get(ruleUri), qs, null);
@@ -666,9 +687,7 @@ private void loadRulesForDataSet(Map rules, String dataSetUr
@Override
protected void processQuerySolution(QuerySolution qs) {
try {
- if (isInvalidPolicySolution(qs)) {
- throw new Exception();
- }
+ isInvalidPolicySolution(qs);
if (isRuleContinues(rules, qs)) {
String ruleUri = qs.getResource("rule").getURI();
populateRule(rules.get(ruleUri), qs, dataSetKey);
@@ -748,36 +767,33 @@ private static void populateRule(AccessRule ar, QuerySolution qs, AttributeValue
}
}
- private static boolean isInvalidPolicySolution(QuerySolution qs) {
+ private static void isInvalidPolicySolution(QuerySolution qs) {
if (!qs.contains("policyUri") || !qs.get("policyUri").isResource()) {
- log.debug("Query solution doesn't contain policy uri");
- return true;
+ throw new InvalidSolutionException("Query solution doesn't contain policy uri");
}
String policy = qs.get("policyUri").asResource().getURI();
if (!qs.contains("rule") || !qs.get("rule").isResource()) {
- log.debug(String.format("Query solution for policy <%s> doesn't contain rule uri", policy));
- return true;
+ throw new InvalidSolutionException(
+ String.format("Query solution for policy <%s> doesn't contain rule uri", policy));
}
String rule = qs.get("rule").asResource().getLocalName();
if (!qs.contains("check") || !qs.get("check").isResource()) {
- log.debug(String.format("Query solution for policy <%s> doesn't contain check uri", policy));
- return true;
+ throw new InvalidSolutionException(
+ String.format("Query solution for policy <%s> doesn't contain check uri", policy));
}
String check = qs.get("check").asResource().getLocalName();
if (!qs.contains("value")) {
- log.debug(String.format("Query solution for policy <%s> rule %s check %s doesn't contain value", policy,
- rule, check));
- return true;
+ throw new InvalidSolutionException(String.format(
+ "Query solution for policy <%s> rule %s check %s doesn't contain value", policy, rule, check));
}
if (!qs.contains("typeId") || !qs.get("typeId").isLiteral()) {
- log.debug(String.format("Query solution for policy <%s> doesn't contain check type id", policy));
- return true;
+ throw new InvalidSolutionException(
+ String.format("Query solution for policy <%s> doesn't contain check type id", policy));
}
if (!qs.contains("testId") || !qs.get("testId").isLiteral()) {
- log.debug(String.format("Query solution for policy <%s> doesn't contain check test id", policy));
- return true;
+ throw new InvalidSolutionException(
+ String.format("Query solution for policy <%s> doesn't contain check test id", policy));
}
- return false;
}
private static void debug(String template, Object... objects) {
@@ -801,9 +817,9 @@ private ChangeSet makeChangeSet() {
return cs;
}
- public String getDataSetUriByKey(String[] uris, String[] ids) {
- long expectedSize = uris.length + ids.length;
- final String queryText = getDataSetByKeyQuery(uris, ids);
+ public String getDataSetUriByKey(String... ids) {
+ long expectedSize = ids.length;
+ final String queryText = getDataSetByKeyQuery(ids);
debug("SPARQL Query to get policy data set values:\n %s", queryText);
String[] uri = new String[1];
try {
@@ -851,6 +867,9 @@ protected void processQuerySolution(QuerySolution qs) {
if (Attribute.SUBJECT_ROLE_URI.toString().equals(type)) {
compositeKey.setRole(id);
}
+ if ("NAMED_KEY_COMPONENT".equals(type)) {
+ compositeKey.addNamedKey(id);
+ }
}
} else {
//assume keyComponent is a role
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AndAuthorizationRequest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AndAuthorizationRequest.java
index a4b84adc44..e377eda442 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AndAuthorizationRequest.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AndAuthorizationRequest.java
@@ -6,7 +6,6 @@
import java.util.List;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
public class AndAuthorizationRequest extends AuthorizationRequest {
@@ -43,9 +42,4 @@ public AccessOperation getAccessOperation() {
return null;
}
- @Override
- public IdentifierBundle getIds() {
- return null;
- }
-
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizationRequest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizationRequest.java
index 788cb9290c..60a3f3e3c8 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizationRequest.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/AuthorizationRequest.java
@@ -4,11 +4,12 @@
import java.util.Collections;
import java.util.List;
+import java.util.Set;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.DecisionResult;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
public abstract class AuthorizationRequest {
@@ -17,14 +18,7 @@ public abstract class AuthorizationRequest {
public static enum WRAP_TYPE { AND , OR } ;
- IdentifierBundle ids;
- private List editorUris = Collections.emptyList();
- List roleUris = Collections.emptyList();
-
-
- public void setRoleUris(List roleUris) {
- this.roleUris = roleUris;
- }
+ private UserAccount userAccount;
public WRAP_TYPE getWrapType() {
return null;
@@ -42,24 +36,24 @@ public List getItems() {
public abstract AccessOperation getAccessOperation();
- public IdentifierBundle getIds() {
- return ids;
+ public Set getRoleUris() {
+ return userAccount.getPermissionSetUris();
}
-
- public List getRoleUris() {
- return roleUris;
+
+ public void setUserAccount(UserAccount userAccount) {
+ this.userAccount = userAccount;
}
- public void setIds(IdentifierBundle ids) {
- this.ids = ids;
+ public UserAccount getUserAccount() {
+ return userAccount;
}
- public void setEditorUris(List list) {
- editorUris = list;
+ public Set getEditorUris() {
+ return userAccount.getProxiedIndividualUris();
}
-
- public List getEditorUris(){
- return editorUris;
+
+ public String getExternalAuthId() {
+ return userAccount.getExternalAuthId();
}
public static AuthorizationRequest or(AuthorizationRequest fist, AuthorizationRequest second) {
@@ -79,14 +73,13 @@ public AuthorizationRequest and(AuthorizationRequest second) {
@Override
public String toString() {
String result = "";
- if (!getRoleUris().isEmpty()) {
- result += String.format("User with roles '%s' ", getRoleUris().toString());
- }
- if (!getEditorUris().isEmpty()) {
- result += String.format(" profile uris '%s' ", getEditorUris().toString());
- }
result += String.format(" requested '%s' ", getAccessOperation());
result += String.format(" on '%s' ", getAccessObject());
return result;
}
+
+ public boolean isRootUser() {
+ return userAccount.isRootUser();
+ }
+
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ForbiddenAuthorizationRequest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ForbiddenAuthorizationRequest.java
index 62b8d6ff68..a80a51ccf4 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ForbiddenAuthorizationRequest.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ForbiddenAuthorizationRequest.java
@@ -3,7 +3,6 @@
package edu.cornell.mannlib.vitro.webapp.auth.requestedAction;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.DecisionResult;
@@ -30,8 +29,4 @@ public AccessOperation getAccessOperation() {
return null;
}
- @Override
- public IdentifierBundle getIds() {
- return null;
- }
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/OrAuthorizationRequest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/OrAuthorizationRequest.java
index e59991120c..56d3f55e3e 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/OrAuthorizationRequest.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/OrAuthorizationRequest.java
@@ -6,7 +6,6 @@
import java.util.List;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
public class OrAuthorizationRequest extends AuthorizationRequest {
@@ -43,9 +42,4 @@ public AccessOperation getAccessOperation() {
return null;
}
- @Override
- public IdentifierBundle getIds() {
- return null;
- }
-
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/AbstractCaptchaProvider.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/AbstractCaptchaProvider.java
new file mode 100644
index 0000000000..f79e0276a9
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/AbstractCaptchaProvider.java
@@ -0,0 +1,47 @@
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * The AbstractCaptchaProvider is an abstract class providing a base structure for captcha providers.
+ * It includes methods for generating refresh challenges, adding captcha-related fields to page context,
+ * and validating user inputs against captcha challenges.
+ *
+ * @see CaptchaBundle
+ */
+public abstract class AbstractCaptchaProvider {
+
+ protected static final Log log = LogFactory.getLog(AbstractCaptchaProvider.class.getName());
+
+ /**
+ * Generates a refresh challenge, typically used for updating the captcha displayed on the page.
+ * Returns empty CaptchaBundle in case of 3rd party implementations
+ *
+ * @return CaptchaBundle containing the refreshed captcha challenge.
+ * @throws IOException If there is an issue generating the refresh challenge.
+ */
+ abstract CaptchaBundle generateRefreshChallenge() throws IOException;
+
+ /**
+ * Adds captcha-related fields to the provided page context, allowing integration with web pages.
+ *
+ * @param context The context map representing the page's variables.
+ * @throws IOException If there is an issue adding captcha-related fields to the page context.
+ */
+ abstract void addCaptchaRelatedFieldsToPageContext(Map context) throws IOException;
+
+ /**
+ * Validates the user input against a captcha challenge identified by the provided challengeId.
+ *
+ * @param captchaInput The user's input to be validated.
+ * @param challengeId The identifier of the captcha challenge (ignored in case of 3rd party implementations).
+ * @return True if the input is valid, false otherwise.
+ */
+ boolean validateCaptcha(String captchaInput, String challengeId) {
+ return false;
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaBundle.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaBundle.java
new file mode 100644
index 0000000000..5466d5b9a3
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaBundle.java
@@ -0,0 +1,57 @@
+/* $This file is distributed under the terms of the license in LICENSE$ */
+
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.util.Objects;
+
+/**
+ * Represents a bundle containing a CAPTCHA image in Base64 format, the associated code,
+ * and a unique challenge identifier.
+ *
+ * @author Ivan Mrsulja
+ * @version 1.0
+ */
+public class CaptchaBundle {
+
+ private final String b64Image;
+
+ private final String code;
+
+ private final String challengeId;
+
+
+ public CaptchaBundle(String b64Image, String code, String challengeId) {
+ this.b64Image = b64Image;
+ this.code = code;
+ this.challengeId = challengeId;
+ }
+
+ public String getB64Image() {
+ return b64Image;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getCaptchaId() {
+ return challengeId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CaptchaBundle that = (CaptchaBundle) o;
+ return Objects.equals(code, that.code) && Objects.equals(challengeId, that.challengeId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(code, challengeId);
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaDifficulty.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaDifficulty.java
new file mode 100644
index 0000000000..5b2bd17f09
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaDifficulty.java
@@ -0,0 +1,6 @@
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+public enum CaptchaDifficulty {
+ EASY,
+ HARD
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaImplementation.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaImplementation.java
new file mode 100644
index 0000000000..7f55b43d8f
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaImplementation.java
@@ -0,0 +1,7 @@
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+public enum CaptchaImplementation {
+ RECAPTCHAV2,
+ NANOCAPTCHA,
+ NONE;
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaServiceBean.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaServiceBean.java
new file mode 100644
index 0000000000..e5a92b039b
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/CaptchaServiceBean.java
@@ -0,0 +1,138 @@
+/* $This file is distributed under the terms of the license in LICENSE$ */
+
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+
+import com.google.common.base.Strings;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+
+
+/**
+ * This class provides services related to CAPTCHA challenges and validation.
+ * It includes method delegates for generating challenges, validating challenge responses,
+ * and managing CAPTCHA challenges for specific hosts.
+ *
+ * @author Ivan Mrsulja
+ * @version 1.0
+ */
+public class CaptchaServiceBean {
+
+ private static final Cache captchaChallenges =
+ CacheBuilder.newBuilder()
+ .maximumSize(1000)
+ .expireAfterWrite(5, TimeUnit.MINUTES)
+ .build();
+
+ private static AbstractCaptchaProvider captchaProvider;
+
+ static {
+ CaptchaImplementation captchaImplementation = getCaptchaImpl();
+ switch (captchaImplementation) {
+ case RECAPTCHAV2:
+ captchaProvider = new Recaptchav2Provider();
+ break;
+ case NANOCAPTCHA:
+ captchaProvider = new NanocaptchaProvider();
+ break;
+ case NONE:
+ captchaProvider = new DummyCaptchaProvider();
+ break;
+ }
+ }
+
+ /**
+ * Generates a new CAPTCHA challenge (returns empty CaptchaBundle for 3rd party providers).
+ *
+ * @return A CaptchaBundle containing the CAPTCHA image in Base64 format, the content,
+ * and a unique identifier.
+ * @throws IOException If an error occurs during image conversion.
+ */
+ public static CaptchaBundle generateRefreshedChallenge() throws IOException {
+ return captchaProvider.generateRefreshChallenge();
+ }
+
+ /**
+ * Retrieves a CAPTCHA challenge for a specific host based on the provided CAPTCHA ID
+ * Removes the challenge from the storage after retrieval.
+ *
+ * @param captchaId The CAPTCHA ID to match.
+ * @return An Optional containing the CaptchaBundle if a matching challenge is found,
+ * or an empty Optional otherwise.
+ */
+ public static Optional getChallenge(String captchaId) {
+ CaptchaBundle challengeForHost = captchaChallenges.getIfPresent(captchaId);
+ if (challengeForHost == null) {
+ return Optional.empty();
+ }
+
+ captchaChallenges.invalidate(captchaId);
+
+ return Optional.of(challengeForHost);
+ }
+
+ /**
+ * Gets the map containing CAPTCHA challenges for different hosts.
+ *
+ * @return A ConcurrentHashMap with host addresses as keys and CaptchaBundle objects as values.
+ */
+ public static Cache getCaptchaChallenges() {
+ return captchaChallenges;
+ }
+
+ /**
+ * Retrieves the configured captcha implementation based on the application's configuration properties.
+ * If captcha functionality is disabled, returns NONE. If the captcha implementation is not specified,
+ * defaults to NANOCAPTCHA.
+ *
+ * @return The selected captcha implementation (NANOCAPTCHA, RECAPTCHAv2, or NONE).
+ */
+ public static CaptchaImplementation getCaptchaImpl() {
+ String captchaEnabledSetting = ConfigurationProperties.getInstance().getProperty("captcha.enabled");
+
+ if (Objects.nonNull(captchaEnabledSetting) && !Boolean.parseBoolean(captchaEnabledSetting)) {
+ return CaptchaImplementation.NONE;
+ }
+
+ String captchaImplSetting =
+ ConfigurationProperties.getInstance().getProperty("captcha.implementation");
+
+ if (Strings.isNullOrEmpty(captchaImplSetting) ||
+ (!captchaImplSetting.equalsIgnoreCase(CaptchaImplementation.RECAPTCHAV2.name()) &&
+ !captchaImplSetting.equalsIgnoreCase(CaptchaImplementation.NANOCAPTCHA.name()))) {
+ captchaImplSetting = CaptchaImplementation.NANOCAPTCHA.name();
+ }
+
+ return CaptchaImplementation.valueOf(captchaImplSetting.toUpperCase());
+ }
+
+ /**
+ * Adds captcha-related fields to the given page context map. The specific fields added depend on the
+ * configured captcha implementation.
+ *
+ * @param context The page context map to which captcha-related fields are added.
+ * @throws IOException If there is an IO error during captcha challenge generation.
+ */
+ public static void addCaptchaRelatedFieldsToPageContext(Map context) throws IOException {
+ CaptchaImplementation captchaImpl = getCaptchaImpl();
+ context.put("captchaToUse", captchaImpl.name());
+ captchaProvider.addCaptchaRelatedFieldsToPageContext(context);
+ }
+
+ /**
+ * Validates a user's captcha input.
+ *
+ * @param captchaInput The user's input for the captcha challenge.
+ * @param challengeId The unique identifier for the challenge (if captcha is 3rd party, this param is ignored).
+ * @return {@code true} if the captcha input is valid, {@code false} otherwise.
+ */
+ public static boolean validateCaptcha(String captchaInput, String challengeId) {
+ return captchaProvider.validateCaptcha(captchaInput, challengeId);
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/DummyCaptchaProvider.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/DummyCaptchaProvider.java
new file mode 100644
index 0000000000..923f6fcb13
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/DummyCaptchaProvider.java
@@ -0,0 +1,29 @@
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.util.Map;
+
+/**
+ * DummyCaptchaProvider is a concrete implementation of AbstractCaptchaProvider,
+ * serving as a fallback when CAPTCHA is disabled. Validation will always pass,
+ * in order to fulfill validation logic.
+ *
+ * @see AbstractCaptchaProvider
+ * @see CaptchaBundle
+ */
+public class DummyCaptchaProvider extends AbstractCaptchaProvider {
+
+ @Override
+ CaptchaBundle generateRefreshChallenge() {
+ return new CaptchaBundle("", "", ""); // No refresh challenges if there is no implementation
+ }
+
+ @Override
+ void addCaptchaRelatedFieldsToPageContext(Map context) {
+ // No added fields necessary if there is no implementation
+ }
+
+ @Override
+ boolean validateCaptcha(String captchaInput, String challengeId) {
+ return true; // validation always passes
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/NanocaptchaProvider.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/NanocaptchaProvider.java
new file mode 100644
index 0000000000..630a8bcf27
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/NanocaptchaProvider.java
@@ -0,0 +1,126 @@
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.awt.Color;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.SecureRandom;
+import java.util.Base64;
+import java.util.Map;
+import java.util.Optional;
+import java.util.UUID;
+
+import javax.imageio.ImageIO;
+
+import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+import net.logicsquad.nanocaptcha.image.ImageCaptcha;
+import net.logicsquad.nanocaptcha.image.backgrounds.GradiatedBackgroundProducer;
+import net.logicsquad.nanocaptcha.image.filter.FishEyeImageFilter;
+import net.logicsquad.nanocaptcha.image.filter.StretchImageFilter;
+import net.logicsquad.nanocaptcha.image.noise.CurvedLineNoiseProducer;
+import net.logicsquad.nanocaptcha.image.noise.StraightLineNoiseProducer;
+
+/**
+ * NanocaptchaProvider generates and manages captcha challenges using Nanocaptcha.
+ * This class extends AbstractCaptchaProvider and supports easy and hard difficulty levels.
+ *
+ * @see AbstractCaptchaProvider
+ * @see CaptchaBundle
+ * @see CaptchaServiceBean
+ * @see CaptchaDifficulty
+ */
+public class NanocaptchaProvider extends AbstractCaptchaProvider {
+
+ private final SecureRandom random = new SecureRandom();
+
+ @Override
+ public CaptchaBundle generateRefreshChallenge() throws IOException {
+ return generateChallenge();
+ }
+
+ @Override
+ public void addCaptchaRelatedFieldsToPageContext(Map context) throws IOException {
+ CaptchaBundle captchaChallenge = generateChallenge();
+ CaptchaServiceBean.getCaptchaChallenges().put(captchaChallenge.getCaptchaId(), captchaChallenge);
+
+ context.put("challenge", captchaChallenge.getB64Image());
+ context.put("challengeId", captchaChallenge.getCaptchaId());
+ }
+
+ @Override
+ public boolean validateCaptcha(String captchaInput, String challengeId) {
+ Optional optionalChallenge = CaptchaServiceBean.getChallenge(challengeId);
+ return optionalChallenge.isPresent() && optionalChallenge.get().getCode().equals(captchaInput);
+ }
+
+ /**
+ * Generates a captcha challenge.
+ *
+ * @return CaptchaBundle containing the captcha image encoded in Base64,
+ * captcha content, and a randomly generated UUID.
+ * @throws IOException If there is an issue generating the captcha.
+ */
+ private CaptchaBundle generateChallenge() throws IOException {
+ CaptchaDifficulty difficulty = getCaptchaDifficulty();
+ ImageCaptcha.Builder imageCaptchaBuilder = new ImageCaptcha.Builder(220, 85)
+ .addContent(random.nextInt(2) + 5)
+ .addBackground(new GradiatedBackgroundProducer())
+ .addNoise(new StraightLineNoiseProducer(getRandomColor(), 2))
+ .addFilter(new StretchImageFilter())
+ .addBorder();
+
+ if (difficulty.equals(CaptchaDifficulty.HARD)) {
+ imageCaptchaBuilder
+ .addNoise(new CurvedLineNoiseProducer(getRandomColor(), 2f))
+ .addFilter(new StretchImageFilter())
+ .addFilter(new FishEyeImageFilter())
+ .build();
+ }
+
+ ImageCaptcha imageCaptcha = imageCaptchaBuilder.build();
+ return new CaptchaBundle(convertToBase64(imageCaptcha.getImage()), imageCaptcha.getContent(),
+ UUID.randomUUID().toString());
+ }
+
+ /**
+ * Converts a BufferedImage object to Base64 format.
+ *
+ * @param image The BufferedImage to convert.
+ * @return The Base64-encoded string representation of the image.
+ * @throws IOException If an error occurs during image conversion.
+ */
+ private String convertToBase64(BufferedImage image) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ImageIO.write(image, "png", baos);
+ byte[] imageBytes = baos.toByteArray();
+
+ return Base64.getEncoder().encodeToString(imageBytes);
+ }
+
+ /**
+ * Retrieves the configured difficulty level for generating captchas.
+ * If the difficulty level is not specified or is not HARD, the default difficulty is set to EASY.
+ *
+ * @return The difficulty level for captcha generation (EASY or HARD).
+ */
+ private CaptchaDifficulty getCaptchaDifficulty() {
+ String difficulty = ConfigurationProperties.getInstance().getProperty("nanocaptcha.difficulty");
+ try {
+ return CaptchaDifficulty.valueOf(difficulty.toUpperCase());
+ } catch (NullPointerException | IllegalArgumentException e) {
+ return CaptchaDifficulty.EASY;
+ }
+ }
+
+ /**
+ * Generates a random Color object.
+ *
+ * @return A randomly generated Color object.
+ */
+ private Color getRandomColor() {
+ int r = random.nextInt(256);
+ int g = random.nextInt(256);
+ int b = random.nextInt(256);
+ return new Color(r, g, b);
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ReCaptchaResponse.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ReCaptchaResponse.java
new file mode 100644
index 0000000000..ae1420e383
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/ReCaptchaResponse.java
@@ -0,0 +1,64 @@
+/* $This file is distributed under the terms of the license in LICENSE$ */
+
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.util.Date;
+
+/**
+ * Represents the response from Google's reCAPTCHA API.
+ * It includes information about the success of the reCAPTCHA verification,
+ * the timestamp of the challenge, and the hostname associated with the verification.
+ *
+ * @author Ivan Mrsulja
+ * @version 1.0
+ */
+public class ReCaptchaResponse {
+
+ private boolean success;
+
+ private Date challenge_ts;
+
+ private String hostname;
+
+ public ReCaptchaResponse() {
+ }
+
+ public ReCaptchaResponse(boolean success, Date challenge_ts, String hostname) {
+ this.success = success;
+ this.challenge_ts = challenge_ts;
+ this.hostname = hostname;
+ }
+
+ public boolean isSuccess() {
+ return success;
+ }
+
+ public void setSuccess(boolean success) {
+ this.success = success;
+ }
+
+ public Date getChallenge_ts() {
+ return challenge_ts;
+ }
+
+ public void setChallenge_ts(Date challenge_ts) {
+ this.challenge_ts = challenge_ts;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public void setHostname(String hostname) {
+ this.hostname = hostname;
+ }
+
+ @Override
+ public String toString() {
+ return "ReCaptchaResponse{" +
+ "success=" + success +
+ ", challenge_ts=" + challenge_ts +
+ ", hostname='" + hostname + '\'' +
+ '}';
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/Recaptchav2Provider.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/Recaptchav2Provider.java
new file mode 100644
index 0000000000..163746885f
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/Recaptchav2Provider.java
@@ -0,0 +1,69 @@
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.Objects;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+
+/**
+ * Recaptchav2Provider generates and manages captcha challenges using Google RECAPTCHAv2.
+ * This class extends AbstractCaptchaProvider.
+ *
+ * @see AbstractCaptchaProvider
+ * @see CaptchaBundle
+ */
+public class Recaptchav2Provider extends AbstractCaptchaProvider {
+
+ @Override
+ public CaptchaBundle generateRefreshChallenge() {
+ return new CaptchaBundle("", "", ""); // RECAPTCHAv2 does not generate refresh challenges on backend side
+ }
+
+ @Override
+ public void addCaptchaRelatedFieldsToPageContext(Map context) {
+ context.put("siteKey",
+ Objects.requireNonNull(ConfigurationProperties.getInstance().getProperty("recaptcha.siteKey"),
+ "You have to provide a site key through configuration file."));
+ }
+
+ @Override
+ public boolean validateCaptcha(String captchaInput, String challengeId) {
+ return validateReCaptcha(captchaInput);
+ }
+
+ /**
+ * Validates a reCAPTCHA response using Google's reCAPTCHA API.
+ *
+ * @param recaptchaResponse The reCAPTCHA response to validate.
+ * @return True if the reCAPTCHA response is valid, false otherwise.
+ */
+ public boolean validateReCaptcha(String recaptchaResponse) {
+ String secretKey =
+ Objects.requireNonNull(ConfigurationProperties.getInstance().getProperty("recaptcha.secretKey"),
+ "You have to provide a secret key through configuration file.");
+ String verificationUrl =
+ "https://www.google.com/recaptcha/api/siteverify?secret=" + secretKey + "&response=" + recaptchaResponse;
+
+ try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
+ HttpGet verificationRequest = new HttpGet(verificationUrl);
+ HttpResponse verificationResponse = httpClient.execute(verificationRequest);
+
+ String responseBody = EntityUtils.toString(verificationResponse.getEntity());
+ ObjectMapper objectMapper = new ObjectMapper();
+ ReCaptchaResponse response = objectMapper.readValue(responseBody, ReCaptchaResponse.class);
+
+ return response.isSuccess();
+ } catch (IOException e) {
+ log.warn("ReCaptcha validation failed.");
+ }
+
+ return false;
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/RefreshCaptchaController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/RefreshCaptchaController.java
new file mode 100644
index 0000000000..3e4c7a3e38
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/beans/RefreshCaptchaController.java
@@ -0,0 +1,33 @@
+/* $This file is distributed under the terms of the license in LICENSE$ */
+
+package edu.cornell.mannlib.vitro.webapp.beans;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.twelvemonkeys.servlet.HttpServlet;
+
+@WebServlet(name = "refreshCaptcha", urlPatterns = {"/refreshCaptcha"}, loadOnStartup = 5)
+public class RefreshCaptchaController extends HttpServlet {
+
+ protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ throws ServletException, IOException {
+ String oldChallengeId = request.getParameter("oldChallengeId");
+
+ response.setContentType("application/json");
+ PrintWriter out = response.getWriter();
+
+ CaptchaBundle newChallenge = CaptchaServiceBean.generateRefreshedChallenge();
+ CaptchaServiceBean.getCaptchaChallenges().invalidate(oldChallengeId);
+ CaptchaServiceBean.getCaptchaChallenges().put(newChallenge.getCaptchaId(), newChallenge);
+
+ out.println("{\"challenge\": \"" + newChallenge.getB64Image() + "\", \"challengeId\": \"" +
+ newChallenge.getCaptchaId() + "\"}");
+ }
+
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java
index 8e22ae0439..4440e72af2 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/accounts/user/UserAccountsResetPasswordPage.java
@@ -6,7 +6,6 @@
import java.util.HashMap;
import java.util.Map;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ForgotPasswordController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ForgotPasswordController.java
new file mode 100644
index 0000000000..1ee5f7d380
--- /dev/null
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/ForgotPasswordController.java
@@ -0,0 +1,413 @@
+/* $This file is distributed under the terms of the license in LICENSE$ */
+
+package edu.cornell.mannlib.vitro.webapp.controller.authenticate;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Random;
+
+import javax.mail.Message;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServletRequest;
+
+import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
+import edu.cornell.mannlib.vitro.webapp.beans.CaptchaImplementation;
+import edu.cornell.mannlib.vitro.webapp.beans.CaptchaServiceBean;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
+import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
+import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
+import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
+import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
+import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
+import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
+import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
+import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
+import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailMessage;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+@WebServlet(name = "forgotPassword", urlPatterns = {"/forgotPassword"})
+public class ForgotPasswordController extends FreemarkerHttpServlet {
+
+ private static final String RESET_PASSWORD_URL = "/accounts/resetPassword";
+
+ private static final int DAYS_TO_USE_PASSWORD_LINK = 5;
+
+ private static final String TEMPLATE_NAME = "userAccounts-resetPasswordRequest.ftl";
+ private static final Log log = LogFactory.getLog(ForgotPasswordController.class.getName());
+ private CaptchaImplementation captchaImpl;
+
+ @Override
+ protected ResponseValues processRequest(VitroRequest vreq) throws Exception {
+ // Random time interval sleep so attacker can't calculate whether provided email is bound to an account or not
+ sleepForRandomTime();
+
+ captchaImpl = CaptchaServiceBean.getCaptchaImpl();
+
+ Map dataContext = new HashMap<>();
+ setCommonValues(dataContext, vreq);
+ CaptchaServiceBean.addCaptchaRelatedFieldsToPageContext(dataContext);
+ boolean isEnabled = isFunctionalityEnabled();
+ dataContext.put("isEnabled", isEnabled);
+
+ // Handle GET request (display the form) or print error message if functionality is disabled
+ if (!isEnabled || vreq.getMethod().equalsIgnoreCase("GET")) {
+ return showForm(dataContext);
+ }
+
+ return handlePostRequest(vreq, dataContext);
+ }
+
+ /**
+ * Handles a POST request for password reset. Validates the captcha, checks for spam mitigation,
+ * processes the password change request, and notifies the user.
+ *
+ * @param vreq The Vitro request object.
+ * @param dataContext A map containing additional data context for processing the request.
+ * @return A response containing the appropriate values for rendering the result.
+ */
+ private ResponseValues handlePostRequest(VitroRequest vreq, Map dataContext) {
+ UserAccountsDao userAccountsDao = constructUserAccountsDao();
+ I18nBundle i18n = I18n.bundle(vreq);
+
+ // Check for impossible length input
+ String rawEmailInput = vreq.getParameter("email");
+ if (rawEmailInput != null && rawEmailInput.length() > 320) {
+ dataContext.put("errorMessage", i18n.text("error_invalid_email"));
+ return showForm(dataContext);
+ }
+
+ String email = getNonNullTrimmedParameterValue(vreq, "email");
+ if (!email.matches("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")) {
+ dataContext.put("errorMessage", i18n.text("error_invalid_email", email));
+ return showForm(dataContext);
+ }
+
+ if (!captchaIsValid(vreq)) {
+ dataContext.put("wrongCaptcha", true);
+ dataContext.put("emailValue", email);
+ return showForm(dataContext);
+ }
+
+ dataContext.put("showPasswordChangeForm", false);
+
+ Optional userAccountOptional = getAccountForInternalAuth(email);
+ if (userAccountOptional.isPresent()) {
+ requestPasswordChange(userAccountOptional.get(), userAccountsDao);
+ notifyUser(userAccountOptional.get(), i18n, vreq);
+ } else {
+ log.info("User tried to reset password with an unassociated email: " + email);
+ }
+
+ return emailSentNotification(dataContext, i18n, email);
+ }
+
+ /**
+ * Validates the captcha input based on the configured captcha implementation.
+ * The method extracts the necessary input parameters from the provided VitroRequest.
+ * For reCAPTCHA v2, it uses the "g-recaptcha-response" parameter.
+ * For other captcha implementations (e.g., NanoCaptcha), it uses "userSolution" for captcha input
+ * and "challengeId" for challenge identification.
+ *
+ * @param vreq the VitroRequest containing the captcha parameters
+ * @return true if the captcha is valid, false otherwise
+ */
+ private boolean captchaIsValid(VitroRequest vreq) {
+ String captchaInput;
+ String challengeId;
+ switch (captchaImpl) {
+ case RECAPTCHAV2:
+ captchaInput = getNonNullTrimmedParameterValue(vreq, "g-recaptcha-response");
+ challengeId = "";
+ break;
+ case NANOCAPTCHA:
+ default:
+ captchaInput = getNonNullTrimmedParameterValue(vreq, "userSolution");
+ challengeId = getNonNullTrimmedParameterValue(vreq, "challengeId");
+ }
+
+ return CaptchaServiceBean.validateCaptcha(captchaInput, challengeId);
+ }
+
+ /**
+ * Notifies the user about a password reset request by sending an email.
+ *
+ * @param userAccount The user account for which the password reset is requested.
+ * @param i18n The internationalization bundle for language translation.
+ * @param vreq The VitroRequest object containing request information.
+ */
+ private void notifyUser(UserAccount userAccount, I18nBundle i18n, VitroRequest vreq) {
+ Map body = new HashMap<>();
+ body.put("userAccount", userAccount);
+ body.put("passwordLink",
+ buildResetPasswordLink(userAccount.getEmailAddress(), userAccount.getEmailKey(), vreq));
+ body.put("siteName", vreq.getAppBean().getApplicationName());
+ body.put("subject", i18n.text("password_reset_pending_email_subject"));
+ body.put("textMessage", i18n.text("password_reset_pending_email_plain_text"));
+ body.put("htmlMessage", i18n.text("password_reset_pending_email_html_text"));
+
+ sendEmailMessage(body, vreq, userAccount.getEmailAddress());
+
+ if (adminShouldBeNotified()) {
+ notifyAdmin(userAccount, i18n, vreq);
+ }
+ }
+
+ /**
+ * Notifies the administrator about a password reset for a user account.
+ *
+ * @param userAccount The user account for which the password is being reset.
+ * @param i18n The internationalization bundle for translating messages.
+ * @param vreq The Vitro request object.
+ */
+ private void notifyAdmin(UserAccount userAccount, I18nBundle i18n, VitroRequest vreq) {
+ Map body = new HashMap<>();
+ body.put("userAccount", userAccount);
+ body.put("siteName", vreq.getAppBean().getApplicationName());
+ body.put("subject", i18n.text("password_reset_admin_notification_email_subject"));
+ body.put("textMessage", i18n.text("password_reset_admin_notification_email_plain_text"));
+ body.put("htmlMessage", i18n.text("password_reset_admin_notification_email_html"));
+
+ String adminEmailAddress =
+ Objects.requireNonNull(ConfigurationProperties.getInstance().getProperty("email.replyTo"));
+
+ sendEmailMessage(body, vreq, adminEmailAddress);
+ }
+
+ /**
+ * Sends an email message using Freemarker templates.
+ *
+ * @param body The map containing email body information.
+ * @param vreq The Vitro request object.
+ * @param recipientEmail The email address of the recipient.
+ */
+ private void sendEmailMessage(Map body, VitroRequest vreq, String recipientEmail) {
+ FreemarkerEmailMessage emailMessage = FreemarkerEmailFactory
+ .createNewMessage(vreq);
+ emailMessage.addRecipient(Message.RecipientType.TO, recipientEmail);
+ emailMessage.setBodyMap(body);
+ emailMessage.processTemplate();
+ emailMessage.send();
+ }
+
+ /**
+ * Checks whether the admin should be notified about password resets.
+ *
+ * @return True if the admin should be notified, false otherwise.
+ */
+ private boolean adminShouldBeNotified() {
+ String adminShouldBeNotified =
+ ConfigurationProperties.getInstance().getProperty("authentication.forgotPassword.notify-admin");
+
+ return Boolean.parseBoolean(adminShouldBeNotified);
+ }
+
+ /**
+ * Sets common values in the data context for rendering templates.
+ *
+ * @param dataContext The data context to store common values.
+ * @param vreq The VitroRequest object containing request information.
+ */
+ private void setCommonValues(Map dataContext, VitroRequest vreq) {
+ ApplicationBean appBean = vreq.getAppBean();
+
+ dataContext.put("forgotPasswordUrl", getForgotPasswordUrl(vreq));
+ dataContext.put("contactUrl", getContactUrl(vreq));
+ dataContext.put("contextPath", vreq.getContextPath());
+ dataContext.put("emailConfigured", FreemarkerEmailFactory.isConfigured(vreq));
+ dataContext.put("emailValue", "");
+ dataContext.put("contactEmailConfigured", StringUtils.isNotBlank(appBean.getContactMail()));
+ dataContext.put("wrongCaptcha", false);
+ }
+
+ /**
+ * Creates a response for indicating that an email has been successfully sent.
+ *
+ * @param dataContext The data context for rendering the response.
+ * @param i18n The internationalization bundle for language translation.
+ * @param email The email address to which the notification was sent.
+ * @return A ResponseValues object containing the email sent message.
+ */
+ private ResponseValues emailSentNotification(Map dataContext, I18nBundle i18n, String email) {
+ dataContext.put("message", i18n.text("password_reset_email_sent", email));
+ return new TemplateResponseValues(TEMPLATE_NAME, dataContext);
+ }
+
+ /**
+ * Shows a password change form.
+ *
+ * @param dataContext The data context for rendering the form.
+ * @return A ResponseValues object for displaying the form.
+ */
+ private ResponseValues showForm(Map dataContext) {
+ dataContext.put("showPasswordChangeForm", true);
+ return new TemplateResponseValues(TEMPLATE_NAME, dataContext);
+ }
+
+ /**
+ * Constructs a UserAccountsDao using the provided VitroRequest.
+ *
+ * @return A UserAccountsDao instance for user account operations.
+ */
+ private UserAccountsDao constructUserAccountsDao() {
+ WebappDaoFactory wdf = ModelAccess.getInstance().getWebappDaoFactory();
+ return wdf.getUserAccountsDao();
+ }
+
+ /**
+ * Requests a password change for a user account by updating necessary account properties.
+ *
+ * @param userAccount The user account for which the password change is requested.
+ * @param userAccountsDao The data access object for user accounts.
+ */
+ private void requestPasswordChange(UserAccount userAccount, UserAccountsDao userAccountsDao) {
+ userAccount.setPasswordLinkExpires(calculateExpirationDate().getTime());
+ userAccount.generateEmailKey();
+ userAccountsDao.updateUserAccount(userAccount);
+ }
+
+ /**
+ * Retrieves a user account for internal authentication based on the provided email address.
+ *
+ * @param emailAddress The email address associated with the user account.
+ * @return An Optional containing the user account if found,
+ * or empty if not found or if the UserAccountsDao is null.
+ */
+ private Optional getAccountForInternalAuth(String emailAddress) {
+ UserAccountsDao userAccountsDao = getUserAccountsDao();
+ if (userAccountsDao == null) {
+ return Optional.empty();
+ }
+
+ return Optional.ofNullable(userAccountsDao.getUserAccountByEmail(emailAddress));
+ }
+
+ /**
+ * Retrieves a UserAccountsDao instance from the provided HttpServletRequest.
+ *
+ * @return A UserAccountsDao instance for user account operations.
+ */
+ private UserAccountsDao getUserAccountsDao() {
+ UserAccountsDao userAccountsDao = getWebappDaoFactory()
+ .getUserAccountsDao();
+ if (userAccountsDao == null) {
+ log.error("getUserAccountsDao: no UserAccountsDao");
+ }
+
+ return userAccountsDao;
+ }
+
+ /**
+ * Retrieves a WebappDaoFactory instance from the provided HttpServletRequest.
+ *
+ * @return A WebappDaoFactory instance for database access.
+ */
+ private WebappDaoFactory getWebappDaoFactory() {
+ return ModelAccess.getInstance().getWebappDaoFactory();
+ }
+
+ /**
+ * Builds a reset password link using the provided email and key, and the VitroRequest information.
+ *
+ * @param email The email address of the user.
+ * @param key The key for resetting the password.
+ * @param vreq The VitroRequest object containing request information.
+ * @return The reset password link as a string.
+ */
+ private String buildResetPasswordLink(String email, String key, VitroRequest vreq) {
+ try {
+ String relativeUrl = UrlBuilder.getUrl(RESET_PASSWORD_URL, "user", email, "key", key);
+
+ URL context = new URL(vreq.getRequestURL().toString());
+ URL url = new URL(context, relativeUrl);
+ return url.toExternalForm();
+ } catch (MalformedURLException e) {
+ return "error_creating_password_link";
+ }
+ }
+
+ /**
+ * Calculates the expiration date for a password reset link.
+ *
+ * @return The expiration date as a Date object.
+ */
+ private Date calculateExpirationDate() {
+ Calendar c = Calendar.getInstance();
+ c.add(Calendar.DATE, DAYS_TO_USE_PASSWORD_LINK);
+ return c.getTime();
+ }
+
+ /**
+ * Retrieves the URL for the "Forgot Password" page from the VitroRequest context.
+ *
+ * @param request The VitroRequest object containing request information.
+ * @return The URL for the "Forgot Password" page as a string.
+ */
+ private String getForgotPasswordUrl(VitroRequest request) {
+ String contextPath = request.getContextPath();
+ return contextPath + "/forgotPassword";
+ }
+
+ /**
+ * Retrieves the URL for the "Contact" page from the VitroRequest context.
+ *
+ * @param request The VitroRequest object containing request information.
+ * @return The URL for the "Contact" page as a string.
+ */
+ private String getContactUrl(VitroRequest request) {
+ String contextPath = request.getContextPath();
+ return contextPath + "/contact";
+ }
+
+ /**
+ * Checks whether the functionality for password recovery is enabled based on the configuration.
+ *
+ * @return 'true' if the functionality is enabled, 'false' otherwise.
+ */
+ private boolean isFunctionalityEnabled() {
+ String enabled = ConfigurationProperties.getInstance().getProperty("authentication.forgotPassword");
+ return enabled != null && enabled.equalsIgnoreCase("enabled");
+ }
+
+ /**
+ * Causes the current thread to sleep for a random duration to mitigate time-based attacks.
+ */
+ private void sleepForRandomTime() {
+ Random random = new Random();
+ int minSleepTime = 100;
+ int maxSleepTime = 500;
+ int randomSleepTime = random.nextInt(maxSleepTime - minSleepTime + 1) + minSleepTime;
+
+ try {
+ Thread.sleep(randomSleepTime);
+ } catch (InterruptedException e) {
+ log.error(randomSleepTime + "ms sleep time for mitigating time-based attacks was interrupted.");
+ }
+ }
+
+ /**
+ * Retrieves the non-null and trimmed value of the specified request parameter.
+ * If the parameter value is null, an empty string is returned.
+ * Leading and trailing whitespace is removed from the parameter value.
+ *
+ * @param request the HttpServletRequest containing the parameters
+ * @param parameterKey the key of the parameter to retrieve
+ * @return the non-null and trimmed value of the specified request parameter,
+ * or an empty string if the parameter is null
+ */
+ private String getNonNullTrimmedParameterValue(HttpServletRequest request, String parameterKey) {
+ String parameterValue = request.getParameter(parameterKey);
+ return (parameterValue == null) ? "" : parameterValue.trim();
+ }
+}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/RestrictedAuthenticator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/RestrictedAuthenticator.java
index 783acd1405..96481ac22f 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/RestrictedAuthenticator.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/authenticate/RestrictedAuthenticator.java
@@ -7,8 +7,6 @@
import javax.servlet.http.HttpServletRequest;
import edu.cornell.mannlib.vedit.beans.LoginStatusBean.AuthenticationSource;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.ArrayIdentifierBundle;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
@@ -47,11 +45,7 @@ public boolean isUserPermittedToLogin(UserAccount userAccount) {
return false;
}
- ArrayIdentifierBundle ids = new ArrayIdentifierBundle();
- ids.addAll(getIdsForUserAccount(userAccount));
- ids.addAll(RequestIdentifiers.getIdBundleForRequest(req));
-
- return PolicyHelper.isAuthorizedForActions(ids, SimplePermission.LOGIN_DURING_MAINTENANCE.ACTION);
+ return PolicyHelper.isAuthorizedForActions(userAccount, SimplePermission.LOGIN_DURING_MAINTENANCE.ACTION);
}
@Override
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java
index 7153ddaf9f..ef8bd4c53b 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/DatapropRetryController.java
@@ -35,6 +35,7 @@
import edu.cornell.mannlib.vitro.webapp.dao.DatatypeDao;
import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
@@ -182,8 +183,9 @@ public void doPost (HttpServletRequest request, HttpServletResponse response) {
request.setAttribute("title","Data Property Editing Form");
request.setAttribute("_action",action);
request.setAttribute("unqualifiedClassName","DatatypeProperty");
-
+ request.setAttribute("i18n", I18n.bundle(vreq));
addAccessAttributes(request, objectForEditing.getURI(), AccessObjectType.DATA_PROPERTY);
+ addNotRelatedPropertySuppressions(request, objectForEditing.getURI(), AccessObjectType.DATA_PROPERTY);
setRequestAttributes(request,epo);
try {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/EntityRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/EntityRetryController.java
index 926545550d..9bbcf58e92 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/EntityRetryController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/EntityRetryController.java
@@ -37,6 +37,7 @@
import edu.cornell.mannlib.vedit.forwarder.impl.UrlForwarder;
import edu.cornell.mannlib.vedit.util.FormUtils;
import edu.cornell.mannlib.vedit.validator.impl.RequiredFieldValidator;
+import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessObjectType;
import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission;
import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
import edu.cornell.mannlib.vitro.webapp.beans.DataProperty;
@@ -53,6 +54,7 @@
import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
import edu.cornell.mannlib.vitro.webapp.edit.listener.impl.IndividualDataPropertyStatementProcessor;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
@WebServlet(name = "EntityRetryController", urlPatterns = {"/entity_retry"} )
public class EntityRetryController extends BaseEditController {
@@ -297,6 +299,9 @@ public void doPost (HttpServletRequest request, HttpServletResponse response) {
// request.setAttribute("bodyAttr"," onLoad=\"monikerInit()\"");
request.setAttribute("_action",action);
request.setAttribute("unqualifiedClassName","Individual");
+ addUriSuppressions(request, individualForEditing.getURI(), AccessObjectType.INDIVIDUAL);
+ request.setAttribute("i18n", I18n.bundle(vreq));
+
setRequestAttributes(request,epo);
try {
JSPPageHandler.renderBasicPage(request, response, "/templates/edit/formBasic.jsp");
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java
index 00dd403109..4035ff210a 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/FauxPropertyRetryController.java
@@ -43,6 +43,7 @@
import edu.cornell.mannlib.vitro.webapp.dao.FauxPropertyDao;
import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
/**
@@ -81,6 +82,8 @@ public void doPost(HttpServletRequest req, HttpServletResponse response) {
req.setAttribute("_faux_property_type", aot);
addAccessAttributes(req, populator.beanForEditing.getConfigUri(), aot);
+ addNotRelatedPropertySuppressions(req, populator.beanForEditing.getConfigUri(), aot);
+ req.setAttribute("i18n", I18n.bundle(req));
setRequestAttributes(req, epo);
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java
index a269a9c5f9..e09f06da60 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/PropertyRetryController.java
@@ -34,6 +34,7 @@
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.ObjectPropertyDao;
import edu.cornell.mannlib.vitro.webapp.dao.OntologyDao;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
public class PropertyRetryController extends BaseEditController {
@@ -182,8 +183,11 @@ public void doPost (HttpServletRequest req, HttpServletResponse response) {
request.setAttribute("scripts","/templates/edit/formBasic.js");
request.setAttribute("title","Property Editing Form");
request.setAttribute("_action",action);
+ request.setAttribute("i18n", I18n.bundle(request));
addAccessAttributes(request, propertyForEditing.getURI(), AccessObjectType.OBJECT_PROPERTY);
+ addNotRelatedPropertySuppressions(request, propertyForEditing.getURI(), AccessObjectType.OBJECT_PROPERTY);
+
setRequestAttributes(request,epo);
try {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java
index 0ba8117966..0952bda7a7 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/edit/VclassRetryController.java
@@ -35,6 +35,7 @@
import edu.cornell.mannlib.vitro.webapp.dao.VClassDao;
import edu.cornell.mannlib.vitro.webapp.dao.VClassGroupDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
@WebServlet(name = "VclassRetryController", urlPatterns = {"/vclass_retry"} )
@@ -161,8 +162,12 @@ public void doPost (HttpServletRequest req, HttpServletResponse response) {
request.setAttribute("title","Class Editing Form");
request.setAttribute("_action",action);
request.setAttribute("unqualifiedClassName","VClass");
+ request.setAttribute("i18n", I18n.bundle(request));
addAccessAttributes(request, vclassForEditing.getURI(), AccessObjectType.CLASS);
+ addTypeSuppressions(request, vclassForEditing.getURI(), AccessObjectType.INDIVIDUAL);
+ addNotRelatedTypeSuppressions(request, vclassForEditing.getURI(), AccessObjectType.INDIVIDUAL);
+
setRequestAttributes(request,epo);
try {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactFormController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactFormController.java
index 9c33959562..68168b9249 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactFormController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactFormController.java
@@ -2,20 +2,21 @@
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
+import javax.servlet.annotation.WebServlet;
import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
+import edu.cornell.mannlib.vitro.webapp.beans.CaptchaServiceBean;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
-
-import javax.servlet.annotation.WebServlet;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
/**
* Controller for comments ("contact us") page
@@ -30,14 +31,14 @@ public class ContactFormController extends FreemarkerHttpServlet {
private static final String TEMPLATE_DEFAULT = "contactForm-form.ftl";
private static final String TEMPLATE_ERROR = "contactForm-error.ftl";
+
@Override
protected String getTitle(String siteName, VitroRequest vreq) {
return siteName + " Feedback Form";
}
@Override
- protected ResponseValues processRequest(VitroRequest vreq) {
-
+ protected ResponseValues processRequest(VitroRequest vreq) throws IOException {
ApplicationBean appBean = vreq.getAppBean();
String templateName;
@@ -58,7 +59,9 @@ else if (StringUtils.isBlank(appBean.getContactMail())) {
}
else {
+ CaptchaServiceBean.addCaptchaRelatedFieldsToPageContext(body);
+ body.put("contextPath", vreq.getContextPath());
body.put("formAction", "submitFeedback");
if (vreq.getHeader("Referer") == null) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactMailController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactMailController.java
index 1444e9115b..9361d51974 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactMailController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/ContactMailController.java
@@ -11,6 +11,8 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
import javax.mail.Address;
import javax.mail.Message;
@@ -24,28 +26,31 @@
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
import edu.cornell.mannlib.vitro.webapp.application.ApplicationUtils;
import edu.cornell.mannlib.vitro.webapp.beans.ApplicationBean;
+import edu.cornell.mannlib.vitro.webapp.beans.CaptchaImplementation;
+import edu.cornell.mannlib.vitro.webapp.beans.CaptchaServiceBean;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.TemplateProcessingHelper.TemplateProcessingException;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
import edu.cornell.mannlib.vitro.webapp.email.FreemarkerEmailFactory;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
+import edu.cornell.mannlib.vitro.webapp.i18n.I18nBundle;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
@WebServlet(name = "sendMail", urlPatterns = {"/submitFeedback"}, loadOnStartup = 5)
public class ContactMailController extends FreemarkerHttpServlet {
- private static final Log log = LogFactory
- .getLog(ContactMailController.class);
+ private static final Log log = LogFactory
+ .getLog(ContactMailController.class);
private static final long serialVersionUID = 1L;
- private final static String SPAM_MESSAGE = "Your message was flagged as spam.";
+ private final static String SPAM_MESSAGE = "Your message was flagged as spam.";
- private final static String WEB_USERNAME_PARAM = "webusername";
+ private final static String WEB_USERNAME_PARAM = "webusername";
private final static String WEB_USEREMAIL_PARAM = "webuseremail";
- private final static String COMMENTS_PARAM = "s34gfd88p9x1";
+ private final static String COMMENTS_PARAM = "s34gfd88p9x1";
private final static String TEMPLATE_CONFIRMATION = "contactForm-confirmation.ftl";
private final static String TEMPLATE_EMAIL = "contactForm-email.ftl";
@@ -53,135 +58,148 @@ public class ContactMailController extends FreemarkerHttpServlet {
private final static String TEMPLATE_ERROR = "contactForm-error.ftl";
private final static String TEMPLATE_FORM = "contactForm-form.ftl";
- private static final String EMAIL_JOURNAL_FILE_DIR = "emailJournal";
- private static final String EMAIL_JOURNAL_FILE_NAME = "contactFormEmails.html";
+ private static final String EMAIL_JOURNAL_FILE_DIR = "emailJournal";
+ private static final String EMAIL_JOURNAL_FILE_NAME = "contactFormEmails.html";
+
+ private CaptchaImplementation captchaImpl;
- @Override
+ @Override
protected String getTitle(String siteName, VitroRequest vreq) {
return siteName + " Feedback Form";
}
@Override
- protected ResponseValues processRequest(VitroRequest vreq) {
- if (!FreemarkerEmailFactory.isConfigured(vreq)) {
- return errorNoSmtpServer();
- }
-
- String[] recipients = figureRecipients(vreq);
- if (recipients.length == 0) {
- return errorNoRecipients();
- }
-
- String webusername = nonNullAndTrim(vreq, WEB_USERNAME_PARAM);
- String webuseremail = nonNullAndTrim(vreq, WEB_USEREMAIL_PARAM);
- String comments = nonNullAndTrim(vreq, COMMENTS_PARAM);
- String formType = nonNullAndTrim(vreq, "DeliveryType");
- String captchaInput = nonNullAndTrim(vreq, "defaultReal");
- String captchaDisplay = nonNullAndTrim(vreq, "defaultRealHash");
-
- String errorMsg = validateInput(webusername, webuseremail, comments, captchaInput, captchaDisplay);
-
- if ( errorMsg != null) {
- return errorParametersNotValid(errorMsg, webusername, webuseremail, comments);
- }
-
- String spamReason = checkForSpam(comments, formType);
- if (spamReason != null) {
- return errorSpam();
- }
-
- return processValidRequest(vreq, webusername, webuseremail, recipients, comments);
- }
-
- private String[] figureRecipients(VitroRequest vreq) {
- String contactMailAddresses = vreq.getAppBean().getContactMail().trim();
- if ((contactMailAddresses == null) || contactMailAddresses.isEmpty()) {
- return new String[0];
- }
-
- return contactMailAddresses.split(",");
- }
-
- private ResponseValues processValidRequest(VitroRequest vreq,
- String webusername, String webuseremail, String[] recipients,
- String comments) throws Error {
- String statusMsg = null; // holds the error status
-
- ApplicationBean appBean = vreq.getAppBean();
- String deliveryfrom = "Message from the " + appBean.getApplicationName() + " Contact Form";
-
- String originalReferer = getOriginalRefererFromSession(vreq);
-
- String msgText = composeEmail(webusername, webuseremail, comments,
- deliveryfrom, originalReferer, vreq.getRemoteAddr(), vreq);
-
- try {
- // Write the message to the journal file
- FileWriter fw = new FileWriter(locateTheJournalFile(), true);
- PrintWriter outFile = new PrintWriter(fw);
- writeBackupCopy(outFile, msgText, vreq);
-
- try {
- // Send the message
- Session s = FreemarkerEmailFactory.getEmailSession(vreq);
- sendMessage(s, webuseremail, webusername, recipients, deliveryfrom, msgText);
- } catch (AddressException e) {
- statusMsg = "Please supply a valid email address.";
- outFile.println( statusMsg );
- outFile.println( e.getMessage() );
- } catch (SendFailedException e) {
- statusMsg = "The system was unable to deliver your mail. Please try again later. [SEND FAILED]";
- outFile.println( statusMsg );
- outFile.println( e.getMessage() );
- } catch (MessagingException e) {
- statusMsg = "The system was unable to deliver your mail. Please try again later. [MESSAGING]";
- outFile.println( statusMsg );
- outFile.println( e.getMessage() );
- e.printStackTrace();
- }
-
- outFile.close();
- }
- catch (IOException e){
- log.error("Can't open file to write email backup");
- }
-
- if (statusMsg == null) {
- // Message was sent successfully
- return new TemplateResponseValues(TEMPLATE_CONFIRMATION);
- } else {
- Map body = new HashMap();
- body.put("errorMessage", statusMsg);
- return new TemplateResponseValues(TEMPLATE_ERROR, body);
- }
- }
-
- /**
- * The journal file belongs in a sub-directory of the Vitro home directory.
- * If the sub-directory doesn't exist, create it.
- */
- private File locateTheJournalFile() {
- File homeDir = ApplicationUtils.instance().getHomeDirectory().getPath().toFile();
- File journalDir = new File(homeDir, EMAIL_JOURNAL_FILE_DIR);
- if (!journalDir.exists()) {
- boolean created = journalDir.mkdir();
- if (!created) {
- throw new IllegalStateException(
- "Unable to create email journal directory at '"
- + journalDir + "'");
- }
- }
-
- File journalFile = new File(journalDir, EMAIL_JOURNAL_FILE_NAME);
- return journalFile;
- }
-
-
- private String getOriginalRefererFromSession(VitroRequest vreq) {
- String originalReferer = (String) vreq.getSession().getAttribute("contactFormReferer");
- if (originalReferer != null) {
- vreq.getSession().removeAttribute("contactFormReferer");
+ protected ResponseValues processRequest(VitroRequest vreq) throws IOException {
+ if (!FreemarkerEmailFactory.isConfigured(vreq)) {
+ return errorNoSmtpServer();
+ }
+
+ String[] recipients = figureRecipients(vreq);
+ if (recipients.length == 0) {
+ return errorNoRecipients();
+ }
+
+ captchaImpl = CaptchaServiceBean.getCaptchaImpl();
+
+ String webusername = nonNullAndTrim(vreq, WEB_USERNAME_PARAM);
+ String webuseremail = nonNullAndTrim(vreq, WEB_USEREMAIL_PARAM);
+ String comments = nonNullAndTrim(vreq, COMMENTS_PARAM);
+ String formType = nonNullAndTrim(vreq, "DeliveryType");
+
+ String captchaInput;
+ String captchaId = "";
+ switch (captchaImpl) {
+ case RECAPTCHAV2:
+ captchaInput = nonNullAndTrim(vreq, "g-recaptcha-response");
+ break;
+ case NANOCAPTCHA:
+ default:
+ captchaInput = nonNullAndTrim(vreq, "userSolution");
+ captchaId = nonNullAndTrim(vreq, "challengeId");
+ }
+
+ String errorMsg = validateInput(webusername, webuseremail, comments, captchaInput, captchaId, vreq);
+
+ if (errorMsg != null) {
+ return errorParametersNotValid(errorMsg, webusername, webuseremail, comments, vreq.getContextPath());
+ }
+
+ String spamReason = checkForSpam(comments, formType);
+ if (spamReason != null) {
+ return errorSpam();
+ }
+
+ return processValidRequest(vreq, webusername, webuseremail, recipients, comments);
+ }
+
+ private String[] figureRecipients(VitroRequest vreq) {
+ String contactMailAddresses = vreq.getAppBean().getContactMail().trim();
+ if ((contactMailAddresses == null) || contactMailAddresses.isEmpty()) {
+ return new String[0];
+ }
+
+ return contactMailAddresses.split(",");
+ }
+
+ private ResponseValues processValidRequest(VitroRequest vreq,
+ String webusername, String webuseremail, String[] recipients,
+ String comments) throws Error {
+ String statusMsg = null; // holds the error status
+
+ ApplicationBean appBean = vreq.getAppBean();
+ String deliveryfrom = "Message from the " + appBean.getApplicationName() + " Contact Form";
+
+ String originalReferer = getOriginalRefererFromSession(vreq);
+
+ String msgText = composeEmail(webusername, webuseremail, comments,
+ deliveryfrom, originalReferer, vreq.getRemoteAddr(), vreq);
+
+ try {
+ // Write the message to the journal file
+ FileWriter fw = new FileWriter(locateTheJournalFile(), true);
+ PrintWriter outFile = new PrintWriter(fw);
+ writeBackupCopy(outFile, msgText, vreq);
+
+ try {
+ // Send the message
+ Session s = FreemarkerEmailFactory.getEmailSession(vreq);
+ sendMessage(s, webuseremail, webusername, recipients, deliveryfrom, msgText);
+ } catch (AddressException e) {
+ statusMsg = "Please supply a valid email address.";
+ outFile.println(statusMsg);
+ outFile.println(e.getMessage());
+ } catch (SendFailedException e) {
+ statusMsg = "The system was unable to deliver your mail. Please try again later. [SEND FAILED]";
+ outFile.println(statusMsg);
+ outFile.println(e.getMessage());
+ } catch (MessagingException e) {
+ statusMsg = "The system was unable to deliver your mail. Please try again later. [MESSAGING]";
+ outFile.println(statusMsg);
+ outFile.println(e.getMessage());
+ e.printStackTrace();
+ }
+
+ outFile.close();
+ } catch (IOException e) {
+ log.error("Can't open file to write email backup");
+ }
+
+ if (statusMsg == null) {
+ // Message was sent successfully
+ return new TemplateResponseValues(TEMPLATE_CONFIRMATION);
+ } else {
+ Map body = new HashMap();
+ body.put("errorMessage", statusMsg);
+ return new TemplateResponseValues(TEMPLATE_ERROR, body);
+ }
+ }
+
+ /**
+ * The journal file belongs in a sub-directory of the Vitro home directory.
+ * If the sub-directory doesn't exist, create it.
+ */
+ private File locateTheJournalFile() {
+ File homeDir = ApplicationUtils.instance().getHomeDirectory().getPath().toFile();
+ File journalDir = new File(homeDir, EMAIL_JOURNAL_FILE_DIR);
+ if (!journalDir.exists()) {
+ boolean created = journalDir.mkdir();
+ if (!created) {
+ throw new IllegalStateException(
+ "Unable to create email journal directory at '"
+ + journalDir + "'");
+ }
+ }
+
+ File journalFile = new File(journalDir, EMAIL_JOURNAL_FILE_NAME);
+ return journalFile;
+ }
+
+
+ private String getOriginalRefererFromSession(VitroRequest vreq) {
+ String originalReferer = (String) vreq.getSession().getAttribute("contactFormReferer");
+ if (originalReferer != null) {
+ vreq.getSession().removeAttribute("contactFormReferer");
/* does not support legitimate clients that don't send the Referer header
String referer = request.getHeader("Referer");
if (referer == null ||
@@ -192,25 +210,28 @@ private String getOriginalRefererFromSession(VitroRequest vreq) {
statusMsg = SPAM_MESSAGE;
}
*/
- } else {
- originalReferer = "none";
- }
- return originalReferer;
- }
-
- /** Intended to mangle url so it can get through spam filtering
- * http://host/dir/servlet?param=value -> host: dir/servlet?param=value */
- public String stripProtocol( String in ){
- if( in == null )
+ } else {
+ originalReferer = "none";
+ }
+ return originalReferer;
+ }
+
+ /**
+ * Intended to mangle url so it can get through spam filtering
+ * http://host/dir/servlet?param=value -> host: dir/servlet?param=value
+ */
+ public String stripProtocol(String in) {
+ if (in == null) {
return "";
- else
- return in.replaceAll("http://", "host: " );
+ } else {
+ return in.replaceAll("http://", "host: ");
+ }
}
private String composeEmail(String webusername, String webuseremail,
- String comments, String deliveryfrom,
- String originalReferer, String ipAddr,
- HttpServletRequest request) {
+ String comments, String deliveryfrom,
+ String originalReferer, String ipAddr,
+ HttpServletRequest request) {
Map email = new HashMap();
String template = TEMPLATE_EMAIL;
@@ -220,7 +241,7 @@ private String composeEmail(String webusername, String webuseremail,
email.put("emailAddress", webuseremail);
email.put("comments", comments);
email.put("ip", ipAddr);
- if ( !(originalReferer == null || originalReferer.equals("none")) ) {
+ if (!(originalReferer == null || originalReferer.equals("none"))) {
email.put("referrer", UrlBuilder.urlDecode(originalReferer));
}
@@ -233,13 +254,13 @@ private String composeEmail(String webusername, String webuseremail,
}
private void writeBackupCopy(PrintWriter outFile, String msgText,
- HttpServletRequest request) {
+ HttpServletRequest request) {
Map backup = new HashMap();
String template = TEMPLATE_BACKUP;
- Calendar cal = Calendar.getInstance();
- backup.put("datetime", cal.getTime().toString());
+ Calendar cal = Calendar.getInstance();
+ backup.put("datetime", cal.getTime().toString());
backup.put("msgText", msgText);
try {
@@ -253,77 +274,78 @@ private void writeBackupCopy(PrintWriter outFile, String msgText,
}
private void sendMessage(Session s, String webuseremail, String webusername,
- String[] recipients, String deliveryfrom, String msgText)
- throws AddressException, SendFailedException, MessagingException {
+ String[] recipients, String deliveryfrom, String msgText)
+ throws MessagingException {
// Construct the message
- MimeMessage msg = new MimeMessage( s );
+ MimeMessage msg = new MimeMessage(s);
//System.out.println("trying to send message from servlet");
// Set the reply address
try {
- msg.setReplyTo( new Address[] { new InternetAddress( webuseremail, webusername ) } );
+ msg.setReplyTo(new Address[] {new InternetAddress(webuseremail, webusername)});
} catch (UnsupportedEncodingException e) {
- log.error("Can't set message reply with personal name " + webusername +
- " due to UnsupportedEncodingException");
+ log.error("Can't set message reply with personal name " + webusername +
+ " due to UnsupportedEncodingException");
// msg.setFrom( new InternetAddress( webuseremail ) );
}
// Set the recipient address
- InternetAddress[] address=new InternetAddress[recipients.length];
- for (int i=0; i 0) {
- msg.setFrom(address[0]);
- } else {
- msg.setFrom( new InternetAddress( webuseremail ) );
- }
+ // Set the from address
+ if (address != null && address.length > 0) {
+ msg.setFrom(address[0]);
+ } else {
+ msg.setFrom(new InternetAddress(webuseremail));
+ }
// Set the subject and text
- msg.setSubject( deliveryfrom );
+ msg.setSubject(deliveryfrom);
// add the multipart to the message
- msg.setContent(msgText,"text/html; charset=UTF-8");
+ msg.setContent(msgText, "text/html; charset=UTF-8");
// set the Date: header
- msg.setSentDate( new Date() );
+ msg.setSentDate(new Date());
- Transport.send( msg ); // try to send the message via smtp - catch error exceptions
+ Transport.send(msg); // try to send the message via smtp - catch error exceptions
}
- private String nonNullAndTrim(HttpServletRequest req, String key) {
- String value = req.getParameter(key);
- return (value == null) ? "" : value.trim();
- }
+ private String nonNullAndTrim(HttpServletRequest req, String key) {
+ String value = req.getParameter(key);
+ return (value == null) ? "" : value.trim();
+ }
private String validateInput(String webusername, String webuseremail,
- String comments, String captchaInput, String captchaDisplay) {
+ String comments, String captchaInput, String challengeId, VitroRequest vreq) {
+ I18nBundle i18nBundle = I18n.bundle(vreq);
- if( webusername.isEmpty() ){
- return "Please enter a value in the Full name field.";
+ if (webusername.isEmpty()) {
+ return i18nBundle.text("full_name_empty");
}
- if( webuseremail.isEmpty() ){
- return "Please enter a valid email address.";
+ if (webuseremail.isEmpty()) {
+ return i18nBundle.text("email_address_empty");
}
if (comments.isEmpty()) {
- return "Please enter your comments or questions in the space provided.";
+ return i18nBundle.text("comments_empty");
}
- if (captchaInput.isEmpty()) {
- return "Please enter the contents of the gray box in the security field provided.";
+ if (!captchaImpl.equals(CaptchaImplementation.NONE) && captchaInput.isEmpty()) {
+ return i18nBundle.text("captcha_user_sol_empty");
}
- if ( !captchaHash(captchaInput).equals(captchaDisplay) ) {
- return "The value you entered in the security field did not match the letters displayed in the gray box.";
- }
+ if (CaptchaServiceBean.validateCaptcha(captchaInput, challengeId)) {
+ return null;
+ }
- return null;
+ return i18nBundle.text("captcha_user_sol_invalid");
}
/**
@@ -331,22 +353,22 @@ private String validateInput(String webusername, String webuseremail,
* containing the reason the message was flagged as spam.
*/
private String checkForSpam(String comments, String formType) {
- /* If the form doesn't specify a delivery type, treat as spam. */
- if (!"contact".equals(formType)) {
- return "The form specifies no delivery type.";
- }
+ /* If the form doesn't specify a delivery type, treat as spam. */
+ if (!"contact".equals(formType)) {
+ return "The form specifies no delivery type.";
+ }
/* if this blog markup is found, treat comment as blog spam */
if (
(comments.indexOf("[/url]") > -1
- || comments.indexOf("[/URL]") > -1
- || comments.indexOf("[url=") > -1
- || comments.indexOf("[URL=") > -1)) {
+ || comments.indexOf("[/URL]") > -1
+ || comments.indexOf("[url=") > -1
+ || comments.indexOf("[URL=") > -1)) {
return "The message contained blog link markup.";
}
/* if message is absurdly short, treat as blog spam */
- if (comments.length()<15) {
+ if (comments.length() < 15) {
return "The message was too short.";
}
@@ -354,45 +376,51 @@ private String checkForSpam(String comments, String formType) {
}
- private String captchaHash(String value) {
- int hash = 5381;
- value = value.toUpperCase();
- for(int i = 0; i < value.length(); i++) {
- hash = ((hash << 5) + hash) + value.charAt(i);
- }
- return String.valueOf(hash);
- }
+ private String captchaHash(String value) {
+ int hash = 5381;
+ value = value.toUpperCase();
+ for (int i = 0; i < value.length(); i++) {
+ hash = ((hash << 5) + hash) + value.charAt(i);
+ }
+ return String.valueOf(hash);
+ }
- private ResponseValues errorNoSmtpServer() {
+ private ResponseValues errorNoSmtpServer() {
Map body = new HashMap();
body.put("errorMessage",
- "This application has not yet been configured to send mail. " +
+ "This application has not yet been configured to send mail. " +
"Email properties must be specified in the configuration properties file.");
- return new TemplateResponseValues(TEMPLATE_ERROR, body);
- }
-
- private ResponseValues errorNoRecipients() {
- Map body = new HashMap();
- body.put("errorMessage", "To establish the Contact Us mail capability "
- + "the system administrators must specify "
- + "at least one email address.");
- return new TemplateResponseValues(TEMPLATE_ERROR, body);
- }
-
- private ResponseValues errorParametersNotValid(String errorMsg, String webusername, String webuseremail, String comments) {
+ return new TemplateResponseValues(TEMPLATE_ERROR, body);
+ }
+
+ private ResponseValues errorNoRecipients() {
Map body = new HashMap();
- body.put("errorMessage", errorMsg);
- body.put("formAction", "submitFeedback");
- body.put("webusername", webusername);
- body.put("webuseremail", webuseremail);
- body.put("comments", comments);
- return new TemplateResponseValues(TEMPLATE_FORM, body);
- }
-
- private ResponseValues errorSpam() {
- Map body = new HashMap();
- body.put("errorMessage", SPAM_MESSAGE);
- return new TemplateResponseValues(TEMPLATE_ERROR, body);
- }
+ body.put("errorMessage", "To establish the Contact Us mail capability "
+ + "the system administrators must specify "
+ + "at least one email address.");
+ return new TemplateResponseValues(TEMPLATE_ERROR, body);
+ }
+ private ResponseValues errorParametersNotValid(String errorMsg, String webusername, String webuseremail,
+ String comments, String contextPath)
+ throws IOException {
+ Map body = new HashMap<>();
+ body.put("errorMessage", errorMsg);
+ body.put("formAction", "submitFeedback");
+ body.put("webusername", webusername);
+ body.put("webuseremail", webuseremail);
+ body.put("comments", comments);
+ body.put("captchaToUse", captchaImpl);
+ body.put("contextPath", contextPath);
+
+ CaptchaServiceBean.addCaptchaRelatedFieldsToPageContext(body);
+
+ return new TemplateResponseValues(TEMPLATE_FORM, body);
+ }
+
+ private ResponseValues errorSpam() {
+ Map body = new HashMap();
+ body.put("errorMessage", SPAM_MESSAGE);
+ return new TemplateResponseValues(TEMPLATE_ERROR, body);
+ }
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java
index cd19d42232..13b3c56758 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/FreemarkerHttpServlet.java
@@ -20,9 +20,7 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-import edu.cornell.mannlib.vitro.webapp.auth.checks.ProximityChecker;
import edu.cornell.mannlib.vitro.webapp.auth.checks.QueryResultsMapCache;
-import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
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.AuthorizationRequest;
@@ -101,7 +99,7 @@ public void doGet( HttpServletRequest request, HttpServletResponse response )
VitroRequest vreq = new VitroRequest(request);
ResponseValues responseValues = null;
- try(QueryResultsMapCache personResourceCache = new QueryResultsMapCache()) {
+ try(QueryResultsMapCache sparqlQueryCache = new QueryResultsMapCache()) {
// This method does a redirect if the required authorizations are not met, so just return.
if (!isAuthorizedToDisplayPage(request, response, requiredActions(vreq))) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java
index 3b74a2208b..ecff94b2ce 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualListController.java
@@ -8,6 +8,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
import javax.servlet.annotation.WebServlet;
@@ -28,6 +29,8 @@
import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchEngineException;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchQuery;
+import edu.cornell.mannlib.vitro.webapp.search.controller.SearchFilter;
+import edu.cornell.mannlib.vitro.webapp.search.controller.SearchFiltering;
import edu.cornell.mannlib.vitro.webapp.utils.searchengine.SearchQueryUtils;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividual;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individuallist.ListedIndividualBuilder;
@@ -188,7 +191,7 @@ private static IndividualListResults buildAndExecuteVClassQuery(List cla
boolean languageFilter = Boolean.valueOf(props.getProperty(LANGUAGE_FILTER_PROPERTY, "false"));
IndividualListQueryResults results = buildAndExecuteVClassQuery(classUris, alpha,
((languageFilter) ? vreq.getLocale() : null), page, pageSize,
- vreq.getWebappDaoFactory().getIndividualDao());
+ vreq);
IndividualListResults indListResults = getResultsForVClassQuery(results, page, pageSize, alpha, vreq);
return indListResults;
}
@@ -196,7 +199,7 @@ private static IndividualListResults buildAndExecuteVClassQuery(List cla
public static IndividualListResults getRandomResultsForVClass(String vclassURI, int page, int pageSize, VitroRequest vreq) {
try{
List classUris = Collections.singletonList(vclassURI);
- IndividualListQueryResults results = buildAndExecuteRandomVClassQuery(classUris, page, pageSize, vreq.getWebappDaoFactory().getIndividualDao());
+ IndividualListQueryResults results = buildAndExecuteRandomVClassQuery(classUris, page, pageSize, vreq);
return getResultsForVClassQuery(results, page, pageSize, "", vreq);
} catch(Throwable th) {
log.error("An error occurred retrieving random results for vclass query", th);
@@ -216,9 +219,12 @@ private static IndividualListResults getResultsForVClassQuery(IndividualListQuer
private static IndividualListQueryResults buildAndExecuteVClassQuery(
List vclassURIs, String alpha, Locale locale, int page,
- int pageSize, IndividualDao indDao)
+ int pageSize, VitroRequest vreq)
throws SearchEngineException {
+ IndividualDao indDao = vreq.getWebappDaoFactory().getIndividualDao();
SearchQuery query = SearchQueryUtils.getQuery(vclassURIs, alpha, locale, page, pageSize);
+ Set currentRoles = SearchFiltering.getCurrentUserRoles(vreq);
+ SearchFiltering.addDefaultFilters(query, currentRoles);
IndividualListQueryResults results = IndividualListQueryResults.runQuery(query, indDao);
log.debug("Executed search query for " + vclassURIs);
if (results.getIndividuals().isEmpty()) {
@@ -228,9 +234,12 @@ private static IndividualListQueryResults buildAndExecuteVClassQuery(
}
private static IndividualListQueryResults buildAndExecuteRandomVClassQuery(
- List vclassURIs, int page, int pageSize, IndividualDao indDao)
+ List vclassURIs, int page, int pageSize, VitroRequest vreq)
throws SearchEngineException {
+ IndividualDao indDao = vreq.getWebappDaoFactory().getIndividualDao();
SearchQuery query = SearchQueryUtils.getRandomQuery(vclassURIs, page, pageSize);
+ Set currentRoles = SearchFiltering.getCurrentUserRoles(vreq);
+ SearchFiltering.addDefaultFilters(query, currentRoles);
IndividualListQueryResults results = IndividualListQueryResults.runQuery(query, indDao);
log.debug("Executed search query for " + vclassURIs);
if (results.getIndividuals().isEmpty()) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java
index a5e2a99829..f27c07aa90 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java
@@ -73,7 +73,7 @@ private AuthorizationRequest getActionsForPage( VitroRequest vreq ) throws Excep
if (StringUtils.isBlank(uri)) {
continue;
}
- NamedAccessObject ao = new NamedAccessObject(uri, AccessObjectType.NAMED_OBJECT);
+ NamedAccessObject ao = new NamedAccessObject(uri);
auth = auth.and( new SimpleAuthorizationRequest(ao, AccessOperation.EXECUTE));
}
return auth;
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/grefine/JSONReconcileServlet.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/grefine/JSONReconcileServlet.java
index 8469d377e9..0df0ead34c 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/grefine/JSONReconcileServlet.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/grefine/JSONReconcileServlet.java
@@ -3,6 +3,7 @@
package edu.cornell.mannlib.vitro.webapp.controller.grefine;
import java.io.IOException;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -11,7 +12,6 @@
import java.util.Map;
import javax.servlet.ServletException;
-import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -70,6 +70,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doGet(req, resp);
resp.setContentType("application/json");
+ resp.setCharacterEncoding("UTF-8");
VitroRequest vreq = new VitroRequest(req);
try {
@@ -81,7 +82,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
.toString() : vreq.getParameter("callback") + "("
+ qJson.toString() + ")";
// System.out.println("JSONReconcileServlet result: " + responseStr);
- ServletOutputStream out = resp.getOutputStream();
+ PrintWriter out = resp.getWriter();
out.print(responseStr);
} else { // metadata
String defaultNamespace = null;
@@ -97,7 +98,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp)
ObjectNode metaJson = getMetadata(req, resp, defaultNamespace, defaultTypeList, serverName, serverPort);
String callbackStr = (vreq.getParameter("callback") == null) ? ""
: vreq.getParameter("callback");
- ServletOutputStream out = resp.getOutputStream();
+ PrintWriter out = resp.getWriter();
out.print(callbackStr + "(" + metaJson.toString() + ")");
}
} catch (Exception ex) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java
index b8d9745b93..00cd6837c6 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/controller/individual/IndividualController.java
@@ -14,7 +14,11 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
+import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
+import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
+import edu.cornell.mannlib.vitro.webapp.auth.objects.IndividualAccessObject;
+import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.AuthorizationRequest;
+import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.SimpleAuthorizationRequest;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
@@ -22,6 +26,7 @@
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.RedirectResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
+import edu.cornell.mannlib.vitro.webapp.controller.individual.IndividualRequestInfo.Type;
import edu.cornell.mannlib.vitro.webapp.i18n.I18n;
/**
@@ -113,6 +118,26 @@ protected ResponseValues processRequest(VitroRequest vreq) {
return new ExceptionResponseValues(e);
}
}
+
+ @Override
+ public AuthorizationRequest requiredActions(VitroRequest vreq) {
+ try {
+ IndividualRequestInfo requestInfo = analyzeTheRequest(vreq);
+ switch (requestInfo.getType()) {
+ case RDF_REDIRECT:
+ case NO_INDIVIDUAL:
+ case BYTESTREAM_REDIRECT:
+ return AuthorizationRequest.AUTHORIZED;
+ default:
+ AccessObject ao = new IndividualAccessObject(requestInfo.getIndividual().getURI());
+ ao.setModel(vreq.getJenaOntModel());
+ AuthorizationRequest request = new SimpleAuthorizationRequest(ao, AccessOperation.DISPLAY);
+ return request;
+ }
+ } catch (Throwable e) {
+ return AuthorizationRequest.UNAUTHORIZED;
+ }
+ }
private IndividualRequestInfo analyzeTheRequest(VitroRequest vreq) {
return new IndividualRequestAnalyzer(vreq,
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java
index 964726abcb..925d76364f 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/DisplayVocabulary.java
@@ -126,6 +126,14 @@ public class DisplayVocabulary {
public static final String SAVE_TO_VAR = DISPLAY_NS + "saveToVar" ;
public static final String QUERY_MODEL = DISPLAY_NS + "queryModel";
public static final String QUERY = DISPLAY_NS + "query";
+ public static final String DISPLAY_URI_PARAM = DISPLAY_NS + "uri";
+ public static final String DISPLAY_STRING_PARAM = DISPLAY_NS + "string";
+ public static final String DISPLAY_INT_PARAM = DISPLAY_NS + "int";
+ public static final String DISPLAY_LONG_PARAM = DISPLAY_NS + "long";
+ public static final String DISPLAY_FLOAT_PARAM = DISPLAY_NS + "float";
+ public static final String DISPLAY_DOUBLE_PARAM = DISPLAY_NS + "double";
+ public static final String DISPLAY_BOOLEAN_PARAM = DISPLAY_NS + "boolean";
+
/* URI of property for Fixed HTML Generator */
public static final String FIXED_HTML_VALUE = DISPLAY_NS + "htmlValue";
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/FilterByDisplayPermission.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/FilterByDisplayPermission.java
index a4a18b6fd1..1953449dd7 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/FilterByDisplayPermission.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/FilterByDisplayPermission.java
@@ -5,8 +5,6 @@
import static edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject.SOME_URI;
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.ActiveIdentifierBundleFactories;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.objects.DataPropertyAccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.objects.DataPropertyStatementAccessObject;
@@ -17,6 +15,7 @@
import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import net.sf.jga.fn.UnaryFunctor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -27,7 +26,7 @@
*/
public class FilterByDisplayPermission extends VitroFiltersImpl {
private static final Log log = LogFactory.getLog(FilterByDisplayPermission.class);
- IdentifierBundle accessSubject = ActiveIdentifierBundleFactories.getUserIdentifierBundle(null);
+ UserAccount userAccount = PolicyHelper.getUserAccount(null);
public FilterByDisplayPermission() {
setDataPropertyFilter(new DataPropertyFilterByPolicy());
@@ -37,7 +36,7 @@ public FilterByDisplayPermission() {
}
boolean checkAuthorization(AccessObject accessObject) {
- boolean decision = PolicyHelper.isAuthorizedForActions(accessSubject, accessObject, AccessOperation.DISPLAY);
+ boolean decision = PolicyHelper.isAuthorizedForActions(userAccount, accessObject, AccessOperation.DISPLAY);
log.debug("decision is " + decision);
return decision;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/HideFromDisplayByPolicyFilter.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/HideFromDisplayByPolicyFilter.java
index 449919a48e..d667bb7a9d 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/HideFromDisplayByPolicyFilter.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/filtering/filters/HideFromDisplayByPolicyFilter.java
@@ -3,14 +3,12 @@
package edu.cornell.mannlib.vitro.webapp.dao.filtering.filters;
import net.sf.jga.fn.UnaryFunctor;
-
-import static edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject.SOME_URI;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+import static edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject.SOME_URI;
+
import edu.cornell.mannlib.vitro.webapp.auth.attributes.AccessOperation;
-import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
import edu.cornell.mannlib.vitro.webapp.auth.objects.AccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.objects.DataPropertyAccessObject;
import edu.cornell.mannlib.vitro.webapp.auth.objects.DataPropertyStatementAccessObject;
@@ -21,21 +19,24 @@
import edu.cornell.mannlib.vitro.webapp.beans.DataPropertyStatement;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectProperty;
import edu.cornell.mannlib.vitro.webapp.beans.ObjectPropertyStatement;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
/**
* Ask the current policies whether we can show these things to the user.
*/
public class HideFromDisplayByPolicyFilter extends VitroFiltersImpl {
- private static final Log log = LogFactory
- .getLog(HideFromDisplayByPolicyFilter.class);
- private final IdentifierBundle idBundle;
+ private static final Log log = LogFactory.getLog(HideFromDisplayByPolicyFilter.class);
+
+ private final UserAccount userAccount;
- public HideFromDisplayByPolicyFilter(IdentifierBundle idBundle) {
- if (idBundle == null) {
- throw new NullPointerException("idBundle may not be null.");
+ public HideFromDisplayByPolicyFilter(UserAccount userAccount) {
+ if (userAccount == null) {
+ log.debug("UserAccount provided for PolicyFilter is null. Fall back to default user account.");
+ userAccount = PolicyHelper.getUserAccount(null);
}
- this.idBundle = idBundle;
+ this.userAccount = userAccount;
setDataPropertyFilter(new DataPropertyFilterByPolicy());
setObjectPropertyFilter(new ObjectPropertyFilterByPolicy());
@@ -44,7 +45,7 @@ public HideFromDisplayByPolicyFilter(IdentifierBundle idBundle) {
}
boolean checkAuthorization(AccessObject whatToAuth) {
- return PolicyHelper.isAuthorizedForActions(idBundle, whatToAuth, AccessOperation.DISPLAY);
+ return PolicyHelper.isAuthorizedForActions(userAccount, whatToAuth, AccessOperation.DISPLAY);
}
private class DataPropertyFilterByPolicy extends
@@ -80,7 +81,7 @@ public Boolean fn(ObjectPropertyStatement ops) {
ObjectProperty predicate = getOrCreateProperty(ops);
String objectUri = ops.getObjectURI();
return checkAuthorization(new ObjectPropertyStatementAccessObject(
- null, subjectUri, predicate, objectUri));
+ ModelAccess.getInstance().getOntModel(), subjectUri, predicate, objectUri));
}
/**
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaModelUtils.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaModelUtils.java
index 67fb0f52db..e28e616b8f 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaModelUtils.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaModelUtils.java
@@ -2,6 +2,9 @@
package edu.cornell.mannlib.vitro.webapp.dao.jena;
+import static edu.cornell.mannlib.vitro.webapp.dao.jena.SparqlGraph.sparqlNode;
+import static edu.cornell.mannlib.vitro.webapp.dao.jena.SparqlGraph.sparqlNodeDelete;
+
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
@@ -565,25 +568,45 @@ private static List sort(List stmts) {
private static void addStatementPatterns(List stmts,
StringBuffer patternBuff, boolean whereClause) {
+ Set lines = new HashSet<>();
for(Statement stmt : stmts) {
Triple t = stmt.asTriple();
- patternBuff.append(SparqlGraph.sparqlNodeDelete(t.getSubject(), null));
- patternBuff.append(" ");
- patternBuff.append(SparqlGraph.sparqlNodeDelete(t.getPredicate(), null));
- patternBuff.append(" ");
- patternBuff.append(SparqlGraph.sparqlNodeDelete(t.getObject(), null));
- patternBuff.append(" .\n");
+ String lineWithoutVars = getLine(t, false);
+ if (lines.contains(lineWithoutVars)) {
+ continue;
+ } else {
+ lines.add(lineWithoutVars);
+ }
+ patternBuff.append(getLine(t, true));
if (whereClause) {
if (t.getSubject().isBlank()) {
patternBuff.append(" FILTER(isBlank(").append(
- SparqlGraph.sparqlNodeDelete(t.getSubject(), null)).append(")) \n");
+ sparqlNodeDelete(t.getSubject(), null)).append(")) \n");
}
if (t.getObject().isBlank()) {
patternBuff.append(" FILTER(isBlank(").append(
- SparqlGraph.sparqlNodeDelete(t.getObject(), null)).append(")) \n");
+ sparqlNodeDelete(t.getObject(), null)).append(")) \n");
}
}
}
}
+ private static String getLine(Triple t, boolean createBlankNodeVariables) {
+ if (createBlankNodeVariables) {
+ return sparqlNodeDelete(t.getSubject(), null) +
+ " " +
+ sparqlNodeDelete(t.getPredicate(), null) +
+ " " +
+ sparqlNodeDelete(t.getObject(), null) +
+ " .\n";
+ } else {
+ return sparqlNode(t.getSubject(), null) +
+ " " +
+ sparqlNode(t.getPredicate(), null) +
+ " " +
+ sparqlNode(t.getObject(), null) +
+ " .\n";
+ }
+ }
+
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/DateTimeIntervalValidationVTwo.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/DateTimeIntervalValidationVTwo.java
index 73bd586ad8..d2c682673d 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/DateTimeIntervalValidationVTwo.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/VTwo/DateTimeIntervalValidationVTwo.java
@@ -195,7 +195,7 @@ private Map checkDateLiterals(
Calendar endCal = endDate.asCalendar();
if( endCal != null ){
- if( !startCal.before( endCal ) ){
+ if (startCal.after(endCal)) {
if( startPrecision == VitroVocabulary.Precision.YEAR
&& endPrecision == VitroVocabulary.Precision.YEAR ){
errors.putAll( checkYears(startCal,endCal));
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/IdModelSelector.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/IdModelSelector.java
index ae28fa7865..a1e4ff3ab2 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/IdModelSelector.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/IdModelSelector.java
@@ -1,6 +1,7 @@
/* $This file is distributed under the terms of the license in LICENSE$ */
package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration;
+import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService.CONFIGURATION;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService.CONTENT;
import javax.servlet.ServletContext;
@@ -11,6 +12,8 @@
import edu.cornell.mannlib.vitro.webapp.dao.jena.VitroModelSource;
import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService;
public class IdModelSelector implements ModelSelector {
@@ -25,13 +28,26 @@ public IdModelSelector(String name) {
@Override
public Model getModel(HttpServletRequest request, ServletContext context) {
- ModelMaker modelMaker = ModelAccess.on(context).getModelMaker(CONTENT);
+ ModelMaker modelMaker = ModelAccess.getInstance().getModelMaker(getRdfService());
VitroModelSource mSource = new VitroModelSource(modelMaker, context);
return mSource.getModel(name);
}
+ private WhichService getRdfService() {
+ return isConfigurationModel() ? CONFIGURATION : CONTENT;
+ }
+
+ private boolean isConfigurationModel() {
+ return name.equals(VitroModelSource.ModelName.DISPLAY.toString()) ||
+ name.equals(VitroModelSource.ModelName.DISPLAY_TBOX.toString()) ||
+ name.equals(VitroModelSource.ModelName.DISPLAY_DISPLAY.toString());
+ }
+
@Override
public String getDefaultGraphUri() {
+ if (ModelNames.namesMap.containsKey(name)) {
+ return ModelNames.namesMap.get(name);
+ }
return name;
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java
index adb7e8bb88..5b084dc249 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/configuration/generators/DefaultObjectPropertyFormGenerator.java
@@ -2,8 +2,6 @@
package edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.generators;
-import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.LANGUAGE_NEUTRAL;
-import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.POLICY_NEUTRAL;
import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY;
import java.util.ArrayList;
@@ -11,6 +9,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.servlet.http.HttpSession;
@@ -45,6 +44,7 @@
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResponse;
import edu.cornell.mannlib.vitro.webapp.modules.searchEngine.SearchResultDocumentList;
import edu.cornell.mannlib.vitro.webapp.search.VitroSearchTermNames;
+import edu.cornell.mannlib.vitro.webapp.search.controller.SearchFiltering;
import edu.cornell.mannlib.vitro.webapp.utils.FrontEndEditingUtils;
import edu.cornell.mannlib.vitro.webapp.utils.FrontEndEditingUtils.EditMode;
@@ -187,7 +187,7 @@ private boolean tooManyRangeOptions(VitroRequest vreq, HttpSession session ) thr
if(types.size() == 0 ){
types.add(VitroVocabulary.OWL_THING);
}
-
+ Set currentRoles = SearchFiltering.getCurrentUserRoles(vreq);
long count = 0;
for( String type:types){
//search query for type count.
@@ -198,6 +198,7 @@ private boolean tooManyRangeOptions(VitroRequest vreq, HttpSession session ) thr
query.setQuery( VitroSearchTermNames.RDFTYPE + ":" + type);
}
query.setRows(0);
+ SearchFiltering.addDefaultFilters(query, currentRoles);
SearchResponse rsp = searchEngine.query(query);
SearchResultDocumentList docs = rsp.getResults();
long found = docs.getNumFound();
@@ -549,9 +550,9 @@ public void addFormSpecificDataForAC(EditConfigurationVTwo editConfiguration, Vi
String objectLabel = EditConfigurationUtils.getObjectIndividual(vreq).getName();
formSpecificData.put("objectLabel", objectLabel);
}
-
+ Set currentRoles = SearchFiltering.getCurrentUserRoles(vreq);
//TODO: find out if there are any individuals in the classes of objectTypes
- formSpecificData.put("rangeIndividualsExist", rangeIndividualsExist(types) );
+ formSpecificData.put("rangeIndividualsExist", rangeIndividualsExist(types, currentRoles) );
formSpecificData.put("sparqlForAcFilter", getSparqlForAcFilter(vreq));
if(customErrorMessages != null && !customErrorMessages.isEmpty()) {
@@ -561,7 +562,7 @@ public void addFormSpecificDataForAC(EditConfigurationVTwo editConfiguration, Vi
editConfiguration.setFormSpecificData(formSpecificData);
}
- private Object rangeIndividualsExist(List types) throws SearchEngineException {
+ private Object rangeIndividualsExist(List types, Set currentRoles) throws SearchEngineException {
SearchEngine searchEngine = ApplicationUtils.instance().getSearchEngine();
boolean rangeIndividualsFound = false;
@@ -570,6 +571,7 @@ private Object rangeIndividualsExist(List types) throws SearchEngineExce
SearchQuery query = searchEngine.createQuery();
query.setQuery( VitroSearchTermNames.RDFTYPE + ":" + type.getURI());
query.setRows(0);
+ SearchFiltering.addDefaultFilters(query, currentRoles);
SearchResponse rsp = searchEngine.query(query);
SearchResultDocumentList docs = rsp.getResults();
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java
index 0d8d1d33e4..390bf667e5 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/edit/n3editing/controller/ProcessRdfFormController.java
@@ -40,6 +40,11 @@
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.VTwo.RdfLiteralHash;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.ModelSelector;
import edu.cornell.mannlib.vitro.webapp.edit.n3editing.configuration.preprocessors.LimitRemovalsToLanguage;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess;
+import edu.cornell.mannlib.vitro.webapp.modelaccess.ModelAccess.WhichService;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.model.RDFServiceModel;
/**
* This servlet will convert a request to an EditSubmission,
@@ -107,7 +112,8 @@ protected ResponseValues processRequest(VitroRequest vreq) {
configuration.addModelChangePreprocessor(new LimitRemovalsToLanguage(vreq.getLocale()));
N3EditUtils.preprocessModels(changes, configuration, vreq);
- ProcessRdfForm.applyChangesToWriteModel(changes, vreq.getRDFService(), writeModelSelector.getDefaultGraphUri(), N3EditUtils.getEditorUri(vreq));
+ String graphUri = writeModelSelector.getDefaultGraphUri();
+ ProcessRdfForm.applyChangesToWriteModel(changes, getRdfService(graphUri, writeModel), graphUri, N3EditUtils.getEditorUri(vreq));
//Here we are trying to get the entity to return to URL,
//More involved processing for data property apparently
@@ -119,6 +125,21 @@ protected ResponseValues processRequest(VitroRequest vreq) {
return PostEditCleanupController.doPostEditRedirect(vreq, entityToReturnTo);
}
+ private RDFService getRdfService(String graphUri, Model writeModel) {
+ RDFService configuration = ModelAccess.getInstance().getRDFService(WhichService.CONFIGURATION);
+ boolean isConfigurationGraph = false;
+ try {
+ isConfigurationGraph = configuration.getGraphURIs().contains(graphUri);
+ } catch (RDFServiceException e) {
+ log.error(e, e);
+ }
+ if (isConfigurationGraph) {
+ return new RDFServiceModel(writeModel);
+ } else {
+ return ModelAccess.getInstance().getRDFService();
+ }
+ }
+
//In case of back button confusion
//Currently returning an error message:
//Later TODO: Per Brian Caruso's instructions, replicate
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java
index 719b041ab9..f32b349154 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/freemarker/config/FreemarkerConfiguration.java
@@ -28,6 +28,7 @@
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
+import edu.cornell.mannlib.vitro.webapp.web.directives.DataGetterDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective;
@@ -263,6 +264,7 @@ private void addDirectives(FreemarkerConfigurationImpl c) {
c.setSharedVariable("shortView", new IndividualShortViewDirective());
c.setSharedVariable("url", new UrlDirective());
c.setSharedVariable("widget", new WidgetDirective());
+ c.setSharedVariable("dataGetter", new DataGetterDirective());
}
private void addMethods(FreemarkerConfigurationImpl c) {
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java
index 64e1e49fe8..9749d157d5 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/modelaccess/impl/RequestModelAccessImpl.java
@@ -21,7 +21,7 @@
import org.apache.jena.ontology.OntModel;
import org.apache.jena.query.Dataset;
-
+import edu.cornell.mannlib.vedit.beans.LoginStatusBean;
import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers;
import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyStore;
import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
@@ -340,7 +340,7 @@ private WebappDaoFactory createWebappDaoFactory(WebappDaoFactoryKey key) {
private WebappDaoFactory addPolicyAwareness(WebappDaoFactory unaware) {
HideFromDisplayByPolicyFilter filter = new HideFromDisplayByPolicyFilter(
- RequestIdentifiers.getIdBundleForRequest(req));
+ LoginStatusBean.getCurrentUser(req));
return new WebappDaoFactoryFiltering(unaware, filter);
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingModel.java
index 2657c6c660..ee1986e9d3 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingModel.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingModel.java
@@ -51,7 +51,7 @@ public BulkUpdatingModel(Model m) {
@Override
public Model add(StmtIterator iter) {
if (updater != null && iter != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
while (iter.hasNext()) {
g.add(iter.nextStatement().asTriple());
}
@@ -75,7 +75,7 @@ public Model add(Model m) {
@Override
public Model add(Statement[] statements) {
if (updater != null && statements != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
for (Statement s : statements) {
g.add(s.asTriple());
}
@@ -154,7 +154,7 @@ public Model read(InputStream reader, String base, String lang) {
@Override
public Model remove(StmtIterator iter) {
if (updater != null && iter != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
while (iter.hasNext()) {
g.add(iter.nextStatement().asTriple());
}
@@ -178,7 +178,7 @@ public Model remove(Model m) {
@Override
public Model remove(Statement[] statements) {
if (updater != null && statements != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
for (Statement s : statements) {
g.add(s.asTriple());
}
@@ -192,7 +192,7 @@ public Model remove(Statement[] statements) {
@Override
public Model remove(List statements) {
if (updater != null && statements != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
for (Statement s : statements) {
g.add(s.asTriple());
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingOntModel.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingOntModel.java
index 44a5582b87..6f0fd3dd90 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingOntModel.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/adapters/BulkUpdatingOntModel.java
@@ -52,7 +52,7 @@ protected BulkUpdatingOntModel(OntModel m) {
@Override
public Model add(StmtIterator iter) {
if (updater != null && iter != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
while (iter.hasNext()) {
g.add(iter.nextStatement().asTriple());
}
@@ -76,7 +76,7 @@ public Model add(Model m) {
@Override
public Model add(Statement[] statements) {
if (updater != null && statements != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
for (Statement s : statements) {
g.add(s.asTriple());
}
@@ -155,7 +155,7 @@ public Model read(InputStream reader, String base, String lang) {
@Override
public Model remove(StmtIterator iter) {
if (updater != null && iter != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
while (iter.hasNext()) {
g.add(iter.nextStatement().asTriple());
}
@@ -179,7 +179,7 @@ public Model remove(Model m) {
@Override
public Model remove(Statement[] statements) {
if (updater != null && statements != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
for (Statement s : statements) {
g.add(s.asTriple());
}
@@ -193,7 +193,7 @@ public Model remove(Statement[] statements) {
@Override
public Model remove(List statements) {
if (updater != null && statements != null) {
- Graph g = GraphFactory.createPlainGraph();
+ Graph g = GraphFactory.createGraphMem();
for (Statement s : statements) {
g.add(s.asTriple());
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/SameAsFilteringRDFServiceFactory.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/SameAsFilteringRDFServiceFactory.java
index e585c54aaf..3d9dff381e 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/SameAsFilteringRDFServiceFactory.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/filter/SameAsFilteringRDFServiceFactory.java
@@ -10,9 +10,17 @@
import java.util.Iterator;
import java.util.List;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService.ModelSerializationFormat;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceImpl;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
@@ -30,27 +38,18 @@
import org.apache.jena.rdf.model.StmtIterator;
import org.apache.jena.vocabulary.OWL;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService.ModelSerializationFormat;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceFactory;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceImpl;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
-
public class SameAsFilteringRDFServiceFactory implements RDFServiceFactory {
- private final static Log log = LogFactory.getLog(
- SameAsFilteringRDFServiceFactory.class);
+ private final static Log log = LogFactory.getLog(SameAsFilteringRDFServiceFactory.class);
private RDFServiceFactory f;
private Model sameAsModel;
public SameAsFilteringRDFServiceFactory(RDFServiceFactory rdfServiceFactory) {
this.f = rdfServiceFactory;
try {
- InputStream in = f.getRDFService().sparqlConstructQuery("CONSTRUCT { ?s <" + OWL.sameAs.getURI() + "> ?o } WHERE { ?s <" + OWL.sameAs.getURI() + "> ?o } ", ModelSerializationFormat.N3);
+ InputStream in = f.getRDFService().sparqlConstructQuery(
+ "CONSTRUCT { ?s <" + OWL.sameAs.getURI() + "> ?o } WHERE { ?s <" + OWL.sameAs.getURI() + "> ?o } ",
+ ModelSerializationFormat.N3);
sameAsModel = RDFServiceUtils.parseModel(in, ModelSerializationFormat.N3);
} catch (RDFServiceException e) {
throw new RuntimeException(e);
@@ -98,11 +97,9 @@ public SameAsFilteringRDFService(RDFService rdfService) {
}
@Override
- public InputStream sparqlConstructQuery(String query,
- RDFService.ModelSerializationFormat resultFormat)
- throws RDFServiceException {
- Model m = RDFServiceUtils.parseModel(
- s.sparqlConstructQuery(query, resultFormat), resultFormat);
+ public InputStream sparqlConstructQuery(String query, RDFService.ModelSerializationFormat resultFormat)
+ throws RDFServiceException {
+ Model m = RDFServiceUtils.parseModel(s.sparqlConstructQuery(query, resultFormat), resultFormat);
Model filtered = ModelFactory.createDefaultModel();
StmtIterator stmtIt = m.listStatements();
while (stmtIt.hasNext()) {
@@ -112,14 +109,12 @@ public InputStream sparqlConstructQuery(String query,
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
- filtered.write(out, RDFServiceUtils.getSerializationFormatString(
- resultFormat));
+ filtered.write(out, RDFServiceUtils.getSerializationFormatString(resultFormat));
return new ByteArrayInputStream(out.toByteArray());
}
@Override
- public void sparqlConstructQuery(String query, Model model)
- throws RDFServiceException {
+ public void sparqlConstructQuery(String query, Model model) throws RDFServiceException {
Model m = ModelFactory.createDefaultModel();
s.sparqlConstructQuery(query, m);
@@ -133,11 +128,9 @@ public void sparqlConstructQuery(String query, Model model)
}
@Override
- public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat)
- throws RDFServiceException {
- ResultSet rs = ResultSetFactory.load(
- s.sparqlSelectQuery(query, resultFormat),
- RDFServiceUtils.getJenaResultSetFormat(resultFormat));
+ public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat) throws RDFServiceException {
+ ResultSet rs = ResultSetFactory.load(s.sparqlSelectQuery(query, resultFormat),
+ RDFServiceUtils.getJenaResultSetFormat(resultFormat));
List solutions = new ArrayList();
while (rs.hasNext()) {
QuerySolution solution = rs.nextSolution();
@@ -148,27 +141,26 @@ public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat)
ResultSet resultSet = new FilteredResultSet(solutions, rs);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
switch (resultFormat) {
- case CSV:
- ResultSetFormatter.outputAsCSV(outputStream,resultSet);
- break;
- case TEXT:
- ResultSetFormatter.out(outputStream,resultSet);
- break;
- case JSON:
- ResultSetFormatter.outputAsJSON(outputStream, resultSet);
- break;
- case XML:
- ResultSetFormatter.outputAsXML(outputStream, resultSet);
- break;
- default:
- throw new RDFServiceException("unrecognized result format");
+ case CSV:
+ ResultSetFormatter.outputAsCSV(outputStream, resultSet);
+ break;
+ case TEXT:
+ ResultSetFormatter.out(outputStream, resultSet);
+ break;
+ case JSON:
+ ResultSetFormatter.outputAsJSON(outputStream, resultSet);
+ break;
+ case XML:
+ ResultSetFormatter.outputAsXML(outputStream, resultSet);
+ break;
+ default:
+ throw new RDFServiceException("unrecognized result format");
}
return new ByteArrayInputStream(outputStream.toByteArray());
}
@Override
- public void sparqlSelectQuery(String query, ResultSetConsumer consumer)
- throws RDFServiceException {
+ public void sparqlSelectQuery(String query, ResultSetConsumer consumer) throws RDFServiceException {
s.sparqlSelectQuery(query, new ResultSetConsumer.Chaining(consumer) {
@Override
@@ -200,13 +192,14 @@ private List getSameAsResources(Resource resource) {
if (resource.isAnon()) {
return sameAsResources;
}
- String queryStr = "SELECT DISTINCT ?s WHERE { <" + resource.getURI() + "> <" + OWL.sameAs.getURI() + "> ?s } ORDER BY ?s";
- try {
+ String queryStr = "SELECT DISTINCT ?s WHERE { <" + resource.getURI() + "> <" + OWL.sameAs.getURI()
+ + "> ?s } ORDER BY ?s";
+ try {
Query query = QueryFactory.create(queryStr);
QueryExecution qe = QueryExecutionFactory.create(query, sameAsModel);
try {
ResultSet rs = qe.execSelect();
- //ResultSet rs = JSONInput.fromJSON(s.sparqlSelectQuery(queryStr, ResultFormat.JSON));
+ // ResultSet rs = JSONInput.fromJSON(s.sparqlSelectQuery(queryStr, ResultFormat.JSON));
while (rs.hasNext()) {
QuerySolution q = rs.next();
Resource res = q.getResource("s");
@@ -219,14 +212,14 @@ private List getSameAsResources(Resource resource) {
qe.close();
}
return sameAsResources;
- } catch (/*RDFService*/Exception e) {
+ } catch (/* RDFService */Exception e) {
throw new RuntimeException(e);
}
}
private boolean isRedundant(QuerySolution q) {
Iterator varIt = q.varNames();
- while(varIt.hasNext()) {
+ while (varIt.hasNext()) {
String varName = varIt.next();
RDFNode n = q.get(varName);
if (n.isResource()) {
@@ -241,17 +234,14 @@ private boolean isRedundant(QuerySolution q) {
}
@Override
- public boolean changeSetUpdate(ChangeSet changeSet)
- throws RDFServiceException {
+ public boolean changeSetUpdate(ChangeSet changeSet) throws RDFServiceException {
return s.changeSetUpdate(changeSet);
}
@Override
- public InputStream sparqlDescribeQuery(String query,
- ModelSerializationFormat resultFormat)
+ public InputStream sparqlDescribeQuery(String query, ModelSerializationFormat resultFormat)
throws RDFServiceException {
- Model m = RDFServiceUtils.parseModel(
- s.sparqlConstructQuery(query, resultFormat), resultFormat);
+ Model m = RDFServiceUtils.parseModel(s.sparqlConstructQuery(query, resultFormat), resultFormat);
Model filtered = ModelFactory.createDefaultModel();
StmtIterator stmtIt = m.listStatements();
while (stmtIt.hasNext()) {
@@ -261,8 +251,7 @@ public InputStream sparqlDescribeQuery(String query,
}
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
- filtered.write(out, RDFServiceUtils.getSerializationFormatString(
- resultFormat));
+ filtered.write(out, RDFServiceUtils.getSerializationFormatString(resultFormat));
return new ByteArrayInputStream(out.toByteArray());
}
@@ -282,27 +271,23 @@ public void getGraphMetadata() throws RDFServiceException {
}
@Override
- public void serializeAll(OutputStream outputStream)
- throws RDFServiceException {
- s.serializeAll(outputStream);
- }
-
- @Override
- public void serializeGraph(String graphURI, OutputStream outputStream)
- throws RDFServiceException {
- s.serializeGraph(graphURI, outputStream);
- }
-
- @Override
- public boolean isEquivalentGraph(String graphURI,
- InputStream serializedGraph,
- ModelSerializationFormat serializationFormat) throws RDFServiceException {
- return s.isEquivalentGraph(graphURI, serializedGraph, serializationFormat);
- }
+ public void serializeAll(OutputStream outputStream) throws RDFServiceException {
+ s.serializeAll(outputStream);
+ }
@Override
- public boolean isEquivalentGraph(String graphURI,
- Model graph) throws RDFServiceException {
+ public void serializeGraph(String graphURI, OutputStream outputStream) throws RDFServiceException {
+ s.serializeGraph(graphURI, outputStream);
+ }
+
+ @Override
+ public boolean isEquivalentGraph(String graphURI, InputStream serializedGraph,
+ ModelSerializationFormat serializationFormat) throws RDFServiceException {
+ return s.isEquivalentGraph(graphURI, serializedGraph, serializationFormat);
+ }
+
+ @Override
+ public boolean isEquivalentGraph(String graphURI, Model graph) throws RDFServiceException {
return s.isEquivalentGraph(graphURI, graph);
}
@@ -316,8 +301,10 @@ public void close() {
s.close();
}
+ @Override
+ protected void rebuildGraphUris() {
+ }
}
-
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java
index 92c7329ab8..69e7c16f11 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/RDFServiceImpl.java
@@ -5,13 +5,23 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
+import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange.Operation;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
+import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.apache.jena.atlas.io.StringWriterI;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.NodeFactory;
@@ -32,109 +42,115 @@
import org.apache.jena.riot.out.NodeFormatterTTL;
import org.apache.jena.vocabulary.RDF;
-import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange.Operation;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
-import edu.cornell.mannlib.vitro.webapp.utils.logging.ToString;
-
public abstract class RDFServiceImpl implements RDFService {
- private static final Log log = LogFactory.getLog(RDFServiceImpl.class);
-
- protected String defaultWriteGraphURI;
- protected List registeredListeners = new CopyOnWriteArrayList();
- protected List registeredJenaListeners = new CopyOnWriteArrayList();
+ private static final Log log = LogFactory.getLog(RDFServiceImpl.class);
- @Override
- public void newIndividual(String individualURI,
- String individualTypeURI) throws RDFServiceException {
+ protected String defaultWriteGraphURI;
+ protected List registeredListeners = new CopyOnWriteArrayList();
+ protected List registeredJenaListeners = new CopyOnWriteArrayList();
+ protected final List graphURIs = new CopyOnWriteArrayList();
+ protected volatile boolean rebuildGraphURICache = true;
+ protected volatile boolean isRebuildGraphURICacheRunning = false;
- newIndividual(individualURI, individualTypeURI, defaultWriteGraphURI);
- }
+ @Override
+ public void newIndividual(String individualURI, String individualTypeURI) throws RDFServiceException {
+
+ newIndividual(individualURI, individualTypeURI, defaultWriteGraphURI);
+ }
@Override
- public void newIndividual(String individualURI,
- String individualTypeURI,
- String graphURI) throws RDFServiceException {
-
- StringBuilder containsQuery = new StringBuilder("ASK { \n");
- if (graphURI != null) {
- containsQuery.append(" GRAPH <").append(graphURI).append("> { ");
- }
- containsQuery.append("<");
- containsQuery.append(individualURI);
- containsQuery.append("> ");
- containsQuery.append("?p ?o");
- if (graphURI != null) {
- containsQuery.append(" } \n");
- }
- containsQuery.append("\n}");
-
- if (sparqlAskQuery(containsQuery.toString())) {
+ public void newIndividual(String individualURI, String individualTypeURI, String graphURI)
+ throws RDFServiceException {
+
+ StringBuilder containsQuery = new StringBuilder("ASK { \n");
+ if (graphURI != null) {
+ containsQuery.append(" GRAPH <").append(graphURI).append("> { ");
+ }
+ containsQuery.append("<");
+ containsQuery.append(individualURI);
+ containsQuery.append("> ");
+ containsQuery.append("?p ?o");
+ if (graphURI != null) {
+ containsQuery.append(" } \n");
+ }
+ containsQuery.append("\n}");
+
+ if (sparqlAskQuery(containsQuery.toString())) {
throw new RDFServiceException("individual already exists");
- } else {
- Triple triple = new Triple(NodeFactory.createURI(individualURI), RDF.type.asNode(), NodeFactory.createURI(individualTypeURI));
- //addTriple(triple, graphURI);
+ } else {
+ Triple triple = new Triple(NodeFactory.createURI(individualURI), RDF.type.asNode(),
+ NodeFactory.createURI(individualTypeURI));
+ // addTriple(triple, graphURI);
ChangeSet cs = this.manufactureChangeSet();
- cs.addAddition(new ByteArrayInputStream(
- sparqlTriple(triple).getBytes()), ModelSerializationFormat.N3, graphURI);
+ cs.addAddition(new ByteArrayInputStream(sparqlTriple(triple).getBytes()), ModelSerializationFormat.N3,
+ graphURI);
changeSetUpdate(cs);
- }
+ }
}
- @Override
- public String getDefaultWriteGraphURI() throws RDFServiceException {
+ /**
+ * Get a list of all the graph URIs in the RDF store.
+ */
+ @Override
+ public List getGraphURIs() throws RDFServiceException {
+ if (rebuildGraphURICache && !isRebuildGraphURICacheRunning) {
+ rebuildGraphUris();
+ }
+ return graphURIs;
+ }
+
+ protected abstract void rebuildGraphUris();
+
+ @Override
+ public String getDefaultWriteGraphURI() throws RDFServiceException {
return defaultWriteGraphURI;
- }
-
- @Override
- public synchronized void registerListener(ChangeListener changeListener) throws RDFServiceException {
- if (!registeredListeners.contains(changeListener)) {
- registeredListeners.add(changeListener);
- }
- }
-
- @Override
- public synchronized void unregisterListener(ChangeListener changeListener) throws RDFServiceException {
- registeredListeners.remove(changeListener);
- }
-
- @Override
- public synchronized void registerJenaModelChangedListener(ModelChangedListener changeListener) throws RDFServiceException {
- if (!registeredJenaListeners.contains(changeListener)) {
- registeredJenaListeners.add(changeListener);
- }
- }
-
- @Override
- public synchronized void unregisterJenaModelChangedListener(ModelChangedListener changeListener) throws RDFServiceException {
- registeredJenaListeners.remove(changeListener);
- }
-
- public synchronized List getRegisteredListeners() {
- return this.registeredListeners;
- }
-
- public synchronized List getRegisteredJenaModelChangedListeners() {
- return this.registeredJenaListeners;
- }
-
- @Override
- public ChangeSet manufactureChangeSet() {
- return new ChangeSetImpl();
- }
-
- protected void notifyListenersOfChanges(ChangeSet changeSet)
- throws IOException {
+ }
+
+ @Override
+ public synchronized void registerListener(ChangeListener changeListener) throws RDFServiceException {
+ if (!registeredListeners.contains(changeListener)) {
+ registeredListeners.add(changeListener);
+ }
+ }
+
+ @Override
+ public synchronized void unregisterListener(ChangeListener changeListener) throws RDFServiceException {
+ registeredListeners.remove(changeListener);
+ }
+
+ @Override
+ public synchronized void registerJenaModelChangedListener(ModelChangedListener changeListener)
+ throws RDFServiceException {
+ if (!registeredJenaListeners.contains(changeListener)) {
+ registeredJenaListeners.add(changeListener);
+ }
+ }
+
+ @Override
+ public synchronized void unregisterJenaModelChangedListener(ModelChangedListener changeListener)
+ throws RDFServiceException {
+ registeredJenaListeners.remove(changeListener);
+ }
+
+ public synchronized List getRegisteredListeners() {
+ return this.registeredListeners;
+ }
+
+ public synchronized List getRegisteredJenaModelChangedListeners() {
+ return this.registeredJenaListeners;
+ }
+
+ @Override
+ public ChangeSet manufactureChangeSet() {
+ return new ChangeSetImpl();
+ }
+
+ protected void notifyListenersOfChanges(ChangeSet changeSet) throws IOException {
if (registeredListeners.isEmpty() && registeredJenaListeners.isEmpty()) {
return;
}
- for (ModelChange modelChange: changeSet.getModelChanges()) {
+ for (ModelChange modelChange : changeSet.getModelChanges()) {
notifyListeners(modelChange);
}
}
@@ -158,12 +174,10 @@ protected void notifyListeners(ModelChange modelChange) throws IOException {
}
if (Operation.ADD.equals(modelChange.getOperation())) {
tempModel.read(modelChange.getSerializedModel(), null,
- RDFServiceUtils.getSerializationFormatString(
- modelChange.getSerializationFormat()));
+ RDFServiceUtils.getSerializationFormatString(modelChange.getSerializationFormat()));
} else if (Operation.REMOVE.equals(modelChange.getOperation())) {
- tempModel.remove(RDFServiceUtils.parseModel(
- modelChange.getSerializedModel(),
- modelChange.getSerializationFormat()));
+ tempModel.remove(
+ RDFServiceUtils.parseModel(modelChange.getSerializedModel(), modelChange.getSerializationFormat()));
}
while (jenaIter.hasNext()) {
tempModel.unregister(jenaIter.next());
@@ -180,16 +194,15 @@ public void notifyListenersOfEvent(Object event) {
}
}
- protected boolean isPreconditionSatisfied(String query,
- RDFService.SPARQLQueryType queryType)
- throws RDFServiceException {
+ protected boolean isPreconditionSatisfied(String query, RDFService.SPARQLQueryType queryType)
+ throws RDFServiceException {
Model model = ModelFactory.createDefaultModel();
switch (queryType) {
case DESCRIBE:
- model.read(sparqlDescribeQuery(query,RDFService.ModelSerializationFormat.N3), null);
+ model.read(sparqlDescribeQuery(query, RDFService.ModelSerializationFormat.N3), null);
return !model.isEmpty();
case CONSTRUCT:
- model.read(sparqlConstructQuery(query,RDFService.ModelSerializationFormat.N3), null);
+ model.read(sparqlConstructQuery(query, RDFService.ModelSerializationFormat.N3), null);
return !model.isEmpty();
case SELECT:
return sparqlSelectQueryHasResults(query);
@@ -265,36 +278,54 @@ protected static String sparqlNode(Node node, String varName) {
}
}
- // see http://www.python.org/doc/2.5.2/ref/strings.html
- // or see jena's n3 grammar jena/src/org.apache/jena/n3/n3.g
- protected static void pyString(StringBuffer sbuff, String s) {
+ // see http://www.python.org/doc/2.5.2/ref/strings.html
+ // or see jena's n3 grammar jena/src/org.apache/jena/n3/n3.g
+ protected static void pyString(StringBuffer sbuff, String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// Escape escapes and quotes
- if (c == '\\' || c == '"' )
- {
- sbuff.append('\\') ;
- sbuff.append(c) ;
- continue ;
+ if (c == '\\' || c == '"') {
+ sbuff.append('\\');
+ sbuff.append(c);
+ continue;
}
// Whitespace
- if (c == '\n'){ sbuff.append("\\n");continue; }
- if (c == '\t'){ sbuff.append("\\t");continue; }
- if (c == '\r'){ sbuff.append("\\r");continue; }
- if (c == '\f'){ sbuff.append("\\f");continue; }
- if (c == '\b'){ sbuff.append("\\b");continue; }
- if( c == 7 ) { sbuff.append("\\a");continue; }
+ if (c == '\n') {
+ sbuff.append("\\n");
+ continue;
+ }
+ if (c == '\t') {
+ sbuff.append("\\t");
+ continue;
+ }
+ if (c == '\r') {
+ sbuff.append("\\r");
+ continue;
+ }
+ if (c == '\f') {
+ sbuff.append("\\f");
+ continue;
+ }
+ if (c == '\b') {
+ sbuff.append("\\b");
+ continue;
+ }
+ if (c == 7) {
+ sbuff.append("\\a");
+ continue;
+ }
// Output as is (subject to UTF-8 encoding on output that is)
- sbuff.append(c) ;
+ sbuff.append(c);
}
}
/**
- * Returns a pair of models. The first contains any statement containing at
- * least one blank node. The second contains all remaining statements.
+ * Returns a pair of models. The first contains any statement containing at least one blank node. The second
+ * contains all remaining statements.
+ *
* @param gm Jena model
*/
@@ -317,47 +348,45 @@ protected Model[] separateStatementsWithBlankNodes(Model gm) {
}
protected Query createQuery(String queryString) throws RDFServiceException {
- List syntaxes = Arrays.asList(
- Syntax.defaultQuerySyntax, Syntax.syntaxSPARQL_11,
- Syntax.syntaxSPARQL_10, Syntax.syntaxSPARQL, Syntax.syntaxARQ);
+ List syntaxes = Arrays.asList(Syntax.defaultQuerySyntax, Syntax.syntaxSPARQL_11, Syntax.syntaxSPARQL_10,
+ Syntax.syntaxSPARQL, Syntax.syntaxARQ);
Query q = null;
Iterator syntaxIt = syntaxes.iterator();
while (q == null) {
Syntax syntax = syntaxIt.next();
try {
- q = QueryFactory.create(queryString, syntax);
+ q = QueryFactory.create(queryString, syntax);
} catch (QueryParseException e) {
- if (!syntaxIt.hasNext()) {
- throw new RDFServiceException("Failed to parse query \""
- + queryString + "\"", e);
- }
+ if (!syntaxIt.hasNext()) {
+ throw new RDFServiceException("Failed to parse query \"" + queryString + "\"", e);
+ }
}
}
return q;
}
- @Override
- public String toString() {
- return ToString.simpleName(this) + "[" + ToString.hashHex(this) + "]";
- }
+ @Override
+ public String toString() {
+ return ToString.simpleName(this) + "[" + ToString.hashHex(this) + "]";
+ }
@Override
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
StringBuilder whereClause = new StringBuilder();
- if ( subject != null ) {
+ if (subject != null) {
appendNode(whereClause.append(' '), subject);
} else {
whereClause.append(" ?s");
}
- if ( predicate != null ) {
+ if (predicate != null) {
appendNode(whereClause.append(' '), predicate);
} else {
whereClause.append(" ?p");
}
- if ( object != null ) {
+ if (object != null) {
appendNode(whereClause.append(' '), object);
} else {
whereClause.append(" ?o");
@@ -376,25 +405,26 @@ public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) thr
}
@Override
- public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
+ public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset)
+ throws RDFServiceException {
StringBuilder whereClause = new StringBuilder();
StringBuilder orderBy = new StringBuilder();
- if ( subject != null ) {
+ if (subject != null) {
appendNode(whereClause.append(' '), subject);
} else {
whereClause.append(" ?s");
orderBy.append(" ?s");
}
- if ( predicate != null ) {
+ if (predicate != null) {
appendNode(whereClause.append(' '), predicate);
} else {
whereClause.append(" ?p");
orderBy.append(" ?p");
}
- if ( object != null ) {
+ if (object != null) {
appendNode(whereClause.append(' '), object);
} else {
whereClause.append(" ?o");
@@ -453,19 +483,34 @@ protected void processQuerySolution(QuerySolution qs) {
}
}
}
- /*
- * UQAM Useful among other things to transport the linguistic context in the service
- * (non-Javadoc)
- * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest)
- */
- private VitroRequest vitroRequest;
- public void setVitroRequest(VitroRequest vitroRequest) {
- this.vitroRequest = vitroRequest;
- }
+ /*
+ * UQAM Useful among other things to transport the linguistic context in the service (non-Javadoc)
+ *
+ * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.
+ * controller.VitroRequest)
+ */
+ private VitroRequest vitroRequest;
- public VitroRequest getVitroRequest() {
- return vitroRequest;
- }
+ public void setVitroRequest(VitroRequest vitroRequest) {
+ this.vitroRequest = vitroRequest;
+ }
+
+ public VitroRequest getVitroRequest() {
+ return vitroRequest;
+ }
+
+ protected void updateGraphURIs(Set newURIs) {
+ Set oldURIs = new HashSet(graphURIs);
+ if (newURIs.equals(oldURIs)) {
+ return;
+ }
+ Set removedURIs = new HashSet(oldURIs);
+ removedURIs.removeAll(newURIs);
+ graphURIs.removeAll(removedURIs);
+ Set addedURIs = new HashSet(newURIs);
+ addedURIs.removeAll(oldURIs);
+ graphURIs.addAll(addedURIs);
+ }
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java
index 84405d1696..f8cc93a677 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/jena/RDFServiceJena.java
@@ -9,9 +9,7 @@
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.dao.jena.DatasetWrapper;
@@ -58,58 +56,53 @@ public abstract class RDFServiceJena extends RDFServiceImpl implements RDFServic
protected abstract DatasetWrapper getDatasetWrapper();
- protected volatile boolean rebuildGraphURICache = true;
- protected volatile boolean isRebuildGraphURICacheRunning = false;
- protected final List graphURIs = new CopyOnWriteArrayList();
-
@Override
- public abstract boolean changeSetUpdate(ChangeSet changeSet) throws RDFServiceException;
+ public abstract boolean changeSetUpdate(ChangeSet changeSet) throws RDFServiceException;
protected void notifyListenersOfPreChangeEvents(ChangeSet changeSet) {
- for (Object o : changeSet.getPreChangeEvents()) {
- this.notifyListenersOfEvent(o);
- }
- }
+ for (Object o : changeSet.getPreChangeEvents()) {
+ this.notifyListenersOfEvent(o);
+ }
+ }
protected void insureThatInputStreamsAreResettable(ChangeSet changeSet) throws IOException {
- for (ModelChange modelChange: changeSet.getModelChanges()) {
+ for (ModelChange modelChange : changeSet.getModelChanges()) {
if (!modelChange.getSerializedModel().markSupported()) {
byte[] bytes = IOUtils.toByteArray(modelChange.getSerializedModel());
modelChange.setSerializedModel(new ByteArrayInputStream(bytes));
}
modelChange.getSerializedModel().mark(Integer.MAX_VALUE);
}
- }
+ }
protected void applyChangeSetToModel(ChangeSet changeSet, Dataset dataset) {
- for (ModelChange modelChange: changeSet.getModelChanges()) {
- dataset.getLock().enterCriticalSection(Lock.WRITE);
- try {
- Model model = (modelChange.getGraphURI() == null) ?
- dataset.getDefaultModel() :
- dataset.getNamedModel(modelChange.getGraphURI());
- operateOnModel(model, modelChange);
- } finally {
- dataset.getLock().leaveCriticalSection();
- }
- }
- }
+ for (ModelChange modelChange : changeSet.getModelChanges()) {
+ dataset.getLock().enterCriticalSection(Lock.WRITE);
+ try {
+ Model model = (modelChange.getGraphURI() == null) ? dataset.getDefaultModel()
+ : dataset.getNamedModel(modelChange.getGraphURI());
+ operateOnModel(model, modelChange);
+ } finally {
+ dataset.getLock().leaveCriticalSection();
+ }
+ }
+ }
protected void notifyListenersOfPostChangeEvents(ChangeSet changeSet) {
- for (Object o : changeSet.getPostChangeEvents()) {
- this.notifyListenersOfEvent(o);
- }
- }
+ for (Object o : changeSet.getPostChangeEvents()) {
+ this.notifyListenersOfEvent(o);
+ }
+ }
protected void operateOnModel(Model model, ModelChange modelChange) {
model.enterCriticalSection(Lock.WRITE);
try {
- if (log.isDebugEnabled()) {
- dumpOperation(model, modelChange);
- }
+ if (log.isDebugEnabled()) {
+ dumpOperation(model, modelChange);
+ }
if (modelChange.getOperation() == ModelChange.Operation.ADD) {
- Model addition = parseModel(modelChange);
- model.add(addition);
+ Model addition = parseModel(modelChange);
+ model.add(addition);
} else if (modelChange.getOperation() == ModelChange.Operation.REMOVE) {
Model removal = parseModel(modelChange);
JenaModelUtils.removeWithBlankNodesAsVariables(removal, model);
@@ -121,56 +114,52 @@ protected void operateOnModel(Model model, ModelChange modelChange) {
}
}
- /**
- * As a debug statement, log info about the model change operation: add or
- * delete, model URI, model class, punctuation count, beginning of the
- * string.
- */
- private void dumpOperation(Model model, ModelChange modelChange) {
- String op = String.valueOf(modelChange.getOperation());
-
- byte[] changeBytes = new byte[0];
- try {
- modelChange.getSerializedModel().mark(Integer.MAX_VALUE);
- changeBytes = IOUtils.toByteArray(modelChange.getSerializedModel());
- modelChange.getSerializedModel().reset();
- } catch (IOException e) {
- // leave it empty.
- }
-
- int puncCount = 0;
- boolean inUri = false;
- boolean inQuotes = false;
- for (byte b : changeBytes) {
- if (inQuotes) {
- if (b == '"') {
- inQuotes = false;
- }
- } else if (inUri) {
- if (b == '>') {
- inUri = false;
- }
- } else {
- if (b == '"') {
- inQuotes = true;
- } else if (b == '<') {
- inUri = true;
- } else if ((b == ',') || (b == ';') || (b == '.')) {
- puncCount++;
- }
- }
- }
-
- String changeString = new String(changeBytes).replace('\n', ' ');
-
- log.debug(String.format(
- ">>>>OPERATION: %3.3s %03dpunc, format=%s, graphUri='%s'\n"
- + " start=%.200s\n" + " model=%s",
- modelChange.getOperation(), puncCount,
- modelChange.getSerializationFormat(),
- modelChange.getGraphURI(), changeString,
- ToString.modelToString(model)));
- }
+ /**
+ * As a debug statement, log info about the model change operation: add or delete, model URI, model class,
+ * punctuation count, beginning of the string.
+ */
+ private void dumpOperation(Model model, ModelChange modelChange) {
+ String op = String.valueOf(modelChange.getOperation());
+
+ byte[] changeBytes = new byte[0];
+ try {
+ modelChange.getSerializedModel().mark(Integer.MAX_VALUE);
+ changeBytes = IOUtils.toByteArray(modelChange.getSerializedModel());
+ modelChange.getSerializedModel().reset();
+ } catch (IOException e) {
+ // leave it empty.
+ }
+
+ int puncCount = 0;
+ boolean inUri = false;
+ boolean inQuotes = false;
+ for (byte b : changeBytes) {
+ if (inQuotes) {
+ if (b == '"') {
+ inQuotes = false;
+ }
+ } else if (inUri) {
+ if (b == '>') {
+ inUri = false;
+ }
+ } else {
+ if (b == '"') {
+ inQuotes = true;
+ } else if (b == '<') {
+ inUri = true;
+ } else if ((b == ',') || (b == ';') || (b == '.')) {
+ puncCount++;
+ }
+ }
+ }
+
+ String changeString = new String(changeBytes).replace('\n', ' ');
+
+ log.debug(String.format(
+ ">>>>OPERATION: %3.3s %03dpunc, format=%s, graphUri='%s'\n" + " start=%.200s\n" + " model=%s",
+ modelChange.getOperation(), puncCount, modelChange.getSerializationFormat(), modelChange.getGraphURI(),
+ changeString, ToString.modelToString(model)));
+ }
private Model parseModel(ModelChange modelChange) {
Model model = ModelFactory.createDefaultModel();
@@ -179,8 +168,8 @@ private Model parseModel(ModelChange modelChange) {
return model;
}
- private InputStream getRDFResultStream(String query, boolean construct,
- ModelSerializationFormat resultFormat) throws RDFServiceException {
+ private InputStream getRDFResultStream(String query, boolean construct, ModelSerializationFormat resultFormat)
+ throws RDFServiceException {
DatasetWrapper dw = getDatasetWrapper();
try {
Dataset d = dw.getDataset();
@@ -222,8 +211,8 @@ private void getRDFModel(String query, boolean construct, Model model) throws RD
private static final boolean DESCRIBE = false;
@Override
- public InputStream sparqlConstructQuery(String query,
- ModelSerializationFormat resultFormat) throws RDFServiceException {
+ public InputStream sparqlConstructQuery(String query, ModelSerializationFormat resultFormat)
+ throws RDFServiceException {
return getRDFResultStream(query, CONSTRUCT, resultFormat);
}
@@ -232,17 +221,16 @@ public void sparqlConstructQuery(String query, Model model) throws RDFServiceExc
}
@Override
- public InputStream sparqlDescribeQuery(String query,
- ModelSerializationFormat resultFormat) throws RDFServiceException {
+ public InputStream sparqlDescribeQuery(String query, ModelSerializationFormat resultFormat)
+ throws RDFServiceException {
return getRDFResultStream(query, DESCRIBE, resultFormat);
}
- /**
- * TODO Is there a way to accomplish this without buffering the entire result?
- */
+ /**
+ * TODO Is there a way to accomplish this without buffering the entire result?
+ */
@Override
- public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat)
- throws RDFServiceException {
+ public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat) throws RDFServiceException {
DatasetWrapper dw = getDatasetWrapper();
try {
Dataset d = dw.getDataset();
@@ -252,20 +240,20 @@ public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat)
ResultSet resultSet = qe.execSelect();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
switch (resultFormat) {
- case CSV:
- ResultSetFormatter.outputAsCSV(outputStream,resultSet);
- break;
- case TEXT:
- ResultSetFormatter.out(outputStream,resultSet);
- break;
- case JSON:
- ResultSetFormatter.outputAsJSON(outputStream, resultSet);
- break;
- case XML:
- ResultSetFormatter.outputAsXML(outputStream, resultSet);
- break;
- default:
- throw new RDFServiceException("unrecognized result format");
+ case CSV:
+ ResultSetFormatter.outputAsCSV(outputStream, resultSet);
+ break;
+ case TEXT:
+ ResultSetFormatter.out(outputStream, resultSet);
+ break;
+ case JSON:
+ ResultSetFormatter.outputAsJSON(outputStream, resultSet);
+ break;
+ case XML:
+ ResultSetFormatter.outputAsXML(outputStream, resultSet);
+ break;
+ default:
+ throw new RDFServiceException("unrecognized result format");
}
InputStream result = new ByteArrayInputStream(outputStream.toByteArray());
return result;
@@ -278,8 +266,7 @@ public InputStream sparqlSelectQuery(String query, ResultFormat resultFormat)
}
@Override
- public void sparqlSelectQuery(String query, ResultSetConsumer consumer)
- throws RDFServiceException {
+ public void sparqlSelectQuery(String query, ResultSetConsumer consumer) throws RDFServiceException {
DatasetWrapper dw = getDatasetWrapper();
try {
Dataset d = dw.getDataset();
@@ -295,7 +282,7 @@ public void sparqlSelectQuery(String query, ResultSetConsumer consumer)
}
}
- @Override
+ @Override
public boolean sparqlAskQuery(String query) throws RDFServiceException {
DatasetWrapper dw = getDatasetWrapper();
try {
@@ -312,14 +299,6 @@ public boolean sparqlAskQuery(String query) throws RDFServiceException {
}
}
- @Override
- public List getGraphURIs() throws RDFServiceException {
- if (rebuildGraphURICache && !isRebuildGraphURICacheRunning) {
- rebuildGraphUris();
- }
- return graphURIs;
- }
-
protected void rebuildGraphUris() {
Thread thread = new VitroBackgroundThread(new Runnable() {
public void run() {
@@ -328,33 +307,24 @@ public void run() {
DatasetWrapper dw = getDatasetWrapper();
try {
isRebuildGraphURICacheRunning = true;
+ rebuildGraphURICache = false;
Dataset d = dw.getDataset();
- Set newGraphUris = new HashSet<>();
+ Set newURIs = new HashSet<>();
d.begin(ReadWrite.READ);
try {
Iterator nameIt = d.listNames();
while (nameIt.hasNext()) {
- newGraphUris.add(nameIt.next());
+ newURIs.add(nameIt.next());
}
} finally {
d.end();
}
- Set oldGraphUris = new HashSet(graphURIs);
- if (newGraphUris.equals(oldGraphUris)) {
- return;
- }
- Set removedGraphUris = new HashSet(oldGraphUris);
- removedGraphUris.removeAll(newGraphUris);
- graphURIs.removeAll(removedGraphUris);
- Set addedGraphUris = new HashSet(newGraphUris);
- addedGraphUris.removeAll(oldGraphUris);
- graphURIs.addAll(addedGraphUris);
+ updateGraphURIs(newURIs);
} catch (Exception e) {
log.error(e, e);
} finally {
isRebuildGraphURICacheRunning = false;
dw.close();
- rebuildGraphURICache = false;
}
}
}
@@ -365,66 +335,64 @@ public void run() {
@Override
public void getGraphMetadata() throws RDFServiceException {
- // nothing to do
+ // nothing to do
+ }
+
+ @Override
+ public void serializeAll(OutputStream outputStream) throws RDFServiceException {
+ String query = "SELECT * WHERE { GRAPH ?g {?s ?p ?o}}";
+ serialize(outputStream, query);
+ }
+
+ @Override
+ public void serializeGraph(String graphURI, OutputStream outputStream) throws RDFServiceException {
+ String query = "SELECT * WHERE { GRAPH <" + graphURI + "> {?s ?p ?o}}";
+ serialize(outputStream, query);
+ }
+
+ private void serialize(OutputStream outputStream, String query) throws RDFServiceException {
+ DatasetWrapper dw = getDatasetWrapper();
+ try {
+ Dataset d = dw.getDataset();
+ Query q = createQuery(query);
+ QueryExecution qe = createQueryExecution(query, q, d);
+ // These properties only help for SDB, but shouldn't hurt for TDB.
+ qe.getContext().set(SDB.jdbcFetchSize, Integer.MIN_VALUE);
+ qe.getContext().set(SDB.jdbcStream, true);
+ qe.getContext().set(SDB.streamGraphAPI, true);
+ try {
+ ResultSet resultSet = qe.execSelect();
+ if (resultSet.getResultVars().contains("g")) {
+ Iterator quads = new ResultSetQuadsIterator(resultSet);
+ RDFDataMgr.writeQuads(outputStream, quads);
+ } else {
+ Iterator triples = new ResultSetTriplesIterator(resultSet);
+ RDFDataMgr.writeTriples(outputStream, triples);
+ }
+ } finally {
+ qe.close();
+ }
+ } finally {
+ dw.close();
+ }
}
+ /**
+ * The basic version. Parse the model from the file, read the model from the tripleStore, and ask whether they are
+ * isomorphic.
+ */
@Override
- public void serializeAll(OutputStream outputStream)
- throws RDFServiceException {
- String query = "SELECT * WHERE { GRAPH ?g {?s ?p ?o}}";
- serialize(outputStream, query);
- }
-
- @Override
- public void serializeGraph(String graphURI, OutputStream outputStream)
- throws RDFServiceException {
- String query = "SELECT * WHERE { GRAPH <" + graphURI + "> {?s ?p ?o}}";
- serialize(outputStream, query);
- }
-
- private void serialize(OutputStream outputStream, String query) throws RDFServiceException {
- DatasetWrapper dw = getDatasetWrapper();
- try {
- Dataset d = dw.getDataset();
- Query q = createQuery(query);
- QueryExecution qe = createQueryExecution(query, q, d);
- // These properties only help for SDB, but shouldn't hurt for TDB.
- qe.getContext().set(SDB.jdbcFetchSize, Integer.MIN_VALUE);
- qe.getContext().set(SDB.jdbcStream, true);
- qe.getContext().set(SDB.streamGraphAPI, true);
- try {
- ResultSet resultSet = qe.execSelect();
- if (resultSet.getResultVars().contains("g")) {
- Iterator quads = new ResultSetQuadsIterator(resultSet);
- RDFDataMgr.writeQuads(outputStream, quads);
- } else {
- Iterator triples = new ResultSetTriplesIterator(resultSet);
- RDFDataMgr.writeTriples(outputStream, triples);
- }
- } finally {
- qe.close();
- }
- } finally {
- dw.close();
- }
- }
-
- /**
- * The basic version. Parse the model from the file, read the model from the
- * tripleStore, and ask whether they are isomorphic.
- */
- @Override
- public boolean isEquivalentGraph(String graphURI, InputStream serializedGraph,
- ModelSerializationFormat serializationFormat) throws RDFServiceException {
- Model fileModel = RDFServiceUtils.parseModel(serializedGraph, serializationFormat);
- Model tripleStoreModel = new RDFServiceDataset(this).getNamedModel(graphURI);
- Model fromTripleStoreModel = ModelFactory.createDefaultModel().add(tripleStoreModel);
- return fileModel.isIsomorphicWith(fromTripleStoreModel);
- }
+ public boolean isEquivalentGraph(String graphURI, InputStream serializedGraph,
+ ModelSerializationFormat serializationFormat) throws RDFServiceException {
+ Model fileModel = RDFServiceUtils.parseModel(serializedGraph, serializationFormat);
+ Model tripleStoreModel = new RDFServiceDataset(this).getNamedModel(graphURI);
+ Model fromTripleStoreModel = ModelFactory.createDefaultModel().add(tripleStoreModel);
+ return fileModel.isIsomorphicWith(fromTripleStoreModel);
+ }
/**
- * The basic version. Parse the model from the file, read the model from the
- * tripleStore, and ask whether they are isomorphic.
+ * The basic version. Parse the model from the file, read the model from the tripleStore, and ask whether they are
+ * isomorphic.
*/
@Override
public boolean isEquivalentGraph(String graphURI, Model graph) throws RDFServiceException {
@@ -439,15 +407,16 @@ public boolean isEquivalentGraph(String graphURI, Model graph) throws RDFService
@Override
public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) throws RDFServiceException {
- Query countQuery = QueryFactory.create("SELECT (COUNT(?s) AS ?count) WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o", Syntax.syntaxSPARQL_11);
+ Query countQuery = QueryFactory.create("SELECT (COUNT(?s) AS ?count) WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o",
+ Syntax.syntaxSPARQL_11);
QuerySolutionMap map = new QuerySolutionMap();
- if ( subject != null ) {
+ if (subject != null) {
map.add("s", subject);
}
- if ( predicate != null ) {
+ if (predicate != null) {
map.add("p", predicate);
}
- if ( object != null ) {
+ if (object != null) {
map.add("o", object);
}
@@ -457,7 +426,7 @@ public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) thr
try (QueryExecution qexec = QueryExecutionFactory.create(countQuery, d, map)) {
ResultSet results = qexec.execSelect();
if (results.hasNext()) {
- QuerySolution soln = results.nextSolution() ;
+ QuerySolution soln = results.nextSolution();
Literal literal = soln.getLiteral("count");
return literal.getLong();
}
@@ -470,16 +439,17 @@ public long countTriples(RDFNode subject, RDFNode predicate, RDFNode object) thr
}
@Override
- public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset) throws RDFServiceException {
+ public Model getTriples(RDFNode subject, RDFNode predicate, RDFNode object, long limit, long offset)
+ throws RDFServiceException {
Query query = QueryFactory.create("CONSTRUCT WHERE { ?s ?p ?o }", Syntax.syntaxSPARQL_11);
QuerySolutionMap map = new QuerySolutionMap();
- if ( subject != null ) {
+ if (subject != null) {
map.add("s", subject);
}
- if ( predicate != null ) {
+ if (predicate != null) {
map.add("p", predicate);
}
- if ( object != null ) {
+ if (object != null) {
map.add("o", object);
}
@@ -514,19 +484,22 @@ public void close() {
protected QueryExecution createQueryExecution(String queryString, Query q, Dataset d) {
return QueryExecutionFactory.create(q, d);
}
- /*
- * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service
- * (non-Javadoc)
- * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.controller.VitroRequest)
- */
- private VitroRequest vitroRequest;
-
- public void setVitroRequest(VitroRequest vitroRequest) {
- this.vitroRequest = vitroRequest;
- }
-
- public VitroRequest getVitroRequest() {
- return vitroRequest;
- }
+
+ /*
+ * UQAM-Linguistic-Management Useful among other things to transport the linguistic context in the service
+ * (non-Javadoc)
+ *
+ * @see edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService#setVitroRequest(edu.cornell.mannlib.vitro.webapp.
+ * controller.VitroRequest)
+ */
+ private VitroRequest vitroRequest;
+
+ public void setVitroRequest(VitroRequest vitroRequest) {
+ this.vitroRequest = vitroRequest;
+ }
+
+ public VitroRequest getVitroRequest() {
+ return vitroRequest;
+ }
}
diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/sparql/RDFServiceSparql.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/sparql/RDFServiceSparql.java
index c3fec0c1b0..ed8767c559 100644
--- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/sparql/RDFServiceSparql.java
+++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/rdfservice/impl/sparql/RDFServiceSparql.java
@@ -11,11 +11,29 @@
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collections;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
+import edu.cornell.mannlib.vitro.webapp.dao.jena.JenaModelUtils;
+import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset;
+import edu.cornell.mannlib.vitro.webapp.dao.jena.SparqlGraph;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.ChangeSetImpl;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceImpl;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
+import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.jena.RDFServiceJena;
+import edu.cornell.mannlib.vitro.webapp.utils.http.HttpClientFactory;
+import edu.cornell.mannlib.vitro.webapp.utils.sparql.ResultSetIterators.ResultSetQuadsIterator;
+import edu.cornell.mannlib.vitro.webapp.utils.sparql.ResultSetIterators.ResultSetTriplesIterator;
+import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread;
import org.apache.commons.io.IOUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -56,22 +74,6 @@
import org.apache.jena.riot.RDFDataMgr;
import org.apache.jena.sparql.core.Quad;
-import edu.cornell.mannlib.vitro.webapp.dao.jena.JenaModelUtils;
-import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset;
-import edu.cornell.mannlib.vitro.webapp.dao.jena.SparqlGraph;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeListener;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ChangeSet;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ModelChange;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFService;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.RDFServiceException;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.ResultSetConsumer;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.ChangeSetImpl;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceImpl;
-import edu.cornell.mannlib.vitro.webapp.rdfservice.impl.RDFServiceUtils;
-import edu.cornell.mannlib.vitro.webapp.utils.http.HttpClientFactory;
-import edu.cornell.mannlib.vitro.webapp.utils.sparql.ResultSetIterators.ResultSetQuadsIterator;
-import edu.cornell.mannlib.vitro.webapp.utils.sparql.ResultSetIterators.ResultSetTriplesIterator;
-
/*
* API to write, read, and update Vitro's RDF store, with support
* to allow listening, logging and auditing.
@@ -79,864 +81,848 @@
*/
public class RDFServiceSparql extends RDFServiceImpl implements RDFService {
- private static final Log log = LogFactory.getLog(RDFServiceImpl.class);
- protected String readEndpointURI;
- protected String updateEndpointURI;
- // the number of triples to be
- private static final int CHUNK_SIZE = 5000; // added/removed in a single
- // SPARQL UPDATE
-
- protected HttpClient httpClient;
-
- protected boolean rebuildGraphURICache = true;
- private List graphURIs = null;
-
- /**
- * Returns an RDFService for a remote repository
- * @param readEndpointURI - URI of the read SPARQL endpoint for the knowledge base
- * @param updateEndpointURI - URI of the update SPARQL endpoint for the knowledge base
- * @param defaultWriteGraphURI - URI of the default write graph within the knowledge base.
- * this is the graph that will be written to when a graph
- * is not explicitly specified.
- *
- * The default read graph is the union of all graphs in the
- * knowledge base
- */
- public RDFServiceSparql(String readEndpointURI, String updateEndpointURI, String defaultWriteGraphURI) {
- this.readEndpointURI = readEndpointURI;
- this.updateEndpointURI = updateEndpointURI;
- httpClient = HttpClientFactory.getHttpClient();
-
- if (RDFServiceSparql.class.getName().equals(this.getClass().getName())) {
- testConnection();
- }
- }
-
- protected void testConnection() {
- try {
- this.sparqlSelectQuery(
- "SELECT ?s WHERE { ?s a " +
- " }",
- RDFService.ResultFormat.JSON);
- } catch (Exception e) {
- throw new RuntimeException("Unable to connect to endpoint at " +
- readEndpointURI, e);
- }
- }
-
- /**
- * Returns an RDFService for a remote repository
- * @param readEndpointURI - URI of the read SPARQL endpoint for the knowledge base
- * @param updateEndpointURI - URI of the update SPARQL endpoint for the knowledge base
- *
- * The default read graph is the union of all graphs in the
- * knowledge base
- */
- public RDFServiceSparql(String readEndpointURI, String updateEndpointURI) {
- this(readEndpointURI, updateEndpointURI, null);
- }
-
- /**
- * Returns an RDFService for a remote repository
- * @param endpointURI - URI of the read and update SPARQL endpoint for the knowledge base
- *
- * The default read graph is the union of all graphs in the
- * knowledge base
- */
- public RDFServiceSparql(String endpointURI) {
- this(endpointURI, endpointURI, null);
- }
-
- public void close() {
- // nothing for now
- }
-
- /**
- * Perform a series of additions to and or removals from specified graphs
- * in the RDF store. preConditionSparql will be executed against the
- * union of all the graphs in the knowledge base before any updates are made.
- * If the precondition query returns a non-empty result no updates
- * will be made.
- *
- * @param changeSet - a set of changes to be performed on the RDF store.
- *
- * @return boolean - indicates whether the precondition was satisfied
- */
- @Override
- public boolean changeSetUpdate(ChangeSet changeSet)
- throws RDFServiceException {
-
- if (changeSet.getPreconditionQuery() != null
- && !isPreconditionSatisfied(
- changeSet.getPreconditionQuery(),
- changeSet.getPreconditionQueryType())) {
- return false;
- }
-
- try {
-
- for (Object o : changeSet.getPreChangeEvents()) {
- this.notifyListenersOfEvent(o);
- }
-
- for (ModelChange modelChange : changeSet.getModelChanges()) {
- if (!modelChange.getSerializedModel().markSupported()) {
- byte[] bytes = IOUtils.toByteArray(modelChange.getSerializedModel());
- modelChange.setSerializedModel(new ByteArrayInputStream(bytes));
- }
- modelChange.getSerializedModel().mark(Integer.MAX_VALUE);
- performChange(modelChange);
- }
-
- notifyListenersOfChanges(changeSet);
-
- for (Object o : changeSet.getPostChangeEvents()) {
- this.notifyListenersOfEvent(o);
- }
-
- } catch (Exception e) {
- log.error(e, e);
- throw new RDFServiceException(e);
- } finally {
- rebuildGraphURICache = true;
- }
- return true;
- }
-
- /**
- * Performs a SPARQL construct query against the knowledge base. The query may have
- * an embedded graph identifier.
- *
- * @param queryStr - the SPARQL query to be executed against the RDF store
- * @param resultFormat - type of serialization for RDF result of the SPARQL query
- */
- @Override
- public InputStream sparqlConstructQuery(String queryStr,
- RDFServiceImpl.ModelSerializationFormat resultFormat) throws RDFServiceException {
-
- Model model = ModelFactory.createDefaultModel();
- Query query = createQuery(queryStr);
- QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
-
- try {
- qe.execConstruct(model);
- } catch (Exception e) {
- log.error("Error executing CONSTRUCT against remote endpoint: " + queryStr);
- } finally {
- qe.close();
- }
-
- ByteArrayOutputStream serializedModel = new ByteArrayOutputStream();
- model.write(serializedModel,getSerializationFormatString(resultFormat));
- InputStream result = new ByteArrayInputStream(serializedModel.toByteArray());
- return result;
- }
-
- public void sparqlConstructQuery(String queryStr, Model model) throws RDFServiceException {
-
- Query query = createQuery(queryStr);
- QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
-
- try {
- qe.execConstruct(model);
- } catch (Exception e) {
- log.error("Error executing CONSTRUCT against remote endpoint: " + queryStr);
- } finally {
- qe.close();
- }
- }
-
- /**
- * Performs a SPARQL describe query against the knowledge base. The query may have
- * an embedded graph identifier.
- *
- * @param queryStr - the SPARQL query to be executed against the RDF store
- * @param resultFormat - type of serialization for RDF result of the SPARQL query
- *
- * @return InputStream - the result of the query
- *
- */
- @Override
- public InputStream sparqlDescribeQuery(String queryStr,
- RDFServiceImpl.ModelSerializationFormat resultFormat) throws RDFServiceException {
-
- Model model = ModelFactory.createDefaultModel();
- Query query = createQuery(queryStr);
- QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
-
- try {
- qe.execDescribe(model);
- } finally {
- qe.close();
- }
-
- ByteArrayOutputStream serializedModel = new ByteArrayOutputStream();
- model.write(serializedModel,getSerializationFormatString(resultFormat));
- InputStream result = new ByteArrayInputStream(serializedModel.toByteArray());
- return result;
- }
-
- /**
- * Performs a SPARQL select query against the knowledge base. The query may have
- * an embedded graph identifier.
- *
- * @param queryStr - the SPARQL query to be executed against the RDF store
- * @param resultFormat - format for the result of the Select query
- *
- * @return InputStream - the result of the query
- *
- */
- @Override
- public InputStream sparqlSelectQuery(String queryStr, RDFService.ResultFormat resultFormat) throws RDFServiceException {
-
- //QueryEngineHTTP qh = new QueryEngineHTTP(readEndpointURI, queryStr);
-
- try {
- HttpGet meth = new HttpGet(new URIBuilder(readEndpointURI).addParameter("query", queryStr).build());
- meth.addHeader("Accept", "application/sparql-results+xml");
- HttpContext context = getContext(meth);
- HttpResponse response = context != null ? httpClient.execute(meth, context) : httpClient.execute(meth);
- try {
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode > 399) {
- log.error("response " + statusCode + " to query. \n");
- log.debug("update string: \n" + queryStr);
- throw new RDFServiceException("Unable to perform SPARQL SELECT");
- }
-
- try (InputStream in = response.getEntity().getContent()) {
- ResultSet resultSet = ResultSetFactory.fromXML(in);
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- switch (resultFormat) {
- case CSV:
- ResultSetFormatter.outputAsCSV(outputStream, resultSet);
- break;
- case TEXT:
- ResultSetFormatter.out(outputStream, resultSet);
- break;
- case JSON:
- ResultSetFormatter.outputAsJSON(outputStream, resultSet);
- break;
- case XML:
- ResultSetFormatter.outputAsXML(outputStream, resultSet);
- break;
- default:
- throw new RDFServiceException("unrecognized result format");
- }
- InputStream result = new ByteArrayInputStream(
- outputStream.toByteArray());
- return result;
- }
- } finally {
- EntityUtils.consume(response.getEntity());
- }
- } catch (IOException | URISyntaxException ioe) {
- throw new RuntimeException(ioe);
- }
- }
-
- public void sparqlSelectQuery(String queryStr, ResultSetConsumer consumer) throws RDFServiceException {
-
- //QueryEngineHTTP qh = new QueryEngineHTTP(readEndpointURI, queryStr);
-
- try {
- HttpGet meth = new HttpGet(new URIBuilder(readEndpointURI).addParameter("query", queryStr).build());
- meth.addHeader("Accept", "application/sparql-results+xml");
- HttpContext context = getContext(meth);
- HttpResponse response = context != null ? httpClient.execute(meth, context) : httpClient.execute(meth);
- try {
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode > 399) {
- log.error("response " + statusCode + " to query. \n");
- log.debug("update string: \n" + queryStr);
- throw new RDFServiceException("Unable to perform SPARQL UPDATE");
- }
-
- try (InputStream in = response.getEntity().getContent()) {
- consumer.processResultSet(ResultSetFactory.fromXML(in));
- }
- } finally {
- EntityUtils.consume(response.getEntity());
- }
- } catch (IOException | URISyntaxException ioe) {
- throw new RuntimeException(ioe);
- }
- }
-
- /**
- * Performs a SPARQL ASK query against the knowledge base. The query may have
- * an embedded graph identifier.
- *
- * @param queryStr - the SPARQL query to be executed against the RDF store
- *
- * @return boolean - the result of the SPARQL query
- */
- @Override
- public boolean sparqlAskQuery(String queryStr) throws RDFServiceException {
-
- Query query = createQuery(queryStr);
- QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
-
- try {
- return qe.execAsk();
- } finally {
- qe.close();
- }
- }
-
- /**
- * Get a list of all the graph URIs in the RDF store.
- */
- @Override
- public List getGraphURIs() throws RDFServiceException {
- if (graphURIs == null || rebuildGraphURICache) {
- graphURIs = getGraphURIsFromSparqlQuery();
- rebuildGraphURICache = false;
- }
- return graphURIs;
- }
-
- private List getGraphURIsFromSparqlQuery() throws RDFServiceException {
- String fastJenaQuery = "SELECT DISTINCT ?g WHERE { GRAPH ?g {} } ORDER BY ?g";
- String standardQuery = "SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }";
- List graphURIs = new ArrayList();
- try {
- graphURIs = getGraphURIsFromSparqlQuery(fastJenaQuery);
- } catch (Exception e) {
- log.debug("Unable to use non-standard ARQ query for graph list", e);
- }
- if (graphURIs.isEmpty()) {
- graphURIs = getGraphURIsFromSparqlQuery(standardQuery);
- Collections.sort(graphURIs);
- }
- return graphURIs;
- }
-
- private List getGraphURIsFromSparqlQuery(String queryString) throws RDFServiceException {
- final List graphURIs = new ArrayList();
- try {
- sparqlSelectQuery(queryString, new ResultSetConsumer() {
- @Override
- protected void processQuerySolution(QuerySolution qs) {
- if (qs != null) { // no idea how this happens, but it seems to
- RDFNode n = qs.getResource("g");
- if (n != null && n.isResource()) {
- graphURIs.add(((Resource) n).getURI());
- }
- }
- }
- });
- } catch (Exception e) {
- throw new RDFServiceException("Unable to list graph URIs", e);
- }
- return graphURIs;
- }
-
- /**
- */
- @Override
- public void getGraphMetadata() throws RDFServiceException {
- throw new UnsupportedOperationException();
- }
-
- /**
- * Get the URI of the default write graph
- *
- * @return String URI of default write graph
- */
- @Override
- public String getDefaultWriteGraphURI() throws RDFServiceException {
- return defaultWriteGraphURI;
- }
-
- /**
- * Register a listener to listen to changes in any graph in
- * the RDF store.
- *
- */
- @Override
- public synchronized void registerListener(ChangeListener changeListener) throws RDFServiceException {
-
- if (!registeredListeners.contains(changeListener)) {
- registeredListeners.add(changeListener);
- }
- }
-
- /**
- * Unregister a listener from listening to changes in any graph
- * in the RDF store.
- *
- */
- @Override
- public synchronized void unregisterListener(ChangeListener changeListener) throws RDFServiceException {
- registeredListeners.remove(changeListener);
- }
-
- /**
- * Create a ChangeSet object
- *
- * @return a ChangeSet object
- */
- @Override
- public ChangeSet manufactureChangeSet() {
- return new ChangeSetImpl();
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // Non-override methods below
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- protected String getReadEndpointURI() {
- return readEndpointURI;
- }
-
- protected String getUpdateEndpointURI() {
- return updateEndpointURI;
- }
-
- protected void executeUpdate(String updateString) throws RDFServiceException {
- try {
- HttpPost meth = new HttpPost(updateEndpointURI);
- meth.addHeader("Content-Type", "application/x-www-form-urlencoded; charset="+Consts.UTF_8);
- meth.setEntity(new UrlEncodedFormEntity(Arrays.asList(new BasicNameValuePair("update", updateString)),Consts.UTF_8));
- HttpContext context = getContext(meth);
- HttpResponse response = context != null ? httpClient.execute(meth, context) : httpClient.execute(meth);
- try {
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode > 399) {
- log.error("response " + response.getStatusLine() + " to update. \n");
- //log.debug("update string: \n" + updateString);
- throw new RDFServiceException("Unable to perform SPARQL UPDATE");
- }
- } finally {
- EntityUtils.consume(response.getEntity());
- }
- } catch (Exception e) {
- log.debug("update string: \n" + updateString);
- throw new RDFServiceException("Unable to perform change set update", e);
- }
- }
-
- private void addModel(Model model, String graphURI) throws RDFServiceException {
- try {
- long start = System.currentTimeMillis();
- verbModel(model, graphURI, "INSERT");
- log.info((System.currentTimeMillis() - start) + " ms to insert " + model.size() + " triples");
- } finally {
- rebuildGraphURICache = true;
- }
- }
-
- private void deleteModel(Model model, String graphURI) throws RDFServiceException {
- try {
- verbModel(model, graphURI, "DELETE");
- } finally {
- rebuildGraphURICache = true;
- }
- }
-
- private void verbModel(Model model, String graphURI, String verb) throws RDFServiceException {
- Model m = ModelFactory.createDefaultModel();
- StmtIterator stmtIt = model.listStatements();
- int count = 0;
- try {
- while (stmtIt.hasNext()) {
- count++;
- m.add(stmtIt.nextStatement());
- if (count % CHUNK_SIZE == 0 || !stmtIt.hasNext()) {
- StringWriter sw = new StringWriter();
- m.write(sw, "N-TRIPLE");
- StringBuilder updateStringBuff = new StringBuilder();
- updateStringBuff.append(verb).append(" DATA { ").append((graphURI != null) ? "GRAPH <" + graphURI + "> { " : "");
- updateStringBuff.append(sw);
- updateStringBuff.append((graphURI != null) ? " } " : "").append(" }");
-
- String updateString = updateStringBuff.toString();
-
- executeUpdate(updateString);
-
- m.removeAll();
- }
- }
- } finally {
- stmtIt.close();
- }
- }
-
-// protected void addTriple(Triple t, String graphURI) throws RDFServiceException {
-// try {
-// StringBuffer updateString = new StringBuffer();
-// updateString.append("INSERT DATA { ");
-// updateString.append((graphURI != null) ? "GRAPH <" + graphURI + "> { " : "");
-// updateString.append(sparqlNodeUpdate(t.getSubject(), ""));
-// updateString.append(" ");
-// updateString.append(sparqlNodeUpdate(t.getPredicate(), ""));
-// updateString.append(" ");
-// updateString.append(sparqlNodeUpdate(t.getObject(), ""));
-// updateString.append(" }");
-// updateString.append((graphURI != null) ? " } " : "");
+ private static final Log log = LogFactory.getLog(RDFServiceImpl.class);
+ protected String readEndpointURI;
+ protected String updateEndpointURI;
+ // the number of triples to be
+ private static final int CHUNK_SIZE = 5000; // added/removed in a single
+ // SPARQL UPDATE
+
+ protected HttpClient httpClient;
+
+ /**
+ * Returns an RDFService for a remote repository
+ *
+ * @param readEndpointURI - URI of the read SPARQL endpoint for the knowledge base
+ * @param updateEndpointURI - URI of the update SPARQL endpoint for the knowledge base
+ * @param defaultWriteGraphURI - URI of the default write graph within the knowledge base. this is the graph that
+ * will be written to when a graph is not explicitly specified.
+ *
+ * The default read graph is the union of all graphs in the knowledge base
+ */
+ public RDFServiceSparql(String readEndpointURI, String updateEndpointURI, String defaultWriteGraphURI) {
+ this.readEndpointURI = readEndpointURI;
+ this.updateEndpointURI = updateEndpointURI;
+ httpClient = HttpClientFactory.getHttpClient();
+
+ if (RDFServiceSparql.class.getName().equals(this.getClass().getName())) {
+ testConnection();
+ }
+ }
+
+ protected void testConnection() {
+ try {
+ this.sparqlSelectQuery(
+ "SELECT ?s WHERE { ?s a " + " }",
+ RDFService.ResultFormat.JSON);
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to connect to endpoint at " + readEndpointURI, e);
+ }
+ }
+
+ /**
+ * Returns an RDFService for a remote repository
+ *
+ * @param readEndpointURI - URI of the read SPARQL endpoint for the knowledge base
+ * @param updateEndpointURI - URI of the update SPARQL endpoint for the knowledge base
+ *
+ * The default read graph is the union of all graphs in the knowledge base
+ */
+ public RDFServiceSparql(String readEndpointURI, String updateEndpointURI) {
+ this(readEndpointURI, updateEndpointURI, null);
+ }
+
+ /**
+ * Returns an RDFService for a remote repository
+ *
+ * @param endpointURI - URI of the read and update SPARQL endpoint for the knowledge base
+ *
+ * The default read graph is the union of all graphs in the knowledge base
+ */
+ public RDFServiceSparql(String endpointURI) {
+ this(endpointURI, endpointURI, null);
+ }
+
+ public void close() {
+ // nothing for now
+ }
+
+ /**
+ * Perform a series of additions to and or removals from specified graphs in the RDF store. preConditionSparql will
+ * be executed against the union of all the graphs in the knowledge base before any updates are made. If the
+ * precondition query returns a non-empty result no updates will be made.
+ *
+ * @param changeSet - a set of changes to be performed on the RDF store.
+ *
+ * @return boolean - indicates whether the precondition was satisfied
+ */
+ @Override
+ public boolean changeSetUpdate(ChangeSet changeSet) throws RDFServiceException {
+
+ if (changeSet.getPreconditionQuery() != null
+ && !isPreconditionSatisfied(changeSet.getPreconditionQuery(), changeSet.getPreconditionQueryType())) {
+ return false;
+ }
+
+ try {
+
+ for (Object o : changeSet.getPreChangeEvents()) {
+ this.notifyListenersOfEvent(o);
+ }
+
+ for (ModelChange modelChange : changeSet.getModelChanges()) {
+ if (!modelChange.getSerializedModel().markSupported()) {
+ byte[] bytes = IOUtils.toByteArray(modelChange.getSerializedModel());
+ modelChange.setSerializedModel(new ByteArrayInputStream(bytes));
+ }
+ modelChange.getSerializedModel().mark(Integer.MAX_VALUE);
+ performChange(modelChange);
+ }
+
+ notifyListenersOfChanges(changeSet);
+
+ for (Object o : changeSet.getPostChangeEvents()) {
+ this.notifyListenersOfEvent(o);
+ }
+
+ } catch (Exception e) {
+ log.error(e, e);
+ throw new RDFServiceException(e);
+ } finally {
+ rebuildGraphURICache = true;
+ }
+ return true;
+ }
+
+ /**
+ * Performs a SPARQL construct query against the knowledge base. The query may have an embedded graph identifier.
+ *
+ * @param queryStr - the SPARQL query to be executed against the RDF store
+ * @param resultFormat - type of serialization for RDF result of the SPARQL query
+ */
+ @Override
+ public InputStream sparqlConstructQuery(String queryStr, RDFServiceImpl.ModelSerializationFormat resultFormat)
+ throws RDFServiceException {
+
+ Model model = ModelFactory.createDefaultModel();
+ Query query = createQuery(queryStr);
+ QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
+
+ try {
+ qe.execConstruct(model);
+ } catch (Exception e) {
+ log.error("Error executing CONSTRUCT against remote endpoint: " + queryStr);
+ } finally {
+ qe.close();
+ }
+
+ ByteArrayOutputStream serializedModel = new ByteArrayOutputStream();
+ model.write(serializedModel, getSerializationFormatString(resultFormat));
+ InputStream result = new ByteArrayInputStream(serializedModel.toByteArray());
+ return result;
+ }
+
+ public void sparqlConstructQuery(String queryStr, Model model) throws RDFServiceException {
+
+ Query query = createQuery(queryStr);
+ QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
+
+ try {
+ qe.execConstruct(model);
+ } catch (Exception e) {
+ log.error("Error executing CONSTRUCT against remote endpoint: " + queryStr);
+ } finally {
+ qe.close();
+ }
+ }
+
+ /**
+ * Performs a SPARQL describe query against the knowledge base. The query may have an embedded graph identifier.
+ *
+ * @param queryStr - the SPARQL query to be executed against the RDF store
+ * @param resultFormat - type of serialization for RDF result of the SPARQL query
+ *
+ * @return InputStream - the result of the query
+ *
+ */
+ @Override
+ public InputStream sparqlDescribeQuery(String queryStr, RDFServiceImpl.ModelSerializationFormat resultFormat)
+ throws RDFServiceException {
+
+ Model model = ModelFactory.createDefaultModel();
+ Query query = createQuery(queryStr);
+ QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
+
+ try {
+ qe.execDescribe(model);
+ } finally {
+ qe.close();
+ }
+
+ ByteArrayOutputStream serializedModel = new ByteArrayOutputStream();
+ model.write(serializedModel, getSerializationFormatString(resultFormat));
+ InputStream result = new ByteArrayInputStream(serializedModel.toByteArray());
+ return result;
+ }
+
+ /**
+ * Performs a SPARQL select query against the knowledge base. The query may have an embedded graph identifier.
+ *
+ * @param queryStr - the SPARQL query to be executed against the RDF store
+ * @param resultFormat - format for the result of the Select query
+ *
+ * @return InputStream - the result of the query
+ *
+ */
+ @Override
+ public InputStream sparqlSelectQuery(String queryStr, RDFService.ResultFormat resultFormat)
+ throws RDFServiceException {
+
+ // QueryEngineHTTP qh = new QueryEngineHTTP(readEndpointURI, queryStr);
+
+ try {
+ HttpGet meth = new HttpGet(new URIBuilder(readEndpointURI).addParameter("query", queryStr).build());
+ meth.addHeader("Accept", "application/sparql-results+xml");
+ HttpContext context = getContext(meth);
+ HttpResponse response = context != null ? httpClient.execute(meth, context) : httpClient.execute(meth);
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode > 399) {
+ log.error("response " + statusCode + " to query. \n");
+ log.debug("update string: \n" + queryStr);
+ throw new RDFServiceException("Unable to perform SPARQL SELECT");
+ }
+
+ try (InputStream in = response.getEntity().getContent()) {
+ ResultSet resultSet = ResultSetFactory.fromXML(in);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ switch (resultFormat) {
+ case CSV:
+ ResultSetFormatter.outputAsCSV(outputStream, resultSet);
+ break;
+ case TEXT:
+ ResultSetFormatter.out(outputStream, resultSet);
+ break;
+ case JSON:
+ ResultSetFormatter.outputAsJSON(outputStream, resultSet);
+ break;
+ case XML:
+ ResultSetFormatter.outputAsXML(outputStream, resultSet);
+ break;
+ default:
+ throw new RDFServiceException("unrecognized result format");
+ }
+ InputStream result = new ByteArrayInputStream(outputStream.toByteArray());
+ return result;
+ }
+ } finally {
+ EntityUtils.consume(response.getEntity());
+ }
+ } catch (IOException | URISyntaxException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ public void sparqlSelectQuery(String queryStr, ResultSetConsumer consumer) throws RDFServiceException {
+
+ // QueryEngineHTTP qh = new QueryEngineHTTP(readEndpointURI, queryStr);
+
+ try {
+ HttpGet meth = new HttpGet(new URIBuilder(readEndpointURI).addParameter("query", queryStr).build());
+ meth.addHeader("Accept", "application/sparql-results+xml");
+ HttpContext context = getContext(meth);
+ HttpResponse response = context != null ? httpClient.execute(meth, context) : httpClient.execute(meth);
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode > 399) {
+ log.error("response " + statusCode + " to query. \n");
+ log.debug("update string: \n" + queryStr);
+ throw new RDFServiceException("Unable to perform SPARQL UPDATE");
+ }
+
+ try (InputStream in = response.getEntity().getContent()) {
+ consumer.processResultSet(ResultSetFactory.fromXML(in));
+ }
+ } finally {
+ EntityUtils.consume(response.getEntity());
+ }
+ } catch (IOException | URISyntaxException ioe) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ /**
+ * Performs a SPARQL ASK query against the knowledge base. The query may have an embedded graph identifier.
+ *
+ * @param queryStr - the SPARQL query to be executed against the RDF store
+ *
+ * @return boolean - the result of the SPARQL query
+ */
+ @Override
+ public boolean sparqlAskQuery(String queryStr) throws RDFServiceException {
+
+ Query query = createQuery(queryStr);
+ QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
+
+ try {
+ return qe.execAsk();
+ } finally {
+ qe.close();
+ }
+ }
+
+ protected void rebuildGraphUris() {
+ Thread thread = new VitroBackgroundThread(new Runnable() {
+ public void run() {
+ synchronized (RDFServiceJena.class) {
+ if (rebuildGraphURICache) {
+ try {
+ isRebuildGraphURICacheRunning = true;
+ rebuildGraphURICache = false;
+ Set newURIs = new HashSet<>();
+ try {
+ String fastJenaQuery = "SELECT DISTINCT ?g WHERE { GRAPH ?g {} } ORDER BY ?g";
+ newURIs.addAll(getGraphURIsFromSparqlQuery(fastJenaQuery));
+ } catch (Exception e) {
+ log.debug("Unable to use non-standard ARQ query for graph list", e);
+ }
+ if (graphURIs.isEmpty()) {
+ String standardQuery = "SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }";
+ newURIs.addAll(getGraphURIsFromSparqlQuery(standardQuery));
+ }
+ updateGraphURIs(newURIs);
+ } catch (Exception e) {
+ log.error(e, e);
+ } finally {
+ isRebuildGraphURICacheRunning = false;
+ }
+ }
+ }
+ }
+ }, "Rebuild graphURI cache thread");
+ thread.start();
+ }
+
+ private List getGraphURIsFromSparqlQuery(String queryString) throws RDFServiceException {
+ final List graphURIs = new ArrayList();
+ try {
+ sparqlSelectQuery(queryString, new ResultSetConsumer() {
+ @Override
+ protected void processQuerySolution(QuerySolution qs) {
+ if (qs != null) { // no idea how this happens, but it seems to
+ RDFNode n = qs.getResource("g");
+ if (n != null && n.isResource()) {
+ graphURIs.add(((Resource) n).getURI());
+ }
+ }
+ }
+ });
+ } catch (Exception e) {
+ throw new RDFServiceException("Unable to list graph URIs", e);
+ }
+ return graphURIs;
+ }
+
+ /**
+ */
+ @Override
+ public void getGraphMetadata() throws RDFServiceException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the URI of the default write graph
+ *
+ * @return String URI of default write graph
+ */
+ @Override
+ public String getDefaultWriteGraphURI() throws RDFServiceException {
+ return defaultWriteGraphURI;
+ }
+
+ /**
+ * Register a listener to listen to changes in any graph in the RDF store.
+ *
+ */
+ @Override
+ public synchronized void registerListener(ChangeListener changeListener) throws RDFServiceException {
+
+ if (!registeredListeners.contains(changeListener)) {
+ registeredListeners.add(changeListener);
+ }
+ }
+
+ /**
+ * Unregister a listener from listening to changes in any graph in the RDF store.
+ *
+ */
+ @Override
+ public synchronized void unregisterListener(ChangeListener changeListener) throws RDFServiceException {
+ registeredListeners.remove(changeListener);
+ }
+
+ /**
+ * Create a ChangeSet object
+ *
+ * @return a ChangeSet object
+ */
+ @Override
+ public ChangeSet manufactureChangeSet() {
+ return new ChangeSetImpl();
+ }
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // Non-override methods below
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ protected String getReadEndpointURI() {
+ return readEndpointURI;
+ }
+
+ protected String getUpdateEndpointURI() {
+ return updateEndpointURI;
+ }
+
+ protected void executeUpdate(String updateString) throws RDFServiceException {
+ try {
+ HttpPost meth = new HttpPost(updateEndpointURI);
+ meth.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=" + Consts.UTF_8);
+ meth.setEntity(new UrlEncodedFormEntity(Arrays.asList(new BasicNameValuePair("update", updateString)),
+ Consts.UTF_8));
+ HttpContext context = getContext(meth);
+ HttpResponse response = context != null ? httpClient.execute(meth, context) : httpClient.execute(meth);
+ try {
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode > 399) {
+ log.error("response " + response.getStatusLine() + " to update. \n");
+ // log.debug("update string: \n" + updateString);
+ throw new RDFServiceException("Unable to perform SPARQL UPDATE");
+ }
+ } finally {
+ EntityUtils.consume(response.getEntity());
+ }
+ } catch (Exception e) {
+ log.debug("update string: \n" + updateString);
+ throw new RDFServiceException("Unable to perform change set update", e);
+ }
+ }
+
+ private void addModel(Model model, String graphURI) throws RDFServiceException {
+ try {
+ long start = System.currentTimeMillis();
+ verbModel(model, graphURI, "INSERT");
+ log.info((System.currentTimeMillis() - start) + " ms to insert " + model.size() + " triples");
+ } finally {
+ rebuildGraphURICache = true;
+ }
+ }
+
+ private void deleteModel(Model model, String graphURI) throws RDFServiceException {
+ try {
+ verbModel(model, graphURI, "DELETE");
+ } finally {
+ rebuildGraphURICache = true;
+ }
+ }
+
+ private void verbModel(Model model, String graphURI, String verb) throws RDFServiceException {
+ Model m = ModelFactory.createDefaultModel();
+ StmtIterator stmtIt = model.listStatements();
+ int count = 0;
+ try {
+ while (stmtIt.hasNext()) {
+ count++;
+ m.add(stmtIt.nextStatement());
+ if (count % CHUNK_SIZE == 0 || !stmtIt.hasNext()) {
+ StringWriter sw = new StringWriter();
+ m.write(sw, "N-TRIPLE");
+ StringBuilder updateStringBuff = new StringBuilder();
+ updateStringBuff.append(verb).append(" DATA { ")
+ .append((graphURI != null) ? "GRAPH <" + graphURI + "> { " : "");
+ updateStringBuff.append(sw);
+ updateStringBuff.append((graphURI != null) ? " } " : "").append(" }");
+
+ String updateString = updateStringBuff.toString();
+
+ executeUpdate(updateString);
+
+ m.removeAll();
+ }
+ }
+ } finally {
+ stmtIt.close();
+ }
+ }
+
+// protected void addTriple(Triple t, String graphURI) throws RDFServiceException {
+// try {
+// StringBuffer updateString = new StringBuffer();
+// updateString.append("INSERT DATA { ");
+// updateString.append((graphURI != null) ? "GRAPH <" + graphURI + "> { " : "");
+// updateString.append(sparqlNodeUpdate(t.getSubject(), ""));
+// updateString.append(" ");
+// updateString.append(sparqlNodeUpdate(t.getPredicate(), ""));
+// updateString.append(" ");
+// updateString.append(sparqlNodeUpdate(t.getObject(), ""));
+// updateString.append(" }");
+// updateString.append((graphURI != null) ? " } " : "");
//
-// executeUpdate(updateString.toString());
-// notifyListeners(t, ModelChange.Operation.ADD, graphURI);
-// } finally {
-// rebuildGraphURICache = true;
-// }
-// }
+// executeUpdate(updateString.toString());
+// notifyListeners(t, ModelChange.Operation.ADD, graphURI);
+// } finally {
+// rebuildGraphURICache = true;
+// }
+// }
//
-// protected void removeTriple(Triple t, String graphURI) throws RDFServiceException {
-// try {
-// StringBuffer updateString = new StringBuffer();
-// updateString.append("DELETE DATA { ");
-// updateString.append((graphURI != null) ? "GRAPH <" + graphURI + "> { " : "");
-// updateString.append(sparqlNodeUpdate(t.getSubject(), ""));
-// updateString.append(" ");
-// updateString.append(sparqlNodeUpdate(t.getPredicate(), ""));
-// updateString.append(" ");
-// updateString.append(sparqlNodeUpdate(t.getObject(), ""));
-// updateString.append(" }");
-// updateString.append((graphURI != null) ? " } " : "");
+// protected void removeTriple(Triple t, String graphURI) throws RDFServiceException {
+// try {
+// StringBuffer updateString = new StringBuffer();
+// updateString.append("DELETE DATA { ");
+// updateString.append((graphURI != null) ? "GRAPH <" + graphURI + "> { " : "");
+// updateString.append(sparqlNodeUpdate(t.getSubject(), ""));
+// updateString.append(" ");
+// updateString.append(sparqlNodeUpdate(t.getPredicate(), ""));
+// updateString.append(" ");
+// updateString.append(sparqlNodeUpdate(t.getObject(), ""));
+// updateString.append(" }");
+// updateString.append((graphURI != null) ? " } " : "");
//
-// executeUpdate(updateString.toString());
-// notifyListeners(t, ModelChange.Operation.REMOVE, graphURI);
-// } finally {
-// rebuildGraphURICache = true;
-// }
-// }
-
- @Override
- protected boolean isPreconditionSatisfied(String query,
- RDFService.SPARQLQueryType queryType)
- throws RDFServiceException {
- Model model = ModelFactory.createDefaultModel();
-
- switch (queryType) {
- case DESCRIBE:
- model.read(sparqlDescribeQuery(query,RDFService.ModelSerializationFormat.N3), null);
- return !model.isEmpty();
- case CONSTRUCT:
- model.read(sparqlConstructQuery(query,RDFService.ModelSerializationFormat.N3), null);
- return !model.isEmpty();
- case SELECT:
- return sparqlSelectQueryHasResults(query);
- case ASK:
- return sparqlAskQuery(query);
- default:
- throw new RDFServiceException("unrecognized SPARQL query type");
- }
- }
-
- @Override
- protected boolean sparqlSelectQueryHasResults(String queryStr) throws RDFServiceException {
-
- Query query = createQuery(queryStr);
- QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
-
- try {
- ResultSet resultSet = qe.execSelect();
- return resultSet.hasNext();
- } finally {
- qe.close();
- }
- }
-
- private void performChange(ModelChange modelChange) throws RDFServiceException {
- Model model = parseModel(modelChange);
- Model[] separatedModel = separateStatementsWithBlankNodes(model);
- if (modelChange.getOperation() == ModelChange.Operation.ADD) {
- addModel(separatedModel[1], modelChange.getGraphURI());
- addBlankNodesWithSparqlUpdate(separatedModel[0], modelChange.getGraphURI());
- } else if (modelChange.getOperation() == ModelChange.Operation.REMOVE) {
- deleteModel(separatedModel[1], modelChange.getGraphURI());
- removeBlankNodesWithSparqlUpdate(separatedModel[0], modelChange.getGraphURI());
- } else {
- log.error("unrecognized operation type");
- }
- }
-
- private void addBlankNodesWithSparqlUpdate(Model model, String graphURI)
- throws RDFServiceException {
- updateBlankNodesWithSparqlUpdate(model, graphURI, ADD);
- }
-
- private void removeBlankNodesWithSparqlUpdate(Model model, String graphURI)
- throws RDFServiceException {
- updateBlankNodesWithSparqlUpdate(model, graphURI, REMOVE);
- }
-
- private static final boolean ADD = true;
- private static final boolean REMOVE = false;
-
- private void updateBlankNodesWithSparqlUpdate(Model model, String graphURI, boolean add)
- throws RDFServiceException {
- List blankNodeStatements = new ArrayList();
- StmtIterator stmtIt = model.listStatements();
- while (stmtIt.hasNext()) {
- Statement stmt = stmtIt.nextStatement();
- if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
- blankNodeStatements.add(stmt);
- }
- }
-
- if(blankNodeStatements.size() == 0) {
- return;
- }
-
- Model blankNodeModel = ModelFactory.createDefaultModel();
- blankNodeModel.add(blankNodeStatements);
-
- log.debug("update model size " + model.size());
- log.debug("blank node model size " + blankNodeModel.size());
-
- if (!add && blankNodeModel.size() == 1) {
- log.warn("Deleting single triple with blank node: " + blankNodeModel);
- log.warn("This likely indicates a problem; excessive data may be deleted.");
- }
-
- Query rootFinderQuery = QueryFactory.create(JenaModelUtils.BNODE_ROOT_QUERY);
- QueryExecution qe = QueryExecutionFactory.create(rootFinderQuery, blankNodeModel);
- try {
- ResultSet rs = qe.execSelect();
- while (rs.hasNext()) {
- QuerySolution qs = rs.next();
- org.apache.jena.rdf.model.Resource s = qs.getResource("s");
- String treeFinder = makeDescribe(s);
- Query treeFinderQuery = QueryFactory.create(treeFinder);
- QueryExecution qee = QueryExecutionFactory.create(treeFinderQuery, blankNodeModel);
- try {
- Model tree = qee.execDescribe();
- if (s.isAnon()) {
- if (add) {
- addModel(tree, graphURI);
- } else {
- removeUsingSparqlUpdate(tree, graphURI);
- }
- } else {
- StmtIterator sit = tree.listStatements(s, null, (RDFNode) null);
- while (sit.hasNext()) {
- Statement stmt = sit.nextStatement();
- RDFNode n = stmt.getObject();
- Model m2 = ModelFactory.createDefaultModel();
- if (n.isResource()) {
- org.apache.jena.rdf.model.Resource s2 =
- (org.apache.jena.rdf.model.Resource) n;
- // now run yet another describe query
- String smallerTree = makeDescribe(s2);
- Query smallerTreeQuery = QueryFactory.create(smallerTree);
- QueryExecution qe3 = QueryExecutionFactory.create(
- smallerTreeQuery, tree);
- try {
- qe3.execDescribe(m2);
- } finally {
- qe3.close();
- }
- }
- m2.add(stmt);
- if (add) {
- addModel(m2, graphURI);
- } else {
- removeUsingSparqlUpdate(m2, graphURI);
- }
- }
- }
- } finally {
- qee.close();
- }
- }
- } finally {
- qe.close();
- }
- }
-
- private void removeUsingSparqlUpdate(Model model, String graphURI)
- throws RDFServiceException {
-
- StmtIterator stmtIt = model.listStatements();
-
- if (!stmtIt.hasNext()) {
- stmtIt.close();
- return;
- }
-
- StringBuffer queryBuff = new StringBuffer();
- if (graphURI != null) {
- queryBuff.append("WITH <").append(graphURI).append("> \n");
- }
- queryBuff.append("DELETE { \n");
- List stmts = stmtIt.toList();
- sort(stmts);
- addStatementPatterns(stmts, queryBuff, !WHERE_CLAUSE);
- queryBuff.append("} WHERE { \n");
- stmtIt = model.listStatements();
- stmts = stmtIt.toList();
- sort(stmts);
- addStatementPatterns(stmts, queryBuff, WHERE_CLAUSE);
- queryBuff.append("} \n");
-
- if(log.isDebugEnabled()) {
- log.debug(queryBuff.toString());
- }
- executeUpdate(queryBuff.toString());
- }
-
- private List sort(List stmts) {
- List output = new ArrayList();
- int originalSize = stmts.size();
- if (originalSize == 1)
- return stmts;
- List remaining = stmts;
- ConcurrentLinkedQueue subjQueue =
- new ConcurrentLinkedQueue();
- for(Statement stmt : remaining) {
- if(stmt.getSubject().isURIResource()) {
- subjQueue.add(stmt.getSubject());
- break;
- }
- }
- if (subjQueue.isEmpty()) {
- throw new RuntimeException("No named subject in statement patterns");
- }
- while(remaining.size() > 0) {
- if(subjQueue.isEmpty()) {
- subjQueue.add(remaining.get(0).getSubject());
- }
- while(!subjQueue.isEmpty()) {
- org.apache.jena.rdf.model.Resource subj = subjQueue.poll();
- List temp = new ArrayList();
- for (Statement stmt : remaining) {
- if(stmt.getSubject().equals(subj)) {
- output.add(stmt);
- if (stmt.getObject().isResource()) {
- subjQueue.add((org.apache.jena.rdf.model.Resource) stmt.getObject());
- }
- } else {
- temp.add(stmt);
- }
- }
- remaining = temp;
- }
- }
- if(output.size() != originalSize) {
- throw new RuntimeException("original list size was " + originalSize +
- " but sorted size is " + output.size());
- }
- return output;
- }
-
- private static final boolean WHERE_CLAUSE = true;
-
- private void addStatementPatterns(List stmts, StringBuffer patternBuff, boolean whereClause) {
- for(Statement stmt : stmts) {
- Triple t = stmt.asTriple();
- patternBuff.append(SparqlGraph.sparqlNodeDelete(t.getSubject(), null));
- patternBuff.append(" ");
- patternBuff.append(SparqlGraph.sparqlNodeDelete(t.getPredicate(), null));
- patternBuff.append(" ");
- patternBuff.append(SparqlGraph.sparqlNodeDelete(t.getObject(), null));
- patternBuff.append(" .\n");
- if (whereClause) {
- if (t.getSubject().isBlank()) {
- patternBuff.append(" FILTER(isBlank(").append(SparqlGraph.sparqlNodeDelete(t.getSubject(), null)).append(")) \n");
- }
- if (t.getObject().isBlank()) {
- patternBuff.append(" FILTER(isBlank(").append(SparqlGraph.sparqlNodeDelete(t.getObject(), null)).append(")) \n");
- }
- }
- }
- }
-
- private String makeDescribe(org.apache.jena.rdf.model.Resource s) {
- StringBuilder query = new StringBuilder("DESCRIBE <") ;
- if (s.isAnon()) {
- query.append("_:").append(s.getId().toString());
- } else {
- query.append(s.getURI());
- }
- query.append(">");
- return query.toString();
- }
-
- private Model parseModel(ModelChange modelChange) {
- Model model = ModelFactory.createDefaultModel();
- model.read(modelChange.getSerializedModel(), null,
- getSerializationFormatString(modelChange.getSerializationFormat()));
- return model;
- }
-
- @Override
- public void serializeAll(OutputStream outputStream)
- throws RDFServiceException {
- String query = "SELECT * WHERE { GRAPH ?g {?s ?p ?o}}";
- serialize(outputStream, query);
- }
-
- @Override
- public void serializeGraph(String graphURI, OutputStream outputStream)
- throws RDFServiceException {
- String query = "SELECT * WHERE { GRAPH <" + graphURI + "> {?s ?p ?o}}";
- serialize(outputStream, query);
- }
-
- private void serialize(OutputStream outputStream, String query) throws RDFServiceException {
- InputStream resultStream = sparqlSelectQuery(query, RDFService.ResultFormat.JSON);
- ResultSet resultSet = ResultSetFactory.fromJSON(resultStream);
- if (resultSet.getResultVars().contains("g")) {
- Iterator quads = new ResultSetQuadsIterator(resultSet);
- RDFDataMgr.writeQuads(outputStream, quads);
- } else {
- Iterator triples = new ResultSetTriplesIterator(resultSet);
- RDFDataMgr.writeTriples(outputStream, triples);
- }
- }
-
- /**
- * The basic version. Parse the model from the file, read the model from the
- * tripleStore, and ask whether they are isomorphic.
- */
- @Override
- public boolean isEquivalentGraph(String graphURI, InputStream serializedGraph,
- ModelSerializationFormat serializationFormat) throws RDFServiceException {
- Model fileModel = RDFServiceUtils.parseModel(serializedGraph, serializationFormat);
- Model tripleStoreModel = new RDFServiceDataset(this).getNamedModel(graphURI);
- Model fromTripleStoreModel = ModelFactory.createDefaultModel().add(tripleStoreModel);
- return fileModel.isIsomorphicWith(fromTripleStoreModel);
- }
-
- /**
- * The basic version. Parse the model from the file, read the model from the
- * tripleStore, and ask whether they are isomorphic.
- */
- @Override
- public boolean isEquivalentGraph(String graphURI, Model graph) throws RDFServiceException {
- Model tripleStoreModel = new RDFServiceDataset(this).getNamedModel(graphURI);
- Model fromTripleStoreModel = ModelFactory.createDefaultModel().add(tripleStoreModel);
- return graph.isIsomorphicWith(fromTripleStoreModel);
- }
-
- @Override
- public boolean preferPreciseOptionals() {
- return false;
- }
-
- protected HttpContext getContext(HttpRequestBase request) {
- UsernamePasswordCredentials credentials = getCredentials();
- if (credentials != null) {
- try {
- request.addHeader(new BasicScheme().authenticate(credentials, request, null));
-
- CredentialsProvider provider = new BasicCredentialsProvider();
- provider.setCredentials(AuthScope.ANY, getCredentials());
-
- BasicHttpContext context = new BasicHttpContext();
- context.setAttribute(ClientContext.CREDS_PROVIDER, provider);
- return context;
- } catch (AuthenticationException e) {
- log.error("Unable to set credentials");
- }
- }
-
- return new BasicHttpContext();
- }
-
- protected UsernamePasswordCredentials getCredentials() {
- return null;
- }
+// executeUpdate(updateString.toString());
+// notifyListeners(t, ModelChange.Operation.REMOVE, graphURI);
+// } finally {
+// rebuildGraphURICache = true;
+// }
+// }
+
+ @Override
+ protected boolean isPreconditionSatisfied(String query, RDFService.SPARQLQueryType queryType)
+ throws RDFServiceException {
+ Model model = ModelFactory.createDefaultModel();
+
+ switch (queryType) {
+ case DESCRIBE:
+ model.read(sparqlDescribeQuery(query, RDFService.ModelSerializationFormat.N3), null);
+ return !model.isEmpty();
+ case CONSTRUCT:
+ model.read(sparqlConstructQuery(query, RDFService.ModelSerializationFormat.N3), null);
+ return !model.isEmpty();
+ case SELECT:
+ return sparqlSelectQueryHasResults(query);
+ case ASK:
+ return sparqlAskQuery(query);
+ default:
+ throw new RDFServiceException("unrecognized SPARQL query type");
+ }
+ }
+
+ @Override
+ protected boolean sparqlSelectQueryHasResults(String queryStr) throws RDFServiceException {
+
+ Query query = createQuery(queryStr);
+ QueryExecution qe = QueryExecutionFactory.sparqlService(readEndpointURI, query);
+
+ try {
+ ResultSet resultSet = qe.execSelect();
+ return resultSet.hasNext();
+ } finally {
+ qe.close();
+ }
+ }
+
+ private void performChange(ModelChange modelChange) throws RDFServiceException {
+ Model model = parseModel(modelChange);
+ Model[] separatedModel = separateStatementsWithBlankNodes(model);
+ if (modelChange.getOperation() == ModelChange.Operation.ADD) {
+ addModel(separatedModel[1], modelChange.getGraphURI());
+ addBlankNodesWithSparqlUpdate(separatedModel[0], modelChange.getGraphURI());
+ } else if (modelChange.getOperation() == ModelChange.Operation.REMOVE) {
+ deleteModel(separatedModel[1], modelChange.getGraphURI());
+ removeBlankNodesWithSparqlUpdate(separatedModel[0], modelChange.getGraphURI());
+ } else {
+ log.error("unrecognized operation type");
+ }
+ }
+
+ private void addBlankNodesWithSparqlUpdate(Model model, String graphURI) throws RDFServiceException {
+ updateBlankNodesWithSparqlUpdate(model, graphURI, ADD);
+ }
+
+ private void removeBlankNodesWithSparqlUpdate(Model model, String graphURI) throws RDFServiceException {
+ updateBlankNodesWithSparqlUpdate(model, graphURI, REMOVE);
+ }
+
+ private static final boolean ADD = true;
+ private static final boolean REMOVE = false;
+
+ private void updateBlankNodesWithSparqlUpdate(Model model, String graphURI, boolean add)
+ throws RDFServiceException {
+ List blankNodeStatements = new ArrayList();
+ StmtIterator stmtIt = model.listStatements();
+ while (stmtIt.hasNext()) {
+ Statement stmt = stmtIt.nextStatement();
+ if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
+ blankNodeStatements.add(stmt);
+ }
+ }
+
+ if (blankNodeStatements.size() == 0) {
+ return;
+ }
+
+ Model blankNodeModel = ModelFactory.createDefaultModel();
+ blankNodeModel.add(blankNodeStatements);
+
+ log.debug("update model size " + model.size());
+ log.debug("blank node model size " + blankNodeModel.size());
+
+ if (!add && blankNodeModel.size() == 1) {
+ log.warn("Deleting single triple with blank node: " + blankNodeModel);
+ log.warn("This likely indicates a problem; excessive data may be deleted.");
+ }
+
+ Query rootFinderQuery = QueryFactory.create(JenaModelUtils.BNODE_ROOT_QUERY);
+ QueryExecution qe = QueryExecutionFactory.create(rootFinderQuery, blankNodeModel);
+ try {
+ ResultSet rs = qe.execSelect();
+ while (rs.hasNext()) {
+ QuerySolution qs = rs.next();
+ org.apache.jena.rdf.model.Resource s = qs.getResource("s");
+ String treeFinder = makeDescribe(s);
+ Query treeFinderQuery = QueryFactory.create(treeFinder);
+ QueryExecution qee = QueryExecutionFactory.create(treeFinderQuery, blankNodeModel);
+ try {
+ Model tree = qee.execDescribe();
+ if (s.isAnon()) {
+ if (add) {
+ addModel(tree, graphURI);
+ } else {
+ removeUsingSparqlUpdate(tree, graphURI);
+ }
+ } else {
+ StmtIterator sit = tree.listStatements(s, null, (RDFNode) null);
+ while (sit.hasNext()) {
+ Statement stmt = sit.nextStatement();
+ RDFNode n = stmt.getObject();
+ Model m2 = ModelFactory.createDefaultModel();
+ if (n.isResource()) {
+ org.apache.jena.rdf.model.Resource s2 = (org.apache.jena.rdf.model.Resource) n;
+ // now run yet another describe query
+ String smallerTree = makeDescribe(s2);
+ Query smallerTreeQuery = QueryFactory.create(smallerTree);
+ QueryExecution qe3 = QueryExecutionFactory.create(smallerTreeQuery, tree);
+ try {
+ qe3.execDescribe(m2);
+ } finally {
+ qe3.close();
+ }
+ }
+ m2.add(stmt);
+ if (add) {
+ addModel(m2, graphURI);
+ } else {
+ removeUsingSparqlUpdate(m2, graphURI);
+ }
+ }
+ }
+ } finally {
+ qee.close();
+ }
+ }
+ } finally {
+ qe.close();
+ }
+ }
+
+ private void removeUsingSparqlUpdate(Model model, String graphURI) throws RDFServiceException {
+
+ StmtIterator stmtIt = model.listStatements();
+
+ if (!stmtIt.hasNext()) {
+ stmtIt.close();
+ return;
+ }
+
+ StringBuffer queryBuff = new StringBuffer();
+ if (graphURI != null) {
+ queryBuff.append("WITH <").append(graphURI).append("> \n");
+ }
+ queryBuff.append("DELETE { \n");
+ List stmts = stmtIt.toList();
+ sort(stmts);
+ addStatementPatterns(stmts, queryBuff, !WHERE_CLAUSE);
+ queryBuff.append("} WHERE { \n");
+ stmtIt = model.listStatements();
+ stmts = stmtIt.toList();
+ sort(stmts);
+ addStatementPatterns(stmts, queryBuff, WHERE_CLAUSE);
+ queryBuff.append("} \n");
+
+ if (log.isDebugEnabled()) {
+ log.debug(queryBuff.toString());
+ }
+ executeUpdate(queryBuff.toString());
+ }
+
+ private List sort(List stmts) {
+ List output = new ArrayList();
+ int originalSize = stmts.size();
+ if (originalSize == 1) {
+ return stmts;
+ }
+ List remaining = stmts;
+ ConcurrentLinkedQueue subjQueue =
+ new ConcurrentLinkedQueue