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.rest.delivery.jcr.v2;
35
36 import static java.util.stream.Collectors.*;
37
38 import info.magnolia.cms.i18n.I18nContentSupport;
39 import info.magnolia.cms.security.JCRSessionOp;
40 import info.magnolia.cms.util.PathUtil;
41 import info.magnolia.context.MgnlContext;
42 import info.magnolia.objectfactory.ComponentProvider;
43 import info.magnolia.rest.AbstractEndpoint;
44 import info.magnolia.rest.DynamicPath;
45 import info.magnolia.rest.delivery.jcr.NodesResult;
46 import info.magnolia.rest.delivery.jcr.QueryBuilder;
47 import info.magnolia.rest.delivery.jcr.filter.FilteringContentDecoratorBuilder;
48 import info.magnolia.rest.delivery.jcr.filter.NodeTypesPredicate;
49 import info.magnolia.rest.delivery.jcr.i18n.I18n;
50 import info.magnolia.rest.reference.ReferenceContext;
51 import info.magnolia.rest.reference.ReferenceDefinition;
52 import info.magnolia.rest.reference.ReferenceResolver;
53 import info.magnolia.rest.reference.ReferenceResolverDefinition;
54
55 import java.util.Arrays;
56 import java.util.Collections;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Optional;
60 import java.util.function.Function;
61 import java.util.function.Predicate;
62
63 import javax.inject.Inject;
64 import javax.jcr.Item;
65 import javax.jcr.Node;
66 import javax.jcr.NodeIterator;
67 import javax.jcr.RepositoryException;
68 import javax.jcr.Session;
69 import javax.jcr.Workspace;
70 import javax.jcr.query.Query;
71 import javax.jcr.query.QueryResult;
72 import javax.ws.rs.DefaultValue;
73 import javax.ws.rs.GET;
74 import javax.ws.rs.NotFoundException;
75 import javax.ws.rs.Path;
76 import javax.ws.rs.PathParam;
77 import javax.ws.rs.Produces;
78 import javax.ws.rs.QueryParam;
79 import javax.ws.rs.core.MediaType;
80 import javax.ws.rs.core.UriInfo;
81 import javax.ws.rs.ext.ContextResolver;
82 import javax.ws.rs.ext.Providers;
83
84 import org.apache.commons.collections.CollectionUtils;
85 import org.apache.commons.lang3.StringUtils;
86 import org.apache.jackrabbit.commons.iterator.FilteringNodeIterator;
87 import org.slf4j.Logger;
88 import org.slf4j.LoggerFactory;
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 @DynamicPath
105 @Path("/")
106 @I18n
107 public class JcrDeliveryEndpoint extends AbstractEndpoint<JcrDeliveryEndpointDefinition> {
108
109 private static final Logger log = LoggerFactory.getLogger(JcrDeliveryEndpoint.class);
110
111 private static final String PATH_PARAM = "path";
112 private static final String NODE_TYPES_PARAM = "nodeTypes";
113 private static final String KEYWORD_PARAM = "q";
114 private static final String ORDER_BY_PARAM = "orderBy";
115 private static final String OFFSET_PARAM = "offset";
116 private static final String LIMIT_PARAM = "limit";
117 private static final String LANGUAGE_PARAM = "lang";
118 private static final String ALL_LANGUAGE_PARAM = "all";
119 private static final List<String> ENDPOINT_PARAMETERS = Collections.unmodifiableList(Arrays.asList(
120 NODE_TYPES_PARAM,
121 LANGUAGE_PARAM,
122 KEYWORD_PARAM,
123 ORDER_BY_PARAM,
124 OFFSET_PARAM,
125 LIMIT_PARAM
126 ));
127
128 private final ComponentProvider componentProvider;
129 private final I18nContentSupport i18nContentSupport;
130
131 @javax.ws.rs.core.Context
132 private UriInfo uriInfo;
133
134 @javax.ws.rs.core.Context
135 private Providers providers;
136
137 @Inject
138 public JcrDeliveryEndpoint(JcrDeliveryEndpointDefinition endpointDefinition, ComponentProvider componentProvider, I18nContentSupport i18nContentSupport) {
139 super(endpointDefinition);
140 this.componentProvider = componentProvider;
141 this.i18nContentSupport = i18nContentSupport;
142 }
143
144
145
146
147 @GET
148 @Path("/{path:.*(?<!@nodes)}")
149 @Produces({ MediaType.APPLICATION_JSON })
150 public Node readNode(@PathParam(PATH_PARAM) @DefaultValue("/") String path) throws RepositoryException {
151
152 final JcrDeliveryEndpointDefinition definition = getEndpointDefinition();
153 requireNodeTypes(definition);
154 initReferenceContext(definition.getReferences());
155
156 return doSessionOperation(definition.isBypassWorkspaceAcls(), new JCRSessionOp<Node>(definition.getWorkspace()) {
157 @Override
158 public Node exec(Session session) throws RepositoryException {
159 String nodePath = PathUtil.createPath(definition.getRootPath(), path);
160 Node node = session.getNode(nodePath);
161
162 NodeTypesPredicate nodeTypesPredicate = new NodeTypesPredicate(definition.getNodeTypes(), !definition.isStrict());
163 boolean nodeTypeMatched = nodeTypesPredicate.evaluateTyped(node);
164 if (!nodeTypeMatched) {
165 throw new NotFoundException(String.format("Node '%s' does not match any configured node type", node));
166 }
167
168 FilteringContentDecoratorBuilder decorators = new FilteringContentDecoratorBuilder()
169 .childNodeTypes(definition.getChildNodeTypes())
170 .strict(definition.isStrict())
171 .depth(definition.getDepth())
172 .includeSystemProperties(definition.isIncludeSystemProperties());
173
174 return decorators.wrapNode(node);
175 }
176 });
177 }
178
179
180
181
182 @GET
183 @Path("/{path:(.*)}@nodes")
184 @Produces({ MediaType.APPLICATION_JSON })
185 public NodeIterator getChildren(@PathParam(PATH_PARAM) @DefaultValue("") String path) throws RepositoryException {
186
187 final JcrDeliveryEndpointDefinition definition = getEndpointDefinition();
188 requireNodeTypes(definition);
189 initReferenceContext(definition.getReferences());
190
191 return doSessionOperation(definition.isBypassWorkspaceAcls(), new JCRSessionOp<NodeIterator>(definition.getWorkspace()) {
192 @Override
193 public NodeIterator exec(Session session) throws RepositoryException {
194 String nodePath = PathUtil.createPath(definition.getRootPath(), path);
195 Node parent = session.getNode(nodePath);
196
197 NodeIterator nodeIterator = new FilteringNodeIterator(parent.getNodes(), new NodeTypesPredicate(definition.getNodeTypes(), !definition.isStrict()));
198
199 FilteringContentDecoratorBuilder decorators = new FilteringContentDecoratorBuilder()
200 .childNodeTypes(definition.getChildNodeTypes())
201 .strict(definition.isStrict())
202 .depth(definition.getDepth())
203 .includeSystemProperties(definition.isIncludeSystemProperties());
204
205 return decorators.wrapNodeIterator(nodeIterator);
206 }
207 });
208 }
209
210
211
212
213 @GET
214 @Produces({ MediaType.APPLICATION_JSON })
215 public NodesResult queryNodes(@QueryParam(KEYWORD_PARAM) String keyword,
216 @QueryParam(ORDER_BY_PARAM) String orderByParam,
217 @QueryParam(OFFSET_PARAM) Long offsetParam,
218 @QueryParam(LIMIT_PARAM) Long limitParam) throws RepositoryException {
219
220 final JcrDeliveryEndpointDefinition definition = getEndpointDefinition();
221 requireNodeTypes(definition);
222 initReferenceContext(definition.getReferences());
223
224 long offset = offsetParam == null ? 0 : offsetParam;
225 long limit = limitParam == null ? definition.getLimit() : limitParam;
226 List<String> propertiesToOrderBy = StringUtils.isEmpty(orderByParam) ?
227 Collections.emptyList() :
228 Arrays.stream(StringUtils.split(orderByParam, ","))
229 .map(String::trim)
230 .collect(toList());
231 Map<String, List<String>> filteringConditions = uriInfo.getQueryParameters().entrySet().stream()
232 .filter(entry -> !ENDPOINT_PARAMETERS.contains(entry.getKey()) && !entry.getValue().isEmpty())
233 .collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
234
235 NodeIterator results = doSessionOperation(definition.isBypassWorkspaceAcls(), new JCRSessionOp<NodeIterator>(definition.getWorkspace()) {
236 @Override
237 public NodeIterator exec(Session session) throws RepositoryException {
238 Workspace workspaceObj = session.getWorkspace();
239
240 Query query = QueryBuilder.inWorkspace(workspaceObj)
241 .rootPath(definition.getRootPath())
242 .strict(definition.isStrict())
243 .nodeTypes(definition.getNodeTypes())
244 .keyword(keyword)
245 .conditions(filteringConditions)
246 .orderBy(propertiesToOrderBy)
247 .offset(offset)
248 .limit(limit)
249 .build();
250
251 QueryResult result = query.execute();
252 NodeIterator nodeIterator = result.getNodes();
253
254 FilteringContentDecoratorBuilder decorators = new FilteringContentDecoratorBuilder()
255 .childNodeTypes(definition.getChildNodeTypes())
256 .strict(definition.isStrict())
257 .depth(definition.getDepth())
258 .includeSystemProperties(definition.isIncludeSystemProperties());
259
260 nodeIterator = decorators.wrapNodeIterator(nodeIterator);
261
262 return nodeIterator;
263 }
264 });
265
266 return new NodesResult(results, 0);
267 }
268
269 private void requireNodeTypes(JcrDeliveryEndpointDefinition definition) {
270 if (definition.getNodeTypes() == null) {
271 throw new NotFoundException(String.format("%s are configured as null, not serving any content", NODE_TYPES_PARAM));
272 } else if (definition.getNodeTypes().isEmpty()) {
273 throw new NotFoundException(String.format("%s are configured as empty, not serving any content", NODE_TYPES_PARAM));
274 }
275 }
276
277 private <R> R doSessionOperation(boolean bypassWorkspaceAcls, MgnlContext.Op<R, RepositoryException> operation) throws RepositoryException {
278 if (bypassWorkspaceAcls) {
279 return MgnlContext.doInSystemContext(operation);
280 }
281
282 return operation.exec();
283 }
284
285 private void initReferenceContext(List<ReferenceDefinition> references) {
286 if (CollectionUtils.isNotEmpty(references)) {
287
288 ContextResolver<ReferenceContext> contextResolver = providers.getContextResolver(ReferenceContext.class, MediaType.WILDCARD_TYPE);
289 ReferenceContext context = contextResolver.getContext(ReferenceContext.class);
290
291
292 Map<Predicate, ReferenceResolver> resolvers = references.stream()
293 .filter(definition -> definition.getReferenceResolver() != null && definition.getReferenceResolver().getImplementationClass() != null)
294 .collect(toMap((Function<ReferenceDefinition, Predicate>) this::instantiatePredicate, this::instantiateResolver));
295 context.setResolvers(resolvers);
296 }
297 }
298
299 private Predicate<Item> instantiatePredicate(ReferenceDefinition definition) {
300 return item -> {
301 try {
302 Node node = item.isNode() ? (Node) item : item.getParent();
303 boolean matchNodeType = definition.getNodeType() == null || node.isNodeType(definition.getNodeType());
304 boolean matchItemName = definition.getPropertyName() != null && item.getName().matches(definition.getPropertyName());
305 return matchNodeType && matchItemName;
306 } catch (RepositoryException e) {
307 log.warn("Cannot get information from item {}:", item, e);
308 }
309
310 return false;
311 };
312 }
313
314 private ReferenceResolver instantiateResolver(ReferenceDefinition definition) {
315 ReferenceResolverDefinition resolverDefinition = definition.getReferenceResolver();
316 ReferenceResolver resolver = componentProvider.newInstance(resolverDefinition.getImplementationClass(), resolverDefinition);
317 return new ReferenceResolverWrapper(resolver, getEndpointDefinition().isIncludeSystemProperties(), getEndpointDefinition().isBypassWorkspaceAcls(), shouldSupportI18n() ? i18nContentSupport : null);
318 }
319
320 private boolean shouldSupportI18n() {
321 return !ALL_LANGUAGE_PARAM.equalsIgnoreCase(uriInfo.getQueryParameters().getFirst(LANGUAGE_PARAM));
322 }
323
324
325
326
327
328 private class ReferenceResolverWrapper implements ReferenceResolver {
329 private final ReferenceResolver resolver;
330 private final boolean includeSystemProperties;
331 private final boolean useSystemContext;
332 private final I18nContentSupport i18nContentSupport;
333
334 ReferenceResolverWrapper(ReferenceResolver resolver, boolean includeSystemProperties, boolean useSystemContext, I18nContentSupport i18nContentSupport) {
335 this.resolver = resolver;
336 this.includeSystemProperties = includeSystemProperties;
337 this.useSystemContext = useSystemContext;
338 this.i18nContentSupport = i18nContentSupport;
339 }
340
341 @Override
342 public Optional<?> resolve(Object value) {
343 Optional<?> resolvedItem;
344 try {
345 resolvedItem = doSessionOperation(useSystemContext, () -> resolver.resolve(value));
346 } catch (RepositoryException e) {
347 JcrDeliveryEndpoint.log.warn("Cannot resolve referenced item {}:", value, e);
348 resolvedItem = Optional.empty();
349 }
350
351 if (resolvedItem.isPresent() && (!includeSystemProperties || i18nContentSupport != null)) {
352 FilteringContentDecoratorBuilder filteringBuilder = new FilteringContentDecoratorBuilder()
353 .depth(-1)
354 .includeSystemProperties(includeSystemProperties)
355 .supportI18n(i18nContentSupport);
356 if (resolvedItem.get() instanceof Node) {
357 return ((Optional<Node>) resolvedItem).map(filteringBuilder::wrapNode);
358 } else if (resolvedItem.get() instanceof NodeIterator) {
359 return ((Optional<NodeIterator>) resolvedItem).map(filteringBuilder::wrapNodeIterator);
360 }
361 }
362
363 return resolvedItem;
364 }
365 }
366 }