View Javadoc
1   /**
2    * This file Copyright (c) 2013-2017 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.pages.app.editor;
35  
36  import info.magnolia.cms.core.version.VersionManager;
37  import info.magnolia.cms.i18n.I18nContentSupport;
38  import info.magnolia.event.EventBus;
39  import info.magnolia.i18nsystem.SimpleTranslator;
40  import info.magnolia.objectfactory.Components;
41  import info.magnolia.pages.app.editor.event.NodeSelectedEvent;
42  import info.magnolia.pages.app.editor.pagebar.PageBarPresenter;
43  import info.magnolia.pages.app.editor.statusbar.StatusBarPresenter;
44  import info.magnolia.ui.actionbar.ActionbarPresenter;
45  import info.magnolia.ui.actionbar.ActionbarView;
46  import info.magnolia.ui.actionbar.definition.ActionbarDefinition;
47  import info.magnolia.ui.actionbar.definition.ActionbarGroupDefinition;
48  import info.magnolia.ui.actionbar.definition.ActionbarItemDefinition;
49  import info.magnolia.ui.actionbar.definition.ActionbarSectionDefinition;
50  import info.magnolia.ui.api.action.ActionDefinition;
51  import info.magnolia.ui.api.action.ActionExecutionException;
52  import info.magnolia.ui.api.action.ActionExecutor;
53  import info.magnolia.ui.api.app.AppContext;
54  import info.magnolia.ui.api.app.SubAppContext;
55  import info.magnolia.ui.api.app.SubAppEventBus;
56  import info.magnolia.ui.api.availability.AvailabilityChecker;
57  import info.magnolia.ui.api.availability.AvailabilityDefinition;
58  import info.magnolia.ui.api.event.AdmincentralEventBus;
59  import info.magnolia.ui.api.event.ContentChangedEvent;
60  import info.magnolia.ui.api.i18n.I18NAuthoringSupport;
61  import info.magnolia.ui.api.location.Location;
62  import info.magnolia.ui.api.message.Message;
63  import info.magnolia.ui.api.message.MessageType;
64  import info.magnolia.ui.contentapp.definition.ContentSubAppDescriptor;
65  import info.magnolia.ui.contentapp.detail.DetailLocation;
66  import info.magnolia.ui.contentapp.detail.DetailSubAppDescriptor;
67  import info.magnolia.ui.contentapp.detail.DetailView;
68  import info.magnolia.ui.framework.app.BaseSubApp;
69  import info.magnolia.ui.vaadin.editor.PageEditorListener;
70  import info.magnolia.ui.vaadin.editor.pagebar.PageBarView;
71  import info.magnolia.ui.vaadin.gwt.client.shared.AbstractElement;
72  import info.magnolia.ui.vaadin.gwt.client.shared.PageElement;
73  import info.magnolia.ui.vaadin.integration.contentconnector.ContentConnector;
74  import info.magnolia.ui.workbench.StatusBarView;
75  
76  import java.util.Arrays;
77  import java.util.Collections;
78  import java.util.List;
79  import java.util.Locale;
80  import java.util.Map;
81  
82  import javax.inject.Inject;
83  import javax.inject.Named;
84  
85  import org.apache.commons.lang3.ObjectUtils;
86  import org.apache.commons.lang3.StringUtils;
87  import org.slf4j.Logger;
88  import org.slf4j.LoggerFactory;
89  
90  /**
91   * SubApp putting together:
92   * <ul>
93   *   <li>{@link PageEditorPresenter}</li>
94   *   <li>{@link ActionbarPresenter}</li>
95   *   <li>{@link StatusBarPresenter}</li>
96   *   <li>{@link PageBarPresenter}</li>
97   * </ul>
98   * Keeps track on changes coming from the client side of the {@link PageEditorPresenter} by registering a {@link NodeSelectedEvent.Handler}
99   * to the subApp eventbus. This is triggered every time the {@link PageElement}, {@link info.magnolia.ui.vaadin.gwt.client.shared.AreaElement}
100  * or {@link info.magnolia.ui.vaadin.gwt.client.shared.ComponentElement} is selected inside the iFrame. This will result in updating the
101  * {@link ActionbarPresenter} accordingly using action availability. <br />
102  *
103  * When a page change happens, e.g. the user browses inside the iFrame, this will also be detected and trigger a chain
104  * of actions taking care of updating all necessary parts of the UI as well as the underlying framework.
105  *
106  */
107 public class PagesEditorSubApp extends BaseSubApp<PagesEditorSubAppView> implements PagesEditorSubAppView.Listener, ActionbarPresenter.Listener, PageEditorPresenter.Listener {
108 
109     private static final Logger log = LoggerFactory.getLogger(PagesEditorSubApp.class);
110 
111     private final ActionExecutor actionExecutor;
112     private final SubAppContext subAppContext;
113     private final PagesEditorSubAppView view;
114     private final EventBus subAppEventBus;
115     private final EventBus admincentralEventBus;
116     private final PageEditorPresenter pageEditorPresenter;
117     private final ActionbarPresenter actionbarPresenter;
118     private final PagesContentConnector contentConnector;
119     private final AvailabilityChecker availabilityChecker;
120     private final StatusBarPresenter statusBar;
121     private final PageBarPresenter pageBar;
122     private final AppContext appContext;
123     private final SimpleTranslator i18n;
124 
125     private String caption;
126 
127     @Inject
128     public PagesEditorSubApp(final ActionExecutor actionExecutor, final SubAppContext subAppContext, final PagesEditorSubAppView view,
129                              final @Named(AdmincentralEventBus.NAME) EventBus admincentralEventBus, final @Named(SubAppEventBus.NAME) EventBus subAppEventBus,
130                              final PageEditorPresenter pageEditorPresenter, final ActionbarPresenter actionbarPresenter,
131                              final SimpleTranslator i18n, AvailabilityChecker availabilityChecker, final PagesContentConnector contentConnector,
132                              final StatusBarPresenter statusBar, final PageBarPresenter pageBar) {
133         super(subAppContext, view);
134         this.actionExecutor = actionExecutor;
135         this.subAppContext = subAppContext;
136         this.view = view;
137         this.subAppEventBus = subAppEventBus;
138         this.admincentralEventBus = admincentralEventBus;
139         this.pageEditorPresenter = pageEditorPresenter;
140         this.actionbarPresenter = actionbarPresenter;
141         this.availabilityChecker = availabilityChecker;
142         this.statusBar = statusBar;
143         this.pageBar = pageBar;
144         this.contentConnector = contentConnector;
145         this.appContext = subAppContext.getAppContext();
146         this.i18n = i18n;
147         bindHandlers();
148     }
149 
150     /**
151      * @deprecated since 5.5 - use {@link PagesEditorSubApp#PagesEditorSubApp(ActionExecutor, SubAppContext, PagesEditorSubAppView, EventBus, EventBus, PageEditorPresenter, ActionbarPresenter, SimpleTranslator, AvailabilityChecker, PagesContentConnector, StatusBarPresenter, PageBarPresenter)}
152      */
153     @Deprecated
154     public PagesEditorSubApp(final ActionExecutor actionExecutor, final SubAppContext subAppContext, final PagesEditorSubAppView view, @Named(AdmincentralEventBus.NAME) EventBus admincentralEventBus,
155                              final @Named(SubAppEventBus.NAME) EventBus subAppEventBus, final PageEditorPresenter pageEditorPresenter, final ActionbarPresenter actionbarPresenter, VersionManager versionManager, final SimpleTranslator i18n, AvailabilityChecker availabilityChecker,
156                              ContentConnector contentConnector, StatusBarPresenter statusBar, PageBarPresenter pageBar) {
157         this(actionExecutor, subAppContext, view, admincentralEventBus, subAppEventBus, pageEditorPresenter, actionbarPresenter, i18n, availabilityChecker, (PagesContentConnector) contentConnector, statusBar, pageBar);
158     }
159 
160     /**
161      * @deprecated since 5.4 - use {@link PagesEditorSubApp#PagesEditorSubApp(ActionExecutor, SubAppContext, PagesEditorSubAppView, EventBus, EventBus, PageEditorPresenter, ActionbarPresenter, SimpleTranslator, AvailabilityChecker, PagesContentConnector, StatusBarPresenter, PageBarPresenter)}
162      */
163     @Deprecated
164     public PagesEditorSubApp(final ActionExecutor actionExecutor, final SubAppContext subAppContext, final PagesEditorSubAppView view, @Named(AdmincentralEventBus.NAME) EventBus admincentralEventBus,
165             final @Named(SubAppEventBus.NAME) EventBus subAppEventBus, final PageEditorPresenter pageEditorPresenter, final ActionbarPresenter actionbarPresenter, final PageBarView pageBarView,
166             I18NAuthoringSupport i18NAuthoringSupport, I18nContentSupport i18nContentSupport, VersionManager versionManager, final SimpleTranslator i18n, AvailabilityChecker availabilityChecker,
167             ContentConnector contentConnector, StatusBarView statusBarView) {
168         this(actionExecutor, subAppContext, view, admincentralEventBus, subAppEventBus, pageEditorPresenter, actionbarPresenter, i18n, availabilityChecker, (PagesContentConnector) contentConnector, Components.getComponent(StatusBarPresenter.class), Components.getComponent(PageBarPresenter.class));
169     }
170 
171     @Override
172     public String getCaption() {
173         return caption;
174     }
175 
176     public void updateCaption(DetailLocation location) {
177         this.caption = contentConnector.getPageTitle(location.getNodePath(), location.getVersion());
178         pageBar.setPageName(caption, location.getNodePath());
179     }
180 
181     @Override
182     public void updateCaptionForExternalPage(String title) {
183         this.caption = title;
184         pageBar.setPageName(caption);
185     }
186 
187     @Override
188     public PagesEditorSubAppView start(Location location) {
189         view.setListener(this);
190 
191         DetailLocation detailLocation = DetailLocation.wrap(location);
192         super.start(detailLocation);
193 
194         ActionbarDefinition actionbarDefinition = ((ContentSubAppDescriptor) getSubAppContext().getSubAppDescriptor()).getActionbar();
195         Map<String, ActionDefinition> actionDefinitions = getSubAppContext().getSubAppDescriptor().getActions();
196         actionbarPresenter.setListener(this);
197         ActionbarView actionbar = actionbarPresenter.start(actionbarDefinition, actionDefinitions);
198         hideActionbarSections();
199 
200         updateCaption(detailLocation);
201 
202         pageEditorPresenter.setListener(this);
203         view.setActionbarView(actionbar);
204         view.setPageBarView(pageBar.start(detailLocation));
205         view.setPageEditorView(pageEditorPresenter.start(detailLocation));
206         view.setStatusBarView(statusBar.start(detailLocation));
207 
208         return view;
209     }
210 
211     /**
212      * Informs the app framework when navigating pages inside the page editor.
213      * Updates the shell fragment, caption and current location.
214      */
215     protected void updateNodePath(String path) {
216         DetailLocation detailLocation = getCurrentLocation();
217         detailLocation.updateNodePath(path);
218         updateLocationDependentComponents(detailLocation);
219         getAppContext().updateSubAppLocation(getSubAppContext(), detailLocation);
220         pageEditorPresenter.getStatus().setNodePath(path);
221         pageEditorPresenter.updateParameters();
222     }
223 
224     @Override
225     public boolean supportsLocation(Location location) {
226         return getCurrentLocation().getNodePath().equals(DetailLocation.wrap(location).getNodePath());
227     }
228 
229     /**
230      * Wraps the current DefaultLocation in a DetailLocation. Providing getter and setters for used parameters.
231      */
232     @Override
233     public DetailLocation getCurrentLocation() {
234         return DetailLocation.wrap(super.getCurrentLocation());
235     }
236 
237     @Override
238     public void deactivateComponents() {
239         updateActionbar();
240         pageBar.deactivateExtensions();
241         statusBar.deactivateExtensions();
242     }
243 
244     @Override
245     public void locationChanged(Location location) {
246         DetailLocation itemLocation = DetailLocation.wrap(location);
247         super.locationChanged(itemLocation);
248 
249         if (pageEditorPresenter.getStatus().isLocationChanged(itemLocation)) {
250             pageEditorPresenter.reload(itemLocation);
251             updateLocationDependentComponents(itemLocation);
252         }
253     }
254 
255     private void updateLocationDependentComponents(DetailLocation location) {
256         pageBar.onLocationUpdate(location);
257         statusBar.onLocationUpdate(location);
258         updateCaption(location);
259     }
260 
261     private void bindHandlers() {
262 
263         admincentralEventBus.addHandler(ContentChangedEvent.class, new ContentChangedEvent.Handler() {
264 
265             @Override
266             public void onContentChanged(ContentChangedEvent event) {
267                 if (contentConnector.canHandleItem(event.getItemId())) {
268                     if (!contentConnector.itemExists(event.getItemId())) {
269                         getSubAppContext().close();
270                     }
271                 }
272             }
273         });
274 
275         subAppEventBus.addHandler(ContentChangedEvent.class, new ContentChangedEvent.Handler() {
276 
277             @Override
278             public void onContentChanged(ContentChangedEvent event) {
279                 // Updating the shared state.
280                 AbstractElement element = contentConnector.getElementFrom(event.getItemId());
281                 pageEditorPresenter.getStatus().setSelectedElement(element);
282 
283                 // Update the parameters for syncing the shared state.
284                 pageEditorPresenter.updateParameters();
285 
286                 // if locale was changed in the dialog, need to reload pageEditor with url, otherwise just refresh
287                 Locale authoringLocale = subAppContext.getAuthoringLocale();
288                 if (authoringLocale != null && ObjectUtils.notEqual(authoringLocale, pageEditorPresenter.getStatus().getLocale())) {
289                     updateLocationDependentComponents(getCurrentLocation());
290                     // use #loadPageEditor over #refresh to account for potential change of locale
291                     pageEditorPresenter.loadPageEditor();
292                 } else {
293                     pageEditorPresenter.refresh();
294                 }
295             }
296         });
297 
298         subAppEventBus.addHandler(NodeSelectedEvent.class, new NodeSelectedEvent.Handler() {
299 
300             @Override
301             public void onItemSelected(NodeSelectedEvent event) {
302                 AbstractElement element = event.getElement();
303                 if (element instanceof PageElement) {
304                     String path = element.getPath();
305                     if (StringUtils.isEmpty(path)) {
306                         path = "/";
307                     }
308                     if (!path.equals(getCurrentLocation().getNodePath())) {
309                         updateNodePath(path);
310                     }
311                 }
312                 updateActionbar();
313             }
314         });
315 
316     }
317 
318     @Override
319     public void onActionbarItemClicked(String actionName) {
320          prepareAndExecutePagesEditorAction(actionName);
321     }
322 
323     /**
324      * Used for executing actions from the actionbar. So 'server-side' actions which are depending on a selected element
325      * in the page editor.
326      *
327      * @see PageEditorListener#onAction(String, Object...) for 'client-side' action execution as comparison.
328      */
329     protected void prepareAndExecutePagesEditorAction(String actionName) {
330         AbstractElement selectedElement = pageEditorPresenter.getSelectedElement();
331         if (selectedElement == null) {
332             log.warn("Trying to execute action [{}] but no element was selected. Was the page actually loaded?", actionName);
333             return;
334         }
335         try {
336             actionExecutor.execute(actionName, selectedElement, pageEditorPresenter);
337         } catch (ActionExecutionException e) {
338             Message error = new Message(MessageType.ERROR, i18n.translate("pages.pagesEditorSubapp.actionExecutionException.message"), e.getMessage());
339             log.error("An error occurred while executing action [{}]", actionName, e);
340             appContext.sendLocalMessage(error);
341         }
342     }
343 
344     public void updateActionbar() {
345         Object itemId = contentConnector.getItemIdByElement(pageEditorPresenter.getSelectedElement());
346 
347         // Figure out which section to show, only one
348         List<ActionbarSectionDefinition> sections = getSections();
349         ActionbarSectionDefinition sectionDefinition = getVisibleSection(sections, itemId);
350 
351         // Hide all other sections
352         for (ActionbarSectionDefinition section : sections) {
353             actionbarPresenter.hideSection(section.getName());
354         }
355 
356         if (sectionDefinition != null) {
357             // Show our section
358             actionbarPresenter.showSection(sectionDefinition.getName());
359 
360             // Evaluate availability of each action within the section
361             for (ActionbarGroupDefinition groupDefinition : sectionDefinition.getGroups()) {
362                 for (ActionbarItemDefinition itemDefinition : groupDefinition.getItems()) {
363 
364                     String actionName = itemDefinition.getName();
365                     ActionDefinition actionDefinition = actionExecutor.getActionDefinition(actionName);
366                     if (actionDefinition != null) {
367                         AvailabilityDefinition availability = actionDefinition.getAvailability();
368                         if (availabilityChecker.isAvailable(availability, Arrays.asList(itemId))) {
369                             actionbarPresenter.enable(actionName);
370                         } else {
371                             actionbarPresenter.disable(actionName);
372                         }
373                     }
374                 }
375             }
376         }
377     }
378 
379     private List<ActionbarSectionDefinition> getSections() {
380         DetailSubAppDescriptor subAppDescriptor = (DetailSubAppDescriptor) getSubAppContext().getSubAppDescriptor();
381         ActionbarDefinition actionbarDefinition = subAppDescriptor.getActionbar();
382         if (actionbarDefinition == null) {
383             return Collections.emptyList();
384         }
385         return actionbarDefinition.getSections();
386     }
387 
388     private void hideActionbarSections() {
389         for (ActionbarSectionDefinition sectionDefinition : getSections()) {
390             actionbarPresenter.hideSection(sectionDefinition.getName());
391         }
392     }
393 
394     private ActionbarSectionDefinition getVisibleSection(List<ActionbarSectionDefinition> sections, Object itemId) {
395         for (ActionbarSectionDefinition section : sections) {
396             if (availabilityChecker.isAvailable(section.getAvailability(), Arrays.asList(itemId)))
397                 return section;
398         }
399         return null;
400     }
401 
402     @Override
403     public void onEscape() {
404         if (pageEditorPresenter.isMoving()) {
405             pageEditorPresenter.onAction(PageEditorListener.ACTION_CANCEL_MOVE_COMPONENT);
406         } else {
407             // Toggle preview and edit mode.
408             if (getCurrentLocation().getViewType().equals(DetailView.ViewType.EDIT)) {
409                 prepareAndExecutePagesEditorAction(PageEditorListener.ACTION_VIEW_PREVIEW);
410             } else {
411                 prepareAndExecutePagesEditorAction(PageEditorListener.ACTION_VIEW_EDIT);
412             }
413         }
414     }
415 
416     /**
417      * @deprecated since 5.4.2 - Use contentConnector directly.
418      */
419     @Deprecated
420     protected String getWorkspace() {
421         return ((PagesJcrContentConnector)contentConnector).getContentConnectorDefinition().getWorkspace();
422     }
423 
424     @Override
425     public void onMove() {
426         updateActionbar();
427     }
428 
429     @Override
430     public void stop() {
431         pageBar.stop();
432         statusBar.stop();
433     }
434 }