/*
* Eoulsan development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public License version 2.1 or
* later and CeCILL-C. This should be distributed with the code.
* If you do not have a copy, see:
*
* http://www.gnu.org/licenses/lgpl-2.1.txt
* http://www.cecill.info/licences/Licence_CeCILL-C_V1-en.txt
*
* Copyright for this code is held jointly by the Genomic platform
* of the Institut de Biologie de l'École normale supérieure and
* the individual authors. These should be listed in @author doc
* comments.
*
* For more information on the Eoulsan project and its aims,
* or to join the Eoulsan Google group, visit the home page
* at:
*
* http://outils.genomique.biologie.ens.fr/eoulsan
*
*/
package fr.ens.biologie.genomique.eoulsan.core.workflow;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import com.google.common.base.Objects;
import fr.ens.biologie.genomique.eoulsan.AbstractEoulsanRuntime;
import fr.ens.biologie.genomique.eoulsan.EoulsanRuntime;
import fr.ens.biologie.genomique.eoulsan.EoulsanRuntimeException;
import fr.ens.biologie.genomique.eoulsan.Settings;
import fr.ens.biologie.genomique.eoulsan.core.InputPort;
import fr.ens.biologie.genomique.eoulsan.core.OutputPort;
import fr.ens.biologie.genomique.eoulsan.core.Step;
import fr.ens.biologie.genomique.eoulsan.core.TaskContext;
import fr.ens.biologie.genomique.eoulsan.core.Workflow;
import fr.ens.biologie.genomique.eoulsan.data.Data;
import fr.ens.biologie.genomique.eoulsan.data.DataFile;
import fr.ens.biologie.genomique.eoulsan.data.DataFormat;
import fr.ens.biologie.genomique.eoulsan.util.ClassLoaderObjectInputStream;
/**
* This class define a task context.
* @author Laurent Jourdren
* @since 2.0
*/
public class TaskContextImpl implements TaskContext, Serializable {
/** Serialization version UID. */
private static final long serialVersionUID = 8288158811122533646L;
private static int instanceCounter;
private final int id;
private final WorkflowContext workflowContext;
private String contextName;
private final AbstractStep step;
private final Map<String, Data> inputData = new HashMap<>();
private final Map<String, AbstractData> outputData = new HashMap<>();
//
// Getters
//
public int getId() {
return this.id;
}
@Override
public String getContextName() {
return this.contextName;
}
/**
* Get the local working directory.
* @return Returns the local working directory
*/
public DataFile getLocalWorkingPathname() {
return this.workflowContext.getLocalWorkingDirectory();
}
/**
* Get the Hadoop working directory.
* @return Returns the Hadoop working directory
*/
public DataFile getHadoopWorkingPathname() {
return this.workflowContext.getHadoopWorkingDirectory();
}
/**
* Get the job directory.
* @return Returns the job directory
*/
public DataFile getJobDirectory() {
return this.workflowContext.getJobDirectory();
}
/**
* Get the task output directory.
* @return Returns the task output directory
*/
public DataFile getTaskOutputDirectory() {
return this.workflowContext.getTaskDirectory();
}
@Override
public DataFile getOutputDirectory() {
return this.workflowContext.getOutputDirectory();
}
@Override
public DataFile getStepOutputDirectory() {
return this.step.getStepOutputDirectory();
}
@Override
public String getJobId() {
return this.workflowContext.getJobId();
}
@Override
public String getJobHost() {
return this.workflowContext.getJobHost();
}
@Override
public long getContextCreationTime() {
return this.workflowContext.getContextCreationTime();
}
@Override
public DataFile getDesignFile() {
return this.workflowContext.getDesignFile();
}
@Override
public DataFile getWorkflowFile() {
return this.workflowContext.getWorkflowFile();
}
/**
* Get the application jar path.
* @return Returns the jar path
*/
public DataFile getJarPathname() {
return this.workflowContext.getJarFile();
}
@Override
public String getJobUUID() {
return this.workflowContext.getJobUUID();
}
@Override
public String getJobDescription() {
return this.workflowContext.getJobDescription();
}
@Override
public String getJobEnvironment() {
return this.workflowContext.getJobEnvironment();
}
@Override
public String getCommandName() {
return this.workflowContext.getCommandName();
}
@Override
public String getCommandDescription() {
return this.workflowContext.getCommandDescription();
}
@Override
public String getCommandAuthor() {
return this.workflowContext.getCommandAuthor();
}
@Override
public Workflow getWorkflow() {
return this.workflowContext.getWorkflow();
}
@Override
public Step getCurrentStep() {
return this.step;
}
/**
* Get the AbstractWorkflowStep object.
* @return a AbstractWorkflowStep object
*/
AbstractStep getStep() {
return this.step;
}
//
// Setters
//
@Override
public void setContextName(final String contextName) {
checkNotNull(contextName, "contextName argument cannot be null");
// TODO Check if the context name is unique for the step
this.contextName = contextName.trim();
}
//
// Other methods
//
@Override
public AbstractEoulsanRuntime getRuntime() {
return this.workflowContext.getRuntime();
}
@Override
public Settings getSettings() {
return this.workflowContext.getSettings();
}
@Override
public Logger getLogger() {
return this.workflowContext.getLogger();
}
@Override
public Data getInputData(final String portName) {
checkNotNull(portName, "portName cannot be null");
checkArgument(this.inputData.containsKey(portName),
"unknown input port name: " + portName);
return new UnmodifiableData(this.inputData.get(portName));
}
@Override
public Data getInputData(final DataFormat format) {
return getInputData(getInputPortNameForFormat(format));
}
@Override
public Data getOutputData(final String portName, final String dataName) {
return getOutputData(portName, dataName, -1);
}
@Override
public Data getOutputData(final String portName, final String dataName,
final int part) {
checkNotNull(portName, "portName cannot be null");
checkArgument(this.outputData.containsKey(portName),
"unknown output port name: " + portName);
final AbstractData data = this.outputData.get(portName);
data.setName(dataName);
data.setPart(part);
return data;
}
@Override
public Data getOutputData(final String portName, final Data origin) {
checkNotNull(origin, "origin cannot be null");
final Data result =
getOutputData(portName, origin.getName(), origin.getPart());
// Set the metadata of the new data from the origin data only if each data
// are not a list
if (!result.isList() && !origin.isList()) {
result.getMetadata().set(origin.getMetadata());
}
return result;
}
@Override
public Data getOutputData(final DataFormat format, final String dataName) {
return getOutputData(format, dataName, -1);
}
@Override
public Data getOutputData(final DataFormat format, final String dataName,
final int part) {
return getOutputData(getOutputPortNameForFormat(format), dataName);
}
@Override
public Data getOutputData(final DataFormat format, final Data origin) {
return getOutputData(getOutputPortNameForFormat(format), origin);
}
/**
* Update serialized output data. This method is used when process serialized
* task result.
* @param data data to set
*/
private void updateOutputData(final Map<String, AbstractData> data) {
checkNotNull(data, "data argument cannot be null");
checkArgument(data.size() == this.outputData.size(),
"Unexpected number of output data ("
+ this.outputData.size() + " was expected): " + data.size());
for (Map.Entry<String, AbstractData> e : data.entrySet()) {
checkArgument(this.outputData.containsKey(e.getKey()),
"Unknown port: " + e.getKey());
// Update outputData
this.outputData.put(e.getKey(), e.getValue());
}
}
@Override
public File getLocalTempDirectory() {
return EoulsanRuntime.getRuntime().getTempDirectory();
}
/**
* Create the prefix of a related task file.
* @return a string with the prefix of the task file
*/
public String getTaskFilePrefix() {
return getStep().getId() + "_context#" + getId();
}
//
// Package methods
//
/**
* Get raw access to input data stored in the object.
* @param port name of the input port
* @return a Data object
*/
public Data getInputData(final InputPort port) {
checkNotNull(port, "port cannot be null");
if (!this.inputData.containsKey(port.getName())) {
throw new EoulsanRuntimeException(
"Unknown port: " + port.getName() + " for step " + this.step.getId());
}
return this.inputData.get(port.getName());
}
/**
* Get raw access to output data stored in the object.
* @param port name of the output port
* @return a Data object
*/
Data getOutputData(final OutputPort port) {
checkNotNull(port, "port cannot be null");
if (!this.outputData.containsKey(port.getName())) {
throw new EoulsanRuntimeException(
"Unknown port: " + port.getName() + " for step " + this.step.getId());
}
return this.outputData.get(port.getName());
}
AbstractStep getWorkflowStep() {
return this.step;
}
//
// Private methods
//
/**
* Get the input port for a given format.
* @param format the format
* @return the port that matches to the format
*/
private String getInputPortNameForFormat(final DataFormat format) {
checkNotNull(format, "The format is null");
final List<StepInputPort> ports =
this.step.getWorkflowInputPorts().getPortsWithDataFormat(format);
switch (ports.size()) {
case 0:
throw new EoulsanRuntimeException("The step "
+ this.step.getId() + " do not provide an input port with format: "
+ format.getName());
case 1:
return ports.get(0).getName();
default:
throw new EoulsanRuntimeException("The step "
+ this.step.getId()
+ " provide more than one input port with format: "
+ format.getName());
}
}
/**
* Get the output port for a given format.
* @param format the format
* @return the port that matches to the format
*/
private String getOutputPortNameForFormat(final DataFormat format) {
checkNotNull(format, "The format is null");
final List<StepOutputPort> ports =
this.step.getWorkflowOutputPorts().getPortsWithDataFormat(format);
switch (ports.size()) {
case 0:
throw new EoulsanRuntimeException("The step "
+ this.step.getId() + " do not provide an output port with format: "
+ format.getName());
case 1:
return ports.get(0).getName();
default:
throw new EoulsanRuntimeException("The step "
+ this.step.getId()
+ " provide more than one output port with format: "
+ format.getName());
}
}
//
// Other methods
//
@Override
public String toString() {
return Objects.toStringHelper(this).add("id", this.id)
.add("step", this.step.getId()).add("contextName", this.contextName)
.toString();
}
//
// Serialization methods
//
/**
* Serialize the TaskContext object.
* @param file output DataFile
* @throws IOException if an error occurs while creating the file
*/
public void serialize(final File file) throws IOException {
checkNotNull(file, "file argument cannot be null");
try (OutputStream out = new FileOutputStream(file)) {
serialize(out);
}
}
/**
* Serialize the TaskContext object.
* @param file output DataFile
* @throws IOException if an error occurs while creating the file
*/
public void serialize(final DataFile file) throws IOException {
checkNotNull(file, "file argument cannot be null");
try (OutputStream out = file.create()) {
serialize(out);
}
}
/**
* Serialize the TaskContext object.
* @param out output stream
* @throws IOException if an error occurs while creating the file
*/
public void serialize(final OutputStream out) throws IOException {
checkNotNull(out, "out argument cannot be null");
try (final ObjectOutputStream oos = new ObjectOutputStream(out)) {
oos.writeObject(this);
oos.writeObject(EoulsanRuntime.getSettings());
}
}
/**
* Deserialize the TaskContext object. Warning: this method update the values
* of the settings of the Eoulsan runtime.
* @param file input DataFile
* @throws IOException if an error occurs while reading the file
*/
public static TaskContextImpl deserialize(final File file)
throws IOException {
checkNotNull(file, "file argument cannot be null");
try (InputStream in = new FileInputStream(file)) {
return deserialize(in);
}
}
/**
* Deserialize the TaskContext object. Warning: this method update the values
* of the settings of the Eoulsan runtime.
* @param file input DataFile
* @throws IOException if an error occurs while reading the file
*/
public static TaskContextImpl deserialize(final DataFile file)
throws IOException {
checkNotNull(file, "file argument cannot be null");
try (InputStream in = file.open()) {
return deserialize(in);
}
}
/**
* Deserialize the TaskContext object. Warning: this method update the values
* of the settings of the Eoulsan runtime.
* @param in input stream
* @throws IOException if an error occurs while reading the file
*/
public static TaskContextImpl deserialize(final InputStream in)
throws IOException {
checkNotNull(in, "in argument cannot be null");
try (final ObjectInputStream ois = new ClassLoaderObjectInputStream(in)) {
// Read TaskContext object
final TaskContextImpl result = (TaskContextImpl) ois.readObject();
// Read Settings object
final Settings settings = (Settings) ois.readObject();
// Overwrite current Settings of Eoulsan runtime
EoulsanRuntime.getSettings().setSettings(settings);
return result;
} catch (ClassNotFoundException e) {
throw new EoulsanRuntimeException(e);
}
}
/**
* Serialize output data.
* @param file output file
* @throws IOException if an error occurs while creating the file
*/
public void serializeOutputData(final File file) throws IOException {
checkNotNull(file, "file argument cannot be null");
serializeOutputData(new FileOutputStream(file));
}
/**
* Serialize output data.
* @param file output DataFile
* @throws IOException if an error occurs while creating the file
*/
public void serializeOutputData(final DataFile file) throws IOException {
checkNotNull(file, "file argument cannot be null");
serializeOutputData(file.create());
}
/**
* Serialize output data.
* @param out output stream
* @throws IOException if an error occurs while creating the file
*/
public void serializeOutputData(final OutputStream out) throws IOException {
checkNotNull(out, "out argument cannot be null");
final ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(this.outputData);
oos.close();
}
/**
* Deserialize output data.
* @param file input datafile
* @throws IOException if an error occurs while reading the file
*/
public void deserializeOutputData(final DataFile file) throws IOException {
checkNotNull(file, "file argument cannot be null");
deserializeOutputData(file.open());
}
/**
* Deserialize output data.
* @param file input file
* @throws IOException if an error occurs while reading the file
*/
public void deserializeOutputData(final File file) throws IOException {
checkNotNull(file, "file argument cannot be null");
deserializeOutputData(new FileInputStream(file));
}
/**
* Deserialize output data.
* @param in input stream
* @throws IOException if an error occurs while reading the file
*/
public void deserializeOutputData(final InputStream in) throws IOException {
checkNotNull(in, "in argument cannot be null");
try {
final ObjectInputStream ois = new ClassLoaderObjectInputStream(in);
// Read TaskContext object
@SuppressWarnings("unchecked")
final Map<String, AbstractData> outputData =
(Map<String, AbstractData>) ois.readObject();
ois.close();
// Update serialized data
updateOutputData(outputData);
} catch (ClassNotFoundException e) {
throw new EoulsanRuntimeException(e);
}
}
//
// Constructor
//
/**
* Constructor.
* @param step step related to the context
*/
TaskContextImpl(final WorkflowContext workflowContext,
final AbstractStep step, final Map<InputPort, Data> inputData,
final Map<OutputPort, AbstractData> outputData) {
checkNotNull(workflowContext, "workflow context cannot be null");
checkNotNull(step, "step cannot be null");
synchronized (TaskContextImpl.class) {
this.id = (++instanceCounter);
}
this.contextName = "context" + this.id;
this.workflowContext = workflowContext;
this.step = step;
// Copy input and output data
for (Map.Entry<InputPort, Data> e : inputData.entrySet()) {
this.inputData.put(e.getKey().getName(), e.getValue());
}
for (Map.Entry<OutputPort, AbstractData> e : outputData.entrySet()) {
this.outputData.put(e.getKey().getName(), e.getValue());
}
}
}