1111 * distributed under the License is distributed on an "AS IS" BASIS,
1212 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313 * See the License for the specific language governing permissions and
14- */
15- package graphql .servlet ;
14+ */ package graphql .servlet ;
1615
1716import com .fasterxml .jackson .core .JsonParser ;
1817import com .fasterxml .jackson .core .type .TypeReference ;
2221import com .fasterxml .jackson .databind .RuntimeJsonMappingException ;
2322import com .fasterxml .jackson .databind .annotation .JsonDeserialize ;
2423import com .google .common .io .CharStreams ;
25- import graphql .*;
24+ import graphql .ExceptionWhileDataFetching ;
25+ import graphql .ExecutionResult ;
26+ import graphql .GraphQL ;
27+ import graphql .GraphQLError ;
28+ import graphql .InvalidSyntaxError ;
29+ import graphql .execution .ExecutionStrategy ;
2630import graphql .schema .GraphQLFieldDefinition ;
27- import graphql .schema .GraphQLObjectType ;
2831import graphql .schema .GraphQLSchema ;
29- import graphql .schema .GraphQLType ;
3032import graphql .validation .ValidationError ;
3133import lombok .Getter ;
3234import lombok .Setter ;
3638import org .apache .commons .fileupload .FileItemStream ;
3739import org .apache .commons .fileupload .FileUploadException ;
3840import org .apache .commons .fileupload .servlet .ServletFileUpload ;
39- import org .osgi .service .component .annotations .Component ;
40- import org .osgi .service .component .annotations .Reference ;
41- import org .osgi .service .component .annotations .ReferenceCardinality ;
42- import org .osgi .service .component .annotations .ReferencePolicyOption ;
4341
4442import javax .security .auth .Subject ;
4543import javax .servlet .Servlet ;
5250import java .io .InputStreamReader ;
5351import java .security .AccessController ;
5452import java .security .PrivilegedAction ;
55- import java .util .*;
53+ import java .util .ArrayList ;
54+ import java .util .HashMap ;
55+ import java .util .List ;
56+ import java .util .Map ;
57+ import java .util .Optional ;
5658import java .util .stream .Collectors ;
5759
58- import static graphql .schema .GraphQLFieldDefinition .newFieldDefinition ;
59- import static graphql .schema .GraphQLObjectType .newObject ;
60- import static graphql .schema .GraphQLSchema .newSchema ;
61-
60+ /**
61+ * @author Andrew Potter
62+ */
6263@ Slf4j
63- @ Component (property = {"alias=/graphql" , "jmx.objectname=graphql.servlet:type=graphql" })
64- public class GraphQLServlet extends HttpServlet implements Servlet , GraphQLMBean , GraphQLSchemaProvider {
65-
66- private List <GraphQLQueryProvider > queryProviders = new ArrayList <>();
67- private List <GraphQLMutationProvider > mutationProviders = new ArrayList <>();
68- private List <GraphQLTypesProvider > typesProviders = new ArrayList <>();
69-
70- @ Getter
71- GraphQLSchema schema ;
72- @ Getter
73- GraphQLSchema readOnlySchema ;
74-
75- protected void updateSchema () {
76- GraphQLObjectType .Builder object = newObject ().name ("query" );
77-
78- for (GraphQLQueryProvider provider : queryProviders ) {
79- GraphQLObjectType query = provider .getQuery ();
80- object .field (newFieldDefinition ().
81- type (query ).
82- staticValue (provider .context ()).
83- name (provider .getName ()).
84- description (query .getDescription ()).
85- build ());
86- }
87-
88- Set <GraphQLType > types = new HashSet <>();
89- for (GraphQLTypesProvider typesProvider : typesProviders ) {
90- types .addAll (typesProvider .getTypes ());
91- }
64+ public abstract class GraphQLServlet extends HttpServlet implements Servlet , GraphQLMBean , GraphQLSchemaProvider {
9265
93- readOnlySchema = newSchema ().query (object .build ()).build (types );
66+ protected abstract GraphQLContext createContext (Optional <HttpServletRequest > request , Optional <HttpServletResponse > response );
67+ protected abstract ExecutionStrategy getExecutionStrategy ();
68+ protected abstract Map <String , Object > transformVariables (GraphQLSchema schema , String query , Map <String , Object > variables );
9469
95- if (mutationProviders .isEmpty ()) {
96- schema = readOnlySchema ;
97- } else {
98- GraphQLObjectType .Builder mutationObject = newObject ().name ("mutation" );
99-
100- for (GraphQLMutationProvider provider : mutationProviders ) {
101- provider .getMutations ().forEach (mutationObject ::field );
102- }
103-
104- GraphQLObjectType mutationType = mutationObject .build ();
105- if (mutationType .getFieldDefinitions ().size () == 0 ) {
106- schema = readOnlySchema ;
107- } else {
108- schema = newSchema ().query (object .build ()).mutation (mutationType ).build ();
109- }
110- }
111- }
112-
113- public GraphQLServlet () {
114- updateSchema ();
115- }
11670
117- @ Reference (cardinality = ReferenceCardinality .MULTIPLE , policyOption = ReferencePolicyOption .GREEDY )
118- public void bindQueryProvider (GraphQLQueryProvider queryProvider ) {
119- queryProviders .add (queryProvider );
120- updateSchema ();
121- }
122- public void unbindQueryProvider (GraphQLQueryProvider queryProvider ) {
123- queryProviders .remove (queryProvider );
124- updateSchema ();
125- }
71+ private List <GraphQLOperationListener > operationListeners = new ArrayList <>();
12672
127- @ Reference (cardinality = ReferenceCardinality .MULTIPLE , policyOption = ReferencePolicyOption .GREEDY )
128- public void bindMutationProvider (GraphQLMutationProvider mutationProvider ) {
129- mutationProviders .add (mutationProvider );
130- updateSchema ();
131- }
132- public void unbindMutationProvider (GraphQLMutationProvider mutationProvider ) {
133- mutationProviders .remove (mutationProvider );
134- updateSchema ();
73+ public void addOperationListener (GraphQLOperationListener operationListener ) {
74+ operationListeners .add (operationListener );
13575 }
13676
137- @ Reference (cardinality = ReferenceCardinality .MULTIPLE , policyOption = ReferencePolicyOption .GREEDY )
138- public void typesProviders (GraphQLTypesProvider typesProvider ) {
139- typesProviders .add (typesProvider );
140- updateSchema ();
141- }
142- public void unbindTypesProvider (GraphQLTypesProvider typesProvider ) {
143- typesProviders .remove (typesProvider );
144- updateSchema ();
77+ public void removeOperationListener (GraphQLOperationListener operationListener ) {
78+ operationListeners .remove (operationListener );
14579 }
14680
14781 @ Override
14882 public String [] getQueries () {
149- return schema .getQueryType ().getFieldDefinitions ().stream ().map (GraphQLFieldDefinition ::getName ).toArray (String []::new );
83+ return getSchema () .getQueryType ().getFieldDefinitions ().stream ().map (GraphQLFieldDefinition ::getName ).toArray (String []::new );
15084 }
15185
15286 @ Override
15387 public String [] getMutations () {
154- return schema .getMutationType ().getFieldDefinitions ().stream ().map (GraphQLFieldDefinition ::getName ).toArray (String []::new );
155- }
156-
157- private GraphQLContextBuilder contextBuilder = new DefaultGraphQLContextBuilder ();
158- private ExecutionStrategyProvider executionStrategyProvider = new EnhancedExecutionStrategyProvider ();
159-
160- @ Reference (cardinality = ReferenceCardinality .OPTIONAL , policyOption = ReferencePolicyOption .GREEDY )
161- public void setContextProvider (GraphQLContextBuilder contextBuilder ) {
162- this .contextBuilder = contextBuilder ;
163- }
164- public void unsetContextProvider (GraphQLContextBuilder contextBuilder ) {
165- this .contextBuilder = new DefaultGraphQLContextBuilder ();
166- }
167-
168- @ Reference (cardinality = ReferenceCardinality .OPTIONAL )
169- public void setExecutionStrategyProvider (ExecutionStrategyProvider provider ) {
170- executionStrategyProvider = provider ;
171- }
172- public void unsetExecutionStrategyProvider (ExecutionStrategyProvider provider ) {
173- executionStrategyProvider = new EnhancedExecutionStrategyProvider ();
174- }
175-
176- protected GraphQLContext createContext (Optional <HttpServletRequest > req , Optional <HttpServletResponse > resp ) {
177- return contextBuilder .build (req , resp );
178- }
179-
180- private List <GraphQLOperationListener > operationListeners = new ArrayList <>();
181-
182- @ Reference (cardinality = ReferenceCardinality .MULTIPLE , policyOption = ReferencePolicyOption .GREEDY )
183- public void bindOperationListener (GraphQLOperationListener listener ) {
184- operationListeners .add (listener );
185- }
186-
187- public void unbindOperationListener (GraphQLOperationListener listener ) {
188- operationListeners .remove (listener );
88+ return getSchema ().getMutationType ().getFieldDefinitions ().stream ().map (GraphQLFieldDefinition ::getName ).toArray (String []::new );
18989 }
19090
19191 @ Override @ SneakyThrows
19292 public String executeQuery (String query ) {
19393 try {
194- ExecutionResult result = new GraphQL (schema ).execute (query , createContext (Optional .empty (), Optional .empty ()), new HashMap <>());
94+ ExecutionResult result = new GraphQL (getSchema () ).execute (query , createContext (Optional .empty (), Optional .empty ()), new HashMap <>());
19595 if (result .getErrors ().isEmpty ()) {
19696 return new ObjectMapper ().writeValueAsString (result .getData ());
19797 } else {
@@ -202,31 +102,6 @@ public String executeQuery(String query) {
202102 }
203103 }
204104
205- private static class VariablesDeserializer extends JsonDeserializer <Map <String , Object >> {
206-
207- @ Override public Map <String , Object > deserialize (JsonParser p , DeserializationContext ctxt )
208- throws IOException {
209- Object o = p .readValueAs (Object .class );
210- if (o instanceof Map ) {
211- return (Map <String , Object >) o ;
212- } else if (o instanceof String ) {
213- return new ObjectMapper ().readValue ((String ) o , new TypeReference <Map <String , Object >>() {});
214- } else {
215- throw new RuntimeJsonMappingException ("variables should be either an object or a string" );
216- }
217- }
218- }
219-
220- public static class Request {
221- @ Getter @ Setter
222- private String query ;
223- @ Getter @ Setter @ JsonDeserialize (using = VariablesDeserializer .class )
224- private Map <String , Object > variables = new HashMap <>();
225- @ Getter @ Setter
226- private String operationName ;
227-
228- }
229-
230105 @ Override
231106 protected void doGet (HttpServletRequest req , HttpServletResponse resp ) throws ServletException , IOException {
232107 GraphQLContext context = createContext (Optional .of (req ), Optional .of (resp ));
@@ -235,10 +110,10 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
235110 path = req .getServletPath ();
236111 }
237112 if (path .contentEquals ("/schema.json" )) {
238- query (CharStreams .toString (new InputStreamReader (GraphQLServlet .class .getResourceAsStream ("introspectionQuery" ))), null , new HashMap <>(), schema , req , resp , context );
113+ query (CharStreams .toString (new InputStreamReader (GraphQLServlet .class .getResourceAsStream ("introspectionQuery" ))), null , new HashMap <>(), getSchema () , req , resp , context );
239114 } else {
240115 if (req .getParameter ("q" ) != null ) {
241- query (req .getParameter ("q" ), null , new HashMap <>(), readOnlySchema , req , resp , context );
116+ query (req .getParameter ("q" ), null , new HashMap <>(), getReadOnlySchema () , req , resp , context );
242117 } else if (req .getParameter ("query" ) != null ) {
243118 Map <String ,Object > variables = new HashMap <>();
244119 if (req .getParameter ("variables" ) != null ) {
@@ -248,7 +123,7 @@ protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws Se
248123 if (req .getParameter ("operationName" ) != null ) {
249124 operationName = req .getParameter ("operationName" );
250125 }
251- query (req .getParameter ("query" ), operationName , variables , readOnlySchema , req , resp , context );
126+ query (req .getParameter ("query" ), operationName , variables , getReadOnlySchema () , req , resp , context );
252127 }
253128 }
254129 }
@@ -283,7 +158,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws S
283158 if (variables == null ) {
284159 variables = new HashMap <>();
285160 }
286- query (request .query , request .operationName , variables , schema , req , resp , context );
161+ query (request .query , request .operationName , variables , getSchema () , req , resp , context );
287162 }
288163
289164 private void query (String query , String operationName , Map <String , Object > variables , GraphQLSchema schema , HttpServletRequest req , HttpServletResponse resp , GraphQLContext context ) throws IOException {
@@ -296,14 +171,16 @@ public Void run() {
296171 }
297172 });
298173 } else {
299- GraphQLVariables vars = new GraphQLVariables (schema , query , variables );
300- ExecutionResult result = new GraphQL (schema , executionStrategyProvider .getExecutionStrategy ()).execute (query , operationName , context , vars );
174+ Map <String , Object > vars = transformVariables (schema , query , variables );
175+ operationListeners .forEach (l -> l .beforeGraphQLOperation (context , operationName , query , vars ));
176+
177+ ExecutionResult result = new GraphQL (schema , getExecutionStrategy ()).execute (query , operationName , context , vars );
301178 resp .setContentType ("application/json;charset=utf-8" );
302179 if (result .getErrors ().isEmpty ()) {
303180 Map <String , Object > dict = new HashMap <>();
304181 dict .put ("data" , result .getData ());
305182 resp .getWriter ().write (new ObjectMapper ().writeValueAsString (dict ));
306- operationListeners .forEach (l -> l .onGraphQLOperation (context , operationName , query , vars , result .getData ()));
183+ operationListeners .forEach (l -> l .onSuccessfulGraphQLOperation (context , operationName , query , vars , result .getData ()));
307184 } else {
308185 result .getErrors ().stream ().
309186 filter (error -> (error instanceof ExceptionWhileDataFetching )).
@@ -315,8 +192,7 @@ public Void run() {
315192 dict .put ("errors" , errors );
316193
317194 resp .getWriter ().write (new ObjectMapper ().writeValueAsString (dict ));
318- operationListeners .forEach (l -> l .onFailedGraphQLOperation (context , operationName , query , vars ,
319- result .getErrors ()));
195+ operationListeners .forEach (l -> l .onFailedGraphQLOperation (context , operationName , query , vars , result .getErrors ()));
320196 }
321197 }
322198 }
@@ -326,4 +202,28 @@ private List<GraphQLError> getGraphQLErrors(ExecutionResult result) {
326202 filter (error -> error instanceof InvalidSyntaxError || error instanceof ValidationError ).
327203 collect (Collectors .toList ());
328204 }
205+
206+ protected static class VariablesDeserializer extends JsonDeserializer <Map <String , Object >> {
207+ @ Override
208+ public Map <String , Object > deserialize (JsonParser p , DeserializationContext ctxt ) throws IOException {
209+ Object o = p .readValueAs (Object .class );
210+ if (o instanceof Map ) {
211+ return (Map <String , Object >) o ;
212+ } else if (o instanceof String ) {
213+ return new ObjectMapper ().readValue ((String ) o , new TypeReference <Map <String , Object >>() {});
214+ } else {
215+ throw new RuntimeJsonMappingException ("variables should be either an object or a string" );
216+ }
217+ }
218+ }
219+
220+ public static class Request {
221+ @ Getter
222+ @ Setter
223+ private String query ;
224+ @ Getter @ Setter @ JsonDeserialize (using = GraphQLServlet .VariablesDeserializer .class )
225+ private Map <String , Object > variables = new HashMap <>();
226+ @ Getter @ Setter
227+ private String operationName ;
228+ }
329229}
0 commit comments