View Javadoc

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