Commit 6d7c589d authored by Jay Jay Billings's avatar Jay Jay Billings
Browse files

Mostly implemented the primary flow of the Task.


Signed-off-by: default avatarJay Jay Billings <billingsjj@ornl.gov>
parent e211cb29
......@@ -5,8 +5,19 @@
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="src" path="src/test/java"/>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="test" value="true"/>
......@@ -28,17 +39,16 @@
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="module" value="true"/>
<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="ignore_optional_problems" value="true"/>
<attribute name="test" 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"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
......
......@@ -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>
......@@ -79,6 +80,12 @@
<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>
......
......@@ -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,12 +34,20 @@ 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.
......@@ -61,6 +69,8 @@ import org.eclipse.ice.data.IDataElement;
* simultaneously without the caller knowing anything about the client data
* configuration and types.
*
* ----- WIP implementation notes (to be deleted on PR close) -----
*
* Thoughts on provenance tracking? ICE 2.0 used a log file.
*
* They are modeled as the combination of an
......@@ -78,12 +88,14 @@ public interface ITask<T extends IDataElement<T>> {
* 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 setActionData(T actionData);
public void setActionData(T actionData) throws Exception;
/**
* This operation gets the action or client data on which the task is
* working.
* working. It returns a reference to the same data that was passed to
* setActionData().
* @return the data used with the action
*/
public T getActionData();
......@@ -91,8 +103,9 @@ public interface ITask<T extends IDataElement<T>> {
/**
* 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
......
......@@ -11,55 +11,22 @@
*****************************************************************************/
package org.eclipse.ice.tasks;
import java.util.EnumSet;
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 java.io.File;
//import java.io.IOException;
//import java.util.EnumSet;
//
//import org.eclipse.ice.commands.FileHandlerFactory;
//import org.eclipse.ice.commands.IFileHandler;
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;
////
//public class TaskTest {
//
// static String name = "ICEIII/ice/org.eclipse.ice.workflow/src/main/resources/test1.txt";
// static String newName = "ICEIII/ice/org.eclipse.ice.workflow/src/main/resources/test2.txt";
//
// static
//
// public static void moveFile(final String filePath, final String newFilePath)
// throws IOException {
//
// String home = System.getProperty("user.home");
// String separator = String.valueOf(File.separatorChar);
// String homePath = home + separator;
//
// FileHandlerFactory factory = new FileHandlerFactory();
// IFileHandler fileHandler = factory.getFileHandler();
// fileHandler.move(homePath + filePath, homePath + newFilePath);
// return;
// }
//
// public static void main(String[] args) throws Exception {
//
// StateMachine<WorkflowEngine.States, WorkflowEngine.Events> taskProcessor = buildMachine();
// taskProcessor.start();
// taskProcessor.sendEvent(WorkflowEngine.Events.PARAMETERS_RECEIVED);
// taskProcessor.sendEvent(WorkflowEngine.Events.EXECUTION_COMPLETE);
// System.out.println(taskProcessor.getState());
//
// return;
// }
//
//}
//
//
/**
* The basic implementation of ITask.
......@@ -69,6 +36,21 @@ import org.springframework.statemachine.config.StateMachineBuilder.Builder;
*/
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.
*/
......@@ -84,6 +66,29 @@ public class Task<T extends IDataElement<T>> implements ITask<T> {
*/
protected TaskStateData stateData;
/**
* Data used by the action
*/
protected T actionData;
/**
* The action
*/
protected org.eclipse.ice.tasks.Action<T> action;
/**
* 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
*
......@@ -91,36 +96,81 @@ public class Task<T extends IDataElement<T>> implements ITask<T> {
* 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 RuntimeException {
public Task(TaskStateData taskStateData) throws TaskException {
// Check state data
if (taskStateData != null) {
stateData = taskStateData;
} else {
String errMsg = "Cannot create task without state data!";
RuntimeException exception = new RuntimeException(errMsg);
logger.error(errMsg, exception);
throw (exception);
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 actionData) {
// TODO Auto-generated method stub
public void setActionData(T taskActionData) throws TaskException {
if (taskActionData != null) {
// Store the data
actionData = 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() {
// TODO Auto-generated method stub
return null;
return actionData;
}
@Override
public void setAction(org.eclipse.ice.tasks.Action<T> taskAction) {
// TODO Auto-generated method stub
org.springframework.statemachine.action.Action smAction;
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 = 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
......@@ -131,8 +181,11 @@ public class Task<T extends IDataElement<T>> implements ITask<T> {
@Override
public TaskState execute() {
// TODO Auto-generated method stub
return null;
// 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);
return getState();
}
@Override
......@@ -155,33 +208,50 @@ public class Task<T extends IDataElement<T>> implements ITask<T> {
* This operation builds the state machine used to manage states and transitions
* for Tasks.
*
* @throws Exception thrown in the state machine can not be correctly assembled.
* @throws TaskException
*
* @throws Exception thrown in the state machine can not be correctly
* assembled.
*/
private void buildStateMachine() throws Exception {
Builder<TaskState, TaskTransitionEvents> builder = new StateMachineBuilder.Builder<>();
// builder.configureStates().withStates().initial(WorkflowEngine.States.INITIALIZED)
// .states(EnumSet.allOf(WorkflowEngine.States.class));
//
// builder.configureTransitions().withExternal().source(WorkflowEngine.States.INITIALIZED)
// .target(WorkflowEngine.States.EXECUTING).event(WorkflowEngine.Events.PARAMETERS_RECEIVED).and()
// .withExternal().source(WorkflowEngine.States.EXECUTING).target(WorkflowEngine.States.FINISHED)
// .action(new Action<WorkflowEngine.States, WorkflowEngine.Events>() {
//
// @Override
// public void execute(StateContext<WorkflowEngine.States, WorkflowEngine.Events> context) {
// try {
// moveFile(name, newName);
// } catch (IOException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
// }).event(WorkflowEngine.Events.EXECUTION_COMPLETE);
//
// stateMachine = builder.build();
return;
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))
.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));
builder.configureTransitions().withExternal().source(TaskState.INITIALIZED).target(TaskState.WAITING)
.event(TaskTransitionEvents.ACTION_SET).action(new WaitingEventAction(stateData));
// Once all the info is set, the state machine can transition to READY
builder.configureTransitions().withExternal().source(TaskState.WAITING).target(TaskState.READY)
.event(TaskTransitionEvents.ALL_INFO_SET).action(new ReadyEventAction(stateData));
// A call to execute() triggers execution. This requires a transition action...
builder.configureTransitions().withExternal().source(TaskState.READY).target(TaskState.EXECUTING)
.event(TaskTransitionEvents.EXECUTION_TRIGGERED).action(new ExecutingEventAction(stateData));
// ...and a state action
builder.configureStates().withStates().state(TaskState.EXECUTING, new ExecutingStateAction(stateData));
// FIXME! CONFIRM THAT THIS IS WORKING!---^
// If the execution finished as expected, there is a transition event for that
// to finalize the state.
builder.configureTransitions().withExternal().source(TaskState.EXECUTING).target(TaskState.FINISHED)
.event(TaskTransitionEvents.EXECUTION_FINISHED).action(new FinishedEventAction(stateData));
// Pack it up and go on home
stateMachine = builder.build();
} catch (Exception e) {
throwErrorException("Unable to build state machine!");
}
}
}
/******************************************************************************
* 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;
/**
* A dedicated exception for Tasks.
* @author Jay Jay Billings
*
*/
public class TaskException extends Exception {
/**
* Constructor
* @param msg error message from the task
*/
public TaskException(String msg) {
super(msg);
}
/**
* Exception id
*/
private static final long serialVersionUID = -7680227861625984735L;
}
/******************************************************************************
* 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.spring.statemachine;
import org.eclipse.ice.tasks.TaskState;
import org.eclipse.ice.tasks.TaskStateData;
import org.eclipse.ice.tasks.TaskTransitionEvents;
import org.springframework.statemachine.StateContext;
/**
* This is a state machine action that puts the Task state into the EXECUTING
* state.
*
* @author Jay Jay Billings
*/
public class ExecutingEventAction extends StateMachineBaseAction {
/**
* Constructor
*
* @param taskStateData
*/
public ExecutingEventAction(TaskStateData taskStateData) {
super(taskStateData);
}
@Override
public void execute(StateContext<TaskState, TaskTransitionEvents> context) {
TaskState state = TaskState.EXECUTING;
stateData.setTaskState(state);
logger.info("Execution triggered. Action executing. State = {}",state);
}
}
/******************************************************************************
* 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.spring.statemachine;
import org.eclipse.ice.tasks.TaskState;
import org.eclipse.ice.tasks.TaskStateData;
import org.eclipse.ice.tasks.TaskTransitionEvents;
import org.springframework.statemachine.StateContext;
/**
* This is a state machine action that executes after the task enters the
* EXECUTING state. The task executes this action *AFTER* the task enters the
* state.
*
* @author Jay Jay Billings
*
*/
public class ExecutingStateAction extends StateMachineBaseAction {
/**
* Constructor
*
* @param taskStateData state data
*/
public ExecutingStateAction(TaskStateData taskStateData) {
super(taskStateData);
}
@Override
public void execute(StateContext<TaskState, TaskTransitionEvents> context) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
TaskState state = TaskState.FINISHED;
context.getStateMachine().sendEvent(TaskTransitionEvents.EXECUTION_FINISHED);
stateData.setTaskState(state);
logger.info("Task executed. State = {}", state);
}
});
thread.start();
}
}
/******************************************************************************
* 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.spring.statemachine;
import org.eclipse.ice.tasks.TaskState;
import org.eclipse.ice.tasks.TaskStateData;
import org.eclipse.ice.tasks.TaskTransitionEvents;