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