1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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}";
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
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
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
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
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
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
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 }