001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 *     Vladimir Pasquier <vpasquier@nuxeo.com>
019 */
020package org.nuxeo.ecm.automation.core.operations.execution;
021
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.Map;
026
027import org.nuxeo.ecm.automation.AutomationService;
028import org.nuxeo.ecm.automation.OperationContext;
029import org.nuxeo.ecm.automation.OperationException;
030import org.nuxeo.ecm.automation.core.Constants;
031import org.nuxeo.ecm.automation.core.annotations.Context;
032import org.nuxeo.ecm.automation.core.annotations.Operation;
033import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
034import org.nuxeo.ecm.automation.core.annotations.Param;
035import org.nuxeo.ecm.automation.core.util.Properties;
036import org.nuxeo.ecm.core.api.CoreSession;
037import org.nuxeo.ecm.core.api.DocumentModel;
038
039/**
040 * Run an embedded operation chain using the current input. The output is undefined (Void)
041 *
042 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
043 * @since 5.5
044 */
045@Operation(id = RunOperationOnList.ID, category = Constants.CAT_SUBCHAIN_EXECUTION, label = "Run For Each", description = "Run an operation for each element from the list defined by the 'list' parameter. The 'list' parameter is pointing to a context variable that represents the list which will be iterated. The 'item' parameter represents the name of the context variable which will point to the current element in the list at each iteration. You can use the 'isolate' parameter to specify whether or not the evalution context is the same as the parent context or a copy of it. If the 'isolate' parameter is 'true' then a copy of the current context is used and so that modifications in this context will not affect the parent context. Any input is accepted. The input is returned back as output when operation terminates. The 'parameters' injected are accessible in the subcontext ChainParameters. For instance, @{ChainParameters['parameterKey']}.", aliases = {
046        "Context.RunOperationOnList" })
047public class RunOperationOnList {
048
049    public static final String ID = "RunOperationOnList";
050
051    @Context
052    protected OperationContext ctx;
053
054    @Context
055    protected AutomationService service;
056
057    @Context
058    protected CoreSession session;
059
060    @Param(name = "id")
061    protected String chainId;
062
063    @Param(name = "list")
064    protected String listName;
065
066    @Param(name = "item", required = false, values = "item")
067    protected String itemName = "item";
068
069    @Param(name = "isolate", required = false, values = "true")
070    protected boolean isolate = true;
071
072    @Param(name = "parameters", description = "Accessible in the subcontext " + "ChainParameters. For instance, "
073            + "@{ChainParameters['parameterKey']}.", required = false)
074    protected Properties chainParameters = new Properties();
075
076    /**
077     * @since 6.0 Define if the chain in parameter should be executed in new transaction.
078     */
079    @Param(name = "newTx", required = false, values = "false", description = "Define if the chain in parameter should be "
080            + "executed in new transaction.")
081    protected boolean newTx = false;
082
083    /**
084     * @since 6.0 Define transaction timeout (default to 60 sec).
085     */
086    @Param(name = "timeout", required = false, description = "Define " + "transaction timeout (default to 60 sec).")
087    protected Integer timeout = 60;
088
089    /**
090     * @since 6.0 Define if transaction should rollback or not (default to true).
091     */
092    @Param(name = "rollbackGlobalOnError", required = false, values = "true", description = "Define if transaction should rollback or not "
093            + "(default to true)")
094    protected boolean rollbackGlobalOnError = true;
095
096    @OperationMethod
097    public void run() throws OperationException {
098        // Handle isolation option
099        Map<String, Object> vars = isolate ? new HashMap<>(ctx.getVars()) : ctx.getVars();
100        try (OperationContext subctx = ctx.getSubContext(isolate, ctx.getInput())) {
101
102            // Running chain/operation for each list elements
103            Collection<?> list = null;
104            Object listObject = ctx.get(listName);
105            if (listObject != null) {
106                if (listObject instanceof Object[]) {
107                    list = Arrays.asList((Object[]) listObject);
108                } else if (listObject instanceof Collection<?>) {
109                    list = (Collection<?>) listObject;
110                } else {
111                    throw new UnsupportedOperationException(listObject.getClass() + " is not a Collection");
112                }
113                for (Object value : list) {
114                    subctx.callWithContextVar(() -> {
115                        if (newTx) {
116                            service.runInNewTx(subctx, chainId, chainParameters, timeout, rollbackGlobalOnError);
117                        } else {
118                            service.run(subctx, chainId, chainParameters);
119                        }
120                        return null;
121                    }, itemName, value);
122                }
123            }
124
125            // reconnect documents in the context
126            if (!isolate) {
127                for (Map.Entry<String, Object> var : vars.entrySet()) {
128                    String key = var.getKey();
129                    Object value = var.getValue();
130                    if (!ctx.getVars().containsKey(key)) {
131                        ctx.put(key, value);
132                    } else {
133                        if (session != null && value != null && value instanceof DocumentModel) {
134                            ctx.getVars().put(key, session.getDocument(((DocumentModel) value).getRef()));
135                        } else {
136                            ctx.getVars().put(key, value);
137                        }
138                    }
139                }
140            }
141        }
142    }
143
144}