Clover icon

Magnolia Resources App Module 2.5.3

  1. Project Clover database Thu May 11 2017 16:02:29 CEST
  2. Package info.magnolia.resources.app.workbench

File ResourcesContainer.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart6.png
73% of files have more coverage

Code metrics

38
152
54
1
565
409
79
0.52
2.81
54
1.46
1.6% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
ResourcesContainer 85 152 1.6% 79 103
0.577868957.8%
 

Contributing tests

This file is covered by 18 tests. .

Source view

1    /**
2    * This file Copyright (c) 2015-2017 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
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    * Vaadin hierarchical {@link Container} implementation representing a resource {@link ResourceOrigin}.
82    * <p>
83    * Uses the {@link Resource} as itemId.
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  17 toggle public ResourcesContainer(ResourceOrigin origin, List<String> moduleNames) {
113  17 this.origin = origin;
114  17 this.moduleNames = moduleNames;
115  17 visibleIds.addAll(rootItemIds());
116  17 cache = initializeCache();
117    }
118   
119    /**
120    * Sets whether or not classpath resources should be tentatively filtered based on their registered magnolia modules.
121    *
122    * @param classpathResourcesFiltered setting this to {@code true} only shows classpath resources whose top-level directory is a registered magnolia module;
123    * setting this to {@code false} shows classpath resources from all classpathUrls (still pre-filtered in ClasspathResourceOrigin).
124    * @see info.magnolia.resourceloader.classpath.ClasspathResourceOrigin#excludedPackages()
125    */
 
126  0 toggle public void setClasspathResourcesFiltered(boolean classpathResourcesFiltered) {
127  0 if (this.classpathResourcesFiltered != classpathResourcesFiltered) {
128  0 this.classpathResourcesFiltered = classpathResourcesFiltered;
129  0 showRootsOnly();
130    }
131    }
132   
133    // CONTAINER READ OPERATIONS
134   
 
135  9 toggle @Override
136    public Item getItem(Object itemId) {
137  9 Preconditions.checkArgument(itemId instanceof String, "Expected String argument, but got %s", itemId == null ? "null" : itemId.getClass().getSimpleName());
138  9 final String resourcePath = (String) itemId;
139  9 return cache.getUnchecked(resourcePath);
140    }
141   
 
142  6 toggle private Item createItem(String resourcePath) {
143  6 if (!origin.hasPath(resourcePath)) {
144  0 throw new IllegalArgumentException(String.format("Path [%s] does not exist", resourcePath));
145    }
146   
147  6 final Resource resource = origin.getByPath(resourcePath);
148  6 return ResourcesContainer.newItem(resource);
149    }
150   
151    // STATIC ITEM CREATION (temporary)
152    // TODO to be moved either to some kind of factory component, or to concrete Item implementation
 
153  6 toggle public static Item newItem(Resource resource) {
154  6 final PropertysetItem item = new PropertysetItem();
155    // directories don't have a relevant origin
156  6 item.addItemProperty(RESOURCE_NAME, new ObjectProperty<>(resource.getName()));
157  6 item.addItemProperty(RESOURCE_PATH, new ObjectProperty<>(resource.getPath()));
158  6 item.addItemProperty(DIRECTORY_PROPERTY_ID, new ObjectProperty<>(resource.isDirectory()));
159   
160  6 boolean isEditable = resource.isEditable();
161  6 if (resource instanceof LayeredResource) {
162  3 final LayeredResource layeredResource = (LayeredResource) resource;
163  3 if (resource.isFile()) {
164  0 item.addItemProperty(ORIGIN_NAME_PROPERTY_ID, new ObjectProperty<>(layeredResource.getFirst().getOrigin().getName()));
165  0 item.addItemProperty(CONTENT_PROPERTY_ID, new ResourceContentProperty(resource));
166  0 item.addItemProperty(OVERRIDING_PROPERTY_ID, new ObjectProperty<>(isOverriding(layeredResource)));
167    // TODO: This is sub-optimal. The resource type resolution should be a part of API! See https://jira.magnolia-cms.com/browse/MGNLRES-175
168  0 item.addItemProperty(FORMAT_PROPERTY_ID, new ObjectProperty<>(new Tika().detect(resource.getName())));
169    } else {
170    // Consider JCR-only folders to be editable
171  3 isEditable &= layeredResource.getLayers().size() == 1 && layeredResource.getFirst() instanceof JcrResource;
172    }
173    } else {
174  3 log.error("Unexpectedly encountered Resource {} of type {}, which was not an instance of LayeredResource.", resource, resource.getClass().getSimpleName());
175    }
176   
177  6 item.addItemProperty(EDITABLE_PROPERTY_ID, new ObjectProperty<>(isEditable));
178  6 return item;
179    }
180   
 
