edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.mannlib.vitro.webapp.auth.policy.bean.PropertyRestrictionPolicyHelper.java

Source

/* $This file is distributed under the terms of the license in /doc/license.txt$ */

package edu.cornell.mannlib.vitro.webapp.auth.policy.bean;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.Statement;
import com.hp.hpl.jena.rdf.model.StmtIterator;
import com.hp.hpl.jena.rdf.model.impl.Util;
import com.hp.hpl.jena.shared.Lock;

import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean.RoleLevel;
import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;

/**
 * Assists the role-based policies in determining whether a property or resource
 * may be displayed or modified.
 * 
 * There is a bean in the context that holds the current threshold role levels
 * for displaying and modifying restricted properties.
 * 
 * Create this bean after the Jena model is in place in the context.
 * 
 * Add PropertyRestrictionListener to your EditProcessObject if you are editing
 * a property, to ensure that the bean stays current.
 */
public class PropertyRestrictionPolicyHelper {
    private static final Log log = LogFactory.getLog(PropertyRestrictionPolicyHelper.class);

    private static final Collection<String> PROHIBITED_NAMESPACES = Arrays
            .asList(new String[] { VitroVocabulary.vitroURI, "" });

    private static final Collection<String> PERMITTED_EXCEPTIONS = Arrays
            .asList(new String[] { VitroVocabulary.MONIKER, VitroVocabulary.MODTIME,

                    VitroVocabulary.IND_MAIN_IMAGE,

                    VitroVocabulary.LINK, VitroVocabulary.PRIMARY_LINK, VitroVocabulary.ADDITIONAL_LINK,
                    VitroVocabulary.LINK_ANCHOR, VitroVocabulary.LINK_URL });

    /**
     * The bean is attached to the ServletContext using this attribute name.
     */
    private static final String ATTRIBUTE_NAME = PropertyRestrictionPolicyHelper.class.getName();

    // ----------------------------------------------------------------------
    // static methods
    // ----------------------------------------------------------------------

    public static PropertyRestrictionPolicyHelper getBean(ServletContext ctx) {
        Object attribute = ctx.getAttribute(ATTRIBUTE_NAME);
        if (!(attribute instanceof PropertyRestrictionPolicyHelper)) {
            throw new IllegalStateException("PropertyRestrictionPolicyHelper has not been initialized.");
        }
        return (PropertyRestrictionPolicyHelper) attribute;
    }

    private static void removeBean(ServletContext ctx) {
        ctx.removeAttribute(ATTRIBUTE_NAME);
    }

    public static void setBean(ServletContext ctx, PropertyRestrictionPolicyHelper bean) {
        if (bean == null) {
            throw new NullPointerException("bean may not be null.");
        }
        ctx.setAttribute(ATTRIBUTE_NAME, bean);
    }

    /**
     * Initialize the bean with the standard prohibitions and exceptions, and
     * with the thresholds obtained from the model.
     */
    public static PropertyRestrictionPolicyHelper createBean(OntModel model) {
        Map<String, RoleLevel> displayThresholdMap = new HashMap<String, RoleLevel>();
        Map<String, RoleLevel> modifyThresholdMap = new HashMap<String, RoleLevel>();

        if (model != null) {
            populateThresholdMap(model, displayThresholdMap,
                    VitroVocabulary.HIDDEN_FROM_DISPLAY_BELOW_ROLE_LEVEL_ANNOT);
            populateThresholdMap(model, modifyThresholdMap,
                    VitroVocabulary.PROHIBITED_FROM_UPDATE_BELOW_ROLE_LEVEL_ANNOT);
        }

        PropertyRestrictionPolicyHelper bean = new PropertyRestrictionPolicyHelper(PROHIBITED_NAMESPACES,
                PERMITTED_EXCEPTIONS, displayThresholdMap, modifyThresholdMap);

        return bean;
    }

