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