001    /**
002     * Copyright 2005-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.uif.container;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.util.tree.Node;
020    import org.kuali.rice.core.api.util.tree.Tree;
021    import org.kuali.rice.krad.uif.UifConstants;
022    import org.kuali.rice.krad.uif.component.BindingInfo;
023    import org.kuali.rice.krad.uif.component.Component;
024    import org.kuali.rice.krad.uif.component.DataBinding;
025    import org.kuali.rice.krad.uif.field.MessageField;
026    import org.kuali.rice.krad.uif.util.ComponentUtils;
027    import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
028    import org.kuali.rice.krad.uif.view.View;
029    
030    import java.util.ArrayList;
031    import java.util.List;
032    import java.util.Map;
033    
034    /**
035     * Group component that is backed by a <code>Tree</code> data structure and typically
036     * rendered as a tree in the user interface
037     *
038     * @author Kuali Rice Team (rice.collab@kuali.org)
039     */
040    public class TreeGroup extends Group implements DataBinding{
041        private static final long serialVersionUID = 5841343037089286740L;
042    
043        private String propertyName;
044        private BindingInfo bindingInfo;
045    
046        private Map<Class<?>, NodePrototype> nodePrototypeMap;
047        private NodePrototype defaultNodePrototype;
048    
049        private Tree<Group, MessageField> treeGroups;
050    
051        private org.kuali.rice.krad.uif.widget.Tree tree;
052    
053        public TreeGroup() {
054            super();
055    
056            treeGroups = new Tree<Group, MessageField>();
057        }
058    
059        /**
060         * The following actions are performed:
061         *
062         * <ul>
063         * <li>Set fieldBindModelPath to the collection model path (since the fields
064         * have to belong to the same model as the collection)</li>
065         * <li>Set defaults for binding</li>
066         * <li>Calls view helper service to initialize prototypes</li>
067         * </ul>
068         *
069         */
070        @Override
071        public void performInitialization(View view, Object model) {
072            setFieldBindingObjectPath(getBindingInfo().getBindingObjectPath());
073    
074            super.performInitialization(view, model);
075    
076            if (bindingInfo != null) {
077                bindingInfo.setDefaults(view, getPropertyName());
078            }
079    
080            // TODO: set object path for prototypes equal to the tree group object path?
081    
082            initializeNodePrototypeComponents(view, model);
083        }
084    
085        protected void initializeNodePrototypeComponents(View view, Object model) {
086            view.getViewHelperService().performComponentInitialization(view, model,
087                    defaultNodePrototype.getLabelPrototype());
088            view.getViewHelperService().performComponentInitialization(view, model,
089                    defaultNodePrototype.getDataGroupPrototype());
090    
091            if (nodePrototypeMap != null) {
092                for (Map.Entry<Class<?>, NodePrototype> prototypeEntry : nodePrototypeMap.entrySet()) {
093                    NodePrototype prototype = prototypeEntry.getValue();
094                    if (prototype != null) {
095    
096                        if (prototype.getLabelPrototype() != null) {
097                            view.getViewHelperService().performComponentInitialization(view, model,
098                                    prototype.getLabelPrototype());
099                        } 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    }