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