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