View Javadoc
1   /**
2    * This file Copyright (c) 2003-2014 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.module.admininterface;
35  
36  import info.magnolia.cms.core.Content;
37  import info.magnolia.cms.core.HierarchyManager;
38  import info.magnolia.cms.core.NodeData;
39  import info.magnolia.cms.core.Path;
40  import info.magnolia.cms.exchange.ExchangeException;
41  import info.magnolia.cms.exchange.Syndicator;
42  import info.magnolia.cms.gui.control.ControlImpl;
43  import info.magnolia.cms.gui.control.Tree;
44  import info.magnolia.cms.gui.misc.Sources;
45  import info.magnolia.cms.i18n.Messages;
46  import info.magnolia.cms.i18n.MessagesManager;
47  import info.magnolia.cms.i18n.MessagesUtil;
48  import info.magnolia.cms.security.AccessDeniedException;
49  import info.magnolia.cms.servlets.CommandBasedMVCServletHandler;
50  import info.magnolia.cms.util.AlertUtil;
51  import info.magnolia.cms.util.ContentUtil;
52  import info.magnolia.cms.util.ExclusiveWrite;
53  import info.magnolia.commands.CommandsManager;
54  import info.magnolia.context.Context;
55  import info.magnolia.context.MgnlContext;
56  import info.magnolia.module.admininterface.commands.BaseActivationCommand;
57  import info.magnolia.objectfactory.Classes;
58  
59  import java.io.IOException;
60  import java.util.Iterator;
61  
62  import javax.jcr.PathNotFoundException;
63  import javax.jcr.RepositoryException;
64  import javax.servlet.http.HttpServletRequest;
65  import javax.servlet.http.HttpServletResponse;
66  
67  import org.apache.commons.chain.Command;
68  import org.apache.commons.lang.StringUtils;
69  import org.slf4j.Logger;
70  import org.slf4j.LoggerFactory;
71  
72  
73  /**
74   * This class wraps the tree control. The AdminInterfaceServlet instantiates a subclass. To build your own tree you
75   * have to override the prepareTree() method
76   * @author philipp
77   * @author Fabrizio Giustina
78   */
79  
80  public class AdminTreeMVCHandler extends CommandBasedMVCServletHandler {
81  
82      /**
83       * this are the used actions
84       */
85      protected static final String COMMAND_SHOW_TREE = "show"; //$NON-NLS-1$
86  
87      protected static final String COMMAND_COPY_NODE = "copy"; //$NON-NLS-1$
88  
89      protected static final String COMMAND_MOVE_NODE = "move"; //$NON-NLS-1$
90  
91      protected static final String COMMAND_ACTIVATE = "activate"; //$NON-NLS-1$
92  
93      protected static final String COMMAND_DEACTIVATE = "deactivate"; //$NON-NLS-1$
94  
95      protected static final String COMMAND_CREATE_NODE = "createNode"; //$NON-NLS-1$
96  
97      protected static final String COMMAND_DELETE_NODE = "delete"; //$NON-NLS-1$
98  
99      protected static final String COMMAND_SAVE_VALUE = "saveValue"; //$NON-NLS-1$
100 
101     /**
102      * The view names
103      */
104 
105     protected static final String VIEW_TREE = "tree"; //$NON-NLS-1$
106 
107     protected static final String VIEW_CREATE = "create"; //$NON-NLS-1$
108 
109     protected static final String VIEW_VALUE = "value"; //$NON-NLS-1$
110 
111     protected static final String VIEW_NOTHING = "nothing"; //$NON-NLS-1$
112 
113     protected static final String VIEW_COPY_MOVE = "copymove"; //$NON-NLS-1$
114 
115     /**
116      * Log
117      */
118     private static Logger log = LoggerFactory.getLogger(AdminTreeMVCHandler.class);
119 
120     /**
121      * name of the tree (not the repository)
122      */
123     protected Tree tree;
124 
125     /**
126      * The class to instantiate a tree control
127      */
128     private String treeClass = Tree.class.getName();
129 
130     /**
131      * The class used to instantiate a AdminTreeConfiguration if not provided. This can get configured in the trees
132      * configuration node
133      */
134     private String configurationClass;
135 
136     /**
137      * The configuration used to configure the tree
138      */
139     protected AdminTreeConfiguration configuration;
140 
141     protected String newNodeName = "untitled";
142 
143     protected String createItemType = Tree.ITEM_TYPE_NODEDATA;
144 
145     protected String path;
146 
147     protected String pathOpen;
148 
149     protected String pathSelected;
150 
151     protected String rootPath;
152 
153     /**
154      * Used to pass the saved value to the view
155      */
156     protected String displayValue;
157 
158     protected String newPath;
159 
160     private String repository;
161 
162     private String i18nBasename;
163 
164     /**
165      * Used to display the same tree in the linkbrowser
166      */
167     protected boolean browseMode;
168 
169     private boolean enableDeleteConfirmation = true;
170 
171     /**
172      * Override this method if you are not using the same name for the tree and the repository
173      * @return name of the repository
174      */
175     public String getRepository() {
176         if (repository == null) {
177             repository = this.getName();
178         }
179         return repository;
180     }
181 
182     public void setRepository(String repository) {
183         this.repository = repository;
184     }
185 
186     /**
187      * @return the current HierarchyManager
188      */
189     public HierarchyManager getHierarchyManager() {
190         return MgnlContext.getHierarchyManager(this.getRepository());
191     }
192 
193     public AdminTreeMVCHandler(String name, HttpServletRequest request, HttpServletResponse response) {
194         super(name, request, response);
195     }
196 
197     @Override
198     public void init() {
199         super.init();
200 
201         path = this.getRequest().getParameter("path"); //$NON-NLS-1$
202         if (StringUtils.isEmpty(path)) {
203             if(StringUtils.isNotEmpty(this.getRootPath())){
204                 path = this.getRootPath();
205             }
206             else{
207                 path = "/"; //$NON-NLS-1$
208             }
209         }
210 
211         pathOpen = this.getRequest().getParameter("pathOpen"); //$NON-NLS-1$
212         pathSelected = this.getRequest().getParameter("pathSelected"); //$NON-NLS-1$
213 
214         this.setBrowseMode(StringUtils.equals(this.getRequest().getParameter("browseMode"), "true"));
215     }
216 
217     /**
218      * Depending on the request it is generating a logical command name
219      * @return name of the command
220      */
221     @Override
222     public String getCommand() {
223         // if an explicit action was called
224         if (StringUtils.isNotEmpty(super.getCommand())) {
225             return super.getCommand();
226         }
227         // actions returned from the tree (pased through treeAction)
228         if (StringUtils.isNotEmpty(this.getRequest().getParameter("treeAction"))) { //$NON-NLS-1$
229             int treeAction = Integer.parseInt(this.getRequest().getParameter("treeAction")); //$NON-NLS-1$
230 
231             if (treeAction == Tree.ACTION_COPY) {
232                 return COMMAND_COPY_NODE;
233             }
234             if (treeAction == Tree.ACTION_MOVE) {
235                 return COMMAND_MOVE_NODE;
236             }
237             if (treeAction == Tree.ACTION_ACTIVATE) {
238                 return COMMAND_ACTIVATE;
239             }
240             if (treeAction == Tree.ACTION_DEACTIVATE) {
241                 return COMMAND_DEACTIVATE;
242             }
243 
244             return this.getRequest().getParameter("treeAction"); //$NON-NLS-1$
245         }
246 
247         // other actions depending other informations
248         if (this.getRequest().getParameter("createItemType") != null) { //$NON-NLS-1$
249             return COMMAND_CREATE_NODE;
250         }
251 
252         if (this.getRequest().getParameter("deleteNode") != null) { //$NON-NLS-1$
253             return COMMAND_DELETE_NODE;
254         }
255 
256         // editet any value directly in the columns?
257         if (this.getRequest().getParameter("saveName") != null //$NON-NLS-1$
258                 || this.getRequest().getParameter("saveValue") != null
259                 // value to save is a node data's value (config admin)
260                 || "true".equals(this.getRequest().getParameter("isNodeDataValue")) //$NON-NLS-1$ //$NON-NLS-2$
261                 // value to save is a node data's type (config admin)
262                 || "true".equals(this.getRequest().getParameter("isNodeDataType"))) { //$NON-NLS-1$ //$NON-NLS-2$
263             return COMMAND_SAVE_VALUE;
264         }
265 
266         return COMMAND_SHOW_TREE;
267     }
268 
269     /**
270      * TODO: this is a temporary solution
271      */
272     @Override
273     protected Context getCommandContext(String commandName) {
274         Context context = MgnlContext.getInstance();
275 
276         // set general parameters (repository, path, ..)
277         context.put(Context.ATTRIBUTE_REPOSITORY, this.getRepository());
278 
279         if ("activate".equals(commandName) || "deactivate".equals(commandName)) {
280             context.put(BaseActivationCommand.ATTRIBUTE_SYNDICATOR, getActivationSyndicator(this.pathSelected));
281             if (this.pathSelected != null) {
282                 try {
283                     final String uuid = MgnlContext.getHierarchyManager(repository).getContent(this.pathSelected).getUUID();
284                     // really only the uuid should be used to identify a piece of content and nothing else
285                     context.put(Context.ATTRIBUTE_UUID, uuid);
286                     // retrieve content again using uuid and system context to get unaltered path.
287                     final String realPath = MgnlContext.getSystemContext().getHierarchyManager(repository).getContentByUUID(uuid).getHandle();
288                     context.put(Context.ATTRIBUTE_PATH, realPath);
289                 } catch (RepositoryException e) {
290                     // this should never happen, user just clicked on the content in admin central
291                     log.error("Failed to retrieve content node [{}:{}].", this.repository, this.pathSelected);
292                 }
293             }
294         } else if (this.pathSelected != null) {
295             // pathSelected is null in case of delete operation, it should be the responsibility of the caller
296             // to set the context attributes properly
297             context.put(Context.ATTRIBUTE_PATH, this.pathSelected);
298         }
299 
300         return context;
301     }
302 
303     /**
304      * Allow default catalogue
305      */
306     @Override
307     protected Command findCommand(String commandName) {
308         Command cmd = super.findCommand(commandName);
309         if (cmd == null) {
310             cmd = CommandsManager.getInstance().getCommand(CommandsManager.DEFAULT_CATALOG, commandName);
311         }
312         return cmd;
313     }
314 
315     /**
316      * Show the tree after execution of a command
317      */
318     @Override
319     protected String getViewNameAfterExecution(String commandName, Context ctx) {
320         return VIEW_TREE;
321     }
322 
323     /**
324      * Show the tree
325      */
326     public String show() {
327         return VIEW_TREE;
328     }
329 
330     /**
331      * Create a new node and show the tree
332      * @return newly created content node
333      */
334     public String createNode() {
335         getTree().setPath(path);
336         synchronized (ExclusiveWrite.getInstance()) {
337             String name = getTree().createNode(this.getNewNodeName(), createItemType);
338             setNewNodeName(name);
339         }
340         return VIEW_TREE;
341     }
342 
343     /**
344      * Copy a node
345      */
346     public String copy() {
347         try {
348             synchronized (ExclusiveWrite.getInstance()) {
349                 copyOrMove(Tree.ACTION_COPY);
350             }
351         }
352         catch (Exception e) {
353             log.error("can't copy", e);
354             AlertUtil.setMessage(MessagesManager.get("tree.error.copy") + " " + AlertUtil.getExceptionMessage(e));
355         }
356         return VIEW_COPY_MOVE;
357     }
358 
359     /**
360      * Move a node
361      */
362     public String move() {
363         try {
364             synchronized (ExclusiveWrite.getInstance()) {
365                 copyOrMove(Tree.ACTION_MOVE);
366             }
367         }
368         catch (Exception e) {
369             log.error("can't move", e);
370             AlertUtil.setMessage(MessagesManager.get("tree.error.move") + " " + AlertUtil.getExceptionMessage(e));
371         }
372         return VIEW_COPY_MOVE;
373 
374     }
375 
376     /**
377      * @param action
378      * @throws RepositoryException
379      * @throws ExchangeException
380      */
381     protected void copyOrMove(int action) throws ExchangeException, RepositoryException {
382         String pathClipboard = this.getRequest().getParameter("pathClipboard"); //$NON-NLS-1$
383         int pasteType = Integer.parseInt(this.getRequest().getParameter("pasteType")); //$NON-NLS-1$
384         newPath = pasteNode(pathClipboard, pathSelected, pasteType, action);
385 
386         if (pasteType == Tree.PASTETYPE_SUB) {
387             pathOpen = pathSelected;
388         }
389         else {
390             // open parent path of destination path
391             pathOpen = pathSelected.substring(0, pathSelected.lastIndexOf("/")); //$NON-NLS-1$
392         }
393 
394         pathSelected = null;
395     }
396 
397     public void deleteNode(String parentPath, String label) throws ExchangeException, RepositoryException {
398         Content parentNode = getHierarchyManager().getContent(parentPath);
399         String path;
400         if (!parentPath.equals("/")) { //$NON-NLS-1$
401             path = parentPath + "/" + label; //$NON-NLS-1$
402         }
403         else {
404             path = "/" + label; //$NON-NLS-1$
405         }
406         this.deactivateNode(path);
407         parentNode.delete(label);
408         parentNode.save();
409     }
410 
411     public void deleteNode(String path) throws Exception {
412         String parentPath = StringUtils.substringBeforeLast(path, "/"); //$NON-NLS-1$
413         String label = StringUtils.substringAfterLast(path, "/"); //$NON-NLS-1$
414         deleteNode(parentPath, label);
415     }
416 
417     public String delete() {
418         String deleteNode = this.getRequest().getParameter("deleteNode"); //$NON-NLS-1$
419         try {
420             synchronized (ExclusiveWrite.getInstance()) {
421                 deleteNode(path, deleteNode);
422             }
423         }
424         catch (Exception e) {
425             log.error("can't delete", e);
426             AlertUtil.setMessage(MessagesManager.get("tree.error.delete") + " " + AlertUtil.getExceptionMessage(e));
427         }
428         return VIEW_TREE;
429     }
430 
431     /**
432      * Create the <code>Syndicator</code> to activate the specified path. method implementation will make sure that
433      * proper node collection Rule and Sysdicator is used
434      * @param path node path to be activated
435      * @return the <code>Syndicator</code> used to activate
436      */
437     public Syndicator getActivationSyndicator(String path) {
438         // use command configuration
439         return null;
440     }
441 
442     /**
443      * Execute the deactivation command
444      * @param path
445      * @throws ExchangeException
446      * @throws RepositoryException
447      */
448     public void deactivateNode(String path) throws ExchangeException, RepositoryException {
449         if (MgnlContext.getHierarchyManager(this.getRepository()).isNodeData(path)) {
450             return;
451         }
452         CommandsManager cm = CommandsManager.getInstance();
453 
454         Command cmd = cm.getCommand(this.getName(), "deactivate");
455         if (cmd == null) {
456             cmd = cm.getCommand(CommandsManager.DEFAULT_CATALOG, "deactivate");
457         }
458 
459         if (cmd == null) {
460             log.error("deactivate command not found, deactivation will not be performed");
461             return;
462         }
463 
464         Context ctx = this.getCommandContext("deactivate");
465         // override/set path for deactivation
466         // Path is set to "/" if this is called via delete command and not directly through the tree handler
467         ctx.setAttribute(Context.ATTRIBUTE_PATH, path, Context.LOCAL_SCOPE);
468         try {
469             cmd.execute(ctx);
470         }
471         catch (Exception e) {
472             throw new ExchangeException(e);
473         }
474 
475     }
476 
477     public Content copyMoveNode(String source, String destination, boolean move) throws ExchangeException,
478     RepositoryException {
479         // todo: ??? generic -> RequestInterceptor.java
480         final HierarchyManager hm = getHierarchyManager();
481         if (hm.isExist(destination)) {
482             String parentPath = StringUtils.substringBeforeLast(destination, "/"); //$NON-NLS-1$
483             String label = StringUtils.substringAfterLast(destination, "/"); //$NON-NLS-1$
484             label = Path.getUniqueLabel(getHierarchyManager(), parentPath, label);
485             destination = parentPath + "/" + label; //$NON-NLS-1$
486         }
487         if (move) {
488             if (destination.indexOf(source + "/") == 0) { //$NON-NLS-1$
489                 // todo: disable this possibility in javascript
490                 // move source into destination not possible
491                 return null;
492             }
493             try {
494                 hm.moveTo(source, destination);
495             }
496             catch (Exception e) {
497                 // try to move below node data
498                 return null;
499             }
500         }
501         else {
502             // copy
503             hm.copyTo(source, destination);
504         }
505         Content newContent = hm.getContent(destination);
506         try {
507             newContent.updateMetaData();
508             if (!move) {
509                 // move doesn't deactivate. Updating metadata is enough to notice the change (status modified)
510                 newContent.getMetaData().setUnActivated();
511             }
512             updateChildMetaData(move, newContent);
513         }
514         catch (Exception e) {
515             if (log.isDebugEnabled()) {
516                 log.debug("Exception caught: " + e.getMessage(), e); //$NON-NLS-1$
517             }
518         }
519         newContent.save();
520         return newContent;
521     }
522 
523     private void updateChildMetaData(boolean move, Content newContent) throws RepositoryException, AccessDeniedException {
524         // update all children as well
525         for (Iterator iter = newContent.getChildren().iterator(); iter.hasNext();) {
526             Content child = (Content) iter.next();
527             child.updateMetaData();
528             if (!move) {
529                 // move doesn't deactivate. Updating metadata is enough to notice the change (status modified)
530                 child.getMetaData().setUnActivated();
531             }
532             updateChildMetaData(move, child);
533         }
534     }
535 
536     public void moveNode(String source, String destination) throws ExchangeException, RepositoryException {
537         this.copyMoveNode(source, destination, true);
538     }
539 
540     public void copyNode(String source, String destination) throws ExchangeException, RepositoryException {
541         this.copyMoveNode(source, destination, false);
542     }
543 
544     public String renameNode(String newLabel) throws AccessDeniedException, ExchangeException, PathNotFoundException,
545     RepositoryException {
546         String returnValue;
547         String parentPath = StringUtils.substringBeforeLast(this.getPath(), "/"); //$NON-NLS-1$
548         newLabel = Path.getValidatedLabel(newLabel);
549 
550         // don't rename if it uses the same name as the current
551         if (this.getPath().endsWith("/" + newLabel)) {
552             return newLabel;
553         }
554 
555         String dest = parentPath + "/" + newLabel; //$NON-NLS-1$
556         if (getHierarchyManager().isExist(dest)) {
557             newLabel = Path.getUniqueLabel(getHierarchyManager(), parentPath, newLabel);
558             dest = parentPath + "/" + newLabel; //$NON-NLS-1$
559         }
560 
561         //this.deactivateNode(this.getPath());
562 
563         log.info("Moving node from " + this.getPath() + " to " + dest); //$NON-NLS-1$ //$NON-NLS-2$
564         if (getHierarchyManager().isNodeData(this.getPath())) {
565             Content parentPage = getHierarchyManager().getContent(parentPath);
566             NodeData newNodeData = parentPage.createNodeData(newLabel);
567             NodeData existingNodeData = getHierarchyManager().getNodeData(this.getPath());
568             newNodeData.setValue(existingNodeData.getValue());
569             existingNodeData.delete();
570             dest = parentPath;
571         }
572         else {
573             Content current = getHierarchyManager().getContent(this.getPath());
574             ContentUtil.rename(current, newLabel);
575             current.getParent().save();
576         }
577 
578         Content newPage = getHierarchyManager().getContent(dest);
579         returnValue = newLabel;
580         newPage.updateMetaData();
581         newPage.save();
582 
583         return returnValue;
584     }
585 
586     /**
587      * Saves a value edited directly inside the tree. This can also be a lable
588      * @return name of the view
589      */
590     public String saveValue() {
591         String saveName = this.getRequest().getParameter("saveName"); //$NON-NLS-1$
592         Tree tree = getTree();
593 
594         // value to save is a node data's value (config admin)
595         boolean isNodeDataValue = "true".equals(this.getRequest().getParameter("isNodeDataValue")); //$NON-NLS-1$ //$NON-NLS-2$
596 
597         // value to save is a node data's type (config admin)
598         boolean isNodeDataType = "true".equals(this.getRequest().getParameter("isNodeDataType")); //$NON-NLS-1$ //$NON-NLS-2$
599 
600         String value = StringUtils.defaultString(this.getRequest().getParameter("saveValue")); //$NON-NLS-1$
601 
602         displayValue = StringUtils.EMPTY;
603         // value to save is a content's meta information
604         boolean isMeta = "true".equals(this.getRequest().getParameter("isMeta")); //$NON-NLS-1$ //$NON-NLS-2$
605         // value to save is a label (name of page, content node or node data)
606         boolean isLabel = "true".equals(this.getRequest().getParameter("isLabel")); //$NON-NLS-1$ //$NON-NLS-2$
607 
608         if (isNodeDataValue || isNodeDataType) {
609             tree.setPath(StringUtils.substringBeforeLast(path, "/")); //$NON-NLS-1$
610             saveName = StringUtils.substringAfterLast(path, "/"); //$NON-NLS-1$
611         }
612         else {
613             // "/modules/templating/Templates/x"
614             tree.setPath(path);
615         }
616 
617         if (isLabel) {
618             displayValue = rename(value);
619         }
620         else if (isNodeDataType) {
621             int type = Integer.valueOf(value).intValue();
622             synchronized (ExclusiveWrite.getInstance()) {
623                 displayValue = tree.saveNodeDataType(saveName, type);
624             }
625         }
626         else {
627             synchronized (ExclusiveWrite.getInstance()) {
628                 displayValue = tree.saveNodeData(saveName, value, isMeta);
629             }
630         }
631 
632         // if there was a displayValue passed show it instead of the written value
633         displayValue = StringUtils.defaultString(this.getRequest().getParameter("displayValue"), value); //$NON-NLS-1$
634 
635         displayValue = encodeHTML(displayValue);
636 
637         return VIEW_VALUE;
638     }
639 
640     public String encodeHTML(String value){
641         value = value.replace("<", "&lt;");
642         value = value.replace(">", "&gt;");
643         return value;
644     }
645 
646     /**
647      * Called during a renaming of a node. First is the action saveValue called
648      * @param value the new name
649      * @return return the new name (can change if there were not allowed characters passed)
650      */
651     protected String rename(String value) {
652         try {
653             synchronized (ExclusiveWrite.getInstance()) {
654                 return renameNode(value);
655             }
656         }
657         catch (Exception e) {
658             log.error("can't rename", e);
659             AlertUtil.setMessage(MessagesManager.get("tree.error.rename") + " " + AlertUtil.getExceptionMessage(e));
660         }
661         return StringUtils.EMPTY;
662     }
663 
664     public String pasteNode(String pathOrigin, String pathSelected, int pasteType, int action)
665             throws ExchangeException, RepositoryException {
666         boolean move = false;
667         if (action == Tree.ACTION_MOVE) {
668             move = true;
669         }
670         String label = StringUtils.substringAfterLast(pathOrigin, "/"); //$NON-NLS-1$
671         String slash = "/"; //$NON-NLS-1$
672         if (pathSelected.equals("/")) { //$NON-NLS-1$
673             slash = StringUtils.EMPTY;
674         }
675         String destination = pathSelected + slash + label;
676         if (pasteType == Tree.PASTETYPE_SUB && action != Tree.ACTION_COPY && destination.equals(pathOrigin)) {
677             // drag node to parent node: move to last position
678             pasteType = Tree.PASTETYPE_LAST;
679         }
680         if (pasteType == Tree.PASTETYPE_SUB) {
681             destination = pathSelected + slash + label;
682             Content touchedContent = this.copyMoveNode(pathOrigin, destination, move);
683             if (touchedContent == null) {
684                 return StringUtils.EMPTY;
685             }
686             return touchedContent.getHandle();
687 
688         }
689         else if (pasteType == Tree.PASTETYPE_LAST) {
690             // LAST only available for sorting inside the same directory
691             try {
692                 Content touchedContent = getHierarchyManager().getContent(pathOrigin);
693                 return touchedContent.getHandle();
694             }
695             catch (RepositoryException re) {
696                 return StringUtils.EMPTY;
697             }
698         }
699         else {
700             try {
701                 // PASTETYPE_ABOVE | PASTETYPE_BELOW
702                 String nameSelected = StringUtils.substringAfterLast(pathSelected, "/"); //$NON-NLS-1$
703                 String nameOrigin = StringUtils.substringAfterLast(pathOrigin, "/"); //$NON-NLS-1$
704                 Content tomove = getHierarchyManager().getContent(pathOrigin);
705                 Content selected = getHierarchyManager().getContent(pathSelected);
706                 // ordering inside a node?
707                 // do not check the uuid since this is not working on the root node !!
708                 if (tomove.getParent().getHandle().equals(selected.getParent().getHandle())) {
709                     // if move just set the new ordering
710                     if (move) {
711                         tomove.getParent().orderBefore(nameOrigin, nameSelected);
712                         // deactivate
713                         tomove.updateMetaData();
714                         // this.deactivateNode(pathOrigin);
715                         tomove.getParent().save();
716                     }
717                     else {
718                         Content newNode = this.copyMoveNode(pathOrigin, pathOrigin, move);
719                         tomove.getParent().orderBefore(newNode.getName(), nameSelected);
720                         tomove.getParent().save();
721                     }
722 
723                 }
724                 else {
725                     String newOrigin = selected.getParent().getHandle() + "/" + nameOrigin;
726                     // clean the newOrigin if we move/copy to the root
727                     if (newOrigin.startsWith("//")) {
728                         newOrigin = StringUtils.removeStart(newOrigin, "/");
729                     }
730                     Content newNode = this.copyMoveNode(pathOrigin, newOrigin, move);
731 
732                     if (pasteType == Tree.PASTETYPE_ABOVE) {
733                         newNode.getParent().orderBefore(newNode.getName(), nameSelected);
734                         newNode.getParent().save();
735                     }
736                 }
737                 return tomove.getHandle();
738             }
739             catch (RepositoryException re) {
740                 re.printStackTrace();
741                 log.error("Problem when pasting node", re);
742                 return StringUtils.EMPTY;
743             }
744         }
745     }
746 
747     /**
748      * Render the tree depending on the view name.
749      * @param view
750      * @throws IOException
751      */
752     @Override
753     public void renderHtml(String view) throws IOException {
754         StringBuffer html = new StringBuffer(500);
755 
756         // an alert can happen if there were deactivation problems during a renaming
757         if (AlertUtil.isMessageSet()) {
758             html.append("<input type=\"hidden\" id=\"mgnlMessage\" value=\"");
759             html.append(ControlImpl.escapeHTML(AlertUtil.getMessage()));
760             html.append("\" />");
761         }
762 
763         if (VIEW_TREE.equals(view) || VIEW_CREATE.equals(view) || VIEW_COPY_MOVE.equals(view)) {
764             // if there was a node created we have not to set the pathes
765             if (!view.equals(VIEW_CREATE)) {
766                 getTree().setPathOpen(pathOpen);
767                 getTree().setPathSelected(pathSelected);
768             }
769 
770             // after moving or copying
771             if (view.equals(VIEW_COPY_MOVE)) {
772                 // pass new path to tree.js for selecting the newly created node
773                 // NOTE: tree.js checks for this pattern; adapt it there, if any changes are made here
774                 html.append("<input type=\"hidden\" id=\"mgnlSelectNode\" value=\"");
775                 html.append(newPath);
776                 html.append("\" />");
777             }
778 
779             renderTree(html);
780         }
781 
782         // after saving a column value
783         else if (view.equals(VIEW_VALUE)) {
784             html.append(displayValue);
785         }
786         this.getResponse().getWriter().print(html);
787     }
788 
789     /**
790      * Create the html for the tree. Calls tree.getHtml after calling prepareTree.
791      * @param html
792      */
793     protected void renderTree(StringBuffer html) {
794         String mode = StringUtils.defaultString(this.getRequest().getParameter("treeMode")); //$NON-NLS-1$
795         boolean snippetMode = mode.equals("snippet"); //$NON-NLS-1$
796         Tree tree = getTree();
797 
798         tree.setJavascriptTree("mgnlTreeControl"); //$NON-NLS-1$
799         tree.setBrowseMode(this.isBrowseMode());
800 
801         if (!snippetMode) {
802             // MAGNOLIA-2221 MAGNOLIA-2793 Enabling this doctype puts Firefox in standards-compliance mode and completely breaks the tree rendering
803             // html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); //$NON-NLS-1$
804             html.append("<html>\n");
805             html.append("<head>\n"); //$NON-NLS-1$
806             html.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"); //$NON-NLS-1$
807             renderHeaderIncludes(html);
808             html.append("<title>Magnolia</title>\n"); //$NON-NLS-1$
809             html.append("</head>\n");
810             html.append("<body class=\"mgnlBgDark\">");
811         }
812 
813         tree.setSnippetMode(snippetMode);
814         tree.setHeight(50);
815 
816         tree.setPath(path);
817 
818         this.getConfiguration().prepareTree(tree, this.isBrowseMode(), this.getRequest());
819         this.getConfiguration().prepareContextMenu(tree, this.isBrowseMode(), this.getRequest());
820         this.getConfiguration().prepareFunctionBar(tree, this.isBrowseMode(), this.getRequest());
821 
822         if (!snippetMode) {
823             html.append("<div id=\"");
824             html.append(tree.getJavascriptTree());
825             html.append("_DivSuper\" style=\"display:block;\">");
826         }
827         html.append(tree.getHtml());
828         if (!snippetMode) {
829             html.append("</div>"); //$NON-NLS-1$
830         }
831 
832         if (!snippetMode) {
833             html.append("</body></html>"); //$NON-NLS-1$
834         }
835     }
836 
837     /**
838      * @param html
839      */
840     protected void renderHeaderIncludes(StringBuffer html) {
841         html.append(new Sources(this.getRequest().getContextPath()).getHtmlJs());
842         html.append(new Sources(this.getRequest().getContextPath()).getHtmlCss());
843     }
844 
845     protected void setTree(Tree tree) {
846         this.tree = tree;
847     }
848 
849     protected Tree getTree() {
850         if (tree == null) {
851             tree = Classes.quietNewInstance(getTreeClass(), getName(), getRepository());
852 
853             if (tree == null) {
854                 // try to get the Tree with the deprecated constructor !
855                 log.warn("The {} Tree class is probably using the deprecated (String name, String repository, HttpServletRequest request) constructor. Please use the (String name, String repository) constructor instead.", getTreeClass());
856                 tree = Classes.quietNewInstance(this.getTreeClass(), getName(), getRepository(), getRequest());
857             }
858             if (tree == null) {
859                 // again and this time fail w/ proper error ... or throw npe
860                 try {
861                     tree = Classes.newInstance(getTreeClass(), getName(), getRepository());
862                 } catch (ClassNotFoundException e) {
863                     log.error("Failed to find implementation class " + getTreeClass() + " for tree named " + getName() + " for repository " + getRepository(), e);
864                     return null;
865                 }
866             }
867             if (tree == null) {
868                 throw new NullPointerException("Failed to instantiate tree implemtation " + getTreeClass() + " named " + getName() + " for repository " + getRepository());
869             }
870             tree.setRootPath(this.getRootPath());
871         }
872         return tree;
873     }
874 
875 
876     public String getNewNodeName() {
877         return this.newNodeName;
878     }
879 
880 
881     public void setNewNodeName(String newNodeName) {
882         this.newNodeName = newNodeName;
883     }
884 
885     protected String getPath() {
886         return path;
887     }
888 
889     protected String getPathSelected() {
890         return pathSelected;
891     }
892 
893 
894     public String getCreateItemType() {
895         return this.createItemType;
896     }
897 
898 
899     public void setCreateItemType(String createItemType) {
900         this.createItemType = createItemType;
901     }
902 
903     /**
904      * @return Returns the browseMode.
905      */
906     public boolean isBrowseMode() {
907         return browseMode;
908     }
909 
910     /**
911      * @param browseMode The browseMode to set.
912      */
913     public void setBrowseMode(boolean browseMode) {
914         this.browseMode = browseMode;
915     }
916 
917     /**
918      * Returns the configuration object for this tree; if it's not been instanciated yet,
919      * this method attempts to instanciate the configurationClass. (i.e a pre-instanciated
920      * AdminTreeConfiguration could already have been set by content2bean)
921      */
922     public AdminTreeConfiguration getConfiguration() {
923         if (this.configuration == null) {
924             if (getConfigurationClass() == null) {
925                 return null;
926             }
927             final AdminTreeConfiguration treeConfiguration = Classes.quietNewInstance(getConfigurationClass());
928             setConfiguration(treeConfiguration);
929         }
930         return this.configuration;
931     }
932 
933     /**
934      * @param configuration The configuration to set.
935      */
936     public void setConfiguration(AdminTreeConfiguration configuration) {
937         this.configuration = configuration;
938         final Messages messages = MessagesUtil.chainWithDefault(getI18nBasename());
939         configuration.setMessages(messages);
940         if (configuration instanceof AbstractTreeConfiguration) {
941             ((AbstractTreeConfiguration) configuration).setEnableDeleteConfirmation(isEnableDeleteConfirmation());
942         }
943     }
944 
945     public String getConfigurationClass() {
946         return configurationClass;
947     }
948 
949     public void setConfigurationClass(String configClass) {
950         this.configurationClass = configClass;
951     }
952 
953     public String getTreeClass() {
954         return treeClass;
955     }
956 
957     public void setTreeClass(String treeClass) {
958         this.treeClass = treeClass;
959     }
960 
961     public String getI18nBasename() {
962         return i18nBasename;
963     }
964 
965     public void setI18nBasename(String i18nBasename) {
966         this.i18nBasename = i18nBasename;
967     }
968 
969     public String getRootPath() {
970         return rootPath;
971     }
972 
973     public void setRootPath(String rootPath) {
974         this.rootPath = rootPath;
975     }
976 
977     public boolean isEnableDeleteConfirmation() {
978         return enableDeleteConfirmation;
979     }
980 
981     public void setEnableDeleteConfirmation(boolean enableConfirmation) {
982         this.enableDeleteConfirmation = enableConfirmation;
983         AdminTreeConfiguration conf = getConfiguration();
984         if (conf != null && conf instanceof AbstractTreeConfiguration) {
985             ((AbstractTreeConfiguration) conf).setEnableDeleteConfirmation(isEnableDeleteConfirmation());
986         }
987     }
988 }