org.mapfish.print.processor.ProcessorGraphNode Maven / Gradle / Ivy
package org.mapfish.print.processor;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Lists;
import jsr166y.RecursiveTask;
import org.mapfish.print.ExceptionUtils;
import org.mapfish.print.output.Values;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
/**
* Represents one node in the Processor dependency graph ({@link ProcessorDependencyGraph}).
*
*
* @param Same as {@link org.mapfish.print.processor.Processor} In parameter
* @param Same as {@link org.mapfish.print.processor.Processor} Out parameter
* @author jesseeichar on 3/24/14.
*/
public final class ProcessorGraphNode {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessorGraphNode.class);
private final Processor processor;
private final List> dependencies = Lists.newArrayList();
private final List> requirements = Lists.newArrayList();
private final MetricRegistry metricRegistry;
/**
* Constructor.
*
* @param processor The processor associated with this node.
* @param metricRegistry registry for timing the execution time of the processor.
*/
public ProcessorGraphNode(@Nonnull final Processor processor, @Nonnull final MetricRegistry metricRegistry) {
this.processor = processor;
this.metricRegistry = metricRegistry;
}
public Processor, ?> getProcessor() {
return this.processor;
}
/**
* Add a dependency to this node.
*
* @param node the dependency to add.
*/
public void addDependency(final ProcessorGraphNode node) {
this.dependencies.add(node);
node.addRequirement(this);
}
private void addRequirement(final ProcessorGraphNode node) {
this.requirements.add(node);
}
protected List> getRequirements() {
return this.requirements;
}
/**
* Returns true if the node has requirements, that is there are other
* nodes that should be run first.
*/
public boolean hasRequirements() {
return !this.requirements.isEmpty();
}
/**
* Create a ForkJoinTask for running in a fork join pool.
*
* @param execContext the execution context, used for tracking certain aspects of the execution.
* @return a task ready to be submitted to a fork join pool.
*/
@SuppressWarnings("unchecked")
public Optional createTask(@Nonnull final ProcessorExecutionContext execContext) {
if (!execContext.tryStart(this)) {
return Optional.absent();
} else {
return Optional.of(new ProcessorNodeForkJoinTask(this, execContext));
}
}
/**
* Get the output mapper from processor.
*/
@Nonnull
public BiMap getOutputMapper() {
final BiMap outputMapper = this.processor.getOutputMapperBiMap();
if (outputMapper == null) {
return HashBiMap.create();
}
return outputMapper;
}
/**
* Return input mapper from processor.
*/
@Nonnull
public BiMap getInputMapper() {
final BiMap inputMapper = this.processor.getInputMapperBiMap();
if (inputMapper == null) {
return HashBiMap.create();
}
return inputMapper;
}
/**
* Create a string representing this node.
*
* @param builder the builder to add the string to.
* @param indent the number of steps of indent for this node
*/
public void toString(final StringBuilder builder, final int indent) {
int spaces = (indent) * 2;
for (int i = 0; i < spaces; i++) {
builder.append(' ');
}
if (indent > 0) {
builder.append("+-- ");
}
builder.append(this.processor);
for (ProcessorGraphNode dependency : this.dependencies) {
builder.append('\n');
dependency.toString(builder, indent + 1);
}
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
toString(builder, 0);
return builder.toString();
}
public String getName() {
return this.processor.toString();
}
/**
* Create a set containing all the processor at the current node and the entire subgraph.
*/
public Set extends Processor, ?>> getAllProcessors() {
IdentityHashMap, Void> all = new IdentityHashMap, Void>();
all.put(this.getProcessor(), null);
for (ProcessorGraphNode, ?> dependency : this.dependencies) {
for (Processor, ?> p : dependency.getAllProcessors()) {
all.put(p, null);
}
}
return all.keySet();
}
/**
* A ForkJoinTask that will run the processor and all of its dependencies.
*/
public static final class ProcessorNodeForkJoinTask extends RecursiveTask {
private final ProcessorExecutionContext execContext;
private final ProcessorGraphNode node;
private ProcessorNodeForkJoinTask(final ProcessorGraphNode node, final ProcessorExecutionContext execContext) {
this.node = node;
this.execContext = execContext;
}
@Override
protected Values compute() {
final Values values = this.execContext.getValues();
final Processor process = this.node.processor;
final MetricRegistry registry = this.node.metricRegistry;
final String name = ProcessorGraphNode.class.getName() + "_compute():" + process.getClass();
Timer.Context timerContext = registry.timer(name).time();
try {
In inputParameter = ProcessorUtils.populateInputParameter(process, values);
Out output;
try {
LOGGER.info("Executing process: " + process);
output = process.execute(inputParameter, this.execContext.getContext());
LOGGER.info("Succeeded in executing process: " + process);
} catch (Exception e) {
if (this.execContext.getContext().isCanceled()) {
// the processor is already canceled, so we don't care if something fails
throw new CancellationException();
} else {
LOGGER.error("Error while executing process: " + process, e);
throw ExceptionUtils.getRuntimeException(e);
}
}
if (output != null) {
ProcessorUtils.writeProcessorOutputToValues(output, process, values);
}
} finally {
this.execContext.finished(this.node);
final long processorTime = TimeUnit.MILLISECONDS.convert(timerContext.stop(), TimeUnit.NANOSECONDS);
LOGGER.debug("Time taken to run processor: '" + process.getClass() + "' was " + processorTime + " ms");
}
if (this.execContext.getContext().isCanceled()) {
throw new CancellationException();
}
executeDependencyProcessors();
return values;
}
private void executeDependencyProcessors() {
final List> dependencyNodes = this.node.dependencies;
List> tasks = new ArrayList>(dependencyNodes.size());
// fork all but 1 dependencies (the first will be ran in current thread)
for (final ProcessorGraphNode, ?> depNode : dependencyNodes) {
Optional task = depNode.createTask(this.execContext);
if (task.isPresent()) {
tasks.add(task.get());
if (tasks.size() > 1) {
task.get().fork();
}
}
}
if (!tasks.isEmpty()) {
// compute one task in current thread so as not to waste threads
tasks.get(0).compute();
for (ProcessorNodeForkJoinTask task : tasks.subList(1, tasks.size())) {
task.join();
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy