View Javadoc
1   /**
2    * This file Copyright (c) 2020 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.blossom.dialog;
35  
36  import info.magnolia.config.registry.DefinitionProvider;
37  import info.magnolia.config.registry.DefinitionProviderWrapper;
38  import info.magnolia.config.registry.Registry;
39  import info.magnolia.config.registry.decoration.DefinitionDecorator;
40  import info.magnolia.config.registry.decoration.DefinitionDecoratorMetadata;
41  import info.magnolia.objectfactory.Components;
42  import info.magnolia.objectfactory.MgnlInstantiationException;
43  import info.magnolia.objectfactory.NoSuchComponentException;
44  import info.magnolia.ui.admincentral.dialog.action.CancelDialogActionDefinition;
45  import info.magnolia.ui.admincentral.dialog.action.SaveDialogActionDefinition;
46  import info.magnolia.ui.api.app.AppContext;
47  import info.magnolia.ui.api.app.AppController;
48  import info.magnolia.ui.contentapp.configuration.ContentAppDescriptor;
49  import info.magnolia.ui.dialog.DialogDefinition;
50  import info.magnolia.ui.dialog.definition.ConfiguredFormDialogDefinition;
51  
52  import java.util.Optional;
53  import java.util.function.Predicate;
54  
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * Populates a blossom dialogs by invoking methods on the factory object.
60   */
61  public class DialogCreatorDefinitionDecorator implements DefinitionDecorator<DialogDefinition> {
62      private static final Logger log = LoggerFactory.getLogger(DialogCreatorDefinitionDecorator.class);
63      private final BlossomDialogDescription dialogDescription;
64  
65      public DialogCreatorDefinitionDecorator(BlossomDialogDescription dialogDescription) {
66          this.dialogDescription = dialogDescription;
67      }
68  
69      @Override
70      public DefinitionDecoratorMetadata metadata() {
71          return () -> "blossom";
72      }
73  
74      @Override
75      public boolean appliesTo(DefinitionProvider<DialogDefinition> definitionProvider) {
76          if (definitionProvider instanceof BlossomDialogDefinitionProvider
77                  && dialogDescription.getId().equals(definitionProvider.get().getId())) {
78              return new CompatibilityAppsPredicate().test(((BlossomDialogDefinitionProvider) definitionProvider));
79          }
80          return false;
81      }
82  
83      @Override
84      public DefinitionProvider<DialogDefinition> decorate(DefinitionProvider<DialogDefinition> definitionProvider) {
85  
86          DialogCreationContext context = Optional.ofNullable(DialogCreationContextHolder.get())
87                  .orElse(new DialogCreationContext());
88  
89          context.setId(dialogDescription.getId());
90  
91          DialogCreator dialogCreator = dialogDescription.getDialogCreator();
92          DialogFactoryMetaData factoryMetaData = dialogDescription.getFactoryMetaData();
93          try {
94              dialogCreator.createDialog(factoryMetaData, context);
95          } catch (Exception e) {
96              String factoryMetaDataInfo = factoryMetaData.getFactoryMethod() != null ?
97                      factoryMetaData.getFactoryMethod().toString() :
98                      factoryMetaData.getFactoryObject().getClass().toString();
99              log.error("Failed to create dialog for {}", factoryMetaDataInfo, e);
100             throw new Registry.InvalidDefinitionException(dialogDescription.getId());
101         }
102 
103         ConfiguredFormDialogDefinition dialog = context.getDialog();
104 
105         if (dialog != null && dialog.getActions().isEmpty()) {
106             SaveDialogActionDefinition callbackAction = new SaveDialogActionDefinition();
107             callbackAction.setName("commit");
108             callbackAction.setLabel("save");
109             dialog.getActions().put(callbackAction.getName(), callbackAction);
110 
111             CancelDialogActionDefinition cancelAction = new CancelDialogActionDefinition();
112             cancelAction.setName("cancel");
113             cancelAction.setLabel("cancel");
114             dialog.getActions().put(cancelAction.getName(), cancelAction);
115         }
116 
117         return new DefinitionProviderWrapper<DialogDefinition>(definitionProvider) {
118             @Override
119             public DialogDefinition get() throws Registry.InvalidDefinitionException {
120                 return dialog;
121             }
122         };
123     }
124 
125     /**
126      * The {@link CompatibilityAppsPredicate} denotes <i>when</i> to trigger invocations of the dialog factories.
127      *
128      * <p>In a Magnolia 5 UI Framework context, there are usually two subsequent invocations;
129      * the latter one comes from the {@link BlossomFormDialogPresenter}, once the {@link DialogCreationContext} has been populated with Vaadin 7 Item, JCR Node, etc.
130      * So we need to defer actual invocation of controllers to that point.</p>
131      *
132      * <p>In a Magnolia 6 UI Framework context, there is only a single invocation, and injecting JCR Node and other content attributes is currently not supported.</p>
133      */
134     class CompatibilityAppsPredicate implements Predicate<BlossomDialogDefinitionProvider> {
135         @Override
136         public boolean test(BlossomDialogDefinitionProvider definitionProvider) {
137             AppController appController;
138             try {
139                 appController = Components.getComponent(AppController.class);
140             } catch (MgnlInstantiationException | NoSuchComponentException e) {
141                 log.warn("No AppController available while invoking Blossom dialog with id {}. Not invoking dialog controller.", definitionProvider.getMetadata().getReferenceId());
142                 return false;
143             }
144 
145             Optional<AppContext> appContext = appController.getCurrentAppContext();
146             if (!appContext.isPresent()) {
147                 log.warn("Unable to determine current app while invoking Blossom dialog with id {}. Not invoking dialog controller.", definitionProvider.getMetadata().getReferenceId());
148                 return false;
149             }
150 
151             // bail out early if Definitions app (loose dependency), it's also a new UI FW ContentAppDescriptor
152             // and AppDescriptors are i18nized, hence the check for assignability.
153             try {
154                 if (Class.forName("info.magnolia.definitions.app.DefinitionsAppDescriptor").isAssignableFrom(appContext.get().getAppDescriptor().getClass())) {
155                     log.debug("Dialog creator invoked while in Definitions app. Skipping dialog controller.");
156                     return false;
157                 }
158             } catch (ClassNotFoundException e) {
159                 log.debug("Couldn't find DefinitionsAppDescriptor.");
160             }
161 
162             // DialogCreationContextHolder is only #set by BlossomFormDialogPresenter; skip as long as it's not there, see class-level javadoc.
163             if (info.magnolia.ui.contentapp.ContentAppDescriptor.class.isAssignableFrom(appContext.get().getAppDescriptor().getClass())) {
164                 if (DialogCreationContextHolder.get() == null) {
165                     log.debug("Dialog creator initially invoked for old content app without a DialogCreationContext set. Skipping dialog controller.");
166                     return false;
167                 } else {
168                     log.debug("Dialog creator invoked from a Magnolia 5 content-app with DialogCreationContext set.");
169                     return true;
170                 }
171             } else if (ContentAppDescriptor.class.isAssignableFrom(appContext.get().getAppDescriptor().getClass())) {
172                 log.warn("Dialog creator invoked from a Magnolia 6 content-app. Injecting dialog context objects is not supported yet.");
173                 return true;
174             }
175 
176             log.debug("Dialog creator invoked outside of old/new content app. Skipping dialog controller.");
177             return false;
178         }
179     }
180 }