View Javadoc

1   /**
2    * This file Copyright (c) 2013-2014 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.workbench.tree.drop;
35  
36  import static info.magnolia.jcr.util.NodeUtil.*;
37  
38  import info.magnolia.jcr.util.NodeUtil;
39  import info.magnolia.ui.vaadin.integration.jcr.JcrItemAdapter;
40  import info.magnolia.ui.workbench.tree.HierarchicalJcrContainer;
41  import info.magnolia.ui.workbench.tree.MoveHandler;
42  import info.magnolia.ui.workbench.tree.MoveLocation;
43  
44  import java.util.Arrays;
45  import java.util.Collection;
46  import java.util.Iterator;
47  import java.util.Set;
48  
49  import javax.jcr.Item;
50  import javax.jcr.Node;
51  import javax.jcr.Property;
52  import javax.jcr.RepositoryException;
53  
54  import org.slf4j.Logger;
55  import org.slf4j.LoggerFactory;
56  
57  import com.vaadin.event.DataBoundTransferable;
58  import com.vaadin.event.Transferable;
59  import com.vaadin.event.dd.DragAndDropEvent;
60  import com.vaadin.event.dd.DropHandler;
61  import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
62  import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
63  import com.vaadin.shared.ui.dd.VerticalDropLocation;
64  import com.vaadin.ui.AbstractSelect.AbstractSelectTargetDetails;
65  import com.vaadin.ui.TreeTable;
66  
67  /**
68   * Vaadin {@link DropHandler} for moving JCR nodes and properties in trees, with Drag and Drop.
69   * <p>
70   * Additionally, the dropping conditions can be restricted, by implementing a {@link DropConstraint}
71   * and configuring it in the {@link info.magnolia.ui.workbench.definition.WorkbenchDefinition WorkbenchDefinition}.
72   */
73  public class TreeViewDropHandler implements MoveHandler, DropHandler {
74  
75      private final Logger log = LoggerFactory.getLogger(getClass());
76  
77      private TreeTable tree;
78      private DropConstraint constraint;
79      private AcceptCriterion serverSideCriterion;
80  
81      public TreeViewDropHandler() {
82          createAcceptCriterion();
83      }
84  
85      public TreeViewDropHandler(TreeTable tree, DropConstraint constraint) {
86          this.tree = tree;
87          this.constraint = constraint;
88          createAcceptCriterion();
89      }
90  
91      @Override
92      public void drop(DragAndDropEvent dropEvent) {
93          // Called whenever a drop occurs on the component
94  
95          // Make sure the drag source is the same tree
96          Transferable t = dropEvent.getTransferable();
97  
98          // First acceptance criteria.
99          // Make sure the drag source is the same tree
100         if (t.getSourceComponent() != tree || !(t instanceof DataBoundTransferable)) {
101             return;
102         }
103 
104         AbstractSelectTargetDetails target = (AbstractSelectTargetDetails) dropEvent.getTargetDetails();
105         // Get id's of the target item
106         Object targetItemId = target.getItemIdOver();
107         // On which side of the target the item was dropped
108         VerticalDropLocation location = target.getDropLocation();
109         if (location == null) {
110             log.debug("DropLocation is null. Do nothing.");
111             return;
112         }
113         // Get id's of the dragged items
114         Iterator<Object> selected = getItemIdsToMove(dropEvent).iterator();
115         while (selected.hasNext()) {
116             Object sourceItemId = selected.next();
117             moveNode(sourceItemId, targetItemId, location);
118         }
119     }
120 
121     /**
122      * Returns a collection of itemIds to move:
123      * <ul>
124      * <li>all <em>selected</em> itemIds if and only if the dragging node is <em>also</em> selected</li>
125      * <li>only the dragging itemId if it's not selected</li>.
126      * </ul>
127      */
128     private Collection<Object> getItemIdsToMove(DragAndDropEvent dropEvent) {
129         Transferable t = dropEvent.getTransferable();
130         Object draggingItemId = ((DataBoundTransferable) t).getItemId();
131 
132         // all selected itemIds if and only if the dragging node is also selected
133         Set<Object> selectedItemIds = (Set<Object>) ((TreeTable) t.getSourceComponent()).getValue();
134         if (selectedItemIds.contains(draggingItemId)) {
135             return selectedItemIds;
136         }
137 
138         // only the dragging itemId if it's not selected
139         return Arrays.asList(draggingItemId);
140     }
141 
142     /**
143      * Accept dragged Elements.
144      */
145     @Override
146     public AcceptCriterion getAcceptCriterion() {
147         return serverSideCriterion;
148     }
149 
150     /**
151      * Move a node within a tree onto, above or below another node depending on
152      * the drop location.
153      *
154      * @param sourceItemId
155      * id of the item to move
156      * @param targetItemId
157      * id of the item onto which the source node should be moved
158      * @param location
159      * VerticalDropLocation indicating where the source node was
160      * dropped relative to the target node
161      */
162     private void moveNode(Object sourceItemId, Object targetItemId, VerticalDropLocation location) {
163         log.debug("DropLocation: {}", location.name());
164         // Get Item from tree
165         HierarchicalJcrContainer container = (HierarchicalJcrContainer) tree.getContainerDataSource();
166         JcrItemAdapter sourceItem = (JcrItemAdapter) container.getItem(sourceItemId);
167         JcrItemAdapter targetItem = (JcrItemAdapter) container.getItem(targetItemId);
168 
169         // Sorting goes as
170         // - If dropped ON a node, we append it as a child
171         // - If dropped on the TOP part of a node, we move/add it before
172         // the node
173         // - If dropped on the BOTTOM part of a node, we move/add it
174         // after the node
175 
176         if (location == VerticalDropLocation.MIDDLE) {
177             if (constraint.allowedAsChild(sourceItem, targetItem)) {
178                 // move first in the container
179                 moveItem(sourceItem, targetItem, MoveLocation.INSIDE);
180                 container.setParent(sourceItemId, targetItemId);
181             }
182         } else {
183             Object parentId = container.getParent(targetItemId);
184             if (location == VerticalDropLocation.TOP) {
185                 if (parentId != null && constraint.allowedBefore(sourceItem, targetItem)) {
186                     // move first in the container
187                     moveItem(sourceItem, targetItem, MoveLocation.BEFORE);
188                     container.setParent(sourceItemId, parentId);
189                 }
190             } else if (location == VerticalDropLocation.BOTTOM) {
191                 if (parentId != null && constraint.allowedAfter(sourceItem, targetItem)) {
192                     moveItem(sourceItem, targetItem, MoveLocation.AFTER);
193                     container.setParent(sourceItemId, parentId);
194                 }
195             }
196         }
197     }
198 
199     /**
200      * Create a serverSide {@link AcceptCriterion} based on the {@link DropConstraint} implementation.
201      */
202     private void createAcceptCriterion() {
203         serverSideCriterion = new ServerSideCriterion() {
204 
205             @Override
206             public boolean accept(DragAndDropEvent dragEvent) {
207                 boolean res = true;
208                 Iterator<Object> selected = getItemIdsToMove(dragEvent).iterator();
209                 while (selected.hasNext()) {
210                     Object sourceItemId = selected.next();
211                     HierarchicalJcrContainer container = (HierarchicalJcrContainer) tree.getContainerDataSource();
212                     JcrItemAdapter sourceItem = (JcrItemAdapter) container.getItem(sourceItemId);
213                     res &= constraint.allowedToMove(sourceItem);
214                 }
215                 return res;
216             }
217         };
218 
219     }
220 
221     /**
222      * Performs check for moving items. Evaluates true when first node is of type Node and source and target are not same.
223      */
224     public boolean basicMoveCheck(Item source, Item target) {
225         try {
226             // target must be node, to allow moving in
227             if (!target.isNode()) {
228                 return false;
229             }
230             // Source and origin are the same... do nothing
231             if (target.getPath().equals(source.getPath()) && target.getSession().getWorkspace().getName().equals(source.getSession().getWorkspace().getName())) {
232                 return false;
233             }
234             // Source can not be a child of target.
235             return !NodeUtil.isSame((Node) target, source.getParent());
236         } catch (RepositoryException re) {
237             log.debug("Cannot determine whether drag and drop is possible due to: {}", re.getMessage(), re);
238             return false;
239         }
240     }
241 
242     /**
243      * Performs move of the source node into target node or next to target node depending on the value of MoveLocation.
244      * Move is performed in session-write mode and as such requires explicit call to session.save() after performing the operation.
245      */
246     public void moveNode(Node nodeToMove, Node newParent, MoveLocation location) throws RepositoryException {
247         if (!basicMoveCheck(nodeToMove, newParent)) {
248             return;
249         }
250         switch (location) {
251         case INSIDE:
252             String newPath = combinePathAndName(newParent.getPath(), nodeToMove.getName());
253             nodeToMove.getSession().move(nodeToMove.getPath(), newPath);
254             break;
255         case BEFORE:
256             moveNodeBefore(nodeToMove, newParent);
257             break;
258         case AFTER:
259             moveNodeAfter(nodeToMove, newParent);
260             break;
261         }
262     }
263 
264     /**
265      * Performs move of the source node or property into target node or next to target node depending on the value of MoveLocation.
266      * This method will persist move by calling session.save() explicitly. And will return true/false depending on whether move was successful or not.
267      */
268     @Override
269     public boolean moveItem(com.vaadin.data.Item source, com.vaadin.data.Item target, MoveLocation location) {
270         Item sourceItem = ((JcrItemAdapter) source).getJcrItem();
271         Item targetItem = ((JcrItemAdapter) target).getJcrItem();
272         if (!basicMoveCheck(sourceItem, targetItem)) {
273             return false;
274         }
275         try {
276             final Node targetNode = (Node) targetItem;
277             if (sourceItem.isNode()) {
278                 moveNode((Node) sourceItem, targetNode, location);
279             } else {
280                 NodeUtil.moveProperty((Property) sourceItem, targetNode);
281             }
282             sourceItem.getSession().save();
283             return true;
284         } catch (RepositoryException re) {
285             log.debug("Cannot execute drag and drop action", re);
286             return false;
287         }
288     }
289 
290     @Override
291     public DropHandler asDropHandler() {
292         return this;
293     }
294 }