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 static java.util.stream.Collectors.toList;
37  
38  import info.magnolia.cms.core.version.VersionManager;
39  import info.magnolia.event.EventBus;
40  import info.magnolia.objectfactory.ComponentProvider;
41  import info.magnolia.objectfactory.Components;
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.availability.AvailabilityChecker;
46  import info.magnolia.ui.api.availability.AvailabilityDefinition;
47  import info.magnolia.ui.api.event.AdmincentralEventBus;
48  import info.magnolia.ui.api.event.ContentChangedEvent;
49  import info.magnolia.ui.api.ioc.UiContextScoped;
50  import info.magnolia.ui.contentapp.browser.ActionExecutionService;
51  import info.magnolia.ui.contentapp.browser.context.ValueContext;
52  import info.magnolia.ui.contentapp.detail.ContentDetailSubApp;
53  import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition;
54  import info.magnolia.ui.dialog.formdialog.FormDialogPresenter;
55  import info.magnolia.ui.form.EditorCallback;
56  import info.magnolia.ui.form.EditorValidator;
57  import info.magnolia.ui.framework.UiFrameworkView;
58  import info.magnolia.ui.framework.databinding.ItemProviderStrategy;
59  import info.magnolia.ui.framework.databinding.view.EditorView;
60  import info.magnolia.ui.framework.datasource.DatasourceComponent;
61  import info.magnolia.ui.framework.datasource.components.SelectedItems;
62  import info.magnolia.ui.framework.message.MessagesManager;
63  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredJcrContentConnectorDefinition;
64  import info.magnolia.ui.vaadin.integration.contentconnector.ConfiguredNodeTypeDefinition;
65  import info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnector;
66  import info.magnolia.ui.vaadin.integration.jcr.JcrItemUtil;
67  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
68  import info.magnolia.util.OptionalConsumer;
69  
70  import java.util.ArrayList;
71  import java.util.Arrays;
72  import java.util.Collection;
73  import java.util.List;
74  import java.util.Optional;
75  import java.util.concurrent.Callable;
76  import java.util.function.Predicate;
77  import java.util.stream.Collectors;
78  import java.util.stream.Stream;
79  
80  import javax.inject.Inject;
81  import javax.inject.Named;
82  import javax.inject.Provider;
83  import javax.jcr.Item;
84  import javax.jcr.Node;
85  import javax.jcr.RepositoryException;
86  
87  import com.vaadin.data.BinderValidationStatus;
88  
89  /**
90   * Compatibility action execution service.
91   * Adds compatibility parameters:
92   * {@link info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter}s,
93   * {@link info.magnolia.ui.vaadin.integration.contentconnector.JcrContentConnector},
94   * {@link info.magnolia.ui.form.EditorCallback} and {@link info.magnolia.ui.form.EditorValidator}
95   * as action parameters.
96   */
97  @UiContextScoped
98  public class CompatibilityActionExecutionService extends ActionExecutionService {
99  
100     private final ComponentProvider componentProvider;
101     private final EventBus eventBus;
102 
103     @Inject
104     public CompatibilityActionExecutionService(ActionExecutor actionExecutor, MessagesManager messagesManager, AvailabilityChecker availabilityChecker, ComponentProvider componentProvider, @Named(AdmincentralEventBus.NAME) EventBus eventBus) {
105         super(actionExecutor, messagesManager, availabilityChecker);
106         this.componentProvider = componentProvider;
107         this.eventBus = eventBus;
108     }
109 
110     @Override
111     public void executeAction(String actionName, Object... parameters) {
112         Collection<Object> compatibilityParameters = new ArrayList<>(Arrays.asList(parameters));
113 
114         Optional<SelectedItems> selectedItems = Stream.of(parameters)
115                 .filter(SelectedItems.class::isInstance)
116                 .findFirst()
117                 .map(SelectedItems.class::cast);
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         Collection<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); //EditorValidator, EditorCallback
154         } else {
155             selectedItems.ifPresent(o -> o.getFirst().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(SelectedItems<T> selectedItems) {
162         final List<Object> wrappedLegacyItems = selectedItems.stream().map(this::wrapForLegacyAvailabilityChecking).collect(toList());
163 //        wrappedLegacyItems.addAll(items); //TODO we should support a new availability, so the old one has to filter these out
164         return super.getAvailabilityFilter(SelectedItems.of(wrappedLegacyItems));
165     }
166 
167     private void mimicEditor(Collection<Object> compatibilityParameters, Collection<Node> jcrNodes, Optional<SubAppContext> subAppContext, Optional<EditorView> editorView) {
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         invokeComponentProviderSafely(() -> componentProvider.newInstance(ValueContextProvider.class))
198                 .map(InjectedComponentProvider::get)
199                 .ifPresent(context -> eventBus.addHandler(ContentChangedEvent.class, event -> context.current().set(SelectedItems.of(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 Collection<Node> findJcrNodes(Optional<SelectedItems> selectedItems) {
222         return (Collection<Node>) selectedItems.map((SelectedItems items) -> items.stream()
223                 .filter(Node.class::isInstance)
224                 .map(Node.class::cast)
225                 .collect(Collectors.toList()))
226                 .orElse(new ArrayList<>());
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(@DatasourceComponent 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 }