181  3 toggle protected static boolean isOverriding(LayeredResource resource) {
182  3 return resource.getLayers().size() > 1;
183    }
184   
 
185    toggle @Override
186    public Collection<String> getContainerPropertyIds() {
187    return Collections.unmodifiableCollection(containerPropertyIds.keySet());
188    }
189   
 
190    toggle @Override
191    public Collection<String> getItemIds() {
192    return Collections.unmodifiableCollection(visibleIds);
193    }
194   
 
195  0 toggle @Override
196    public Property<?> getContainerProperty(Object itemId, Object propertyId) {
197  0 return getItem(itemId).getItemProperty(propertyId);
198    }
199   
 
200  0 toggle @Override
201    public boolean addContainerProperty(Object propertyId, Class<?> type, Object defaultValue) throws UnsupportedOperationException {
202  0 containerPropertyIds.put((String) propertyId, type);
203  0 return true;
204    }
205   
 
206  0 toggle @Override
207    public boolean removeContainerProperty(Object propertyId) throws UnsupportedOperationException {
208  0 return containerPropertyIds.remove(propertyId) != null;
209    }
210   
 
211  0 toggle @Override
212    public Class<?> getType(Object propertyId) {
213  0 return containerPropertyIds.get(propertyId);
214    }
215   
 
216  0 toggle @Override
217    public int size() {
218  0 return visibleIds.size();
219    }
220   
 
221  0 toggle @Override
222    public boolean containsId(Object itemId) {
223  0 return origin.hasPath(((String) itemId));
224    }
225   
226    // CONTAINER.ORDERED
227   
 
228  0 toggle @Override
229    public Object nextItemId(Object itemId) {
230  0 int index = visibleIds.indexOf(itemId);
231  0 if (index == -1 || index == visibleIds.size() - 1) {
232  0 return null;
233    }
234  0 return visibleIds.get(index + 1);
235    }
236   
 
237  0 toggle @Override
238    public Object prevItemId(Object itemId) {
239  0 int index = visibleIds.indexOf(itemId);
240  0 if (index <= 0) {
241  0 return null;
242    }
243  0 return visibleIds.get(index - 1);
244    }
245   
 
246  0 toggle @Override
247    public Object firstItemId() {
248  0 return visibleIds.getFirst();
249    }
250   
 
251  0 toggle @Override
252    public Object lastItemId() {
253  0 return visibleIds.getLast();
254    }
255   
 
256  0 toggle @Override
257    public boolean isFirstId(Object itemId) {
258  0 return ObjectUtils.equals(itemId, visibleIds.getFirst());
259    }
260   
 
261  0 toggle @Override
262    public boolean isLastId(Object itemId) {
263  0 return ObjectUtils.equals(itemId, visibleIds.getLast());
264    }
265   
266    // CONTAINER.INDEXED
267   
 
268  1 toggle @Override
269    public int indexOfId(Object itemId) {
270  1 return visibleIds.indexOf(itemId);
271    }
272   
 
273  0 toggle @Override
274    public Object getIdByIndex(int index) {
275  0 return visibleIds.get(index);
276    }
277   
 
278  1 toggle @Override
279    public List<String> getItemIds(int startIndex, int numberOfItems) {
280  1 return visibleIds.subList(startIndex, Math.min(startIndex + numberOfItems, visibleIds.size()));
281    }
282   
283    // CONTAINER.HIERARCHICAL
284   
 
285  0 toggle @Override
286    public List<String> getChildren(Object itemId) {
287  0 return Lists.transform(origin.getByPath((String) itemId).listChildren(), resourceToPath());
288    }
289   
 
290  19 toggle @Override
291    public String getParent(Object itemId) {
292  19 try {
293  19 if (itemId instanceof String) {
294  18 Resource resource = origin.getByPath((String) itemId);
295  17 final Resource parent = resource.getParent();
296  17 if (parent != null && ObjectUtils.notEqual(origin.getRoot(), parent)) {
297  5 return parent.getPath();
298    }
299    }
300    } catch (ResourceOrigin.ResourceNotFoundException e) {
301  1 log.debug("Resource could not be found for path {}", itemId);
302    }
303   
304  14 return null;
305    }
306   
 
