001/*
002 * (C) Copyright 2013 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 *     vpasquier <vpasquier@nuxeo.com>
019 *     slacoin <slacoin@nuxeo.com>
020 */
021package org.nuxeo.ecm.automation;
022
023import java.security.Principal;
024import java.util.ArrayList;
025import java.util.Collection;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.ecm.automation.core.impl.InvokableMethod;
034import org.nuxeo.ecm.automation.core.trace.Trace;
035import org.nuxeo.ecm.core.api.CoreSession;
036import org.nuxeo.runtime.api.Framework;
037import org.nuxeo.runtime.transaction.TransactionHelper;
038
039/**
040 * An operation context. Holds context objects, a context parameters map and a list of operations to run.
041 * <p>
042 * Context objects are:
043 * <ul>
044 * <li>The Operation Chain Input - optional. It will be used as the input for the first operation in the chain. If input
045 * is null then only VOID methods in the first operation will be matched.
046 * <li>A Core Session - which is optional and should be provided by the caller. (either at creation time as a
047 * constructor argument, either using the {@link #setCoreSession(CoreSession)} method. When running the operation chain
048 * in asynchronous mode another session will be created by preserving the current session credentials.
049 * </ul>
050 * <p>
051 * Each entry in the operation list contains the ID of the operation to be run and a map of operation parameters to use
052 * when initializing the operation.
053 * <p>
054 * The context parameters map can be filled with contextual information by the caller. Each operation will be able to
055 * access the contextual data at runtime and to update it if needed.
056 *
057 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
058 */
059public class OperationContext implements Map<String, Object> {
060
061    private static final Log log = LogFactory.getLog(OperationContext.class);
062
063    /**
064     * The context variables map
065     */
066    protected Map<String, Object> vars;
067
068    /**
069     * Whether to save the session at the end of the chain execution. The default is true.
070     */
071    protected boolean commit = true;
072
073    protected final transient List<CleanupHandler> cleanupHandlers;
074
075    /**
076     * Each stack use a key the type of the objects in the stack: document, documents, blob or blobs
077     */
078    protected final transient Map<String, List<Object>> stacks;
079
080    /**
081     * A logins stack manage multiple logins and sessions in a single chain execution
082     */
083    protected transient LoginStack loginStack;
084
085    /**
086     * The execution input that will be updated after an operation run with the operation output
087     */
088    protected Object input;
089
090    /**
091     * A list of trace. Since 5.7.3 messages is no longer useful for tracing. Use chain call backs to do it.
092     */
093    protected List<String> trace;
094
095    /**
096     * @since 5.7.3 Collect operation invokes.
097     */
098    protected ChainCallback chainCallback;
099
100    public OperationContext() {
101        this(null, null);
102    }
103
104    public OperationContext(OperationContext ctx) {
105        if (ctx.loginStack == null) {
106            this.loginStack = new LoginStack(ctx.getCoreSession());
107        } else {
108            this.loginStack = ctx.loginStack;
109        }
110        this.vars = ctx.vars;
111        this.cleanupHandlers = ctx.cleanupHandlers;
112        this.stacks = ctx.stacks;
113        this.commit = ctx.commit;
114        this.input = ctx.input;
115        this.trace = ctx.trace;
116        this.chainCallback = ctx.chainCallback;
117    }
118
119    public OperationContext(CoreSession session) {
120        this(session, null);
121    }
122
123    public OperationContext(CoreSession session, Map<String, Object> vars) {
124        stacks = new HashMap<String, List<Object>>();
125        cleanupHandlers = new ArrayList<CleanupHandler>();
126        loginStack = new LoginStack(session);
127        trace = new ArrayList<String>();
128        chainCallback = new ChainCallback();
129        this.vars = vars != null ? vars : new HashMap<String, Object>();
130    }
131
132    public void setCoreSession(CoreSession session) {
133        loginStack.setSession(session);
134    }
135
136    public void setCommit(boolean commit) {
137        this.commit = commit;
138    }
139
140    public boolean isCommit() {
141        return commit;
142    }
143
144    public CoreSession getCoreSession() {
145        return loginStack.getSession();
146    }
147
148    public LoginStack getLoginStack() {
149        return loginStack;
150    }
151
152    public Principal getPrincipal() {
153        CoreSession session = loginStack.getSession();
154        return session != null ? session.getPrincipal() : null;
155    }
156
157    public void setInput(Object input) {
158        this.input = input;
159    }
160
161    public Object getInput() {
162        return input;
163    }
164
165    public Object peek(String type) {
166        List<Object> stack = stacks.get(type);
167        if (stack == null) {
168            return null;
169        }
170        return stack.isEmpty() ? null : stack.get(stack.size() - 1);
171    }
172
173    public void push(String type, Object obj) {
174        List<Object> stack = stacks.get(type);
175        if (stack == null) {
176            stack = new ArrayList<Object>();
177            stacks.put(type, stack);
178        }
179        stack.add(obj);
180    }
181
182    public Object pop(String type) {
183        List<Object> stack = stacks.get(type);
184        if (stack == null) {
185            return null;
186        }
187        return stack.isEmpty() ? null : stack.remove(stack.size() - 1);
188    }
189
190    public Object pull(String type) {
191        List<Object> stack = stacks.get(type);
192        if (stack == null) {
193            return null;
194        }
195        return stack.isEmpty() ? null : stack.remove(0);
196    }
197
198    public <T> T getAdapter(Class<T> type) {
199        if (type.isAssignableFrom(getClass())) {
200            return type.cast(this);
201        } else if (type.isAssignableFrom(CoreSession.class)) {
202            return type.cast(getCoreSession());
203        } else if (type.isAssignableFrom(Principal.class)) {
204            return type.cast(getPrincipal());
205        } else { // try nuxeo services
206            return Framework.getService(type);
207        }
208    }
209
210    /**
211     * since 5.7.3 #addTrace is no longer useful for tracing. Use chain call backs to do it.
212     */
213    @Deprecated
214    public void addTrace(String trace) {
215        this.trace.add(trace);
216    }
217
218    /**
219     * since 5.7.3 #getTrace is no longer useful for tracing. Use chain call backs to do it.
220     */
221    @Deprecated
222    public List<String> getTrace() {
223        return trace;
224    }
225
226    /**
227     * since 5.7.3 #getFormattedTrace is no longer useful for tracing. Use chain call backs to do it.
228     */
229    @Deprecated
230    public String getFormattedTrace() {
231        String crlf = System.getProperty("line.separator");
232        StringBuilder buf = new StringBuilder();
233        for (String t : trace) {
234            buf.append("> ").append(t).append(crlf);
235        }
236        return buf.toString();
237    }
238
239    public void addCleanupHandler(CleanupHandler handler) {
240        cleanupHandlers.add(handler);
241    }
242
243    public void removeCleanupHandler(CleanupHandler handler) {
244        cleanupHandlers.remove(handler);
245    }
246
247    public void dispose() throws OperationException {
248        trace.clear();
249        loginStack.clear();
250        for (CleanupHandler handler : cleanupHandlers) {
251            handler.cleanup();
252        }
253    }
254
255    /**
256     * Set the rollback mark on the current tx. This will cause the transaction to rollback. Also this is setting the
257     * session commit flag on false
258     */
259    public void setRollback() {
260        setCommit(false);
261        TransactionHelper.setTransactionRollbackOnly();
262    }
263
264    public Map<String, Object> getVars() {
265        return vars;
266    }
267
268    /** the map API */
269
270    @Override
271    public int size() {
272        return vars.size();
273    }
274
275    @Override
276    public boolean isEmpty() {
277        return vars.isEmpty();
278    }
279
280    @Override
281    public boolean containsKey(Object key) {
282        return vars.containsKey(key);
283    }
284
285    @Override
286    public boolean containsValue(Object value) {
287        return vars.containsValue(value);
288    }
289
290    @Override
291    public Object get(Object key) {
292        return vars.get(key);
293    }
294
295    @Override
296    public Object put(String key, Object value) {
297        return vars.put(key, value);
298    }
299
300    @Override
301    public Object remove(Object key) {
302        return vars.remove(key);
303    }
304
305    @Override
306    public void putAll(Map<? extends String, ? extends Object> m) {
307        vars.putAll(m);
308    }
309
310    @Override
311    public void clear() {
312        vars.clear();
313    }
314
315    @Override
316    public Set<String> keySet() {
317        return vars.keySet();
318    }
319
320    @Override
321    public Collection<Object> values() {
322        return vars.values();
323    }
324
325    @Override
326    public Set<java.util.Map.Entry<String, Object>> entrySet() {
327        return vars.entrySet();
328    }
329
330    /**
331     * ChainCallback store all automation traces for execution
332     *
333     * @since 5.7.3
334     */
335    protected static class ChainCallback implements OperationCallback {
336
337        public OperationCallback operationCallback;
338
339        protected void set(OperationCallback callback) {
340            operationCallback = callback;
341        }
342
343        @Override
344        public void onChain(OperationType chain) {
345            operationCallback.onChain(chain);
346        }
347
348        @Override
349        public void onOperation(OperationContext context, OperationType type, InvokableMethod method,
350                Map<String, Object> parms) {
351            operationCallback.onOperation(context, type, method, parms);
352        }
353
354        @Override
355        public void onError(OperationException error) {
356            operationCallback.onError(error);
357        }
358
359        @Override
360        public void onOutput(Object output) {
361            operationCallback.onOutput(output);
362        }
363
364        @Override
365        public Trace getTrace() {
366            return operationCallback.getTrace();
367        }
368
369        @Override
370        public String getFormattedText() {
371            throw new UnsupportedOperationException("#getFormattedText is not available for: " + this);
372        }
373
374    }
375
376    /**
377     * @since 5.7.3
378     */
379    public OperationCallback getChainCallback() {
380        return chainCallback.operationCallback;
381    }
382
383    /**
384     * @since 5.7.3
385     */
386    public void addChainCallback(OperationCallback chainCallback) {
387        this.chainCallback.set(chainCallback);
388    }
389
390    /**
391     * @since 5.7.3
392     * @param isolate define if keeps context variables for the subcontext
393     * @return a subcontext
394     */
395    public OperationContext getSubContext(Boolean isolate, Object input) {
396        Map<String, Object> vars = isolate ? new HashMap<String, Object>(getVars()) : getVars();
397        OperationContext subctx = new OperationContext(getCoreSession(), vars);
398        subctx.setInput(input);
399        subctx.addChainCallback(getChainCallback());
400        return subctx;
401    }
402}