View Javadoc
1   /**
2    * Copyright 2005-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.engine.node.hierarchyrouting;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.commons.collections.CollectionUtils;
24  import org.apache.commons.collections.Transformer;
25  import org.apache.commons.lang.ObjectUtils;
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.commons.lang.builder.ToStringBuilder;
28  import org.apache.log4j.Logger;
29  import org.kuali.rice.kew.engine.RouteContext;
30  import org.kuali.rice.kew.engine.node.NodeState;
31  import org.kuali.rice.kew.engine.node.RouteNode;
32  import org.kuali.rice.kew.engine.node.RouteNodeConfigParam;
33  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
34  import org.kuali.rice.kew.rule.NamedRuleSelector;
35  import org.kuali.rice.kew.util.Utilities;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.NodeList;
38  
39  
40  /**
41   * A simple hierarchy provider that provides hierarchy based on doc content
42   * <pre>
43   * stop id="..." recipient="..." type="..."
44   *     stop ...
45   *     stop ...
46   *       stop ...
47   * </pre>
48   * @author Kuali Rice Team (rice.collab@kuali.org)
49   */
50  public class SimpleHierarchyProvider implements HierarchyProvider {
51      private static final Logger LOG = Logger.getLogger(SimpleHierarchyProvider.class);
52  
53      /**
54       * Simple implementation of Stop.  Contains stop recipient type,
55       * recipient string, and string id, and maintains pointers to
56       * parent and children stops.
57       */
58      static class SimpleStop implements Stop {
59          private static enum RecipientType { USER, WORKGROUP };
60          public SimpleStop parent;
61          public List<SimpleStop> children = new ArrayList<SimpleStop>();
62          public RecipientType type;
63          public String recipient;
64          public String id;
65  
66          public String toString() {
67              return new ToStringBuilder(this).append("id", id)
68                                              .append("recipient", recipient)
69                                              .append("type", type)
70                                              .append("parent", parent == null ? null : parent.id)
71                                              .append("children", StringUtils.join(CollectionUtils.collect(children, new Transformer() {
72                                                          public Object transform(Object o) { return ((SimpleStop) o).id; }
73                                                      })
74                                                      , ','))
75                                              .toString();
76                                                      
77          }
78          
79          public boolean equals(Object o) {
80              if (!(o instanceof SimpleStop)) return false;
81              return id.equals(((SimpleStop) o).id);
82          }
83          
84          public int hashCode() {
85              return ObjectUtils.hashCode(id);
86          }
87      }
88  
89      /**
90       * The root stop
91       */
92      private SimpleStop root;
93      /**
94       * Map of Stop id-to-Stop instance
95       */
96      private Map<String, SimpleStop> stops = new HashMap<String, SimpleStop>();
97  
98      public void init(RouteNodeInstance nodeInstance, RouteContext context) {
99          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 }