View Javadoc
1   /**
2    * This file Copyright (c) 2010-2018 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.contentapp.movedialog;
35  
36  import static java.util.stream.Collectors.toList;
37  
38  import info.magnolia.config.MutableWrapper;
39  import info.magnolia.context.MgnlContext;
40  import info.magnolia.event.EventBus;
41  import info.magnolia.event.ResettableEventBus;
42  import info.magnolia.event.SimpleEventBus;
43  import info.magnolia.i18nsystem.I18nizer;
44  import info.magnolia.i18nsystem.SimpleTranslator;
45  import info.magnolia.objectfactory.ComponentProvider;
46  import info.magnolia.ui.api.action.ActionDefinition;
47  import info.magnolia.ui.api.action.ConfiguredActionDefinition;
48  import info.magnolia.ui.api.app.AppContext;
49  import info.magnolia.ui.api.overlay.OverlayLayer.ModalityLevel;
50  import info.magnolia.ui.api.view.View;
51  import info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor;
52  import info.magnolia.ui.contentapp.movedialog.action.MoveCancelledAction;
53  import info.magnolia.ui.contentapp.movedialog.action.MoveNodeActionDefinition;
54  import info.magnolia.ui.dialog.BaseDialogPresenter;
55  import info.magnolia.ui.dialog.DialogView;
56  import info.magnolia.ui.dialog.actionarea.DialogActionExecutor;
57  import info.magnolia.ui.dialog.actionarea.definition.ConfiguredEditorActionAreaDefinition;
58  import info.magnolia.ui.dialog.definition.ConfiguredDialogDefinition;
59  import info.magnolia.ui.dialog.definition.DialogDefinition;
60  import info.magnolia.ui.dialog.definition.SecondaryActionDefinition;
61  import info.magnolia.ui.imageprovider.definition.ImageProviderDefinition;
62  import info.magnolia.ui.vaadin.dialog.BaseDialog;
63  import info.magnolia.ui.vaadin.integration.NullItem;
64  import info.magnolia.ui.vaadin.integration.contentconnector.ContentConnector;
65  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnector;
66  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
67  import info.magnolia.ui.workbench.WorkbenchPresenter;
68  import info.magnolia.ui.workbench.column.definition.ColumnDefinition;
69  import info.magnolia.ui.workbench.definition.ContentPresenterDefinition;
70  import info.magnolia.ui.workbench.definition.ContentPresenterDefinitionMutator;
71  import info.magnolia.ui.workbench.definition.WorkbenchDefinition;
72  import info.magnolia.ui.workbench.definition.WorkbenchDefinitionMutator;
73  import info.magnolia.ui.workbench.tree.MoveHandler;
74  import info.magnolia.ui.workbench.tree.MoveLocation;
75  import info.magnolia.ui.workbench.tree.TreePresenter;
76  import info.magnolia.ui.workbench.tree.drop.AlwaysTrueDropConstraint;
77  import info.magnolia.ui.workbench.tree.drop.DropConstraint;
78  import info.magnolia.ui.workbench.tree.drop.JcrDropConstraint;
79  
80  import java.util.HashMap;
81  import java.util.HashSet;
82  import java.util.Iterator;
83  import java.util.LinkedList;
84  import java.util.List;
85  import java.util.Map;
86  import java.util.Set;
87  
88  import javax.inject.Inject;
89  import javax.jcr.RepositoryException;
90  
91  import org.slf4j.Logger;
92  import org.slf4j.LoggerFactory;
93  
94  import com.vaadin.v7.data.Item;
95  import com.vaadin.v7.data.Property.ValueChangeListener;
96  
97  /**
98   * Implementation of {@link MoveDialogPresenter}.
99   */
100 public class MoveDialogPresenterImpl extends BaseDialogPresenter implements MoveDialogPresenter {
101 
102     private static final Logger log = LoggerFactory.getLogger(MoveDialogPresenterImpl.class);
103 
104     private final DialogView dialogView;
105 
106     private final EventBus eventBus = new ResettableEventBus(new SimpleEventBus());
107 
108     private final WorkbenchPresenter workbenchPresenter;
109 
110     private final AppContext appContext;
111 
112     private final I18nizer i18nizer;
113 
114     private final ContentConnector contentConnector;
115 
116     private final MoveHandler dropHandler;
117 
118     private final Map<MoveLocation, ActionDefinition> actionMap = new HashMap<>();
119 
120     private List<Item> nodesToMove;
121 
122     private DropConstraint constraint;
123 
124     private MoveActionCallback callback;
125 
126     private Item currentHostCandidate;
127 
128     private WorkbenchDefinition workbenchDefinition;
129 
130     @Inject
131     public MoveDialogPresenterImpl(ComponentProvider componentProvider, DialogView dialogView, WorkbenchPresenter workbenchPresenter, DialogActionExecutor executor, AppContext appContext, I18nizer i18nizer, SimpleTranslator simpleTranslator, ContentConnector contentConnector) {
132         super(componentProvider, executor, dialogView, i18nizer, simpleTranslator);
133         this.dialogView = dialogView;
134         this.workbenchPresenter = workbenchPresenter;
135         this.appContext = appContext;
136         this.i18nizer = i18nizer;
137         this.contentConnector = contentConnector;
138         this.dropHandler = componentProvider.newInstance(MoveHandler.class);
139     }
140 
141     @Override
142     public Object[] getActionParameters(String actionName) {
143         return new Object[] { nodesToMove, callback, appContext, getHostCandidate(), dropHandler };
144     }
145 
146     @Override
147     public DialogView start(BrowserSubAppDescriptor subAppDescriptor, List<Item> nodesToMove, MoveActionCallback callback) {
148         if (nodesToMove.isEmpty()) {
149             throw new IllegalArgumentException("Nodes to be moved cannot be empty.");
150         }
151 
152         final ImageProviderDefinition imageProviderDefinition = prepareImageProviderDefinition(subAppDescriptor);
153         this.workbenchDefinition = prepareWorkbenchDefinition(subAppDescriptor);
154 
155         this.nodesToMove = nodesToMove;
156 
157         // In case dropConstraint class does not exist, set the AlwaysTrueDropConstraint by default
158         Class<? extends DropConstraint> dropConstraintClass = workbenchDefinition.getDropConstraintClass();
159         if (dropConstraintClass == null) {
160             dropConstraintClass = contentConnector instanceof JcrContentConnector ? JcrDropConstraint.class : AlwaysTrueDropConstraint.class;
161         }
162 
163         this.constraint = componentProvider.newInstance(dropConstraintClass);
164 
165         this.callback = callback;
166 
167         initActions();
168 
169         final MoveDialogWorkbenchField field = new MoveDialogWorkbenchField(
170                 workbenchDefinition,
171                 imageProviderDefinition,
172                 workbenchPresenter,
173                 eventBus);
174 
175         field.addStyleName("choose-dialog");
176         field.setSizeFull();
177 
178         dialogView.setContent((View) () -> field);
179         field.addValueChangeListener((ValueChangeListener) event -> {
180             currentHostCandidate = contentConnector.getItem(event.getProperty().getValue());
181             updatePossibleMoveLocations(currentHostCandidate);
182 
183         });
184 
185         DialogDefinition dialogDefinition = prepareDialogDefinition();
186         getExecutor().setDialogDefinition(dialogDefinition);
187         dialogView.setCaption(dialogDefinition.getLabel());
188         dialogView.addDialogCloseHandler(dialogView1 -> ((ResettableEventBus) eventBus).reset());
189         super.start(dialogDefinition, appContext);
190 
191         Object firstItemId = contentConnector.getItemId(nodesToMove.get(0));
192         field.setValue(firstItemId);
193 
194         List<Object> movingItemIds = nodesToMove.stream().map(contentConnector::getItemId).collect(toList());
195         field.markMovingItem(movingItemIds);
196 
197         getView().getActionAreaView().getViewForAction(MoveLocation.INSIDE.name()).asVaadinComponent().addStyleName("commit");
198         getView().setClosable(true);
199         return dialogView;
200     }
201 
202     private ImageProviderDefinition prepareImageProviderDefinition(BrowserSubAppDescriptor subAppDescriptor) {
203         return MutableWrapper.wrap(subAppDescriptor.getImageProvider());
204     }
205 
206     private WorkbenchDefinition prepareWorkbenchDefinition(BrowserSubAppDescriptor subAppDescriptor) {
207         final WorkbenchDefinition workbenchDefinition = MutableWrapper.wrap(subAppDescriptor.getWorkbench());
208 
209         List<ContentPresenterDefinition> contentViews = workbenchDefinition.getContentViews();
210 
211         for (ContentPresenterDefinition contentPresenter : contentViews) {
212             if (TreePresenter.class.isAssignableFrom(contentPresenter.getImplementationClass())) {
213                 ContentPresenterDefinitionMutator.accessMutable(contentPresenter).setImplementationClass(MoveDialogTreePresenterWrapper.class);
214             }
215         }
216 
217         WorkbenchDefinitionMutator.accessMutable(workbenchDefinition)
218                 .setDialogWorkbench(true)
219                 .setEditable(false);
220 
221         for (ContentPresenterDefinition contentPresenter : workbenchDefinition.getContentViews()) {
222             removeAllColumnsAfterFirst(contentPresenter);
223         }
224 
225         return workbenchDefinition;
226     }
227 
228     private void removeAllColumnsAfterFirst(ContentPresenterDefinition contentPresenter) {
229         if (!contentPresenter.getColumns().isEmpty()) {
230             ColumnDefinition column = contentPresenter.getColumns().get(0);
231             contentPresenter.getColumns().clear();
232             contentPresenter.getColumns().add(column);
233         }
234     }
235 
236     /**
237      * @deprecated since 5.5.5, MovePossibilityPredicates have been unified and replaced with DropConstraints.
238      */
239     @Deprecated
240     protected void initMovePossibilityPredicates() {
241         log.warn("initMovePossibilityPredicates function is deprecated since 5.5.5 ,MovePossibilityPredicates have been unified and replaced with DropConstraints.");
242     }
243 
244     protected void updatePossibleMoveLocations(Item possibleHost) {
245         Set<MoveLocation> possibleLocations = new HashSet<>();
246         if (possibleHost == null) {
247             possibleHost = new NullItem();
248         }
249 
250         for (MoveLocation location : MoveLocation.values()) {
251             if (isMovePossible(possibleHost, location)) {
252                 possibleLocations.add(location);
253             }
254         }
255 
256         getActionArea().setPossibleMoveLocations(possibleLocations);
257     }
258 
259     private boolean isMovePossible(Item possibleHost, MoveLocation location) {
260         Iterator<? extends Item> it = nodesToMove.iterator();
261         boolean isPossible = true;
262         while (it.hasNext() && isPossible) {
263             Item item = it.next();
264             switch (location) {
265             case INSIDE:
266                 isPossible &= constraint.allowedAsChild(item, possibleHost);
267                 break;
268             case BEFORE:
269                 isPossible &= constraint.allowedBefore(item, possibleHost);
270                 break;
271             case AFTER:
272                 isPossible &= constraint.allowedAfter(item, possibleHost);
273                 break;
274             default:
275                 break;
276             }
277         }
278         return isPossible;
279     }
280 
281     private void initActions() {
282         for (MoveLocation location : MoveLocation.values()) {
283             ConfiguredActionDefinition definition = i18nizer.decorate(new MoveNodeActionDefinition(location));
284             definition.setName(location.name());
285             actionMap.put(location, definition);
286         }
287     }
288 
289     private DialogDefinition prepareDialogDefinition() {
290         ConfiguredDialogDefinition def = new ConfiguredDialogDefinition();
291         def = i18nizer.decorate(def);
292         def.setId("ui-contentapp:code:MoveDialogPresenterImpl.moveDialog");
293 
294         for (MoveLocation location : MoveLocation.values()) {
295             def.addAction(actionMap.get(location));
296         }
297 
298         ConfiguredActionDefinition cancelDef = new ConfiguredActionDefinition();
299         cancelDef.setName("cancel");
300         cancelDef.setImplementationClass(MoveCancelledAction.class);
301         def.addAction(cancelDef);
302 
303         ConfiguredEditorActionAreaDefinition actionAreaDefinition = new ConfiguredEditorActionAreaDefinition();
304         actionAreaDefinition.setPresenterClass(MoveDialogActionAreaPresenter.class);
305 
306         List<SecondaryActionDefinition> secondaryActions = new LinkedList<>();
307         secondaryActions.add(new SecondaryActionDefinition(MoveLocation.AFTER.name()));
308         secondaryActions.add(new SecondaryActionDefinition(MoveLocation.BEFORE.name()));
309         actionAreaDefinition.setSecondaryActions(secondaryActions);
310 
311         def.setActionArea(actionAreaDefinition);
312         def.setModalityLevel(ModalityLevel.LIGHT);
313         return def;
314     }
315 
316     @Override
317     public MoveDialogActionAreaPresenter getActionArea() {
318         return (MoveDialogActionAreaPresenter) super.getActionArea();
319     }
320 
321     @Override
322     protected DialogActionExecutor getExecutor() {
323         return (DialogActionExecutor) super.getExecutor();
324     }
325 
326     @Override
327     protected void executeAction(String actionName, Object[] actionContextParams) {
328         actionName = getCorrectActionName(actionName);
329         if (actionName != null) {
330             super.executeAction(actionName, actionContextParams);
331         }
332     }
333 
334     public String getCorrectActionName(String actionName) {
335         if (actionName.equals(BaseDialog.COMMIT_ACTION_NAME)) {
336             boolean moveInsideActionEnabled = getView().getActionAreaView().getViewForAction(MoveLocation.INSIDE.name()).asVaadinComponent().isEnabled();
337             actionName = moveInsideActionEnabled ? MoveLocation.INSIDE.name() : null;
338         }
339         return actionName;
340     }
341 
342     private Item getHostCandidate() {
343         if (currentHostCandidate != null) {
344             return currentHostCandidate;
345         } else {
346             try {
347                 String workspace = ((JcrContentConnector) contentConnector).getContentConnectorDefinition().getWorkspace();
348                 return new JcrNodeAdapter(MgnlContext.getJCRSession(workspace).getRootNode());
349             } catch (RepositoryException e) {
350                 return null;
351             }
352         }
353     }
354 
355     @Override
356     protected void onCancel () {
357         if (callback != null) {
358             callback.onMoveCancelled();
359         }
360     }
361 }