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