/** * Create a {@link ProcessorDependencyGraph}. * * @param processors the processors that will be part of the graph * @return a {@link org.mapfish.print.processor.ProcessorDependencyGraph} constructed from the * passed in processors */ @SuppressWarnings("unchecked") public ProcessorDependencyGraph build(final List<? extends Processor> processors) { ProcessorDependencyGraph graph = new ProcessorDependencyGraph(); final Map<String, ProcessorGraphNode<Object, Object>> provideBy = new HashMap<String, ProcessorGraphNode<Object, Object>>(); final Map<String, Class<?>> outputTypes = new HashMap<String, Class<?>>(); final List<ProcessorGraphNode<Object, Object>> nodes = new ArrayList<ProcessorGraphNode<Object, Object>>(processors.size()); for (Processor<Object, Object> processor : processors) { final ProcessorGraphNode<Object, Object> node = new ProcessorGraphNode<Object, Object>(processor, this.metricRegistry); for (OutputValue value : getOutputValues(node)) { String outputName = value.getName(); if (provideBy.containsKey(outputName)) { // there is already an output with the same name if (value.canBeRenamed()) { // if this is just a debug output, we can simply rename it outputName = outputName + "_" + UUID.randomUUID().toString(); } else { throw new IllegalArgumentException( "Multiple processors provide the same output mapping: '" + processor + "' and '" + provideBy.get(outputName) + "' both provide: '" + outputName + "'. You have to rename one of the outputs and the corresponding input so that" + " there is no ambiguity with regards to the input a processor consumes."); } } provideBy.put(outputName, node); outputTypes.put(outputName, value.getType()); } nodes.add(node); } ArrayList<ProcessorDependency> allDependencies = Lists.newArrayList(this.dependencies); for (ProcessorGraphNode<Object, Object> node : nodes) { if (node.getProcessor() instanceof CustomDependencies) { CustomDependencies custom = (CustomDependencies) node.getProcessor(); allDependencies.addAll(custom.createDependencies(nodes)); } } final SetMultimap<ProcessorGraphNode<Object, Object>, InputValue> inputsForNodes = cacheInputsForNodes(nodes); for (ProcessorGraphNode<Object, Object> node : nodes) { final Set<InputValue> inputs = inputsForNodes.get(node); // check explicit, external dependencies between nodes checkExternalDependencies(allDependencies, node, nodes); // check input/output value dependencies for (InputValue input : inputs) { final ProcessorGraphNode<Object, Object> solution = provideBy.get(input.getName()); if (solution != null && solution != node) { // check that the provided output has the same type final Class<?> inputType = input.getType(); final Class<?> outputType = outputTypes.get(input.getName()); if (inputType.isAssignableFrom(outputType)) { solution.addDependency(node); } else { throw new IllegalArgumentException( "Type conflict: Processor '" + solution.getName() + "' provides an output with name '" + input.getName() + "' and of type '" + outputType + " ', while " + "processor '" + node.getName() + "' expects an input of that name with type '" + inputType + "'! Please rename one of the attributes in the mappings of the processors."); } } } } // once all dependencies are discovered, select the root nodes for (ProcessorGraphNode<Object, Object> node : nodes) { // a root node is a node that has no requirements (that is no other node // should be executed before the node) and that has only external inputs if (!node.hasRequirements() && hasNoneOrOnlyExternalInput(node, inputsForNodes.get(node), provideBy)) { graph.addRoot(node); } } Assert.isTrue( graph.getAllProcessors().containsAll(processors), "'" + graph + "' does not contain all the processors: " + processors); return graph; }