/*
* ProActive Parallel Suite(TM):
* The Open Source library for parallel and distributed
* Workflows & Scheduling, Orchestration, Cloud Automation
* and Big Data Analysis on Enterprise Grids & Clouds.
*
* Copyright (c) 2007 - 2017 ActiveEon
* Contact: contact@activeeon.com
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation: version 3 of
* the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* If needed, contact us to obtain a release under GPL Version 2 or 3
* or a different license than the AGPL.
*/
package org.ow2.proactive.scheduler.common.task.executable;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.script.ScriptContext;
import org.objectweb.proactive.annotation.PublicAPI;
import org.ow2.proactive.scheduler.common.SchedulerConstants;
import org.ow2.proactive.scheduler.common.task.TaskResult;
import org.ow2.proactive.scheduler.common.task.executable.internal.JavaStandaloneExecutableInitializer;
import org.ow2.proactive.scheduler.common.task.util.SerializationUtil;
import org.ow2.proactive.scheduler.task.SchedulerVars;
import org.ow2.proactive.scripting.helper.progress.ProgressFile;
/**
* AbstractJavaExecutable : base class of Java Executables
*
* @author The ProActive Team
* @since ProActive Scheduling 0.9
*/
@PublicAPI
public abstract class JavaExecutable {
// this value is set only on worker node side !!
// see JavaTaskLauncher
protected JavaStandaloneExecutableInitializer execInitializer;
private Map<String, Serializable> propagatedVariables;
private Map<String, String> metadata;
private String inputSpace, outputSpace, globalSpace, localSpace, userSpace;
/**
* Initialize the executable using the given executable Initializer.
*
* @param execInitializer the executable Initializer used to init the executable itself
*
* @throws Exception an exception if something goes wrong during executable initialization.
*/
public void internalInit(JavaStandaloneExecutableInitializer execInitializer, ScriptContext sc) throws Exception {
this.execInitializer = execInitializer;
// at this point, the context class loader is the TaskClassLoader
// see JavaExecutableContainer.getExecutable()
Map<String, Serializable> arguments = this.execInitializer.getArguments(Thread.currentThread()
.getContextClassLoader());
Map<String, Serializable> propagatedVariables = SerializationUtil.deserializeVariableMap(execInitializer.getPropagatedVariables());
setVariables(propagatedVariables);
// update arguments
updateVariables(arguments, getVariables());
initDataSpaces(sc);
init(arguments);
initMetadata(sc);
}
/**
* Initialization default method for a java task.<br>
* <p>
* By default, this method does automatic assignment between the value given in the arguments map
* and the fields contained in your executable.<br>
* If the field type and the argument type are different and if the argument type is String
* (i.e. for all jobs defined with XML descriptors), then a automatic mapping is tried.
* Managed types are byte, short, int, long, boolean and the corresponding classes, other type
* must be handle by user by overriding this method.<br><br>
* For example, if you set as argument the key="var", value="12" in the XML descriptor<br>
* just add an int (or Integer, long, Long) field named "var" in your executable.
* The default {@link #init(java.util.Map)} method will store your arguments into the integer class field.
* </p>
* To avoid this default behavior, just override this method to make your own initialization.
*
* @param args a map containing the different parameter names and values given by the user task.
*/
public void init(Map<String, Serializable> args) throws Exception {
if (args == null) {
return;
}
Class<?> current = this.getClass();
while (JavaExecutable.class.isAssignableFrom(current)) {
for (Entry<String, Serializable> e : args.entrySet()) {
try {
Field f = current.getDeclaredField(e.getKey());
// if f does not exist -> catch block
f.setAccessible(true);
Class<?> fieldClass = f.getType();
Class<?> valueClass = e.getValue().getClass();
// unbox manually as it is not done automatically
// ie : int is not assignable from Integer
if (valueClass.equals(Integer.class) || valueClass.equals(Short.class) ||
valueClass.equals(Long.class) || valueClass.equals(Byte.class) ||
valueClass.equals(Boolean.class)) {
e.setValue(e.getValue().toString());
valueClass = String.class;
}
if (String.class.equals(valueClass) && !String.class.equals(fieldClass)) {
String valueAsString = (String) e.getValue();
// parameter has been defined as string in XML
// try to convert it automatically
if (fieldClass.equals(Integer.class) || fieldClass.equals(int.class)) {
f.set(this, Integer.parseInt(valueAsString));
} else if (fieldClass.equals(Short.class) || fieldClass.equals(short.class)) {
f.set(this, Short.parseShort(valueAsString));
} else if (fieldClass.equals(Long.class) || fieldClass.equals(long.class)) {
f.set(this, Long.parseLong(valueAsString));
} else if (fieldClass.equals(Byte.class) || fieldClass.equals(byte.class)) {
f.set(this, Byte.parseByte(valueAsString));
} else if (fieldClass.equals(Boolean.class) || fieldClass.equals(boolean.class)) {
f.set(this, Boolean.parseBoolean(valueAsString));
}
} else if (fieldClass.isAssignableFrom(valueClass)) {
// no conversion for other type than String and primitive
f.set(this, e.getValue());
}
} catch (Exception ex) {
// nothing to do, no automatic assignment can be done for this field
}
}
current = current.getSuperclass();
}
}
/**
* Initialization of the dataSpaces.<br>
*
* @param sc the ScriptContext including as bindings the dataSpaces locations.
*/
public void initDataSpaces(ScriptContext sc) {
this.inputSpace = (String) sc.getAttribute(SchedulerConstants.DS_INPUT_BINDING_NAME);
this.outputSpace = (String) sc.getAttribute(SchedulerConstants.DS_OUTPUT_BINDING_NAME);
this.globalSpace = (String) sc.getAttribute(SchedulerConstants.DS_GLOBAL_BINDING_NAME);
this.userSpace = (String) sc.getAttribute(SchedulerConstants.DS_USER_BINDING_NAME);
this.localSpace = (String) sc.getAttribute(SchedulerConstants.DS_SCRATCH_BINDING_NAME);
}
/**
* Initialization of the metadata.<br>
*
* @param sc the ScriptContext including as bindings the metadata map.
*/
public void initMetadata(ScriptContext sc) {
this.metadata = (Map<String, String>) sc.getAttribute(SchedulerConstants.RESULT_METADATA_VARIABLE);
}
/**
* Use this method for a multi-node task. It returns the list of nodes url demanded by the user
* while describing the task.<br>
* In a task, one node is used to start the task itself, the other are returned by this method.<br>
* If user describe the task using the "numberOfNodes" property set to 5, then this method
* returns a list containing 4 nodes. The first one being used by the task itself.
*
* @return the list of nodes demanded by the user.
*/
public List<String> getNodesURL() {
return execInitializer.getNodesURL();
}
/**
* When iteration occurs due to a {@link org.ow2.proactive.scheduler.common.task.flow.FlowActionType#LOOP} FlowAction,
* each new iterated instance of a task is assigned an iteration index so
* that it can be uniquely identified.
* <p>
* This is a convenience method to retrieve the Iteration Index that was exported
* as a Java Property by the TaskLauncher.
*
* @return the Iteration Index of this Task
*/
public final int getIterationIndex() {
return (Integer) getVariables().get(SchedulerVars.PA_TASK_ITERATION.toString());
}
/**
* When replication occurs due to a {@link org.ow2.proactive.scheduler.common.task.flow.FlowActionType#REPLICATE} FlowAction,
* each new replicated instance of a task is assigned a replication index so
* that it can be uniquely identified.
* <p>
* This is a convenience method to retrieve the Replication Index that was exported
* as a Java Property by the TaskLauncher.
*
* @return the Replication Index of this Task
*/
public final int getReplicationIndex() {
return (Integer) getVariables().get(SchedulerVars.PA_TASK_REPLICATION.toString());
}
/**
* Third-party credentials are specific to each Scheduler user and stored on the server side.
* They consist of key-value pairs and are exposed in Java tasks via this method.
*
* @param key the credential's key whose associated value is to be returned
* @return the credential's value associated with the key parameter
*/
protected String getThirdPartyCredential(String key) {
return execInitializer.getThirdPartyCredentials().get(key);
}
/**
* When using non forked Java tasks, you should use this PrintStream instead of System.out.
* @return a stream that will write to the task's output stream
*/
protected PrintStream getOut() {
return execInitializer.getOutputSink();
}
/**
* When using non forked Java tasks, you should use this PrintStream instead of System.err.
* @return a stream that will write to the task's error stream
*/
protected PrintStream getErr() {
return execInitializer.getErrorSink();
}
private void updateVariables(Map<String, Serializable> old, Map<String, Serializable> updated) {
for (String k : old.keySet()) {
if (updated.containsKey(k)) {
old.put(k, updated.get(k));
}
}
}
/**
* The content of this method will be executed once after being scheduled.<br>
* This may generate a result as an {@link Object}. It can be whatever you want.<br>
* The results list order correspond to the order in the dependence list.
*
* @param results the results (as a taskResult) from parent tasks.
* @throws Throwable any exception thrown by the user's code
* @return any serializable object from the user.
*/
public abstract Serializable execute(TaskResult... results) throws Throwable;
/**
* Set the progress value for this Executable. Progress value must be ranged
* between 0 and 100.
* @param newValue the new progress value
* @return the previous progress value
* @throws IllegalArgumentException if the value is not ranged between 0 and 100.
*/
protected final int setProgress(int newValue) throws IllegalArgumentException {
if (newValue < 0 || newValue > 100) {
throw new IllegalArgumentException("Progress value must be ranged between 0 and 100");
}
String progressFilePath = (String) getVariables().get(SchedulerVars.PA_TASK_PROGRESS_FILE.toString());
int previousValue = ProgressFile.getProgress(progressFilePath);
ProgressFile.setProgress(progressFilePath, newValue);
return previousValue;
}
/**
* Return the current progress value for this executable, ranged between 0 and 100.
*
* @return the current progress value for this executable.
*/
public int getProgress() {
return ProgressFile.getProgress((String) getVariables().get(SchedulerVars.PA_TASK_PROGRESS_FILE.toString()));
}
public Map<String, Serializable> getVariables() {
return this.propagatedVariables;
}
/**
* Return the result metadata map
*
* @return metadata map
*/
public Map<String, String> getMetadata() {
return this.metadata;
}
public void setVariables(Map<String, Serializable> propagatedVariables) {
this.propagatedVariables = propagatedVariables;
}
public String getInputSpace() {
return this.inputSpace;
}
public String getOutputSpace() {
return this.outputSpace;
}
public String getGlobalSpace() {
return this.globalSpace;
}
public String getLocalSpace() {
return this.localSpace;
}
public String getUserSpace() {
return this.userSpace;
}
}