All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dspace.content.crosswalk.METSRightsCrosswalk Maven / Gradle / Ivy

There is a newer version: 8.0
Show newest version
/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.content.crosswalk;

import java.io.IOException;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.ResourcePolicy;
import org.dspace.authorize.factory.AuthorizeServiceFactory;
import org.dspace.authorize.service.AuthorizeService;
import org.dspace.authorize.service.ResourcePolicyService;
import org.dspace.content.DSpaceObject;
import org.dspace.content.packager.PackageException;
import org.dspace.content.packager.PackageUtils;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.eperson.factory.EPersonServiceFactory;
import org.dspace.eperson.service.EPersonService;
import org.dspace.eperson.service.GroupService;
import org.jdom.Element;
import org.jdom.Namespace;

/**
 * METSRights Ingestion and Dissemination Crosswalk
 * 

* Translate between DSpace internal policies (i.e. permissions) and the * METSRights metadata schema * (see * http://www.loc.gov/standards/rights/METSRights.xsd for details). *

* Examples of METSRights usage available from: * * http://www.loc.gov/standards/rights/ *

* This Crosswalk provides a way to export DSpace permissions into a standard * format, and then re-import or restore them into a DSpace instance. * * @author Tim Donohue * @version $Revision: 2108 $ */ public class METSRightsCrosswalk implements IngestionCrosswalk, DisseminationCrosswalk { /** * log4j category */ private static Logger log = org.apache.logging.log4j.LogManager.getLogger(METSRightsCrosswalk.class); private static final Namespace METSRights_NS = Namespace.getNamespace("rights", "http://cosimo.stanford.edu/sdr/metsrights/"); // XML schemaLocation fragment for this crosswalk, from config. private String schemaLocation = METSRights_NS.getURI() + " http://cosimo.stanford.edu/sdr/metsrights.xsd"; private static final Namespace namespaces[] = {METSRights_NS}; private static final Map otherTypesMapping = new HashMap(); static { //Mapping of DSpace Policy Actions to METSRights PermissionType values // (These are the values stored in the @OTHERPERMITTYPE attribute in METSRights) // NOTE: READ, WRITE, DELETE are not included here as they map directly to existing METSRights PermissionTypes otherTypesMapping.put(Constants.ADD, "ADD CONTENTS"); otherTypesMapping.put(Constants.REMOVE, "REMOVE CONTENTS"); otherTypesMapping.put(Constants.ADMIN, "ADMIN"); otherTypesMapping.put(Constants.DEFAULT_BITSTREAM_READ, "READ FILE CONTENTS"); otherTypesMapping.put(Constants.DEFAULT_ITEM_READ, "READ ITEM CONTENTS"); } // Value of METSRights @CONTEXTCLASS attribute to use for DSpace Groups private static final String GROUP_CONTEXTCLASS = "MANAGED GRP"; // Value of METSRights @CONTEXTCLASS attribute to use for DSpace EPeople private static final String PERSON_CONTEXTCLASS = "ACADEMIC USER"; // Value of METSRights @CONTEXTCLASS attribute to use for "Anonymous" DSpace Group private static final String ANONYMOUS_CONTEXTCLASS = "GENERAL PUBLIC"; // Value of METSRights @CONTEXTCLASS attribute to use for "Administrator" DSpace Group private static final String ADMIN_CONTEXTCLASS = "REPOSITORY MGR"; // Value of METSRights @USERTYPE attribute to use for DSpace Groups private static final String GROUP_USERTYPE = "GROUP"; // Value of METSRights @USERTYPE attribute to use for DSpace Groups private static final String PERSON_USERTYPE = "INDIVIDUAL"; protected AuthorizeService authorizeService = AuthorizeServiceFactory.getInstance().getAuthorizeService(); protected EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); protected GroupService groupService = EPersonServiceFactory.getInstance().getGroupService(); protected ResourcePolicyService resourcePolicyService = AuthorizeServiceFactory.getInstance() .getResourcePolicyService(); /*----------- Dissemination functions -------------------*/ @Override public Namespace[] getNamespaces() { return (Namespace[]) ArrayUtils.clone(namespaces); } @Override public String getSchemaLocation() { return schemaLocation; } @Override public boolean canDisseminate(DSpaceObject dso) { //can disseminate all types of DSpace Objects, except for SITE return (dso.getType() != Constants.SITE); } /** * Actually Disseminate into METSRights schema. This method locates all DSpace * policies (permissions) for the provided object, and translates them into * METSRights PermissionTypes. * * @param context context * @param dso DSpace Object * @return XML Element corresponding to the new {@code } translation * @throws CrosswalkException if crosswalk error * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error */ @Override public Element disseminateElement(Context context, DSpaceObject dso) throws CrosswalkException, IOException, SQLException, AuthorizeException { if (dso == null) { return null; } else if (dso.getType() == Constants.SITE) { // we don't have a way to provide METSRights for a SITE object throw new CrosswalkObjectNotSupported("The METSRightsCrosswalk cannot crosswalk a SITE object"); } //Root element: RightsDeclarationMD // All DSpace content is just under LICENSE -- no other rights can be claimed Element rightsMD = new Element("RightsDeclarationMD", METSRights_NS); rightsMD.setAttribute("RIGHTSCATEGORY", "LICENSED"); //Three sections to METSRights: // * RightsDeclaration - general rights statement // * RightsHolder - info about who owns rights // * Context - info about specific permissions granted // We're just crosswalking DSpace policies to "Context" permissions by default // It's too difficult to make statements about who owns the rights and // what those rights are -- too many types of content can be stored in DSpace //Get all policies on this DSpace Object List policies = authorizeService.getPolicies(context, dso); //For each DSpace policy for (ResourcePolicy policy : policies) { // DSpace Policies can either reference a Group or an Individual, but not both! Group group = policy.getGroup(); EPerson person = policy.getEPerson(); // Create our node for this policy Element rightsContext = new Element("Context", METSRights_NS); String rpName = policy.getRpName(); if (rpName != null) { rightsContext.setAttribute("rpName", rpName); } // As of DSpace 3.0, policies may have an effective date range, check if a policy is effective rightsContext.setAttribute("in-effect", "true"); Date now = new Date(); SimpleDateFormat iso8601 = new SimpleDateFormat("yyyy-MM-dd"); if (policy.getStartDate() != null) { rightsContext.setAttribute("start-date", iso8601.format(policy.getStartDate())); if (policy.getStartDate().after(now)) { rightsContext.setAttribute("in-effect", "false"); } } if (policy.getEndDate() != null) { rightsContext.setAttribute("end-date", iso8601.format(policy.getEndDate())); if (policy.getEndDate().before(now)) { rightsContext.setAttribute("in-effect", "false"); } } //First, handle Group-based policies // For Group policies we need to setup a // [group-name]... if (group != null) { //Default all DSpace groups to have "MANAGED GRP" as the type String contextClass = GROUP_CONTEXTCLASS; if (StringUtils.equals(group.getName(), Group.ANONYMOUS)) { //DSpace Anonymous Group = 'GENERAL PUBLIC' type contextClass = ANONYMOUS_CONTEXTCLASS; } else if (StringUtils.equals(group.getName(), Group.ADMIN)) { //DSpace Administrator Group = 'REPOSITORY MGR' type contextClass = ADMIN_CONTEXTCLASS; } rightsContext.setAttribute("CONTEXTCLASS", contextClass); //If this is a "MANAGED GRP", then create a child //to specify the group Name, and set @USERTYPE='GROUP' if (contextClass.equals(GROUP_CONTEXTCLASS)) { try { //Translate the Group name for export. This ensures that groups with Internal IDs in their // names // (e.g. COLLECTION_1_ADMIN) are properly translated using the corresponding Handle or // external identifier. String exportGroupName = PackageUtils.translateGroupNameForExport(context, group.getName()); //If translated group name is returned as "null", this means the Group name // had an Internal Collection/Community ID embedded, which could not be // translated properly to a Handle. We will NOT export these groups, // as they could cause conflicts or data integrity problems if they are // imported into another DSpace system. if (exportGroupName != null && !exportGroupName.isEmpty()) { //Create element. Add the Group's name to that element Element rightsUser = new Element("UserName", METSRights_NS); rightsUser.setAttribute("USERTYPE", GROUP_USERTYPE); rightsUser.addContent(exportGroupName); rightsContext.addContent(rightsUser); } else { //Skip over this Group, as we couldn't translate it for export. //The Group seems to refer to a Community or Collection which no longer exists continue; } } catch (PackageException pe) { //A PackageException will only be thrown if translateGroupNameForExport() fails //We'll just wrap it as a CrosswalkException and throw it upwards throw new CrosswalkException(pe); } } rightsMD.addContent(rightsContext); } else if (person != null) { //Next, handle User-based policies // For User policies we need to setup a // [group-name]... // All EPeople are considered 'Academic Users' rightsContext.setAttribute("CONTEXTCLASS", PERSON_CONTEXTCLASS); //Create a node corresponding to person's email, set @USERTYPE='INDIVIDUAL' Element rightsUser = new Element("UserName", METSRights_NS); rightsUser.setAttribute("USERTYPE", PERSON_USERTYPE); rightsUser.addContent(person.getEmail()); rightsContext.addContent(rightsUser); rightsMD.addContent(rightsContext); } else { log.error("Policy " + String.valueOf(policy.getID()) + " is neither user nor group! Omitted from package."); } //Translate the DSpace ResourcePolicy into a element Element rightsPerm = translatePermissions(policy); rightsContext.addContent(rightsPerm); } //end for each policy return rightsMD; } @Override public List disseminateList(Context context, DSpaceObject dso) throws CrosswalkException, IOException, SQLException, AuthorizeException { List result = new ArrayList(1); result.add(disseminateElement(context, dso)); return result; } @Override public boolean preferList() { return false; } /** * Translates a DSpace ResourcePolicy's permissions into a METSRights * Permissions element. Returns the created * Permissions element. This element may be empty if * there was an issue translating the ResourcePolicy. * * @param policy The DSpace ResourcePolicy * @return the Element representing the METSRIghts Permissions or null. */ private Element translatePermissions(ResourcePolicy policy) { //Create our node to store all permissions in this context Element rightsPerm = new Element("Permissions", METSRights_NS); //Determine the 'actions' permitted by this DSpace policy, and translate to METSRights PermissionTypes int action = policy.getAction(); //All READ-based actions = cannot modify or delete object if (action == Constants.READ || action == Constants.DEFAULT_BITSTREAM_READ || action == Constants.DEFAULT_ITEM_READ) { // For DSpace, READ = Discover and Display rightsPerm.setAttribute("DISCOVER", "true"); rightsPerm.setAttribute("DISPLAY", "true"); //Read = cannot modify or delete rightsPerm.setAttribute("MODIFY", "false"); rightsPerm.setAttribute("DELETE", "false"); } else if (action == Constants.WRITE || action == Constants.ADD) { //All WRITE-based actions = can modify, but cannot delete rightsPerm.setAttribute("DISCOVER", "true"); rightsPerm.setAttribute("DISPLAY", "true"); //Write = can modify, but cannot delete rightsPerm.setAttribute("MODIFY", "true"); rightsPerm.setAttribute("DELETE", "false"); } else if (action == Constants.DELETE || action == Constants.REMOVE) { //All DELETE-based actions = can modify & can delete //(NOTE: Although Constants.DELETE is marked as "obsolete", it is still used in dspace-api) rightsPerm.setAttribute("DISCOVER", "true"); rightsPerm.setAttribute("DISPLAY", "true"); //Delete = can both modify and delete rightsPerm.setAttribute("MODIFY", "true"); rightsPerm.setAttribute("DELETE", "true"); } else if (action == Constants.ADMIN) { //ADMIN action = full permissions rightsPerm.setAttribute("DISCOVER", "true"); rightsPerm.setAttribute("DISPLAY", "true"); rightsPerm.setAttribute("COPY", "true"); rightsPerm.setAttribute("DUPLICATE", "true"); rightsPerm.setAttribute("MODIFY", "true"); rightsPerm.setAttribute("DELETE", "true"); rightsPerm.setAttribute("PRINT", "true"); } else { //Unknown action -- don't enable any rights by default //NOTE: ALL WORKFLOW RELATED ACTIONS ARE NOT INCLUDED IN METSRIGHTS //DSpace API no longer assigns nor checks any of the following 'action' types: // * Constants.WORKFLOW_STEP_1 // * Constants.WORKFLOW_STEP_2 // * Constants.WORKFLOW_STEP_3 // * Constants.WORKFLOW_ABORT } //end if //Also add in OTHER permissionTypes, as necessary (see 'otherTypesMapping' above) // (These OTHER permissionTypes are used to tell apart similar DSpace permissions during Ingestion) if (otherTypesMapping.containsKey(action)) { //if found in our 'otherTypesMapping', enable @OTHER attribute and add in the appropriate value to // @OTHERPERMITTYPE attribute rightsPerm.setAttribute("OTHER", "true"); rightsPerm.setAttribute("OTHERPERMITTYPE", otherTypesMapping.get(action)); } return rightsPerm; } /*----------- Ingestion functions -------------------*/ /** * Ingest a whole XML document, starting at specified root. * * @param context The relevant DSpace Context. * @param dso DSpace object to ingest * @param root root element * @param createMissingMetadataFields whether to create missing fields * @throws CrosswalkException if crosswalk error * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error */ @Override public void ingest(Context context, DSpaceObject dso, Element root, boolean createMissingMetadataFields) throws CrosswalkException, IOException, SQLException, AuthorizeException { if (!(root.getName().equals("RightsDeclarationMD"))) { throw new MetadataValidationException("Wrong root element for METSRights: " + root.toString()); } ingest(context, dso, root.getChildren(), createMissingMetadataFields); } /** * Ingest a List of XML elements *

* This method creates new DSpace Policies based on the parsed * METSRights XML contents. These Policies assign permissions * to DSpace Groups or EPeople. *

* NOTE: This crosswalk will NOT create missing DSpace Groups or EPeople. * Therefore, it is recommended to use this METSRightsCrosswalk in * conjunction with another Crosswalk which can create/restore missing * Groups or EPeople (e.g. RoleCrosswalk). * * @param context context * @param dso Dspace object * @param ml list of elements * @param createMissingMetadataFields whether to create missing fields * @throws CrosswalkException if crosswalk error * @throws IOException if IO error * @throws SQLException if database error * @throws AuthorizeException if authorization error * @see RoleCrosswalk */ @Override public void ingest(Context context, DSpaceObject dso, List ml, boolean createMissingMetadataFields) throws CrosswalkException, IOException, SQLException, AuthorizeException { // SITE objects are not supported by the METSRightsCrosswalk if (dso.getType() == Constants.SITE) { throw new CrosswalkObjectNotSupported( "Wrong target object type, METSRightsCrosswalk cannot crosswalk a SITE object."); } // If we're fed the top-level wrapper element, recurse into its guts. // What we need to analyze are the elements underneath it. if (!ml.isEmpty() && ml.get(0).getName().equals("RightsDeclarationMD")) { ingest(context, dso, ml.get(0).getChildren(), createMissingMetadataFields); } else { // Loop through each Element in the passed in List, creating a ResourcePolicy for each List policies = new ArrayList<>(); for (Element element : ml) { // Must be a "Context" section (where permissions are stored) if (element.getName().equals("Context")) { //get what class of context this is String contextClass = element.getAttributeValue("CONTEXTCLASS"); ResourcePolicy rp = resourcePolicyService.create(context); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // get reference to the element // Note: we are assuming here that there will only ever be ONE // element. Currently there are no known use cases for multiple. Element permsElement = element.getChild("Permissions", METSRights_NS); if (permsElement == null) { log.error("No element was found. Skipping this element."); continue; } if (element.getAttributeValue("rpName") != null) { rp.setRpName(element.getAttributeValue("rpName")); } try { if (element.getAttributeValue("start-date") != null) { rp.setStartDate(sdf.parse(element.getAttributeValue("start-date"))); } if (element.getAttributeValue("end-date") != null) { rp.setEndDate(sdf.parse(element.getAttributeValue("end-date"))); } } catch (ParseException ex) { log.error("Failed to parse embargo date. The date needs to be in the format 'yyyy-MM-dd'.", ex); } //Check if this permission pertains to Anonymous users if (ANONYMOUS_CONTEXTCLASS.equals(contextClass)) { //get DSpace Anonymous group, ID=0 Group anonGroup = groupService.findByName(context, Group.ANONYMOUS); if (anonGroup == null) { throw new CrosswalkInternalException( "The DSpace database has not been properly initialized. The Anonymous Group is " + "missing from the database."); } rp.setGroup(anonGroup); } else if (ADMIN_CONTEXTCLASS.equals(contextClass)) { // else if this permission declaration pertains to Administrators // get DSpace Administrator group Group adminGroup = groupService.findByName(context, Group.ADMIN); if (adminGroup == null) { throw new CrosswalkInternalException( "The DSpace database has not been properly initialized. The Administrator Group is " + "missing from the database."); } rp.setGroup(adminGroup); } else if (GROUP_CONTEXTCLASS.equals(contextClass)) { // else if this permission pertains to another DSpace group try { //we need to find the name of DSpace group it pertains to //Get the text within the child element, // this is the group's name String groupName = element.getChildTextTrim("UserName", METSRights_NS); //Translate Group name back to internal ID format (e.g. COLLECTION__ADMIN) // from its external format (e.g. COLLECTION__ADMIN) groupName = PackageUtils.translateGroupNameForImport(context, groupName); //Check if this group exists in DSpace already Group group = groupService.findByName(context, groupName); //if not found, throw an error -- user should restore group from the SITE AIP if (group == null) { throw new CrosswalkInternalException("Cannot restore Group permissions on object (" + "type=" + Constants.typeText[dso .getType()] + ", " + "handle=" + dso.getHandle() + ", " + "ID=" + dso.getID() + "). The Group named '" + groupName + "' is" + " missing from DSpace. " + "Please restore this group using the SITE " + "AIP, or recreate it."); } //assign group to policy rp.setGroup(group); } catch (PackageException pe) { //A PackageException will only be thrown if translateDefaultGroupName() fails //We'll just wrap it as a CrosswalkException and throw it upwards throw new CrosswalkException(pe); } } else if (PERSON_CONTEXTCLASS.equals(contextClass)) { // else if this permission pertains to a DSpace person // we need to find the person it pertains to // Get the text within the child element, // this is the person's email address String personEmail = element.getChildTextTrim("UserName", METSRights_NS); //Check if this person exists in DSpace already EPerson person = ePersonService.findByEmail(context, personEmail); //If cannot find by email, try by netID //(though METSRights should contain email if it was exported by DSpace) if (person == null) { person = ePersonService.findByNetid(context, personEmail); } //if not found, throw an error -- user should restore person from the SITE AIP if (person == null) { throw new CrosswalkInternalException("Cannot restore Person permissions on object (" + "type=" + Constants.typeText[dso .getType()] + ", " + "handle=" + dso.getHandle() + ", " + "ID=" + dso.getID() + "). The Person with email/netid '" + personEmail + "' is missing from DSpace. " + "Please restore this Person object using the " + "SITE AIP, or recreate it."); } //assign person to the policy rp.setEPerson(person); } else { log.error("Unrecognized CONTEXTCLASS: " + contextClass); } //set permissions on policy add to list of policies rp.setAction(parsePermissions(permsElement)); policies.add(rp); } //end if "Context" element } //end for loop // Finally, we need to remove any existing policies from the current object, // and replace them with the policies provided via METSRights. NOTE: // if the list of policies provided by METSRights is an empty list, then // the final object will have no policies attached. authorizeService.removeAllPolicies(context, dso); authorizeService.addPolicies(context, policies, dso); } // end else } /** * Parses the 'permsElement' (corresponding to a Permissions * element) to find the corresponding DSpace permission type. This * DSpace permission type must be one of the Action IDs specified in * org.dspace.core.Constants *

* Returns -1 if failed to parse permissions. * * @param permsElement The METSRights Permissions element * @return A DSpace Action ID from org.dspace.core.Constants */ private int parsePermissions(Element permsElement) { //First, check if the @OTHERPERMITTYPE attribute is specified String otherPermitType = permsElement.getAttributeValue("OTHERPERMITTYPE"); //if @OTHERPERMITTYPE attribute exists, it will map directly to a DSpace Action type if (otherPermitType != null && !otherPermitType.isEmpty()) { if (otherTypesMapping.containsValue(otherPermitType)) { //find the Action ID this value maps to for (int actionType : otherTypesMapping.keySet()) { //if found, this is the Action ID corresponding to this permission if (otherTypesMapping.get(actionType).equals(otherPermitType)) { return actionType; } } } else { log.warn("Unrecognized @OTHERPERMITTYPE attribute value (" + otherPermitType + ") found in METSRights section of METS Manifest."); } } else { // Otherwise, a closer analysis of all Permission element attributes is necessary boolean discoverPermit = Boolean.parseBoolean(permsElement.getAttributeValue("DISCOVER")); boolean displayPermit = Boolean.parseBoolean(permsElement.getAttributeValue("DISPLAY")); boolean modifyPermit = Boolean.parseBoolean(permsElement.getAttributeValue("MODIFY")); boolean deletePermit = Boolean.parseBoolean(permsElement.getAttributeValue("DELETE")); boolean otherPermit = Boolean.parseBoolean(permsElement.getAttributeValue("OTHER")); //if DELETE='true' if (deletePermit && !otherPermit) { //This must refer to the DELETE action type //(note REMOVE & ADMIN action type have @OTHERPERMITTYPE values specified) return Constants.DELETE; } else if (modifyPermit && !otherPermit) { //This must refer to the WRITE action type //(note ADD action type has an @OTHERPERMITTYPE value specified) return Constants.WRITE; } else if (discoverPermit && displayPermit && !otherPermit) { //This must refer to the READ action type return Constants.READ; } } //if we got here, we failed to parse out proper permissions // return -1 to signify failure (as 0 = READ permissions) return -1; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy