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.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     private 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 existingNodeData = getHierarchyManager().getNodeData(this.getPath());
567             parentPage.setNodeData(newLabel, existingNodeData.getValue());
568             existingNodeData.delete();
569             dest = parentPath;
570         }
571         else {
572             Content current = getHierarchyManager().getContent(this.getPath());
573             ContentUtil.rename(current, newLabel);
574             current.getParent().save();
575         }
576 
577         Content newPage = getHierarchyManager().getContent(dest);
578         returnValue = newLabel;
579         newPage.updateMetaData();
580         newPage.save();
581 
582         return returnValue;
583     }
584 
585     /**
586      * Saves a value edited directly inside the tree. This can also be a lable
587      * @return name of the view
588      */
589     public String saveValue() {
590         String saveName = this.getRequest().getParameter("saveName"); //$NON-NLS-1$
591         Tree tree = getTree();
592 
593         // value to save is a node data's value (config admin)
594         boolean isNodeDataValue = "true".equals(this.getRequest().getParameter("isNodeDataValue")); //$NON-NLS-1$ //$NON-NLS-2$
595 
596         // value to save is a node data's type (config admin)
597         boolean isNodeDataType = "true".equals(this.getRequest().getParameter("isNodeDataType")); //$NON-NLS-1$ //$NON-NLS-2$
598 
599         String value = StringUtils.defaultString(this.getRequest().getParameter("saveValue")); //$NON-NLS-1$
600         displayValue = StringUtils.EMPTY;
601         // value to save is a content's meta information
602         boolean isMeta = "true".equals(this.getRequest().getParameter("isMeta")); //$NON-NLS-1$ //$NON-NLS-2$
603         // value to save is a label (name of page, content node or node data)
604         boolean isLabel = "true".equals(this.getRequest().getParameter("isLabel")); //$NON-NLS-1$ //$NON-NLS-2$
605 
606         if (isNodeDataValue || isNodeDataType) {
607             tree.setPath(StringUtils.substringBeforeLast(path, "/")); //$NON-NLS-1$
608             saveName = StringUtils.substringAfterLast(path, "/"); //$NON-NLS-1$
609         }
610         else {
611             // "/modules/templating/Templates/x"
612             tree.setPath(path);
613         }
614 
615         if (isLabel) {
616             displayValue = rename(value);
617         }
618         else if (isNodeDataType) {
619             int type = Integer.valueOf(value).intValue();
620             synchronized (ExclusiveWrite.getInstance()) {
621                 displayValue = tree.saveNodeDataType(saveName, type);
622             }
623         }
624         else {
625             synchronized (ExclusiveWrite.getInstance()) {
626                 displayValue = tree.saveNodeData(saveName, value, isMeta);
627             }
628         }
629 
630         // if there was a displayValue passed show it instead of the written value
631         displayValue = StringUtils.defaultString(this.getRequest().getParameter("displayValue"), value); //$NON-NLS-1$
632 
633         displayValue = encodeHTML(displayValue);
634 
635         return VIEW_VALUE;
636     }
637 
638     public String encodeHTML(String value){
639         value = value.replace("<", "&lt;");
640         value = value.replace(">", "&gt;");
641         return value;
642     }
643 
644     /**
645      * Called during a renaming of a node. First is the action saveValue called
646      * @param value the new name
647      * @return return the new name (can change if there were not allowed characters passed)
648      */
649     protected String rename(String value) {
650         try {
651             synchronized (ExclusiveWrite.getInstance()) {
652                 return renameNode(value);
653             }
654         }
655         catch (Exception e) {
656             log.error("can't rename", e);
657             AlertUtil.setMessage(MessagesManager.get("tree.error.rename") + " " + AlertUtil.getExceptionMessage(e));
658         }
659         return StringUtils.EMPTY;
660     }
661 
662     public String pasteNode(String pathOrigin, String pathSelected, int pasteType, int action)
663     throws ExchangeException, RepositoryException {
664         boolean move = false;
665         if (action == Tree.ACTION_MOVE) {
666             move = true;
667         }
668         String label = StringUtils.substringAfterLast(pathOrigin, "/"); //$NON-NLS-1$
669         String slash = "/"; //$NON-NLS-1$
670         if (pathSelected.equals("/")) { //$NON-NLS-1$
671             slash = StringUtils.EMPTY;
672         }
673         String destination = pathSelected + slash + label;
674         if (pasteType == Tree.PASTETYPE_SUB && action != Tree.ACTION_COPY && destination.equals(pathOrigin)) {
675             // drag node to parent node: move to last position
676             pasteType = Tree.PASTETYPE_LAST;
677         }
678         if (pasteType == Tree.PASTETYPE_SUB) {
679             destination = pathSelected + slash + label;
680             Content touchedContent = this.copyMoveNode(pathOrigin, destination, move);
681             if (touchedContent == null) {
682                 return StringUtils.EMPTY;
683             }
684             return touchedContent.getHandle();
685 
686         }
687         else if (pasteType == Tree.PASTETYPE_LAST) {
688             // LAST only available for sorting inside the same directory
689             try {
690                 Content touchedContent = getHierarchyManager().getContent(pathOrigin);
691                 return touchedContent.getHandle();
692             }
693             catch (RepositoryException re) {
694                 return StringUtils.EMPTY;
695             }
696         }
697         else {
698             try {
699                 // PASTETYPE_ABOVE | PASTETYPE_BELOW
700                 String nameSelected = StringUtils.substringAfterLast(pathSelected, "/"); //$NON-NLS-1$
701                 String nameOrigin = StringUtils.substringAfterLast(pathOrigin, "/"); //$NON-NLS-1$
702                 Content tomove = getHierarchyManager().getContent(pathOrigin);
703                 Content selected = getHierarchyManager().getContent(pathSelected);
704                 // ordering inside a node?
705                 // do not check the uuid since this is not working on the root node !!
706                 if (tomove.getParent().getHandle().equals(selected.getParent().getHandle())) {
707                     // if move just set the new ordering
708                     if (move) {
709                         tomove.getParent().orderBefore(nameOrigin, nameSelected);
710                         // deactivate
711                         tomove.updateMetaData();
712                         // this.deactivateNode(pathOrigin);
713                         tomove.getParent().save();
714                     }
715                     else {
716                         Content newNode = this.copyMoveNode(pathOrigin, pathOrigin, move);
717                         tomove.getParent().orderBefore(newNode.getName(), nameSelected);
718                         tomove.getParent().save();
719                     }
720 
721                 }
722                 else {
723                     String newOrigin = selected.getParent().getHandle() + "/" + nameOrigin;
724                     // clean the newOrigin if we move/copy to the root
725                     if (newOrigin.startsWith("//")) {
726                         newOrigin = StringUtils.removeStart(newOrigin, "/");
727                     }
728                     Content newNode = this.copyMoveNode(pathOrigin, newOrigin, move);
729 
730                     if (pasteType == Tree.PASTETYPE_ABOVE) {
731                         newNode.getParent().orderBefore(newNode.getName(), nameSelected);
732                         newNode.getParent().save();
733                     }
734                 }
735                 return tomove.getHandle();
736             }
737             catch (RepositoryException re) {
738                 re.printStackTrace();
739                 log.error("Problem when pasting node", re);
740                 return StringUtils.EMPTY;
741             }
742         }
743     }
744 
745     /**
746      * Render the tree depending on the view name.
747      * @param view
748      * @throws IOException
749      */
750     public void renderHtml(String view) throws IOException {
751         StringBuffer html = new StringBuffer(500);
752 
753         // an alert can happen if there were deactivation problems during a renaming
754         if (AlertUtil.isMessageSet()) {
755             html.append("<input type=\"hidden\" id=\"mgnlMessage\" value=\"");
756             html.append(ControlImpl.escapeHTML(AlertUtil.getMessage()));
757             html.append("\" />");
758         }
759 
760         if (VIEW_TREE.equals(view) || VIEW_CREATE.equals(view) || VIEW_COPY_MOVE.equals(view)) {
761             // if there was a node created we have not to set the pathes
762             if (!view.equals(VIEW_CREATE)) {
763                 getTree().setPathOpen(pathOpen);
764                 getTree().setPathSelected(pathSelected);
765             }
766 
767             // after moving or copying
768             if (view.equals(VIEW_COPY_MOVE)) {
769                 // pass new path to tree.js for selecting the newly created node
770                 // NOTE: tree.js checks for this pattern; adapt it there, if any changes are made here
771                 html.append("<input type=\"hidden\" id=\"mgnlSelectNode\" value=\"");
772                 html.append(newPath);
773                 html.append("\" />");
774             }
775 
776             renderTree(html);
777         }
778 
779         // after saving a column value
780         else if (view.equals(VIEW_VALUE)) {
781             html.append(displayValue);
782         }
783         this.getResponse().getWriter().print(html);
784     }
785 
786     /**
787      * Create the html for the tree. Calls tree.getHtml after calling prepareTree.
788      * @param html
789      */
790     protected void renderTree(StringBuffer html) {
791         String mode = StringUtils.defaultString(this.getRequest().getParameter("treeMode")); //$NON-NLS-1$
792         boolean snippetMode = mode.equals("snippet"); //$NON-NLS-1$
793         Tree tree = getTree();
794 
795         tree.setJavascriptTree("mgnlTreeControl"); //$NON-NLS-1$
796         tree.setBrowseMode(this.isBrowseMode());
797 
798         if (!snippetMode) {
799             // MAGNOLIA-2221 MAGNOLIA-2793 Enabling this doctype puts Firefox in standards-compliance mode and completely breaks the tree rendering
800             // html.append("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"); //$NON-NLS-1$
801             html.append("<html>\n");
802             html.append("<head>\n"); //$NON-NLS-1$
803             html.append("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"/>\n"); //$NON-NLS-1$
804             renderHeaderIncludes(html);
805             html.append("<title>Magnolia</title>\n"); //$NON-NLS-1$
806             html.append("</head>\n");
807             html.append("<body class=\"mgnlBgDark\">");
808         }
809 
810         tree.setSnippetMode(snippetMode);
811         tree.setHeight(50);
812 
813         tree.setPath(path);
814 
815         this.getConfiguration().prepareTree(tree, this.isBrowseMode(), this.getRequest());
816         this.getConfiguration().prepareContextMenu(tree, this.isBrowseMode(), this.getRequest());
817         this.getConfiguration().prepareFunctionBar(tree, this.isBrowseMode(), this.getRequest());
818 
819         if (!snippetMode) {
820             html.append("<div id=\"");
821             html.append(tree.getJavascriptTree());
822             html.append("_DivSuper\" style=\"display:block;\">");
823         }
824         html.append(tree.getHtml());
825         if (!snippetMode) {
826             html.append("</div>"); //$NON-NLS-1$
827         }
828 
829         if (!snippetMode) {
830             html.append("</body></html>"); //$NON-NLS-1$
831         }
832     }
833 
834     /**
835      * @param html
836      */
837     protected void renderHeaderIncludes(StringBuffer html) {
838         html.append(new Sources(this.getRequest().getContextPath()).getHtmlJs());
839         html.append(new Sources(this.getRequest().getContextPath()).getHtmlCss());
840     }
841 
842     protected void setTree(Tree tree) {
843         this.tree = tree;
844     }
845 
846     protected Tree getTree() {
847         if (tree == null) {
848             tree = Classes.quietNewInstance(getTreeClass(), getName(), getRepository());
849 
850             if (tree == null) {
851                 // try to get the Tree with the deprecated constructor !
852                 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());
853                 tree = Classes.quietNewInstance(this.getTreeClass(), getName(), getRepository(), getRequest());
854             }
855             tree.setRootPath(this.getRootPath());
856         }
857         return tree;
858     }
859 
860 
861     public String getNewNodeName() {
862         return this.newNodeName;
863     }
864 
865 
866     public void setNewNodeName(String newNodeName) {
867         this.newNodeName = newNodeName;
868     }
869 
870     protected String getPath() {
871         return path;
872     }
873 
874     protected String getPathSelected() {
875         return pathSelected;
876     }
877 
878 
879     public String getCreateItemType() {
880         return this.createItemType;
881     }
882 
883 
884     public void setCreateItemType(String createItemType) {
885         this.createItemType = createItemType;
886     }
887 
888     /**
889      * @return Returns the browseMode.
890      */
891     public boolean isBrowseMode() {
892         return browseMode;
893     }
894 
895     /**
896      * @param browseMode The browseMode to set.
897      */
898     public void setBrowseMode(boolean browseMode) {
899         this.browseMode = browseMode;
900     }
901 
902     /**
903      * Returns the configuration object for this tree; if it's not been instanciated yet,
904      * this method attempts to instanciate the configurationClass. (i.e a pre-instanciated
905      * AdminTreeConfiguration could already have been set by content2bean)
906      */
907     public AdminTreeConfiguration getConfiguration() {
908         if (this.configuration == null) {
909             if (getConfigurationClass() == null) {
910                 return null;
911             }
912             final AdminTreeConfiguration treeConfiguration = Classes.quietNewInstance(getConfigurationClass());
913             setConfiguration(treeConfiguration);
914         }
915         return this.configuration;
916     }
917 
918     /**
919      * @param configuration The configuration to set.
920      */
921     public void setConfiguration(AdminTreeConfiguration configuration) {
922         this.configuration = configuration;
923         final Messages messages = MessagesUtil.chainWithDefault(getI18nBasename());
924         configuration.setMessages(messages);
925         if (configuration instanceof AbstractTreeConfiguration) {
926             ((AbstractTreeConfiguration) configuration).setEnableDeleteConfirmation(isEnableDeleteConfirmation());
927         }
928     }
929 
930     public String getConfigurationClass() {
931         return configurationClass;
932     }
933 
934     public void setConfigurationClass(String configClass) {
935         this.configurationClass = configClass;
936     }
937 
938     public String getTreeClass() {
939         return treeClass;
940     }
941 
942     public void setTreeClass(String treeClass) {
943         this.treeClass = treeClass;
944     }
945 
946     public String getI18nBasename() {
947         return i18nBasename;
948     }
949 
950     public void setI18nBasename(String i18nBasename) {
951         this.i18nBasename = i18nBasename;
952     }
953 
954     public String getRootPath() {
955         return rootPath;
956     }
957 
958     public void setRootPath(String rootPath) {
959         this.rootPath = rootPath;
960     }
961 
962     public boolean isEnableDeleteConfirmation() {
963         return enableDeleteConfirmation;
964     }
965 
966     public void setEnableDeleteConfirmation(boolean enableConfirmation) {
967         this.enableDeleteConfirmation = enableConfirmation;
968         AdminTreeConfiguration conf = getConfiguration();
969         if (conf != null && conf instanceof AbstractTreeConfiguration) {
970             ((AbstractTreeConfiguration) conf).setEnableDeleteConfirmation(isEnableDeleteConfirmation());
971         }
972     }
973 }