001/** 002 * Copyright 2005-2014 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.hierarchyrouting; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022 023import org.apache.commons.collections.CollectionUtils; 024import org.apache.commons.collections.Transformer; 025import org.apache.commons.lang.ObjectUtils; 026import org.apache.commons.lang.StringUtils; 027import org.apache.commons.lang.builder.ToStringBuilder; 028import org.apache.log4j.Logger; 029import org.kuali.rice.kew.engine.RouteContext; 030import org.kuali.rice.kew.engine.node.NodeState; 031import org.kuali.rice.kew.engine.node.RouteNode; 032import org.kuali.rice.kew.engine.node.RouteNodeConfigParam; 033import org.kuali.rice.kew.engine.node.RouteNodeInstance; 034import org.kuali.rice.kew.rule.NamedRuleSelector; 035import org.kuali.rice.kew.util.Utilities; 036import org.w3c.dom.Element; 037import org.w3c.dom.NodeList; 038 039 040/** 041 * A simple hierarchy provider that provides hierarchy based on doc content 042 * <pre> 043 * stop id="..." recipient="..." type="..." 044 * stop ... 045 * stop ... 046 * stop ... 047 * </pre> 048 * @author Kuali Rice Team (rice.collab@kuali.org) 049 */ 050public class SimpleHierarchyProvider implements HierarchyProvider { 051 private static final Logger LOG = Logger.getLogger(SimpleHierarchyProvider.class); 052 053 /** 054 * Simple implementation of Stop. Contains stop recipient type, 055 * recipient string, and string id, and maintains pointers to 056 * parent and children stops. 057 */ 058 static class SimpleStop implements Stop { 059 private static enum RecipientType { USER, WORKGROUP }; 060 public SimpleStop parent; 061 public List<SimpleStop> children = new ArrayList<SimpleStop>(); 062 public RecipientType type; 063 public String recipient; 064 public String id; 065 066 public String toString() { 067 return new ToStringBuilder(this).append("id", id) 068 .append("recipient", recipient) 069 .append("type", type) 070 .append("parent", parent == null ? null : parent.id) 071 .append("children", StringUtils.join(CollectionUtils.collect(children, new Transformer() { 072 public Object transform(Object o) { return ((SimpleStop) o).id; } 073 }) 074 , ',')) 075 .toString(); 076 077 } 078 079 public boolean equals(Object o) { 080 if (!(o instanceof SimpleStop)) return false; 081 return id.equals(((SimpleStop) o).id); 082 } 083 084 public int hashCode() { 085 return ObjectUtils.hashCode(id); 086 } 087 } 088 089 /** 090 * The root stop 091 */ 092 private SimpleStop root; 093 /** 094 * Map of Stop id-to-Stop instance 095 */ 096 private Map<String, SimpleStop> stops = new HashMap<String, SimpleStop>(); 097 098 public void init(RouteNodeInstance nodeInstance, RouteContext context) { 099 init(context.getDocumentContent().getDocument().getDocumentElement()); 100 } 101 102 /** 103 * This constructor can be used in tests 104 * @param element the root Element of the hierarchy XML 105 */ 106 public void init(Element element) { 107 Element rootStop = findRootStop(element); 108 root = parseStops(rootStop, null); 109 } 110 111 /** 112 * @param e the element at which to start the search 113 * @return the first stop element encountered 114 * @throws RuntimeException if no stop elements were encountered 115 */ 116 protected Element findRootStop(Element e) { 117 if ("stop".equals(e.getNodeName())) return e; 118 NodeList nl = e.getElementsByTagName("stop"); 119 if (nl == null || nl.getLength() == 0) { 120 throw new RuntimeException("No stops found"); 121 } 122 return (Element) nl.item(0); 123 } 124 125 /** 126 * Parses a hierarchy of stop elements recursively, and populates the stops Map. 127 * @param e a stop element 128 * @param parent the parent of the current element (if any) 129 * @return the SimpleStop instance for the initial element 130 */ 131 protected SimpleStop parseStops(Element e, SimpleStop parent) { 132 LOG.debug("parsing element: " + e + " parent: " + parent); 133 SimpleStop stop = parseStop(e); 134 LOG.debug("parsed stop: "+ stop); 135 stop.parent = parent; 136 if (parent != null) { 137 parent.children.add(stop); 138 } 139 stops.put(e.getAttribute("id"), stop); 140 NodeList nl = e.getChildNodes(); 141 for (int i = 0; i < nl.getLength(); i++) { 142 org.w3c.dom.Node n = nl.item(i); 143 if (!(n instanceof Element)) continue; 144 parseStops((Element) n, stop); 145 } 146 return stop; 147 } 148 149 /** 150 * Parses stop info from a stop element 151 * @param e the stop element 152 * @return a SimpleStop initialized with attribute/property information 153 */ 154 protected SimpleStop parseStop(Element e) { 155 SimpleStop ss = new SimpleStop(); 156 String recipient = e.getAttribute("recipient"); 157 String type = e.getAttribute("type"); 158 String id = e.getAttribute("id"); 159 if (id == null) { 160 throw new RuntimeException("malformed document content, missing id attribute: " + e); 161 } 162 /* make optional, since ruleselector/rules can govern this ? */ 163 /* 164 if (recipient == null) { 165 throw new RuntimeException("malformed document content, missing recipient attribute: " + e); 166 } 167 if (type == null) { 168 throw new RuntimeException("malformed document content, missing type attribute: " + e); 169 } 170 */ 171 ss.id = id; 172 ss.recipient = recipient; 173 174 if (!StringUtils.isEmpty(type)) { 175 SimpleStop.RecipientType rtype = SimpleStop.RecipientType.valueOf(type.toUpperCase()); 176 ss.type = rtype; 177 } 178 179 return ss; 180 } 181 182 /* Returns the list of stops in the stops Map which have 0 children */ 183 public List<Stop> getLeafStops(RouteContext context) { 184 List<Stop> leafStops = new ArrayList<Stop>(); 185 for (SimpleStop stop: stops.values()) { 186 if (stop.children.size() == 0) { 187 leafStops.add(stop); 188 } 189 } 190 return leafStops; 191 } 192 193 /* Looks up a stop in the stops map by identifier */ 194 public Stop getStopByIdentifier(String stopId) { 195 return stops.get(stopId); 196 } 197 198 /* Returns the identifier for the specified stop */ 199 public String getStopIdentifier(Stop stop) { 200 return ((SimpleStop) stop).id; 201 } 202 203 public boolean hasStop(RouteNodeInstance nodeInstance) { 204 return nodeInstance.getNodeState("id") != null; 205 } 206 207 public void setStop(RouteNodeInstance requestNodeInstance, Stop stop) { 208 SimpleStop ss = (SimpleStop) stop; 209 requestNodeInstance.addNodeState(new NodeState("id", getStopIdentifier(stop))); 210 //requestNodeInstance.addNodeState(new NodeState(KewApiConstants.RULE_SELECTOR_NODE_STATE_KEY, "named")); 211 //requestNodeInstance.addNodeState(new NodeState(KewApiConstants.RULE_NAME_NODE_STATE_KEY, "NodeInstanceRecipientRule")); 212 if (ss.recipient != null) { 213 requestNodeInstance.addNodeState(new NodeState("recipient", ((SimpleStop) stop).recipient)); 214 } 215 if (ss.type != null) { 216 requestNodeInstance.addNodeState(new NodeState("type", ((SimpleStop) stop).type.name().toLowerCase())); 217 } 218 } 219 220 public boolean equals(Stop a, Stop b) { 221 return ObjectUtils.equals(a, b); 222 } 223 224 public Stop getParent(Stop stop) { 225 return ((SimpleStop) stop).parent; 226 } 227 228 public boolean isRoot(Stop stop) { 229 return equals(stop, root); 230 } 231 232 public Stop getStop(RouteNodeInstance nodeInstance) { 233 NodeState state = nodeInstance.getNodeState("id"); 234 if (state == null) { 235 //return null; 236 throw new RuntimeException(); 237 } else { 238 LOG.warn("id Node state on nodeinstance " + nodeInstance + ": " + state); 239 return stops.get(state.getValue()); 240 } 241 } 242 243 /* Propagates the rule selector and rule name from the hierarchy node to the request node */ 244 public void configureRequestNode(RouteNodeInstance hierarchyNodeInstance, RouteNode node) { 245 Map<String, String> cfgMap = Utilities.getKeyValueCollectionAsMap(node.getConfigParams()); 246 // propagate rule selector and name from hierarchy node 247 if (!cfgMap.containsKey(RouteNode.RULE_SELECTOR_CFG_KEY)) { 248 Map<String, String> hierarchyCfgMap = Utilities.getKeyValueCollectionAsMap(hierarchyNodeInstance.getRouteNode().getConfigParams()); 249 node.getConfigParams().add(new RouteNodeConfigParam(node, RouteNode.RULE_SELECTOR_CFG_KEY, hierarchyCfgMap.get(RouteNode.RULE_SELECTOR_CFG_KEY))); 250 } 251 if (!cfgMap.containsKey(NamedRuleSelector.RULE_NAME_CFG_KEY)) { 252 Map<String, String> hierarchyCfgMap = Utilities.getKeyValueCollectionAsMap(hierarchyNodeInstance.getRouteNode().getConfigParams()); 253 node.getConfigParams().add(new RouteNodeConfigParam(node, NamedRuleSelector.RULE_NAME_CFG_KEY, hierarchyCfgMap.get(NamedRuleSelector.RULE_NAME_CFG_KEY))); 254 } 255 } 256}