View Javadoc

1   /**
2    * Copyright (c) 2002-2011 "Neo Technology,"
3    * Network Engine for Objects in Lund AB [http://neotechnology.com]
4    *
5    * This file is part of Neo4j.
6    *
7    * Neo4j is free software: you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation, either version 3 of the License, or
10   * (at your option) any later version.
11   *
12   * This program is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19   */
20  package org.neo4j.server.rest.web;
21  
22  import java.util.Collections;
23  import java.util.LinkedHashSet;
24  import java.util.Map;
25  
26  import javax.ws.rs.*;
27  import javax.ws.rs.core.Context;
28  import javax.ws.rs.core.MediaType;
29  import javax.ws.rs.core.Response;
30  import javax.ws.rs.core.UriInfo;
31  
32  import org.neo4j.graphdb.NotFoundException;
33  import org.neo4j.server.database.Database;
34  import org.neo4j.server.rest.domain.EndNodeNotFoundException;
35  import org.neo4j.server.rest.domain.StartNodeNotFoundException;
36  import org.neo4j.server.rest.domain.StartNodeSameAsEndNodeException;
37  import org.neo4j.server.rest.domain.TraverserReturnType;
38  import org.neo4j.server.rest.repr.BadInputException;
39  import org.neo4j.server.rest.repr.InputFormat;
40  import org.neo4j.server.rest.repr.OutputFormat;
41  import org.neo4j.server.rest.repr.PropertiesRepresentation;
42  import org.neo4j.server.rest.web.DatabaseActions.RelationshipDirection;
43  
44  @Path("/")
45  public class RestfulGraphDatabase {
46      @SuppressWarnings("serial")
47      public static class AmpersandSeparatedCollection extends LinkedHashSet<String> {
48          public AmpersandSeparatedCollection(String path) {
49              for (String e : path.split("&")) {
50                  if (e.trim().length() > 0) {
51                      add(e);
52                  }
53              }
54          }
55      }
56  
57      private static final String PATH_NODES = "node";
58      private static final String PATH_NODE = PATH_NODES + "/{nodeId}";
59      private static final String PATH_NODE_PROPERTIES = PATH_NODE + "/properties";
60      private static final String PATH_NODE_PROPERTY = PATH_NODE_PROPERTIES + "/{key}";
61      private static final String PATH_NODE_RELATIONSHIPS = PATH_NODE + "/relationships";
62      private static final String PATH_RELATIONSHIP = "relationship/{relationshipId}";
63      private static final String PATH_NODE_RELATIONSHIPS_W_DIR = PATH_NODE_RELATIONSHIPS + "/{direction}";
64      private static final String PATH_NODE_RELATIONSHIPS_W_DIR_N_TYPES = PATH_NODE_RELATIONSHIPS_W_DIR + "/{types}";
65      private static final String PATH_RELATIONSHIP_PROPERTIES = PATH_RELATIONSHIP + "/properties";
66      private static final String PATH_RELATIONSHIP_PROPERTY = PATH_RELATIONSHIP_PROPERTIES + "/{key}";
67      private static final String PATH_NODE_TRAVERSE = PATH_NODE + "/traverse/{returnType}";
68      private static final String PATH_NODE_PATH = PATH_NODE + "/path";
69      private static final String PATH_NODE_PATHS = PATH_NODE + "/paths";
70  
71      protected static final String PATH_NODE_INDEX = "index/node";
72      protected static final String PATH_NAMED_NODE_INDEX = PATH_NODE_INDEX + "/{indexName}";
73      protected static final String PATH_NODE_INDEX_GET = PATH_NAMED_NODE_INDEX + "/{key}/{value}";
74      protected static final String PATH_NODE_INDEX_QUERY = PATH_NAMED_NODE_INDEX + "/{key}"; // http://localhost/db/data/index/node/foo?query=somelucenestuff
75      protected static final String PATH_NODE_INDEX_ID = PATH_NODE_INDEX_GET + "/{id}";
76      protected static final String PATH_NODE_INDEX_REMOVE_KEY = PATH_NAMED_NODE_INDEX + "/{key}/{id}";
77      protected static final String PATH_NODE_INDEX_REMOVE = PATH_NAMED_NODE_INDEX + "/{id}";
78  
79      protected static final String PATH_RELATIONSHIP_INDEX = "index/relationship";
80      protected static final String PATH_NAMED_RELATIONSHIP_INDEX = PATH_RELATIONSHIP_INDEX + "/{indexName}";
81      protected static final String PATH_RELATIONSHIP_INDEX_GET = PATH_NAMED_RELATIONSHIP_INDEX + "/{key}/{value}";
82      protected static final String PATH_RELATIONSHIP_INDEX_QUERY = PATH_NAMED_RELATIONSHIP_INDEX + "/{key}";
83      protected static final String PATH_RELATIONSHIP_INDEX_ID = PATH_RELATIONSHIP_INDEX_GET + "/{id}";
84      protected static final String PATH_RELATIONSHIP_INDEX_REMOVE_KEY = PATH_NAMED_RELATIONSHIP_INDEX + "/{key}/{id}";
85      protected static final String PATH_RELATIONSHIP_INDEX_REMOVE = PATH_NAMED_RELATIONSHIP_INDEX + "/{id}";
86  
87      private final DatabaseActions server;
88      private final OutputFormat output;
89      private final InputFormat input;
90  
91      public RestfulGraphDatabase(@Context UriInfo uriInfo, @Context Database database, @Context InputFormat input, @Context OutputFormat output) {
92          this.input = input;
93          this.output = output;
94          this.server = new DatabaseActions(database);
95      }
96  
97      private static Response nothing() {
98          return Response.noContent().build();
99      }
100 
101     private long extractNodeId(String uri) throws BadInputException {
102         try {
103             return Long.parseLong(uri.substring(uri.lastIndexOf("/") + 1));
104         } catch (NumberFormatException ex) {
105             throw new BadInputException(ex);
106         } catch (NullPointerException ex) {
107             throw new BadInputException(ex);
108         }
109     }
110 
111     @GET
112     public Response getRoot() {
113         return output.ok(server.root());
114     }
115 
116     // Nodes
117 
118     @POST
119     @Path(PATH_NODES)
120     public Response createNode(String body) {
121         try {
122             return output.created(server.createNode(input.readMap(body)));
123         } catch (ArrayStoreException ase) {
124             return generateBadRequestDueToMangledJsonResponse(body);
125         } catch (BadInputException e) {
126             return output.badRequest(e);
127         }
128     }
129 
130     private Response generateBadRequestDueToMangledJsonResponse(String body) {
131         return Response.status(400).type(MediaType.TEXT_PLAIN).entity("Invalid JSON array in POST body: " + body).build();
132     }
133 
134     @GET
135     @Path(PATH_NODE)
136     public Response getNode(@PathParam("nodeId") long nodeId) {
137         try {
138             return output.ok(server.getNode(nodeId));
139         } catch (NodeNotFoundException e) {
140             return output.notFound(e);
141         }
142     }
143 
144     @DELETE
145     @Path(PATH_NODE)
146     public Response deleteNode(@PathParam("nodeId") long nodeId) {
147         try {
148             server.deleteNode(nodeId);
149             return nothing();
150         } catch (NodeNotFoundException e) {
151             return output.notFound(e);
152         } catch (OperationFailureException e) {
153             return output.conflict(e);
154         }
155     }
156 
157     // Node properties
158 
159     @PUT
160     @Path(PATH_NODE_PROPERTIES)
161     public Response setAllNodeProperties(@PathParam("nodeId") long nodeId, String body) {
162         try {
163             server.setAllNodeProperties(nodeId, input.readMap(body));
164         } catch (BadInputException e) {
165             return output.badRequest(e);
166         } catch (ArrayStoreException ase) {
167             return generateBadRequestDueToMangledJsonResponse(body);
168         } catch (NodeNotFoundException e) {
169             return output.notFound(e);
170         }
171         return nothing();
172     }
173 
174     @GET
175     @Path(PATH_NODE_PROPERTIES)
176     public Response getAllNodeProperties(@PathParam("nodeId") long nodeId) {
177         final PropertiesRepresentation properties;
178         try {
179             properties = server.getAllNodeProperties(nodeId);
180         } catch (NodeNotFoundException e) {
181             return output.notFound(e);
182         }
183 
184         if (properties.isEmpty()) {
185             return nothing();
186         }
187 
188         return output.ok(properties);
189     }
190 
191     @PUT
192     @Path(PATH_NODE_PROPERTY)
193     public Response setNodeProperty(@PathParam("nodeId") long nodeId, @PathParam("key") String key, String body) {
194         try {
195             server.setNodeProperty(nodeId, key, input.readValue(body));
196         } catch (BadInputException e) {
197             return output.badRequest(e);
198         } catch (ArrayStoreException ase) {
199             return generateBadRequestDueToMangledJsonResponse(body);
200         } catch (NodeNotFoundException e) {
201             return output.notFound(e);
202         }
203         return nothing();
204     }
205 
206     @GET
207     @Path(PATH_NODE_PROPERTY)
208     public Response getNodeProperty(@PathParam("nodeId") long nodeId, @PathParam("key") String key) {
209         try {
210             return output.ok(server.getNodeProperty(nodeId, key));
211         } catch (NodeNotFoundException e) {
212             return output.notFound(e);
213         } catch (NoSuchPropertyException e) {
214             return output.notFound(e);
215         }
216     }
217 
218     @DELETE
219     @Path(PATH_NODE_PROPERTY)
220     public Response deleteNodeProperty(@PathParam("nodeId") long nodeId, @PathParam("key") String key) {
221         try {
222             server.removeNodeProperty(nodeId, key);
223         } catch (NodeNotFoundException e) {
224             return output.notFound(e);
225         } catch (NoSuchPropertyException e) {
226             return output.notFound(e);
227         }
228         return nothing();
229     }
230 
231     @DELETE
232     @Path(PATH_NODE_PROPERTIES)
233     public Response deleteAllNodeProperties(@PathParam("nodeId") long nodeId) {
234         try {
235             server.removeAllNodeProperties(nodeId);
236         } catch (NodeNotFoundException e) {
237             return output.notFound(e);
238         }
239         return nothing();
240     }
241 
242     // Relationships
243 
244     @POST
245     @Path(PATH_NODE_RELATIONSHIPS)
246     public Response createRelationship(@PathParam("nodeId") long startNodeId, String body) {
247         final Map<String, Object> data;
248         final long endNodeId;
249         final String type;
250         final Map<String, Object> properties;
251         try {
252             data = input.readMap(body);
253             endNodeId = extractNodeId((String) data.get("to"));
254             type = (String) data.get("type");
255             properties = (Map<String, Object>) data.get("data");
256         } catch (BadInputException e) {
257             return output.badRequest(e);
258         } catch (ClassCastException e) {
259             return output.badRequest(e);
260         }
261         try {
262             return output.created(server.createRelationship(startNodeId, endNodeId, type, properties));
263         } catch (StartNodeNotFoundException e) {
264             return output.notFound(e);
265         } catch (EndNodeNotFoundException e) {
266             return output.badRequest(e);
267         } catch (StartNodeSameAsEndNodeException e) {
268             return output.badRequest(e);
269         } catch (PropertyValueException e) {
270             return output.badRequest(e);
271         } catch (BadInputException e) {
272             return output.badRequest(e);
273         }
274     }
275 
276     @GET
277     @Path(PATH_RELATIONSHIP)
278     public Response getRelationship(@PathParam("relationshipId") long relationshipId) {
279         try {
280             return output.ok(server.getRelationship(relationshipId));
281         } catch (RelationshipNotFoundException e) {
282             return output.notFound(e);
283         }
284     }
285 
286     @DELETE
287     @Path(PATH_RELATIONSHIP)
288     public Response deleteRelationship(@PathParam("relationshipId") long relationshipId) {
289         try {
290             server.deleteRelationship(relationshipId);
291         } catch (RelationshipNotFoundException e) {
292             return output.notFound(e);
293         }
294         return nothing();
295     }
296 
297     @GET
298     @Path(PATH_NODE_RELATIONSHIPS_W_DIR)
299     public Response getNodeRelationships(@PathParam("nodeId") long nodeId, @PathParam("direction") RelationshipDirection direction) {
300         try {
301             return output.ok(server.getNodeRelationships(nodeId, direction, Collections.<String> emptyList()));
302         } catch (NodeNotFoundException e) {
303             return output.notFound(e);
304         }
305     }
306 
307     @GET
308     @Path(PATH_NODE_RELATIONSHIPS_W_DIR_N_TYPES)
309     public Response getNodeRelationships(@PathParam("nodeId") long nodeId, @PathParam("direction") RelationshipDirection direction,
310             @PathParam("types") AmpersandSeparatedCollection types) {
311         try {
312             return output.ok(server.getNodeRelationships(nodeId, direction, types));
313         } catch (NodeNotFoundException e) {
314             return output.notFound(e);
315         }
316     }
317 
318     // Relationship properties
319 
320     @GET
321     @Path(PATH_RELATIONSHIP_PROPERTIES)
322     public Response getAllRelationshipProperties(@PathParam("relationshipId") long relationshipId) {
323         final PropertiesRepresentation properties;
324         try {
325             properties = server.getAllRelationshipProperties(relationshipId);
326         } catch (RelationshipNotFoundException e) {
327             return output.notFound(e);
328         }
329         if (properties.isEmpty()) {
330             return nothing();
331         } else {
332             return output.ok(properties);
333         }
334     }
335 
336     @GET
337     @Path(PATH_RELATIONSHIP_PROPERTY)
338     public Response getRelationshipProperty(@PathParam("relationshipId") long relationshipId, @PathParam("key") String key) {
339         try {
340             return output.ok(server.getRelationshipProperty(relationshipId, key));
341         } catch (RelationshipNotFoundException e) {
342             return output.notFound(e);
343         } catch (NoSuchPropertyException e) {
344             return output.notFound(e);
345         }
346     }
347 
348     @PUT
349     @Path(PATH_RELATIONSHIP_PROPERTIES)
350     @Consumes(MediaType.APPLICATION_JSON)
351     public Response setAllRelationshipProperties(@PathParam("relationshipId") long relationshipId, String body) {
352         try {
353             server.setAllRelationshipProperties(relationshipId, input.readMap(body));
354         } catch (BadInputException e) {
355             return output.badRequest(e);
356         } catch (RelationshipNotFoundException e) {
357             return output.notFound(e);
358         }
359         return nothing();
360     }
361 
362     @PUT
363     @Path(PATH_RELATIONSHIP_PROPERTY)
364     @Consumes(MediaType.APPLICATION_JSON)
365     public Response setRelationshipProperty(@PathParam("relationshipId") long relationshipId, @PathParam("key") String key, String body) {
366         try {
367             server.setRelationshipProperty(relationshipId, key, input.readValue(body));
368         } catch (BadInputException e) {
369             return output.badRequest(e);
370         } catch (RelationshipNotFoundException e) {
371             return output.notFound(e);
372         }
373         return nothing();
374     }
375 
376     @DELETE
377     @Path(PATH_RELATIONSHIP_PROPERTIES)
378     public Response deleteAllRelationshipProperties(@PathParam("relationshipId") long relationshipId) {
379         try {
380             server.removeAllRelationshipProperties(relationshipId);
381         } catch (RelationshipNotFoundException e) {
382             return output.notFound(e);
383         }
384         return nothing();
385     }
386 
387     @DELETE
388     @Path(PATH_RELATIONSHIP_PROPERTY)
389     public Response deleteRelationshipProperty(@PathParam("relationshipId") long relationshipId, @PathParam("key") String key) {
390         try {
391             server.removeRelationshipProperty(relationshipId, key);
392         } catch (RelationshipNotFoundException e) {
393             return output.notFound(e);
394         } catch (NoSuchPropertyException e) {
395             return output.notFound(e);
396         }
397         return nothing();
398     }
399 
400     // Index
401 
402     @GET
403     @Path(PATH_NODE_INDEX)
404     public Response getNodeIndexRoot() {
405         if (server.getNodeIndexNames().length == 0) {
406             return output.noContent();
407         }
408         return output.ok(server.nodeIndexRoot());
409     }
410 
411     @POST
412     @Path(PATH_NODE_INDEX)
413     @Consumes(MediaType.APPLICATION_JSON)
414     public Response jsonCreateNodeIndex(String json) {
415         try {
416             return output.created(server.createNodeIndex(input.readMap(json)));
417         } catch (BadInputException e) {
418             return output.badRequest(e);
419         }
420     }
421 
422     @GET
423     @Path(PATH_RELATIONSHIP_INDEX)
424     public Response getRelationshipIndexRoot() {
425         if (server.getRelationshipIndexNames().length == 0) {
426             return output.noContent();
427         }
428         return output.ok(server.relationshipIndexRoot());
429     }
430 
431     @POST
432     @Path(PATH_RELATIONSHIP_INDEX)
433     @Consumes(MediaType.APPLICATION_JSON)
434     public Response jsonCreateRelationshipIndex(String json) {
435         try {
436             return output.created(server.createRelationshipIndex(input.readMap(json)));
437         } catch (BadInputException e) {
438             return output.badRequest(e);
439         }
440     }
441 
442     @POST
443     @Path(PATH_NODE_INDEX_GET)
444     @Consumes(MediaType.APPLICATION_JSON)
445     public Response addToNodeIndex(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value, String objectUri) {
446         try {
447             return output.created(server.addToNodeIndex(indexName, key, value, extractNodeId(input.readUri(objectUri).toString())));
448         } catch (BadInputException e) {
449             return output.badRequest(e);
450         }
451     }
452 
453     @POST
454     @Path(PATH_RELATIONSHIP_INDEX_GET)
455     public Response addToRelationshipIndex(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value,
456             String objectUri) {
457         try {
458             return output.created(server.addToRelationshipIndex(indexName, key, value, extractNodeId(input.readUri(objectUri).toString())));
459         } catch (BadInputException e) {
460             return output.badRequest(e);
461         }
462     }
463 
464     @GET
465     @Path(PATH_NODE_INDEX_ID)
466     public Response getNodeFromIndexUri(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value,
467             @PathParam("id") long id) {
468         try {
469             return output.ok(server.getIndexedNode(indexName, key, value, id));
470         } catch (NotFoundException nfe) {
471             return output.notFound(nfe);
472         }
473     }
474 
475     @GET
476     @Path(PATH_RELATIONSHIP_INDEX_ID)
477     public Response getRelationshipFromIndexUri(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value,
478             @PathParam("id") long id) {
479         return output.ok(server.getIndexedRelationship(indexName, key, value, id));
480     }
481 
482     @GET
483     @Path(PATH_NODE_INDEX_GET)
484     public Response getIndexedNodes(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value) {
485         try {
486             return output.ok(server.getIndexedNodesByExactMatch(indexName, key, value));
487         } catch (NotFoundException nfe) {
488             return output.notFound(nfe);
489         }
490     }
491 
492     @GET
493     @Path(PATH_NODE_INDEX_QUERY)
494     public Response getIndexedNodesByQuery(@PathParam("indexName") String indexName, @PathParam("key") String key, @QueryParam("query") String query) {
495         try {
496             return output.ok(server.getIndexedNodesByQuery(indexName, key, query));
497         } catch (NotFoundException nfe) {
498             return output.notFound(nfe);
499         }
500     }
501 
502     @GET
503     @Path(PATH_RELATIONSHIP_INDEX_GET)
504     public Response getIndexedRelationships(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value) {
505         try {
506             return output.ok(server.getIndexedRelationships(indexName, key, value));
507         } catch (NotFoundException nfe) {
508             return output.notFound(nfe);
509         }
510     }
511 
512     @GET
513     @Path(PATH_RELATIONSHIP_INDEX_QUERY)
514     public Response getIndexedRelationshipsByQuery(@PathParam("indexName") String indexName, @PathParam("key") String key, @QueryParam("query") String query) {
515         try {
516             return output.ok(server.getIndexedRelationshipsByQuery(indexName, key, query));
517         } catch (NotFoundException nfe) {
518             return output.notFound(nfe);
519         }
520     }
521 
522     @DELETE
523     @Path(PATH_NODE_INDEX_ID)
524     public Response deleteFromNodeIndex(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value,
525             @PathParam("id") long id) {
526         try {
527             server.removeFromNodeIndex(indexName, key, value, id);
528             return nothing();
529         } catch (NotFoundException nfe) {
530             return output.notFound(nfe);
531         }
532     }
533 
534     @DELETE
535     @Path(PATH_NODE_INDEX_REMOVE_KEY)
536     public Response deleteFromNodeIndexNoValue(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("id") long id) {
537         try {
538             server.removeFromNodeIndexNoValue(indexName, key, id);
539             return nothing();
540         } catch (NotFoundException nfe) {
541             return output.notFound(nfe);
542         }
543     }
544     
545     @DELETE
546     @Path(PATH_NODE_INDEX_REMOVE)
547     public Response deleteFromNodeIndexNoKeyValue(@PathParam("indexName") String indexName, @PathParam("id") long id) {
548         try {
549             server.removeFromNodeIndexNoKeyValue(indexName, id);
550             return nothing();
551         } catch (NotFoundException nfe) {
552             return output.notFound(nfe);
553         }
554     }
555     
556     @DELETE
557     @Path(PATH_RELATIONSHIP_INDEX_ID)
558     public Response deleteFromRelationshipIndex(@PathParam("indexName") String indexName, @PathParam("key") String key, @PathParam("value") String value,
559             @PathParam("id") long id) {
560         try {
561             server.removeFromRelationshipIndex(indexName, key, value, id);
562             return nothing();
563         } catch (NotFoundException nfe) {
564             return output.notFound(nfe);
565         }
566     }
567 
568     @DELETE
569     @Path(PATH_RELATIONSHIP_INDEX_REMOVE_KEY)
570     public Response deleteFromRelationshipIndexnoValue(@PathParam("indexName") String indexName, @PathParam("key") String key,
571             @PathParam("id") long id) {
572         try {
573             server.removeFromRelationshipIndexNoValue(indexName, key, id);
574             return nothing();
575         } catch (NotFoundException nfe) {
576             return output.notFound(nfe);
577         }
578     }
579     
580     @DELETE
581     @Path(PATH_RELATIONSHIP_INDEX_REMOVE)
582     public Response deleteFromRelationshipIndex(@PathParam("indexName") String indexName, @PathParam("value") String value,
583             @PathParam("id") long id) {
584         try {
585             server.removeFromRelationshipIndexNoKeyValue(indexName, id);
586             return nothing();
587         } catch (NotFoundException nfe) {
588             return output.notFound(nfe);
589         }
590     }
591     
592     // Traversal
593 
594     @POST
595     @Path(PATH_NODE_TRAVERSE)
596     public Response traverse(@PathParam("nodeId") long startNode, @PathParam("returnType") TraverserReturnType returnType, String body) {
597         try {
598             return output.ok(server.traverse(startNode, input.readMap(body), returnType));
599         } catch (BadInputException e) {
600             return output.badRequest(e);
601         } catch (NotFoundException e) {
602             return output.notFound(e);
603         }
604     }
605 
606     @POST
607     @Path(PATH_NODE_PATH)
608     public Response singlePath(@PathParam("nodeId") long startNode, String body) {
609         final Map<String, Object> description;
610         final long endNode;
611         try {
612             description = input.readMap(body);
613             endNode = extractNodeId((String) description.get("to"));
614             return output.ok(server.findSinglePath(startNode, endNode, description));
615         } catch (BadInputException e) {
616             return output.badRequest(e);
617         } catch (ClassCastException e) {
618             return output.badRequest(e);
619         } catch (NotFoundException e) {
620             return output.notFound();
621         }
622 
623     }
624 
625     @POST
626     @Path(PATH_NODE_PATHS)
627     public Response allPaths(@PathParam("nodeId") long startNode, String body) {
628         final Map<String, Object> description;
629         final long endNode;
630         try {
631             description = input.readMap(body);
632             endNode = extractNodeId((String) description.get("to"));
633             return output.ok(server.findPaths(startNode, endNode, description));
634         } catch (BadInputException e) {
635             return output.badRequest(e);
636         } catch (ClassCastException e) {
637             return output.badRequest(e);
638         }
639     }
640 
641 }