/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.brooklyn.util.core.task; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.ExecutionManager; import org.apache.brooklyn.api.mgmt.HasTaskChildren; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.mgmt.TaskAdaptable; import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext; import org.apache.brooklyn.core.entity.EntityInternal; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags; import org.apache.brooklyn.core.mgmt.BrooklynTaskTags.WrappedEntity; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.collect.Iterables; /** * A means of executing tasks against an ExecutionManager with a given bucket/set of tags pre-defined * (so that it can look like an {@link Executor} and also supply {@link ExecutorService#submit(Callable)} */ public class BasicExecutionContext extends AbstractExecutionContext { private static final Logger log = LoggerFactory.getLogger(BasicExecutionContext.class); static final ThreadLocal<BasicExecutionContext> perThreadExecutionContext = new ThreadLocal<BasicExecutionContext>(); public static BasicExecutionContext getCurrentExecutionContext() { return perThreadExecutionContext.get(); } final ExecutionManager executionManager; final Set<Object> tags = new LinkedHashSet<Object>(); public BasicExecutionContext(ExecutionManager executionManager) { this(Collections.emptyMap(), executionManager); } /** * Supported flags are {@code tag} and {@code tags} * * @see ExecutionManager#submit(Map, Task) */ public BasicExecutionContext(Map<?, ?> flags, ExecutionManager executionManager) { this.executionManager = executionManager; if (flags.get("tag") != null) tags.add(flags.remove("tag")); if (flags.containsKey("tags")) tags.addAll((Collection<?>)flags.remove("tags")); // FIXME brooklyn-specific check, just for sanity // the context tag should always be a non-proxy entity, because that is what is passed to effector tasks // which may require access to internal methods for (Object tag: tags) { if (tag instanceof BrooklynTaskTags.WrappedEntity) { if (Proxy.isProxyClass(((WrappedEntity)tag).entity.getClass())) { log.warn(""+this+" has entity proxy in "+tag); } } } } public ExecutionManager getExecutionManager() { return executionManager; } /** returns tasks started by this context (or tasks which have all the tags on this object) */ public Set<Task<?>> getTasks() { return executionManager.getTasksWithAllTags((Set<?>)tags); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected <T> Task<T> submitInternal(Map<?,?> propertiesQ, final Object task) { if (task instanceof TaskAdaptable<?> && !(task instanceof Task<?>)) return submitInternal(propertiesQ, ((TaskAdaptable<?>)task).asTask()); Map properties = propertiesQ; if (properties.get("tags")==null) properties.put("tags", new ArrayList()); Collection taskTags = (Collection)properties.get("tags"); // FIXME some of this is brooklyn-specific logic, should be moved to a BrooklynExecContext subclass; // the issue is that we want to ensure that cross-entity calls switch execution contexts; // previously it was all very messy how that was handled (and it didn't really handle it in many cases) if (task instanceof Task<?>) taskTags.addAll( ((Task<?>)task).getTags() ); Entity target = BrooklynTaskTags.getWrappedEntityOfType(taskTags, BrooklynTaskTags.TARGET_ENTITY); if (target!=null && !tags.contains(BrooklynTaskTags.tagForContextEntity(target))) { // task is switching execution context boundaries /* * longer notes: * you fall in to this block if the caller requests a target entity different to the current context * (e.g. where entity X is invoking an effector on Y, it will start in X's context, * but the effector should run in Y's context). * * if X is invoking an effector on himself in his own context, or a sensor or other task, it will not come in to this block. */ final ExecutionContext tc = ((EntityInternal)target).getExecutionContext(); if (log.isDebugEnabled()) log.debug("Switching task context on execution of "+task+": from "+this+" to "+target+" (in "+Tasks.current()+")"); if (task instanceof Task<?>) { final Task<T> t = (Task<T>)task; if (!Tasks.isQueuedOrSubmitted(t) && (!(Tasks.current() instanceof HasTaskChildren) || !Iterables.contains( ((HasTaskChildren)Tasks.current()).getChildren(), t ))) { // this task is switching execution context boundaries _and_ it is not a child and not yet queued, // so wrap it in a task running in this context to keep a reference to the child // (this matters when we are navigating in the GUI; without it we lose the reference to the child // when browsing in the context of the parent) return submit(Tasks.<T>builder().displayName("Cross-context execution: "+t.getDescription()).dynamic(true).body(new Callable<T>() { public T call() { return DynamicTasks.get(t); } }).build()); } else { // if we are already tracked by parent, just submit it return tc.submit(t); } } else { // as above, but here we are definitely not a child (what we are submitting isn't even a task) // (will only come here if properties defines tags including a target entity, which probably never happens) submit(Tasks.<T>builder().displayName("Cross-context execution").dynamic(true).body(new Callable<T>() { public T call() { if (task instanceof Callable) { return DynamicTasks.queue( Tasks.<T>builder().dynamic(false).body((Callable<T>)task).build() ).getUnchecked(); } else if (task instanceof Runnable) { return DynamicTasks.queue( Tasks.<T>builder().dynamic(false).body((Runnable)task).build() ).getUnchecked(); } else { throw new IllegalArgumentException("Unhandled task type: "+task+"; type="+(task!=null ? task.getClass() : "null")); } } }).build()); } } EntitlementContext entitlementContext = BrooklynTaskTags.getEntitlement(taskTags); if (entitlementContext==null) entitlementContext = Entitlements.getEntitlementContext(); if (entitlementContext!=null) { taskTags.add(BrooklynTaskTags.tagForEntitlement(entitlementContext)); } taskTags.addAll(tags); if (Tasks.current()!=null && BrooklynTaskTags.isTransient(Tasks.current()) && !taskTags.contains(BrooklynTaskTags.NON_TRANSIENT_TASK_TAG) && !taskTags.contains(BrooklynTaskTags.TRANSIENT_TASK_TAG)) { // tag as transient if submitter is transient, unless explicitly tagged as non-transient taskTags.add(BrooklynTaskTags.TRANSIENT_TASK_TAG); } final Object startCallback = properties.get("newTaskStartCallback"); properties.put("newTaskStartCallback", new Function<Task<?>,Void>() { public Void apply(Task<?> it) { registerPerThreadExecutionContext(); if (startCallback!=null) BasicExecutionManager.invokeCallback(startCallback, it); return null; }}); final Object endCallback = properties.get("newTaskEndCallback"); properties.put("newTaskEndCallback", new Function<Task<?>,Void>() { public Void apply(Task<?> it) { try { if (endCallback!=null) BasicExecutionManager.invokeCallback(endCallback, it); } finally { clearPerThreadExecutionContext(); } return null; }}); if (task instanceof Task) { return executionManager.submit(properties, (Task)task); } else if (task instanceof Callable) { return executionManager.submit(properties, (Callable)task); } else if (task instanceof Runnable) { return (Task<T>) executionManager.submit(properties, (Runnable)task); } else { throw new IllegalArgumentException("Unhandled task type: task="+task+"; type="+(task!=null ? task.getClass() : "null")); } } private void registerPerThreadExecutionContext() { perThreadExecutionContext.set(this); } private void clearPerThreadExecutionContext() { perThreadExecutionContext.remove(); } @Override public boolean isShutdown() { return getExecutionManager().isShutdown(); } @Override public String toString() { return super.toString()+"("+tags+")"; } }