001 /**
002 * Copyright 2005-2011 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 */
016 package org.kuali.rice.kew.engine.node.hierarchyrouting;
017
018 import java.util.ArrayList;
019 import java.util.HashMap;
020 import java.util.List;
021 import java.util.Map;
022
023 import org.apache.commons.collections.CollectionUtils;
024 import org.apache.commons.collections.Transformer;
025 import org.apache.commons.lang.ObjectUtils;
026 import org.apache.commons.lang.StringUtils;
027 import org.apache.commons.lang.builder.ToStringBuilder;
028 import org.apache.log4j.Logger;
029 import org.kuali.rice.kew.engine.RouteContext;
030 import org.kuali.rice.kew.engine.node.NodeState;
031 import org.kuali.rice.kew.engine.node.RouteNode;
032 import org.kuali.rice.kew.engine.node.RouteNodeConfigParam;
033 import org.kuali.rice.kew.engine.node.RouteNodeInstance;
034 import org.kuali.rice.kew.rule.NamedRuleSelector;
035 import org.kuali.rice.kew.util.Utilities;
036 import org.w3c.dom.Element;
037 import 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 */
050 public 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 }