View Javadoc

1   /**
2    * This file Copyright (c) 2003-2012 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.cms.gui.control;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.core.HierarchyManager;
38  import info.magnolia.cms.core.ItemType;
39  import info.magnolia.cms.core.MgnlNodeType;
40  import info.magnolia.cms.core.NodeData;
41  import info.magnolia.cms.core.Path;
42  import info.magnolia.cms.util.MetaDataUtil;
43  import info.magnolia.cms.util.NodeDataUtil;
44  import info.magnolia.context.MgnlContext;
45  import info.magnolia.freemarker.FreemarkerUtil;
46  
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.Collections;
50  import java.util.Comparator;
51  import java.util.HashMap;
52  import java.util.HashSet;
53  import java.util.Iterator;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.Set;
57  import java.util.TreeSet;
58  
59  import javax.jcr.PropertyType;
60  import javax.jcr.RepositoryException;
61  import javax.jcr.Value;
62  import javax.servlet.http.HttpServletRequest;
63  
64  import org.apache.commons.lang.StringEscapeUtils;
65  import org.apache.commons.lang.StringUtils;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  
69  
70  /**
71   * @author Vinzenz Wyser
72   * @version 2.0
73   */
74  public class Tree extends ControlImpl {
75  
76      public static final String DOCROOT = "/.resources/controls/tree/"; //$NON-NLS-1$
77  
78      public static final String ICONDOCROOT = "/.resources/icons/16/"; //$NON-NLS-1$
79  
80      public static final String DEFAULT_ICON = ICONDOCROOT + "cubes.gif";
81  
82      public static final String DEFAULT_ICON_CONTENT = ICONDOCROOT + "document_plain_earth.gif";
83  
84      public static final String DEFAULT_ICON_CONTENTNODE = DEFAULT_ICON;
85  
86      public static final String DEFAULT_ICON_NODEDATA = ICONDOCROOT + "cube_green.gif";
87      
88      public static final String DEFAULT_ICON_DELETED = ICONDOCROOT + "document_deleted.gif";
89  
90      /**
91       * @deprecated since 4.4, use ItemType.MGNL_NODE_DATA instead
92       */
93      @Deprecated
94      public static final String ITEM_TYPE_NODEDATA = "mgnl:nodeData";
95  
96      public static final int ACTION_MOVE = 0;
97  
98      public static final int ACTION_COPY = 1;
99  
100     public static final int ACTION_ACTIVATE = 2;
101 
102     public static final int ACTION_DEACTIVATE = 3;
103 
104     public static final int PASTETYPE_ABOVE = 0;
105 
106     public static final int PASTETYPE_BELOW = 1;
107 
108     public static final int PASTETYPE_SUB = 2;
109 
110     public static final int PASTETYPE_LAST = 3;
111 
112     /**
113      * Logger.
114      */
115     private static Logger log = LoggerFactory.getLogger(Tree.class);
116 
117     private String repository;
118 
119     private String pathOpen;
120 
121     private String pathCurrent;
122 
123     private String pathSelected;
124 
125     private String rootPath;
126 
127     private int indentionWidth = 15;
128 
129     private final List<String> itemTypes = new ArrayList<String>();
130 
131     private final Set<String> strictTypes = new HashSet<String>();
132 
133     private int height = 400;
134 
135     private final Map<String, String> icons = new HashMap<String, String>();
136 
137     private String iconOndblclick;
138 
139     private String shifterExpand = DOCROOT + "shifter_EXPAND.gif"; //$NON-NLS-1$
140 
141     private String shifterCollaspe = DOCROOT + "shifter_COLLAPSE.gif"; //$NON-NLS-1$
142 
143     private String shifterEmpty = DOCROOT + "shifter_EMPTY.gif"; //$NON-NLS-1$
144 
145     private boolean drawShifter = true;
146 
147     private String javascriptTree = "mgnlTreeControl"; //$NON-NLS-1$
148 
149     private List columns = new ArrayList();
150 
151     private ContextMenu menu;
152 
153     private Comparator sortComparator;
154 
155     /**
156      * the bar at the bottom of the page holding some function buttons
157      */
158     private FunctionBar functionBar;
159 
160     private boolean snippetMode = true;
161 
162     private String columnResizer = DOCROOT + "columnResizer.gif"; //$NON-NLS-1$
163 
164     private boolean browseMode;
165 
166     /**
167      * Use the getter method to access this HierarchyManager.
168      */
169     private HierarchyManager hm;
170 
171     /**
172      * @deprecated don't pass the request
173      */
174     @Deprecated
175     public Tree(String name, String repository, HttpServletRequest request) {
176         this(name, repository);
177     }
178 
179     /**
180      * Constructor.
181      * @param name name of the tree (name of the treehandler)
182      * @param repository name of the repository (i.e. "website", "users")
183      */
184     public Tree(String name, String repository) {
185         this.setName(name);
186         this.setRepository(repository);
187         this.setMenu(new ContextMenu(this.getJavascriptTree()));
188         this.setFunctionBar(new FunctionBar(this.getJavascriptTree()));
189 
190         this.setHierarchyManager(MgnlContext.getHierarchyManager(this.getRepository()));
191 
192         // set default icons
193         this.addIcon(MgnlNodeType.NT_CONTENT, DEFAULT_ICON_CONTENT);
194         this.addIcon(MgnlNodeType.NT_CONTENTNODE, DEFAULT_ICON_CONTENTNODE);
195         this.addIcon(MgnlNodeType.MGNL_NODE_DATA, DEFAULT_ICON_NODEDATA);
196         this.addIcon(MgnlNodeType.MIX_DELETED, DEFAULT_ICON_DELETED);
197     }
198 
199     /**
200      * Constructor: the name of the tree is the same as the name of the repository
201      * @param repository
202      * @param request
203      * @deprecated use Tree(name, repository) instead
204      */
205     @Deprecated
206     public Tree(String repository, HttpServletRequest request) {
207         this(repository, repository, request);
208     }
209 
210     public void setRepository(String s) {
211         this.repository = s;
212     }
213 
214     public String getRepository() {
215         return this.repository;
216     }
217 
218     public void setPathOpen(String s) {
219         this.pathOpen = s;
220     }
221 
222     public String getPathOpen() {
223         return this.pathOpen;
224     }
225 
226     /**
227      * Sets which path will be selected (and opened - overwrites pathOpen).
228      * @param s
229      */
230     public void setPathSelected(String s) {
231         if (StringUtils.isNotEmpty(s)) {
232             if (StringUtils.isEmpty(this.getPathOpen())) {
233                 this.setPathOpen(StringUtils.substringBeforeLast(s, "/")); //$NON-NLS-1$
234             }
235         }
236         this.pathSelected = s;
237     }
238 
239     public String getPathSelected() {
240         return this.pathSelected;
241     }
242 
243     @Override
244     public String getPath() {
245         if (super.getPath() != null) {
246             return super.getPath();
247         }
248 
249         return ("/"); //$NON-NLS-1$
250     }
251 
252     protected void setPathCurrent(String s) {
253         this.pathCurrent = s;
254     }
255 
256     protected String getPathCurrent() {
257         return this.pathCurrent;
258     }
259 
260     public void setIndentionWidth(int i) {
261         this.indentionWidth = i;
262     }
263 
264     public int getIndentionWidth() {
265         return this.indentionWidth;
266     }
267 
268     public List getItemTypes() {
269         return this.itemTypes;
270     }
271 
272     /**
273      * Add a itemType to the itemTypes that will be shown in this branch.
274      * @deprecated pass the icon to use as a second parameter
275      */
276     @Deprecated
277     public void addItemType(String s) {
278         addItemType(s, null);
279     }
280 
281     /**
282      * Adds an itemType to the list of existing types and associates an icon with it.
283      * This method will cause only items of given type to be displayed. The items of subtype of given type will not be displayed.
284      * To display also subtypes see {@link Tree#addItemType(String, String, boolean)}
285      * @param type The item type
286      * @param icon The icon used to display items of given type
287      *
288      * @see #addItemType(String, String, boolean)
289      */
290     public void addItemType(String type, String icon) {
291         addItemType(type, icon, true);
292     }
293 
294     public void addItemType(String type, boolean strict) {
295         addItemType(type, null, strict);
296     }
297 
298     /**
299      * Adds an itemType to the list of existing types and associates an icon with it.
300      * The type can be specified as a strict, in which case only the items of given type will be displayed, but sub types will be ignored,
301      * or non-strict in which case the items of sub types of given type will also be rendered.
302      * @param type The item type
303      * @param icon The icon used to display items of given type
304      * @param strict Flag specifying whether or not to show also items of sub type of given type
305      */
306     public void addItemType(String type, String icon, boolean strict) {
307         this.itemTypes.add(type);
308         if (icon != null) {
309             setIcon(type, icon);
310         }
311         if (strict) {
312             strictTypes.add(type);
313         } else {
314             strictTypes.remove(type);
315         }
316     }
317 
318     /**
319      * Add a itemType to the itemTypes that will be shown in this branch.
320      * @param s itemType (one of: ItemType.CONTENT, ItemType.CONTENTNODE)
321      */
322     public void addItemType(ItemType s) {
323         addItemType(s.getSystemName(), null);
324     }
325 
326     /**
327      * Set the icon of pages.
328      * @param src source of the image
329      * @deprecated since 4.4, use addIcon(ItemType.CONTENT.getSystemName(), src) instead
330      */
331     @Deprecated
332     public void setIconPage(String src) {
333         this.setIcon(ItemType.CONTENT.getSystemName(), src);
334     }
335 
336     public String getIconPage() {
337         return this.getIcon(ItemType.CONTENT.getSystemName());
338     }
339 
340     /**
341      * Set the icon of content nodes.
342      * @param src source of the image
343      * @deprecated since 4.4, use addIcon(ItemType.CONTENTNODE.getSystemName(), src) instead
344      */
345     @Deprecated
346     public void setIconContentNode(String src) {
347         this.setIcon(ItemType.CONTENTNODE.getSystemName(), src);
348     }
349 
350     public String getIconContentNode() {
351         return this.getIcon(ItemType.CONTENTNODE.getSystemName());
352     }
353 
354     /**
355      * Set the icon of node data.
356      * @param src source of the image
357      * @deprecated since 4.4, use addIcon(ItemType.MGNL_NODE_DATA, src) instead
358      */
359     @Deprecated
360     public void setIconNodeData(String src) {
361         this.setIcon(ITEM_TYPE_NODEDATA, src);
362     }
363 
364     public String getIconNodeData() {
365         return this.getIcon(ITEM_TYPE_NODEDATA);
366     }
367 
368     protected String getIcon(String itemType) {
369         return icons.get(itemType);
370     }
371 
372     protected String getIcon(Content node, NodeData nodedata, String itemType) {
373         // deleted icon trumps them all.
374         try {
375             if (node != null && node.hasMixin(ItemType.DELETED_NODE_MIXIN)) {
376                 return icons.get(ItemType.DELETED_NODE_MIXIN);
377             }
378         } catch (RepositoryException e) {
379             log.error("Failed to read content of " + node.getHandle());
380         }
381         if(icons.containsKey(itemType)){
382             return icons.get(itemType);
383         }
384         return DEFAULT_ICON;
385     }
386 
387     /**
388      * @deprecated since 4.4 use {@link #addIcon(String, String)} instead.
389      */
390     @Deprecated
391     public void setIcon(String typeName, String icon) {
392         addIcon(typeName, icon);
393     }
394 
395     public void addIcon(String typeName, String icon) {
396         icons.put(typeName, icon);
397     }
398     /**
399      * Set the double click event of the icon.
400      * @param s javascriopt method
401      */
402     public void setIconOndblclick(String s) {
403         this.iconOndblclick = s;
404     }
405 
406     public String getIconOndblclick() {
407         return this.iconOndblclick;
408     }
409 
410     /**
411      * Set the shifter image (expand branch). "_EXPAND" in file name will be replaced by "_COLLAPSE" after expanding
412      * e.g. myShifterIcon_EXPAND.gif
413      * @param src source of the image
414      */
415     public void setShifterExpand(String src) {
416         this.shifterExpand = src;
417     }
418 
419     public String getShifterExpand() {
420         return this.shifterExpand;
421     }
422 
423     /**
424      * Set the shifter image (collapse branch). "_COLLAPSE" in file name will be replaced by "_EXPAND" after collapsing
425      * e.g. myShifterIcon_COLLAPSE.gif
426      * @param src source of the image
427      */
428     public void setShifterCollapse(String src) {
429         this.shifterCollaspe = src;
430     }
431 
432     public String getShifterCollapse() {
433         return this.shifterCollaspe;
434     }
435 
436     /**
437      * Set the shifter image when no children are available (not expandable). "_EMPTY" in the file name will be replaced
438      * when children are available e.g. myShifterIcon_EMPTY.gif
439      * @param src source of the image
440      */
441     public void setShifterEmpty(String src) {
442         this.shifterEmpty = src;
443     }
444 
445     public String getShifterEmpty() {
446         return this.shifterEmpty;
447     }
448 
449     public void setDrawShifter(boolean b) {
450         this.drawShifter = b;
451     }
452 
453     public boolean getDrawShifter() {
454         return this.drawShifter;
455     }
456 
457     public void setHeight(int i) {
458         this.height = i;
459     }
460 
461     public int getHeight() {
462         return this.height;
463     }
464 
465     /**
466      * Set the columns (for pages and content nodes only).
467      * @param al list of TreeColumns
468      */
469     public void setColums(List al) {
470         this.columns = al;
471     }
472 
473     public List getColumns() {
474         return this.columns;
475     }
476 
477     public TreeColumn getColumns(int col) {
478         return (TreeColumn) this.getColumns().get(col);
479     }
480 
481     public void addColumn(TreeColumn tc) {
482         tc.setJavascriptTree(this.getJavascriptTree());
483         this.getColumns().add(tc);
484     }
485 
486     /**
487      * Set the name of the javascript tree object.
488      * @param variableName
489      */
490     public void setJavascriptTree(String variableName) {
491         this.javascriptTree = variableName;
492         this.menu.setName(variableName + "Menu"); //$NON-NLS-1$
493     }
494 
495     public String getJavascriptTree() {
496         return this.javascriptTree;
497     }
498 
499     /**
500      * Sets if only a snippet (requested branch) shall be returnde or including the surounding html (tree header, js/css
501      * links etc).
502      * @param b true: snippet only
503      */
504     public void setSnippetMode(boolean b) {
505         this.snippetMode = b;
506     }
507 
508     public boolean getSnippetMode() {
509         return this.snippetMode;
510     }
511 
512     // @todo: set size of column resizer gif and pass it to js object
513     public void setColumnResizer(String src) {
514         this.columnResizer = src;
515     }
516 
517     public String getColumnResizer() {
518         return this.columnResizer;
519     }
520 
521     public String createNode(String itemType) {
522         return this.createNode("untitled", itemType); //$NON-NLS-1$
523     }
524 
525     /**
526      * Creates a new node (either <code>NodeData</code> or <code>Content</code>) with the specified name (<tt>label</tt>)
527      * and type.
528      * @param label new node name
529      * @param itemType new node type
530      */
531     public String createNode(String label, String itemType) {
532         try {
533             Content parentNode = getHierarchyManager().getContent(this.getPath());
534             String name = getUniqueLabel(label);
535             if (itemType.equals(ITEM_TYPE_NODEDATA)) {
536                 parentNode.createNodeData(name);
537             } else {
538                 Content newNode;
539                 newNode = parentNode.createContent(name, itemType);
540                 newNode.getMetaData().setAuthorId(MgnlContext.getUser().getName());
541                 newNode.getMetaData().setCreationDate();
542                 newNode.getMetaData().setModificationDate();
543             }
544             parentNode.save();
545             return name;
546         } catch (Exception e) {
547             log.error(e.getMessage(), e);
548             return StringUtils.EMPTY; // reset the name, so that you can check if the node was created
549         }
550 
551     }
552 
553     public String saveNodeData(String nodeDataName, String value, boolean isMeta) {
554         String returnValue = StringUtils.EMPTY;
555         try {
556             Content content = getHierarchyManager().getContent(this.getPath());
557             if (!isMeta) {
558                 NodeData node;
559                 int type = PropertyType.STRING;
560                 if (!content.getNodeData(nodeDataName).isExist()) {
561                     node = content.createNodeData(nodeDataName);
562                 }
563                 else {
564                     node = content.getNodeData(nodeDataName);
565                     type = node.getType();
566                 }
567                 // todo: share with Contorol.Save
568                 if (node.isMultiValue() != NodeData.MULTIVALUE_TRUE) {
569                     switch (type) {
570                     case PropertyType.STRING:
571                         node.setValue(value);
572                         break;
573                     case PropertyType.BOOLEAN:
574                         if (value.equals("true")) { //$NON-NLS-1$
575                             node.setValue(true);
576                         }
577                         else {
578                             node.setValue(false);
579                         }
580                         break;
581                     case PropertyType.DOUBLE:
582                         try {
583                             node.setValue(Double.valueOf(value).doubleValue());
584                         }
585                         catch (Exception e) {
586                             node.setValue(0);
587                         }
588                         break;
589                     case PropertyType.LONG:
590                         try {
591                             node.setValue(Long.valueOf(value).longValue());
592                         }
593                         catch (Exception e) {
594                             node.setValue(0);
595                         }
596                         break;
597                     case PropertyType.DATE:
598                         // todo
599                         break;
600                     }
601                 }
602                 content.updateMetaData();
603                 content.save();
604                 returnValue = NodeDataUtil.getValueString(node);
605             }
606             else {
607                 content.getMetaData().setProperty(nodeDataName, value);
608                 content.updateMetaData();
609                 content.save();
610                 returnValue = MetaDataUtil.getPropertyValueString(content, nodeDataName);
611             }
612         }
613         catch (Exception e) {
614             if (log.isDebugEnabled()) {
615                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
616             }
617         }
618         return returnValue;
619     }
620 
621     public String saveNodeDataType(String nodeDataName, int type) {
622         try {
623             Content content = getHierarchyManager().getContent(this.getPath());
624             Value value = null;
625             if (content.getNodeData(nodeDataName).isExist()) {
626                 value = content.getNodeData(nodeDataName).getValue();
627                 content.deleteNodeData(nodeDataName);
628             }
629             NodeData node = content.createNodeData(nodeDataName);
630             if (value != null && node.isMultiValue() != NodeData.MULTIVALUE_TRUE) {
631                 switch (type) {
632                 case PropertyType.STRING:
633                     node.setValue(value.getString());
634                     break;
635                 case PropertyType.BOOLEAN:
636                     if (value != null && value.getBoolean()) {
637                         node.setValue(true);
638                     }
639                     else {
640                         node.setValue(false);
641                     }
642                     break;
643                 case PropertyType.DOUBLE:
644                     try {
645                         node.setValue(value.getDouble());
646                     }
647                     catch (Exception e) {
648                         node.setValue(0);
649                     }
650                     break;
651                 case PropertyType.LONG:
652                     try {
653                         node.setValue(value.getLong());
654                     }
655                     catch (Exception e) {
656                         node.setValue(0);
657                     }
658                     break;
659                 case PropertyType.DATE:
660                     // todo
661                     break;
662                 }
663             }
664             content.updateMetaData();
665             content.save();
666             return PropertyType.nameFromValue(content.getNodeData(nodeDataName).getType());
667             // return PropertyType.nameFromValue(node.getType());
668         }
669         catch (Exception e) {
670             if (log.isDebugEnabled()) {
671                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
672             }
673         }
674         return StringUtils.EMPTY;
675     }
676 
677     /**
678      *
679      * @param label
680      * @return unique label
681      */
682     protected String getUniqueLabel(String label) {
683         String slash = "/"; //$NON-NLS-1$
684         boolean isRoot = false;
685         if ("/".equals(getPath())) { //$NON-NLS-1$
686             isRoot = true;
687             slash = StringUtils.EMPTY;
688         }
689         if (getHierarchyManager().isExist(this.getPath() + slash + label)) {
690             // todo: bugfix getUniqueLabel???
691             if (isRoot) {
692                 label = Path.getUniqueLabel(getHierarchyManager(), StringUtils.EMPTY, label);
693             } else {
694                 label = Path.getUniqueLabel(getHierarchyManager(), this.getPath(), label);
695             }
696         }
697         return label;
698     }
699 
700     @Override
701     public String getHtml() {
702         StringBuffer html = new StringBuffer();
703         html.append(this.getHtmlPre());
704 
705         if (!this.getSnippetMode()) {
706             html.append(this.getHtmlHeader());
707         }
708         this.setPathCurrent(this.getPath());
709         html.append(this.getHtmlChildren());
710         if (!this.getSnippetMode()) {
711             html.append(this.getHtmlFooter());
712         }
713         html.append(this.getHtmlPost());
714         return html.toString();
715     }
716 
717     public String getHtmlHeader() {
718 
719         StringBuffer str = new StringBuffer();
720         try {
721             Map params = populateTemplateParameters();
722             str.append(FreemarkerUtil.process("info/magnolia/cms/gui/control/TreeHeader.ftl", params));
723         }
724         catch (Exception e) {
725             log.error("can't render tree header", e);
726         }
727         return str.toString();
728     }
729 
730     public String getHtmlFooter() {
731         StringBuffer html = new StringBuffer();
732         html.append("</div>"); //$NON-NLS-1$
733 
734         Map params = populateTemplateParameters();
735 
736         // include the tree footer / menu divs
737         html.append(FreemarkerUtil.process("info/magnolia/cms/gui/control/TreeFooter.ftl", params));
738 
739         return html.toString();
740     }
741 
742     protected Map populateTemplateParameters() {
743         boolean permissionWrite = true;
744         try {
745             Content root = getHierarchyManager().getContent(this.getPath());
746             permissionWrite = root.isGranted(info.magnolia.cms.security.Permission.WRITE);
747         }
748         catch (RepositoryException e) {
749             if (log.isDebugEnabled()) {
750                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
751             }
752         }
753 
754         // lineInter: line between nodes, to allow set cursor between nodes
755         // line to place a very last position
756         String lineId = this.getJavascriptTree() + "_" + this.getPath() + "_LineInter"; //$NON-NLS-1$ //$NON-NLS-2$
757 
758         // prepare the data for the templates
759         Map<String, Object> params = new HashMap<String, Object>();
760         params.put("tree", this);
761         params.put("PASTETYPE_SUB", new Integer(Tree.PASTETYPE_SUB));
762         params.put("DOCROOT", Tree.DOCROOT);
763         params.put("lineId", lineId);
764         params.put("permissionWrite", Boolean.valueOf(permissionWrite));
765         params.put("columns", this.getColumns());
766         params.put("menu", this.getMenu());
767         params.put("treeCssClass", "mgnlTreeDiv");
768         if (this.isBrowseMode()) {
769             params.put("treeCssClass", "mgnlTreeBrowseModeDiv mgnlTreeDiv");
770         }
771         return params;
772     }
773 
774     public String getHtmlBranch() {
775         return StringUtils.EMPTY;
776     }
777 
778     public String getHtmlChildren() {
779         StringBuffer html = new StringBuffer();
780         Content parentNode;
781         try {
782             parentNode = getHierarchyManager().getContent(this.getPathCurrent());
783             // loop the children of the different item types
784             for (int i = 0; i < this.getItemTypes().size(); i++) {
785                 String type = (String) this.getItemTypes().get(i);
786                 if (hasSub(parentNode, type)) {
787                     html.append(this.getHtmlChildrenOfOneType(parentNode, type));
788                 }
789             }
790         }
791         catch (RepositoryException e) {
792             if (log.isDebugEnabled()) {
793                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
794             }
795         }
796         return html.toString();
797     }
798 
799     public String getHtmlChildrenOfOneType(Content parentNode, String itemType) {
800         StringBuffer html = new StringBuffer();
801         try {
802             Iterator it = collectRenderedItems(parentNode, itemType);
803             while (it.hasNext()) {
804                 Object maybeContent = it.next();
805                 if (maybeContent instanceof Content) {
806                     Content c = (Content) maybeContent;
807                     // ensure no subtypes of strict types are included by mistake
808                     if (strictTypes.contains(itemType) && !c.getItemType().getSystemName().equals(itemType)) {
809                         continue;
810                     }
811                 }
812                 getHtmlOfSingleItem(html, parentNode, itemType, maybeContent);
813             }
814         }
815         catch (RepositoryException e) {
816             if (log.isDebugEnabled()) {
817                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
818             }
819         }
820         return html.toString();
821     }
822 
823     protected void getHtmlOfSingleItem(StringBuffer html, Content parentNode, String itemType, Object item) throws RepositoryException {
824         Content c = null;
825         NodeData d = null;
826         String handle;
827         String name;
828         boolean hasSub = false;
829         boolean showSub = false;
830         boolean isActivated = false;
831         boolean isDeleted = false;
832         boolean permissionWrite = false;
833         boolean permissionWriteParent = false;
834         if (itemType.equals(ITEM_TYPE_NODEDATA)) {
835             d = (NodeData) item;
836             handle = d.getHandle();
837             name = d.getName();
838 
839             if (d.isGranted(info.magnolia.cms.security.Permission.WRITE) && d.isMultiValue() != NodeData.MULTIVALUE_TRUE) {
840                 permissionWrite = true;
841             }
842         }
843         else {
844             c = (Content) item;
845 
846             handle = c.getHandle();
847             if (this.getColumns().size() == 0) {
848                 name = c.getName();
849             }
850             else {
851                 this.getColumns(0).setWebsiteNode(c);
852                 name = this.getColumns(0).getHtml();
853             }
854             if (c.isGranted(info.magnolia.cms.security.Permission.WRITE)) {
855                 permissionWrite = true;
856             }
857             if (c.hasMixin(ItemType.DELETED_NODE_MIXIN)) {
858                 isDeleted = true;
859             }
860             if (c.getAncestor(c.getLevel() - 1).isGranted(info.magnolia.cms.security.Permission.WRITE)) {
861                 permissionWriteParent = true;
862             }
863             isActivated = c.getMetaData().getIsActivated();
864             for (int i = 0; i < this.getItemTypes().size(); i++) {
865                 String type = (String) this.getItemTypes().get(i);
866 
867                 hasSub = hasSub(c, type);
868 
869                 if (hasSub) {
870                     if (this.getPathOpen() != null
871                             && (this.getPathOpen().indexOf(handle + "/") == 0 || this.getPathOpen().equals(handle))) { //$NON-NLS-1$
872                         showSub = true;
873                     }
874                     break;
875                 }
876             }
877         }
878 
879         // get next if this node is not shown
880         if (!showNode(c, d, itemType)) {
881             return;
882         }
883 
884         String icon = getIcon(c, d, itemType);
885 
886         String idPre = this.javascriptTree + "_" + handle; //$NON-NLS-1$
887         String jsHighlightNode = this.javascriptTree + ".nodeHighlight(this,'" //$NON-NLS-1$
888         + handle
889         + "'," //$NON-NLS-1$
890         + Boolean.toString(permissionWrite)
891         + ");"; //$NON-NLS-1$
892         String jsResetNode = this.javascriptTree + ".nodeReset(this,'" + handle + "');"; //$NON-NLS-1$ //$NON-NLS-2$
893         String jsSelectNode = this.javascriptTree + ".selectNode('" //$NON-NLS-1$
894         + handle
895         + "'," //$NON-NLS-1$
896         + Boolean.toString(permissionWrite)
897         + ",'" //$NON-NLS-1$
898         + itemType
899         + "');"; //$NON-NLS-1$
900         String jsExpandNode;
901         if (this.getDrawShifter()) {
902             jsExpandNode = this.javascriptTree + ".expandNode('" + handle + "');"; //$NON-NLS-1$ //$NON-NLS-2$
903         }
904         else {
905             jsExpandNode = jsSelectNode;
906         }
907         String jsHighlightLine = this.javascriptTree + ".moveNodeHighlightLine('" + idPre + "_LineInter');"; //$NON-NLS-1$ //$NON-NLS-2$
908         String jsResetLine = this.javascriptTree + ".moveNodeResetLine('" + idPre + "_LineInter');"; //$NON-NLS-1$ //$NON-NLS-2$
909 
910         // lineInter: line between nodes, to allow set cursor between nodes
911         // try to avoid blank images, setting js actions on divs should be ok
912         if (permissionWriteParent) {
913             html.append("<div id=\"");
914             html.append(idPre);
915             html.append("_LineInter\" class=\"mgnlTreeLineInter mgnlLineEnabled\" onmouseover=\"");
916             html.append(jsHighlightLine);
917             html.append("\" onmouseout=\"");
918             html.append(jsResetLine);
919             html.append("\" onmousedown=\"");
920             html.append(this.javascriptTree);
921             html.append(".pasteNode('");
922             html.append(handle);
923             html.append("'," + Tree.PASTETYPE_ABOVE + ",true);\" ></div>");
924         }
925         else {
926             html.append("<div id=\"");
927             html.append(idPre);
928             html.append("_LineInter\" class=\"mgnlTreeLineInter mgnlLineDisabled\"></div>");
929         }
930 
931         html.append("<div id=\"");
932         html.append(idPre);
933         html.append("_DivMain\" style=\"position:relative;top:0;left:0;width:100%;height:18px;\">");
934         html.append("&nbsp;"); // do not remove! //$NON-NLS-1$
935 
936         html.append("<span id=\"");
937         html.append(idPre);
938         html.append("_Column0Outer\" class=\"mgnlTreeColumn ");
939         html.append(this.javascriptTree);
940         html.append("CssClassColumn0\" style=\"padding-left:");
941 
942         html.append(getPaddingLeft(parentNode));
943         html.append("px;\">");
944         if (this.getDrawShifter()) {
945             String shifter = StringUtils.EMPTY;
946             if (hasSub) {
947                 if (showSub) {
948                     if (this.getShifterCollapse() != null) {
949                         shifter = this.getShifterCollapse();
950                     }
951                 }
952                 else {
953                     if (this.getShifterExpand() != null) {
954                         shifter = this.getShifterExpand();
955                     }
956                 }
957             }
958             else {
959                 if (this.getShifterEmpty() != null) {
960                     shifter = this.getShifterEmpty();
961                 }
962             }
963             if (StringUtils.isNotEmpty(shifter)) {
964                 html.append("<img id=\"");
965                 html.append(idPre);
966                 html.append("_Shifter\" onmousedown=\"");
967                 html.append(this.javascriptTree);
968                 html.append(".shifterDown('");
969                 html.append(handle);
970                 html.append("');\" onmouseout=\"");
971                 html.append(this.javascriptTree);
972                 html.append(".shifterOut();\" class=\"mgnlTreeShifter\" src=\"");
973                 html.append(this.getRequest().getContextPath());
974                 html.append(shifter);
975                 html.append("\" />");
976             }
977         }
978         html.append("<span id=\"");
979         html.append(idPre);
980         html.append("_Name\" onmouseover=\"");
981         html.append(jsHighlightNode);
982         html.append("\" onmouseout=\"");
983         html.append(jsResetNode);
984         html.append("\" onmousedown=\"");
985         html.append(jsSelectNode);
986         html.append(this.javascriptTree);
987         html.append(".pasteNode('");
988         html.append(handle);
989         html.append("'," + Tree.PASTETYPE_SUB + ",");
990         html.append(permissionWrite);
991         html.append(");\">");
992         if (StringUtils.isNotEmpty(icon)) {
993             html.append("<img id=\"");
994             html.append(idPre);
995             html.append("_Icon\" class=\"mgnlTreeIcon\" src=\"");
996             html.append(this.getRequest().getContextPath());
997             html.append(icon);
998             html.append("\" onmousedown=\"");
999             html.append(jsExpandNode);
1000             html.append("\"");
1001             if (this.getIconOndblclick() != null) {
1002                 html.append(" ondblclick=\"");
1003                 html.append(this.getIconOndblclick());
1004                 html.append("\"");
1005             }
1006             html.append(" />"); //$NON-NLS-1$
1007         }
1008         String dblclick = StringUtils.EMPTY;
1009         String htmlEdit = this.getColumns(0).getHtmlEdit();
1010         if (permissionWrite && StringUtils.isNotEmpty(htmlEdit)) {
1011             dblclick = " ondblclick=\"" + this.javascriptTree + ".editNodeData(this,'" + handle + "',0,'"+ StringUtils.replace(htmlEdit, "\"", "&quot;") + "');\""; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
1012         }
1013         html.append("<span class=\"mgnlTreeText\" id=\"");
1014         html.append(idPre);
1015         html.append("_Column0Main\"");
1016         html.append(dblclick);
1017         html.append(">");
1018         html.append(name);
1019         html.append("</span></span></span>"); //$NON-NLS-1$
1020 
1021         // this is done because js is not executed when you get it with ajax
1022         html.append(new Hidden(idPre + "_PermissionWrite", Boolean.toString(permissionWrite), false).getHtml()); //$NON-NLS-1$
1023         html.append(new Hidden(idPre + "_IsDeleted", Boolean.toString(isDeleted), false).getHtml()); //$NON-NLS-1$
1024         html.append(new Hidden(idPre + "_ItemType", itemType, false).getHtml()); //$NON-NLS-1$
1025         html.append(new Hidden(idPre + "_IsActivated", Boolean.toString(isActivated), false).getHtml()); //$NON-NLS-1$
1026 
1027         // Put your own stuff here. Good luck!
1028         onGetHtmlOfSingleItem(html, parentNode, itemType, item , idPre);
1029 
1030         for (int i = 1; i < this.getColumns().size(); i++) {
1031             String str = StringUtils.EMPTY;
1032             TreeColumn tc = this.getColumns(i);
1033             if (!itemType.equals(ITEM_TYPE_NODEDATA)) {
1034                 // content node ItemType.NT_CONTENTNODE and ItemType.NT_CONTENT
1035                 if (!tc.getIsNodeDataType() && !tc.getIsNodeDataValue()) {
1036                     tc.setWebsiteNode(c);
1037                     tc.setId(handle);
1038                     str = tc.getHtml();
1039                 }
1040             }
1041             else {
1042                 if (tc.getIsNodeDataType()) {
1043                     str = NodeDataUtil.getTypeName(d);
1044                 }
1045                 else if (tc.getIsNodeDataValue()) {
1046                     final String stringValue = NodeDataUtil.getValueString(d);
1047                     str = StringEscapeUtils.escapeXml(stringValue);
1048                 }
1049                 if (StringUtils.isEmpty(str)) {
1050                     str = TreeColumn.EMPTY;
1051                 }
1052                 tc.setName(name); // workaround, will be passed to js TreeColumn object
1053             }
1054             tc.setEvent("onmouseover", jsHighlightNode, true); //$NON-NLS-1$
1055             tc.setEvent("onmouseout", jsResetNode, true); //$NON-NLS-1$
1056             tc.setEvent("onmousedown", jsSelectNode, true); //$NON-NLS-1$
1057             html.append("<span class=\"mgnlTreeColumn ");
1058             html.append(this.javascriptTree);
1059             html.append("CssClassColumn");
1060             html.append(i);
1061             html.append("\"><span id=\"");
1062             html.append(idPre);
1063             html.append("_Column");
1064             html.append(i);
1065             html.append("Main\"");
1066             html.append(tc.getHtmlCssClass());
1067             html.append(tc.getHtmlEvents());
1068             htmlEdit = tc.getHtmlEdit();
1069             if (permissionWrite && StringUtils.isNotEmpty(htmlEdit)) {
1070                 html.append(" ondblclick=\"");
1071                 html.append(this.javascriptTree);
1072                 html.append(".editNodeData(this,'");
1073                 html.append(handle);
1074                 html.append("',");
1075                 html.append(i);
1076                 html.append(",'");
1077                 html.append(StringUtils.replace(htmlEdit, "\"", "&quot;"));
1078                 html.append("');\"");
1079                 html.append(",'");
1080 
1081             }
1082             html.append(">");
1083             html.append(str);
1084             html.append("</span></span>");
1085         }
1086         html.append("</div>"); //$NON-NLS-1$
1087         String display = "none"; //$NON-NLS-1$
1088         if (showSub) {
1089             display = "block"; //$NON-NLS-1$
1090         }
1091         html.append("<div id=\"");
1092         html.append(idPre);
1093         html.append("_DivSub\" style=\"display:");
1094         html.append(display);
1095         html.append(";\">");
1096         if (hasSub) {
1097             if (showSub) {
1098                 String pathRemaining = this.getPathOpen().substring(this.getPathCurrent().length());
1099                 if (pathRemaining.length() > 0) {
1100                     // get rid of first slash (/people/franz -> people/franz)
1101                     String slash = "/"; //$NON-NLS-1$
1102                     if (this.getPathCurrent().equals("/")) { //$NON-NLS-1$
1103                         // first slash already removed
1104                         slash = StringUtils.EMPTY; // no slash needed between pathCurrent and nextChunk
1105                     }
1106                     else {
1107                         pathRemaining = pathRemaining.substring(1);
1108                     }
1109                     String nextChunk = StringUtils.substringBefore(pathRemaining, "/"); //$NON-NLS-1$
1110 
1111                     String pathNext = this.getPathCurrent() + slash + nextChunk;
1112                     this.setPathCurrent(pathNext);
1113                     html.append(this.getHtmlChildren());
1114                 }
1115             }
1116         }
1117         html.append("</div>\n"); //$NON-NLS-1$
1118 
1119     }
1120 
1121     protected void onGetHtmlOfSingleItem(StringBuffer html, Content parentNode, String itemType, Object item, String idPre) {
1122     }
1123 
1124     protected Iterator collectRenderedItems(Content parentNode, String itemType) {
1125         Iterator it;
1126         if (itemType.equalsIgnoreCase(ITEM_TYPE_NODEDATA)) {
1127             List nodeDatas = new ArrayList(parentNode.getNodeDataCollection());
1128             // order them alphabetically
1129             Collections.sort(nodeDatas, new Comparator() {
1130 
1131                 @Override
1132                 public int compare(Object arg0, Object arg1) {
1133                     return ((NodeData) arg0).getName().compareTo(((NodeData) arg1).getName());
1134                 }
1135             });
1136             it = nodeDatas.iterator();
1137         }
1138         else {
1139             Collection nodes = parentNode.getChildren(itemType);
1140             Comparator comp = this.getSortComparator();
1141             if(comp != null){
1142                 Collection sortedNodes = new TreeSet(comp);
1143                 sortedNodes.addAll(nodes);
1144                 nodes = sortedNodes;
1145             }
1146             it = nodes.iterator();
1147         }
1148         return it;
1149     }
1150 
1151     protected int getPaddingLeft(Content parentNode) throws RepositoryException {
1152         int left = (parentNode.getLevel() - StringUtils.countMatches(getRootPath(), "/")) * this.getIndentionWidth();
1153         int paddingLeft = left + 8;
1154         if (paddingLeft < 8) {
1155             paddingLeft = 8;
1156         }
1157         return paddingLeft;
1158     }
1159 
1160     protected boolean hasSub(Content c, String type) {
1161         int size;
1162         if (type.equalsIgnoreCase(ITEM_TYPE_NODEDATA)) {
1163             size = c.getNodeDataCollection().size();
1164         }
1165         else {
1166             size = c.getChildren(type).size();
1167         }
1168         return size > 0;
1169     }
1170 
1171     /**
1172      * Override to make special exclusions. The current nodedata or node is passed.
1173      * @param node
1174      * @param nodedata
1175      * @param itemType
1176      * @return true if the node is shown
1177      */
1178     protected boolean showNode(Content node, NodeData nodedata, String itemType) {
1179         return true;
1180     }
1181 
1182     /**
1183      * @param item ContextMenuItem
1184      */
1185     public void addMenuItem(ContextMenuItem item) {
1186         menu.addMenuItem(item);
1187     }
1188 
1189     /**
1190      * @param item FunctionBarItem
1191      */
1192     public void addFunctionBarItem(FunctionBarItem item) {
1193         if (item != null) {
1194             item.setJavascriptMenuName(functionBar.getJavascriptName());
1195         }
1196         functionBar.addMenuItem(item);
1197     }
1198 
1199     /**
1200      * Convenience method to add a function bar item that already exists in the context menu.
1201      */
1202     public void addFunctionBarItemFromContextMenu(String itemName) {
1203         final ContextMenuItem menuItem = getMenu().getMenuItemByName(itemName);
1204         if (menuItem != null) {
1205             addFunctionBarItem(new FunctionBarItem(menuItem));
1206         }
1207     }
1208 
1209     /**
1210      * Add a separator line between context menu items.
1211      */
1212     public void addSeparator() {
1213         menu.addMenuItem(null);
1214     }
1215 
1216     public ContextMenu getMenu() {
1217         return this.menu;
1218     }
1219 
1220     /**
1221      * @return the function bar object
1222      */
1223     public FunctionBar getFunctionBar() {
1224         return this.functionBar;
1225     }
1226 
1227     public void setMenu(ContextMenu menu) {
1228         this.menu = menu;
1229     }
1230 
1231     /**
1232      * @param functionBar the function bar object
1233      */
1234     public void setFunctionBar(FunctionBar functionBar) {
1235         this.functionBar = functionBar;
1236     }
1237 
1238     /**
1239      * @return Returns the browseMode.
1240      */
1241     public boolean isBrowseMode() {
1242         return browseMode;
1243     }
1244 
1245     /**
1246      * @param browseMode The browseMode to set.
1247      */
1248     public void setBrowseMode(boolean browseMode) {
1249         this.browseMode = browseMode;
1250     }
1251 
1252     /**
1253      * @return the current HierarchyManager
1254      */
1255     public HierarchyManager getHierarchyManager() {
1256         return hm;
1257     }
1258 
1259     /**
1260      * @param hm The HierarchyManager to set.
1261      */
1262     private void setHierarchyManager(HierarchyManager hm) {
1263         this.hm = hm;
1264     }
1265 
1266 
1267     public Comparator getSortComparator() {
1268         return sortComparator;
1269     }
1270 
1271 
1272     public void setSortComparator(Comparator sortComperator) {
1273         this.sortComparator = sortComperator;
1274     }
1275 
1276     public String getRootPath() {
1277         return rootPath;
1278     }
1279 
1280     public void setRootPath(String rootPath) {
1281         this.rootPath = rootPath;
1282     }
1283 
1284 
1285 }