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