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