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.resources.app.workbench;
35
36 import static java.util.stream.Collectors.*;
37
38 import info.magnolia.resourceloader.Resource;
39 import info.magnolia.resourceloader.ResourceOrigin;
40 import info.magnolia.resourceloader.classpath.ClasspathResourceOrigin;
41 import info.magnolia.resourceloader.jcr.JcrResource;
42 import info.magnolia.resourceloader.layered.LayeredResource;
43 import info.magnolia.ui.workbench.container.Refreshable;
44
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.HashSet;
50 import java.util.Iterator;
51 import java.util.LinkedHashMap;
52 import java.util.LinkedList;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Set;
56 import java.util.function.Function;
57
58 import org.apache.commons.lang3.ObjectUtils;
59 import org.apache.tika.Tika;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.google.common.cache.CacheBuilder;
64 import com.google.common.cache.CacheLoader;
65 import com.google.common.cache.LoadingCache;
66 import com.vaadin.v7.data.Collapsible;
67 import com.vaadin.v7.data.Container;
68 import com.vaadin.v7.data.Item;
69 import com.vaadin.v7.data.Property;
70 import com.vaadin.v7.data.util.AbstractContainer;
71 import com.vaadin.v7.data.util.ObjectProperty;
72 import com.vaadin.v7.data.util.PropertysetItem;
73
74
75
76
77
78
79 public class ResourcesContainer extends AbstractContainer implements Container, Container.Indexed, Container.Hierarchical, Collapsible, Refreshable, Container.ItemSetChangeNotifier {
80
81 private static final Logger log = LoggerFactory.getLogger(ResourcesContainer.class);
82 private static final String UNSUPPORTED_WRITE_OPERATION = "ResourceContainer does not support write operations.";
83 private static final int DEFAULT_ITEM_CACHE_SIZE = 150;
84
85 public static final Object RESOURCE_NAME = "name";
86 public static final String ORIGIN_NAME_PROPERTY_ID = "originName";
87 public static final String CONTENT_PROPERTY_ID = "content";
88 public static final String OVERRIDING_PROPERTY_ID = "overriding";
89 public static final String RESOURCE_PATH = "path";
90 public static final String FORMAT_PROPERTY_ID = "format";
91 public static final String DIRECTORY_PROPERTY_ID = "directory";
92 public static final String EDITABLE_PROPERTY_ID = "editable";
93 public static final String ROOT_DIRECTORY = "/";
94
95
96 private final Map<String, Class<?>> containerPropertyIds = new LinkedHashMap<>();
97
98 private final ResourceOrigin origin;
99 private final List<String> moduleNames;
100 private boolean classpathResourcesFiltered;
101
102 private LinkedList<String> visibleIds = new LinkedList<>();
103 private Set<String> expandedIds = new HashSet<>();
104 private LoadingCache<String, Item> cache;
105
106 public ResourcesContainer(ResourceOrigin origin, List<String> moduleNames) {
107 this.origin = origin;
108 this.moduleNames = moduleNames;
109 visibleIds.addAll(rootItemIds());
110 cache = initializeCache();
111 }
112
113
114
115
116
117
118
119
120 public void setClasspathResourcesFiltered(boolean classpathResourcesFiltered) {
121 if (this.classpathResourcesFiltered != classpathResourcesFiltered) {
122 this.classpathResourcesFiltered = classpathResourcesFiltered;
123 showRootsOnly();
124 }
125 }
126
127
128
129 @Override
130 public Item getItem(Object itemId) {
131 if (itemId == null) {
132 return null;
133 }
134 final String resourcePath = (String) itemId;
135 return cache.getUnchecked(resourcePath);
136 }
137
138 private Item createItem(String resourcePath) {
139 if (!origin.hasPath(resourcePath)) {
140 throw new IllegalArgumentException(String.format("Path [%s] does not exist", resourcePath));
141 }
142
143 final Resource resource = origin.getByPath(resourcePath);
144 return ResourcesContainer.newItem(resource);
145 }
146
147
148
149 public static Item newItem(Resource resource) {
150 final PropertysetItem item = new PropertysetItem();
151
152 item.addItemProperty(RESOURCE_NAME, new ObjectProperty<>(resource.getName()));
153 item.addItemProperty(RESOURCE_PATH, new ObjectProperty<>(resource.getPath()));
154 item.addItemProperty(DIRECTORY_PROPERTY_ID, new ObjectProperty<>(resource.isDirectory()));
155
156 boolean isEditable = resource.isEditable();
157 if (resource instanceof LayeredResource) {
158 final LayeredResource layeredResource = (LayeredResource) resource;
159 if (resource.isFile()) {
160 item.addItemProperty(ORIGIN_NAME_PROPERTY_ID, new ObjectProperty<>(layeredResource.getFirst().getOrigin().getName()));
161 item.addItemProperty(CONTENT_PROPERTY_ID, new ResourceContentProperty(resource));
162 item.addItemProperty(OVERRIDING_PROPERTY_ID, new ObjectProperty<>(isOverriding(layeredResource)));
163
164 item.addItemProperty(FORMAT_PROPERTY_ID, new ObjectProperty<>(new Tika().detect(resource.getName())));
165 } else {
166
167 isEditable &= layeredResource.getLayers().size() == 1 && layeredResource.getFirst() instanceof JcrResource;
168 }
169 } else {
170 log.error("Unexpectedly encountered Resource {} of type {}, which was not an instance of LayeredResource.", resource, resource.getClass().getSimpleName());
171 }
172
173 item.addItemProperty(EDITABLE_PROPERTY_ID, new ObjectProperty<>(isEditable));
174 return item;
175 }
176
177 protected static boolean isOverriding(LayeredResource resource) {
178 return resource.getLayers().size() > 1;
179 }
180
181 @Override
182 public Collection<String> getContainerPropertyIds() {
183 return Collections.unmodifiableCollection(containerPropertyIds.keySet());
184 }
185
186 @Override
187 public Collection<String> getItemIds() {
188 return Collections.unmodifiableCollection(visibleIds);
189 }
190
191 @Override
192 public Property<?> getContainerProperty(Object itemId, Object propertyId) {
193 return getItem(itemId).getItemProperty(propertyId);
194 }
195
196 @Override
197 public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException {
198 containerPropertyIds.put((String) propertyId, type);
199 return true;
200 }
201
202 @Override
203 public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
204 return containerPropertyIds.remove(propertyId) != null;
205 }
206
207 @Override
208 public Class<?> getType(Object propertyId) {
209 return containerPropertyIds.get(propertyId);
210 }
211
212 @Override
213 public int size() {
214 return visibleIds.size();
215 }
216
217 @Override
218 public boolean containsId(Object itemId) {
219 return origin.hasPath(((String) itemId));
220 }
221
222
223
224 @Override
225 public Object nextItemId(Object itemId) {
226 int index = visibleIds.indexOf(itemId);
227 if (index == -1 || index == visibleIds.size() - 1) {
228 return null;
229 }
230 return visibleIds.get(index + 1);
231 }
232
233 @Override
234 public Object prevItemId(Object itemId) {
235 int index = visibleIds.indexOf(itemId);
236 if (index <= 0) {
237 return null;
238 }
239 return visibleIds.get(index - 1);
240 }
241
242 @Override
243 public Object firstItemId() {
244 return visibleIds.getFirst();
245 }
246
247 @Override
248 public Object lastItemId() {
249 return visibleIds.getLast();
250 }
251
252 @Override
253 public boolean isFirstId(Object itemId) {
254 return ObjectUtils.equals(itemId, visibleIds.getFirst());
255 }
256
257 @Override
258 public boolean isLastId(Object itemId) {
259 return ObjectUtils.equals(itemId, visibleIds.getLast());
260 }
261
262
263
264 @Override
265 public int indexOfId(Object itemId) {
266 return visibleIds.indexOf(itemId);
267 }
268
269 @Override
270 public Object getIdByIndex(int index) {
271 return visibleIds.get(index);
272 }
273
274 @Override
275 public List<String> getItemIds(int startIndex, int numberOfItems) {
276 return visibleIds.subList(startIndex, Math.min(startIndex + numberOfItems, visibleIds.size()));
277 }
278
279
280
281 @Override
282 public List<String> getChildren(Object itemId) {
283 return origin.getByPath((String) itemId).listChildren().stream()
284 .map(Resource::getPath)
285 .collect(toList());
286 }
287
288 @Override
289 public String getParent(Object itemId) {
290 try {
291 if (itemId instanceof String) {
292 Resource resource = origin.getByPath((String) itemId);
293 final Resource parent = resource.getParent();
294 if (parent != null && ObjectUtils.notEqual(origin.getRoot(), parent)) {
295 return parent.getPath();
296 }
297 }
298 } catch (ResourceOrigin.ResourceNotFoundException e) {
299 log.debug("Resource could not be found for path {}", itemId);
300
301 return origin.getRoot().getPath();
302 }
303
304 return null;
305 }
306
307 @Override
308 public Collection<String> rootItemIds() {
309 final Resource root = origin.getRoot();
310 final List<String> directRootChildren = root.listChildren().stream()
311 .map(Resource::getPath)
312 .collect(toList());
313
314 if (!classpathResourcesFiltered) {
315 return directRootChildren;
316 }
317
318 return directRootChildren.stream()
319 .map((Function<String, Resource>) origin::getByPath)
320 .filter(this::isVisibleResource)
321 .map(Resource::getPath)
322 .collect(toList());
323 }
324
325
326
327
328
329 private boolean isVisibleResource(Resource resource) {
330 LayeredResource layeredResource = LayeredResource.class.cast(resource);
331 ResourceOrigin activeOrigin = layeredResource.getFirst().getOrigin();
332 return !(activeOrigin instanceof ClasspathResourceOrigin) || moduleNames.contains(layeredResource.getName());
333 }
334
335 private boolean isClassPathResource(LayeredResource layeredResource) {
336 final ResourceOrigin activeOrigin = layeredResource.getFirst().getOrigin();
337 return activeOrigin instanceof ClasspathResourceOrigin;
338 }
339
340 @Override
341 public boolean areChildrenAllowed(Object itemId) {
342 if (itemId instanceof String) {
343 String path = (String) itemId;
344 if (origin.hasPath(path)) {
345 return origin.getByPath(path).isDirectory();
346 }
347 }
348 return false;
349 }
350
351 @Override
352 public boolean isRoot(Object itemId) {
353 try {
354 if (itemId instanceof String) {
355 Resource resource = origin.getByPath((String) itemId);
356 return origin.getRoot().equals(resource.getParent());
357 }
358 } catch (ResourceOrigin.ResourceNotFoundException e) {
359 log.debug("Resource could not be found for path {}", itemId);
360
361 return true;
362 }
363
364 return false;
365 }
366
367 @Override
368 public boolean hasChildren(Object itemId) {
369 return !origin.getByPath((String) itemId).listChildren().isEmpty();
370 }
371
372
373
374 @Override
375 public void setCollapsed(Object itemId, boolean collapsed) {
376 try {
377 if (!collapsed) {
378 doExpand(itemId);
379 } else {
380 doCollapse(itemId);
381 }
382 } catch (ResourceOrigin.ResourceNotFoundException e) {
383 log.info("Resource {} is removed or deleted, container needs to be refreshed!", itemId);
384 refresh();
385 }
386 }
387
388 protected void doExpand(Object itemId) {
389 final String pathToExpand = (String) itemId;
390
391
392 if (expandedIds.contains(pathToExpand)) {
393 return;
394 }
395
396 Resource parentResource = origin.getByPath(pathToExpand);
397 List<String> inserts = parentResource.listChildren().stream()
398 .map(Resource::getPath)
399 .collect(toList());
400
401
402
403 final String parentId = getParent(itemId);
404 if (parentId != null && !expandedIds.contains(parentId)) {
405 doExpand(parentId);
406 }
407
408 int insertionIndex = visibleIds.indexOf(itemId) + 1;
409
410 int i = 0;
411 while (i < inserts.size()) {
412 String child = inserts.get(i);
413 i++;
414 if (expandedIds.contains(child)) {
415 inserts.addAll(i, origin.getByPath(child).listChildren().stream()
416 .map(Resource::getPath)
417 .collect(toList()));
418 }
419 }
420 visibleIds.addAll(insertionIndex, inserts);
421 expandedIds.add(pathToExpand);
422 }
423
424 protected void doCollapse(Object itemId) {
425 int fromIndex = visibleIds.indexOf(itemId) + 1;
426
427 if (fromIndex == visibleIds.size()) {
428 return;
429 }
430
431 int toIndex = fromIndex;
432
433 Iterator<String> it = visibleIds.subList(fromIndex, visibleIds.size()).iterator();
434 List<String> ancestors = new ArrayList<>(Arrays.asList((String) itemId));
435 while (it.hasNext()) {
436 String nextPath = it.next();
437
438 Resource resource = origin.getByPath(nextPath);
439
440 if (ancestors.contains(resource.getParent().getPath())) {
441 toIndex++;
442 ancestors.add(nextPath);
443 } else {
444 break;
445 }
446 }
447 visibleIds.subList(fromIndex, toIndex).clear();
448 expandedIds.remove(itemId);
449 }
450
451 @Override
452 public boolean isCollapsed(Object itemId) {
453 return !expandedIds.contains(itemId);
454 }
455
456 protected void showRootsOnly() {
457 this.visibleIds.clear();
458 this.visibleIds.addAll(rootItemIds());
459 this.expandedIds.clear();
460
461 fireItemSetChange();
462 }
463
464
465 @Override
466 public void refresh() {
467 this.visibleIds.clear();
468 this.cache.invalidateAll();
469 this.visibleIds.addAll(rootItemIds());
470
471 Set<String> expandedIdsCopy = expandedIds.stream()
472 .filter(origin::hasPath)
473 .collect(toSet());
474
475 expandedIds.clear();
476 expandedIdsCopy.forEach(this::doExpand);
477
478 fireItemSetChange();
479 }
480
481
482
483 @Override
484 public Item addItem(Object itemId) throws UnsupportedOperationException {
485 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
486 }
487
488 @Override
489 public Object addItem() throws UnsupportedOperationException {
490 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
491 }
492
493 @Override
494 public boolean removeItem(Object itemId) throws UnsupportedOperationException {
495 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
496 }
497
498 @Override
499 public boolean removeAllItems() throws UnsupportedOperationException {
500 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
501 }
502
503 @Override
504 public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
505 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
506 }
507
508 @Override
509 public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
510 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
511 }
512
513 @Override
514 public Object addItemAt(int index) throws UnsupportedOperationException {
515 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
516 }
517
518 @Override
519 public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
520 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
521 }
522
523 @Override
524 public boolean setParent(Object itemId, Object newParentId) throws UnsupportedOperationException {
525 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
526 }
527
528 @Override
529 public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) throws UnsupportedOperationException {
530 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
531 }
532
533
534
535 @Override
536 public void addItemSetChangeListener(ItemSetChangeListener listener) {
537 super.addItemSetChangeListener(listener);
538 }
539
540 @Override
541 public void addListener(ItemSetChangeListener listener) {
542 super.addListener(listener);
543 }
544
545 @Override
546 public void removeItemSetChangeListener(ItemSetChangeListener listener) {
547 super.removeItemSetChangeListener(listener);
548 }
549
550 @Override
551 public void removeListener(ItemSetChangeListener listener) {
552 super.removeListener(listener);
553 }
554
555 private LoadingCache<String, Item> initializeCache() {
556 return CacheBuilder.newBuilder()
557 .maximumSize(DEFAULT_ITEM_CACHE_SIZE)
558 .build(new CacheLoader<String, Item>() {
559 @Override
560 public Item load(String itemId) throws Exception {
561 return createItem(itemId);
562 }
563 });
564 }
565 }