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 com.vaadin.shared.data.sort.SortDirection.ASCENDING;
37 import static info.magnolia.ui.contentapp.JcrDataProviderUtils.*;
38
39 import info.magnolia.context.Context;
40 import info.magnolia.ui.contentapp.observation.JcrDataSourceObservation;
41 import info.magnolia.ui.datasource.jcr.JcrDatasourceDefinition;
42 import info.magnolia.ui.framework.ioc.Destructible;
43
44 import java.util.Optional;
45 import java.util.stream.Collectors;
46 import java.util.stream.Stream;
47
48 import javax.inject.Provider;
49 import javax.jcr.Node;
50 import javax.jcr.NodeIterator;
51 import javax.jcr.RepositoryException;
52 import javax.jcr.Session;
53
54 import org.apache.commons.collections4.CollectionUtils;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57
58 import com.vaadin.data.provider.AbstractBackEndDataProvider;
59 import com.vaadin.data.provider.Query;
60 import com.vaadin.data.provider.SortOrder;
61
62 import lombok.SneakyThrows;
63
64
65
66
67 public class JcrDataProvider extends AbstractBackEndDataProvider<Node, DataFilter> implements Destructible {
68 private static final Logger log = LoggerFactory.getLogger(JcrDataProvider.class);
69 private final JcrDatasourceDefinition definition;
70 private final Provider<Context> contextProvider;
71 private final JcrDataSourceObservation observation;
72
73 public JcrDataProvider(JcrDatasourceDefinition definition, JcrDataSourceObservation observation, Provider<Context> contextProvider) {
74 this.definition = definition;
75 this.contextProvider = contextProvider;
76 this.observation = observation;
77 observation.register(definition,this);
78 }
79
80 @Override
81 public void destroy() {
82 observation.destroy();
83 }
84
85 @Override
86 public boolean isInMemory() {
87 return false;
88 }
89
90 @Override
91 @SneakyThrows
92 public Stream<Node> fetchFromBackEnd(Query<Node, DataFilter> query) {
93 javax.jcr.query.Query q = createJcrQuery(query);
94 q.setLimit(query.getLimit());
95 q.setOffset(query.getOffset());
96
97 final NodeIterator result = q.execute().getNodes();
98
99 return streamQueryResults(query, result);
100 }
101
102 @Override
103 @SneakyThrows
104 public int sizeInBackEnd(Query<Node, DataFilter> query) {
105 return (int)createJcrQuery(query).execute().getNodes().getSize();
106 }
107
108 @Override
109 @SneakyThrows
110 public Object getId(Node item) {
111 return item.getIdentifier();
112 }
113
114 private Session getSession() throws RepositoryException {
115 return contextProvider.get().getJCRSession(definition.getWorkspace());
116 }
117
118 @SneakyThrows
119 private javax.jcr.query.Query createJcrQuery(Query<Node, DataFilter> query) {
120 final StringBuilder statement = new StringBuilder("select * from [nt:base] as t ");
121
122 createWhereClause().ifPresent(clause -> statement.append(clause));
123 createOrderByClause(query).ifPresent(clause -> statement.append(clause));
124
125 log.debug("JCR query statement is {}", statement);
126
127 return getSession()
128 .getWorkspace()
129 .getQueryManager()
130 .createQuery(statement.toString(), javax.jcr.query.Query.JCR_SQL2);
131 }
132
133 private Stream<Node> streamQueryResults(Query<Node, DataFilter> query, NodeIterator nodes) {
134
135 return streamJcrItems(nodes)
136 .map(node -> wrap((Node) node))
137 .filter(node ->
138 query.getFilter()
139 .map(DataFilter::getPathFilter)
140 .map(path -> isItemPathMatching((Node) node, path))
141 .orElse(true));
142 }
143
144 private Optional<String> createWhereClause() {
145 if (CollectionUtils.isEmpty(definition.getAllowedNodeTypes())) {
146 return Optional.empty();
147 }
148
149
150 final String types = definition.getAllowedNodeTypes()
151 .stream()
152 .map(type -> String.format("t.[jcr:primaryType] = '%s'", type))
153 .collect(Collectors.joining(" or "));
154 return Optional.of(String.format(" where (%s)", types));
155 }
156
157 private Optional<String> createOrderByClause(Query<Node, DataFilter> query) {
158 if (!definition.isSortable()) {
159 return Optional.empty();
160 }
161 StringBuilder sortOrder = new StringBuilder();
162
163
164 if (CollectionUtils.isNotEmpty(definition.getSortByProperties())) {
165 sortOrder.append(definition.getSortByProperties()
166 .stream()
167 .map(o -> String.format("t.[%s] asc", o))
168 .collect(Collectors.joining(",")));
169 }
170
171 sortOrder.append(query.getSortOrders()
172 .stream()
173 .map(so -> resolveProperty(so))
174 .collect(Collectors.joining(",")));
175
176 return sortOrder.length() == 0 ? Optional.empty() : Optional.of(String.format(" order by %s", sortOrder.toString()));
177 }
178
179 private String resolveProperty(SortOrder so) {
180 return String.format("%s %s", "jcrName".equals(so.getSorted()) ? "lower(name(t))" :
181 String.format("t.[%s]", so.getSorted()), so.getDirection() == ASCENDING? "asc" : "desc");
182 }
183 }