View Javadoc

1   package eu.ehri.extension;
2   
3   import com.fasterxml.jackson.core.JsonGenerator;
4   import eu.ehri.extension.base.AbstractAccessibleResource;
5   import eu.ehri.extension.errors.ExecutionError;
6   import eu.ehri.project.core.Tx;
7   import eu.ehri.project.graphql.GraphQLImpl;
8   import eu.ehri.project.graphql.GraphQLQuery;
9   import eu.ehri.project.graphql.StreamingGraphQL;
10  import eu.ehri.project.models.base.Accessible;
11  import eu.ehri.project.persistence.Bundle;
12  import graphql.ExecutionInput;
13  import graphql.ExecutionResult;
14  import graphql.GraphQL;
15  import graphql.introspection.IntrospectionQuery;
16  import graphql.language.Document;
17  import graphql.schema.GraphQLSchema;
18  import org.neo4j.graphdb.GraphDatabaseService;
19  
20  import javax.ws.rs.Consumes;
21  import javax.ws.rs.GET;
22  import javax.ws.rs.POST;
23  import javax.ws.rs.Path;
24  import javax.ws.rs.Produces;
25  import javax.ws.rs.core.Context;
26  import javax.ws.rs.core.MediaType;
27  import javax.ws.rs.core.Response;
28  import javax.ws.rs.core.StreamingOutput;
29  
30  /**
31   * GraphQL implementation.
32   */
33  @Path(GraphQLResource.ENDPOINT)
34  public class GraphQLResource extends AbstractAccessibleResource<Accessible> {
35  
36      public static final String ENDPOINT = "graphql";
37  
38      public GraphQLResource(@Context GraphDatabaseService database) {
39          super(database, Accessible.class);
40      }
41  
42      // Helpers
43  
44      /**
45       * Fetch the introspected GraphQL schema.
46       *
47       * @return JSON data describing the schema.
48       */
49      @GET
50      @Produces(MediaType.APPLICATION_JSON)
51      public ExecutionResult describe() throws Exception {
52          try (final Tx tx = beginTx()) {
53              GraphQLSchema schema = new GraphQLImpl(api()).getSchema();
54              tx.success();
55              return GraphQL.newGraphQL(schema).build()
56                      .execute(IntrospectionQuery.INTROSPECTION_QUERY);
57          }
58      }
59  
60      /**
61       * Run a GraphQL query.
62       *
63       * @param q a query object containing <code>query</code> and <code>variables</code>
64       *          fields
65       * @return the results of the query as JSON
66       */
67      @POST
68      @Consumes(MediaType.APPLICATION_JSON)
69      @Produces(MediaType.APPLICATION_JSON)
70      public Response query(GraphQLQuery q) throws Exception {
71          try (final Tx tx = beginTx()) {
72              boolean stream = isStreaming();
73              GraphQLSchema schema = new GraphQLImpl(api(), stream).getSchema();
74              Object data = stream ? lazyExecution(schema, q) : strictExecution(schema, q);
75              tx.success();
76              return Response.ok(data).build();
77          }
78      }
79  
80      /**
81       * Run a GraphQL query.
82       *
83       * @param q a GraphQL query as text.
84       * @return the results of the query as JSON
85       */
86      @POST
87      @Consumes({MediaType.TEXT_PLAIN, MediaType.APPLICATION_OCTET_STREAM})
88      @Produces(MediaType.APPLICATION_JSON)
89      public Response query(String q) throws Exception {
90          return query(new GraphQLQuery(q));
91      }
92  
93      private ExecutionResult strictExecution(GraphQLSchema schema, GraphQLQuery q) {
94          ExecutionResult executionResult = GraphQL.newGraphQL(schema).build()
95                  .execute(ExecutionInput.newExecutionInput()
96                          .query(q.getQuery())
97                          .operationName(q.getOperationName())
98                          .variables(q.getVariables()).build());
99          if (!executionResult.getErrors().isEmpty()) {
100             throw new ExecutionError(executionResult.getErrors());
101         }
102         return executionResult;
103     }
104 
105     private StreamingOutput lazyExecution(GraphQLSchema schema, GraphQLQuery q) {
106         // FIXME: Ugly: have to reinitialise the schema in this transaction
107         // otherwise iterables will be invalid.
108         final StreamingGraphQL ql = new StreamingGraphQL(schema);
109         final Document document = ql.parseAndValidate(q.getQuery(), q.getOperationName(), q.getVariables());
110         return outputStream -> {
111             try (final Tx tx = beginTx();
112                  final JsonGenerator generator = jsonFactory
113                          .createGenerator(outputStream)
114                          .useDefaultPrettyPrinter()) {
115                 final StreamingGraphQL ql2 = new StreamingGraphQL(
116                         new GraphQLImpl(api(), true).getSchema());
117                 generator.writeStartObject();
118                 generator.writeFieldName(Bundle.DATA_KEY);
119                 ql2.execute(generator, q.getQuery(), document, q.getOperationName(),
120                         null, q.getVariables());
121                 generator.writeEndObject();
122                 tx.success();
123             }
124         };
125     }
126 }