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 if (itemId == null) {
293 throw new IllegalArgumentException("The provided itemId is null.");
294 }
295 Resource resource;
296 try {
297 resource = origin.getByPath((String) itemId);
298 } catch (ResourceOrigin.ResourceNotFoundException e) {
299 log.debug("Resource could not be found for path {}", itemId);
300 throw new IllegalArgumentException("Attempted to resolve the parent resource item id of non-existing (possibly removed) resource", e);
301 }
302
303 final Resource parent = resource.getParent();
304 if (parent != null && ObjectUtils.notEqual(origin.getRoot(), parent)) {
305 return parent.getPath();
306 }
307
308 return null;
309 }
310
311 @Override
312 public Collection<String> rootItemIds() {
313 final Resource root = origin.getRoot();
314 final List<String> directRootChildren = Lists.transform(root.listChildren(), resourceToPath());
315
316 if (!classpathResourcesFiltered) {
317 return directRootChildren;
318 }
319
320 return Collections2.filter(directRootChildren, new Predicate<String>() {
321
322 private boolean isClassPathResource(LayeredResource layeredResource) {
323 final ResourceOrigin activeOrigin = layeredResource.getFirst().getOrigin();
324 return activeOrigin instanceof ClasspathResourceOrigin;
325 }
326
327 @Override
328 public boolean apply(String resourcePath) {
329 final LayeredResource layeredResource = (LayeredResource) origin.getByPath(resourcePath);
330 boolean isClasspathResource = isClassPathResource(layeredResource);
331 return !isClasspathResource || moduleNames.contains(layeredResource.getName());
332 }
333 });
334 }
335
336
337 @Override
338 public boolean areChildrenAllowed(Object itemId) {
339 return origin.getByPath((String) itemId).isDirectory();
340 }
341
342 @Override
343 public boolean isRoot(Object itemId) {
344 try {
345 Resource resource = origin.getByPath((String) itemId);
346 return origin.getRoot().equals(resource.getParent());
347 } catch (ResourceOrigin.ResourceNotFoundException e) {
348 log.debug("Resource could not be found for path {}", itemId);
349 return false;
350 }
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 if (!collapsed) {
363 doExpand(itemId);
364 } else {
365 doCollapse(itemId);
366 }
367 }
368
369 protected void doExpand(Object itemId) {
370 final String pathToExpand = (String) itemId;
371
372
373 if (expandedIds.contains(pathToExpand)) {
374 return;
375 }
376
377
378
379 final String parentId = getParent(itemId);
380 if (parentId != null && !expandedIds.contains(parentId)) {
381 doExpand(parentId);
382 }
383
384
385 int insertionIndex = visibleIds.indexOf(itemId) + 1;
386 List<String> inserts = new ArrayList<>();
387
388 Resource parentResource = origin.getByPath(pathToExpand);
389 inserts.addAll(Lists.transform(parentResource.listChildren(), resourceToPath()));
390
391 int i = 0;
392 while (i < inserts.size()) {
393 String child = inserts.get(i);
394 i++;
395 if (expandedIds.contains(child)) {
396 inserts.addAll(i, Lists.transform(origin.getByPath(child).listChildren(), resourceToPath()));
397 }
398 }
399 visibleIds.addAll(insertionIndex, inserts);
400 expandedIds.add(pathToExpand);
401 }
402
403 protected void doCollapse(Object itemId) {
404 int fromIndex = visibleIds.indexOf(itemId) + 1;
405
406 if (fromIndex == visibleIds.size()) {
407 return;
408 }
409
410 int toIndex = fromIndex;
411
412 Iterator<String> it = visibleIds.subList(fromIndex, visibleIds.size()).iterator();
413 List<String> ancestors = new ArrayList<>(Arrays.asList((String) itemId));
414 while (it.hasNext()) {
415 String nextPath = it.next();
416
417 Resource resource;
418 try {
419 resource = origin.getByPath(nextPath);
420 } catch (ResourceOrigin.ResourceNotFoundException e) {
421 log.debug("Resource could not be found for path {}", nextPath);
422 it.remove();
423 continue;
424 }
425
426 if (ancestors.contains(resource.getParent().getPath())) {
427 toIndex++;
428 ancestors.add(nextPath);
429 } else {
430 break;
431 }
432 }
433 visibleIds.subList(fromIndex, toIndex).clear();
434 expandedIds.remove(itemId);
435 }
436
437 @Override
438 public boolean isCollapsed(Object itemId) {
439 return !expandedIds.contains(itemId);
440 }
441
442 protected void showRootsOnly() {
443 this.visibleIds.clear();
444 this.visibleIds.addAll(rootItemIds());
445 this.expandedIds.clear();
446
447 fireItemSetChange();
448 }
449
450
451 @Override
452 public void refresh() {
453 final Predicate<String> originHasPath = new Predicate<String>() {
454 @Override
455 public boolean apply(@Nullable String input) {
456 return origin.hasPath(input);
457 }
458 };
459
460 this.visibleIds.clear();
461 this.cache.invalidateAll();
462 this.visibleIds.addAll(rootItemIds());
463
464 final HashSet<String> expandedIdsCopy = Sets.newHashSet(Iterables.filter(expandedIds, originHasPath));
465 expandedIds.clear();
466
467 for (final String expandedId : expandedIdsCopy) {
468 doExpand(expandedId);
469 }
470
471 fireItemSetChange();
472 }
473
474
475
476 @Override
477 public Item addItem(Object itemId) throws UnsupportedOperationException {
478 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
479 }
480
481 @Override
482 public Object addItem() throws UnsupportedOperationException {
483 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
484 }
485
486 @Override
487 public boolean removeItem(Object itemId) throws UnsupportedOperationException {
488 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
489 }
490
491 @Override
492 public boolean removeAllItems() throws UnsupportedOperationException {
493 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
494 }
495
496 @Override
497 public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
498 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
499 }
500
501 @Override
502 public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
503 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
504 }
505
506 @Override
507 public Object addItemAt(int index) throws UnsupportedOperationException {
508 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
509 }
510
511 @Override
512 public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
513 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
514 }
515
516 @Override
517 public boolean setParent(Object itemId, Object newParentId) throws UnsupportedOperationException {
518 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
519 }
520
521 @Override
522 public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) throws UnsupportedOperationException {
523 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
524 }
525
526
527
528 @Override
529 public void addItemSetChangeListener(ItemSetChangeListener listener) {
530 super.addItemSetChangeListener(listener);
531 }
532
533 @Override
534 public void addListener(ItemSetChangeListener listener) {
535 super.addListener(listener);
536 }
537
538 @Override
539 public void removeItemSetChangeListener(ItemSetChangeListener listener) {
540 super.removeItemSetChangeListener(listener);
541 }
542
543 @Override
544 public void removeListener(ItemSetChangeListener listener) {
545 super.removeListener(listener);
546 }
547
548 private Function<Resource, String> resourceToPath() {
549 return new Function<Resource, String>() {
550 @Nullable
551 @Override
552 public String apply(Resource resource) {
553 return resource.getPath();
554 }
555 };
556 }
557
558 private LoadingCache<String, Item> initializeCache() {
559 return CacheBuilder
560 .newBuilder()
561 .maximumSize(DEFAULT_ITEM_CACHE_SIZE)
562 .build(new CacheLoader<String, Item>() {
563 @Override
564 public Item load(String itemId) throws Exception {
565 return createItem(itemId);
566 }
567 });
568 }
569 }