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