Unverified Commit 17d39956 authored by Jay Billings's avatar Jay Billings Committed by GitHub

Merge pull request #457 from eclipse/jay/tasks

New Tasks infrastructure to replace Item
parents f57ec4ab eae4b42c
......@@ -29,6 +29,15 @@
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
......
/*******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Initial API and implementation and/or initial documentation -
* Jay Jay Billings
*******************************************************************************/
package org.eclipse.ice.renderer;
/**
*
* @author Jay Jay Billings
*
*/
public enum PersonEnum {
TALL,
SHORT
}
......@@ -9,24 +9,40 @@ import org.eclipse.ice.dev.annotations.Persisted;
public class PersonSpec {
/**
* The person's age.
*
* This doc will be copied to the implementation.
*/
@DataField.Default("42")
@DataField private int age;
/**
* The person's first name.
*
* This doc will be copied to the implementation.
*/
@DataField.Default(value = "Bob", isString = true)
@DataField private String firstName;
/**
* The person's last name.
*
* This doc will be copied to the implementation.
*/
@DataField.Default(value = "Builder", isString = true)
@DataField private String lastName;
/**
* An example constant value. This one probably doesn't actually make sense.
*
* This doc will be copied to the implementation.
*/
@DataField public static final String COLLECTION = "people";
/**
* Basic enumeration example. Note that the fully qualified value name needs
* to be added and the first time the enumeration is used the build needs
* to be run.
*/
@DataField.Default(value="org.eclipse.ice.renderer.PersonEnum.TALL");
@DataField public PersonEnum height;
}
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
......@@ -20,12 +20,7 @@
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
......@@ -36,12 +31,17 @@
</classpathentry>
<classpathentry kind="src" path="target/generated-sources/annotations">
<attributes>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="ignore_optional_problems" value="true"/>
<attribute name="m2e-apt" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
<attributes>
<attribute name="optional" value="true"/>
......
......@@ -6,7 +6,8 @@
<artifactId>org.eclipse.ice.tasks</artifactId>
<version>3.0.0-SNAPSHOT</version>
<name>Eclipse ICE Tasks</name>
<description>This package defines the interfaces and associated artifacts for tasks in Eclipse ICE</description>
<description>This package defines the interfaces and associated
artifacts for tasks in Eclipse ICE</description>
<properties>
......@@ -15,10 +16,26 @@
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.release>11</maven.compiler.release>
<org.springframework.version>5.2.7.RELEASE</org.springframework.version>
<spring-statemachine-version>2.2.0.RELEASE</spring-statemachine-version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>11</release>
<annotationProcessorPaths>
<path>
<groupId>org.eclipse.ice</groupId>
<artifactId>org.eclipse.ice.dev.annotations</artifactId>
<version>3.0.0-SNAPSHOT</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
......@@ -49,6 +66,38 @@
<version>3.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.ice</groupId>
<artifactId>org.eclipse.ice.dev.annotations</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<version>4.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-bom</artifactId>
<version>${spring-statemachine-version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
\ No newline at end of file
......@@ -40,7 +40,12 @@ public interface ActionType {
/**
* Actions of this type move files
*/
MOVE_FILE
MOVE_FILE,
/**
* Actions of this type are used for diagnostics or testing
*/
DIAGNOSTIC
}
......
......@@ -34,28 +34,48 @@ import org.eclipse.ice.data.IDataElement;
* according to the execution order provided in the hook type table (see
* {@link TaskHookType}). Tasks support multiple hooks of each type since many
* different things may be required to support the execution of the main
* action. Hooks of the same type are executed as a queue (first-in-first-out).
* action. Hooks of the same type are executed as a queue (first in, first
* out).
*
* Tasks execute in one of several Task States (see {@link TaskState}) that are
* roughly correlated to hooks and action. Tasks start their life cycle with
* initialization and end with the execution of the action and any
* postprocessing hooks.
* postprocessing hooks. Tasks hold in the READY state once their data is
* configured to indicate that they are ready to be executed.
*
* Tasks cannot execute without both action data and an action. Executing
* without an action is impossible by definition. Executing without action data
* is a design choice to encourage the development of actions with data
* separated from the implementation. This makes it possible to have well-
* scoped actions with minimal hardwiring or blob type code.
*
* Tasks store state data externally and do not control their own storage. Thus
* the must be configured when built to store state to the proper location. If
* no state controller is provided, tasks will attempt to store state locally
* by default. State storage is separate from data storage, which is tracked by
* individual data models.
* the must be configured when built to store state to the proper location.
* State data storage is separate from client data storage, which is tracked by
* individual data models. State data refers specifically to data that tracks
* the status of the Task, not data that is used as input or gathered as output
* from the action which is generally referred to as action or client data. The
* TaskDataState object must be provided on construction by the caller of the
* constructor, which in most cases should be a builder or dependency injection
* engine.
*
* Tasks can be observed by listeners (see {@link ITaskListener}). Events are
* dispatched asynchronously and listeners are not consulted for command and
* control.
*
* Data is injected into tasks using a setter instead of including it directly
* in the run() operation. This supports option-operand separation and a
* specific case of its use is in the execution of multiple tasks
* simultaneously without the caller knowing anything about the data
* configuration.
* Client data is injected into tasks using a setter instead of including it
* directly in the run() operation. This supports option-operand separation and
* a specific case of its use is in the execution of multiple tasks
* simultaneously without the caller knowing anything about the client data
* configuration and types.
*
* Note that there is a difference between state machine actions and Task
* Actions. The former is a construct used by the Spring State Machine library
* and describes events attached to events or states in a state machine. The
* latter specifically refers to Actions executed as part of Tasks in the
* workflow model of Billings, 2019.
*
* ----- WIP implementation notes (to be deleted on PR close) -----
*
* Thoughts on provenance tracking? ICE 2.0 used a log file.
*
......@@ -71,22 +91,27 @@ import org.eclipse.ice.data.IDataElement;
public interface ITask<T extends IDataElement<T>> {
/**
* This operation sets the data on which the task should execute.
* This operation sets the action or client data on which the task should
* execute.
* @param taskData The data for the task
* @exception an exception is thrown is the data is null
*/
public void setData(T taskData);
public void setActionData(T actionData) throws Exception;
/**
* This operation gets the data on which the task is working.
* @return the data
* This operation gets the action or client data on which the task is
* working. It returns a reference to the same data that was passed to
* setActionData().
* @return the data used with the action
*/
public T getData();
public T getActionData();
/**
* This function sets the Action that the task executes.
* @param taskAction the task's action.
* @exception an exception is thrown if the task action is null
*/
public void setAction(final Action<T> taskAction);
public void setAction(final Action<T> taskAction) throws Exception;
/**
* This operation adds a hook to the task that will be executed in support
......@@ -116,4 +141,22 @@ public interface ITask<T extends IDataElement<T>> {
*/
public TaskState getState();
/**
* This operation returns the full set of state data for the Task. This
* includes all data available for a standard identifiable data element.
* This data is provided primarily for diagnostic and planning purposes.
*
* Note that the task state data returned is a copy of the state data
* since clients should not be able to directly influence the state of
* the task through its data.
*
* Clients using this simply to gather the state of the task should call
* getState() instead since it does not include a costly copy and
* extensive metadata.
*
* @return the full set of state data for the task.
*/
public TaskStateData getTaskStateData();
}
/******************************************************************************
* Copyright (c) 2020- UT-Battelle, LLC.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Initial API and implementation and/or initial documentation -
* Jay Jay Billings
*****************************************************************************/
package org.eclipse.ice.tasks;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.ice.data.IDataElement;
import org.eclipse.ice.tasks.spring.statemachine.ExecutingEventAction;
import org.eclipse.ice.tasks.spring.statemachine.ExecutingStateAction;
import org.eclipse.ice.tasks.spring.statemachine.FinishedEventAction;
import org.eclipse.ice.tasks.spring.statemachine.InitializedStateAction;
import org.eclipse.ice.tasks.spring.statemachine.ReadyEventAction;
import org.eclipse.ice.tasks.spring.statemachine.WaitingEventAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.StateMachineBuilder;
import org.springframework.statemachine.config.StateMachineBuilder.Builder;
/**
* The basic implementation of ITask.
*
* @author Jay Jay Billings
*
*/
public class Task<T extends IDataElement<T>> implements ITask<T> {
/**
* Error message for erroneous construction
*/
public static final String CONSTRUCTION_ERR = "Task state data cannot be null";
/**
* Error message for erroneous action data set
*/
public static final String ACTION_DATA_SET_ERR = "Action data cannot be null.";
/**
* Error message for erroneous action set
*/
public static final String ACTION_SET_ERR = "The action cannot be null.";
/**
* The state machine used to manage the task's state and transitions.
*/
protected StateMachine<TaskState, TaskTransitionEvents> stateMachine;
/**
* Logging tool
*/
private static final Logger logger = LoggerFactory.getLogger(Task.class);
/**
* State data stored about this task
*/
protected AtomicReference<TaskStateData> stateData;
/**
* Data used by the action
*/
protected AtomicReference<T> actionData;
/**
* The action
*/
protected AtomicReference<org.eclipse.ice.tasks.Action<T>> action;
/**
* The state machine action used to execute the action
*/
private ExecutingStateAction<T> smExecutingAction;
/**
* Utility function for throwing exceptions
*
* @param msg error message to go with the exception and in the log
* @throws TaskException
* @throws Exception the exception
*/
private void throwErrorException(String msg) throws TaskException {
TaskException exception = new TaskException(msg);
logger.error(msg, exception);
throw (exception);
}
/**
* Constructor
*
* @param stateData the task state data must be provided on initialization
* because tasks do not manage any data. See
* {@link org.eclipse.ice.tasks.ITask}. Construction will fail
* without a valid state data structure.
* @throws TaskException
*/
public Task(TaskStateData taskStateData) throws TaskException {
// Check state data
if (taskStateData != null) {
stateData = new AtomicReference<>(taskStateData);
smExecutingAction = new ExecutingStateAction<>(stateData.get());
action = new AtomicReference<>();
actionData = new AtomicReference<>();
} else {
throwErrorException(CONSTRUCTION_ERR);
}
// Initialize the state machine
buildStateMachine();
stateMachine.start();
logger.info("State data set and state machine initialized. " + "Ready to rock and roll.");
}
@Override
public void setActionData(T taskActionData) throws TaskException {
if (taskActionData != null) {
// Store the data
actionData.set(taskActionData);
// The state machine can only get into the waiting state if the action or the
// action data are set, so check for that and if so move on to the ready state.
TaskTransitionEvents event;
if (getState().equals(TaskState.WAITING)) {
event = TaskTransitionEvents.ALL_INFO_SET;
} else {
// Otherwise wait. Note that event here is different than in setAction().
event = TaskTransitionEvents.ACTION_DATA_SET;
}
// Once the action data is set, the task should wait
stateMachine.sendEvent(event);
} else {
throwErrorException(ACTION_DATA_SET_ERR);
}
}
@Override
public T getActionData() {
return actionData.get();
}
@Override
public void setAction(org.eclipse.ice.tasks.Action<T> taskAction) throws TaskException {
// Make sure the error is not null before updating the states
if (taskAction != null) {
// Store the action
action.set(taskAction);
TaskTransitionEvents event;
// The state machine can only get into the waiting state if the action or the
// action data are set, so check for that and if so move on to the ready state.
if (getState().equals(TaskState.WAITING)) {
event = TaskTransitionEvents.ALL_INFO_SET;
} else {
// Otherwise wait. Note that the event here is different than in
// setActionData().
event = TaskTransitionEvents.ACTION_SET;
}
// Throw the event to wait or get ready.
stateMachine.sendEvent(event);
} else {
throwErrorException(ACTION_SET_ERR);
}
}
@Override
public void addHook(Hook<T> hook) {
// TODO Auto-generated method stub
}
@Override
public TaskState execute() {
// Set the action and data references for the execution action
smExecutingAction.setActionData(actionData.get());
smExecutingAction.setTaskAction(action.get());
// Execute the action. Note that there is a transition event tied to the state
// event for execution. The transition event changes the state to EXECUTING
// while the state event makes some more informed decisions.
stateMachine.sendEvent(TaskTransitionEvents.EXECUTION_TRIGGERED);
logger.info("Task execution initiated.");
return getState();
}
@Override
public TaskState cancel() {
// TODO Auto-generated method stub
return null;
}
@Override
public TaskState getState() {
return stateData.get().getTaskState();
}
@Override
public TaskStateData getTaskStateData() {
return (TaskStateData) stateData.get().clone();
}
/**
* This operation builds the state machine used to manage states and transitions
* for Tasks.
*
* @throws TaskException
*
* @throws Exception thrown in the state machine can not be correctly
* assembled.
*/
private void buildStateMachine() throws TaskException {
try {
// Setup the state machine builder
Builder<TaskState, TaskTransitionEvents> builder = new StateMachineBuilder.Builder<>();
// Go into the intial state and add the others states to the set
builder.configureStates().withStates()
.initial(TaskState.INITIALIZED, new InitializedStateAction<>(stateData.get()))
.states(EnumSet.allOf(TaskState.class));
// Configure the transitions for action data and actions.
builder.configureTransitions().withExternal().source(TaskState.INITIALIZED).target(TaskState.WAITING)
.event(TaskTransitionEvents.ACTION_DATA_SET).action(new WaitingEventAction<>(stateData.get()));
builder.configureTransitions().withExternal().source(TaskState.INITIALIZED).target(TaskState.WAITING)
.event(TaskTransitionEvents.ACTION_SET).action(new WaitingEventAction<>(stateData.get()));
// Once all the info is set, the state machine can transition to READY
builder.configureTransitions().withExternal().source(TaskState.WAITING).target(TaskState.READY)