Clover icon

Magnolia REST Services 2.0-alpha2

  1. Project Clover database Tue Oct 10 2017 14:50:59 CEST
  2. Package info.magnolia.rest.service.node.v1

File NodeEndpoint.java

 

Coverage histogram

../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

34
68
6
1
316
204
30
0.44
11.33
6
5

Classes

Class Line # Actions
NodeEndpoint 80 68 0% 30 2
0.981481598.1%
 

Contributing tests

This file is covered by 29 tests. .

Source view

1    /**
2    * This file Copyright (c) 2012-2017 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.rest.service.node.v1;
35   
36    import info.magnolia.cms.util.ExclusiveWrite;
37    import info.magnolia.cms.util.PathUtil;
38    import info.magnolia.context.MgnlContext;
39    import info.magnolia.rest.AbstractEndpoint;
40    import info.magnolia.rest.service.node.definition.NodeEndpointDefinition;
41   
42    import java.util.ArrayList;
43    import java.util.Arrays;
44    import java.util.List;
45   
46    import javax.inject.Inject;
47    import javax.jcr.Node;
48    import javax.jcr.RepositoryException;
49    import javax.jcr.Session;
50    import javax.ws.rs.Consumes;
51    import javax.ws.rs.DELETE;
52    import javax.ws.rs.DefaultValue;
53    import javax.ws.rs.GET;
54    import javax.ws.rs.POST;
55    import javax.ws.rs.PUT;
56    import javax.ws.rs.Path;
57    import javax.ws.rs.PathParam;
58    import javax.ws.rs.Produces;
59    import javax.ws.rs.QueryParam;
60    import javax.ws.rs.core.MediaType;
61    import javax.ws.rs.core.Response;
62   
63    import org.apache.commons.lang3.StringUtils;
64    import org.slf4j.Logger;
65    import org.slf4j.LoggerFactory;
66   
67    import io.swagger.annotations.Api;
68    import io.swagger.annotations.ApiOperation;
69    import io.swagger.annotations.ApiResponse;
70    import io.swagger.annotations.ApiResponses;
71   
72   
73    /**
74    * Endpoint for accessing and manipulating nodes.
75    *
76    * @param <D> The endpoint definition
77    */
78    @Api(value = "/nodes/v1", description = "The nodes API")
79    @Path("/nodes/v1")
 