    /**
     * Find all the resources that possess this property, and map the resource
     * URI to the required RoleLevel.
     */
    private static void populateThresholdMap(OntModel model, Map<String, RoleLevel> map, String propertyUri) {
        model.enterCriticalSection(Lock.READ);
        try {
            Property property = model.getProperty(propertyUri);
            StmtIterator stmts = model.listStatements((Resource) null, property, (Resource) null);
            while (stmts.hasNext()) {
                Statement stmt = stmts.next();
                Resource subject = stmt.getSubject();
                RDFNode objectNode = stmt.getObject();
                if ((subject == null) || (!(objectNode instanceof Resource))) {
                    continue;
                }
                Resource object = (Resource) objectNode;
                RoleLevel role = RoleLevel.getRoleByUri(object.getURI());
                map.put(subject.getURI(), role);
            }
            stmts.close();
        } finally {
            model.leaveCriticalSection();
        }
    }

    // ----------------------------------------------------------------------
    // the bean
    // ----------------------------------------------------------------------

    /**
     * URIs in these namespaces can't be modified, unless they are listed in the
     * exceptions.
     */
    private final Collection<String> modifyProhibitedNamespaces;

    /**
     * These URIs can be modified, even if they are in the prohibited
     * namespaces.
     */
    private final Collection<String> modifyExceptionsAllowedUris;

    /**
     * These URIs can be displayed only if the user's role is at least as high
     * as the threshold role.
     */
    private final Map<String, RoleLevel> displayThresholdMap;

    /**
     * These URIs can be modified only if the user's role is at least as high as
     * the threshold role.
     */
    private final Map<String, RoleLevel> modifyThresholdMap;

    /**
     * Store unmodifiable versions of the inputs.
     * 
     * Protected access: the bean should only be created by the static methods,
     * or by unit tests.
     */
    protected PropertyRestrictionPolicyHelper(Collection<String> modifyProhibitedNamespaces,
            Collection<String> modifyExceptionsAllowedUris, Map<String, RoleLevel> displayThresholdMap,
            Map<String, RoleLevel> modifyThresholdMap) {
        this.modifyProhibitedNamespaces = unmodifiable(modifyProhibitedNamespaces);
        this.modifyExceptionsAllowedUris = unmodifiable(modifyExceptionsAllowedUris);
        this.displayThresholdMap = unmodifiable(displayThresholdMap);
        this.modifyThresholdMap = unmodifiable(modifyThresholdMap);

        if (log.isDebugEnabled()) {
            log.debug("prohibited: " + this.modifyProhibitedNamespaces);
            log.debug("exceptions: " + this.modifyExceptionsAllowedUris);
            log.debug("display thresholds: " + this.displayThresholdMap);
            log.debug("modify thresholds: " + this.modifyThresholdMap);
        }
    }

    private Collection<String> unmodifiable(Collection<String> raw) {
        if (raw == null) {
            return Collections.emptyList();
        } else {
            return Collections.unmodifiableCollection(new HashSet<String>(raw));
        }
    }

    private Map<String, RoleLevel> unmodifiable(Map<String, RoleLevel> raw) {
        if (raw == null) {
            return Collections.emptyMap();
        } else {
            return Collections.unmodifiableMap(new HashMap<String, RoleLevel>(raw));
        }
    }

    /**
     * Any resource can be displayed.
     * 
     * (Someday we may want to implement display restrictions based on VClass.)
     */
    @SuppressWarnings("unused")
    public boolean canDisplayResource(String resourceUri, RoleLevel userRole) {
        if (resourceUri == null) {
            log.debug("can't display resource: resourceUri was null");
            return false;
        }

        log.debug("can display resource '" + resourceUri + "'");
        return true;
    }

