View Javadoc
1   /**
2    * This file Copyright (c) 2013-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;
35  
36  import info.magnolia.cms.security.User;
37  import info.magnolia.commands.CommandsManager;
38  import info.magnolia.commands.chain.Command;
39  import info.magnolia.context.Context;
40  import info.magnolia.context.MgnlContext;
41  import info.magnolia.i18nsystem.SimpleTranslator;
42  import info.magnolia.jcr.RuntimeRepositoryException;
43  import info.magnolia.objectfactory.Components;
44  import info.magnolia.ui.api.action.ActionExecutionException;
45  import info.magnolia.ui.api.action.CommandActionDefinition;
46  import info.magnolia.ui.api.context.UiContext;
47  import info.magnolia.ui.framework.action.async.AsyncActionExecutor;
48  import info.magnolia.ui.vaadin.integration.jcr.JcrItemAdapter;
49  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
50  
51  import java.util.Collections;
52  import java.util.HashMap;
53  import java.util.List;
54  import java.util.Map;
55  
56  import javax.jcr.Item;
57  import javax.jcr.Node;
58  import javax.jcr.RepositoryException;
59  
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  import com.google.common.collect.Lists;
64  
65  /**
66   * Base action supporting execution of commands. Will delegate execution to {@link AsyncActionExecutor} if {@link CommandActionDefinition#asynchronous}
67   * is set.
68   *
69   * @param <D> {@link info.magnolia.ui.api.action.CommandActionDefinition}.
70   */
71  public class AbstractCommandAction<D extends CommandActionDefinition> extends AbstractMultiItemAction<D> {
72  
73      private static final Logger log = LoggerFactory.getLogger(AbstractCommandAction.class);
74  
75      public static final String COMMAND_RESULT = "command_result";
76      public static final String LONG_RUNNING_ACTION_NOTIFICATION = "ui-framework.abstractcommand.asyncaction.long";
77      public static final String PARALLEL_EXECUTION_NOT_ALLOWED_NOTIFICATION = "ui-framework.abstractcommand.parallelExecutionNotAllowed";
78  
79      private final CommandsManager commandsManager;
80      private final SimpleTranslator i18n;
81      private final User user;
82      private final Command command;
83      private final AsyncActionExecutor asyncExecutor;
84  
85      private Map<String, Object> params;
86      private String successMessage;
87      private String failureMessage;
88  
89      public AbstractCommandAction(final D definition, final JcrItemAdapter item, final CommandsManager commandsManager, UiContext uiContext, SimpleTranslator i18n) {
90          this(definition, Lists.newArrayList(item), commandsManager, uiContext, i18n);
91      }
92  
93      public AbstractCommandAction(final D definition, final List<JcrItemAdapter> items, final CommandsManager commandsManager, UiContext uiContext, SimpleTranslator i18n) {
94          super(definition, items, uiContext);
95          this.commandsManager = commandsManager;
96          this.i18n = i18n;
97          this.user = MgnlContext.getUser();
98          // Init Command.
99          this.command = commandsManager.getCommand(getDefinition().getCatalog(), getDefinition().getCommand());
100         this.asyncExecutor = Components.getComponentProvider().newInstance(AsyncActionExecutor.class, getDefinition(), getUiContext());
101     }
102 
103     /**
104      * Builds a map of parameters which will be passed to the current command
105      * for execution. Called by {@link #onPreExecute()}. Default implementation returns
106      * a map containing the parameters defined at
107      * {@link CommandActionDefinition#getParams()}. It also adds the following
108      * parameters with values retrieved from the passed node.
109      * <ul>
110      * <li>Context.ATTRIBUTE_REPOSITORY = current node's workspace name
111      * <li>Context.ATTRIBUTE_UUID = current node's identifier
112      * <li>Context.ATTRIBUTE_PATH = current node's path
113      * </ul>
114      * Subclasses can override this method to add further parameters to the
115      * command execution. E.g.
116      *
117      * <pre>
118      * protected Map&lt;String, Object&gt; buildParams(final Node node) {
119      *     Map&lt;String, Object&gt; params = super.buildParams(node);
120      *     params.put(Context.ATTRIBUTE_RECURSIVE, getDefinition().isRecursive());
121      *     return params;
122      * }
123      * </pre>
124      */
125     protected Map<String, Object> buildParams(final Item jcrItem) {
126         Map<String, Object> params = new HashMap<String, Object>();
127         if (!getDefinition().getParams().isEmpty()) {
128             params.putAll(getDefinition().getParams());
129         }
130         try {
131             final String path = jcrItem.getPath();
132             final String workspace = jcrItem.getSession().getWorkspace().getName();
133             final String identifier = jcrItem.isNode() ? ((Node) jcrItem).getIdentifier() : jcrItem.getParent().getIdentifier();
134 
135             params.put(Context.ATTRIBUTE_REPOSITORY, workspace);
136             // really only the identifier should be used to identify a piece of content and nothing else
137             params.put(Context.ATTRIBUTE_UUID, identifier);
138             params.put(Context.ATTRIBUTE_PATH, path);
139             params.put(Context.ATTRIBUTE_USERNAME, user.getName());
140             params.put(Context.ATTRIBUTE_REQUESTOR, user.getName());
141         } catch (RepositoryException e) {
142             throw new RuntimeRepositoryException(e);
143         }
144         return params;
145     }
146 
147     /**
148      * @return the <em>immutable</em> map of parameters to be used for command execution.
149      * @see AbstractCommandAction#buildParams(javax.jcr.Item)
150      */
151     public final Map<String, Object> getParams() {
152         return Collections.unmodifiableMap(params);
153     }
154 
155     public final CommandsManager getCommandsManager() {
156         return commandsManager;
157     }
158 
159     /**
160      * Handles the retrieval of the {@link Command} instance defined in the {@link CommandActionDefinition} associated with this action and then
161      * performs the actual command execution.
162      *
163      * @throws info.magnolia.ui.api.action.ActionExecutionException if no command is found or if command execution throws an exception.
164      */
165     @Override
166     protected void executeOnItem(JcrItemAdapter item) throws ActionExecutionException {
167         this.failureMessage = null;
168         this.successMessage = null;
169         try {
170             onPreExecute();
171         } catch (Exception e) {
172             onError(e);
173             log.debug("Command execution failed during pre execution tasks.");
174             throw new ActionExecutionException(e);
175         }
176         if (command == null) {
177             throw new ActionExecutionException(String.format("Could not find command [%s] in any catalog", getDefinition().getCommand()));
178         }
179 
180         long start = System.currentTimeMillis();
181 
182         // this was set after scheduling the job.. I don't see, why it can't be set here.. it's used in e.g. in ActivationAction#onPostExecute()
183         boolean stopProcessing = false;
184 
185         try {
186             log.debug("Executing command [{}] from catalog [{}] with the following parameters [{}]...", getDefinition().getCommand(), getDefinition().getCatalog(), getParams());
187 
188             if (isInvokeAsynchronously()) {
189                 try {
190                     boolean inBackground = asyncExecutor.execute(item, params);
191 
192                     // override the configured successMessage stating that the execution will take longer and is running in background.
193                     if (inBackground) {
194                         this.successMessage = i18n.translate(LONG_RUNNING_ACTION_NOTIFICATION);
195                     }
196                 } catch (AsyncActionExecutor.ParallelExecutionException e) {
197                     this.failureMessage = i18n.translate(PARALLEL_EXECUTION_NOT_ALLOWED_NOTIFICATION);
198                     stopProcessing = true;
199                 }
200             } else {
201                 stopProcessing = commandsManager.executeCommand(command, getParams());
202             }
203             MgnlContext.getInstance().setAttribute(COMMAND_RESULT, stopProcessing, Context.LOCAL_SCOPE);
204 
205             onPostExecute();
206             log.debug("Command executed successfully in {} ms ", System.currentTimeMillis() - start);
207         } catch (Exception e) {
208             onError(e);
209             log.debug("Command execution failed after {} ms ", System.currentTimeMillis() - start);
210             log.debug(e.getMessage(), e);
211             throw new ActionExecutionException(e);
212         }
213     }
214 
215     protected boolean isInvokeAsynchronously() {
216         return getDefinition().isAsynchronous();
217     }
218 
219     /**
220      * Pre Command Execution. Class that implement CommansActionBase should use
221      * this in order to perform pre Command Tasks.
222      * When overriding make sure to call super to build the parameter map.
223      */
224     protected void onPreExecute() throws Exception {
225         this.params = buildParams(getCurrentItem().getJcrItem());
226     }
227 
228     /**
229      * Post Command Execution. Class that implement CommansActionBase should use
230      * this in order to perform post Command Tasks.
231      */
232     protected void onPostExecute() throws Exception {
233         // Sub Class can override this method.
234     }
235 
236     /**
237      * Class that implement CommansActionBase should use
238      * this in order to perform tasks or notification in case of error.
239      */
240     protected void onError(Exception e) {
241         String message = i18n.translate("ui-framework.abstractcommand.executionfailure");
242         getUiContext().openNotification(MessageStyleTypeEnum.ERROR, true, message);
243     }
244 
245     /**
246      * @return current command.
247      */
248     protected Command getCommand() {
249         return this.command;
250     }
251 
252     @Override
253     protected String getSuccessMessage() {
254         // by default, we expect the command-based actions to be limited to single-item, but we still extend MultiAction to make our lives miserable
255         return successMessage;
256     }
257 
258     @Override
259     protected String getFailureMessage() {
260         // by default, we expect the command-based actions to be limited to single-item
261         return failureMessage;
262     }
263 
264     protected SimpleTranslator getI18n() {
265         return i18n;
266     }
267 }