001 /*
002 * JBoss DNA (http://www.jboss.org/dna)
003 * See the COPYRIGHT.txt file distributed with this work for information
004 * regarding copyright ownership. Some portions may be licensed
005 * to Red Hat, Inc. under one or more contributor license agreements.
006 * See the AUTHORS.txt file in the distribution for a full listing of
007 * individual contributors.
008 *
009 * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010 * is licensed to you under the terms of the GNU Lesser General Public License as
011 * published by the Free Software Foundation; either version 2.1 of
012 * the License, or (at your option) any later version.
013 *
014 * JBoss DNA is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017 * Lesser General Public License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this software; if not, write to the Free
021 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023 */
024 package org.jboss.dna.web.jcr.rest;
025
026 import java.io.IOException;
027 import java.util.ArrayList;
028 import java.util.Arrays;
029 import java.util.HashMap;
030 import java.util.HashSet;
031 import java.util.Iterator;
032 import java.util.List;
033 import java.util.Map;
034 import java.util.Set;
035 import javax.jcr.Item;
036 import javax.jcr.Node;
037 import javax.jcr.NodeIterator;
038 import javax.jcr.PathNotFoundException;
039 import javax.jcr.Property;
040 import javax.jcr.PropertyIterator;
041 import javax.jcr.RepositoryException;
042 import javax.jcr.Session;
043 import javax.jcr.Value;
044 import javax.jcr.nodetype.NodeType;
045 import javax.jcr.nodetype.PropertyDefinition;
046 import javax.servlet.http.HttpServletRequest;
047 import javax.ws.rs.Consumes;
048 import javax.ws.rs.DELETE;
049 import javax.ws.rs.DefaultValue;
050 import javax.ws.rs.GET;
051 import javax.ws.rs.POST;
052 import javax.ws.rs.PUT;
053 import javax.ws.rs.Path;
054 import javax.ws.rs.PathParam;
055 import javax.ws.rs.Produces;
056 import javax.ws.rs.QueryParam;
057 import javax.ws.rs.core.Context;
058 import javax.ws.rs.core.Response;
059 import javax.ws.rs.core.Response.Status;
060 import javax.ws.rs.ext.ExceptionMapper;
061 import javax.ws.rs.ext.Provider;
062 import net.jcip.annotations.Immutable;
063 import org.codehaus.jettison.json.JSONArray;
064 import org.codehaus.jettison.json.JSONException;
065 import org.codehaus.jettison.json.JSONObject;
066 import org.jboss.dna.common.text.UrlEncoder;
067 import org.jboss.dna.web.jcr.rest.model.RepositoryEntry;
068 import org.jboss.dna.web.jcr.rest.model.WorkspaceEntry;
069 import org.jboss.resteasy.spi.NotFoundException;
070 import org.jboss.resteasy.spi.UnauthorizedException;
071
072 /**
073 * RESTEasy handler to provide the JCR resources at the URIs below. Please note that these URIs assume a context of {@code
074 * /resources} for the web application.
075 * <table border="1">
076 * <tr>
077 * <th>URI Pattern</th>
078 * <th>Description</th>
079 * <th>Supported Methods</th>
080 * </tr>
081 * <tr>
082 * <td>/resources</td>
083 * <td>returns a list of accessible repositories</td>
084 * <td>GET</td>
085 * </tr>
086 * <tr>
087 * <td>/resources/{repositoryName}</td>
088 * <td>returns a list of accessible workspaces within that repository</td>
089 * <td>GET</td>
090 * </tr>
091 * <tr>
092 * <td>/resources/{repositoryName}/{workspaceName}</td>
093 * <td>returns a list of operations within the workspace</td>
094 * <td>GET</td>
095 * </tr>
096 * <tr>
097 * <td>/resources/{repositoryName}/{workspaceName}/item/{path}</td>
098 * <td>accesses the item (node or property) at the path</td>
099 * <td>ALL</td>
100 * </tr>
101 * </table>
102 */
103 @Immutable
104 @Path( "/" )
105 public class JcrResources {
106
107 private static final UrlEncoder URL_ENCODER = new UrlEncoder();
108
109 private static final String PROPERTIES_HOLDER = "properties";
110 private static final String CHILD_NODE_HOLDER = "children";
111
112 private static final String PRIMARY_TYPE_PROPERTY = "jcr:primaryType";
113 private static final String MIXIN_TYPES_PROPERTY = "jcr:mixinTypes";
114
115 /** Name to be used when the repository name is empty string as {@code "//"} is not a valid path. */
116 public static final String EMPTY_REPOSITORY_NAME = "<default>";
117 /** Name to be used when the workspace name is empty string as {@code "//"} is not a valid path. */
118 public static final String EMPTY_WORKSPACE_NAME = "<default>";
119
120 /**
121 * Returns an active session for the given workspace name in the named repository.
122 *
123 * @param request the servlet request; may not be null or unauthenticated
124 * @param rawRepositoryName the URL-encoded name of the repository in which the session is created
125 * @param rawWorkspaceName the URL-encoded name of the workspace to which the session should be connected
126 * @return an active session with the given workspace in the named repository
127 * @throws RepositoryException if any other error occurs
128 */
129 private Session getSession( HttpServletRequest request,
130 String rawRepositoryName,
131 String rawWorkspaceName ) throws NotFoundException, RepositoryException {
132 assert request != null;
133 assert request.getUserPrincipal() != null : "Request must be authorized";
134
135 // Sanity check
136 if (request.getUserPrincipal() == null) {
137 throw new UnauthorizedException("Client is not authorized");
138 }
139
140 return RepositoryFactory.getSession(request, repositoryNameFor(rawRepositoryName), workspaceNameFor(rawWorkspaceName));
141 }
142
143 /**
144 * Returns the list of JCR repositories available on this server
145 *
146 * @param request the servlet request; may not be null
147 * @return the list of JCR repositories available on this server
148 */
149 @GET
150 @Path( "/" )
151 @Produces( "application/json" )
152 public Map<String, RepositoryEntry> getRepositories( @Context HttpServletRequest request ) {
153 assert request != null;
154
155 Map<String, RepositoryEntry> repositories = new HashMap<String, RepositoryEntry>();
156
157 for (String name : RepositoryFactory.getJcrRepositoryNames()) {
158 if (name.trim().length() == 0) {
159 name = EMPTY_REPOSITORY_NAME;
160 }
161 name = URL_ENCODER.encode(name);
162 repositories.put(name, new RepositoryEntry(request.getContextPath(), name));
163 }
164
165 return repositories;
166 }
167
168 /**
169 * Returns the list of workspaces available to this user within the named repository.
170 *
171 * @param rawRepositoryName the name of the repository; may not be null
172 * @param request the servlet request; may not be null
173 * @return the list of workspaces available to this user within the named repository.
174 * @throws IOException if the given repository name does not map to any repositories and there is an error writing the error
175 * code to the response.
176 * @throws RepositoryException if there is any other error accessing the list of available workspaces for the repository
177 */
178 @GET
179 @Path( "/{repositoryName}" )
180 @Produces( "application/json" )
181 public Map<String, WorkspaceEntry> getWorkspaces( @Context HttpServletRequest request,
182 @PathParam( "repositoryName" ) String rawRepositoryName )
183 throws RepositoryException, IOException {
184
185 assert request != null;
186 assert rawRepositoryName != null;
187
188 Map<String, WorkspaceEntry> workspaces = new HashMap<String, WorkspaceEntry>();
189
190 Session session = getSession(request, rawRepositoryName, null);
191 rawRepositoryName = URL_ENCODER.encode(rawRepositoryName);
192
193 for (String name : session.getWorkspace().getAccessibleWorkspaceNames()) {
194 if (name.trim().length() == 0) {
195 name = EMPTY_WORKSPACE_NAME;
196 }
197 name = URL_ENCODER.encode(name);
198 workspaces.put(name, new WorkspaceEntry(request.getContextPath(), rawRepositoryName, name));
199 }
200
201 return workspaces;
202 }
203
204 /**
205 * Handles GET requests for an item in a workspace.
206 *
207 * @param request the servlet request; may not be null or unauthenticated
208 * @param rawRepositoryName the URL-encoded repository name
209 * @param rawWorkspaceName the URL-encoded workspace name
210 * @param path the path to the item
211 * @param depth the depth of the node graph that should be returned if {@code path} refers to a node. @{code 0} means return
212 * the requested node only. A negative value indicates that the full subgraph under the node should be returned. This
213 * parameter defaults to {@code 0} and is ignored if {@code path} refers to a property.
214 * @return the JSON-encoded version of the item (and, if the item is a node, its subgraph, depending on the value of {@code
215 * depth})
216 * @throws NotFoundException if the named repository does not exists, the named workspace does not exist, or the user does not
217 * have access to the named workspace
218 * @throws JSONException if there is an error encoding the node
219 * @throws UnauthorizedException if the given login information is invalid
220 * @throws RepositoryException if any other error occurs
221 * @see #EMPTY_REPOSITORY_NAME
222 * @see #EMPTY_WORKSPACE_NAME
223 * @see Session#getItem(String)
224 */
225 @GET
226 @Path( "/{repositoryName}/{workspaceName}/items{path:.*}" )
227 @Produces( "application/json" )
228 public String getItem( @Context HttpServletRequest request,
229 @PathParam( "repositoryName" ) String rawRepositoryName,
230 @PathParam( "workspaceName" ) String rawWorkspaceName,
231 @PathParam( "path" ) String path,
232 @QueryParam( "dna:depth" ) @DefaultValue( "0" ) int depth )
233 throws JSONException, UnauthorizedException, RepositoryException {
234 assert path != null;
235 assert rawRepositoryName != null;
236 assert rawWorkspaceName != null;
237
238 Session session = getSession(request, rawRepositoryName, rawWorkspaceName);
239 Item item;
240
241 if ("/".equals(path) || "".equals(path)) {
242 item = session.getRootNode();
243 } else {
244 try {
245 item = session.getItem(path);
246 } catch (PathNotFoundException pnfe) {
247 throw new NotFoundException(pnfe.getMessage(), pnfe);
248 }
249 }
250
251 if (item instanceof Node) {
252 return jsonFor((Node)item, depth).toString();
253 }
254 return jsonFor((Property)item);
255 }
256
257 /**
258 * Returns the JSON-encoded version of the given property. If the property is single-valued, the returned string is {@code
259 * property.getValue().getString()} encoded as a JSON string. If the property is multi-valued with {@code N} values, this
260 * method returns a JSON array containing {@code property.getValues()[N].getString()} for all values of {@code N}.
261 *
262 * @param property the property to be encoded
263 * @return the JSON-encoded version of the property
264 * @throws RepositoryException if an error occurs accessing the property, its values, or its definition.
265 * @see Property#getDefinition()
266 * @see PropertyDefinition#isMultiple()
267 */
268 private String jsonFor( Property property ) throws RepositoryException {
269 if (property.getDefinition().isMultiple()) {
270 Value[] values = property.getValues();
271 List<String> list = new ArrayList<String>(values.length);
272 for (int i = 0; i < values.length; i++) {
273 list.add(values[i].getString());
274 }
275 return new JSONArray(list).toString();
276 }
277 return JSONObject.quote(property.getValue().getString());
278 }
279
280 /**
281 * Recursively returns the JSON-encoding of a node and its children to depth {@code toDepth}.
282 *
283 * @param node the node to be encoded
284 * @param toDepth the depth to which the recursion should extend; {@code 0} means no further recursion should occur.
285 * @return the JSON-encoding of a node and its children to depth {@code toDepth}.
286 * @throws JSONException if there is an error encoding the node
287 * @throws RepositoryException if any other error occurs
288 */
289 private JSONObject jsonFor( Node node,
290 int toDepth ) throws JSONException, RepositoryException {
291 JSONObject jsonNode = new JSONObject();
292
293 JSONObject properties = new JSONObject();
294
295 for (PropertyIterator iter = node.getProperties(); iter.hasNext();) {
296 Property prop = iter.nextProperty();
297 String propName = prop.getName();
298
299 if (prop.getDefinition().isMultiple()) {
300 Value[] values = prop.getValues();
301 JSONArray array = new JSONArray();
302 for (int i = 0; i < values.length; i++) {
303 array.put(values[i].getString());
304 }
305 properties.put(propName, array);
306
307 } else {
308 properties.put(propName, prop.getValue().getString());
309 }
310
311 }
312 if (properties.length() > 0) {
313 jsonNode.put(PROPERTIES_HOLDER, properties);
314 }
315
316 if (toDepth == 0) {
317 List<String> children = new ArrayList<String>();
318
319 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
320 Node child = iter.nextNode();
321
322 children.add(child.getName());
323 }
324
325 if (children.size() > 0) {
326 jsonNode.put(CHILD_NODE_HOLDER, new JSONArray(children));
327 }
328 } else {
329 JSONObject children = new JSONObject();
330
331 for (NodeIterator iter = node.getNodes(); iter.hasNext();) {
332 Node child = iter.nextNode();
333
334 children.put(child.getName(), jsonFor(child, toDepth - 1));
335 }
336
337 if (children.length() > 0) {
338 jsonNode.put(CHILD_NODE_HOLDER, children);
339 }
340 }
341
342 return jsonNode;
343 }
344
345 /**
346 * Adds the content of the request as a node (or subtree of nodes) at the location specified by {@code path}.
347 * <p>
348 * The primary type and mixin type(s) may optionally be specified through the {@code jcr:primaryType} and {@code
349 * jcr:mixinTypes} properties.
350 * </p>
351 *
352 * @param request the servlet request; may not be null or unauthenticated
353 * @param rawRepositoryName the URL-encoded repository name
354 * @param rawWorkspaceName the URL-encoded workspace name
355 * @param path the path to the item
356 * @param requestContent the JSON-encoded representation of the node or nodes to be added
357 * @return the JSON-encoded representation of the node or nodes that were added. This will differ from {@code requestContent}
358 * in that auto-created and protected properties (e.g., jcr:uuid) will be populated.
359 * @throws NotFoundException if the parent of the item to be added does not exist
360 * @throws UnauthorizedException if the user does not have the access required to create the node at this path
361 * @throws JSONException if there is an error encoding the node
362 * @throws RepositoryException if any other error occurs
363 */
364 @POST
365 @Path( "/{repositoryName}/{workspaceName}/items/{path:.*}" )
366 @Consumes( "application/json" )
367 public Response postItem( @Context HttpServletRequest request,
368 @PathParam( "repositoryName" ) String rawRepositoryName,
369 @PathParam( "workspaceName" ) String rawWorkspaceName,
370 @PathParam( "path" ) String path,
371 String requestContent )
372 throws NotFoundException, UnauthorizedException, RepositoryException, JSONException {
373
374 assert rawRepositoryName != null;
375 assert rawWorkspaceName != null;
376 assert path != null;
377 JSONObject body = new JSONObject(requestContent);
378
379 int lastSlashInd = path.lastIndexOf('/');
380 String parentPath = lastSlashInd == -1 ? "/" : "/" + path.substring(0, lastSlashInd);
381 String newNodeName = lastSlashInd == -1 ? path : path.substring(lastSlashInd + 1);
382
383 Session session = getSession(request, rawRepositoryName, rawWorkspaceName);
384
385 Node parentNode = (Node)session.getItem(parentPath);
386
387 Node newNode = addNode(parentNode, newNodeName, body);
388
389 session.save();
390
391 String json = jsonFor(newNode, -1).toString();
392 return Response.status(Status.CREATED).entity(json).build();
393 }
394
395 /**
396 * Adds the node described by {@code jsonNode} with name {@code nodeName} to the existing node {@code parentNode}.
397 *
398 * @param parentNode the parent of the node to be added
399 * @param nodeName the name of the node to be added
400 * @param jsonNode the JSON-encoded representation of the node or nodes to be added.
401 * @return the JSON-encoded representation of the node or nodes that were added. This will differ from {@code requestContent}
402 * in that auto-created and protected properties (e.g., jcr:uuid) will be populated.
403 * @throws JSONException if there is an error encoding the node
404 * @throws RepositoryException if any other error occurs
405 */
406 private Node addNode( Node parentNode,
407 String nodeName,
408 JSONObject jsonNode ) throws RepositoryException, JSONException {
409 Node newNode;
410
411 JSONObject properties = jsonNode.has(PROPERTIES_HOLDER) ? jsonNode.getJSONObject(PROPERTIES_HOLDER) : new JSONObject();
412
413 if (properties.has(PRIMARY_TYPE_PROPERTY)) {
414 String primaryType = properties.getString(PRIMARY_TYPE_PROPERTY);
415 newNode = parentNode.addNode(nodeName, primaryType);
416 } else {
417 newNode = parentNode.addNode(nodeName);
418 }
419
420 if (properties.has(MIXIN_TYPES_PROPERTY)) {
421 Object rawMixinTypes = properties.get(MIXIN_TYPES_PROPERTY);
422
423 if (rawMixinTypes instanceof JSONArray) {
424 JSONArray mixinTypes = (JSONArray)rawMixinTypes;
425 for (int i = 0; i < mixinTypes.length(); i++) {
426 newNode.addMixin(mixinTypes.getString(i));
427 }
428
429 } else {
430 newNode.addMixin(rawMixinTypes.toString());
431
432 }
433 }
434
435 for (Iterator<?> iter = properties.keys(); iter.hasNext();) {
436 String key = (String)iter.next();
437
438 if (PRIMARY_TYPE_PROPERTY.equals(key)) continue;
439 if (MIXIN_TYPES_PROPERTY.equals(key)) continue;
440 setPropertyOnNode(newNode, key, properties.get(key));
441 }
442
443 if (jsonNode.has(CHILD_NODE_HOLDER)) {
444 JSONObject children = jsonNode.getJSONObject(CHILD_NODE_HOLDER);
445
446 for (Iterator<?> iter = children.keys(); iter.hasNext();) {
447 String childName = (String)iter.next();
448 JSONObject child = children.getJSONObject(childName);
449
450 addNode(newNode, childName, child);
451 }
452 }
453
454 return newNode;
455 }
456
457 /**
458 * Sets the named property on the given node. This method expects {@code value} to be either a JSON string or a JSON array of
459 * JSON strings. If {@code value} is a JSON array, {@code Node#setProperty(String, String[]) the multi-valued property setter}
460 * will be used.
461 *
462 * @param node the node on which the property is to be set
463 * @param propName the name of the property to set
464 * @param value the JSON-encoded values to be set
465 * @throws RepositoryException if there is an error setting the property
466 * @throws JSONException if {@code value} cannot be decoded
467 */
468 private void setPropertyOnNode( Node node,
469 String propName,
470 Object value ) throws RepositoryException, JSONException {
471 String[] values;
472 if (value instanceof JSONArray) {
473 JSONArray jsonValues = (JSONArray)value;
474 values = new String[jsonValues.length()];
475
476 for (int i = 0; i < values.length; i++) {
477 values[i] = jsonValues.getString(i);
478 }
479 } else {
480 values = new String[] { (String)value };
481 }
482
483 if (propName.equals(JcrResources.MIXIN_TYPES_PROPERTY)) {
484 Set<String> toBeMixins = new HashSet<String>(Arrays.asList(values));
485 Set<String> asIsMixins = new HashSet<String>();
486
487 for (NodeType nodeType : node.getMixinNodeTypes()) {
488 asIsMixins.add(nodeType.getName());
489 }
490
491 Set<String> mixinsToAdd = new HashSet<String>(toBeMixins);
492 mixinsToAdd.removeAll(asIsMixins);
493 asIsMixins.removeAll(toBeMixins);
494
495 for (String nodeType : mixinsToAdd) {
496 node.addMixin(nodeType);
497 }
498
499 for (String nodeType : asIsMixins) {
500 node.removeMixin(nodeType);
501 }
502 } else {
503 if (values.length == 1) {
504 node.setProperty(propName, values[0]);
505
506 }
507 else {
508 node.setProperty(propName, values);
509 }
510 }
511 }
512
513 /**
514 * Deletes the item at {@code path}.
515 *
516 * @param request the servlet request; may not be null or unauthenticated
517 * @param rawRepositoryName the URL-encoded repository name
518 * @param rawWorkspaceName the URL-encoded workspace name
519 * @param path the path to the item
520 * @throws NotFoundException if no item exists at {@code path}
521 * @throws UnauthorizedException if the user does not have the access required to delete the item at this path
522 * @throws RepositoryException if any other error occurs
523 */
524 @DELETE
525 @Path( "/{repositoryName}/{workspaceName}/items{path:.*}" )
526 @Consumes( "application/json" )
527 public void deleteItem( @Context HttpServletRequest request,
528 @PathParam( "repositoryName" ) String rawRepositoryName,
529 @PathParam( "workspaceName" ) String rawWorkspaceName,
530 @PathParam( "path" ) String path )
531 throws NotFoundException, UnauthorizedException, RepositoryException {
532
533 assert rawRepositoryName != null;
534 assert rawWorkspaceName != null;
535 assert path != null;
536
537 Session session = getSession(request, rawRepositoryName, rawWorkspaceName);
538
539 Item item;
540 try {
541 item = session.getItem(path);
542 } catch (PathNotFoundException pnfe) {
543 throw new NotFoundException(pnfe.getMessage(), pnfe);
544 }
545 item.remove();
546 session.save();
547 }
548
549 /**
550 * Updates the properties at the path.
551 * <p>
552 * If path points to a property, this method expects the request content to be either a JSON array or a JSON string. The array
553 * or string will become the values or value of the property. If path points to a node, this method expects the request
554 * content to be a JSON object. The keys of the objects correspond to property names that will be set and the values for the
555 * keys correspond to the values that will be set on the properties.
556 * </p>
557 *
558 * @param request the servlet request; may not be null or unauthenticated
559 * @param rawRepositoryName the URL-encoded repository name
560 * @param rawWorkspaceName the URL-encoded workspace name
561 * @param path the path to the item
562 * @param requestContent the JSON-encoded representation of the values and, possibly, properties to be set
563 * @return the JSON-encoded representation of the node on which the property or properties were set.
564 * @throws NotFoundException if the parent of the item to be added does not exist
565 * @throws UnauthorizedException if the user does not have the access required to create the node at this path
566 * @throws JSONException if there is an error encoding the node
567 * @throws RepositoryException if any other error occurs
568 */
569 @PUT
570 @Path( "/{repositoryName}/{workspaceName}/items{path:.*}" )
571 @Consumes( "application/json" )
572 public String putItem( @Context HttpServletRequest request,
573 @PathParam( "repositoryName" ) String rawRepositoryName,
574 @PathParam( "workspaceName" ) String rawWorkspaceName,
575 @PathParam( "path" ) String path,
576 String requestContent ) throws UnauthorizedException, JSONException, RepositoryException {
577
578 assert path != null;
579 assert rawRepositoryName != null;
580 assert rawWorkspaceName != null;
581
582 Session session = getSession(request, rawRepositoryName, rawWorkspaceName);
583 Node node;
584 Item item;
585 if ("".equals(path) || "/".equals(path)) {
586 item = session.getRootNode();
587 } else {
588 try {
589 item = session.getItem(path);
590 } catch (PathNotFoundException pnfe) {
591 throw new NotFoundException(pnfe.getMessage(), pnfe);
592 }
593 }
594
595 if (item instanceof Node) {
596 JSONObject properties = new JSONObject(requestContent);
597 node = (Node)item;
598
599 for (Iterator<?> iter = properties.keys(); iter.hasNext();) {
600 String key = (String)iter.next();
601
602 setPropertyOnNode(node, key, properties.get(key));
603 }
604
605 } else {
606 /*
607 * The incoming content should be a JSON string or a JSON array. Wrap it into an object so it can be parsed more easily
608 */
609
610 JSONObject properties = new JSONObject("{ \"value\": " + requestContent + "}");
611 Property property = (Property)item;
612 node = property.getParent();
613
614 setPropertyOnNode(node, property.getName(), properties.get("value"));
615 }
616 node.save();
617 return jsonFor(node, 0).toString();
618 }
619
620 private String workspaceNameFor( String rawWorkspaceName ) {
621 String workspaceName = URL_ENCODER.decode(rawWorkspaceName);
622
623 if (EMPTY_WORKSPACE_NAME.equals(workspaceName)) {
624 workspaceName = "";
625 }
626
627 return workspaceName;
628 }
629
630 private String repositoryNameFor( String rawRepositoryName ) {
631 String repositoryName = URL_ENCODER.decode(rawRepositoryName);
632
633 if (EMPTY_REPOSITORY_NAME.equals(repositoryName)) {
634 repositoryName = "";
635 }
636
637 return repositoryName;
638 }
639
640 @Provider
641 public static class NotFoundExceptionMapper implements ExceptionMapper<NotFoundException> {
642
643 public Response toResponse( NotFoundException exception ) {
644 return Response.status(Status.NOT_FOUND).entity(exception.getMessage()).build();
645 }
646
647 }
648
649 @Provider
650 public static class JSONExceptionMapper implements ExceptionMapper<JSONException> {
651
652 public Response toResponse( JSONException exception ) {
653 return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
654 }
655
656 }
657
658 @Provider
659 public static class RepositoryExceptionMapper implements ExceptionMapper<RepositoryException> {
660
661 public Response toResponse( RepositoryException exception ) {
662 /*
663 * This error code is murky - the request must have been syntactically valid to get to
664 * the JCR operations, but there isn't an HTTP status code for "semantically invalid."
665 */
666 return Response.status(Status.BAD_REQUEST).entity(exception.getMessage()).build();
667 }
668
669 }
670
671 }