Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
// Copyright 2011 Google 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.google.appengine.tools.pipeline.impl;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.taskqueue.TaskAlreadyExistsException;
import com.google.appengine.tools.pipeline.FutureList;
import com.google.appengine.tools.pipeline.ImmediateValue;
import com.google.appengine.tools.pipeline.Job;
import com.google.appengine.tools.pipeline.Job0;
import com.google.appengine.tools.pipeline.JobSetting;
import com.google.appengine.tools.pipeline.NoSuchObjectException;
import com.google.appengine.tools.pipeline.OrphanedObjectException;
import com.google.appengine.tools.pipeline.Value;
import com.google.appengine.tools.pipeline.impl.backend.AppEngineBackEnd;
import com.google.appengine.tools.pipeline.impl.backend.PipelineBackEnd;
import com.google.appengine.tools.pipeline.impl.backend.UpdateSpec;
import com.google.appengine.tools.pipeline.impl.backend.UpdateSpec.Group;
import com.google.appengine.tools.pipeline.impl.model.Barrier;
import com.google.appengine.tools.pipeline.impl.model.ExceptionRecord;
import com.google.appengine.tools.pipeline.impl.model.JobInstanceRecord;
import com.google.appengine.tools.pipeline.impl.model.JobRecord;
import com.google.appengine.tools.pipeline.impl.model.JobRecord.InflationType;
import com.google.appengine.tools.pipeline.impl.model.JobRecord.State;
import com.google.appengine.tools.pipeline.impl.model.PipelineObjects;
import com.google.appengine.tools.pipeline.impl.model.Slot;
import com.google.appengine.tools.pipeline.impl.model.SlotDescriptor;
import com.google.appengine.tools.pipeline.impl.servlets.PipelineServlet;
import com.google.appengine.tools.pipeline.impl.tasks.CancelJobTask;
import com.google.appengine.tools.pipeline.impl.tasks.DelayedSlotFillTask;
import com.google.appengine.tools.pipeline.impl.tasks.DeletePipelineTask;
import com.google.appengine.tools.pipeline.impl.tasks.FanoutTask;
import com.google.appengine.tools.pipeline.impl.tasks.FinalizeJobTask;
import com.google.appengine.tools.pipeline.impl.tasks.HandleChildExceptionTask;
import com.google.appengine.tools.pipeline.impl.tasks.HandleSlotFilledTask;
import com.google.appengine.tools.pipeline.impl.tasks.RunJobTask;
import com.google.appengine.tools.pipeline.impl.tasks.Task;
import com.google.appengine.tools.pipeline.impl.util.GUIDGenerator;
import com.google.appengine.tools.pipeline.impl.util.StringUtils;
import com.google.appengine.tools.pipeline.util.Pair;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* The central hub of the Pipeline implementation.
*
* @author [email protected] (Mitch Rudominer)
*
*/
public class PipelineManager {
private static final Logger logger = Logger.getLogger(PipelineManager.class.getName());
private static PipelineBackEnd backEnd = new AppEngineBackEnd();
/**
* Creates and launches a new Pipeline
*
* Creates the root Job with its associated Barriers and Slots and saves them
* to the data store. All slots in the root job are immediately filled with
* the values of the parameters to this method, and
* {@link HandleSlotFilledTask HandleSlotFilledTasks} are enqueued for each of
* the filled slots.
*
* @param settings JobSetting array used to control details of the Pipeline
* @param jobInstance A user-supplied instance of {@link Job} that will serve
* as the root job of the Pipeline.
* @param params Arguments to the root job's run() method
* @return The pipelineID of the newly created pipeline, also known as the
* rootJobID.
*/
public static String startNewPipeline(
JobSetting[] settings, Job> jobInstance, Object... params) {
UpdateSpec updateSpec = new UpdateSpec(null);
Job> rootJobInstance = jobInstance;
// If rootJobInstance has exceptionHandler it has to be wrapped to ensure that root job
// ends up in finalized state in case of exception of run method and
// exceptionHandler returning a result.
if (JobRecord.isExceptionHandlerSpecified(jobInstance)) {
rootJobInstance = new RootJobInstance(jobInstance, settings, params);
params = new Object[0];
}
// Create the root Job and its associated Barriers and Slots
// Passing null for parent JobRecord and graphGUID
// Create HandleSlotFilledTasks for the input parameters.
JobRecord jobRecord = registerNewJobRecord(
updateSpec, settings, null, null, rootJobInstance, params);
updateSpec.setRootJobKey(jobRecord.getRootJobKey());
// Save the Pipeline model objects and enqueue the tasks that start the Pipeline executing.
backEnd.save(updateSpec, jobRecord.getQueueSettings());
return jobRecord.getKey().getName();
}
/**
* Creates a new JobRecord with its associated Barriers and Slots. Also
* creates new {@link HandleSlotFilledTask} for any inputs to the Job that are
* immediately specified. Registers all newly created objects with the
* provided {@code UpdateSpec} for later saving.
*
* This method is called when starting a new Pipeline, in which case it is
* used to create the root job, and it is called from within the run() method
* of a generator job in order to create a child job.
*
* @param updateSpec The {@code UpdateSpec} with which to register all newly
* created objects. All objects will be added to the
* {@link UpdateSpec#getNonTransactionalGroup() non-transaction group}
* of the {@code UpdateSpec}.
* @param settings Array of {@code JobSetting} to apply to the newly created
* JobRecord.
* @param generatorJob The generator job or {@code null} if we are creating
* the root job.
* @param graphGUID The GUID of the child graph to which the new Job belongs
* or {@code null} if we are creating the root job.
* @param jobInstance The user-supplied instance of {@code Job} that
* implements the Job that the newly created JobRecord represents.
* @param params The arguments to be passed to the run() method of the newly
* created Job. Each argument may be an actual value or it may be an
* object of type {@link Value} representing either an
* {@link ImmediateValue} or a
* {@link com.google.appengine.tools.pipeline.FutureValue FutureValue}.
* For each element of the array, if the Object is not of type
* {@link Value} then it is interpreted as an {@link ImmediateValue}
* with the given Object as its value.
* @return The newly constructed JobRecord.
*/
public static JobRecord registerNewJobRecord(UpdateSpec updateSpec, JobSetting[] settings,
JobRecord generatorJob, String graphGUID, Job> jobInstance, Object[] params) {
JobRecord jobRecord;
if (generatorJob == null) {
// Root Job
if (graphGUID != null) {
throw new IllegalArgumentException("graphGUID must be null for root jobs");
}
jobRecord = JobRecord.createRootJobRecord(jobInstance, settings);
} else {
jobRecord = new JobRecord(generatorJob, graphGUID, jobInstance, false, settings);
}
return registerNewJobRecord(updateSpec, jobRecord, params);
}
public static JobRecord registerNewJobRecord(UpdateSpec updateSpec, JobRecord jobRecord,
Object[] params) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("registerNewJobRecord job(\"" + jobRecord + "\")");
}
updateSpec.setRootJobKey(jobRecord.getRootJobKey());
Key generatorKey = jobRecord.getGeneratorJobKey();
// Add slots to the RunBarrier corresponding to the input parameters
String graphGuid = jobRecord.getGraphGuid();
for (Object param : params) {
Value> value;
if (null != param && param instanceof Value>) {
value = (Value>) param;
} else {
value = new ImmediateValue<>(param);
}
registerSlotsWithBarrier(updateSpec, value, jobRecord.getRootJobKey(), generatorKey,
jobRecord.getQueueSettings(), graphGuid, jobRecord.getRunBarrierInflated());
}
if (0 == jobRecord.getRunBarrierInflated().getWaitingOnKeys().size()) {
// If the run barrier is not waiting on anything, add a phantom filled
// slot in order to trigger a HandleSlotFilledTask in order to trigger
// a RunJobTask.
Slot slot = new Slot(jobRecord.getRootJobKey(), generatorKey, graphGuid);
jobRecord.getRunBarrierInflated().addPhantomArgumentSlot(slot);
registerSlotFilled(updateSpec, jobRecord.getQueueSettings(), slot, null);
}
// Register the newly created objects with the UpdateSpec.
// The slots in the run Barrier have already been registered
// and the finalize Barrier doesn't have any slots yet.
// Any HandleSlotFilledTasks have also been registered already.
Group updateGroup = updateSpec.getNonTransactionalGroup();
updateGroup.includeBarrier(jobRecord.getRunBarrierInflated());
updateGroup.includeBarrier(jobRecord.getFinalizeBarrierInflated());
updateGroup.includeSlot(jobRecord.getOutputSlotInflated());
updateGroup.includeJob(jobRecord);
updateGroup.includeJobInstanceRecord(jobRecord.getJobInstanceInflated());
return jobRecord;
}
/**
* Given a {@code Value} and a {@code Barrier}, we add one or more slots to
* the waitingFor list of the barrier corresponding to the {@code Value}. We
* also create {@link HandleSlotFilledTask HandleSlotFilledTasks} for all
* filled slots. We register all newly created Slots and Tasks with the given
* {@code UpdateSpec}.
*
* If the value is an {@code ImmediateValue} we make a new slot, register it
* as filled and add it. If the value is a {@code FutureValue} we add the slot
* wrapped by the {@code FutreValueImpl}. If the value is a {@code FutureList}
* then we add multiple slots, one for each {@code Value} in the List wrapped
* by the {@code FutureList}. This process is not recursive because we do not
* currently support {@code FutureLists} of {@code FutureLists}.
*
* @param updateSpec All newly created Slots will be added to the
* {@link UpdateSpec#getNonTransactionalGroup() non-transactional
* group} of the updateSpec. All {@link HandleSlotFilledTask
* HandleSlotFilledTasks} created will be added to the
* {@link UpdateSpec#getFinalTransaction() final transaction}. Note
* that {@code barrier} will not be added to updateSpec. That must be
* done by the caller.
* @param value A {@code Value}. {@code Null} is interpreted as an
* {@code ImmediateValue} with a value of {@code Null}.
* @param rootJobKey The rootJobKey of the Pipeline in which the given Barrier
* lives.
* @param generatorJobKey The key of the generator Job of the local graph in
* which the given barrier lives, or {@code null} if the barrier lives
* in the root Job graph.
* @param queueSettings The QueueSettings for tasks created by this method
* @param graphGUID The GUID of the local graph in which the barrier lives, or
* {@code null} if the barrier lives in the root Job graph.
* @param barrier The barrier to which we will add the slots
*/
private static void registerSlotsWithBarrier(UpdateSpec updateSpec, Value> value,
Key rootJobKey, Key generatorJobKey, QueueSettings queueSettings, String graphGUID,
Barrier barrier) {
if (null == value || value instanceof ImmediateValue>) {
Object concreteValue = null;
if (null != value) {
ImmediateValue> iv = (ImmediateValue>) value;
concreteValue = iv.getValue();
}
Slot slot = new Slot(rootJobKey, generatorJobKey, graphGUID);
registerSlotFilled(updateSpec, queueSettings, slot, concreteValue);
barrier.addRegularArgumentSlot(slot);
} else if (value instanceof FutureValueImpl>) {
FutureValueImpl> futureValue = (FutureValueImpl>) value;
Slot slot = futureValue.getSlot();
barrier.addRegularArgumentSlot(slot);
updateSpec.getNonTransactionalGroup().includeSlot(slot);
} else if (value instanceof FutureList>) {
FutureList> futureList = (FutureList>) value;
List slotList = new ArrayList<>(futureList.getListOfValues().size());
// The dummyListSlot is a marker slot that indicates that the
// next group of slots forms a single list argument.
Slot dummyListSlot = new Slot(rootJobKey, generatorJobKey, graphGUID);
registerSlotFilled(updateSpec, queueSettings, dummyListSlot, null);
for (Value> valFromList : futureList.getListOfValues()) {
Slot slot = null;
if (valFromList instanceof ImmediateValue>) {
ImmediateValue> ivFromList = (ImmediateValue>) valFromList;
slot = new Slot(rootJobKey, generatorJobKey, graphGUID);
registerSlotFilled(updateSpec, queueSettings, slot, ivFromList.getValue());
} else if (valFromList instanceof FutureValueImpl>) {
FutureValueImpl> futureValFromList = (FutureValueImpl>) valFromList;
slot = futureValFromList.getSlot();
} else if (value instanceof FutureList>) {
throw new IllegalArgumentException(
"The Pipeline framework does not currently support FutureLists of FutureLists");
} else {
throwUnrecognizedValueException(valFromList);
}
slotList.add(slot);
updateSpec.getNonTransactionalGroup().includeSlot(slot);
}
barrier.addListArgumentSlots(dummyListSlot, slotList);
} else {
throwUnrecognizedValueException(value);
}
}
private static void throwUnrecognizedValueException(Value> value) {
throw new RuntimeException(
"Internal logic error: Unrecognized implementation of Value interface: "
+ value.getClass().getName());
}
/**
* Given a Slot and a concrete value with which to fill the slot, we fill the
* value with the slot and create a new {@link HandleSlotFilledTask} for the
* newly filled Slot. We register the Slot and the Task with the given
* UpdateSpec for later saving.
*
* @param updateSpec The Slot will be added to the
* {@link UpdateSpec#getNonTransactionalGroup() non-transactional
* group} of the updateSpec. The new {@link HandleSlotFilledTask} will
* be added to the {@link UpdateSpec#getFinalTransaction() final
* transaction}.
* @param queueSettings queue settings for the created task
* @param slot the Slot to fill
* @param value the value with which to fill it
*/
private static void registerSlotFilled(
UpdateSpec updateSpec, QueueSettings queueSettings, Slot slot, Object value) {
slot.fill(value);
updateSpec.getNonTransactionalGroup().includeSlot(slot);
Task task = new HandleSlotFilledTask(slot.getKey(), queueSettings);
updateSpec.getFinalTransaction().registerTask(task);
}
/**
* Returns all the associated PipelineModelObject for a root pipeline.
*
* @throws IllegalArgumentException if root pipeline was not found.
*/
public static PipelineObjects queryFullPipeline(String rootJobHandle) {
Key rootJobKey = KeyFactory.createKey(JobRecord.DATA_STORE_KIND, rootJobHandle);
return backEnd.queryFullPipeline(rootJobKey);
}
public static Pair extends Iterable, String> queryRootPipelines(
String classFilter, String cursor, int limit) {
return backEnd.queryRootPipelines(classFilter, cursor, limit);
}
public static Set getRootPipelinesDisplayName() {
return backEnd.getRootPipelinesDisplayName();
}
private static void checkNonEmpty(String s, String name) {
if (null == s || s.trim().length() == 0) {
throw new IllegalArgumentException(name + " is empty.");
}
}
/**
* Retrieves a JobRecord for the specified job handle. The returned instance
* will be only partially inflated. The run and finalize barriers will not be
* available but the output slot will be.
*
* @param jobHandle The handle of a job.
* @return The corresponding JobRecord
* @throws NoSuchObjectException If a JobRecord with the given handle cannot
* be found in the data store.
*/
public static JobRecord getJob(String jobHandle) throws NoSuchObjectException {
checkNonEmpty(jobHandle, "jobHandle");
Key key = KeyFactory.createKey(JobRecord.DATA_STORE_KIND, jobHandle);
logger.finest("getJob: " + key.getName());
return backEnd.queryJob(key, JobRecord.InflationType.FOR_OUTPUT);
}
/**
* Changes the state of the specified job to STOPPED.
*
* @param jobHandle The handle of a job
* @throws NoSuchObjectException If a JobRecord with the given handle cannot
* be found in the data store.
*/
public static void stopJob(String jobHandle) throws NoSuchObjectException {
checkNonEmpty(jobHandle, "jobHandle");
Key key = KeyFactory.createKey(JobRecord.DATA_STORE_KIND, jobHandle);
JobRecord jobRecord = backEnd.queryJob(key, JobRecord.InflationType.NONE);
jobRecord.setState(State.STOPPED);
UpdateSpec updateSpec = new UpdateSpec(jobRecord.getRootJobKey());
updateSpec.getOrCreateTransaction("stopJob").includeJob(jobRecord);
backEnd.save(updateSpec, jobRecord.getQueueSettings());
}
/**
* Sends cancellation request to the root job.
*
* @param jobHandle The handle of a job
* @throws NoSuchObjectException If a JobRecord with the given handle cannot
* be found in the data store.
*/
public static void cancelJob(String jobHandle) throws NoSuchObjectException {
checkNonEmpty(jobHandle, "jobHandle");
Key key = KeyFactory.createKey(JobRecord.DATA_STORE_KIND, jobHandle);
JobRecord jobRecord = backEnd.queryJob(key, InflationType.NONE);
CancelJobTask cancelJobTask = new CancelJobTask(key, jobRecord.getQueueSettings());
try {
backEnd.enqueue(cancelJobTask);
} catch (TaskAlreadyExistsException e) {
// OK. Some other thread has already enqueued this task.
}
}
/**
* Delete all data store entities corresponding to the given pipeline.
*
* @param pipelineHandle The handle of the pipeline to be deleted
* @param force If this parameter is not {@code true} then this method will
* throw an {@link IllegalStateException} if the specified pipeline is
* not in the {@link State#FINALIZED} or {@link State#STOPPED} state.
* @param async If this parameter is {@code true} then instead of performing
* the delete operation synchronously, this method will enqueue a task
* to perform the operation.
* @throws NoSuchObjectException If there is no Job with the given key.
* @throws IllegalStateException If {@code force = false} and the specified
* pipeline is not in the {@link State#FINALIZED} or
* {@link State#STOPPED} state.
*/
public static void deletePipelineRecords(String pipelineHandle, boolean force, boolean async)
throws NoSuchObjectException, IllegalStateException {
checkNonEmpty(pipelineHandle, "pipelineHandle");
Key key = KeyFactory.createKey(JobRecord.DATA_STORE_KIND, pipelineHandle);
backEnd.deletePipeline(key, force, async);
}
public static void acceptPromisedValue(String promiseHandle, Object value)
throws NoSuchObjectException, OrphanedObjectException {
checkNonEmpty(promiseHandle, "promiseHandle");
Key key = KeyFactory.stringToKey(promiseHandle);
Slot slot = null;
// It is possible, though unlikely, that we might be asked to accept a
// promise before the slot to hold the promise has been saved. We will try 5
// times, sleeping 1, 2, 4, 8 seconds between attempts.
int attempts = 0;
boolean interrupted = false;
try {
while (slot == null) {
attempts++;
try {
slot = backEnd.querySlot(key, false);
} catch (NoSuchObjectException e) {
if (attempts >= 5) {
throw new NoSuchObjectException("There is no promise with handle " + promiseHandle);
}
try {
Thread.sleep((long) Math.pow(2.0, attempts - 1) * 1000L);
} catch (InterruptedException f) {
interrupted = true;
}
}
}
} finally {
// TODO(user): replace with Uninterruptibles#sleepUninterruptibly once we use guava
if (interrupted) {
Thread.currentThread().interrupt();
}
}
Key generatorJobKey = slot.getGeneratorJobKey();
if (null == generatorJobKey) {
throw new RuntimeException(
"Pipeline is fatally corrupted. Slot for promised value has no generatorJobKey: " + slot);
}
JobRecord generatorJob = backEnd.queryJob(generatorJobKey, JobRecord.InflationType.NONE);
if (null == generatorJob) {
throw new RuntimeException("Pipeline is fatally corrupted. "
+ "The generator job for a promised value slot was not found: " + generatorJobKey);
}
String childGraphGuid = generatorJob.getChildGraphGuid();
if (null == childGraphGuid) {
// The generator job has not been saved with a childGraphGuid yet. This can happen if the
// promise handle leaked out to an external thread before the job that generated it
// had finished.
throw new NoSuchObjectException(
"The framework is not ready to accept the promised value yet. "
+ "Please try again after the job that generated the promis handle has completed.");
}
if (!childGraphGuid.equals(slot.getGraphGuid())) {
// The slot has been orphaned
throw new OrphanedObjectException(promiseHandle);
}
UpdateSpec updateSpec = new UpdateSpec(slot.getRootJobKey());
registerSlotFilled(updateSpec, generatorJob.getQueueSettings(), slot, value);
backEnd.save(updateSpec, generatorJob.getQueueSettings());
}
/**
* The root job instance used to wrap a user provided root if the user
* provided root job has exceptionHandler specified.
*/
private static final class RootJobInstance extends Job0