brooklyn.util.task.BasicExecutionContext.java Source code

Java tutorial

Introduction

Here is the source code for brooklyn.util.task.BasicExecutionContext.java

Source

/*
 * 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 brooklyn.util.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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brooklyn.entity.Entity;
import brooklyn.entity.basic.BrooklynTaskTags;
import brooklyn.entity.basic.BrooklynTaskTags.WrappedEntity;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.management.ExecutionContext;
import brooklyn.management.ExecutionManager;
import brooklyn.management.HasTaskChildren;
import brooklyn.management.Task;
import brooklyn.management.TaskAdaptable;
import brooklyn.management.entitlement.EntitlementContext;
import brooklyn.management.entitlement.Entitlements;

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().name("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().name("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);

        final Object startCallback = properties.get("newTaskStartCallback");
        properties.put("newTaskStartCallback", new Function<Object, Void>() {
            public Void apply(Object it) {
                registerPerThreadExecutionContext();
                if (startCallback != null)
                    ExecutionUtils.invoke(startCallback, it);
                return null;
            }
        });

        final Object endCallback = properties.get("newTaskEndCallback");
        properties.put("newTaskEndCallback", new Function<Object, Void>() {
            public Void apply(Object it) {
                try {
                    if (endCallback != null)
                        ExecutionUtils.invoke(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 + ")";
    }
}