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(CoreSession session) { 103 this(session, null); 104 } 105 106 public OperationContext(CoreSession session, Map<String, Object> vars) { 107 stacks = new HashMap<String, List<Object>>(); 108 cleanupHandlers = new ArrayList<CleanupHandler>(); 109 loginStack = new LoginStack(session); 110 trace = new ArrayList<String>(); 111 chainCallback = new ChainCallback(); 112 this.vars = vars != null ? vars : new HashMap<String, Object>(); 113 } 114 115 public void setCoreSession(CoreSession session) { 116 loginStack.setSession(session); 117 } 118 119 public void setCommit(boolean commit) { 120 this.commit = commit; 121 } 122 123 public boolean isCommit() { 124 return commit; 125 } 126 127 public CoreSession getCoreSession() { 128 return loginStack.getSession(); 129 } 130 131 public LoginStack getLoginStack() { 132 return loginStack; 133 } 134 135 public Principal getPrincipal() { 136 CoreSession session = loginStack.getSession(); 137 return session != null ? session.getPrincipal() : null; 138 } 139 140 public void setInput(Object input) { 141 this.input = input; 142 } 143 144 public Object getInput() { 145 return input; 146 } 147 148 public Object peek(String type) { 149 List<Object> stack = stacks.get(type); 150 if (stack == null) { 151 return null; 152 } 153 return stack.isEmpty() ? null : stack.get(stack.size() - 1); 154 } 155 156 public void push(String type, Object obj) { 157 List<Object> stack = stacks.get(type); 158 if (stack == null) { 159 stack = new ArrayList<Object>(); 160 stacks.put(type, stack); 161 } 162 stack.add(obj); 163 } 164 165 public Object pop(String type) { 166 List<Object> stack = stacks.get(type); 167 if (stack == null) { 168 return null; 169 } 170 return stack.isEmpty() ? null : stack.remove(stack.size() - 1); 171 } 172 173 public Object pull(String type) { 174 List<Object> stack = stacks.get(type); 175 if (stack == null) { 176 return null; 177 } 178 return stack.isEmpty() ? null : stack.remove(0); 179 } 180 181 public <T> T getAdapter(Class<T> type) { 182 if (type.isAssignableFrom(getClass())) { 183 return type.cast(this); 184 } else if (type.isAssignableFrom(CoreSession.class)) { 185 return type.cast(getCoreSession()); 186 } else if (type.isAssignableFrom(Principal.class)) { 187 return type.cast(getPrincipal()); 188 } else { // try nuxeo services 189 return Framework.getService(type); 190 } 191 } 192 193 /** 194 * since 5.7.3 #addTrace is no longer useful for tracing. Use chain call backs to do it. 195 */ 196 @Deprecated 197 public void addTrace(String trace) { 198 this.trace.add(trace); 199 } 200 201 /** 202 * since 5.7.3 #getTrace is no longer useful for tracing. Use chain call backs to do it. 203 */ 204 @Deprecated 205 public List<String> getTrace() { 206 return trace; 207 } 208 209 /** 210 * since 5.7.3 #getFormattedTrace is no longer useful for tracing. Use chain call backs to do it. 211 */ 212 @Deprecated 213 public String getFormattedTrace() { 214 String crlf = System.getProperty("line.separator"); 215 StringBuilder buf = new StringBuilder(); 216 for (String t : trace) { 217 buf.append("> ").append(t).append(crlf); 218 } 219 return buf.toString(); 220 } 221 222 public void addCleanupHandler(CleanupHandler handler) { 223 cleanupHandlers.add(handler); 224 } 225 226 public void removeCleanupHandler(CleanupHandler handler) { 227 cleanupHandlers.remove(handler); 228 } 229 230 public void dispose() throws OperationException { 231 trace.clear(); 232 loginStack.clear(); 233 for (CleanupHandler handler : cleanupHandlers) { 234 handler.cleanup(); 235 } 236 } 237 238 /** 239 * Set the rollback mark on the current tx. This will cause the transaction to rollback. Also this is setting the 240 * session commit flag on false 241 */ 242 public void setRollback() { 243 setCommit(false); 244 TransactionHelper.setTransactionRollbackOnly(); 245 } 246 247 public Map<String, Object> getVars() { 248 return vars; 249 } 250 251 /** the map API */ 252 253 @Override 254 public int size() { 255 return vars.size(); 256 } 257 258 @Override 259 public boolean isEmpty() { 260 return vars.isEmpty(); 261 } 262 263 @Override 264 public boolean containsKey(Object key) { 265 return vars.containsKey(key); 266 } 267 268 @Override 269 public boolean containsValue(Object value) { 270 return vars.containsValue(value); 271 } 272 273 @Override 274 public Object get(Object key) { 275 return vars.get(key); 276 } 277 278 @Override 279 public Object put(String key, Object value) { 280 return vars.put(key, value); 281 } 282 283 @Override 284 public Object remove(Object key) { 285 return vars.remove(key); 286 } 287 288 @Override 289 public void putAll(Map<? extends String, ? extends Object> m) { 290 vars.putAll(m); 291 } 292 293 @Override 294 public void clear() { 295 vars.clear(); 296 } 297 298 @Override 299 public Set<String> keySet() { 300 return vars.keySet(); 301 } 302 303 @Override 304 public Collection<Object> values() { 305 return vars.values(); 306 } 307 308 @Override 309 public Set<java.util.Map.Entry<String, Object>> entrySet() { 310 return vars.entrySet(); 311 } 312 313 /** 314 * ChainCallback store all automation traces for execution 315 * 316 * @since 5.7.3 317 */ 318 protected static class ChainCallback implements OperationCallback { 319 320 public OperationCallback operationCallback; 321 322 protected void set(OperationCallback callback) { 323 operationCallback = callback; 324 } 325 326 @Override 327 public void onChain(OperationType chain) { 328 operationCallback.onChain(chain); 329 } 330 331 @Override 332 public void onOperation(OperationContext context, OperationType type, InvokableMethod method, 333 Map<String, Object> parms) { 334 operationCallback.onOperation(context, type, method, parms); 335 } 336 337 @Override 338 public void onError(OperationException error) { 339 operationCallback.onError(error); 340 } 341 342 @Override 343 public void onOutput(Object output) { 344 operationCallback.onOutput(output); 345 } 346 347 @Override 348 public Trace getTrace() { 349 return operationCallback.getTrace(); 350 } 351 352 @Override 353 public String getFormattedText() { 354 throw new UnsupportedOperationException("#getFormattedText is not available for: " + this); 355 } 356 357 } 358 359 /** 360 * @since 5.7.3 361 */ 362 public OperationCallback getChainCallback() { 363 return chainCallback.operationCallback; 364 } 365 366 /** 367 * @since 5.7.3 368 */ 369 public void addChainCallback(OperationCallback chainCallback) { 370 this.chainCallback.set(chainCallback); 371 } 372 373 /** 374 * @since 5.7.3 375 * @param isolate define if keeps context variables for the subcontext 376 * @return a subcontext 377 */ 378 public OperationContext getSubContext(Boolean isolate, Object input) { 379 Map<String, Object> vars = isolate ? new HashMap<String, Object>(getVars()) : getVars(); 380 OperationContext subctx = new OperationContext(getCoreSession(), vars); 381 subctx.setInput(input); 382 subctx.addChainCallback(getChainCallback()); 383 return subctx; 384 } 385}