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