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.app;
35
36 import info.magnolia.cache.browser.app.event.InitializeContainerAfterSuccessfulLoginEvent;
37 import info.magnolia.cache.browser.app.event.RefreshContainerEvent;
38 import info.magnolia.cache.browser.rest.CacheServiceFactory;
39 import info.magnolia.cache.browser.rest.client.CacheService;
40 import info.magnolia.cache.browser.rest.endpoint.CacheEndpoint;
41 import info.magnolia.context.Context;
42 import info.magnolia.context.MgnlContext;
43 import info.magnolia.event.EventBus;
44 import info.magnolia.module.cache.cachekey.CacheKey;
45 import info.magnolia.ui.workbench.container.AbstractContainer;
46 import info.magnolia.ui.workbench.container.Refreshable;
47
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.LinkedHashSet;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56
57 import javax.ws.rs.ClientErrorException;
58
59 import org.apache.commons.lang3.StringUtils;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.cedarsoftware.util.io.JsonReader;
64 import com.fasterxml.jackson.databind.JsonNode;
65 import com.google.common.cache.Cache;
66 import com.google.common.cache.CacheBuilder;
67 import com.vaadin.v7.data.Container;
68 import com.vaadin.v7.data.ContainerHelpers;
69 import com.vaadin.v7.data.Item;
70 import com.vaadin.v7.data.Property;
71
72
73
74
75 public class CacheBrowserContainer extends AbstractContainer implements Container.Indexed, Container.ItemSetChangeNotifier, Container.Sortable, Refreshable {
76
77 private static final Logger log = LoggerFactory.getLogger(CacheBrowserContainer.class);
78
79 public static final int DEFAULT_PAGE_LENGTH = 30;
80
81 public static final int DEFAULT_CACHE_RATIO = 2;
82
83
84
85
86 private int pageLength = DEFAULT_PAGE_LENGTH;
87
88
89
90
91 private int cacheRatio = DEFAULT_CACHE_RATIO;
92
93 private int currentOffset;
94
95 private int size = 0;
96
97 private Cache<Long, CacheKeyItem> itemIndexes = CacheBuilder.newBuilder().maximumSize(1000).build();
98
99 private Set<ItemSetChangeListener> itemSetChangeListeners;
100
101 private final CacheBrowserContentConnectorDefinition contentConnectorDefinition;
102
103 private final CacheServiceFactory cacheServiceFactory;
104
105 private Map<String, CacheService> cacheServices = new HashMap<>();
106
107 private List<String> sortableProperties = new ArrayList<>();
108
109 private String password;
110
111 private String username;
112
113 private String sortOrder;
114
115 private String sortProperty;
116
117 public CacheBrowserContainer(CacheBrowserContentConnectorDefinition contentConnectorDefinition, CacheServiceFactory cacheServiceFactory, EventBus eventBus) {
118 this.contentConnectorDefinition = contentConnectorDefinition;
119 this.cacheServiceFactory = cacheServiceFactory;
120 eventBus.addHandler(InitializeContainerAfterSuccessfulLoginEvent.class, new InitializeContainerAfterSuccessfulLoginEvent.Handler() {
121 @Override
122 public void onLogin(InitializeContainerAfterSuccessfulLoginEvent event) {
123 initialize(event.getUsername(), event.getPassword());
124 }
125 });
126 eventBus.addHandler(RefreshContainerEvent.class, new RefreshContainerEvent.Handler() {
127 @Override
128 public void onRefresh() {
129 initialize(username, password);
130 }
131 });
132 }
133
134 public void initialize(String username, String password) {
135 this.username = username;
136 this.password = password;
137 if (StringUtils.isNotBlank(username) || StringUtils.isNotBlank(password)) {
138 cacheServices = cacheServiceFactory.createCacheServices(new String[] {username, password});
139 }
140 }
141
142 @Override
143 public Item getItem(Object itemId) {
144 for (CacheKeyItem cacheKeyItem : itemIndexes.asMap().values()) {
145 if (cacheKeyItem.getItemId().equals(itemId)) {
146 return cacheKeyItem;
147 }
148 }
149 return null;
150 }
151
152 @Override
153 public Collection<CacheKey> getItemIds() {
154 List<CacheKey> itemIds = new ArrayList<>();
155 for (CacheKeyItem cacheKeyItem : itemIndexes.asMap().values()) {
156 itemIds.add(cacheKeyItem.getItemId());
157 }
158 return Collections.unmodifiableList(itemIds);
159 }
160
161 @Override
162 public Property<?> getContainerProperty(Object itemId, Object propertyId) {
163 final Item item = getItem(itemId);
164 if (item != null) {
165 return item.getItemProperty(propertyId);
166 }
167 log.warn("Couldn't find item {} so property {} can't be retrieved!", itemId, propertyId);
168 return null;
169 }
170
171 @Override
172 public int size() {
173 if (size == 0) {
174 updateSize();
175 }
176 return size;
177 }
178
179 @Override
180 public boolean containsId(Object itemId) {
181 for (CacheKeyItem cacheKeyItem : itemIndexes.asMap().values()) {
182 if (cacheKeyItem.getItemId().equals(itemId)) {
183 return true;
184 }
185 }
186 return false;
187 }
188
189 @Override
190 public Item addItem(Object itemId) throws UnsupportedOperationException {
191 throw new UnsupportedOperationException();
192 }
193
194 @Override
195 public Object addItem() throws UnsupportedOperationException {
196 throw new UnsupportedOperationException();
197 }
198
199 @Override
200 public boolean removeItem(Object itemId) throws UnsupportedOperationException {
201 throw new UnsupportedOperationException();
202 }
203
204 @Override
205 public boolean removeAllItems() throws UnsupportedOperationException {
206 throw new UnsupportedOperationException();
207 }
208
209
210
211 @Override
212 public Object nextItemId(Object itemId) {
213 return getIdByIndex(indexOfId(itemId) + 1);
214 }
215
216 @Override
217 public Object prevItemId(Object itemId) {
218 return getIdByIndex(indexOfId(itemId) - 1);
219 }
220
221 @Override
222 public Object firstItemId() {
223 if (size == 0) {
224 return null;
225 }
226 if (itemIndexes.getIfPresent(0l) != null) {
227 updateOffsetAndCache(0);
228 }
229 return itemIndexes.getIfPresent(0l).getItemId();
230 }
231
232 @Override
233 public Object lastItemId() {
234 final Long lastIx = (long) (size - 1);
235 if (!itemIndexes.asMap().containsKey(lastIx)) {
236 updateOffsetAndCache(size - 1);
237 }
238 return itemIndexes.getIfPresent(lastIx).getItemId();
239 }
240
241 @Override
242 public boolean isFirstId(Object itemId) {
243 return firstItemId().equals(itemId);
244 }
245
246 @Override
247 public boolean isLastId(Object itemId) {
248 return lastItemId().equals(itemId);
249 }
250
251
252
253 @Override
254 public int indexOfId(Object itemId) {
255 if (!containsId(itemId)) {
256 return -1;
257 }
258 boolean wrappedAround = false;
259 while (!wrappedAround) {
260 for (Long i : itemIndexes.asMap().keySet()) {
261 if (itemIndexes.getIfPresent(i) != null && containsId(itemId) ) {
262 return i.intValue();
263 }
264 }
265
266 int nextIndex = (currentOffset / (pageLength * cacheRatio) + 1) * (pageLength * cacheRatio);
267 if (nextIndex >= size) {
268
269 wrappedAround = true;
270 nextIndex = 0;
271 }
272 updateOffsetAndCache(nextIndex);
273 }
274 return -1;
275 }
276
277 @Override
278 public Object getIdByIndex(int index) {
279 if (index < 0 || index > size - 1) {
280 return null;
281 }
282 final Long idx = (long) index;
283 if (itemIndexes.asMap().containsKey(idx)) {
284 return itemIndexes.getIfPresent(idx).getItemId();
285 }
286 log.debug("item id {} not found in cache. Need to update offset, fetch new item ids from jcr repo and put them in cache.", index);
287 updateOffsetAndCache(index);
288 return itemIndexes.getIfPresent(idx) != null ? itemIndexes.getIfPresent(idx).getItemId() : null;
289 }
290
291 @Override
292 public List<?> getItemIds(int startIndex, int numberOfItems) {
293 return ContainerHelpers.getItemIdsUsingGetIdByIndex(startIndex, numberOfItems, this);
294 }
295
296 @Override
297 public Object addItemAt(int index) throws UnsupportedOperationException {
298 throw new UnsupportedOperationException();
299 }
300
301 @Override
302 public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
303 throw new UnsupportedOperationException();
304 }
305
306 @Override
307 public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
308 throw new UnsupportedOperationException();
309 }
310
311 @Override
312 public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
313 throw new UnsupportedOperationException();
314 }
315
316
317
318 @Override
319 public void addItemSetChangeListener(ItemSetChangeListener listener) {
320 if (itemSetChangeListeners == null) {
321 itemSetChangeListeners = new LinkedHashSet<>();
322 }
323 itemSetChangeListeners.add(listener);
324 }
325
326 @Override
327 public void addListener(ItemSetChangeListener listener) {
328 addItemSetChangeListener(listener);
329 }
330
331 @Override
332 public void removeItemSetChangeListener(ItemSetChangeListener listener) {
333 if (itemSetChangeListeners != null) {
334 itemSetChangeListeners.remove(listener);
335 if (itemSetChangeListeners.isEmpty()) {
336 itemSetChangeListeners = null;
337 }
338 }
339 }
340
341 @Override
342 public void removeListener(ItemSetChangeListener listener) {
343 removeItemSetChangeListener(listener);
344 }
345
346 public void fireItemSetChange() {
347 log.debug("Firing item set changed");
348 if (itemSetChangeListeners != null && !itemSetChangeListeners.isEmpty()) {
349 final Container.ItemSetChangeEvent event = new Container.ItemSetChangeEvent() {
350 @Override
351 public Container getContainer() {
352 return CacheBrowserContainer.this;
353 }
354 };
355 Object[] array = itemSetChangeListeners.toArray();
356 for (Object anArray : array) {
357 ItemSetChangeListener listener = (ItemSetChangeListener) anArray;
358 listener.containerItemSetChange(event);
359 }
360 }
361 }
362
363
364
365 @Override
366 public void sort(Object[] propertyId, boolean[] ascending) {
367 clearItemIndexes();
368 resetOffset();
369 sortOrder = ascending[0] ? CacheEndpoint.ASCENDING_ORDER : CacheEndpoint.DESCENDING_ORDER;
370 sortProperty = (String) propertyId[0];
371 getPage();
372 }
373
374 @Override
375 public Collection<String> getSortableContainerPropertyIds() {
376 return sortableProperties;
377 }
378
379
380
381 @Override
382 public void refresh() {
383 size = 0;
384 clearCacheServices();
385 resetOffset();
386 clearItemIndexes();
387 if (MgnlContext.hasAttribute("cache.browser.app.username", Context.SESSION_SCOPE) && MgnlContext.hasAttribute("cache.browser.app.password", Context.SESSION_SCOPE)) {
388 String username = MgnlContext.getAttribute("cache.browser.app.username", Context.SESSION_SCOPE);
389 String password = MgnlContext.getAttribute("cache.browser.app.password", Context.SESSION_SCOPE);
390 initialize(username, password);
391 getPage();
392 }
393 fireItemSetChange();
394 }
395
396
397
398 private void updateOffsetAndCache(int index) {
399 if (itemIndexes.asMap().containsKey((long) index)) {
400 return;
401 }
402 currentOffset = (index / (pageLength * cacheRatio)) * (pageLength * cacheRatio);
403 if (currentOffset < 0) {
404 resetOffset();
405 }
406 getPage();
407 }
408
409 private void resetOffset() {
410 currentOffset = 0;
411 }
412
413 private void getPage() {
414
415 for (Map.Entry<String, CacheService> entry : cacheServices.entrySet()) {
416 String key = entry.getKey();
417 CacheService cacheService = entry.getValue();
418 String json = null;
419 try {
420 json = cacheService.getKeys(contentConnectorDefinition.getCacheName(), currentOffset, pageLength * cacheRatio, sortOrder, sortProperty);
421 } catch (ClientErrorException e) {
422 if (e.getResponse().getEntity() != null && e.getResponse().getEntity() instanceof JsonNode) {
423 JsonNode errorNode = (JsonNode) e.getResponse().getEntity();
424 log.warn("Error occurred while obtaining cache keys for cache {} from public instance {}: {}", contentConnectorDefinition.getCacheName(), entry.getKey(), errorNode.get(CacheEndpoint.PROPERTY_ERROR_MESSAGE).textValue());
425 }
426 log.warn("Error occurred while obtaining cache keys for cache {} from public instance {}: {}", contentConnectorDefinition.getCacheName(), entry.getKey(), e.getMessage());
427 e.getResponse().close();
428 }
429 if (json != null) {
430 List<CacheKey> result = (List<CacheKey>) JsonReader.jsonToJava(json);
431 updateItems(result, key, cacheService);
432 } else {
433 updateItems(Collections.<CacheKey>emptyList(), null, null);
434 }
435 }
436 }
437
438 private void updateItems(List<CacheKey> result, String subscriberName, CacheService cacheService) {
439 long start = System.currentTimeMillis();
440 log.debug("Starting iterating over result set");
441 long rowCount = currentOffset;
442 for (CacheKey cacheKey : result) {
443 CacheKeyItem cacheKeyItem = new CacheKeyItem(contentConnectorDefinition.getCacheName(), cacheKey);
444 if (itemIndexes.asMap().containsValue(cacheKeyItem)) {
445 for (CacheKeyItem item : itemIndexes.asMap().values()) {
446 if (cacheKeyItem.equals(item)) {
447 item.addCacheService(subscriberName, cacheService);
448 }
449 }
450 } else {
451 cacheKeyItem.addCacheService(subscriberName, cacheService);
452 itemIndexes.put(rowCount++, cacheKeyItem);
453 }
454 }
455 log.debug("Done in {} ms", System.currentTimeMillis() - start);
456 }
457
458 private void updateSize() {
459 for (Map.Entry<String, CacheService> entry : cacheServices.entrySet()) {
460 CacheService cacheService = entry.getValue();
461 int newSize = 0;
462 try {
463 newSize = cacheService.getCacheSize(contentConnectorDefinition.getCacheName()).get("size").intValue();
464 } catch (ClientErrorException e) {
465 if (e.getResponse().getEntity() != null && e.getResponse().getEntity() instanceof JsonNode) {
466 JsonNode errorNode = (JsonNode) e.getResponse().getEntity();
467 log.warn("Error occurred while obtaining size of the cache with name {} from public instance {}: {}", contentConnectorDefinition.getCacheName(), entry.getKey(), errorNode.get(CacheEndpoint.PROPERTY_ERROR_MESSAGE).textValue());
468 }
469 log.warn("Error occurred while obtaining size of the cache with name {} from public instance {}: {}", contentConnectorDefinition.getCacheName(), entry.getKey(), e.getMessage());
470 size = 0;
471 e.getResponse().close();
472 }
473 if (size < newSize) {
474 size = newSize;
475 }
476 }
477 }
478
479 private void clearItemIndexes() {
480 itemIndexes.invalidateAll();
481 }
482
483 private void clearCacheServices() {
484 cacheServices.clear();
485 }
486 }