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.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
78
79
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
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
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
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
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 }