/*
* Copyright 2012 LinkedIn, Inc
*
* Licensed 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 com.linkedin.parseq.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import com.linkedin.parseq.promise.Promise;
import com.linkedin.parseq.promise.PromiseListener;
import com.linkedin.parseq.trace.ShallowTraceBuilder;
import com.linkedin.parseq.trace.TraceBuilder;
import com.linkedin.parseq.After;
import com.linkedin.parseq.Cancellable;
import com.linkedin.parseq.Context;
import com.linkedin.parseq.Exceptions;
import com.linkedin.parseq.Task;
/**
* @author Chris Pettitt (cpettitt@linkedin.com)
* @author Chi Chan (ckchan@linkedin.com)
*/
public class ContextImpl implements Context, Cancellable {
private static final Task<?> NO_PARENT = null;
private static final List<Task<?>> NO_PREDECESSORS = Collections.emptyList();
/**
* Plan level configuration and facilities.
*/
private final PlanContext _planContext;
private final Task<Object> _task;
// A thread local that holds the root task iff the root task is being executed
// on the current thread. In all other cases, this thread local will hold a
// null value.
private static final ThreadLocal<Task<?>> _inTask = new ThreadLocal<Task<?>>();
private final Task<?> _parent;
private final List<Task<?>> _predecessorTasks;
private final ConcurrentLinkedQueue<Cancellable> _cancellables = new ConcurrentLinkedQueue<Cancellable>();
public ContextImpl(final PlanContext planContext, final Task<?> task) {
this(planContext, task, NO_PARENT, NO_PREDECESSORS);
}
private ContextImpl(final PlanContext planContext, final Task<?> task, final Task<?> parent,
final List<Task<?>> predecessorTasks) {
_planContext = planContext;
_task = InternalUtil.unwildcardTask(task);
_parent = parent;
_predecessorTasks = predecessorTasks;
}
public void runTask() {
// Cancel everything created by this task once it finishes
_task.addListener(resolvedPromise -> {
for (Iterator<Cancellable> it = _cancellables.iterator(); it.hasNext();) {
final Cancellable cancellable = it.next();
cancellable.cancel(Exceptions.EARLY_FINISH_EXCEPTION);
it.remove();
}
});
_planContext.execute(new PrioritizableRunnable() {
@Override
public void run() {
_inTask.set(_task);
try {
_task.contextRun(ContextImpl.this, _parent, _predecessorTasks);
} finally {
_inTask.remove();
}
}
@Override
public int getPriority() {
return _task.getPriority();
}
});
}
@Override
public Cancellable createTimer(final long time, final TimeUnit unit, final Task<?> task) {
checkInTask();
final Cancellable cancellable = _planContext.schedule(time, unit, new Runnable() {
@Override
public void run() {
runSubTask(task, NO_PREDECESSORS);
}
});
_cancellables.add(cancellable);
return cancellable;
}
@Override
public void run(final Task<?>... tasks) {
checkInTask();
for (final Task<?> task : tasks) {
runSubTask(task, NO_PREDECESSORS);
}
}
@Override
public void runSideEffect(final Task<?>... tasks) {
checkInTask();
for (final Task<?> task : tasks) {
runSideEffectSubTask(task, NO_PREDECESSORS);
}
}
@Override
public After after(final Promise<?>... promises) {
checkInTask();
final List<Task<?>> tmpPredecessorTasks = new ArrayList<Task<?>>();
for (Promise<?> promise : promises) {
if (promise instanceof Task) {
tmpPredecessorTasks.add((Task<?>) promise);
}
}
final List<Task<?>> predecessorTasks = Collections.unmodifiableList(tmpPredecessorTasks);
return new After() {
@Override
public void run(final Task<?> task) {
InternalUtil.after(resolvedPromise -> runSubTask(task, predecessorTasks), promises);
}
@Override
public void run(final Supplier<Task<?>> taskSupplier) {
InternalUtil.after(resolvedPromise -> {
Task<?> task = taskSupplier.get();
if (task != null) {
runSubTask(task, predecessorTasks);
}
} , promises);
}
};
}
@Override
public boolean cancel(Exception reason) {
boolean result = _task.cancel(reason);
//run the task to capture the trace data
_task.contextRun(this, _parent, _predecessorTasks);
return result;
}
@Override
public Object getEngineProperty(String key) {
return _planContext.getEngineProperty(key);
}
private ContextImpl createSubContext(final Task<?> task, final List<Task<?>> predecessors) {
return new ContextImpl(_planContext, task, _task, predecessors);
}
private void runSubTask(final Task<?> task, final List<Task<?>> predecessors) {
final ContextImpl subContext = createSubContext(task, predecessors);
if (!isDone()) {
_cancellables.add(subContext);
subContext.runTask();
} else {
subContext.cancel(Exceptions.EARLY_FINISH_EXCEPTION);
}
}
private void runSideEffectSubTask(final Task<?> taskWrapper, final List<Task<?>> predecessors) {
PlanContext subPlan = _planContext.fork(taskWrapper);
if (subPlan != null) {
new ContextImpl(subPlan, taskWrapper, _task, predecessors).runTask();
} else {
taskWrapper.cancel(new IllegalStateException("Plan is already completed"));
}
}
private boolean isDone() {
return _task.isDone();
}
private void checkInTask() {
Task<?> t = _inTask.get();
if (t != _task) {
throw new IllegalStateException("Context method invoked while not in context's task");
}
}
@Override
public TraceBuilder getTraceBuilder() {
return _planContext.getRelationshipsBuilder();
}
@Override
public ShallowTraceBuilder getShallowTraceBuilder() {
return _task.getShallowTraceBuilder();
}
@Override
public Long getPlanId() {
return _planContext.getId();
}
public Long getTaskId() {
return _task.getId();
}
@Override
public TaskLogger getTaskLogger() {
return _planContext.getTaskLogger();
}
@Override
public String getPlanClass() {
return _planContext.getPlanClass();
}
}