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.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
75
76
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
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
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
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
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 }