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.krad.uif.container;
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.lang.StringUtils;
24  import org.kuali.rice.core.api.util.tree.Node;
25  import org.kuali.rice.core.api.util.tree.Tree;
26  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
27  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
28  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
29  import org.kuali.rice.krad.uif.UifConstants;
30  import org.kuali.rice.krad.uif.component.BindingInfo;
31  import org.kuali.rice.krad.uif.component.Component;
32  import org.kuali.rice.krad.uif.component.DataBinding;
33  import org.kuali.rice.krad.uif.element.Message;
34  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
35  import org.kuali.rice.krad.uif.util.ComponentUtils;
36  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
37  
38  /**
39   * Group component that is backed by a <code>Tree</code> data structure and typically
40   * rendered as a tree in the user interface
41   *
42   * @author Kuali Rice Team (rice.collab@kuali.org)
43   */
44  @BeanTags({@BeanTag(name = "treeGroup-bean", parent = "Uif-TreeGroup"),
45          @BeanTag(name = "treeSection-bean", parent = "Uif-TreeSection"),
46          @BeanTag(name = "treeSubSection-bean", parent = "Uif-TreeSubSection")})
47  public class TreeGroup extends Group implements DataBinding {
48      private static final long serialVersionUID = 5841343037089286740L;
49  
50      private String propertyName;
51      private BindingInfo bindingInfo;
52  
53      private Map<Class<?>, NodePrototype> nodePrototypeMap;
54      private NodePrototype defaultNodePrototype;
55  
56      private Tree<Group, Message> treeGroups;
57  
58      private org.kuali.rice.krad.uif.widget.Tree tree;
59  
60      public TreeGroup() {
61          super();
62  
63          treeGroups = new Tree<Group, Message>();
64      }
65  
66      /**
67       * The following actions are performed:
68       *
69       * <ul>
70       * <li>Set fieldBindModelPath to the collection model path (since the fields
71       * have to belong to the same model as the collection)</li>
72       * <li>Set defaults for binding</li>
73       * <li>Calls view helper service to initialize prototypes</li>
74       * </ul>
75       */
76      @Override
77      public void performInitialization(Object model) {
78          setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
79  
80          super.performInitialization(model);
81  
82          if (bindingInfo != null) {
83              bindingInfo.setDefaults(ViewLifecycle.getActiveLifecycle().getView(), getPropertyName());
84          }
85  
86          // TODO: set object path for prototypes equal to the tree group object path?
87      }
88  
89      @Override
90      public void performApplyModel(Object model, Component parent) {
91          super.performApplyModel(model, parent);
92  
93          buildTreeGroups(model);
94      }
95  
96      /**
97       * Builds the components that will be rendered as part of the tree group
98       *
99       * <p>
100      * The component tree group mirrors the tree data structure on the model. For each node of
101      * the data structure, a corresponding <code>Message</code>  will be created for the node
102      * label, and a <code>Group</code> component for the node data. These are placed into a new
103      * node for the component tree. After the tree is built it is set as a property on the tree group
104      * to be read by the renderer
105      * </p>
106      *
107      * @param view view instance the tree group belongs to
108      * @param model object containing the view data from which the tree data will be retrieved
109      */
110     protected void buildTreeGroups(Object model) {
111         // get Tree data property
112         Tree<Object, String> treeData = ObjectPropertyUtils.getPropertyValue(model, getBindingInfo().getBindingPath());
113 
114         // build component tree that corresponds with tree data
115         Tree<Group, Message> treeGroups = new Tree<Group, Message>();
116 
117         String bindingPrefix = getBindingInfo().getBindingPrefixForNested();
118         Node<Group, Message> rootNode = buildTreeNode(treeData.getRootElement(),
119                 bindingPrefix + /* TODO: hack */ ".rootElement", "root");
120         treeGroups.setRootElement(rootNode);
121 
122         setTreeGroups(treeGroups);
123     }
124 
125     protected Node<Group, Message> buildTreeNode(Node<Object, String> nodeData, String bindingPrefix,
126             String parentNode) {
127         if (nodeData == null) {
128             return null;
129         }
130 
131         Node<Group, Message> node = new Node<Group, Message>();
132         node.setNodeType(nodeData.getNodeType());
133 
134         NodePrototype prototype = getNodePrototype(nodeData);
135 
136         Message message = ComponentUtils.copy(prototype.getLabelPrototype(), parentNode);
137         ComponentUtils.pushObjectToContext(message, UifConstants.ContextVariableNames.NODE, nodeData);
138         message.setMessageText(nodeData.getNodeLabel());
139         node.setNodeLabel(message);
140 
141         Group nodeGroup = ComponentUtils.copyComponent(prototype.getDataGroupPrototype(), bindingPrefix + ".data",
142                 parentNode);
143         ComponentUtils.pushObjectToContext(nodeGroup, UifConstants.ContextVariableNames.NODE, nodeData);
144 
145         String nodePath = bindingPrefix + ".data";
146         if (StringUtils.isNotBlank(getBindingInfo().getBindingObjectPath())) {
147             nodePath = getBindingInfo().getBindingObjectPath() + "." + nodePath;
148         }
149         ComponentUtils.pushObjectToContext(nodeGroup, UifConstants.ContextVariableNames.NODE_PATH, nodePath);
150         node.setData(nodeGroup);
151 
152         List<Node<Group, Message>> nodeChildren = new ArrayList<Node<Group, Message>>();
153 
154         int childIndex = 0;
155         for (Node<Object, String> childDataNode : nodeData.getChildren()) {
156             String nextBindingPrefix = bindingPrefix + ".children[" + childIndex + "]";
157             Node<Group, Message> childNode = buildTreeNode(childDataNode, nextBindingPrefix,
158                     "_node_" + childIndex + ("root".equals(parentNode) ? "_parent_" : "_parent") + parentNode);
159 
160             nodeChildren.add(childNode);
161 
162             // Don't forget about me:
163             ++childIndex;
164         }
165         node.setChildren(nodeChildren);
166 
167         return node;
168     }
169 
170     /**
171      * Gets the NodePrototype to use for the given Node
172      */
173     private NodePrototype getNodePrototype(Node<Object, String> nodeData) {
174         NodePrototype result = null;
175         if (nodeData != null && nodeData.getData() != null) {
176             Class<?> dataClass = nodeData.getData().getClass();
177             result = nodePrototypeMap.get(dataClass);
178 
179             // somewhat lame fallback - to do this right we'd find all entries that are assignable from the data class
180             // and then figure out which one is the closest relative
181             if (result == null) {
182                 for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
183                     if (prototypeEntry.getKey().isAssignableFrom(dataClass)) {
184                         result = prototypeEntry.getValue();
185                         break;
186                     }
187                 }
188             }
189         }
190 
191         if (result == null) {
192             result = defaultNodePrototype;
193         }
194 
195         return result;
196     }
197 
198     /**
199      * @see org.kuali.rice.krad.uif.component.Component#getComponentsForLifecycle()
200      */
201     @Override
202     public List<Component> getComponentsForLifecycle() {
203         List<Component> components = super.getComponentsForLifecycle();
204 
205         components.add(tree);
206         addNodeComponents(treeGroups.getRootElement(), components);
207 
208         return components;
209     }
210 
211     /**
212      * @see org.kuali.rice.krad.uif.component.Component#getComponentPrototypes()
213      */
214     @Override
215     public List<Component> getComponentPrototypes() {
216         List<Component> components = super.getComponentPrototypes();
217 
218         if (defaultNodePrototype != null) {
219             components.add(defaultNodePrototype.getLabelPrototype());
220             components.add(defaultNodePrototype.getDataGroupPrototype());
221         }
222 
223         if (nodePrototypeMap != null) {
224             for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
225                 NodePrototype prototype = prototypeEntry.getValue();
226                 if (prototype != null) {
227                     components.add(prototype.getLabelPrototype());
228                     components.add(prototype.getDataGroupPrototype());
229                 }
230             }
231         }
232 
233         return components;
234     }
235 
236     /**
237      * Retrieves the <code>Component</code> instances from the node for building the nested
238      * components list
239      *
240      * @param node node to pull components from
241      * @param components list to add components to
242      */
243     protected void addNodeComponents(Node<Group, Message> node, List<Component> components) {
244         if (node != null) {
245             components.add(node.getNodeLabel());
246             components.add(node.getData());
247 
248             for (Node<Group, Message> nodeChild : node.getChildren()) {
249                 addNodeComponents(nodeChild, components);
250             }
251         }
252     }
253 
254     @BeanTagAttribute(name = "propertyName")
255     public String getPropertyName() {
256         return propertyName;
257     }
258 
259     public void setPropertyName(String propertyName) {
260         this.propertyName = propertyName;
261     }
262 
263     @BeanTagAttribute(name = "bindingInfo", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
264     public BindingInfo getBindingInfo() {
265         return bindingInfo;
266     }
267 
268     public void setBindingInfo(BindingInfo bindingInfo) {
269         this.bindingInfo = bindingInfo;
270     }
271 
272     /**
273      * @return the defaultNodePrototype
274      */
275     @BeanTagAttribute(name = "defaultNodePrototype", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
276     public NodePrototype getDefaultNodePrototype() {
277         return this.defaultNodePrototype;
278     }
279 
280     /**
281      * @param defaultNodePrototype the defaultNodePrototype to set
282      */
283     public void setDefaultNodePrototype(NodePrototype defaultNodePrototype) {
284         this.defaultNodePrototype = defaultNodePrototype;
285     }
286 
287     /**
288      * @return the nodePrototypeMap
289      */
290     @BeanTagAttribute(name = "NodePrototypeMap", type = BeanTagAttribute.AttributeType.MAPBEAN)
291     public Map<Class<?>, NodePrototype> getNodePrototypeMap() {
292         return this.nodePrototypeMap;
293     }
294 
295     /**
296      * @param nodePrototypeMap the nodePrototypeMap to set
297      */
298     public void setNodePrototypeMap(Map<Class<?>, NodePrototype> nodePrototypeMap) {
299         this.nodePrototypeMap = nodePrototypeMap;
300     }
301 
302     @BeanTagAttribute(name = "treeGroups", type = BeanTagAttribute.AttributeType.MAP2BEAN)
303     public Tree<Group, Message> getTreeGroups() {
304         return treeGroups;
305     }
306 
307     public void setTreeGroups(Tree<Group, Message> treeGroups) {
308         this.treeGroups = treeGroups;
309     }
310 
311     @BeanTagAttribute(name = "tree", type = BeanTagAttribute.AttributeType.SINGLEBEAN)
312     public org.kuali.rice.krad.uif.widget.Tree getTree() {
313         return tree;
314     }
315 
316     public void setTree(org.kuali.rice.krad.uif.widget.Tree tree) {
317         this.tree = tree;
318     }
319 
320     /**
321      * @see org.kuali.rice.krad.datadictionary.DictionaryBeanBase#copyProperties(Object)
322      */
323     @Override
324     protected <T> void copyProperties(T component) {
325         super.copyProperties(component);
326 
327         TreeGroup treeGroupCopy = (TreeGroup) component;
328 
329         treeGroupCopy.setPropertyName(this.propertyName);
330 
331         if (this.bindingInfo != null) {
332             treeGroupCopy.setBindingInfo((BindingInfo) this.bindingInfo.copy());
333         }
334 
335         if (this.defaultNodePrototype != null) {
336             treeGroupCopy.setDefaultNodePrototype((NodePrototype) this.defaultNodePrototype.copy());
337         }
338 
339         if (this.treeGroups != null) {
340             Tree<Group, Message> treeGroupsCopy = new Tree<Group, Message>();
341             treeGroupsCopy.setRootElement(copyNode(this.treeGroups.getRootElement()));
342 
343             treeGroupCopy.setTreeGroups(treeGroupsCopy);
344         }
345 
346         if (this.tree != null) {
347             treeGroupCopy.setTree((org.kuali.rice.krad.uif.widget.Tree) this.tree.copy());
348         }
349 
350         if (this.nodePrototypeMap != null) {
351             Map<Class<?>, NodePrototype> nodePrototypeMapCopy = new HashMap<Class<?>, NodePrototype>();
352             for (Map.Entry<Class<?>, NodePrototype> nodePrototypeMapEntry : nodePrototypeMap.entrySet()) {
353                 NodePrototype prototypeCopy = nodePrototypeMapEntry.getValue().copy();
354                 nodePrototypeMapCopy.put(nodePrototypeMapEntry.getKey(), prototypeCopy);
355             }
356 
357             treeGroupCopy.setNodePrototypeMap(nodePrototypeMapCopy);
358         }
359     }
360 
361     /**
362      * Copies a {@link Node} instance and then recursively copies each of its child nodes
363      *
364      * @param node node instance to copy
365      * @return new node instance copied from given node
366      */
367     protected Node<Group, Message> copyNode(Node<Group, Message> node) {
368         Node<Group, Message> nodeCopy = new Node<Group, Message>();
369 
370         if (node == null) {
371             return null;
372         }
373 
374         if (node.getData() != null) {
375             nodeCopy.setData((Group) node.getData().copy());
376         }
377 
378         if (node.getNodeLabel() != null) {
379             nodeCopy.setNodeLabel((Message) node.getNodeLabel().copy());
380         }
381 
382         if (node.getChildren() != null) {
383             List<Node<Group, Message>> childrenCopy = new ArrayList<Node<Group, Message>>();
384             for (Node<Group, Message> childNode : node.getChildren()) {
385                 childrenCopy.add(copyNode(childNode));
386             }
387 
388             nodeCopy.setChildren(childrenCopy);
389         }
390 
391         return nodeCopy;
392     }
393 }