View Javadoc

1   /*
2    * Copyright 2015 Data Archiving and Networked Services (an institute of
3    * Koninklijke Nederlandse Akademie Van Wetenschappen), King's College London,
4    * Georg-August-Universitaet Goettingen Stiftung Oeffentlichen Rechts
5    *
6    * Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
7    * the European Commission - subsequent versions of the EUPL (the "Licence");
8    * You may not use this work except in compliance with the Licence.
9    * You may obtain a copy of the Licence at:
10   *
11   * https://joinup.ec.europa.eu/software/page/eupl
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the Licence is distributed on an "AS IS" basis,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the Licence for the specific language governing
17   * permissions and limitations under the Licence.
18   */
19  
20  package eu.ehri.project.graphql;
21  
22  import com.fasterxml.jackson.core.JsonGenerator;
23  import graphql.ExecutionResult;
24  import graphql.ExecutionResultImpl;
25  import graphql.execution.AsyncExecutionStrategy;
26  import graphql.execution.DataFetcherExceptionHandlerParameters;
27  import graphql.execution.ExecutionContext;
28  import graphql.execution.ExecutionStrategy;
29  import graphql.execution.ExecutionStrategyParameters;
30  import graphql.execution.ExecutionTypeInfo;
31  import graphql.execution.FieldCollectorParameters;
32  import graphql.execution.TypeResolutionParameters;
33  import graphql.execution.instrumentation.Instrumentation;
34  import graphql.execution.instrumentation.InstrumentationContext;
35  import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters;
36  import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters;
37  import graphql.language.Field;
38  import graphql.schema.DataFetchingEnvironment;
39  import graphql.schema.DataFetchingFieldSelectionSet;
40  import graphql.schema.DataFetchingFieldSelectionSetImpl;
41  import graphql.schema.GraphQLEnumType;
42  import graphql.schema.GraphQLFieldDefinition;
43  import graphql.schema.GraphQLInterfaceType;
44  import graphql.schema.GraphQLList;
45  import graphql.schema.GraphQLObjectType;
46  import graphql.schema.GraphQLOutputType;
47  import graphql.schema.GraphQLScalarType;
48  import graphql.schema.GraphQLType;
49  import graphql.schema.GraphQLUnionType;
50  
51  import java.io.IOException;
52  import java.util.Arrays;
53  import java.util.Collections;
54  import java.util.List;
55  import java.util.Map;
56  import java.util.concurrent.CompletableFuture;
57  
58  import static graphql.execution.ExecutionTypeInfo.newTypeInfo;
59  import static graphql.execution.FieldCollectorParameters.newParameters;
60  import static graphql.schema.DataFetchingEnvironmentBuilder.newDataFetchingEnvironment;
61  
62  /**
63   * Streaming version of an execution strategy.
64   *
65   * TODO: this class duplicates with slight modifications a lot of logic
66   * from the {@link ExecutionStrategy} class - find a way to clean it up
67   * and rationalise things for easier maintenance and upgrading.
68   */
69  public class StreamingExecutionStrategy extends ExecutionStrategy {
70  
71      public void execute(JsonGenerator generator, ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws IOException {
72          generator.writeStartObject();
73          for (String fieldName : parameters.fields().keySet()) {
74              generator.writeFieldName(fieldName);
75              resolveField(generator, executionContext, parameters, parameters.fields().get(fieldName));
76          }
77          generator.writeEndObject();
78      }
79  
80      private void handleFetchingException(ExecutionContext executionContext,
81              ExecutionStrategyParameters parameters,
82              Field field,
83              GraphQLFieldDefinition fieldDef,
84              Map<String, Object> argumentValues,
85              DataFetchingEnvironment environment,
86              Throwable e) {
87          DataFetcherExceptionHandlerParameters handlerParameters = DataFetcherExceptionHandlerParameters.newExceptionParameters()
88                  .executionContext(executionContext)
89                  .dataFetchingEnvironment(environment)
90                  .argumentValues(argumentValues)
91                  .field(field)
92                  .fieldDefinition(fieldDef)
93                  .path(parameters.path())
94                  .exception(e)
95                  .build();
96  
97          dataFetcherExceptionHandler.accept(handlerParameters);
98      }
99  
100     private void resolveField(JsonGenerator generator, ExecutionContext executionContext, ExecutionStrategyParameters parameters, List<Field> fields) throws IOException {
101         Field field = fields.get(0);
102         GraphQLObjectType parentType = parameters.typeInfo().castType(GraphQLObjectType.class);
103         GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field);
104 
105         Map<String, Object> argumentValues = valuesResolver.getArgumentValues(fieldDef.getArguments(), field.getArguments(), executionContext.getVariables());
106 
107         GraphQLOutputType fieldType = fieldDef.getType();
108         DataFetchingFieldSelectionSet fieldCollector = DataFetchingFieldSelectionSetImpl.newCollector(executionContext, fieldType, fields);
109 
110         DataFetchingEnvironment environment = newDataFetchingEnvironment(executionContext)
111                 .source(parameters.source())
112                 .arguments(argumentValues)
113                 .fieldDefinition(fieldDef)
114                 .fields(fields)
115                 .fieldType(fieldType)
116                 .parentType(parentType)
117                 .selectionSet(fieldCollector)
118                 .build();
119 
120         ExecutionTypeInfo fieldTypeInfo = newTypeInfo()
121                 .type(fieldType)
122                 .parentInfo(parameters.typeInfo())
123                 .build();
124 
125         Instrumentation instrumentation = executionContext.getInstrumentation();
126 
127         InstrumentationContext<ExecutionResult> fieldCtx = instrumentation.beginField(new InstrumentationFieldParameters(executionContext, fieldDef, fieldTypeInfo));
128 
129         InstrumentationContext<Object> fetchCtx = instrumentation.beginFieldFetch(new InstrumentationFieldFetchParameters(executionContext, fieldDef, environment));
130         Object resolvedValue = null;
131         try {
132             resolvedValue = fieldDef.getDataFetcher().get(environment);
133             fetchCtx.onEnd(resolvedValue, null);
134         } catch (Exception e) {
135             handleFetchingException(executionContext, parameters, field, fieldDef, argumentValues, environment, e);
136             fetchCtx.onEnd(null, e);
137         }
138 
139         ExecutionStrategyParameters newParameters = ExecutionStrategyParameters.newParameters()
140                 .typeInfo(fieldTypeInfo)
141                 .fields(parameters.fields())
142                 .arguments(argumentValues)
143                 .source(resolvedValue).build();
144 
145         completeValue(generator, executionContext, newParameters, fields);
146 
147         fieldCtx.onEnd(new ExecutionResultImpl(resolvedValue, Collections.emptyList()), null);
148     }
149 
150     private void completeValue(JsonGenerator generator, ExecutionContext executionContext, ExecutionStrategyParameters parameters, List<Field> fields) throws IOException {
151 
152         ExecutionTypeInfo typeInfo = parameters.typeInfo();
153         Object result = parameters.source();
154         GraphQLType fieldType = parameters.typeInfo().getType();
155 
156         if (result == null) {
157             generator.writeNull();
158         } else if (fieldType instanceof GraphQLList) {
159             completeValueForList(generator, executionContext, parameters, fields, result);
160         } else if (fieldType instanceof GraphQLScalarType) {
161             completeValueForScalar(generator, (GraphQLScalarType) fieldType, result);
162         } else if (fieldType instanceof GraphQLEnumType) {
163             completeValueForEnum(generator, (GraphQLEnumType) fieldType, result);
164         } else {
165             GraphQLObjectType resolvedType;
166             if (fieldType instanceof GraphQLInterfaceType) {
167                 TypeResolutionParameters resolutionParams = TypeResolutionParameters.newParameters()
168                         .graphQLInterfaceType((GraphQLInterfaceType) fieldType)
169                         .field(fields.get(0))
170                         .value(parameters.source())
171                         .argumentValues(parameters.arguments())
172                         .schema(executionContext.getGraphQLSchema()).build();
173                 resolvedType = resolveTypeForInterface(resolutionParams);
174 
175             } else if (fieldType instanceof GraphQLUnionType) {
176                 TypeResolutionParameters resolutionParams = TypeResolutionParameters.newParameters()
177                         .graphQLUnionType((GraphQLUnionType) fieldType)
178                         .field(fields.get(0))
179                         .value(parameters.source())
180                         .argumentValues(parameters.arguments())
181                         .schema(executionContext.getGraphQLSchema()).build();
182                 resolvedType = resolveTypeForUnion(resolutionParams);
183             } else {
184                 resolvedType = (GraphQLObjectType) fieldType;
185             }
186 
187             FieldCollectorParameters collectorParameters = newParameters()
188                     .schema(executionContext.getGraphQLSchema())
189                     .objectType(resolvedType)
190                     .fragments(executionContext.getFragmentsByName())
191                     .variables(executionContext.getVariables())
192                     .build();
193 
194             Map<String, List<Field>> subFields = fieldCollector.collectFields(collectorParameters, fields);
195 
196             ExecutionStrategyParameters newParameters = ExecutionStrategyParameters.newParameters()
197                     .typeInfo(typeInfo.treatAs(resolvedType))
198                     .fields(subFields)
199                     .source(result).build();
200 
201             // Calling this from the executionContext to ensure we shift back from mutation strategy to the query strategy.
202             execute(generator, executionContext, newParameters);
203         }
204     }
205 
206     private void completeValueForEnum(JsonGenerator generator, GraphQLEnumType enumType, Object result) throws IOException {
207         generator.writeObject(enumType.getCoercing().serialize(result));
208     }
209 
210     private void completeValueForScalar(JsonGenerator generator, GraphQLScalarType scalarType, Object result) throws IOException {
211         Object serialized = scalarType.getCoercing().serialize(result);
212         //6.6.1 http://facebook.github.io/graphql/#sec-Field-entries
213         if (serialized instanceof Double && ((Double) serialized).isNaN()) {
214             serialized = null;
215         }
216         generator.writeObject(serialized);
217     }
218 
219     private void completeValueForList(JsonGenerator generator, ExecutionContext executionContext, ExecutionStrategyParameters parameters, List<Field> fields, Object result) throws IOException {
220         if (result.getClass().isArray()) {
221             result = Arrays.asList((Object[]) result);
222         }
223 
224         completeValueForList(generator, executionContext, parameters, fields, (Iterable<Object>) result);
225     }
226 
227     private void completeValueForList(JsonGenerator generator, ExecutionContext executionContext, ExecutionStrategyParameters parameters, List<Field> fields, Iterable<Object> result) throws IOException {
228         ExecutionTypeInfo typeInfo = parameters.typeInfo();
229         GraphQLList fieldType = typeInfo.castType(GraphQLList.class);
230 
231         generator.writeStartArray();
232         for (Object item : result) {
233             ExecutionStrategyParameters newParameters = ExecutionStrategyParameters.newParameters()
234                     .typeInfo(typeInfo.treatAs(fieldType.getWrappedType()))
235                     .fields(parameters.fields())
236                     .source(item).build();
237 
238             completeValue(generator, executionContext, newParameters, fields);
239         }
240         generator.writeEndArray();
241     }
242 
243 
244     @Override
245     public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) {
246         return new AsyncExecutionStrategy().execute(executionContext, parameters);
247     }
248 }