    /**
     * A resource cannot be modified if its namespace is in the prohibited list
     * (but some exceptions are allowed).
     * 
     * (Someday we may want to implement modify restrictions based on VClass.)
     */
    @SuppressWarnings("unused")
    public boolean canModifyResource(String resourceUri, RoleLevel userRole) {
        if (resourceUri == null) {
            log.debug("can't modify resource: resourceUri was null");
            return false;
        }

        if (modifyProhibitedNamespaces.contains(namespace(resourceUri))) {
            if (modifyExceptionsAllowedUris.contains(resourceUri)) {
                log.debug("'" + resourceUri + "' is a permitted exception");
            } else {
                log.debug("can't modify resource '" + resourceUri + "': prohibited namespace: '"
                        + namespace(resourceUri) + "'");
                return false;
            }
        }

        log.debug("can modify resource '" + resourceUri + "'");
        return true;
    }

    /**
     * If display of a predicate is restricted, the user's role must be at least
     * as high as the restriction level.
     */
    public boolean canDisplayPredicate(String predicateUri, RoleLevel userRole) {
        if (predicateUri == null) {
            log.debug("can't display predicate: predicateUri was null");
            return false;
        }

        RoleLevel displayThreshold = displayThresholdMap.get(predicateUri);
        if (isAuthorized(userRole, displayThreshold)) {
            log.debug("can display predicate: '" + predicateUri + "', userRole=" + userRole + ", thresholdRole="
                    + displayThreshold);
            return true;
        }

        log.debug("can't display predicate: '" + predicateUri + "', userRole=" + userRole + ", thresholdRole="
                + displayThreshold);
        return false;
    }

    /**
     * A predicate cannot be modified if its namespace is in the prohibited list
     * (some exceptions are allowed).
     * 
     * If modification of a predicate is restricted, the user's role must be at
     * least as high as the restriction level.
     */
    public boolean canModifyPredicate(String predicateUri, RoleLevel userRole) {
        if (predicateUri == null) {
            log.debug("can't modify predicate: predicateUri was null");
            return false;
        }

        if (modifyProhibitedNamespaces.contains(namespace(predicateUri))) {
            if (modifyExceptionsAllowedUris.contains(predicateUri)) {
                log.debug("'" + predicateUri + "' is a permitted exception");
            } else {
                log.debug("can't modify resource '" + predicateUri + "': prohibited namespace: '"
                        + namespace(predicateUri) + "'");
                return false;
            }
        }

        RoleLevel modifyThreshold = modifyThresholdMap.get(predicateUri);
        if (isAuthorized(userRole, modifyThreshold)) {
            log.debug("can modify predicate: '" + predicateUri + "', userRole=" + userRole + ", thresholdRole="
                    + modifyThreshold);
            return true;
        }

        log.debug("can't modify predicate: '" + predicateUri + "', userRole=" + userRole + ", thresholdRole="
                + modifyThreshold);
        return false;
    }

    private boolean isAuthorized(RoleLevel userRole, RoleLevel thresholdRole) {
        if (userRole == null) {
            return false;
        }
        if (thresholdRole == null) {
            return true;
        }
        return userRole.compareTo(thresholdRole) >= 0;
    }

    private String namespace(String uri) {
        return uri.substring(0, Util.splitNamespace(uri));
    }

    // ----------------------------------------------------------------------
    // Setup class
    // ----------------------------------------------------------------------

    /**
     * Create the bean at startup and remove it at shutdown.
     */
    public static class Setup implements ServletContextListener {
        @Override
        public void contextInitialized(ServletContextEvent sce) {
            ServletContext ctx = sce.getServletContext();
            StartupStatus ss = StartupStatus.getBean(ctx);

            try {
                OntModel model = (OntModel) ctx.getAttribute("jenaOntModel");
                if (model == null) {
                    throw new NullPointerException("jenaOntModel has not been initialized.");
                }

                PropertyRestrictionPolicyHelper bean = PropertyRestrictionPolicyHelper.createBean(model);
                PropertyRestrictionPolicyHelper.setBean(ctx, bean);
            } catch (Exception e) {
                ss.fatal(this, "could not set up PropertyRestrictionPolicyHelper", e);
            }
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            removeBean(sce.getServletContext());
        }
    }

}