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