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