80    public class NodeEndpoint<D extends NodeEndpointDefinition> extends AbstractEndpoint<D> {
81   
82    private static final String STATUS_MESSAGE_OK = "OK";
83    private static final String STATUS_MESSAGE_BAD_REQUEST = "Request not understood due to errors or malformed syntax";
84    private static final String STATUS_MESSAGE_UNAUTHORIZED = "Unauthorized";
85    private static final String STATUS_MESSAGE_ACCESS_DENIED = "Access denied";
86    private static final String STATUS_MESSAGE_NODE_NOT_FOUND = "Node not found";
87    private static final String STATUS_MESSAGE_ERROR_OCCURRED = "Error occurred";
88   
89    private final Logger log = LoggerFactory.getLogger(getClass());
90   
91    private RepositoryMarshaller marshaller = new RepositoryMarshaller();
92   
 
93  29 toggle @Inject
94    public NodeEndpoint(final D endpointDefinition) {
95  29 super(endpointDefinition);
96    }
97   
98    /**
99    * Returns a node including its properties and child nodes down to a certain depth.
100    */
 
101  6 toggle @GET
102    @Path("/{workspace}{path:(/.+)?}")
103    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
104    @ApiOperation(value = "Get a node", notes = "Returns a node from the specified workspace and path")
105    @ApiResponses(value = {
106    @ApiResponse(code = 200, message = STATUS_MESSAGE_OK, response = RepositoryNode.class),
107    @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
108    @ApiResponse(code = 404, message = STATUS_MESSAGE_NODE_NOT_FOUND),
109    @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
110    })
111    public Response readNode(
112    @PathParam("workspace") String workspaceName,
113    @PathParam("path") @DefaultValue("/") String path,
114    @QueryParam("depth") @DefaultValue("0") int depth,
115    @QueryParam("excludeNodeTypes") @DefaultValue("") String excludeNodeTypes,
116    @QueryParam("includeMetadata") @DefaultValue("false") boolean includeMetadata) throws RepositoryException {
117   
118  6 final String absPath = StringUtils.defaultIfEmpty(path, "/");
119   
120  6 final Session session = MgnlContext.getJCRSession(workspaceName);
121   
122  6 if (!session.nodeExists(absPath)) {
123  1 return Response.status(Response.Status.NOT_FOUND).build();
124    }
125   
126  5 final Node node = session.getNode(absPath);
127   
128  5 final RepositoryNode response = marshaller.marshallNode(node, depth, splitExcludeNodeTypesString(excludeNodeTypes), includeMetadata);
129   
130  5 log.debug("Returned node [{}]", node.getPath());
131   
132  5 return Response.ok(response).build();
133    }
134   
135    /**
136    * Creates a new node and populates it with the supplied properties. Does not support adding sub nodes. The
137    * submitted node must contain name and type.
138    */
 
139  10 toggle @PUT
140    @Path("/{workspace}{path:(/.+)?}")
141    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
142    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
143    @ApiOperation(value = "Create a node", notes = "Creates a node and adds passed properties")
144    @ApiResponses(value = {
145    @ApiResponse(code = 200, message = STATUS_MESSAGE_OK),
146    @ApiResponse(code = 400, message = STATUS_MESSAGE_BAD_REQUEST),
147    @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
148    @ApiResponse(code = 403, message = STATUS_MESSAGE_ACCESS_DENIED),
149    @ApiResponse(code = 404, message = STATUS_MESSAGE_NODE_NOT_FOUND),
150    @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
151    })
152    public Response createNode(
153    @PathParam("workspace") String workspaceName,
154    @PathParam("path") @DefaultValue("/") String parentPath,
155    RepositoryNode repositoryNode) throws RepositoryException {
156   
157  10 final String parentAbsPath = StringUtils.defaultIfEmpty(parentPath, "/");
158   
159  10 if (StringUtils.isEmpty(repositoryNode.getName())) {
160  1 return Response.status(Response.Status.BAD_REQUEST).build();
161    }
162   
163  9 if (StringUtils.isEmpty(repositoryNode.getType())) {
164  1 return Response.status(Response.Status.BAD_REQUEST).build();
165    }
166   
167  8 if (!StringUtils.isEmpty(repositoryNode.getPath()) && !repositoryNode.getPath().equals(PathUtil.createPath(parentAbsPath, repositoryNode.getName()))) {
168  1 return Response.status(Response.Status.BAD_REQUEST).build();
169    }
170   
171  7 if (repositoryNode.getNodes() != null && !repositoryNode.getNodes().isEmpty()) {
172  1 return Response.status(Response.Status.BAD_REQUEST).build();
173    }
174   
175  6 final Session session = MgnlContext.getJCRSession(workspaceName);
176   
177  6 if (!session.nodeExists(parentAbsPath)) {
178  1 return Response.status(Response.Status.NOT_FOUND).build();
179    }
180   
181  5 final Node parentNode = session.getNode(parentAbsPath);
182   
183  5 if (parentNode.hasNode(repositoryNode.getName())) {
184  1 return Response.status(Response.Status.BAD_REQUEST).build();
185    }
186   
187  4 final Node node = parentNode.addNode(repositoryNode.getName(), repositoryNode.getType());
188   
189  4 if (repositoryNode.getProperties() != null) {
190  4 marshaller.unmarshallProperties(node, repositoryNode.getProperties());
191    }
192   
193  4 synchronized (ExclusiveWrite.getInstance()) {
194  4 session.save();
195    }
196   
197  4 log.debug("Created a new node [{}]", node.getPath());
198   
199  4 return Response.ok().build();
200    }
201   
202    /**
203    * Adds properties to a node. Existing properties are changed if present in the request. Existing properties not
204    * present in the request are not removed.
205    */
 
206  10 toggle @POST
207    @Path("/{workspace}{path:(/.+)?}")
208    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
209    @Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
210    @ApiOperation(value = "Update a node", notes = "Updates a node by adding passed properties")
211    @ApiResponses(value = {
212    @ApiResponse(code = 200, message = STATUS_MESSAGE_OK),
213    @ApiResponse(code = 400, message = STATUS_MESSAGE_BAD_REQUEST),
214    @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
215    @ApiResponse(code = 403, message = STATUS_MESSAGE_ACCESS_DENIED),
216    @ApiResponse(code = 404, message = STATUS_MESSAGE_NODE_NOT_FOUND),
217    @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
218    })
219    public Response updateNode(
220    @PathParam("workspace") String workspaceName,
221    @PathParam("path") @DefaultValue("/") String path,
222    RepositoryNode repositoryNode) throws RepositoryException {
223   
224  10 final String absPath = StringUtils.defaultIfEmpty(path, "/");
225   
226  10 if (repositoryNode.getPath() != null && !StringUtils.equals(absPath, repositoryNode.getPath())) {
227  1 return Response.status(Response.Status.BAD_REQUEST).build();
228    }
229   
230  9 String name = StringUtils.substringAfterLast(absPath, "/");
231  9 if (repositoryNode.getName() != null && !StringUtils.equals(name, repositoryNode.getName())) {
232  1 return Response.status(Response.Status.BAD_REQUEST).build();
233    }
234   
235  8 if (repositoryNode.getNodes() != null && !repositoryNode.getNodes().isEmpty()) {
236  1 return Response.status(Response.Status.BAD_REQUEST).build();
237    }
238   
239  7 final Session session = MgnlContext.getJCRSession(workspaceName);
240   
241  7 if (!session.nodeExists(absPath)) {
242  1 return Response.status(Response.Status.NOT_FOUND).build();
243    }
244   
245  6 final Node node = session.getNode(absPath);
246   
247  6 if (repositoryNode.getType() != null && !repositoryNode.getType().equals(node.getPrimaryNodeType().getName())) {
248  1 return Response.status(Response.Status.BAD_REQUEST).build();
249    }
250   
251  5 if (repositoryNode.getIdentifier() != null && !repositoryNode.getIdentifier().equals(node.getIdentifier())) {
252  1 return Response.status(Response.Status.BAD_REQUEST).build();
253    }
254   
255  4 if (repositoryNode.getProperties() != null) {
256  4 marshaller.unmarshallProperties(node, repositoryNode.getProperties());
257    }
258   
259  4 synchronized (ExclusiveWrite.getInstance()) {
260  4 session.save();
261    }
262   
263  4 log.debug("Updated node [{}]", node.getPath());
264   
265  4 return Response.ok().build();
266    }
267   
268    /**
269    * Delete a node.
270    */
 
271  2 toggle @DELETE
272    @Path("/{workspace}{path:(/.+)?}")
273    @ApiOperation(value = "Delete a node", notes = "Deletes a node")
274    @ApiResponses(value = {
275    @ApiResponse(code = 200, message = STATUS_MESSAGE_OK),
276    @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
277    @ApiResponse(code = 403, message = STATUS_MESSAGE_ACCESS_DENIED),
278    @ApiResponse(code = 404, message = STATUS_MESSAGE_NODE_NOT_FOUND),
279    @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
280    })
281    public Response deleteNode(
282    @PathParam("workspace") String workspaceName,
283    @PathParam("path") @DefaultValue("/") String path) throws RepositoryException {
284   
285  2 final String absPath = StringUtils.defaultIfEmpty(path, "/");
286   
287  2 final Session session = MgnlContext.getJCRSession(workspaceName);
288   
289  2 if (!session.nodeExists(absPath)) {
290  1 return Response.status(Response.Status.NOT_FOUND).build();
291    }
292   
293  1 final Node node = session.getNode(absPath);
294   
295  1 node.remove();
296   
297  1 synchronized (ExclusiveWrite.getInstance()) {
298  1 session.save();
299    }
300   
301  1 log.debug("Deleted node [{}]", absPath);
302   
303  1 return Response.ok().build();
304    }
305   
 
306  11 toggle protected List<String> splitExcludeNodeTypesString(String excludes) {
307  11 List<String> excludeList = new ArrayList<String>();
308   
309  11 if (excludes != null) {
310  10 excludes = StringUtils.replace(excludes, " ", "");
311  10 excludeList = Arrays.asList(StringUtils.split(excludes, ","));
312    }
313   
314  11 return excludeList;
315    }
316    }