307  21 toggle @Override
308    public Collection<String> rootItemIds() {
309  21 final Resource root = origin.getRoot();
310  21 final List<String> directRootChildren = Lists.transform(root.listChildren(), resourceToPath());
311   
312  21 if (!classpathResourcesFiltered) {
313  21 return directRootChildren;
314    }
315   
316  0 return Collections2.filter(directRootChildren, new Predicate<String>() {
317   
 
318  0 toggle private boolean isClassPathResource(LayeredResource layeredResource) {
319  0 final ResourceOrigin activeOrigin = layeredResource.getFirst().getOrigin();
320  0 return activeOrigin instanceof ClasspathResourceOrigin;
321    }
322   
 
323  0 toggle @Override
324    public boolean apply(String resourcePath) {
325  0 final LayeredResource layeredResource = (LayeredResource) origin.getByPath(resourcePath);
326  0 boolean isClasspathResource = isClassPathResource(layeredResource);
327  0 return !isClasspathResource || moduleNames.contains(layeredResource.getName());
328    }
329    });
330    }
331   
332   
 
333  0 toggle @Override
334    public boolean areChildrenAllowed(Object itemId) {
335  0 return origin.getByPath((String) itemId).isDirectory();
336    }
337   
 
338  3 toggle @Override
339    public boolean isRoot(Object itemId) {
340  3 try {
341  3 if (itemId instanceof String) {
342  3 Resource resource = origin.getByPath((String) itemId);
343  3 return origin.getRoot().equals(resource.getParent());
344    }
345    } catch (ResourceOrigin.ResourceNotFoundException e) {
346  0 log.debug("Resource could not be found for path {}", itemId);
347    }
348   
349  0 return false;
350    }
351   
 
352  0 toggle @Override
353    public boolean hasChildren(Object itemId) {
354  0 return !origin.getByPath((String) itemId).listChildren().isEmpty();
355    }
356   
357    // COLLAPSIBLE
358   
 
359  19 toggle @Override
360    public void setCollapsed(Object itemId, boolean collapsed) {
361  19 try {
362  19 if (!collapsed) {
363  14 doExpand(itemId);
364    } else {
365  5 doCollapse(itemId);
366    }
367    } catch (ResourceOrigin.ResourceNotFoundException e) {
368  2 log.info("Resource {} is removed or deleted, container needs to be refreshed!", itemId);
369  2 refresh();
370    }
371    }
372   
 
373  16 toggle protected void doExpand(Object itemId) {
374  16 final String pathToExpand = (String) itemId;
375   
376    // item is already expanded
377  16 if (expandedIds.contains(pathToExpand)) {
378  1 return;
379    }
380   
381    // Make sure all the parent nodes are also expanded
382    // (has to be done e.g. if node is expanded programmatically).
383  15 final String parentId = getParent(itemId);
384  15 if (parentId != null && !expandedIds.contains(parentId)) {
385  2 doExpand(parentId);
386    }
387   
388  15 int insertionIndex = visibleIds.indexOf(itemId) + 1;
389  15 List<String> inserts = new ArrayList<>();
390   
391  15 Resource parentResource = origin.getByPath(pathToExpand);
392  14 inserts.addAll(Lists.transform(parentResource.listChildren(), resourceToPath()));
393   
394  14 int i = 0;
395  32 while (i < inserts.size()) {
396  18 String child = inserts.get(i);
397  18 i++;
398  18 if (expandedIds.contains(child)) {
399  1 inserts.addAll(i, Lists.transform(origin.getByPath(child).listChildren(), resourceToPath()));
400    }
401    }
402  14 visibleIds.addAll(insertionIndex, inserts);
403  14 expandedIds.add(pathToExpand);
404    }
405   
 
406  6 toggle protected void doCollapse(Object itemId) {
407  6 int fromIndex = visibleIds.indexOf(itemId) + 1;
408    // Item is the last in container, nothing to collapse
409  6 if (fromIndex == visibleIds.size()) {
410  1 return;
411    }
412   
413  5 int toIndex = fromIndex;
414    // Find first non-descendant of itemId
415  5 Iterator<String> it = visibleIds.subList(fromIndex, visibleIds.size()).iterator();
416  5 List<String> ancestors = new ArrayList<>(Arrays.asList((String) itemId));
417  13 while (it.hasNext()) {
418  12 String nextPath = it.next();
419   
420  12 Resource resource = origin.getByPath(nextPath);
421   
422  10 if (ancestors.contains(resource.getParent().getPath())) {
423  8 toIndex++;
424  8 ancestors.add(nextPath);
425    } else {
426  2 break;
427    }
428    }
429  3 visibleIds.subList(fromIndex, toIndex).clear();
430  3 expandedIds.remove(itemId);
431    }
432   
 
