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