org.mapfish.print.processor.ProcessorDependencyGraph Maven / Gradle / Ivy
package org.mapfish.print.processor;
import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;
import jsr166y.RecursiveTask;
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 javax.annotation.Nonnull;
import static org.mapfish.print.parser.ParserUtils.FILTER_ONLY_REQUIRED_ATTRIBUTES;
import static org.mapfish.print.parser.ParserUtils.getAttributeNames;
/**
* Represents a graph of the processors dependencies. The root nodes can execute in parallel but processors with
* dependencies must wait for their dependencies to complete before execution.
*
*
* @author jesseeichar on 3/24/14.
*/
public final class ProcessorDependencyGraph {
private static final Logger LOGGER = LoggerFactory.getLogger(ProcessorDependencyGraph.class);
private final List roots;
ProcessorDependencyGraph() {
this.roots = new ArrayList();
}
/**
* Create a ForkJoinTask for running in a fork join pool.
*
* @param values the values to use for getting the required inputs of the processor and putting the output in.
* @return a task ready to be submitted to a fork join pool.
*/
public ProcessorGraphForkJoinTask createTask(@Nonnull final Values values) {
StringBuilder missingAttributes = new StringBuilder();
final Multimap requiredAttributes = getAllRequiredAttributes();
for (String attribute : requiredAttributes.keySet()) {
if (!values.containsKey(attribute)) {
missingAttributes.append("\n\t* ").append(attribute).append(" <- ").append(requiredAttributes.get(attribute));
}
}
if (missingAttributes.length() > 0) {
throw new IllegalArgumentException("It has been found that one or more required attributes are missing from the " +
"values object:" + missingAttributes + "\n");
}
return new ProcessorGraphForkJoinTask(values);
}
/**
* Add a new root node.
*
* @param node new root node.
*/
void addRoot(final ProcessorGraphNode node) {
this.roots.add(node);
}
/**
* Get all the names of inputs that are required to be in the Values object when this graph is executed.
*/
@SuppressWarnings("unchecked")
public Multimap getAllRequiredAttributes() {
Multimap requiredInputs = HashMultimap.create();
for (ProcessorGraphNode root : this.roots) {
final BiMap inputMapper = root.getInputMapper();
for (String attr : inputMapper.keySet()) {
requiredInputs.put(attr, root.getProcessor());
}
final Object inputParameter = root.getProcessor().createInputParameter();
if (inputParameter instanceof Values) {
continue;
} else if (inputParameter != null) {
final Class> inputParameterClass = inputParameter.getClass();
final Set requiredAttributesDefinedInInputParameter = getAttributeNames(inputParameterClass,
FILTER_ONLY_REQUIRED_ATTRIBUTES);
for (String attName : requiredAttributesDefinedInInputParameter) {
try {
if (inputParameterClass.getField(attName).getType() == Values.class) {
continue;
}
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
String mappedName = ProcessorUtils.getInputValueName(
root.getProcessor().getInputPrefix(),
inputMapper, attName);
requiredInputs.put(mappedName, root.getProcessor());
}
}
}
return requiredInputs;
}
public List getRoots() {
return this.roots;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (ProcessorGraphNode root : this.roots) {
if (this.roots.indexOf(root) != 0) {
builder.append('\n');
}
builder.append("+ ");
root.toString(builder, 0);
}
return builder.toString();
}
/**
* Create a set containing all the processors in the graph.
*/
public Set> getAllProcessors() {
IdentityHashMap, Void> all = new IdentityHashMap, Void>();
for (ProcessorGraphNode, ?> root : this.roots) {
for (Processor p : root.getAllProcessors()) {
all.put(p, null);
}
}
return all.keySet();
}
/**
* A ForkJoinTask that will create ForkJoinTasks from each root and run each of them.
*/
public final class ProcessorGraphForkJoinTask extends RecursiveTask {
private final ProcessorExecutionContext execContext;
private ProcessorGraphForkJoinTask(@Nonnull final Values values) {
this.execContext = new ProcessorExecutionContext(values);
}
/**
* Cancels the complete processor graph of a print task.
*
* This is achieved by setting the cancel flag of the execution
* context, so that every processor can stop its execution.
*
* @param mayInterruptIfRunning is ignored
*/
@Override
public boolean cancel(final boolean mayInterruptIfRunning) {
this.execContext.cancel();
return super.cancel(mayInterruptIfRunning);
}
@Override
protected Values compute() {
final ProcessorDependencyGraph graph = ProcessorDependencyGraph.this;
LOGGER.debug("Starting to execute processor graph: \n" + graph);
try {
List> tasks = Lists.newArrayListWithExpectedSize(graph.roots.size());
// fork all but 1 dependencies (the first will be ran in current thread)
for (int i = 0; i < graph.roots.size(); i++) {
@SuppressWarnings("unchecked")
Optional> task = graph.roots.get(i).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 (ProcessorGraphNode.ProcessorNodeForkJoinTask task : tasks.subList(1, tasks.size())) {
task.join();
}
}
} finally {
LOGGER.debug("Finished executing processor graph: \n" + graph);
}
return this.execContext.getValues();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy