View Javadoc
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.property.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.v1.RepositoryMarshaller;
41  import info.magnolia.rest.service.node.v1.RepositoryProperty;
42  import info.magnolia.rest.service.property.definition.PropertyEndpointDefinition;
43  
44  import java.util.List;
45  
46  import javax.inject.Inject;
47  import javax.jcr.Node;
48  import javax.jcr.Property;
49  import javax.jcr.PropertyType;
50  import javax.jcr.RepositoryException;
51  import javax.jcr.Session;
52  import javax.ws.rs.DELETE;
53  import javax.ws.rs.DefaultValue;
54  import javax.ws.rs.GET;
55  import javax.ws.rs.POST;
56  import javax.ws.rs.PUT;
57  import javax.ws.rs.Path;
58  import javax.ws.rs.PathParam;
59  import javax.ws.rs.Produces;
60  import javax.ws.rs.QueryParam;
61  import javax.ws.rs.core.MediaType;
62  import javax.ws.rs.core.Response;
63  
64  import org.apache.commons.lang.StringUtils;
65  import org.slf4j.Logger;
66  import org.slf4j.LoggerFactory;
67  
68  import com.wordnik.swagger.annotations.Api;
69  import com.wordnik.swagger.annotations.ApiOperation;
70  import com.wordnik.swagger.annotations.ApiResponse;
71  import com.wordnik.swagger.annotations.ApiResponses;
72  
73  /**
74   * Endpoint for accessing and manipulating properties.
75   *
76   * @param <D> The endpoint definition
77   */
78  @Api(value = "/properties/v1", description = "The properties API")
79  @Path("/properties/v1")
80  public class PropertyEndpoint<D extends PropertyEndpointDefinition> extends AbstractEndpoint<D> {
81  
82      private static final String STATUS_MESSAGE_OK = "OK";
83      private static final String STATUS_MESSAGE_PROPERTY_ALREADY_EXISTS = "Property already exists";
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_PROPERTY_NOT_FOUND = "Property not found";
88      private static final String STATUS_MESSAGE_ERROR_OCCURRED = "Error occurred";
89  
90      private final Logger log = LoggerFactory.getLogger(getClass());
91  
92      private RepositoryMarshaller marshaller = new RepositoryMarshaller();
93  
94      @Inject
95      public PropertyEndpoint(final D endpointDefinition) {
96          super(endpointDefinition);
97      }
98  
99      /**
100      * Returns the string representation of a property's value.
101      */
102     @GET
103     @Path("/{workspace}{path:(/.+)?}")
104     @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
105     @ApiOperation(value = "Get a property from a node", notes = "Reads a property from a node")
106     @ApiResponses(value = {
107             @ApiResponse(code = 200, message = STATUS_MESSAGE_OK, response = RepositoryProperty.class),
108             @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
109             @ApiResponse(code = 404, message = STATUS_MESSAGE_PROPERTY_NOT_FOUND),
110             @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
111     })
112     public Response readProperty(
113             @PathParam("workspace") String workspaceName,
114             @PathParam("path") @DefaultValue("/") String path) throws RepositoryException {
115 
116         final String absPath = StringUtils.defaultIfEmpty(path, "/");
117 
118         final Session session = MgnlContext.getJCRSession(workspaceName);
119 
120         if (!session.propertyExists(absPath)) {
121             return Response.status(Response.Status.NOT_FOUND).build();
122         }
123 
124         final Property property = session.getProperty(absPath);
125 
126         RepositoryProperty response = marshaller.marshallProperty(property);
127 
128         log.debug("Returned property [{}]", property.getParent().getPath());
129 
130         return Response.ok().entity(response).build();
131     }
132 
133     /**
134      * Adds a property to a node.
135      */
136     @PUT
137     @Path("/{workspace}{path:(/.+)?}")
138     @ApiOperation(value = "Add property on a node", notes = "Adds a property on a node")
139     @ApiResponses(value = {
140             @ApiResponse(code = 200, message = STATUS_MESSAGE_OK),
141             @ApiResponse(code = 400, message = STATUS_MESSAGE_PROPERTY_ALREADY_EXISTS),
142             @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
143             @ApiResponse(code = 403, message = STATUS_MESSAGE_ACCESS_DENIED),
144             @ApiResponse(code = 404, message = STATUS_MESSAGE_NODE_NOT_FOUND),
145             @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
146     })
147     public Response createProperty(
148             @PathParam("workspace") String workspaceName,
149             @PathParam("path") @DefaultValue("/") String parentPath,
150             @QueryParam("name") String name,
151             @QueryParam("value") List<String> valueStrings,
152             @QueryParam("type") @DefaultValue(PropertyType.TYPENAME_STRING) String typeString,
153             @QueryParam("multiple") @DefaultValue("false") boolean multiple) throws RepositoryException {
154 
155         if (!multiple && valueStrings.size() != 1) {
156             return Response.status(Response.Status.BAD_REQUEST).build();
157         }
158 
159         try {
160             PropertyType.valueFromName(typeString);
161         } catch (IllegalArgumentException e) {
162             return Response.status(Response.Status.BAD_REQUEST).build();
163         }
164 
165         final String parentAbsPath = StringUtils.defaultIfEmpty(parentPath, "/");
166 
167         final Session session = MgnlContext.getJCRSession(workspaceName);
168 
169         if (!session.nodeExists(parentAbsPath)) {
170             return Response.status(Response.Status.NOT_FOUND).build();
171         }
172 
173         final Node node = session.getNode(parentAbsPath);
174 
175         if (node.hasProperty(name)) {
176             return Response.status(Response.Status.BAD_REQUEST).build();
177         }
178 
179         RepositoryProperty property = new RepositoryProperty();
180         property.setName(name);
181         property.setMultiple(multiple);
182         property.setType(typeString);
183         property.getValues().addAll(valueStrings);
184 
185         marshaller.unmarshallProperty(node, property);
186 
187         synchronized (ExclusiveWrite.getInstance()) {
188             session.save();
189         }
190 
191         log.debug("Added property [{}]", PathUtil.createPath(parentAbsPath, name));
192 
193         return Response.ok().build();
194     }
195 
196     /**
197      * Sets the value of an existing property.
198      */
199     @POST
200     @Path("/{workspace}{path:(/.+)?}")
201     @ApiOperation(value = "Update property on a node", notes = "Updates a property on a node")
202     @ApiResponses(value = {
203             @ApiResponse(code = 200, message = STATUS_MESSAGE_OK),
204             @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
205             @ApiResponse(code = 403, message = STATUS_MESSAGE_ACCESS_DENIED),
206             @ApiResponse(code = 404, message = STATUS_MESSAGE_PROPERTY_NOT_FOUND),
207             @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
208     })
209     public Response updateProperty(
210             @PathParam("workspace") String workspaceName,
211             @PathParam("path") @DefaultValue("/") String path,
212             @QueryParam("value") List<String> valueStrings,
213             @QueryParam("type") @DefaultValue(PropertyType.TYPENAME_STRING) String typeString,
214             @QueryParam("multiple") @DefaultValue("false") boolean multiple) throws RepositoryException {
215 
216         if (!multiple && valueStrings.size() != 1) {
217             return Response.status(Response.Status.BAD_REQUEST).build();
218         }
219 
220         try {
221             PropertyType.valueFromName(typeString);
222         } catch (IllegalArgumentException e) {
223             return Response.status(Response.Status.BAD_REQUEST).build();
224         }
225 
226         final String absPath = StringUtils.defaultIfEmpty(path, "/");
227         final String name = StringUtils.substringAfterLast(absPath, "/");
228         String parentPath = StringUtils.substringBeforeLast(absPath, "/");
229         if (parentPath.isEmpty()) {
230             parentPath = "/";
231         }
232 
233         final Session session = MgnlContext.getJCRSession(workspaceName);
234 
235         if (!session.propertyExists(absPath)) {
236             return Response.status(Response.Status.NOT_FOUND).build();
237         }
238 
239         final Node node = session.getNode(parentPath);
240 
241         RepositoryProperty property = new RepositoryProperty();
242         property.setName(name);
243         property.setMultiple(multiple);
244         property.setType(typeString);
245         property.getValues().addAll(valueStrings);
246 
247         marshaller.unmarshallProperty(node, property);
248 
249         synchronized (ExclusiveWrite.getInstance()) {
250             session.save();
251         }
252 
253         log.debug("Updated property [{}]", path);
254 
255         return Response.ok().build();
256     }
257 
258     /**
259      * Deletes a property.
260      */
261     @DELETE
262     @Path("/{workspace}{path:(/.+)?}")
263     @ApiOperation(value = "Delete a property", notes = "Deletes a property")
264     @ApiResponses(value = {
265             @ApiResponse(code = 200, message = STATUS_MESSAGE_OK),
266             @ApiResponse(code = 401, message = STATUS_MESSAGE_UNAUTHORIZED),
267             @ApiResponse(code = 403, message = STATUS_MESSAGE_ACCESS_DENIED),
268             @ApiResponse(code = 404, message = STATUS_MESSAGE_PROPERTY_NOT_FOUND),
269             @ApiResponse(code = 500, message = STATUS_MESSAGE_ERROR_OCCURRED)
270     })
271     public Response deleteProperty(
272             @PathParam("workspace") String workspaceName,
273             @PathParam("path") @DefaultValue("/") String path) throws RepositoryException {
274 
275         final String absPath = StringUtils.defaultIfEmpty(path, "/");
276 
277         final Session session = MgnlContext.getJCRSession(workspaceName);
278 
279         if (!session.propertyExists(absPath)) {
280             return Response.status(Response.Status.NOT_FOUND).build();
281         }
282 
283         final Property property = session.getProperty(absPath);
284 
285         property.remove();
286 
287         synchronized (ExclusiveWrite.getInstance()) {
288             session.save();
289         }
290 
291         log.debug("Deleted property [{}]", absPath);
292 
293         return Response.ok().build();
294     }
295 }