Clover icon

Magnolia Resources App Module 2.4.2

  1. Project Clover database Fri Nov 6 2015 16:17:22 CET
  2. Package info.magnolia.resources.app.workbench

File ResourcesContainer.java

 

Coverage histogram

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

Code metrics

36
141
54
1
548
391
75
0.53
2.61
54
1.39
1.7% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
ResourcesContainer 85 141 1.7% 75 102
0.558441655.8%
 

Contributing tests

This file is covered by 15 tests. .

Source view

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