001    package net.databinder.components.tree.hib;
002    
003    import java.io.Serializable;
004    import java.util.Collection;
005    import java.util.List;
006    
007    import javax.swing.tree.DefaultMutableTreeNode;
008    import javax.swing.tree.DefaultTreeModel;
009    import javax.swing.tree.TreeModel;
010    
011    import net.databinder.components.tree.data.DataTreeObject;
012    import net.databinder.models.hib.CriteriaBuilder;
013    import net.databinder.models.hib.HibernateListModel;
014    import net.databinder.models.hib.HibernateObjectModel;
015    
016    import org.apache.wicket.ajax.AjaxRequestTarget;
017    import org.apache.wicket.markup.html.tree.BaseTree;
018    import org.apache.wicket.model.IModel;
019    import org.apache.wicket.model.Model;
020    import org.hibernate.Criteria;
021    import org.hibernate.criterion.Property;
022    
023    
024    /**
025     * An extension of {@link BaseTree} based on node objects being represented by
026     * {@link HibernateObjectModel}s. Additionally, it offers some convenience
027     * methods.
028     * 
029     * @author Thomas Kappler
030     * 
031     * @param <T> the IDataTreeNode implementation being represented by the tree nodes
032     */
033    public abstract class DataTree<T extends DataTreeObject<T>> extends BaseTree {
034            /**
035             * Construct a tree with a root entity.
036             * @param id Wicket id
037             * @param rootModel must contain a root of type T
038             */
039            @SuppressWarnings("unchecked")
040            public DataTree(String id, HibernateObjectModel rootModel) {
041                    super(id);
042                    DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(rootModel);
043                    populateTree(rootNode, ((T)rootModel.getObject()).getChildren());
044                    setModel(new Model(new DefaultTreeModel(rootNode)));
045            }
046            
047            /**
048             * Construct a rootless tree based on a list of top level nodes. 
049             * @param id
050             * @param topLevelModel must contain a List<T> of top level children
051             */
052            @SuppressWarnings("unchecked")
053            public DataTree(String id, HibernateListModel topLevelModel) {
054                    super(id);
055                    setRootLess(true);
056                    DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(topLevelModel);
057                    populateTree(rootNode, (List<T>)topLevelModel.getObject());
058                    setModel(new Model(new DefaultTreeModel(rootNode)));
059            }
060            
061            /**
062             * Convenience criteria builder for fetching top-level entities.
063             */
064            public static class TopLevelCriteriaBuilder implements CriteriaBuilder {
065                    /**
066                     * build criteria for a null "parent" property
067                     */
068                    public void build(Criteria criteria) {
069                            criteria.add(Property.forName("parent").isNull());
070                    }
071            }
072            
073            public DefaultMutableTreeNode clear(AjaxRequestTarget target) {
074                    T newObject = createNewObject();
075                    DefaultMutableTreeNode newRootNode = new DefaultMutableTreeNode(
076                                    new HibernateObjectModel(newObject));
077                    TreeModel treeModel = new DefaultTreeModel(newRootNode);
078                    setModel(new Model((Serializable) treeModel));
079                    repaint(target);
080                    return newRootNode; 
081            }
082            
083            /**
084             * Recursively build the tree nodes according to the structure given by the
085             * beans.
086             * 
087             * @param parent
088             *            a tree node serving as parent to the newly created nodes for
089             *            the elements in children
090             * @param children
091             *            objects to be inserted into the tree below parent
092             */
093            private void populateTree(DefaultMutableTreeNode parent, 
094                            Collection<T> children) {         
095                    for (T t : children) {
096                            HibernateObjectModel m = new HibernateObjectModel(t);
097                            DefaultMutableTreeNode node = new DefaultMutableTreeNode(m);
098                            parent.add(node);
099                            populateTree(node, t.getChildren());
100                    }
101            }
102    
103            /**
104             * Get the IDataTreeNode instance behind this node, or null if the node is the
105             * root of a tree with no root entity.
106             * 
107             * @param node
108             *            a tree node
109             * @return the object represented by node
110             */
111            @SuppressWarnings("unchecked")
112            public T getDataTreeNode(DefaultMutableTreeNode node) {
113                    Object nodeObject = ((IModel) node.getUserObject()).getObject();
114                    return (nodeObject instanceof DataTreeObject<?>) ?
115                            (T) nodeObject : null;
116            }
117            
118            /**
119             * @return the root node of the tree
120             */
121            public DefaultMutableTreeNode getRootNode() {
122                    DefaultTreeModel treeModel = (DefaultTreeModel) getModelObject();
123                    if (treeModel.getRoot() == null) {
124                            return null;
125                    }
126                    return (DefaultMutableTreeNode) treeModel.getRoot();            
127            }
128            
129            /**
130             * Create a new user object using {@link #createNewObject()} and add it
131             * to the tree as a child of parentNode.
132             * 
133             * @param parentNode
134             *            to node serving as parent of the new object
135             * @return the newly created tree node
136             */
137            public DefaultMutableTreeNode addNewChildNode(DefaultMutableTreeNode parentNode) {
138                    T newObject = createNewObject();
139                    
140                    T parent = getDataTreeNode(parentNode);
141                    if (parent != null)
142                            parent.addChild(newObject);
143                    
144                    DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(
145                                    new HibernateObjectModel(newObject)); 
146                    parentNode.add(newNode);
147                    return newNode;
148            }
149            
150            /**
151             * Repaint the tree when something has changed. It possibly does too much,
152             * but you're safe that changes do show after you call it.
153             * 
154             * @param target
155             */
156            public void repaint(AjaxRequestTarget target) {
157                    invalidateAll();
158                    updateTree(target);
159            }
160    
161            /**
162             * Create a new instance of T. Used to create the backing objects of new
163             * tree nodes.
164             * 
165             * @return a new instance of T
166             */
167            protected abstract T createNewObject();
168    
169            /**
170             * Override to update components when another tree node is selected. Does
171             * nothing by default.
172             * 
173             * @param target
174             * @param selectedNode
175             *            the currently selected node
176             */
177            public void updateDependentComponents(AjaxRequestTarget target, DefaultMutableTreeNode selectedNode) {
178                    // Do nothing by default
179            }
180            
181            @Override
182            public void onDetach() {
183                    super.onDetach();
184                    // in a root less tree it's not bound to any component
185                    ((IModel)getRootNode().getUserObject()).detach();
186            }
187    }