Clover icon

Magnolia REST Services 1.1

  1. Project Clover database Thu Aug 13 2015 19:10: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
315
204
30
0.44
11.33
6
5

Classes

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