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