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