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