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