View Javadoc

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