View Javadoc

1   /*
2    * Copyright 2007 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.engine.node.hierarchyrouting.HierarchyProvider;
35  import org.kuali.rice.kew.rule.NamedRuleSelector;
36  import org.kuali.rice.kew.util.Utilities;
37  import org.w3c.dom.Element;
38  import org.w3c.dom.NodeList;
39  
40  
41  /**
42   * A simple hierarchy provider that provides hierarchy based on doc content
43   * <pre>
44   * stop id="..." recipient="..." type="..."
45   *     stop ...
46   *     stop ...
47   *       stop ...
48   * </pre>
49   * @author Kuali Rice Team (rice.collab@kuali.org)
50   */
51  public class SimpleHierarchyProvider implements HierarchyProvider {
52      private static final Logger LOG = Logger.getLogger(SimpleHierarchyProvider.class);
53  
54      /**
55       * Simple implementation of Stop.  Contains stop recipient type,
56       * recipient string, and string id, and maintains pointers to
57       * parent and children stops.
58       */
59      static class SimpleStop implements Stop {
60          private static enum RecipientType { USER, WORKGROUP };
61          public SimpleStop parent;
62          public List<SimpleStop> children = new ArrayList<SimpleStop>();
63          public RecipientType type;
64          public String recipient;
65          public String id;
66  
67          public String toString() {
68              return new ToStringBuilder(this).append("id", id)
69                                              .append("recipient", recipient)
70                                              .append("type", type)
71                                              .append("parent", parent == null ? null : parent.id)
72                                              .append("children", StringUtils.join(CollectionUtils.collect(children, new Transformer() {
73                                                          public Object transform(Object o) { return ((SimpleStop) o).id; }
74                                                      })
75                                                      , ','))
76                                              .toString();
77                                                      
78          }
79          
80          public boolean equals(Object o) {
81              if (!(o instanceof SimpleStop)) return false;
82              return id.equals(((SimpleStop) o).id);
83          }
84          
85          public int hashCode() {
86              return ObjectUtils.hashCode(id);
87          }
88      }
89  
90      /**
91       * The root stop
92       */
93      private SimpleStop root;
94      /**
95       * Map of Stop id-to-Stop instance
96       */
97      private Map<String, SimpleStop> stops = new HashMap<String, SimpleStop>();
98  
99      public void init(RouteNodeInstance nodeInstance, RouteContext context) {
100         init(context.getDocumentContent().getDocument().getDocumentElement());
101     }
102 
103     /**
104      * This constructor can be used in tests
105      * @param element the root Element of the hierarchy XML
106      */
107     public void init(Element element) {
108         Element rootStop = findRootStop(element);
109         root = parseStops(rootStop, null);
110     }
111 
112     /**
113      * @param e the element at which to start the search
114      * @return the first stop element encountered
115      * @throws RuntimeException if no stop elements were encountered 
116      */
117     protected Element findRootStop(Element e) {
118         if ("stop".equals(e.getNodeName())) return e;
119         NodeList nl = e.getElementsByTagName("stop");
120         if (nl == null || nl.getLength() == 0) {
121             throw new RuntimeException("No stops found");
122         }
123         return (Element) nl.item(0);
124     }
125 
126     /**
127      * Parses a hierarchy of stop elements recursively, and populates the stops Map.
128      * @param e a stop element
129      * @param parent the parent of the current element (if any)
130      * @return the SimpleStop instance for the initial element
131      */
132     protected SimpleStop parseStops(Element e, SimpleStop parent) {
133         LOG.debug("parsing element: " + e + " parent: " + parent);
134         SimpleStop stop = parseStop(e);
135         LOG.debug("parsed stop: "+ stop);
136         stop.parent = parent;
137         if (parent != null) {
138             parent.children.add(stop);
139         }
140         stops.put(e.getAttribute("id"), stop);
141         NodeList nl = e.getChildNodes();
142         for (int i = 0; i < nl.getLength(); i++) {
143             org.w3c.dom.Node n = nl.item(i);
144             if (!(n instanceof Element)) continue;
145             parseStops((Element) n, stop);
146         }
147         return stop;
148     }
149 
150     /**
151      * Parses stop info from a stop element
152      * @param e the stop element
153      * @return a SimpleStop initialized with attribute/property information
154      */
155     protected SimpleStop parseStop(Element e) {
156         SimpleStop ss = new SimpleStop();
157         String recipient = e.getAttribute("recipient");
158         String type = e.getAttribute("type");
159         String id = e.getAttribute("id");
160         if (id == null) {
161             throw new RuntimeException("malformed document content, missing id attribute: " + e);
162         }
163         /* make optional, since ruleselector/rules can govern this ? */
164         /*
165         if (recipient == null) {
166             throw new RuntimeException("malformed document content, missing recipient attribute: " + e);
167         }
168         if (type == null) {
169             throw new RuntimeException("malformed document content, missing type attribute: " + e);
170         }
171         */
172         ss.id = id;
173         ss.recipient = recipient;
174 
175         if (!StringUtils.isEmpty(type)) {
176             SimpleStop.RecipientType rtype = SimpleStop.RecipientType.valueOf(type.toUpperCase());
177             ss.type = rtype;
178         }
179 
180         return ss;
181     }
182 
183     /* Returns the list of stops in the stops Map which have 0 children */
184     public List<Stop> getLeafStops(RouteContext context) {
185         List<Stop> leafStops = new ArrayList<Stop>();
186         for (SimpleStop stop: stops.values()) {
187             if (stop.children.size() == 0) {
188                 leafStops.add(stop);
189             }
190         }
191         return leafStops;
192     }
193 
194     /* Looks up a stop in the stops map by identifier */
195     public Stop getStopByIdentifier(String stopId) {
196         return stops.get(stopId);
197     }
198 
199     /* Returns the identifier for the specified stop */
200     public String getStopIdentifier(Stop stop) {
201         return ((SimpleStop) stop).id;
202     }
203 
204     public boolean hasStop(RouteNodeInstance nodeInstance) {
205         return nodeInstance.getNodeState("id") != null;
206     }
207     
208     public void setStop(RouteNodeInstance requestNodeInstance, Stop stop) {
209         SimpleStop ss = (SimpleStop) stop;
210         requestNodeInstance.addNodeState(new NodeState("id", getStopIdentifier(stop)));
211         //requestNodeInstance.addNodeState(new NodeState(KEWConstants.RULE_SELECTOR_NODE_STATE_KEY, "named"));
212         //requestNodeInstance.addNodeState(new NodeState(KEWConstants.RULE_NAME_NODE_STATE_KEY, "NodeInstanceRecipientRule"));
213         if (ss.recipient != null) {
214             requestNodeInstance.addNodeState(new NodeState("recipient", ((SimpleStop) stop).recipient));
215         }
216         if (ss.type != null) {
217             requestNodeInstance.addNodeState(new NodeState("type", ((SimpleStop) stop).type.name().toLowerCase()));
218         }
219     }
220     
221     public boolean equals(Stop a, Stop b) {
222         return ObjectUtils.equals(a, b);
223     }
224 
225     public Stop getParent(Stop stop) {
226         return ((SimpleStop) stop).parent;
227     }
228 
229     public boolean isRoot(Stop stop) {
230         return equals(stop, root);
231     }
232 
233     public Stop getStop(RouteNodeInstance nodeInstance) {
234         NodeState state = nodeInstance.getNodeState("id");
235         if (state == null) {
236             //return null;
237             throw new RuntimeException();
238         } else {
239             LOG.warn("id Node state on nodeinstance " + nodeInstance + ": " + state);
240             return stops.get(state.getValue());
241         }
242     }
243 
244     /* Propagates the rule selector and rule name from the hierarchy node to the request node */
245     public void configureRequestNode(RouteNodeInstance hierarchyNodeInstance, RouteNode node) {
246         Map<String, String> cfgMap = Utilities.getKeyValueCollectionAsMap(node.getConfigParams());
247         // propagate rule selector and name from hierarchy node
248         if (!cfgMap.containsKey(RouteNode.RULE_SELECTOR_CFG_KEY)) {
249             Map<String, String> hierarchyCfgMap = Utilities.getKeyValueCollectionAsMap(hierarchyNodeInstance.getRouteNode().getConfigParams());
250             node.getConfigParams().add(new RouteNodeConfigParam(node, RouteNode.RULE_SELECTOR_CFG_KEY, hierarchyCfgMap.get(RouteNode.RULE_SELECTOR_CFG_KEY)));
251         }
252         if (!cfgMap.containsKey(NamedRuleSelector.RULE_NAME_CFG_KEY)) {
253             Map<String, String> hierarchyCfgMap = Utilities.getKeyValueCollectionAsMap(hierarchyNodeInstance.getRouteNode().getConfigParams());
254             node.getConfigParams().add(new RouteNodeConfigParam(node, NamedRuleSelector.RULE_NAME_CFG_KEY, hierarchyCfgMap.get(NamedRuleSelector.RULE_NAME_CFG_KEY)));
255         }
256     }
257 }