View Javadoc

1   /**
2    * Copyright 2005-2014 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 com.google.common.collect.Maps;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.util.tree.Node;
21  import org.kuali.rice.core.api.util.tree.Tree;
22  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
23  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
24  import org.kuali.rice.krad.datadictionary.parse.BeanTags;
25  import org.kuali.rice.krad.uif.UifConstants;
26  import org.kuali.rice.krad.uif.component.BindingInfo;
27  import org.kuali.rice.krad.uif.component.Component;
28  import org.kuali.rice.krad.uif.component.DataBinding;
29  import org.kuali.rice.krad.uif.element.Message;
30  import org.kuali.rice.krad.uif.util.ComponentUtils;
31  import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
32  import org.kuali.rice.krad.uif.view.View;
33  
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Map;
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(View view, Object model) {
78          setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
79  
80          super.performInitialization(view, model);
81  
82          if (bindingInfo != null) {
83              bindingInfo.setDefaults(view, 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(View view, Object model, Component parent) {
91          super.performApplyModel(view, model, parent);
92  
93          buildTreeGroups(view, 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(View view, 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.uif.component.ComponentBase#copy()
322      */
323     @Override
324     protected <T> void copyProperties(T component) {
325         super.copyProperties(component);
326         TreeGroup treeGroupCopy = (TreeGroup) component;
327         treeGroupCopy.setPropertyName(this.propertyName);
328 
329         if (this.bindingInfo != null) {
330             treeGroupCopy.setBindingInfo((BindingInfo) this.bindingInfo.copy());
331         }
332 
333         if (this.defaultNodePrototype != null) {
334             treeGroupCopy.setDefaultNodePrototype((NodePrototype) this.defaultNodePrototype.copy());
335         }
336 
337         if (this.treeGroups != null) {
338             Tree<Group, Message> treeGroupsCopy = new Tree<Group, Message>();
339             treeGroupsCopy.setRootElement(copyNode(this.treeGroups.getRootElement()));
340 
341             treeGroupCopy.setTreeGroups(treeGroupsCopy);
342         }
343 
344         if (this.tree != null) {
345             treeGroupCopy.setTree((org.kuali.rice.krad.uif.widget.Tree) this.tree.copy());
346         }
347 
348         if (this.nodePrototypeMap != null) {
349             Map<Class<?>, NodePrototype> nodePrototypeMapCopy = Maps.newHashMapWithExpectedSize(
350                     this.getNodePrototypeMap().size());
351             for (Map.Entry<Class<?>, NodePrototype> nodePrototypeMapEntry : nodePrototypeMap.entrySet()) {
352                 NodePrototype prototypeCopy = nodePrototypeMapEntry.getValue().copy();
353                 nodePrototypeMapCopy.put(nodePrototypeMapEntry.getKey(), prototypeCopy);
354             }
355 
356             treeGroupCopy.setNodePrototypeMap(nodePrototypeMapCopy);
357         }
358     }
359 
360     /**
361      * Copies a {@link Node} instance and then recursively copies each of its child nodes
362      *
363      * @param node node instance to copy
364      * @return new node instance copied from given node
365      */
366     protected Node<Group, Message> copyNode(Node<Group, Message> node) {
367         Node<Group, Message> nodeCopy = new Node<Group, Message>();
368 
369         if (node == null) {
370             return null;
371         }
372 
373         if (node.getData() != null) {
374             nodeCopy.setData((Group) node.getData().copy());
375         }
376 
377         if (node.getNodeLabel() != null) {
378             nodeCopy.setNodeLabel((Message) node.getNodeLabel().copy());
379         }
380 
381         if (node.getChildren() != null) {
382             List<Node<Group, Message>> childrenCopy = new ArrayList<Node<Group, Message>>();
383             for (Node<Group, Message> childNode : node.getChildren()) {
384                 childrenCopy.add(copyNode(childNode));
385             }
386 
387             nodeCopy.setChildren(childrenCopy);
388         }
389 
390         return nodeCopy;
391     }
392 }