/*
* Copyright (C) 2014 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 omero.cmd.graphs;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.SetMultimap;
import ome.model.IObject;
import ome.services.graphs.GraphException;
import ome.services.graphs.GraphPathBean;
import ome.services.graphs.GraphPolicy;
import ome.services.graphs.GraphPolicyRulePredicate;
import omero.cmd.SkipHead;
/**
* Adjust graph traversal policy to prevent descent into inclusions beyond certain types.
* @author m.t.b.carroll@dundee.ac.uk
* @since 5.1.0
*/
public class SkipHeadPolicy {
private static final Logger LOGGER = LoggerFactory.getLogger(SkipHeadPolicy.class);
/**
* Adjust an existing graph traversal policy so that orphaned model objects will always or never be included,
* according to their type.
* @param graphPolicy the graph policy to adjust
* @param graphPathBean the graph path bean, for converting class names to the actual classes
* @param startFrom the model object types to from which to start inclusion, may not be empty or {@code null}
* @param startAction the action associated with nodes qualifying as start objects
* @param permissionsOverrides where to note for which {@code startFrom} objects permissions are not to be checked
* @return the adjusted graph policy
* @throws GraphException if no start classes are named
*/
public static GraphPolicy getSkipHeadPolicySkip(final GraphPolicy graphPolicy, final GraphPathBean graphPathBean,
Collection<String> startFrom, final GraphPolicy.Action startAction,
final SetMultimap<String, Long> permissionsOverrides) throws GraphException {
if (CollectionUtils.isEmpty(startFrom)) {
throw new GraphException(SkipHead.class.getSimpleName() + " requires the start classes to be named");
}
/* convert the class names to actual classes */
final Function<String, Class<? extends IObject>> getClassFromName = new Function<String, Class<? extends IObject>>() {
@Override
public Class<? extends IObject> apply(String className) {
final int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
className = className.substring(lastDot + 1);
}
return graphPathBean.getClassForSimpleName(className);
}
};
final ImmutableSet<Class <? extends IObject>> startFromClasses =
ImmutableSet.copyOf(Collections2.transform(startFrom, getClassFromName));
final Predicate<IObject> isStartFrom = new Predicate<IObject>() {
@Override
public boolean apply(IObject subject) {
final Class<? extends IObject> subjectClass = subject.getClass();
for (final Class<? extends IObject> startFromClass : startFromClasses) {
if (startFromClass.isAssignableFrom(subjectClass)) {
return true;
}
}
return false;
}
};
/* construct the function corresponding to the model graph descent truncation */
return new GraphPolicy() {
@Override
public void registerPredicate(GraphPolicyRulePredicate predicate) {
graphPolicy.registerPredicate(predicate);
}
@Override
public GraphPolicy getCleanInstance() {
throw new IllegalStateException("not expecting to provide a clean instance");
}
@Override
public void setCondition(String name) {
graphPolicy.setCondition(name);
}
@Override
public boolean isCondition(String name) {
return graphPolicy.isCondition(name);
}
@Override
public void noteDetails(Session session, IObject object, String realClass, long id) {
graphPolicy.noteDetails(session, object, realClass, id);
}
@Override
public final Set<Details> review(Map<String, Set<Details>> linkedFrom, Details rootObject,
Map<String, Set<Details>> linkedTo, Set<String> notNullable, boolean isErrorRules) throws GraphException {
if (rootObject.action == startAction && isStartFrom.apply(rootObject.subject)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("deferring review of " + rootObject);
}
/* note which permissions overrides to start from */
final String className = rootObject.subject.getClass().getName();
final Long id = rootObject.subject.getId();
if (rootObject.isCheckPermissions) {
permissionsOverrides.remove(className, id);
} else {
permissionsOverrides.put(className, id);
}
/* skip the review, start from this object in a later request */
return Collections.emptySet();
} else {
/* do the review */
return graphPolicy.review(linkedFrom, rootObject, linkedTo, notNullable, isErrorRules);
}
}
};
}
/**
* Adjust an existing graph traversal policy so that for specific model objects permissions are not checked.
* @param graphPolicy the graph policy to adjust
* @param permissionsOverrides for which model objects permissions are not to be checked
* @return the adjusted graph policy
*/
public static GraphPolicy getSkipHeadPolicyPerform(final GraphPolicy graphPolicy,
final SetMultimap<String, Long> permissionsOverrides) {
return new BaseGraphPolicyAdjuster(graphPolicy) {
@Override
protected boolean isAdjustedBeforeReview(Details object) {
if (object.isCheckPermissions &&
permissionsOverrides.containsEntry(object.subject.getClass().getName(), object.subject.getId())) {
object.isCheckPermissions = false;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("preserving previous setting, making " + object);
}
return true;
} else {
return false;
}
}
};
}
}