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.ui.contentapp;
35
36 import static info.magnolia.ui.contentapp.JcrDataProviderUtils.streamJcrItems;
37
38 import info.magnolia.cms.core.MgnlNodeType;
39 import info.magnolia.context.Context;
40 import info.magnolia.ui.contentapp.observation.JcrDataSourceObservation;
41 import info.magnolia.ui.datasource.jcr.SessionReattachingNodeWrapper;
42 import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition;
43 import info.magnolia.ui.datasource.jcr.SessionReattachingPropertyWrapper;
44 import info.magnolia.ui.framework.ioc.Destructible;
45
46 import java.util.Comparator;
47 import java.util.Iterator;
48 import java.util.concurrent.ExecutionException;
49 import java.util.function.Predicate;
50 import java.util.stream.Stream;
51
52 import javax.inject.Provider;
53 import javax.jcr.Item;
54 import javax.jcr.Node;
55 import javax.jcr.Property;
56 import javax.jcr.RepositoryException;
57 import javax.jcr.Session;
58
59 import org.apache.commons.collections4.iterators.IteratorChain;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import com.google.common.cache.Cache;
64 import com.google.common.cache.CacheBuilder;
65 import com.vaadin.data.provider.AbstractBackEndHierarchicalDataProvider;
66 import com.vaadin.data.provider.HierarchicalQuery;
67 import com.vaadin.ui.UI;
68
69 import lombok.SneakyThrows;
70
71
72
73
74 public class HierarchicalJcrDataProvider extends AbstractBackEndHierarchicalDataProvider<Item, DataFilter<JcrDataProviderDefinition>> implements Destructible {
75
76 private static final Logger log = LoggerFactory.getLogger(HierarchicalJcrDataProvider.class);
77
78 private final JcrDatasourceDefinition jcrDatasourceDefinition;
79 private final Provider<Context> contextProvider;
80 private final JcrDataSourceObservation observation;
81
82
83
84
85
86
87
88 private final Cache<Item, String> idCache =
89 CacheBuilder.newBuilder()
90 .maximumSize(1000)
91 .initialCapacity(1000)
92 .build();
93
94 public HierarchicalJcrDataProvider(JcrDatasourceDefinition definition, JcrDataSourceObservation observation, Provider<Context> contextProvider) {
95 this.jcrDatasourceDefinition = definition;
96 this.contextProvider = contextProvider;
97 this.observation = observation;
98
99 final UI ui = UI.getCurrent();
100 observation.register(definition,this);
101 }
102
103 @Override
104 public void destroy() {
105 observation.destroy();
106 }
107
108 @Override
109 public Object getId(Item item) {
110 if (item == null) {
111 return "root";
112 }
113
114 try {
115
116 return idCache.get(item, item::getPath);
117 } catch (ExecutionException e) {
118
119
120 return idCache.getIfPresent(item);
121 }
122 }
123
124 @Override
125 public int getChildCount(HierarchicalQuery<Item, DataFilter<JcrDataProviderDefinition>> query) {
126 Item parent = null;
127 try {
128 parent = query.getParent() == null ? getJcrSession().getRootNode() : query.getParent();
129 return (int) streamQueryResults(query, getChildren(parent), false).count();
130 } catch (RepositoryException e) {
131 log.debug("Failed to resolve child count of [{}]", parent, e);
132 return 0;
133 }
134 }
135
136 @Override
137 public boolean hasChildren(Item item) {
138 try {
139 return getChildren(item).hasNext();
140 } catch (RepositoryException e) {
141 return false;
142 }
143 }
144
145 @SneakyThrows
146 private Item wrap(Item source) {
147 return source.isNode() ?
148 new SessionReattachingNodeWrapper((Node) source) :
149 new SessionReattachingPropertyWrapper((Property) source, contextProvider);
150 }
151
152 @Override
153 @SneakyThrows
154 protected Stream<Item> fetchChildrenFromBackEnd(HierarchicalQuery<Item, DataFilter<JcrDataProviderDefinition>> query) {
155 final Item parent = query.getParent() == null ? getJcrSession().getRootNode() : query.getParent();
156 return streamQueryResults(query, getChildren(parent), true)
157 .skip(query.getOffset())
158 .limit(query.getLimit());
159 }
160
161 private IteratorChain<Item> getChildren(Item item) throws RepositoryException {
162 IteratorChain<Item> iteratorChain = new IteratorChain<>();
163 if (item.isNode()) {
164 Node node = (Node) item;
165 iteratorChain.addIterator(node.getNodes());
166 final Stream<Item> stream = streamJcrItems(node.getProperties());
167 iteratorChain.addIterator(stream.sorted(Comparator.comparing(property -> {
168 try {
169 return property.getName();
170 } catch (RepositoryException e) {
171 return "";
172 }
173 })).iterator());
174 }
175 return iteratorChain;
176 }
177
178 private Stream<Item> streamQueryResults(HierarchicalQuery<Item, DataFilter<JcrDataProviderDefinition>> query, Iterator<Item> nodes, boolean wrapWithSessionPreservation) {
179 return streamJcrItems(nodes)
180 .map(item -> wrapWithSessionPreservation ? this.wrap(item) : item)
181 .filter(filterByAllowedTypes())
182 .filter(node ->
183 query.getFilter()
184 .map(DataFilter::getPathFilter)
185 .map(path -> JcrDataProviderUtils.isItemPathMatching(node, path))
186 .orElse(true));
187 }
188
189 private Predicate<Item> filterByAllowedTypes() {
190 return item -> jcrDatasourceDefinition.getAllowedNodeTypes().stream().anyMatch(nodeType -> {
191 try {
192 if (!item.isNode()) {
193 return jcrDatasourceDefinition.isIncludeProperties() && !item.getName().startsWith(MgnlNodeType.MGNL_PREFIX) && !item.getName().startsWith(MgnlNodeType.JCR_PREFIX);
194 }
195 return ((Node) item).getPrimaryNodeType().isNodeType(nodeType);
196 } catch (RepositoryException e) {
197 return false;
198 }
199 });
200 }
201
202 private Session getJcrSession() throws RepositoryException {
203 return contextProvider.get().getJCRSession(jcrDatasourceDefinition.getWorkspace());
204 }
205 }