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}