/*
* Copyright (C) 2014-2015 University of Dundee & Open Microscopy Environment.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package ome.services.graphs;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hibernate.Session;
import ome.model.IObject;
/**
* A policy guides how to traverse the graph. This class' methods are expected to be fast and are not required to be thread-safe.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public abstract class GraphPolicy {
/**
* The action to take on an object instance.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public static enum Action {
/** do not include the object in the operation */
EXCLUDE,
/** delete the object */
DELETE,
/** include the object in the operation */
INCLUDE,
/** object is inappropriate for the operation and may be related to both included and excluded objects */
OUTSIDE;
}
/**
* If an object instance has any {@link Action#EXCLUDE}d <q>parents</q> that would prevent it from being <q>orphaned</q>.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public static enum Orphan {
/** it is not known and it does not matter if the object is an orphan: effort should not yet be made to find out */
IRRELEVANT,
/** it is not known but it matters if the object is an orphan: effort should be made to find out */
RELEVANT,
/** the object is an orphan */
IS_LAST,
/** the object is not an orphan */
IS_NOT_LAST;
}
/**
* Abilities that the user may have to operate upon model objects.
* Note that system users have all abilities.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public static enum Ability {
/**
* the user's ability to update the object, as judged by
* {@link ome.security.ACLVoter#allowUpdate(IObject, ome.model.internal.Details)}
*/
UPDATE,
/**
* the user's ability to delete the object, as judged by
* {@link ome.security.ACLVoter#allowDelete(IObject, ome.model.internal.Details)}
*/
DELETE,
/**
* the user's ability to change permissions on the object, as judged by
* {@link ome.security.ACLVoter#allowChmod(IObject)}
*/
CHMOD,
/**
* the user actually owns the object
*/
OWN;
}
/**
* A tuple noting the state of a mapped object instance in the current graph traversal.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public static abstract class Details {
/** the unloaded instance */
public final IObject subject;
/**
* the ID of the object's {@link ome.model.meta.Experimenter},
* or {@code null} if the object does not have an owner
*/
public final Long ownerId;
/**
* the ID of the object's {@link ome.model.meta.ExperimenterGroup},
* or {@code null} if the object does not have a group
*/
public final Long groupId;
/** the current permissions on the object */
public final Set<Ability> permissions;
/** the current plan for the object, may be mutated by {@link GraphPolicy#review(Map, Details, Map, Set, boolean)} */
public Action action;
/**
* the current <q>orphan</q> state of the object, may be mutated by {@link GraphPolicy#review(Map, Details, Map, Set, boolean)};
* applies only if {@link #action} is {@link Action#EXCLUDE}
*/
public Orphan orphan;
/** if the user's permissions for the object should be checked before the {@link GraphTraversal.Processor} acts upon it */
public boolean isCheckPermissions;
/**
* Construct a note of an object and its details.
* {@link #equals(Object)} and {@link #hashCode()} consider only the subject, not the action or orphan.
* @param subject the object whose details these are
* @param ownerId the ID of the object's owner
* @param groupId the ID of the object's group
* @param action the current plan for the object
* @param orphan the current <q>orphan</q> state of the object
* @param mayUpdate if the object may be updated
* @param mayDelete if the object may be deleted
* @param mayChmod if the object may have its permissions changed
* @param isOwner if the user owns the object
* @param isCheckPermissions if the user is expected to have the permissions required to process the object
*/
Details(IObject subject, Long ownerId, Long groupId, Action action, Orphan orphan,
boolean mayUpdate, boolean mayDelete, boolean mayChmod, boolean isOwner, boolean isCheckPermissions) {
this.subject = subject;
this.ownerId = ownerId;
this.groupId = groupId;
this.action = action;
this.orphan = orphan;
permissions = EnumSet.noneOf(Ability.class);
if (mayUpdate) {
permissions.add(Ability.UPDATE);
}
if (mayDelete) {
permissions.add(Ability.DELETE);
}
if (mayChmod) {
permissions.add(Ability.CHMOD);
}
if (isOwner) {
permissions.add(Ability.OWN);
}
this.isCheckPermissions = isCheckPermissions;
}
}
/**
* The predicates that have been registered with {@link GraphPolicy#registerPredicate(GraphPolicyRulePredicate)}.
*/
protected final Map<String, GraphPolicyRulePredicate> predicates = new HashMap<String, GraphPolicyRulePredicate>();
/**
* Create a clone of this graph policy that has fresh state.
* @return an instance ready to begin a new graph traversal
*/
public abstract GraphPolicy getCleanInstance();
/**
* Use the given predicate in executing this graph policy.
* @param predicate a graph policy predicate
*/
public void registerPredicate(GraphPolicyRulePredicate predicate) {
predicates.put(predicate.getName(), predicate);
}
/**
* Set a named condition,
* @param name the name of the condition
*/
public abstract void setCondition(String name);
/**
* Check if a condition has been set.
* @param name the name of the condition
* @return if the condition is set
*/
public abstract boolean isCondition(String name);
/**
* Any model object about which policy may be asked is first passed to {@link #noteDetails(Session, IObject, String, long)} before
* {@link GraphPolicy#review(Map, Details, Map, Set, boolean)}. Each object is passed only once.
* Subclasses overriding this method probably ought also override {@link #getCleanInstance()}.
* @param session the Hibernate session, for obtaining more information about the object
* @param object an unloaded model object about which policy may be asked
* @param realClass the real class name of the object
* @param id the ID of the object
*/
public void noteDetails(Session session, IObject object, String realClass, long id) {
for (final GraphPolicyRulePredicate predicate : predicates.values()) {
predicate.noteDetails(session, object, realClass, id);
}
}
/**
* Utility method to return all the objects for review as a single set of objects.
* @param linkedFrom details of the objects linking to the root object
* @param rootObject details of the root objects
* @param linkedTo details of the objects linked by the root object
* @return details of all the objects passed as arguments
*/
public static Set<Details> allObjects(Collection<Set<Details>> linkedFrom, Details rootObject,
Collection<Set<Details>> linkedTo) {
final Set<Details> allTerms = new HashSet<Details>();
allTerms.add(rootObject);
for (final Set<Details> terms : linkedFrom) {
allTerms.addAll(terms);
}
for (final Set<Details> terms : linkedTo) {
allTerms.addAll(terms);
}
return allTerms;
}
/**
* The action to take about the link between the mapped objects.
* An {@link Action#EXCLUDE}d object, once changed from that, may not change back to {@link Action#EXCLUDE}.
* An {@link Action#OUTSIDE} object, once changed to that, may not change back from {@link Action#OUTSIDE}.
* {@link Orphan} values matter only for {@link Action#EXCLUDE}d objects.
* Given {@link Orphan#RELEVANT} if {@link Orphan#IS_LAST} or {@link Orphan#IS_NOT_LAST} can be returned,
* or could be if after {@link Orphan#RELEVANT} is returned then resolved for the other object,
* then appropriate values should be returned accordingly.
* If {@link Orphan#RELEVANT} is returned for an object then this method may be called again with
* {@link Orphan#IS_LAST} or {@link Orphan#IS_NOT_LAST}.
* Class properties' <code>String</code> representation is <code>package.DeclaringClass.propertyName</code>.
* @param linkedFrom map from class property to objects for which the property links to the root object
* @param rootObject the object at the center of this review
* @param linkedTo map from class property to objects to which the property links from the root object
* @param notNullable which properties from the linkedFrom and linkedTo map keys are not nullable
* @param isErrorRules if final checks should be performed instead of normal rule matching
* @return changes to make, included unchanged details typically cause review as root object
* @throws GraphException if there was a problem in applying the policy
*/
public abstract Set<Details> review(Map<String, Set<Details>> linkedFrom, Details rootObject,
Map<String, Set<Details>> linkedTo, Set<String> notNullable, boolean isErrorRules) throws GraphException;
}