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