433  5 toggle @Override
434    public boolean isCollapsed(Object itemId) {
435  5 return !expandedIds.contains(itemId);
436    }
437   
 
438  0 toggle protected void showRootsOnly() {
439  0 this.visibleIds.clear();
440  0 this.visibleIds.addAll(rootItemIds());
441  0 this.expandedIds.clear();
442   
443  0 fireItemSetChange();
444    }
445   
446    // REFRESHABLE
 
447  3 toggle @Override
448    public void refresh() {
449  3 final Predicate<String> originHasPath = new Predicate<String>() {
 
450  1 toggle @Override
451    public boolean apply(@Nullable String input) {
452  1 return origin.hasPath(input);
453    }
454    };
455   
456  3 this.visibleIds.clear();
457  3 this.cache.invalidateAll();
458  3 this.visibleIds.addAll(rootItemIds());
459   
460  3 final HashSet<String> expandedIdsCopy = Sets.newHashSet(Iterables.filter(expandedIds, originHasPath));
461  3 expandedIds.clear();
462   
463  3 for (final String expandedId : expandedIdsCopy) {
464  0 doExpand(expandedId);
465    }
466   
467  3 fireItemSetChange();
468    }
469   
470    // WRITE OPERATIONS
471   
 
472  0 toggle @Override
473    public Item addItem(Object itemId) throws UnsupportedOperationException {
474  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
475    }
476   
 
477  0 toggle @Override
478    public Object addItem() throws UnsupportedOperationException {
479  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
480    }
481   
 
482  0 toggle @Override
483    public boolean removeItem(Object itemId) throws UnsupportedOperationException {
484  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
485    }
486   
 
487  0 toggle @Override
488    public boolean removeAllItems() throws UnsupportedOperationException {
489  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
490    }
491   
 
492  0 toggle @Override
493    public Object addItemAfter(Object previousItemId) throws UnsupportedOperationException {
494  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
495    }
496   
 
497  0 toggle @Override
498    public Item addItemAfter(Object previousItemId, Object newItemId) throws UnsupportedOperationException {
499  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
500    }
501   
 
502  0 toggle @Override
503    public Object addItemAt(int index) throws UnsupportedOperationException {
504  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
505    }
506   
 
507  0 toggle @Override
508    public Item addItemAt(int index, Object newItemId) throws UnsupportedOperationException {
509  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
510    }
511   
 
512  0 toggle @Override
513    public boolean setParent(Object itemId, Object newParentId) throws UnsupportedOperationException {
514  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
515    }
516   
 
517  0 toggle @Override
518    public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed) throws UnsupportedOperationException {
519  0 throw new UnsupportedOperationException(UNSUPPORTED_WRITE_OPERATION);
520    }
521   
522    // OVERRIDES to honor Container.ItemSetChangeNotifier's public interface (AbstractContainer defines those methods as protected)
523   
 
524  0 toggle @Override
525    public void addItemSetChangeListener(ItemSetChangeListener listener) {
526  0 super.addItemSetChangeListener(listener);
527    }
528   
 
529  0 toggle @Override
530    public void addListener(ItemSetChangeListener listener) {
531  0 super.addListener(listener);
532    }
533   
 
534  0 toggle @Override
535    public void removeItemSetChangeListener(ItemSetChangeListener listener) {
536  0 super.removeItemSetChangeListener(listener);
537    }
538   
 
539  0 toggle @Override
540    public void removeListener(ItemSetChangeListener listener) {
541  0 super.removeListener(listener);
542    }
543   
 
544  36 toggle private Function<Resource, String> resourceToPath() {
545  36 return new Function<Resource, String>() {
 
546  56 toggle @Nullable
547    @Override
548    public String apply(Resource resource) {
549  56 return resource.getPath();
550    }
551    };
552    }
553   
 
554  17 toggle private LoadingCache<String, Item> initializeCache() {
555  17 return CacheBuilder
556    .newBuilder()
557    .maximumSize(DEFAULT_ITEM_CACHE_SIZE)
558    .build(new CacheLoader<String, Item>() {
 
559  6 toggle @Override
560    public Item load(String itemId) throws Exception {
561  6 return createItem(itemId);
562    }
563    });
564    }
565    }