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.beans.runtime.FileProperties;
37  import info.magnolia.commands.CommandsManager;
38  import info.magnolia.event.EventBus;
39  import info.magnolia.jcr.util.NodeNameHelper;
40  import info.magnolia.objectfactory.Components;
41  import info.magnolia.ui.api.action.AbstractAction;
42  import info.magnolia.ui.api.action.ActionExecutionException;
43  import info.magnolia.ui.api.event.AdmincentralEventBus;
44  import info.magnolia.ui.api.event.ContentChangedEvent;
45  import info.magnolia.ui.form.EditorCallback;
46  import info.magnolia.ui.form.EditorValidator;
47  import info.magnolia.ui.framework.command.ImportZipCommand;
48  import info.magnolia.ui.vaadin.integration.jcr.JcrItemId;
49  import info.magnolia.ui.vaadin.integration.jcr.JcrItemUtil;
50  import info.magnolia.ui.vaadin.integration.jcr.JcrNodeAdapter;
51  
52  import java.util.HashMap;
53  import java.util.Map;
54  
55  import javax.inject.Inject;
56  import javax.inject.Named;
57  import javax.jcr.Node;
58  import javax.jcr.RepositoryException;
59  
60  import org.apache.commons.io.FilenameUtils;
61  import org.apache.jackrabbit.JcrConstants;
62  import org.apache.jackrabbit.value.BinaryImpl;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  import com.vaadin.v7.data.Item;
67  import com.vaadin.v7.data.Property;
68  
69  /**
70   * Triggers zip archive upload command.
71   * @param <T> definition class.
72   * @see ImportZipCommand
73   */
74  public class ZipUploadDialogAction<T extends ZipUploadActionDefinition> extends AbstractAction<T> {
75  
76      private final Logger log = LoggerFactory.getLogger(getClass());
77  
78      public static final String ENCODING_PROPERTY = "encoding";
79  
80      public static final String EXTRACT_LOCATION_PROPERTY = "extractLocation";
81  
82      private CommandsManager commandsManager;
83  
84      private EditorValidator validator;
85  
86      private EditorCallback callback;
87  
88      private Item item;
89  
90      private NodeNameHelper nodeNameHelper;
91  
92      private EventBus eventBus;
93  
94      @Inject
95      public ZipUploadDialogAction(final T definition, final CommandsManager commandsManager, final EditorValidator validator, final EditorCallback callback, Item item, final NodeNameHelper nodeNameHelper, final @Named(AdmincentralEventBus.NAME) EventBus eventBus) {
96          super(definition);
97          this.commandsManager = commandsManager;
98          this.validator = validator;
99          this.callback = callback;
100         this.item = item;
101         this.nodeNameHelper = nodeNameHelper;
102         this.eventBus = eventBus;
103     }
104 
105     /**
106      * @deprecated since 5.4.13 - use {@link #ZipUploadDialogAction(ZipUploadActionDefinition, CommandsManager, EditorValidator, EditorCallback, Item, NodeNameHelper, EventBus)} instead.
107      */
108     @Deprecated
109     public ZipUploadDialogAction(T definition, final CommandsManager commandsManager, final EditorValidator validator, final EditorCallback callback, Item item) {
110         this(definition, commandsManager, validator, callback, item, Components.getComponent(NodeNameHelper.class), null);
111     }
112 
113     @Override
114     public void execute() throws ActionExecutionException {
115         validator.showValidation(true);
116         if (validator.isValid()) {
117 
118             ExtractLocation extractLocation = ExtractLocation.FOLDER;
119             if (item.getItemProperty(EXTRACT_LOCATION_PROPERTY) != null) {
120                 extractLocation = (ExtractLocation) item.getItemProperty(EXTRACT_LOCATION_PROPERTY).getValue();
121             }
122 
123             // Remove redundant properties.
124             JcrNodeAdapter itemChanged = (JcrNodeAdapter) item;
125             itemChanged.removeItemProperty(ENCODING_PROPERTY);
126             itemChanged.removeItemProperty(EXTRACT_LOCATION_PROPERTY);
127 
128             switch (extractLocation) {
129                 case FOLDER:
130                     extractIntoFolder();
131                     break;
132 
133                 case HERE:
134                     extractHere();
135                     break;
136 
137                 default:
138                     break;
139             }
140 
141         } else {
142             log.info("Validation error(s) occurred. No Import performed.");
143         }
144     }
145 
146     private void extractIntoFolder() throws ActionExecutionException {
147         JcrNodeAdapter zipFolder = (JcrNodeAdapter) item;
148         JcrNodeAdapter importNode = getImportNode(zipFolder);
149 
150         try {
151             zipFolder.setNodeName(getZipFolderName(importNode));
152 
153             // The binary data is redundant for zip folder
154             zipFolder.removeChild(importNode);
155 
156             // The zip folder need saving first to have the real path and pass it to zip command
157             Node importedNode = zipFolder.applyChanges();
158             importedNode.getSession().save();
159 
160             try {
161                 executeCommand(getParams(zipFolder, importNode));
162 
163             } catch (ActionExecutionException e) {
164                 // Remove existing zip folder when exception happens
165                 importedNode.remove();
166                 importedNode.getSession().save();
167                 throw new ActionExecutionException(e);
168             }
169 
170             callback.onSuccess(getDefinition().getName());
171 
172         } catch (RepositoryException e) {
173             log.error("Unable to execute item", e.getMessage());
174             throw new ActionExecutionException(e);
175         }
176     }
177 
178     private void extractHere() throws ActionExecutionException {
179         JcrNodeAdapter itemChanged = (JcrNodeAdapter) item;
180         JcrNodeAdapter importData = getImportNode(itemChanged);
181 
182         try {
183             executeCommand(getParams(itemChanged, importData));
184 
185             if (eventBus != null) {
186                 /**
187                  * HACK: Historically the 'importZip' action is partially mis-configured: instead of starting the importZip dialog from within a specialised action,
188                  * we typically hijack the OpenCreateDialogAction, which presets a callback that attempts to send ContentChangedEvent for some item
189                  * supposedly created with a dialog. However, the importZip action acts differently and we need to send the CCE for the item that we select before opening the dialog.
190                  * To prevent the OpenCreateDialogAction's callback from firing the un-desired CCE, we invoke onCancel() method (effectively just closes a dialog) and send the proper event
191                  * right here in this action.
192                  */
193                 JcrItemId itemIdToSelect = JcrItemUtil.getItemId(itemChanged.getJcrItem());
194                 eventBus.fireEvent(new ContentChangedEvent(itemIdToSelect, true));
195                 callback.onCancel();
196 
197             } else {
198                 callback.onSuccess(getDefinition().getName());
199             }
200 
201         } catch (ActionExecutionException | RepositoryException e) {
202             log.error("Unable to execute item", e.getMessage());
203             throw new ActionExecutionException(e);
204         }
205     }
206 
207     private JcrNodeAdapter getImportNode(JcrNodeAdapter nodeAdapter) throws ActionExecutionException {
208         JcrNodeAdapter importNode = (JcrNodeAdapter) nodeAdapter.getChild("import");
209         if (importNode == null) {
210             throw new ActionExecutionException("Import data not found.");
211         }
212         return importNode;
213     }
214 
215     private String getZipFolderName(JcrNodeAdapter importNode) throws RepositoryException {
216         Property fileNameProperty = importNode.getItemProperty(FileProperties.PROPERTY_FILENAME);
217         String fileName = fileNameProperty != null ? fileNameProperty.getValue().toString() : "";
218         String baseFileName = FilenameUtils.getBaseName(fileName);
219         return nodeNameHelper.getUniqueName(importNode.getJcrItem().getSession(), importNode.getJcrItem().getPath(), baseFileName);
220     }
221 
222     /**
223      * Set node name from zip file name.
224      * @deprecated since 5.4.13 - use {@link #getZipFolderName(JcrNodeAdapter)} and {@link info.magnolia.ui.vaadin.integration.jcr.AbstractJcrNodeAdapter#setNodeName(String)} instead.
225      */
226     @Deprecated
227     protected void setNodeName(JcrNodeAdapter parent, JcrNodeAdapter importNode) throws RepositoryException {
228         parent.setNodeName(getZipFolderName(importNode));
229     }
230 
231     private void executeCommand(Map<String, Object> params) throws ActionExecutionException {
232         long start = System.currentTimeMillis();
233         try {
234             // Set the parameter used by the command.
235             commandsManager.executeCommand(getDefinition().getCatalog(), getDefinition().getCommand(), params);
236             log.debug("Command executed successfully in {} ms ", System.currentTimeMillis() - start);
237         } catch (Exception e) {
238             log.debug("Command execution failed after {} ms ", System.currentTimeMillis() - start);
239             throw new ActionExecutionException(e);
240         }
241     }
242 
243     private Map<String, Object> getParams(JcrNodeAdapter itemChanged, JcrNodeAdapter importNode) throws RepositoryException {
244         Map<String, Object> params = new HashMap<>();
245         params.put(ImportZipCommand.STREAM_PROPERTY, ((Property<BinaryImpl>) importNode.getItemProperty(JcrConstants.JCR_DATA)).getValue().getStream());
246         params.put("repository", itemChanged.getWorkspace());
247         params.put("path", itemChanged.getJcrItem().getPath());
248         return params;
249     }
250 
251     /**
252      * Possible extract locations.
253      */
254     public enum ExtractLocation {
255         HERE, FOLDER;
256     }
257 
258 }