/*
* YAQP - Yet Another QSAR Project: Machine Learning algorithms designed for
* the prediction of toxicological features of chemical compounds become
* available on the Web. Yaqp is developed under OpenTox (http://opentox.org)
* which is an FP7-funded EU research project.
*
* Copyright (C) 2009-2010 Pantelis Sopasakis & Charalampos Chomenides
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.opentox.core.processors;
import java.util.ArrayList;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.opentox.core.exceptions.ProcessorException;
import org.opentox.core.exceptions.YaqpException;
import org.opentox.core.interfaces.JProcessor;
import static org.opentox.core.util.MultiProcessorStatus.STATUS;
import static org.opentox.core.exceptions.Cause.*;
/**
* <p>
* The scope of this processor is to use a single processor in a multi-threaded
* approach. Specifically, if you need to use a specific processor to perform a set
* or processes, that is you want your processor to do a (considerable) number of mutually
* independent jobs simultaneously, you can use a BatchProcessor. A BatchProcessor is a
* processor it self, exploiting some other processor which is the one that does the
* real job. A Batch Processor guarrantees that a set of jobs will run in parallel.
* Provide the set of inputs to the BatchProcecssor and manipulate it as a single
* {@link JProcessor Processor}.
* </p>
* <p>
* A BatchProcessor is an extension of the default Yaqp {@link Processor }, hence
* enabled by default. There are three ways to build your custom BatchProcessor: You
* can implement the interface {@link BatchProcessor }, extends the abstract class
* {@link AbstractBatchProcessor } or do both. The corresponding prototypes are:
* <ul>
* <li>class MyBatchProcessor<I,O,P extends JProcessor<I,O>>
* implements JBatchProcessor<I,O,P<I,O>></li>
* <li>class MyBatchProcessor<I,O,P extends JProcessor<I,O>>
* extends AbstractProcessor<I,O,P<I,O>></li>
* </ul>
* </p>
*
* @author Sopasakis Pantelis
* @author Charalampos Chomenides
* @param <Input> Input to the processor
* @param <Output> Output from the processor
* @param <P> Type of internal processor
*/
@SuppressWarnings({"unchecked"})
public class BatchProcessor<Input, Output, P extends JProcessor<Input, Output>>
extends AbstractBatchProcessor<Input, Output, P> {
private String PROPERTY = getStatus().getClass().getName();
private int corePoolSize = 4, maxPoolSize = 4;
private int timeout = 1;
private TimeUnit timeUnit = TimeUnit.HOURS;
private ThreadPoolExecutor parallel_executor;
private BatchProcessor() {
super();
}
public BatchProcessor(final P processor) {
this();
setProcessor(processor);
}
public BatchProcessor(final P processor, final int corePoolSize, final int maxPoolSize) {
this.corePoolSize = corePoolSize;
this.maxPoolSize = maxPoolSize;
setProcessor(processor);
}
public void setTimeOut(final int timeout, final TimeUnit timeout_unit) {
this.timeout = timeout;
this.timeUnit = timeout_unit;
}
/**
* @param data A list of data to be processed by the BatchProcessor.
* @return Batch Processor Output
* @throws YaqpException In case something goes wrong while processing
* input data.
*/
public ArrayList<Output> process(ArrayList<Input> data) throws YaqpException {
if (data == null) {
throw new NullPointerException("Null input to batch processor");
}
/**
* If the internal processor is synchronized, run all processes in
* a single query.
*/
if (getProcessor().isSynchronized()) {
corePoolSize = 1;
maxPoolSize = 1;
}
// A PROCESSOR WHICH ALWAYS RETURNS 'null'.
Processor n = new Processor() {
public Object process(Object data) throws YaqpException {
return null;
}
};
P nullProcessor = (P) n;
final ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(data.size() + 2);
parallel_executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, 1, TimeUnit.MINUTES, queue);
getStatus().setMessage("processing in progress");
long start_time = 0;
Future[] future_array = new Future[data.size()];
ArrayList<Output> result = new ArrayList<Output>();
start_time = System.currentTimeMillis();
for (int i = 0; i < data.size(); i++) {
getStatus().increment(STATUS.INITIALIZED);
if (getProcessor().isEnabled()) {
future_array[i] = parallel_executor.submit(getCallable(getProcessor(), data.get(i)));
} else {
future_array[i] = parallel_executor.submit(getCallable(nullProcessor, null));
}
}
parallel_executor.shutdown();
/**
* Await for the processors to terminate, but no more than a fixed timeout.
*/
try {
parallel_executor.awaitTermination(timeout, timeUnit);
handleTimeOut();
} catch (InterruptedException ex) {
getStatus().setMessage("interrupted - not running");
getStatus().completed();
firePropertyChange(PROPERTY, null, getStatus());
throw new ProcessorException(XBP1, "A processor was brutally interrupted while performing an operation", ex);
}
for (int i = 0; i < data.size(); i++) {
long error_start_time = 0;
try {
// Successful processing...
error_start_time = System.currentTimeMillis();
if (future_array[i].isDone()) {
try {
result.add((Output) future_array[i].get());
} catch (ClassCastException ex) {
if (isfailSensitive()) {
throw new ClassCastException("Class Cast is imposible for output of batch processor");
}
}
getStatus().increment(STATUS.PROCESSED);
} else {
result.add((Output) null);
getStatus().increment(STATUS.ERROR);
}
firePropertyChange(PROPERTY, null, getStatus());
} catch (InterruptedException ex) {
getStatus().setMessage("completed unexpectedly");
getStatus().completed();
firePropertyChange(PROPERTY, null, getStatus());
throw new ProcessorException(XBP2, "Unknown cause of exception (Interruption)");
} catch (ExecutionException ex) {
/**
* If a processor error happens and the Parallel Processor is fail
* sensitive it should throw an exception and terminate.
*/
if (isfailSensitive()) {
parallel_executor.shutdownNow(); // force shut down.
getStatus().setMessage("completed unsuccessfully");
getStatus().completed();
firePropertyChange(PROPERTY, null, getStatus());
throw new ProcessorException(XBP3, "The batch Processor is fail-sensitive", ex);
}
result.add(null); // If the processor fails, the result should be null.
getStatus().increment(STATUS.ERROR);
getStatus().incrementElapsedTime(STATUS.ERROR, System.currentTimeMillis() - error_start_time);
firePropertyChange(PROPERTY, null, getStatus());
}
}
getStatus().incrementElapsedTime(STATUS.PROCESSED, System.currentTimeMillis() - start_time);
getStatus().setMessage("completed successfully");
getStatus().completed();
firePropertyChange(PROPERTY, null, getStatus());
return result;
}
protected Callable getCallable(final P processor, final Input input) {
Callable callable = new Callable() {
public Output call() throws Exception {
Object o;
o = processor.process(input);
return (Output) o;
}
};
return callable;
}
/**
* Specifies how to cope with timeouts.
* @throws YaqpException
*/
protected void handleTimeOut() throws YaqpException {
if (!parallel_executor.isTerminated()) {
if (isfailSensitive()) {
parallel_executor.shutdownNow();
getStatus().setMessage("completed unsuccessfully - timeout");
getStatus().completed();
firePropertyChange(PROPERTY, null, getStatus());
throw new ProcessorException(XBP7, "Timeout");
}
}
}
}