/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 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 Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.processor.pipeline.choose;
import org.apache.commons.collections.CollectionUtils;
import org.orbeon.dom.Element;
import org.orbeon.oxf.common.ValidationException;
import org.orbeon.oxf.processor.*;
import org.orbeon.oxf.processor.pipeline.PipelineProcessor;
import org.orbeon.oxf.processor.pipeline.ast.*;
import org.orbeon.oxf.xml.NamespaceMapping;
import org.orbeon.oxf.xml.XMLConstants;
import org.orbeon.oxf.xml.dom4j.Dom4jUtils;
import org.orbeon.oxf.xml.dom4j.LocationData;
import java.util.*;
/**
* <p><b>Usage</b>
* <p>This processor is not intended to be used by itelf. Instead, it is used by
* the PipelineProcessor (so we can keep the PipelineProcessor simpler and
* see the "choose" functionnality as if it was just an other processor).
* <p/>
* <p><b>Lifecycle</b>
* <ol>
* <li>createInput("config") is called.
* <li>createInstance() is called.
* <li>At this point the config (<p:choose> block) is read and we determine
* for each branch all the inputs (<input ref="..."> with no corresponding
* <output id="...">) and all the outputs (<output id="..."> with no
* <input ref="...">).
* <li>We ensure that the collection of outputs is exactly the
* same for each branch.
* <li>We create a pipeline config with the text of each branch, adding the
* appropriate <p:param> and appropriate ref="..." attribute to reference
* those parameters.
* <li>The collections of pipelines can be cached.
* <li>We read the input referenced in the <p:choose> and choose which pipeline
* has to be executed.
* <li>We connect the selected pipeline with the input of this processor.
* <li>We start every all the outputs of the pipeline, store the result in a
* SAXStore and play it back when the read of the corresponding output
* is called.
* </ol>
*/
public class AbstractChooseProcessor extends ProcessorImpl implements AbstractProcessor {
public static final String CHOOSE_DATA_INPUT = "$data";
private ASTChoose chooseAST;
private Object validity;
public AbstractChooseProcessor(ASTChoose chooseAST, Object validity) {
this.chooseAST = chooseAST;
this.validity = validity;
setLocationData(chooseAST.getLocationData());
}
public Processor createInstance() {
// We store here the "refs with no id" and "ids with no ref" for each branch.
// Those are list of collections (one collection for each branch).
final List refsWithNoId = new ArrayList();
final List idsWithNoRef = new ArrayList();
final List paramRefs = new ArrayList();
for (Iterator astIterator = chooseAST.getWhen().iterator(); astIterator.hasNext();) {
// Get info about id used in this branch
final ASTWhen when = (ASTWhen) astIterator.next();
IdInfo idInfo = when.getIdInfo();
paramRefs.add(idInfo.getOutputRefs());
// Determine all <p:input ref="..."> with no <p:output id="...">.
// Those are the inputs of this processor.
final Set branchRefsWithNoId = new HashSet(idInfo.getInputRefs());
branchRefsWithNoId.removeAll(idInfo.getOutputIds());
refsWithNoId.add(branchRefsWithNoId);
// Determine all <p:output id="..."> with no <p:input ref="...">.
// Those are the outputs of this processor.
final Set branchIdsWithNoRef = new HashSet(idInfo.getOutputIds());
branchIdsWithNoRef.removeAll(idInfo.getInputRefs());
idsWithNoRef.add(branchIdsWithNoRef);
}
// Make sure that the "ids with no ref" are the same for each branch
if (idsWithNoRef.size() > 1) {
final Collection firstBranchIdsWithNoRef = (Collection) idsWithNoRef.get(0);
int branchId = 0;
for (Iterator i = idsWithNoRef.iterator(); i.hasNext();) {
branchId++;
final Collection branchIdsWithNoRef = (Collection) i.next();
if (branchIdsWithNoRef != firstBranchIdsWithNoRef &&
!CollectionUtils.isEqualCollection(branchIdsWithNoRef, firstBranchIdsWithNoRef))
throw new ValidationException("ASTChoose branch number " + branchId +
" does not declare the same ids " + branchIdsWithNoRef.toString() +
" as the previous branches " + firstBranchIdsWithNoRef.toString(), getLocationData());
}
}
// Make sure that the "param ref" are the same for each branch
if (paramRefs.size() > 1) {
final Collection firstBranchParamRefs = (Collection) paramRefs.get(0);
int branchId = 0;
for (Iterator i = paramRefs.iterator(); i.hasNext();) {
branchId++;
final Collection branchParamRefs = (Collection) i.next();
if (branchParamRefs != firstBranchParamRefs &&
!CollectionUtils.isEqualCollection(branchParamRefs, firstBranchParamRefs))
throw new ValidationException("ASTChoose branch number " + branchId +
" does not declare the same refs " + branchParamRefs.toString() +
" as the previous branches " + firstBranchParamRefs.toString(), getLocationData());
}
}
// Compute the union of "refs with no id" for all the branches
final Set allRefsWithNoId = new HashSet();
for (Iterator i = refsWithNoId.iterator(); i.hasNext();)
allRefsWithNoId.addAll((Set) i.next());
// Create the list of inputs based on allRefsWithNoId
final List astParams = new ArrayList();
for (int i = 0; i < 2; i++) {
final Set parameters;
if (i == 0) {
parameters = allRefsWithNoId;
} else {
parameters = new HashSet();
parameters.addAll((Set) idsWithNoRef.get(0));
parameters.addAll((Set) paramRefs.get(0));
}
for (Iterator j = parameters.iterator(); j.hasNext();) {
final String paramName = (String) j.next();
ASTParam astParam = new ASTParam();
astParam.setType(i == 0 ? ASTParam.INPUT : ASTParam.OUTPUT);
astParam.setName(paramName);
astParams.add(astParam);
}
}
// For each branch, create a new pipeline processor
final List<Processor> branchProcessors = new ArrayList();
final List branchConditions = new ArrayList();
final List<NamespaceMapping> branchNamespaces = new ArrayList<NamespaceMapping>();
for (Iterator astIterator = chooseAST.getWhen().iterator(); astIterator.hasNext();) {
final ASTWhen astWhen = (ASTWhen) astIterator.next();
// Save condition
branchConditions.add(astWhen.getTest());
// Get namespaces declared at this point in the pipeline
if (astWhen.getNode() != null && astWhen.getNamespaces().mapping.size() != 0) {
throw new ValidationException("ASTWhen cannot have both a node and namespaces defined", astWhen.getLocationData());
}
branchNamespaces.add(astWhen.getNode() != null
? new NamespaceMapping(Dom4jUtils.getNamespaceContextNoDefault((Element) astWhen.getNode()))
: astWhen.getNamespaces());
// Add an identity processor to connect the output of the branch to
// the <param type="output"> of the pipeline
final Set idsToConvert = (Set) idsWithNoRef.get(0);
for (Iterator i = idsToConvert.iterator(); i.hasNext();) {
final String id = (String) i.next();
final ASTProcessorCall identityConnector = new ASTProcessorCall(XMLConstants.IDENTITY_PROCESSOR_QNAME);
{
identityConnector.addInput(new ASTInput("data", new ASTHrefId(new ASTOutput(null, id))));
final ASTParam outParam = new ASTParam(ASTParam.OUTPUT, id);
final LocationData locDat = Dom4jUtils.getLocationData();
final ASTOutput astOut = new ASTOutput("data", outParam);
astOut.setLocationData(locDat);
identityConnector.addOutput(astOut);
}
astWhen.addStatement(identityConnector);
}
final ASTPipeline astPipeline = new ASTPipeline();
astPipeline.setValidity(validity);
astPipeline.getParams().addAll(astParams);
astPipeline.getStatements().addAll(astWhen.getStatements());
astPipeline.setNode(astWhen.getNode());
final Processor pipelineProcessor = new PipelineProcessor(astPipeline);
if (getId() != null)
pipelineProcessor.setId(getId() + "-branch" + branchProcessors.size());
branchProcessors.add(pipelineProcessor);
}
return new ConcreteChooseProcessor(getId(), getLocationData(),
branchConditions, branchNamespaces, branchProcessors,
allRefsWithNoId, (Set) idsWithNoRef.get(0), (Set) paramRefs.get(0));
}
}