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