/*
* 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.task;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import org.apache.log4j.Logger;
import org.objectweb.proactive.core.util.converter.ByteToObjectConverter;
import org.objectweb.proactive.core.util.converter.ObjectToByteConverter;
import org.objectweb.proactive.utils.StackTraceUtil;
import org.ow2.proactive.scheduler.common.exception.InternalSchedulerException;
import org.ow2.proactive.scheduler.common.task.TaskId;
import org.ow2.proactive.scheduler.common.task.TaskLogs;
import org.ow2.proactive.scheduler.common.task.TaskResult;
import org.ow2.proactive.scheduler.common.task.flow.FlowAction;
/**
* Class representing the task result.
* A task result can be an exception or a serializable object that you have to cast into your own type.
* Before getting the object it is recommended that you call the hadException() method.
* It will tell you if an exception occurred in the task that generate this result.
*
* @author The ProActive Team
* @since ProActive Scheduling 0.9
*/
@XmlAccessorType(XmlAccessType.FIELD)
public class TaskResultImpl implements TaskResult {
public static final Logger logger = Logger.getLogger(TaskResultImpl.class);
/** The task identification of the result */
private TaskId id = null;
/** The value of the result if no exception occurred as a byte array */
private byte[] serializedValue = null;
/** The value of the result if no exception occurred */
private transient Serializable value = null;
private Map<String, String> metadata = new HashMap<>();
/** The exception thrown by the task as a byte array */
private byte[] serializedException = null;
/** The exception thrown by the task */
private transient Throwable exception = null;
/** Task output */
private TaskLogs output = null;
/** Description definition of this result */
private String previewerClassName = null;
/** result of the FlowScript if there was one, or null */
private FlowAction flowAction = null;
//Managed by taskInfo, this field is here only to bring taskDuration to core AO
private long taskDuration = -1;
/**
* A map which contains the propagated variables of the previous (dependent)
* tasks.
*/
private Map<String, byte[]> propagatedVariables;
public TaskResultImpl(TaskId id, byte[] serializedValue, byte[] serializedException, TaskLogs output,
Map<String, String> metadata, Map<String, byte[]> propagatedVariables) {
this(id, serializedValue, serializedException, output);
this.propagatedVariables = propagatedVariables;
this.metadata = metadata;
}
public TaskResultImpl(TaskId id, byte[] serializedValue, byte[] serializedException, TaskLogs output) {
this(id, output);
this.serializedValue = serializedValue;
this.serializedException = serializedException;
this.output = output;
}
private TaskResultImpl(TaskId id, TaskLogs output) {
this.id = id;
this.output = output;
}
public TaskResultImpl(TaskId id, Throwable exception) {
this(id, exception, null, 0);
}
/**
* Return a new instance of task result represented by a task id, its result and its output.
*
* @param id the identification of the task that send this result.
* @param value the result of the task.
* @param output the output of the task.
* @param execDuration the execution duration of the task itself
*/
public TaskResultImpl(TaskId id, Serializable value, TaskLogs output, long execDuration) {
this(id, output);
this.taskDuration = execDuration;
this.value = value;
try {
if (value instanceof byte[]) {
// object is already a byte array
this.serializedValue = (byte[]) value;
} else {
//try to serialize user result
this.serializedValue = ObjectToByteConverter.ObjectStream.convert(value);
}
} catch (IOException ioe1) {
//error while serializing
logger.error("", ioe1);
try {
//try to serialize the cause as an exception
this.serializedException = ObjectToByteConverter.ObjectStream.convert(ioe1);
} catch (IOException ioe2) {
//cannot serialize the cause
logger.error("", ioe2);
try {
//serialize a NotSerializableException with the cause message
this.serializedException = ObjectToByteConverter.ObjectStream.convert(new NotSerializableException(ioe2.getMessage()));
} catch (IOException ioe3) {
//this should not append as the NotSerializableException is serializable and
//the given argument is a string (also serializable)
logger.error("", ioe3);
}
}
}
}
/**
* Return a new instance of task result represented by a task id and its exception.
*
* @param id the identification of the task that send this result.
* @param exception the exception that occurred in the task.
* @param output the output of the task.
* @param execDuration the execution duration of the task itself
*/
public TaskResultImpl(TaskId id, Throwable exception, TaskLogs output, long execDuration) {
this(id, output);
this.taskDuration = execDuration;
this.exception = exception;
this.serializedException = computeSerializedException(exception);
}
private static byte[] computeSerializedException(Throwable exception) {
byte[] answer = new byte[0];
try {
//try to serialize the user exception
answer = ObjectToByteConverter.ObjectStream.convert(exception);
} catch (IOException ioe2) {
//cannot serialize the exception
logger.error("", ioe2);
try {
//serialize a NotSerializableException with the cause message
answer = ObjectToByteConverter.ObjectStream.convert(new NotSerializableException(ioe2.getMessage()));
} catch (IOException ioe3) {
//this should not append as the NotSerializableException is serializable and
//the given argument is a string (also serializable)
logger.error("", ioe3);
}
}
return answer;
}
/**
* If a FlowScript was executed on this task, its result
* is stored so that the action can be performed later when
* processed by the core.
*
* return the Action to perform for this task
*/
public FlowAction getAction() {
return this.flowAction;
}
/**
* If a FlowScript was executed on this task, its result
* is stored so that the action can be performed later when
* processed by the core.
*
* @param act an Control Flow action to embed in this TaskResult
*/
public void setAction(FlowAction act) {
this.flowAction = act;
}
/**
* @param l logs of the task
*/
public void setLogs(TaskLogs l) {
this.output = l;
}
/**
* @see org.ow2.proactive.scheduler.common.task.TaskResult#hadException()
*/
public boolean hadException() {
return serializedException != null;
}
public void setException(Throwable ex) {
this.exception = ex;
this.serializedException = computeSerializedException(ex);
}
/**
* @see org.ow2.proactive.scheduler.common.task.TaskResult#getTaskId()
*/
public TaskId getTaskId() {
return id;
}
/**
* @see org.ow2.proactive.scheduler.common.task.TaskResult#value()
*/
public Serializable value() throws Throwable {
if (hadException()) {
Throwable thrown = null;
try {
thrown = this.instanciateException(this.getTaskClassLoader());
} catch (IOException e) {
throw new InternalSchedulerException("Cannot instanciate exception thrown by the task " + this.id +
" : " + e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new InternalSchedulerException("Cannot instanciate exception thrown by the task " + this.id +
" : " + e.getMessage(), e);
}
throw thrown;
} else {
try {
return this.instanciateValue(this.getTaskClassLoader());
} catch (IOException e) {
logger.error("", e);
throw new InternalSchedulerException("Cannot instanciate result of the task " + this.id + " : " +
e.getMessage(), e);
} catch (ClassNotFoundException e) {
logger.error("", e);
throw new InternalSchedulerException("Cannot instanciate result of the task " + this.id + " : " +
e.getMessage(), e);
}
}
}
/**
* @see org.ow2.proactive.scheduler.common.task.TaskResult#getException()
*/
public Throwable getException() {
if (hadException()) {
try {
return this.instanciateException(this.getTaskClassLoader());
} catch (IOException e) {
return new InternalSchedulerException("Cannot instanciate exception thrown by the task " + this.id +
" : " + e.getMessage(), e);
} catch (ClassNotFoundException e) {
return new InternalSchedulerException("Cannot instanciate exception thrown by the task " + this.id +
" : " + e.getMessage(), e);
}
} else {
return null;
}
}
/**
* Return the exception message, if any, used by dozer mappings.
*
* @return
*/
public String getExceptionMessage() {
if (hadException()) {
return StackTraceUtil.getStackTrace(getException());
} else {
return null;
}
}
/**
* Set the class that is able to describe this result. See ResultPreview.
*
* @param descClass the name of the class that is able to describe this result.
*/
public void setPreviewerClassName(String descClass) {
if (this.previewerClassName != null) {
throw new RuntimeException("Previewer class cannot be changed");
} else {
this.previewerClassName = descClass;
}
}
/**
* Instanciate thrown exception if any from the serialized version.
* This instanciation must not be performed in a dedicated classloader to
* avoid ClassCastException with the user's code.
* @param cl the classloader where to instanciate the object. Can be null : object is instanciated
* in the default caller classloader.
* @return the exception that has been thrown if any.
* @throws ClassNotFoundException
* @throws IOException
*/
private Throwable instanciateException(ClassLoader cl) throws IOException, ClassNotFoundException {
if (this.serializedException != null && this.exception == null) {
this.exception = (Throwable) ByteToObjectConverter.ObjectStream.convert(this.serializedException, cl);
}
return this.exception;
}
/**
* Instanciate value if any from the serialized version.
* This instanciation must not be performed in a dedicated classloader to
* avoid ClassCastException with the user's code.
* @param cl the classloader where to instanciate the object. Can be null : object is instanciated
* in the default caller classloader.
* @return the value if no exception has been thown.
* @throws ClassNotFoundException
* @throws IOException
*/
private Serializable instanciateValue(ClassLoader cl) throws IOException, ClassNotFoundException {
if (this.serializedValue != null && this.value == null) {
this.value = (Serializable) ByteToObjectConverter.ObjectStream.convert(this.serializedValue, cl);
}
return this.value;
}
/**
* Return the classloader to use for tasks.
* @return on worker node, the taskClassLoader. On client side, the ClassLoader that has loaded the current class.
* @throws IOException if the classloader cannot be created.
*/
private ClassLoader getTaskClassLoader() throws IOException {
ClassLoader currentCCL = Thread.currentThread().getContextClassLoader();
if (currentCCL instanceof TaskClassLoader) {
return currentCCL;
} else {
return this.getClass().getClassLoader();
}
}
/**
* Get the serializedValue.
*
* @return the serializedValue.
*/
public byte[] getSerializedValue() {
return serializedValue;
}
@Override
public Map<String, String> getMetadata() {
return metadata;
}
public void setMetadata(Map<String, String> metadata) {
this.metadata = metadata;
}
/**
* Get the serializedException.
*
* @return the serializedException.
*/
public byte[] getSerializedException() {
return serializedException;
}
/**
* @see org.ow2.proactive.scheduler.common.task.TaskResult#getOutput()
*/
public TaskLogs getOutput() {
return this.output;
}
/**
* Get the previewerClassName.
*
* @return the previewerClassName.
*/
public String getPreviewerClassName() {
return previewerClassName;
}
/**
* Get the real task duration. This duration is the CPU time usage of the associated executable.
*
* @return the real task duration.
*/
public long getTaskDuration() {
return taskDuration;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
try {
if (hadException()) {
return getException().toString();
} else {
return value().toString();
}
} catch (Throwable t) {
return "Result not available";
}
}
/**
* Sets the propagated variables.
*
* @param propagatedVariables a map of propagated variables
*/
public void setPropagatedVariables(Map<String, byte[]> propagatedVariables) {
this.propagatedVariables = propagatedVariables;
}
/**
* Returns a map of propagated variables.
*/
@Override
public Map<String, byte[]> getPropagatedVariables() {
return propagatedVariables;
}
}