/* * Copyright (C) 2012 Glencoe Software, Inc. 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.basic; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import ome.services.messages.ContextMessage; import ome.system.OmeroContext; import omero.cmd.DoAll; import omero.cmd.DoAllRsp; import omero.cmd.ERR; import omero.cmd.HandleI.Cancel; import omero.cmd.Helper; import omero.cmd.IRequest; import omero.cmd.Request; import omero.cmd.Response; import omero.cmd.Status; import omero.cmd.graphs.GraphUtil; /** * Permits performing multiple operations * * @author Josh Moore, josh at glencoesoftware.com * @since 4.4.0 */ public class DoAllI extends DoAll implements IRequest { private static final long serialVersionUID = -323423435135556L; // // Mapping from steps to subrequests // /** * Pointer-like object which is saved for each * sub-request. The logic for properly mapping * from the global step number to the individual * substep number is done here. In order to find * the proper {@link X} instance, use the current * index: * * <pre> * X x = substeps.get(current); * </pre> */ private static class X { /** * Number of steps that should be deducted from the global step * count in order to have the proper substep number */ final int offset; /** * Sub-{@link Helper} instance for the {@link #r} */ final Helper h; /** * Sub-{@link Request} instance which is to be run. */ final IRequest r; final OmeroContext ctx; /** * Calculated context which should be in effect for {@link #r}. */ Map<String, String> c = null; X(int offset, Helper h, IRequest r, OmeroContext ctx) { this.offset = offset; this.h = h; this.r = r; this.ctx = ctx; } /** * Run the {@link IRequest#step(int)} passing in the proper substep * value after being calculated via {@link #offset} */ Object step(int step) { final int substep = step - offset; h.getStatus().currentStep = substep; return r.step(substep); } /** * Run the {@link IRequest#buildResponse(int, Object)} passing in the * proper substep value after being calculated via {@link #offset} */ void buildResponse(int step, Object object) { r.buildResponse(step - offset, object); } /** * Fill in the call context for this instance ignoring nulls and empty * maps. * @param classContext The return value of {@link IRequest#getCallContext()} * @param callContext The corresponding instance from {@link DoAll#contexts} */ void calculateContext(Map<String, String> classContext, Map<String, String> callContext) { putAll(classContext); putAll(callContext); } /** * Helper */ private void putAll(Map<String, String> context) { if (context != null && context.size() > 0) { if (c == null) { c = new HashMap<String, String>(); } c.putAll(context); } } /** * Send a {@link PushContextMessage} to apply this context if not null. */ void login() throws Throwable { if (c != null) { h.debug("Login: %s", c); ctx.publishMessage(new ContextMessage.Push(this, c)); } } /** * If a {@link PushContextMessage} was sent, send a {@link PopContextMessage} * so that the context for following actions are not polluted. */ void logout() throws Throwable { if (c != null) { ctx.publishMessage(new ContextMessage.Pop(this ,c)); } } } /** * State-objects for each subrequest */ private final List<X> substeps = new ArrayList<X>(); /** * current substep. */ private int current = -1; /** * step at which we flip to the next current. */ private int nextAt = 0; /** * Looks up the current substep based on the total step count using * {@link #nextAt} to determine if {@link #current} needs to be incremented. * If login is true, then {@link X#login()} and {@link X#logout} will be * called as appropriate. * * @param step the overall step number * @param login if {@link X#login()} and {@link X#logout} should be called * @return the current substep information */ private X substep(final int step, final boolean login) { X x = null; try { if (step == 0) { // Restart x = substeps.get(0); current = 0; nextAt = x.offset + x.h.getSteps(); if (login) { x.login(); } } else if (step == nextAt) { // Flip to next substep. We should never have // a step which makes current >= substeps.size() X prev = substeps.get(current); if (login) { prev.logout(); } current += 1; x = substeps.get(current); nextAt = x.offset + x.h.getSteps(); if (login) { x.login(); } } else { x = substeps.get(current); } return x; } catch (Throwable t) { throw helper.cancel(new ERR(), t, "substep-lookup-failed", "step", ""+step, "req", (x==null ? "null" : ""+x.r)); } } // // Primary state // private final List<Status> statuses = new ArrayList<Status>(); private final List<Response> responses = new ArrayList<Response>(); /** * Helper instance for this class. Will create a number of sub-helper * instances for each request. */ private Helper helper; // // For publishing messages // // private final OmeroContext ctx; public DoAllI(OmeroContext ctx) { this.ctx = ctx; } // // IRequest methods // public Map<String, String> getCallContext() { return null; } public void init(Helper helper) { this.helper = helper; int steps = 0; try { Map<String, String> allgroups = new HashMap<String, String>(); allgroups.put("omero.group", "-1"); ctx.publishMessage(new ContextMessage.Push(this, allgroups)); try { // Process within -1 block. GraphUtil.combineFacadeRequests(this.requests); } finally { ctx.publishMessage(new ContextMessage.Pop(this, allgroups)); } for (int i = 0; i < this.requests.size(); i++) { final Request req = requests.get(i); final Status substatus = new Status(); final Helper subhelper = helper.subhelper(req, substatus); if (req instanceof IRequest) { IRequest ireq = (IRequest) req; final X x = new X(steps, subhelper, ireq, ctx); try { x.calculateContext(ireq.getCallContext(), (contexts == null || contexts.length <= i) ? null : contexts[i]); x.login(); try { ireq.init(subhelper); statuses.add(substatus); substeps.add(x); long intermediate = substatus.steps; if ((intermediate + steps) > Integer.MAX_VALUE) { throw helper.cancel(new ERR(), null, "too-many-steps", "Steps", ""+intermediate, "Message", "Too many steps found! Try fewer actions in one command"); } steps += intermediate; } finally { x.logout(); } } catch (Cancel c) { throw subcancel(c, x); } } else { throw helper.cancel(new ERR(), null, "bad-request", "type", req.ice_id()); } } } catch (Cancel c) { throw c; // just re-throw } catch (Throwable t) { helper.cancel(new ERR(), t, "bad-init"); } helper.setSteps(steps); } public Object step(int step) { helper.assertStep(step); final X x = substep(step, true); try { return x.step(step); } catch (Cancel c) { throw subcancel(c, x); } } public void finish() { for (X x : substeps) { try { x.login(); try { x.r.finish(); } finally { x.logout(); } } catch (Cancel c) { throw subcancel(c, x); } catch (Throwable t) { helper.cancel(new ERR(), t, "bad-finish"); } } } public void buildResponse(int step, Object object) { helper.assertResponse(step); final X x = substep(step, false); x.buildResponse(step, object); if (helper.isLast(step)) { for (Request subreq : requests) { // Again, must be an irequest IRequest ireq = (IRequest) subreq; responses.add(ireq.getResponse()); } DoAllRsp rsp = new DoAllRsp(responses, statuses); helper.setResponseIfNull(rsp); } } public Response getResponse() { return helper.getResponse(); } protected Cancel subcancel(Cancel c, X x) { final Response subrsp = x.h.getResponse(); final Status substatus = x.h.getStatus(); final Status status = helper.getStatus(); helper.setResponseIfNull(subrsp); status.flags.addAll(substatus.flags); if (status.parameters == null) { status.parameters = new HashMap<String, String>(); } if (substatus.parameters != null) { status.parameters.putAll(substatus.parameters); } status.parameters.put("subrequest", ""+subrsp); status.category = ice_id(); status.name = "subcancel"; throw c; } }