View Javadoc
1   /**
2    * This file Copyright (c) 2012-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;
35  
36  import info.magnolia.config.MutableWrapper;
37  import info.magnolia.objectfactory.ComponentProvider;
38  import info.magnolia.ui.api.app.AppContext;
39  import info.magnolia.ui.api.app.AppView;
40  import info.magnolia.ui.api.app.ChooseDialogCallback;
41  import info.magnolia.ui.api.context.UiContext;
42  import info.magnolia.ui.contentapp.browser.BrowserSubAppDescriptor;
43  import info.magnolia.ui.contentapp.choosedialog.ChooseDialogComponentProviderUtil;
44  import info.magnolia.ui.contentapp.field.WorkbenchFieldDefinition;
45  import info.magnolia.ui.contentapp.renderer.SelectionSensitiveActionRenderer;
46  import info.magnolia.ui.dialog.actionarea.definition.ActionRendererDefinition;
47  import info.magnolia.ui.dialog.actionarea.definition.ConfiguredActionRendererDefinition;
48  import info.magnolia.ui.dialog.choosedialog.ChooseDialogPresenter;
49  import info.magnolia.ui.dialog.definition.ChooseDialogDefinition;
50  import info.magnolia.ui.dialog.definition.ChooseDialogDefinitionMutator;
51  import info.magnolia.ui.dialog.definition.ConfiguredChooseDialogDefinition;
52  import info.magnolia.ui.framework.app.BaseApp;
53  import info.magnolia.ui.framework.ioc.SessionStore;
54  import info.magnolia.ui.framework.ioc.UiAnnotations;
55  import info.magnolia.ui.framework.ioc.UiContextAnnotation;
56  import info.magnolia.ui.framework.ioc.UiContextBoundComponentProvider;
57  import info.magnolia.ui.framework.ioc.UiContextReference;
58  import info.magnolia.ui.vaadin.integration.contentconnector.ContentConnectorDefinition;
59  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnectorDefinition;
60  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnectorDefinitionMutator;
61  import info.magnolia.ui.workbench.definition.WorkbenchDefinition;
62  import info.magnolia.ui.workbench.definition.WorkbenchDefinitionMutator;
63  
64  import java.util.List;
65  import java.util.Map;
66  
67  import javax.inject.Inject;
68  
69  import org.apache.commons.lang3.StringUtils;
70  import org.slf4j.Logger;
71  import org.slf4j.LoggerFactory;
72  
73  import com.google.common.collect.ImmutableList;
74  
75  /**
76   * Extends the {@link BaseApp} by the ability to open a choose dialog.
77   */
78  public class ContentApp extends BaseApp {
79  
80      private static final Logger log = LoggerFactory.getLogger(ContentApp.class);
81  
82      public static final String COMMIT_CHOOSE_DIALOG_ACTION = "commit";
83  
84      @Inject
85      public ContentApp(AppContext appContext, AppView view) {
86          super(appContext, view);
87      }
88  
89      /**
90       * @deprecated since 5.5.6 - use {@link #ContentApp(AppContext, AppView)} instead.
91       */
92      @Deprecated
93      public ContentApp(AppContext appContext, AppView view, ComponentProvider componentProvider) {
94          super(appContext, view);
95      }
96  
97      @Override
98      public void openChooseDialog(UiContext overlayLayer, String selectedId, ChooseDialogCallback callback) {
99          openChooseDialog(overlayLayer, null, selectedId, callback);
100     }
101 
102 
103     @Override
104     public void openChooseDialog(UiContext overlayLayer, String targetTreeRootPath, String selectedId, final ChooseDialogCallback callback) {
105 
106         // fetch or compute chooseDialogDefinition from default subApp
107         ChooseDialogDefinition chooseDialogDefinition;
108         if (appContext.getAppDescriptor() instanceof ContentAppDescriptor) {
109             chooseDialogDefinition = ((ContentAppDescriptor) appContext.getAppDescriptor()).getChooseDialog();
110         } else {
111             chooseDialogDefinition = new ConfiguredChooseDialogDefinition();
112         }
113         chooseDialogDefinition = ensureChooseDialogField(chooseDialogDefinition, targetTreeRootPath);
114         chooseDialogDefinition = addAvailabilityActionRenderer(chooseDialogDefinition);
115 
116         // create chooseDialogComponentProvider and get new instance of presenter from there
117         final UiContextReference stubSubAppContextReference = createStubSubAppContextReference(appContext.getDefaultSubAppDescriptor().getName());
118         final UiContextBoundComponentProvider parentSubAppComponentProvider = new UiContextBoundComponentProvider(stubSubAppContextReference);
119 
120         final UiContextBoundComponentProvider chooseDialogComponentProvider =
121                 ChooseDialogComponentProviderUtil.createChooseDialogComponentProvider(overlayLayer, chooseDialogDefinition, parentSubAppComponentProvider);
122         final ChooseDialogPresenter presenter = chooseDialogComponentProvider.newInstance(chooseDialogDefinition.getPresenterClass(), chooseDialogComponentProvider);
123 
124         if (!StringUtils.isBlank(targetTreeRootPath) && !StringUtils.isBlank(selectedId) && !"/".equals(targetTreeRootPath) && selectedId.startsWith(targetTreeRootPath)) {
125             selectedId = StringUtils.removeStart(selectedId, targetTreeRootPath);
126         }
127 
128         // Wrap the dialog callback with a bean store destruction trigger
129         final UiContextReference uiContextReference = chooseDialogComponentProvider.getUiContextReference();
130         final ChooseDialogCallback composedCallback = ChooseDialogCallback.composeWith(() -> SessionStore.access().releaseBeanStore(uiContextReference), callback);
131         presenter.start(composedCallback, chooseDialogDefinition, overlayLayer, selectedId);
132     }
133 
134     private ChooseDialogDefinitiondefinition/ChooseDialogDefinition.html#ChooseDialogDefinition">ChooseDialogDefinition addAvailabilityActionRenderer(ChooseDialogDefinition chooseDialogDefinition) {
135         Map<String, ActionRendererDefinition> actionRenderers = chooseDialogDefinition.getActionArea().getActionRenderers();
136         if (!actionRenderers.containsKey(COMMIT_CHOOSE_DIALOG_ACTION)) {
137             ConfiguredActionRendererDefinitionrerDefinition.html#ConfiguredActionRendererDefinition">ConfiguredActionRendererDefinition actionRendererDef = new ConfiguredActionRendererDefinition();
138             actionRendererDef.setRendererClass(SelectionSensitiveActionRenderer.class);
139             actionRenderers.put(COMMIT_CHOOSE_DIALOG_ACTION, actionRendererDef);
140         }
141         return chooseDialogDefinition;
142     }
143 
144     private ChooseDialogDefinitionialog/definition/ChooseDialogDefinition.html#ChooseDialogDefinition">ChooseDialogDefinition ensureChooseDialogField(ChooseDialogDefinition definition, String targetTreeRootPath) {
145 
146         if (definition.getField() != null && definition.getContentConnector() != null) {
147             return definition;
148         }
149 
150         // check whether default subApp is a browser to fetch config from
151         if (!(appContext.getDefaultSubAppDescriptor() instanceof BrowserSubAppDescriptor)) {
152             log.error("Cannot start workbench choose dialog since targeted app is not a content app");
153             return definition;
154         }
155         BrowserSubAppDescriptormagnolia/ui/contentapp/browser/BrowserSubAppDescriptor.html#BrowserSubAppDescriptor">BrowserSubAppDescriptor subApp = (BrowserSubAppDescriptor) appContext.getDefaultSubAppDescriptor();
156 
157         // work on cloned definition so that we don't spoil raw config
158 
159         ChooseDialogDefinition chooseDialogDefinition = MutableWrapper.wrap(definition);
160         final ChooseDialogDefinitionMutator dialogDefinitionMutator = ChooseDialogDefinitionMutator.accessMutable(chooseDialogDefinition);
161 
162         // ensure contentConnector
163         if (definition.getContentConnector() == null) {
164             ContentConnectorDefinition contentConnector = MutableWrapper.wrap(subApp.getContentConnector());
165             if (StringUtils.isNotBlank(targetTreeRootPath) && contentConnector instanceof JcrContentConnectorDefinition) {
166                 JcrContentConnectorDefinitionMutator.accessMutable((JcrContentConnectorDefinition) contentConnector).setRootPath(targetTreeRootPath);
167             }
168 
169             dialogDefinitionMutator.setContentConnector(contentConnector);
170         }
171 
172 
173         // ensure workbench field
174         if (chooseDialogDefinition.getField() == null) {
175             WorkbenchFieldDefinitiontion.html#WorkbenchFieldDefinition">WorkbenchFieldDefinition workbenchField = new WorkbenchFieldDefinition();
176             workbenchField.setName("workbenchField");
177             dialogDefinitionMutator.setField(workbenchField);
178         }
179 
180         if (chooseDialogDefinition.getField() instanceof WorkbenchFieldDefinition) {
181             final WorkbenchFieldDefinitionui/contentapp/field/WorkbenchFieldDefinition.html#WorkbenchFieldDefinition">WorkbenchFieldDefinition workbenchField = (WorkbenchFieldDefinition) chooseDialogDefinition.getField();
182 
183             if (workbenchField.getWorkbench() == null) {
184                 WorkbenchDefinition workbench = MutableWrapper.wrap(subApp.getWorkbench());
185                 // mark definition as a dialog workbench so that workbench presenter can disable drag n drop
186                 WorkbenchDefinitionMutator.accessMutable(workbench).setDialogWorkbench(true);
187                 workbenchField.setWorkbench(workbench);
188             }
189 
190             if (workbenchField.getImageProvider() == null) {
191                 workbenchField.setImageProvider(subApp.getImageProvider());
192             }
193         }
194 
195         return chooseDialogDefinition;
196     }
197 
198     /**
199      * Create a stub sub-app context reference without the need to actually have a
200      * running sub-app. Historically the choose dialogs would hijack
201      * major parts of the default (browser) sub-app configuration. We also need to
202      * expose the bindings related to that sub-app to the choose dialog component
203      * provider. Creating such stub reference does expose the bindings.
204      *
205      * However, it does not provide the storage for the possible sub-app scoped
206      * instances, which is a limitation of such approach! Historically, no sub-app
207      * context has been provided to the choose dialog, so this approach is somewhat
208      * 'history-compatible', but a better, more robust solution might be required!
209      */
210     private UiContextReference createStubSubAppContextReference(String subAppName) {
211         return new UiContextReference() {
212 
213             private static final String DISCLAIMER = "STUB_SUB_APP_REFERENCE_NOT_MEANT_TO_BE_USED_AS_BEAN_STORAGE_REFERENCE";
214 
215             @Override
216             public String asString() {
217                 return String.format("%s:%s:%s", appContext.getName(), subAppName, DISCLAIMER);
218             }
219 
220             @Override
221             public UiContextAnnotation getAnnotation() {
222                 return UiAnnotations.forSubApp(appContext.getName(), subAppName);
223             }
224 
225             @Override
226             public List<UiContextReference> getParentReferences() {
227                 final UiContextReference genericSubAppContextReference = UiContextReference.genericSubAppContextReference();
228                 final UiContextReference currentAppContextReference = UiContextReference.ofApp(appContext);
229                 return ImmutableList.<UiContextReference>builder()
230                         .add(genericSubAppContextReference)
231                         .add(currentAppContextReference)
232                         .addAll(currentAppContextReference.getParentReferences())
233                         .build();
234             }
235         };
236     }
237 
238 }