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}