View Javadoc
1   /**
2    * This file Copyright (c) 2015 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.cache.browser.app.action;
35  
36  import info.magnolia.cache.browser.app.CacheKeyItem;
37  import info.magnolia.cache.browser.rest.client.CacheService;
38  import info.magnolia.cache.browser.rest.endpoint.CacheEndpoint;
39  import info.magnolia.i18nsystem.SimpleTranslator;
40  import info.magnolia.ui.api.action.ActionExecutionException;
41  import info.magnolia.ui.api.context.UiContext;
42  
43  import java.io.ByteArrayInputStream;
44  import java.io.ByteArrayOutputStream;
45  import java.io.IOException;
46  import java.io.InputStream;
47  import java.util.HashMap;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.zip.ZipEntry;
51  import java.util.zip.ZipOutputStream;
52  
53  import javax.ws.rs.ClientErrorException;
54  
55  import org.apache.commons.io.IOUtils;
56  import org.apache.commons.lang3.StringUtils;
57  import org.codehaus.jackson.JsonNode;
58  
59  import com.cedarsoftware.util.io.JsonWriter;
60  import com.vaadin.data.Item;
61  import com.vaadin.server.Page;
62  import com.vaadin.server.StreamResource;
63  
64  /**
65   * Action that downloads cache entry content from all active public nodes.
66   */
67  public class DownloadCacheContentAction extends AbstractMultiItemAction<DownloadCacheContentActionDefinition> {
68  
69      private static final String DEFAULT_NAME = "untitled";
70      private static final String CONTENT_TYPE = "application/octet-stream";
71      private Map<String, byte[]> cacheContents = new HashMap<>();
72  
73      public DownloadCacheContentAction(DownloadCacheContentActionDefinition definition, Item relatedItem, UiContext uiContext, SimpleTranslator i18n) {
74          super(definition, relatedItem, uiContext, i18n);
75      }
76  
77      public DownloadCacheContentAction(DownloadCacheContentActionDefinition definition, List<Item> relatedItems, UiContext uiContext, SimpleTranslator i18n) {
78          super(definition, relatedItems, uiContext, i18n);
79      }
80  
81      @Override
82      public void execute() throws ActionExecutionException {
83          super.execute();
84          if (cacheContents.size() == 1) {
85              Map.Entry<String, byte[]> entry = cacheContents.entrySet().iterator().next();
86              downloadFile(entry.getKey(), entry.getValue());
87          } else if (cacheContents.size() > 1) {
88              try {
89                  zipAndDownloadFile(cacheContents);
90              } catch (IOException e) {
91                  throw new ActionExecutionException(e.getMessage());
92              }
93          }
94      }
95  
96      @Override
97      protected void executeOnItem(Item item) throws Exception {
98          if (item instanceof CacheKeyItem) {
99              CacheKeyItem cacheKeyItem = (CacheKeyItem) item;
100             if (cacheKeyItem.getCacheServices().size() > 0) {
101                 for (Map.Entry<String, CacheService> entry : cacheKeyItem.getCacheServices().entrySet()) {
102                     String location = entry.getKey();
103                     CacheService cacheService = entry.getValue();
104                     JsonNode jsonNode;
105                     try {
106                         jsonNode = cacheService.getCacheContent(cacheKeyItem.getCacheName(), JsonWriter.objectToJson(cacheKeyItem.getItemId()));
107                     } catch (ClientErrorException e) {
108                         JsonNode errorNode = (JsonNode) e.getResponse().getEntity();
109                         throw new ActionExecutionException(errorNode.get(CacheEndpoint.PROPERTY_ERROR_MESSAGE).getTextValue());
110                     }
111                     if (jsonNode.has(CacheEndpoint.PROPERTY_UNSUPPORTED_CACHED_ENTRY_TYPE)) {
112                         throw new ActionExecutionException(jsonNode.get(CacheEndpoint.PROPERTY_UNSUPPORTED_CACHED_ENTRY_TYPE).getTextValue());
113                     }
114                     String originalUrl = jsonNode.get(CacheEndpoint.PROPERTY_ORIGINAL_URL).getTextValue();
115                     byte[] data = jsonNode.get(CacheEndpoint.PROPERTY_PLAIN_CONTENT).getBinaryValue();
116                     String name = StringUtils.substringAfterLast(originalUrl, "/");
117                     if (StringUtils.isBlank(name) || "/".equals(name)) {
118                         name = DEFAULT_NAME;
119                     }
120                     name = location + "_" + name;
121                     cacheContents.put(name, data);
122                 }
123             }
124         }
125     }
126 
127     @Override
128     protected String getSuccessMessageKey() {
129         return null;
130     }
131 
132     @Override
133     protected String getFailureMessageKey() {
134         return "cache-browser-app.actions.download.error";
135     }
136 
137     @Override
138     protected String itemToString(Item item) {
139         if (item instanceof CacheKeyItem) {
140             return ((CacheKeyItem) item).getItemId().toString();
141         }
142         return null;
143     }
144 
145     protected void zipAndDownloadFile(Map<String, byte[]> cacheContents) throws IOException {
146         ByteArrayOutputStream baos = new ByteArrayOutputStream();
147         ZipOutputStream zos = new ZipOutputStream(baos);
148         for (Map.Entry<String, byte[]> entry : cacheContents.entrySet()) {
149             String name = entry.getKey();
150             byte[] data = entry.getValue();
151             ZipEntry zipEntry = new ZipEntry(name);
152             zipEntry.setSize(data.length);
153             zos.putNextEntry(zipEntry);
154             zos.write(data);
155             zos.closeEntry();
156         }
157         IOUtils.closeQuietly(zos);
158         downloadFile("cacheContent.zip", baos.toByteArray());
159     }
160 
161     protected void downloadFile(String fileName, final byte[] data) {
162         StreamResource.StreamSource source = new StreamResource.StreamSource() {
163             @Override
164             public InputStream getStream() {
165                 return new ByteArrayInputStream(data);
166             }
167         };
168         fileName = fileName.replaceAll("[^a-zA-Z0-9\\.]+", "-"); // get rid of unwanted characters, should we not do this, we could get 404 error
169         StreamResource resource = new StreamResource(source, fileName);
170         // Accessing the DownloadStream via getStream() will set its cacheTime to whatever is set in the parent
171         // StreamResource. By default it is set to 1000 * 60 * 60 * 24, thus we have to override it beforehand.
172         // A negative value or zero will disable caching of this stream.
173         resource.setCacheTime(-1);
174         resource.getStream().setParameter("Content-Disposition", String.format("attachment; filename=\"%s\"", fileName));
175         resource.setMIMEType(CONTENT_TYPE);
176 
177         Page.getCurrent().open(resource, null, false);
178     }
179 }