View Javadoc
1   /**
2    * This file Copyright (c) 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.framework.action.service;
35  
36  import info.magnolia.cms.core.version.VersionManager;
37  import info.magnolia.event.EventBus;
38  import info.magnolia.objectfactory.ComponentProvider;
39  import info.magnolia.objectfactory.Components;
40  import info.magnolia.ui.UIComponent;
41  import info.magnolia.ui.ValueContext;
42  import info.magnolia.ui.api.action.ActionExecutor;
43  import info.magnolia.ui.api.app.SubApp;
44  import info.magnolia.ui.api.app.SubAppContext;
45  import info.magnolia.ui.api.event.AdmincentralEventBus;
46  import info.magnolia.ui.api.event.ContentChangedEvent;
47  import info.magnolia.ui.api.ioc.UiContextScoped;
48  import info.magnolia.ui.contentapp.browser.ActionExecutionService;
49  import info.magnolia.ui.contentapp.detail.ContentDetailSubApp;
50  import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition;
51  import info.magnolia.ui.dialog.formdialog.FormDialogPresenter;
52  import info.magnolia.ui.editor.EditorView;
53  import info.magnolia.ui.editor.ItemProviderStrategy;
54  import info.magnolia.ui.form.EditorCallback;
55  import info.magnolia.ui.form.EditorValidator;
56  import info.magnolia.ui.framework.message.MessagesManager;
57  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredJcrContentConnectorDefinition;
58  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredNodeTypeDefinition;
59  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnector;
60  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
61  import info.magnolia.util.OptionalConsumer;
62  
63  import java.util.ArrayList;
64  import java.util.Arrays;
65  import java.util.Collection;
66  import java.util.List;
67  import java.util.Optional;
68  import java.util.Set;
69  import java.util.concurrent.Callable;
70  import java.util.stream.Collectors;
71  import java.util.stream.Stream;
72  
73  import javax.inject.Inject;
74  import javax.inject.Named;
75  import javax.inject.Provider;
76  import javax.jcr.Node;
77  import javax.jcr.RepositoryException;
78  
79  import com.google.common.collect.Sets;
80  import com.vaadin.data.BinderValidationStatus;
81  
82  /**
83   * Compatibility action execution service.
84   * Adds compatibility parameters:
85   * {@link info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter}s,
86   * {@link info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnector},
87   * {@link info.magnolia.ui.form.EditorCallback} and {@link info.magnolia.ui.form.EditorValidator}
88   * as action parameters.
89   */
90  @UiContextScoped
91  public class CompatibilityActionExecutionService extends ActionExecutionService {
92  
93      private final ComponentProvider componentProvider;
94      private final EventBus eventBus;
95  
96      @Inject
97      public CompatibilityActionExecutionService(ActionExecutor actionExecutor,
98                                                 MessagesManager messagesManager,
99                                                 ComponentProvider componentProvider,
100                                                @Named(AdmincentralEventBus.NAME) EventBus eventBus) {
101         super(actionExecutor, messagesManager);
102         this.componentProvider = componentProvider;
103         this.eventBus = eventBus;
104     }
105 
106     @Override
107     public void executeAction(String actionName, Object... parameters) {
108         Collection<Object> compatibilityParameters = new ArrayList<>(Arrays.asList(parameters));
109 
110         Optional<ValueContextProvider> valueContextProviderOptional = invokeComponentProviderSafely(() -> componentProvider.newInstance(ValueContextProvider.class));
111 
112         Optional<Collection> selectedItems = valueContextProviderOptional
113                 .map(InjectedComponentProvider::get)
114                 .map(ValueContext::get)
115                 .map(nodeStream ->  nodeStream.collect(Collectors.toList()));
116 
117         OptionalConsumer.of(Stream.of(parameters)
118                 .filter(FormDialogPresenter.class::isInstance)
119                 .findFirst())
120                 .ifNotPresent(() -> invokeComponentProviderSafely(() -> Components.getComponent(FormDialogPresenter.class))
121                         .ifPresent(compatibilityParameters::add));
122 
123         Optional<SubAppContext> subAppContext = invokeComponentProviderSafely(() -> componentProvider.newInstance((SubAppContext.class)));
124         Optional<EditorView> editorView = subAppContext
125                 .map(SubAppContext::getSubApp)
126                 .map(SubApp::getView)
127                 .filter(UIComponent.class::isInstance)
128                 .map(view -> ((UIComponent) view).accessViewBeanStore().get(EditorView.class));
129 
130         Set<Node> jcrNodes = findJcrNodes(selectedItems);
131 
132         if (jcrNodes.size() > 0) {
133             compatibilityParameters.add(wrapForLegacyActionExecution(jcrNodes.iterator().next(), editorView)); //JcrNodeAdapter
134             compatibilityParameters.add(jcrNodes.stream()
135                     .map(node -> wrapForLegacyActionExecution(node, editorView)) //List<JcrNodeAdapter>
136                     .collect(Collectors.toList())
137             );
138             mimicContentConnector(compatibilityParameters); //JcrContentConnector
139             mimicEditor(compatibilityParameters, jcrNodes, subAppContext, editorView, invokeComponentProviderSafely(() -> componentProvider.newInstance(ValueContextProvider.class))); //EditorValidator, EditorCallback
140         } else {
141             selectedItems.ifPresent(o -> o.stream().findFirst().ifPresent(compatibilityParameters::add)); //single, non JCR item
142         }
143         super.executeAction(actionName, compatibilityParameters.toArray(new Object[0]));
144     }
145 
146     private void mimicEditor(Collection<Object> compatibilityParameters, Set<Node> jcrNodes, Optional<SubAppContext> subAppContext, Optional<EditorView> editorView, Optional<ValueContextProvider> valueContextProviderOptional) {
147         editorView.ifPresent(nodeEditorView -> compatibilityParameters.add(new EditorValidator() {
148             @Override
149             public void showValidation(boolean visible) {
150                 //validation is shown automatically in the new framework
151             }
152 
153             @Override
154             public boolean isValid() {
155                 List<BinderValidationStatus<?>> validate = nodeEditorView.validate();
156                 return validate.stream().allMatch(BinderValidationStatus::isOk);
157             }
158         }));
159 
160         compatibilityParameters.add(new EditorCallback() {
161             @Override
162             public void onCancel() {
163                 subAppContext.ifPresent(subAppContext -> {
164                     if (subAppContext.getSubApp() instanceof ContentDetailSubApp) { //TODO support for not implemented overlay dialogs
165                         subAppContext.close();
166                     }
167                 });
168             }
169 
170             @Override
171             public void onSuccess(String actionName) {
172                 onCancel();
173             }
174         });
175 
176         valueContextProviderOptional
177                 .map(InjectedComponentProvider::get)
178                 .ifPresent(context -> eventBus.addHandler(ContentChangedEvent.class, event -> context.set(jcrNodes)));
179     }
180 
181     private void mimicContentConnector(Collection<Object> compatibilityParameters) {
182         invokeComponentProviderSafely(() -> componentProvider.newInstance(JcrDatasourceDefinitionProvider.class))
183                 .map(InjectedComponentProvider::get)
184                 .ifPresent(datasourceDefinition -> {
185                     ConfiguredJcrContentConnectorDefinitionnnectorDefinition.html#ConfiguredJcrContentConnectorDefinition">ConfiguredJcrContentConnectorDefinition contentConnectorDefinition = new ConfiguredJcrContentConnectorDefinition();
186                     contentConnectorDefinition.setWorkspace(datasourceDefinition.getWorkspace());
187                     contentConnectorDefinition.setRootPath(datasourceDefinition.getRootPath());
188                     contentConnectorDefinition.setNodeTypes(datasourceDefinition.getAllowedNodeTypes()
189                             .stream()
190                             .map(nodeType -> {
191                                 ConfiguredNodeTypeDefinitionfiguredNodeTypeDefinition.html#ConfiguredNodeTypeDefinition">ConfiguredNodeTypeDefinition nodeTypeDefinition = new ConfiguredNodeTypeDefinition();
192                                 nodeTypeDefinition.setName(nodeType);
193                                 return nodeTypeDefinition;
194                             })
195                             .collect(Collectors.toList()));
196                     compatibilityParameters.add(new JcrContentConnector(componentProvider.getComponent(VersionManager.class), contentConnectorDefinition));
197                 });
198     }
199 
200     private Set<Node> findJcrNodes(Optional<Collection> selectedItems) {
201         return (Set<Node>) selectedItems.map(items -> items.stream()
202                 .filter(Node.class::isInstance)
203                 .map(Node.class::cast)
204                 .collect(Collectors.toSet()))
205                 .orElse(Sets.newHashSet());
206     }
207 
208     private JcrNodeAdapter wrapForLegacyActionExecution(Object potentialJcrItem, Optional<EditorView> editorView) {
209         return new JcrNodeAdapter((Node) potentialJcrItem) {
210             @Override
211             public Node applyChanges() throws RepositoryException {
212                 editorView.ifPresent(editorView -> editorView.write(getJcrItem()));
213                 return super.applyChanges();
214             }
215         };
216     }
217 
218     private <C> Optional<C> invokeComponentProviderSafely(Callable<C> callable) {
219         try {
220             return Optional.of(callable.call());
221         } catch (Exception e) {
222             return Optional.empty();
223         }
224     }
225 
226     private static class ItemProviderStrategyProvider extends InjectedComponentProvider<ItemProviderStrategy<Node, ?>> {
227         @Inject
228         private ItemProviderStrategyProvider(ItemProviderStrategy<Node, ?> strategy) {
229             super(strategy);
230         }
231     }
232 
233     private static class ValueContextProvider extends InjectedComponentProvider<ValueContext<Node>> {
234         @Inject
235         private ValueContextProvider(ValueContext<Node> valueContext) {
236             super(valueContext);
237         }
238     }
239 
240     private static class JcrDatasourceDefinitionProvider extends InjectedComponentProvider<JcrDatasourceDefinition> {
241         @Inject
242         private JcrDatasourceDefinitionProvider(JcrDatasourceDefinition datasourceDefinition) {
243             super(datasourceDefinition);
244         }
245     }
246 
247     /**
248      * Some components can't be retrieved as a component, can only be injected.
249      */
250     private static class InjectedComponentProvider<T> implements Provider<T> {
251 
252         private final T component;
253 
254         private InjectedComponentProvider(T component) {
255             this.component = component;
256         }
257 
258         @Override
259         public T get() {
260             return component;
261         }
262     }
263 }