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