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 }