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.rest.endpoint;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.module.cache.Cache;
38  import info.magnolia.module.cache.cachekey.CacheKey;
39  import info.magnolia.module.cache.filter.InMemoryCachedEntry;
40  import info.magnolia.module.cache.inject.CacheFactoryProvider;
41  import info.magnolia.rest.AbstractEndpoint;
42  
43  import java.lang.reflect.InvocationTargetException;
44  import java.util.ArrayList;
45  import java.util.Collections;
46  import java.util.Comparator;
47  import java.util.List;
48  
49  import javax.inject.Inject;
50  import javax.ws.rs.DELETE;
51  import javax.ws.rs.GET;
52  import javax.ws.rs.Path;
53  import javax.ws.rs.PathParam;
54  import javax.ws.rs.Produces;
55  import javax.ws.rs.QueryParam;
56  import javax.ws.rs.core.MediaType;
57  import javax.ws.rs.core.Response;
58  
59  import org.apache.commons.beanutils.BeanUtils;
60  import org.apache.commons.lang3.StringUtils;
61  import org.codehaus.jackson.node.JsonNodeFactory;
62  import org.codehaus.jackson.node.ObjectNode;
63  
64  import com.cedarsoftware.util.io.JsonIoException;
65  import com.cedarsoftware.util.io.JsonReader;
66  import com.cedarsoftware.util.io.JsonWriter;
67  import com.wordnik.swagger.annotations.Api;
68  import com.wordnik.swagger.annotations.ApiOperation;
69  
70  /**
71   * Endpoint for retrieving, downloading and deleting cache entries.
72   */
73  @Api(value = "/cache/v1", description = "The Cache API")
74  @Path("/cache/v1")
75  public class CacheEndpoint extends AbstractEndpoint<CacheEndpointDefinition> {
76  
77      public static final String DEFAULT_REST_URL = "/.rest/cache/v1";
78      public static final String PROPERTY_ERROR_MESSAGE = "errorMessage";
79      public static final String PROPERTY_SIZE = "size";
80      public static final String PROPERTY_ORIGINAL_URL = "originalUrl";
81      public static final String PROPERTY_CONTENT_TYPE = "contentType";
82      public static final String PROPERTY_PLAIN_CONTENT = "plainContent";
83      public static final String PROPERTY_UNSUPPORTED_CACHED_ENTRY_TYPE = "unsupportedCachedEntryType";
84      public static final String ASCENDING_ORDER = "ascending";
85      public static final String DESCENDING_ORDER = "descending";
86  
87      private final CacheFactoryProvider cacheFactoryProvider;
88      private final SimpleTranslator i18n;
89  
90      @Inject
91      public CacheEndpoint(CacheEndpointDefinition endpointDefinition, CacheFactoryProvider cacheFactoryProvider, SimpleTranslator i18n) {
92          super(endpointDefinition);
93          this.cacheFactoryProvider = cacheFactoryProvider;
94          this.i18n = i18n;
95      }
96  
97      @GET
98      @Path("/ping")
99      @Produces({MediaType.APPLICATION_JSON})
100     @ApiOperation(value = "Checks if the service is alive")
101     public Response ping() {
102         return Response.ok().build();
103     }
104 
105     @GET
106     @Path("/{cacheName}/getAll")
107     @Produces({MediaType.APPLICATION_JSON})
108     @ApiOperation(value = "Get all cache keys", notes = "Returns all cache keys from the specified cache.")
109     public Response getAllKeys(@PathParam("cacheName") String cacheName) {
110         Cache cache = cacheFactoryProvider.get().getCache(cacheName);
111         if (cache == null) {
112             ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
113             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.getAll.cacheNotFound", cacheName));
114             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
115         }
116         ArrayList<Object> keys = new ArrayList<>(cache.getKeys());
117         String json;
118         json = JsonWriter.objectToJson(keys);
119         return Response.status(Response.Status.OK).entity(json).build();
120     }
121 
122     @GET
123     @Path("/{cacheName}/get")
124     @Produces({MediaType.APPLICATION_JSON})
125     @ApiOperation(value = "Get cache keys based on the offset and page length.", notes = "Returns cache keys from the specified cache.")
126     public Response getKeys(@PathParam("cacheName") String cacheName, @QueryParam("offset") int offset, @QueryParam("pageLength") int pageLength, @QueryParam("sortOrder") String sortOrder, @QueryParam("sortProperty") String sortProperty) {
127         ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
128         Cache cache = cacheFactoryProvider.get().getCache(cacheName);
129         if (cache == null) {
130             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.get.cacheNotFound", cacheName));
131             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
132         }
133         if (offset < 0) {
134             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.get.offsetLesserThanZero", cacheName));
135             return Response.status(Response.Status.BAD_REQUEST).entity(jsonNode).build();
136         }
137         if (pageLength < 1) {
138             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.get.pageLengthLesserThanZero", cacheName));
139             return Response.status(Response.Status.BAD_REQUEST).entity(jsonNode).build();
140         }
141         List<Object> keys = new ArrayList<>(cache.getKeys());
142         if (StringUtils.isNotBlank(sortOrder) && StringUtils.isNotBlank(sortProperty)) {
143             sortKeys(keys, sortOrder, sortProperty);
144         }
145         String json;
146         if (keys.size() > offset) {
147             int length = (keys.size() > (offset + pageLength)) ? offset + pageLength : keys.size();
148             keys = new ArrayList<>(keys.subList(offset, length));
149         } else {
150             keys = new ArrayList<>();
151         }
152         json = JsonWriter.objectToJson(keys);
153         return Response.status(Response.Status.OK).entity(json).build();
154     }
155 
156     @GET
157     @Path("/{cacheName}/size")
158     @Produces({MediaType.APPLICATION_JSON})
159     @ApiOperation(value = "Get cache size.", notes = "Returns cache size for the specified cache.")
160     public Response getCacheSize(@PathParam("cacheName") String cacheName) {
161         Cache cache = cacheFactoryProvider.get().getCache(cacheName);
162         if (cache == null) {
163             ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
164             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.size.cacheNotFound", cacheName));
165             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
166         }
167         int size = cache.getSize();
168         ObjectNode response = JsonNodeFactory.instance.objectNode();
169         response.put(PROPERTY_SIZE, size);
170         return Response.status(Response.Status.OK).entity(response).build();
171     }
172 
173     @DELETE
174     @Path("/{cacheName}/delete")
175     @ApiOperation(value = "Delete item from cache.", notes = "Deletes item from the cache based on the passed cache key json.")
176     public Response delete(@PathParam("cacheName") String cacheName, @QueryParam("cacheKey") String json) {
177         ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
178         if (StringUtils.isBlank(json)) {
179             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.delete.jsonStringIsBlank"));
180             return Response.status(Response.Status.BAD_REQUEST).entity(jsonNode).build();
181         }
182         Cache cache = cacheFactoryProvider.get().getCache(cacheName);
183         CacheKey cacheKey;
184         try {
185             cacheKey = (CacheKey) JsonReader.jsonToJava(json);
186         } catch (JsonIoException e) {
187             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.delete.unableToCreateCacheKeyFromJson", cacheName));
188             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
189         }
190         if (cache == null) {
191             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.delete.cacheNotFound", cacheName));
192             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
193         }
194         if (cacheKey == null) {
195             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.delete.cacheKeyNotFound"));
196             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
197         }
198         cache.remove(cacheKey);
199         return Response.status(Response.Status.OK).build();
200     }
201 
202     @GET
203     @Path("/{cacheName}/download")
204     @Produces({MediaType.APPLICATION_JSON})
205     @ApiOperation(value = "Get cache content.", notes = "Returns cache content for the specified cache key.")
206     public Response getCacheContent(@PathParam("cacheName") String cacheName, @QueryParam("cacheKey") String json) {
207         ObjectNode jsonNode = JsonNodeFactory.instance.objectNode();
208         if (StringUtils.isBlank(json)) {
209             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.download.jsonStringIsBlank"));
210             return Response.status(Response.Status.BAD_REQUEST).entity(jsonNode).build();
211         }
212         Cache cache = cacheFactoryProvider.get().getCache(cacheName);
213         CacheKey cacheKey;
214         try {
215             cacheKey = (CacheKey) JsonReader.jsonToJava(json);
216         } catch (JsonIoException e) {
217             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.download.unableToCreateCacheKeyFromJson", cacheName));
218             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
219         }
220         if (cache == null) {
221             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.download.cacheNotFound", cacheName));
222             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
223         }
224         if (cacheKey == null) {
225             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.download.cacheKeyNotFound"));
226             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
227         }
228         Object cachedEntry = cache.getQuiet(cacheKey);
229         if (cachedEntry == null) {
230             jsonNode.put(PROPERTY_ERROR_MESSAGE, i18n.translate("cache.endpoint.download.cachedEntryNotFound"));
231             return Response.status(Response.Status.NOT_FOUND).entity(jsonNode).build();
232         }
233         if (cachedEntry instanceof InMemoryCachedEntry) {
234             InMemoryCachedEntry inMemoryCachedEntry = (InMemoryCachedEntry) cachedEntry;
235             String originalUrl = inMemoryCachedEntry.getOriginalURL();
236             String contentType = inMemoryCachedEntry.getContentType();
237             byte[] plainContent = inMemoryCachedEntry.getPlainContent();
238             jsonNode.put(PROPERTY_ORIGINAL_URL, originalUrl);
239             jsonNode.put(PROPERTY_CONTENT_TYPE, contentType);
240             jsonNode.put(PROPERTY_PLAIN_CONTENT, plainContent);
241         } else {
242             jsonNode.put(PROPERTY_UNSUPPORTED_CACHED_ENTRY_TYPE, i18n.translate("cache.endpoint.download.unsupportedType", cachedEntry.getClass().getCanonicalName(), InMemoryCachedEntry.class.getCanonicalName()));
243         }
244         return Response.status(Response.Status.OK).entity(jsonNode).build();
245     }
246 
247     private void sortKeys(List<Object> keys, final String sortOrder, final String sortProperty) {
248         Collections.sort(keys, new Comparator<Object>() {
249             @Override
250             public int compare(Object o1, Object o2) {
251                 String prop1;
252                 String prop2;
253                 try {
254                     if (sortProperty.equals("extension")) {
255                         prop1 = StringUtils.substringAfterLast(BeanUtils.getProperty(o1, "uri"), ".");
256                         prop2 = StringUtils.substringAfterLast(BeanUtils.getProperty(o2, "uri"), ".");
257                     } else {
258                         prop1 = BeanUtils.getProperty(o1, sortProperty);
259                         prop2 = BeanUtils.getProperty(o2, sortProperty);
260                     }
261                 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
262                     return 0;
263                 }
264                 if (ASCENDING_ORDER.equals(sortOrder)) {
265                     return prop1.compareTo(prop2);
266                 } else {
267                     return prop2.compareTo(prop1);
268                 }
269             }
270         });
271     }
272 }