001/**
002 * Copyright 2005-2017 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.engine.node;
017
018import org.apache.log4j.Logger;
019import org.kuali.rice.kew.engine.RouteContext;
020import org.kuali.rice.kew.engine.RouteHelper;
021import org.springframework.util.CollectionUtils;
022import org.w3c.dom.Document;
023import org.w3c.dom.Node;
024import org.w3c.dom.NodeList;
025import org.xml.sax.InputSource;
026import org.xml.sax.SAXException;
027
028import javax.xml.parsers.DocumentBuilder;
029import javax.xml.parsers.DocumentBuilderFactory;
030import javax.xml.parsers.ParserConfigurationException;
031import javax.xml.xpath.XPath;
032import javax.xml.xpath.XPathConstants;
033import javax.xml.xpath.XPathExpressionException;
034import javax.xml.xpath.XPathFactory;
035import java.io.IOException;
036import java.io.StringReader;
037import java.util.ArrayList;
038import java.util.List;
039
040/**
041 * A SplitNode {@link SplitNode} implementation which chooses branches to execute using XPath.
042 *
043 * This implementation of SplitNode expects zero or more xpath nodes which specify a branch name and an
044 * XPath expression which should resolve to a boolean value. It checks each xpath node independently and if
045 * it resolves to true the document will route down that branch in the workflow.  If the xpath resolves to false
046 * the branch will be skipped.  This allows for routing to multiple branches if the xpath expressions allow it.
047 * If none of the xpath nodes are matched then it routes to one or more default branches specified in the
048 * configuration.
049 *
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 */
052public class XPathSplitNode implements SplitNode {
053
054    private static final Logger LOG = Logger.getLogger(XPathSplitNode.class);
055
056    private XPath xPath;
057    private NodeList xpathDecisions;
058    private NodeList defaultDecisions;
059
060    @Override
061    public SplitResult process(RouteContext context, RouteHelper helper) throws Exception {
062        loadConfiguration(context);
063        List<String> branchNames = new ArrayList<String>();
064        if(xpathDecisions != null) {
065            for(int i = 0; i < xpathDecisions.getLength(); i++) {
066                Node xpathDecision = xpathDecisions.item(i);
067                String xpathExpression = xpathDecision.getAttributes().getNamedItem("expression").getNodeValue();
068                String branchName = xpathDecision.getAttributes().getNamedItem("branchName").getNodeValue();
069                if((Boolean)getXPath().evaluate(xpathExpression, context.getDocumentContent().getDocument(), XPathConstants.BOOLEAN)) {
070                    branchNames.add(branchName);
071                }
072            }
073        }
074
075        if(CollectionUtils.isEmpty(branchNames) && defaultDecisions != null) {
076            for(int i = 0; i < defaultDecisions.getLength(); i++) {
077                Node xpathDecision = defaultDecisions.item(i);
078                String branchName = xpathDecision.getAttributes().getNamedItem("branchName").getNodeValue();
079                branchNames.add(branchName);
080            }
081        }
082        return new SplitResult(branchNames);
083    }
084
085    private void loadConfiguration(RouteContext context) {
086        try {
087            String contentFragment = context.getNodeInstance().getRouteNode().getContentFragment();
088            DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
089            Document nodeContent = db.parse(new InputSource(new StringReader(contentFragment)));
090
091            this.xpathDecisions = (NodeList)getXPath().evaluate("//split/branchDecisions/xpath", nodeContent, XPathConstants.NODESET);
092            this.defaultDecisions = (NodeList)getXPath().evaluate("//split/branchDecisions/default", nodeContent, XPathConstants.NODESET);
093        } catch (ParserConfigurationException e) {
094            LOG.error("Caught parser exception processing XPathSplitNode configuration", e);
095        } catch (SAXException e) {
096            LOG.error("Caught SAX exception processing XPathSplitNode configuration", e);
097        } catch (IOException e) {
098            LOG.error("Caught IO exception processing XPathSplitNode configuration", e);
099        } catch (XPathExpressionException e) {
100            LOG.error("Caught XPath exception processing XPathSplitNode configuration", e);
101        }
102    }
103
104    public XPath getXPath() {
105        if(this.xPath == null) {
106            XPathFactory xPathFactory = XPathFactory.newInstance();
107            this.xPath = xPathFactory.newXPath();
108        }
109        return xPath;
110    }
111}