/* * Copyright (C) 2014-2016 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.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.collect.HashMultimap; 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 omero.cmd.HandleI.Cancel; import omero.cmd.ERR; import omero.cmd.Helper; import omero.cmd.IRequest; import omero.cmd.GraphModify2; import omero.cmd.Response; import omero.cmd.SkipHead; import omero.cmd.State; import omero.cmd.Status; /** * The skip-head request performs the wrapped request twice: once in dry run mode to discover from which model objects to start, * and then actually starting from those objects. * @author m.t.b.carroll@dundee.ac.uk * @since 5.1.0 */ public class SkipHeadI extends SkipHead implements IRequest { private static final Logger LOGGER = LoggerFactory.getLogger(SkipHeadI.class); private static final ImmutableSet<State> REQUEST_FAILURE_FLAGS = ImmutableSet.of(State.CANCELLED, State.FAILURE); private final GraphPathBean graphPathBean; private final GraphRequestFactory graphRequestFactory; private final Status graphRequestSkipStatus = new Status(); private final Status graphRequestPerformStatus = new Status(); private final List<Object> graphRequestSkipObjects = new ArrayList<Object>(); private final List<Object> graphRequestPerformObjects = new ArrayList<Object>(); private GraphModify2 graphRequestSkip; private GraphModify2 graphRequestPerform; private Helper helper; /** * Construct a new <q>skip-head</q> request; called from {@link GraphRequestFactory#getRequest(Class)}. * @param graphPathBean the graph path bean to use * @param graphRequestFactory a means of instantiating the sub-request */ public SkipHeadI(GraphPathBean graphPathBean, GraphRequestFactory graphRequestFactory) { this.graphPathBean = graphPathBean; this.graphRequestFactory = graphRequestFactory; } @Override public Map<String, String> getCallContext() { return ((IRequest) request).getCallContext(); } @Override public void init(Helper helper) { if (LOGGER.isDebugEnabled()) { final GraphUtil.ParameterReporter arguments = new GraphUtil.ParameterReporter(); arguments.addParameter("startFrom", startFrom); if (request != null) { arguments.addParameter("request", request.getClass().getName()); } arguments.addParameter("targetObjects", targetObjects); arguments.addParameter("childOptions", childOptions); arguments.addParameter("dryRun", dryRun); LOGGER.debug("request: " + arguments); } this.helper = helper; final GraphPolicy.Action startAction; final WrappableRequest<GraphModify2> wrappedRequest; if (request == null) { throw new RuntimeException(new GraphException("must pass a request argument")); } else if (!(request instanceof WrappableRequest)) { throw new RuntimeException(new GraphException( "cannot use " + SkipHead.class.getSimpleName() + " on " + request.getClass().getSimpleName())); } else { /* create the two wrapped requests */ final Class<? extends GraphModify2> requestClass = request.getClass(); wrappedRequest = (WrappableRequest<GraphModify2>) request; startAction = wrappedRequest.getActionForStarting(); graphRequestSkip = graphRequestFactory.getRequest(requestClass); graphRequestPerform = graphRequestFactory.getRequest(requestClass); wrappedRequest.copyFieldsTo(graphRequestPerform); wrappedRequest.copyFieldsTo(graphRequestSkip); /* the skip-head half takes on the top-level options and does not modify any model objects */ GraphUtil.copyFields(this, graphRequestSkip); graphRequestSkip.dryRun = true; if (dryRun) { graphRequestPerform.dryRun = true; } } /* adjust the requests' graph traversal policies */ final SetMultimap<String, Long> permissionsOverrides = HashMultimap.create(); ((WrappableRequest<?>) graphRequestSkip).adjustGraphPolicy(new Function<GraphPolicy, GraphPolicy>() { @Override public GraphPolicy apply(GraphPolicy graphPolicy) { try { /* adjust skip-head half to stop when it reaches the model objects from which to start */ return SkipHeadPolicy.getSkipHeadPolicySkip(graphPolicy, graphPathBean, startFrom, startAction, permissionsOverrides); } catch (GraphException e) { throw new RuntimeException("graph traversal policy adjustment failed: " + e, e); } } }); ((WrappableRequest<?>) graphRequestPerform).adjustGraphPolicy(new Function<GraphPolicy, GraphPolicy>() { @Override public GraphPolicy apply(GraphPolicy graphPolicy) { /* adjust tail half to propagate permissions overrides from skip-head half */ return SkipHeadPolicy.getSkipHeadPolicyPerform(graphPolicy, permissionsOverrides); } }); try { /* initialize the two wrapped requests */ ((IRequest) graphRequestSkip).init(helper.subhelper(graphRequestSkip, graphRequestSkipStatus)); ((IRequest) graphRequestPerform).init(helper.subhelper(graphRequestPerform, graphRequestPerformStatus)); } catch (Cancel c) { /* mark own status as canceled */ Throwable t = c.getCause(); if (t == null) { t = c; } helper.fail(new ERR(), t, "graph-fail"); helper.getStatus().flags.add(State.CANCELLED); /* re-throw wrapped request Cancel */ throw c; } catch (Throwable t) { /* cancel because of wrapped request exception */ throw helper.cancel(new ERR(), t, "graph-fail"); } /* set step count */ graphRequestSkipStatus.steps = 1 + ((WrappableRequest<?>) graphRequestSkip).getStepProvidingCompleteResponse(); helper.setSteps(graphRequestSkipStatus.steps + graphRequestPerformStatus.steps); } @Override public Object step(int step) throws Cancel { helper.assertStep(step); if (step < graphRequestSkipStatus.steps) { try { /* do a skip-head step */ graphRequestSkipStatus.currentStep = step; graphRequestSkipObjects.add(((IRequest) graphRequestSkip).step(step)); } catch (Cancel e) { /* the step failed, so propagate the error response to this request */ helper.getStatus().flags.addAll(REQUEST_FAILURE_FLAGS); helper.setResponseIfNull(((IRequest) graphRequestSkip).getResponse()); throw e; } } else { final int substep = step - graphRequestSkipStatus.steps; if (substep == 0) { /* if the skip-head half is now completed, construct its response to feed into the tail half */ for (int i = 0; i < graphRequestSkipStatus.steps; i++) { ((IRequest) graphRequestSkip).buildResponse(i, graphRequestSkipObjects.get(i)); } final Response response = ((IRequest) graphRequestSkip).getResponse(); final Map<String, List<Long>> allTargetedObjects = ((WrappableRequest<?>) graphRequestSkip).getStartFrom(response); graphRequestPerform.targetObjects = new HashMap<String, List<Long>>(); /* pick out the model objects matching the startFrom classes */ for (String startFromClassName : startFrom) { final int lastDot = startFromClassName.lastIndexOf('.'); if (lastDot > 0) { startFromClassName = startFromClassName.substring(lastDot + 1); } final Class<? extends IObject> startFromClass = graphPathBean.getClassForSimpleName(startFromClassName); for (final Map.Entry<String, List<Long>> targetedObjectsByClass : allTargetedObjects.entrySet()) { final String targetedClassName = targetedObjectsByClass.getKey(); final Class<? extends IObject> targetedClass; try { targetedClass = Class.forName(targetedClassName).asSubclass(IObject.class); } catch (ClassNotFoundException cnfe) { final Exception e = new IllegalStateException( "response from " + graphRequestSkip.getClass() + " refers to class " + targetedClassName); throw helper.cancel(new ERR(), e, "bad-class"); } if (startFromClass.isAssignableFrom(targetedClass)) { final List<Long> ids = targetedObjectsByClass.getValue(); graphRequestPerform.targetObjects.put(targetedClassName, ids); } } } } if (substep < graphRequestPerformStatus.steps) { try { /* do a tail step */ graphRequestPerformStatus.currentStep = substep; graphRequestPerformObjects.add(((IRequest) graphRequestPerform).step(substep)); } catch (Cancel e) { /* the step failed, so propagate the error response to this request */ helper.getStatus().flags.addAll(REQUEST_FAILURE_FLAGS); helper.setResponseIfNull(((IRequest) graphRequestPerform).getResponse()); throw e; } } else { final Exception e = new IllegalArgumentException("model object graph operation has no step " + step); throw helper.cancel(new ERR(), e, "bad-step"); } } return null; } @Override public void finish() { } @Override public void buildResponse(int step, Object object) { helper.assertResponse(step); if (step == 0) { /* use the response of the tail half */ final IRequest tailHalf = (IRequest) graphRequestPerform; for (int substep = 0; substep < graphRequestPerformStatus.steps; substep++) { tailHalf.buildResponse(substep, graphRequestPerformObjects.get(substep)); } final Response response = tailHalf.getResponse(); helper.setResponseIfNull(response); } } @Override public Response getResponse() { return helper.getResponse(); } }