/* Copyright 2014 MITRE Corporation
*
* 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 org.mitre.provenance.workflowengine.activity;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.mitre.provenance.Metadata;
import org.mitre.provenance.plusobject.PLUSObject;
import org.mitre.provenance.plusobject.PLUSString;
import org.mitre.provenance.user.PrivilegeClass;
import org.mitre.provenance.workflowengine.Transition;
/**
* This object models a particular activity in a Workflow.
* Activities are considered black-box by default; they have only inputs, outputs, and transitions.
*
* There are two types of transitions; "transitions" are arcs <b>out of</b> the activity going
* elsewhere. "introductions" are arcs <b>into</b> the activity coming from elsewhere. In this
* sense, they can be thought of as a graph or form of doubly linked list.
* @author moxious
*
*/
public class Activity {
protected String name;
protected Hashtable <String, Metadata> inputs;
protected Hashtable <String, Metadata> outputs;
protected Hashtable <String, PLUSObject> outputMappings;
protected Metadata metadata;
/** Arcs out of this activity elsewhere **/
public Vector <Transition> transitions;
/** Arcs into this activity from elsewhere **/
public Vector <Transition> introductions;
/** Special activity representing the terminating point of a workflow **/
public static Activity END_STATE = new Activity("END");
/** Special activity representing the starting point of a workflow **/
public static Activity START_STATE = new Activity("START");
public Activity(String name) {
this.name = name;
introductions = new Vector <Transition> ();
transitions = new Vector <Transition> ();
metadata = new Metadata();
inputs = new Hashtable <String, Metadata> ();
outputs = new Hashtable <String, Metadata> ();
outputMappings = new Hashtable <String,PLUSObject> ();
} // End Activity
public String toString() {
// return ("<Act: " + getName() + " ID=" + getMetadata().get("id") +
// " IID=" + getMetadata().get("invokeid") + ">");
return "<Activity: " + getName() + ">";
}
public Metadata getMetadata() { return metadata; }
public String getName() { return name; }
/**
* Subclass this method and have it return a the variables that are needed in order for this activity to run
* properly. If your activity relies on having particular inputs with particular names, this is the way to
* tell your caller about those requirements.
* This particular method returns an empty hash.
* @return a list of input variable names mapped to Class objects describing the types needed.
*/
public Hashtable <String,Class<?>> getRequiredInputNames() {
Hashtable <String,Class<?>> x = new Hashtable <String,Class<?>> ();
return x;
} // End getRequiredInputNames
/**
* Given a list of inputs, this method will determine whether or not the activities requirements have been met.
* If all of the necessary inputs are present, it will do nothing.
* @param inputs the inputs to the activity execution.
* @throws ActivityException if some required input is missing, or of the wrong type.s
*/
public void checkInputTypes(Hashtable <String,PLUSObject> inputs) throws ActivityException {
Hashtable <String,Class<?>> req = getRequiredInputNames();
Enumeration<String> z = inputs.keys();
while(z.hasMoreElements()) {
System.out.println("check inputs: has input " + z.nextElement());
}
Enumeration<String> e = req.keys();
while(e.hasMoreElements()) {
String key = (String)e.nextElement();
Class<?> val = req.get(key);
PLUSObject item = inputs.get(key);
if(item == null) throw new ActivityException("check input types(" + getName() +
"): missing input named " + key);
if(!val.isAssignableFrom(item.getClass()))
throw new ActivityException("check input types: input " + key + " is of type " + item.getClass() +
" which is incompatible with required type " + val);
} // End while
// Everything checked out.
return;
} // End checkInputTypes
public void registerInput(String inputParameterName) {
registerInput(inputParameterName, new Metadata());
}
public void registerInput(String inputParameterName, Metadata descriptionOfInput) {
inputs.put(inputParameterName, descriptionOfInput);
} // End registerInput
public void addIntroduction(Transition t) throws Exception {
if(t.to != this)
throw new Exception("You can only add an introduction that ends up at this activity!");
introductions.add(t);
} // End addIntroduction
public void addTransition(Transition t) throws Exception {
if(t.from != this)
throw new Exception("You can only add transitions that start from this activity!");
transitions.add(t);
} // End addTransition
/**
* Determines whether this activity is the end of the line or not.
* @return true if the activity has no outbound transitions, false otherwise.
*/
public boolean isTerminalNode() { return getTransitions().size() == 0; }
public Vector <Transition> getTransitions() { return transitions; }
public Vector <Transition> getIntroductions() { return introductions; }
public int countInputs() { return inputs.size(); }
public int countOutputs() { return outputs.size(); }
public void registerOutput(String outputParameterName) {
registerOutput(outputParameterName, new Metadata());
}
public void registerOutput(String outputParameterName, Metadata descriptionOfOutput) {
outputs.put(outputParameterName, descriptionOfOutput);
} // End
public Hashtable <String,Metadata> getInputs() { return inputs; }
public Hashtable <String,Metadata> getOutputs() { return outputs; }
/**
* Determines whether this activity takes a given variable name as input.
* @see Activity#registerInput(String, Metadata)
* @param name the name of the variable you're interested in
* @return true if the activity inputs that variable, false otherwise.
*/
public boolean hasInput(String name) {
if(name == null) return false;
return inputs.containsKey(name);
} // End hasInput
/**
* Determines whether this activity will output a given variable name. This applies both
* for registered outputs, and "parroted" outputs.
* @see Activity#parrotOutput(String, PLUSObject)
* @see Activity#registerOutput(String, Metadata)
* @param name the name of the variable you're interested in
* @return true if the activity will output that variable, false otherwise.
*/
public boolean hasOutput(String name) {
if(name == null) return false;
// We can say that this activity has a given output if it's either a registered output,
// or also if it's a parroted variable name.
return outputs.containsKey(name) || outputMappings.containsKey(name);
} // End hasOutput
/**
* This method is used when you want an activity to simply emit a particular output after it has finished executing,
* even if it wasn't part of the input. For example, if you want the start state to emit a token, you could tell
* the start activity to "parrot" a particular value, which would serve as an input to your first activity.
*/
public void parrotOutput(String varName, PLUSObject value) {
outputMappings.put(varName, value);
} // End parrotOutput
/**
* Override this method to perform actual work in activities. When an
* activity in a workflow is executed, this method will be called.
*
* <p>Subclasses should put all output variables into the hash outputMappings, which has
* already been created for you, and stores needed "parrot" variables.
*
* <p>This method by default will create output data items with a security code of 5.
* The outputs will also have an uncertainty equal to 0.5
*
* @see Activity#outputMappings
*
* @param inputVariableMappings a hashtable with keys that are the names of input
* paramters, and values that are specific PLUSObjects to be used for those inputs.
* @return a hashtable with keys that are the names of registered output parameters,
* and values that are specific PLUSObjects to be used for those outputs.
*/
public Hashtable <String, PLUSObject> execute(Hashtable <String, PLUSObject> inputVariableMappings)
throws ActivityException {
// Dummy method. Does nothing but create dummy outputs with no processing.
// For each variable name that is supposed to be an output...
Enumeration<String> e = outputs.keys();
while(e.hasMoreElements()) {
String varName = (String)e.nextElement();
// Create a dumb string
PLUSString outputDI = new PLUSString(varName);
// Tag it with whatever metadata was attached to the output variable description.
outputDI.setMetadata(outputs.get(varName));
// Set its uncertainty to be the maximum of all inputs.
outputDI.setUncertainty((float)0.5);
// Set its security to be the maximum of all inputs.
try { outputDI.getPrivileges().addPrivilege(new PrivilegeClass(5)); }
catch(Exception exc) {
System.err.println("Activity.execute: Error " + exc);
throw new ActivityException("Error", exc);
}
// Put it into the hashtable
outputMappings.put(varName, outputDI);
} // End while
return outputMappings;
} // End execute
} // End Activity