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