Skip to content

Commit 8ad0f46

Browse files
author
etienne-sf
committed
Issue #199: code would not compile if the GraphQL schema is too big
1 parent 76a2d06 commit 8ad0f46

File tree

11 files changed

+235
-82
lines changed

11 files changed

+235
-82
lines changed

TODO.md

Lines changed: 5 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,20 @@
11
Here are the next tasks listed, as a TODO list:
22

3-
## TODO list for the 2.x branch
4-
* Document it, then release it
5-
63
## TODO list for the 1.x branch
7-
* Issue #184: Unable to use custom scalars upgrade from 1.18.9 to 1.18.10 (Or 2.0RC1)
4+
* #184 et #198: issues with custom scalars which type is not in the plugin dependencies
5+
* Add an option to add the Serializable interface for generated POJOs
6+
* ==> Done. Answer to the related issue to do, when released
87
* Idea #183: replace hard coded fields by maps. This would save memory for objects with lots of field (4000 fields in the identified use case)
9-
* Add or correct the URL in the README and in the wiki's home.
108
* [Gradle] issue #14 : build is not compatible with the `--configuration-cache` gradle parameter (experimental feature)
119
* `DirectiveRegistryInitializer`:
1210
* separate it from each schema (e.g.: allGraphQlCases client)
1311
* initialize it only once (not at each creation of a GraphQLRequest)
1412
* Tutorial: add the documentation about the application.yml file
1513
* Especially: `spring.main.web-application-type = none`
16-
* Execute FieldTest.test_Issue1114_checkGenerateCode() (in plugin-locic, com.graphql_java_generator.plugin.language)
17-
* Remove Deprecated getStringContentXxx methods in InputParameter
18-
* Subscription: the client remain active after a `Connection refused` (even if the main thread stops)
19-
* When using Web Sockets for the graphql-transport-ws protocol, the Web Socket can be tested with the Ping/Pong messages
20-
* When using Web Sockets, it should be closed when the last subscription is unsubscribed. Issues :
21-
* Be sure that no subscription is starting at the same time (probability is low, but...)
22-
* update the RequestExecutionSpringReactiveImpl.webSocketHandler ? Or mark the webSocketHandler as completed ?
23-
* A way could be that the webSocketHandler marks itself as completed (in a synchronized method), and closes the session, so that RequestExecutionSpringReactiveImpl knows that it needs to open a new one.
14+
* Execute FieldTest.test_Issue1114_checkGenerateCode() (in plugin-logic, com.graphql_java_generator.plugin.language)
2415
* add a _HowTo compile page_ on the wiki (to build the plugin project, Java 9 or latter is needed, even if the built plugin is compatible with Java 8)
25-
* Check if spring-boot-starter-security is really needed. It should be added when a project needs OAuth2.
2616
* @EnableGraphQLRepositories: replace the string (that contains the package name) by a class (so that when changing a package name, the code is still valid)
27-
* Check comment of the executor method. The line below is wrong:
28-
* the request contains the full string that <B><U>follows</U></B> the query/mutation/subscription keyword.<BR/>
29-
* Add an option to add the Serializable interface for generated POJOs
30-
* [Done in the plugin, waiting for the graphql PR acceptance] Publish a PR to have a per request cache, in graphql-java-spring
31-
* Done. Waiting for the PR to be accepted (and then a new release)
32-
* In the meantime: the graphql-java-spring is forked within the graphql-maven-plugin project
33-
* Remove the SubscriptionClientWebSocket class
34-
* Allow to control the list of schema files, and their order (necessary to properly manage the extend keyword)
35-
* Add a description of the GraphQL mojo
36-
* Waiting for [issue 2055](https://github.com/graphql-java/graphql-java/issues/2055) to be solved. Some test cases can then be run again (see the allGraphQLCases.graphqls file)
37-
* [server side] Check graphql-java correction for issue 1844 (Directive values of type 'EnumValue' are not supported yet), waiting for graphql-java v16 release
17+
* Add a description for the GraphQL mojo
3818
* enum values may currently not be a java keyword (seems to be a graphql-java limitation). issue to raise in the graphql-java project
3919
* Document generateJPAAnnotation
4020

41-
## TODO List for 2.0 version:
42-
* Rename the QueryExecutor (and its implementations) into RequestExecutor
43-
* Remove the query/mutation/subscription Response type (currently deprecated)
44-
* copyRuntimeSources: false should be the default value (change to be done in the tutorial and the client-dependency)
45-
* separateUtilityClasses: true should be the default value
46-
* Add a generateDeprecatedRequestResponse plugin parameter. Default value to true (no more XxxxResponse would be generated). With a value of true, the XxxxResponse would still be generated for compatibility with old code.
47-
* Remove the `graphql-java-runtime`, and put the runtime in either the `graphql-java-server-dependencies` or `graphql-java-client-dependencies`
48-
49-
50-
Investigate DTO for database mapping (done in the Gradle and Maven tutorials):
51-
https://stackoverflow.com/questions/60456804/how-to-use-graphql-with-jpa-if-schema-is-different-to-database-structure
52-
https://stackoverflow.com/questions/58801227/graphql-tools-map-entity-type-to-graphql-type/58809449#58809449
53-
54-
55-
Tutorials:
56-
- https://www.howtographql.com/
57-
- dev zone
58-

graphql-java-client-runtime/src/main/java/com/graphql_java_generator/client/GraphqlClientUtils.java

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ public class GraphqlClientUtils {
3737
/** A singleton without Spring */
3838
public static GraphqlClientUtils graphqlClientUtils = new GraphqlClientUtils();
3939

40+
/**
41+
* graphqlTypeMappingImplementations will contain the GraphQLTypeMapping.getJavaClass(String) method for each schema
42+
* used in this execution. This map is used to avoid to dynamically find this method, each time it has to be called.
43+
*
44+
* @see #getClass(String, String, String)
45+
*/
46+
private static Map<String, Method> graphqlTypeMappingGetJavaClassImplementations = new HashMap<>();
47+
4048
Pattern graphqlNamePattern = Pattern.compile("^[_A-Za-z][_0-9A-Za-z]*$");
4149

4250
GraphqlUtils graphqlUtils = new GraphqlUtils();
@@ -49,13 +57,13 @@ public class GraphqlClientUtils {
4957

5058
public GraphqlClientUtils() {
5159
// Add of all predefined scalars
52-
scalars.add(String.class);
53-
scalars.add(int.class);
54-
scalars.add(Integer.class);
55-
scalars.add(float.class);
56-
scalars.add(Float.class);
57-
scalars.add(boolean.class);
58-
scalars.add(Boolean.class);
60+
this.scalars.add(String.class);
61+
this.scalars.add(int.class);
62+
this.scalars.add(Integer.class);
63+
this.scalars.add(float.class);
64+
this.scalars.add(Float.class);
65+
this.scalars.add(boolean.class);
66+
this.scalars.add(Boolean.class);
5967
}
6068

6169
/**
@@ -71,7 +79,7 @@ public void checkName(String graphqlIdentifier) throws GraphQLRequestPreparation
7179
if (graphqlIdentifier == null) {
7280
throw new NullPointerException("A GraphQL identifier may not be null");
7381
}
74-
Matcher m = graphqlNamePattern.matcher(graphqlIdentifier);
82+
Matcher m = this.graphqlNamePattern.matcher(graphqlIdentifier);
7583
if (!m.matches()) {
7684
throw new GraphQLRequestPreparationException("'" + graphqlIdentifier + "' is not a valid GraphQL name");
7785
}
@@ -201,15 +209,16 @@ public boolean isScalar(AccessibleObject fieldOrMethod) throws GraphQLRequestPre
201209
* @return
202210
*/
203211
public Class<?> getClass(String packageName, String graphQLTypeName, String schema) {
212+
String graphQLTypeMappingClassname;
204213

205214
// First case, the simplest: standard GraphQL type
206-
if ("Boolean".equals(graphQLTypeName) || "boolean".equals(graphQLTypeName))
215+
if ("Boolean".equals(graphQLTypeName) || "boolean".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
207216
return Boolean.class;
208-
else if ("Integer".equals(graphQLTypeName) || "Int".equals(graphQLTypeName))
217+
else if ("Integer".equals(graphQLTypeName) || "Int".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
209218
return Integer.class;
210-
else if ("String".equals(graphQLTypeName) || "UUID".equals(graphQLTypeName))
219+
else if ("String".equals(graphQLTypeName) || "UUID".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
211220
return String.class;
212-
else if ("Float".equals(graphQLTypeName) || "Double".equals(graphQLTypeName))
221+
else if ("Float".equals(graphQLTypeName) || "Double".equals(graphQLTypeName)) //$NON-NLS-1$ //$NON-NLS-2$
213222
return Double.class;
214223

215224
// Then custom scalars
@@ -223,23 +232,57 @@ else if ("Float".equals(graphQLTypeName) || "Double".equals(graphQLTypeName))
223232

224233
// Then other GraphQL types. This types should be linked to a generated java class. So we search for a class of
225234
// this name in the given package.
235+
// lookup the java class corresponding to the graphql type from the generated GraphQLTypeMapping
226236
try {
227-
// lookup the java class corresponding to the graphql type from the generated GraphQLTypeMapping
228-
return (Class<?>) getClass().getClassLoader().loadClass(packageName + ".GraphQLTypeMapping")
229-
.getMethod("getJavaClass", String.class).invoke(null, graphQLTypeName);
230-
} catch (ClassNotFoundException e) {
231-
// If GraphqlTypeMapping does not exist (in some tests), fallback to using the type name.
232-
final String className = packageName + "." + GraphqlUtils.graphqlUtils.getJavaName(graphQLTypeName);
237+
return (Class<?>) getGetJavaClassMethod(packageName).invoke(null, graphQLTypeName);
238+
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
239+
throw new RuntimeException("Error while calling GraphQLTypeMapping.getJavaClass(\"" + packageName //$NON-NLS-1$
240+
+ "\"): ClassNotFoundException (" + e.getMessage() + ")", e); //$NON-NLS-1$ //$NON-NLS-2$
241+
}
242+
}
243+
244+
/**
245+
* This method loads tries to load the GraphQLTypeMapping.getJavaClass(String) method in the
246+
* {@link #graphqlTypeMappingGetJavaClassImplementations} map, and return it, so that this method is loaded only
247+
* once for each GraphQL schema managed in this execution.
248+
*
249+
* @param packageName
250+
* @return
251+
*/
252+
private Method getGetJavaClassMethod(String packageName) {
253+
Method ret = graphqlTypeMappingGetJavaClassImplementations.get(packageName);
254+
255+
if (ret == null) {
256+
String graphQLTypeMappingClassname1 = packageName + ".GraphQLTypeMapping"; //$NON-NLS-1$
257+
String graphQLTypeMappingClassname2 = packageName + ".util.GraphQLTypeMapping"; //$NON-NLS-1$
258+
259+
// Let's find the GraphQLTypeMapping class.
260+
Class<?> clazz;
261+
try {
262+
clazz = getClass().getClassLoader().loadClass(graphQLTypeMappingClassname1);
263+
} catch (ClassNotFoundException e) {
264+
try {
265+
clazz = getClass().getClassLoader().loadClass(graphQLTypeMappingClassname2);
266+
} catch (ClassNotFoundException e1) {
267+
throw new RuntimeException(
268+
"ClassNotFoundException: could find neither '" + graphQLTypeMappingClassname1 //$NON-NLS-1$
269+
+ "' class nor '" + graphQLTypeMappingClassname2 + "' class"); //$NON-NLS-1$ //$NON-NLS-2$
270+
}
271+
}
272+
273+
// let's find its getJavaClass method
233274
try {
234-
return getClass().getClassLoader().loadClass(className);
235-
} catch (ClassNotFoundException ex) {
236-
throw new RuntimeException(
237-
"Could not load class '" + className + "' for type '" + graphQLTypeName + "'", e);
275+
ret = clazz.getMethod("getJavaClass", String.class); //$NON-NLS-1$
276+
} catch (NoSuchMethodException | SecurityException e) {
277+
throw new RuntimeException(e.getClass().getSimpleName()
278+
+ ": could find the 'getJavaClass' method in the " + clazz.getName() + " class"); //$NON-NLS-1$ //$NON-NLS-2$
238279
}
239-
} catch (ClassCastException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
240-
throw new RuntimeException("Could not get the class for type '" + graphQLTypeName + "' from '"
241-
+ (packageName + ".GraphQLTypeMapping") + "'", e);
280+
281+
// Let's store it, to avoid to have to find it again, latter on.
282+
graphqlTypeMappingGetJavaClassImplementations.put(packageName, ret);
242283
}
284+
285+
return ret;
243286
}
244287

245288
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** Generated by the default template from graphql-java-generator */
2+
package com.graphql_java_generator.domain.client.allGraphQLCases;
3+
4+
import com.graphql_java_generator.util.GraphqlUtils;
5+
6+
public class GraphQLTypeMapping {
7+
8+
public static Class<?> getJavaClass(String typeName) {
9+
try {
10+
return GraphQLTypeMapping.class.getClassLoader().loadClass(GraphQLTypeMapping.class.getPackage().getName()
11+
+ "." + GraphqlUtils.graphqlUtils.getJavaName(typeName));
12+
} catch (ClassNotFoundException e) {
13+
throw new RuntimeException("Internal test error: could not load the '"
14+
+ GraphQLTypeMapping.class.getPackage().getName() + "." + typeName + "' class", e);
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** Generated by the default template from graphql-java-generator */
2+
package com.graphql_java_generator.domain.client.forum;
3+
4+
import com.graphql_java_generator.util.GraphqlUtils;
5+
6+
public class GraphQLTypeMapping {
7+
8+
public static Class<?> getJavaClass(String typeName) {
9+
try {
10+
return GraphQLTypeMapping.class.getClassLoader().loadClass(GraphQLTypeMapping.class.getPackage().getName()
11+
+ "." + GraphqlUtils.graphqlUtils.getJavaName(typeName));
12+
} catch (ClassNotFoundException e) {
13+
throw new RuntimeException("Internal test error: could not load the '"
14+
+ GraphQLTypeMapping.class.getPackage().getName() + "." + typeName + "' class", e);
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/** Generated by the default template from graphql-java-generator */
2+
package com.graphql_java_generator.domain.client.starwars;
3+
4+
import com.graphql_java_generator.util.GraphqlUtils;
5+
6+
public class GraphQLTypeMapping {
7+
8+
public static Class<?> getJavaClass(String typeName) {
9+
try {
10+
return GraphQLTypeMapping.class.getClassLoader().loadClass(GraphQLTypeMapping.class.getPackage().getName()
11+
+ "." + GraphqlUtils.graphqlUtils.getJavaName(typeName));
12+
} catch (ClassNotFoundException e) {
13+
throw new RuntimeException("Internal test error: could not load the '"
14+
+ GraphQLTypeMapping.class.getPackage().getName() + "." + typeName + "' class", e);
15+
}
16+
}
17+
}

graphql-maven-plugin-logic/src/main/java/com/graphql_java_generator/plugin/CodeTemplate.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public enum CodeTemplate {
3838
SUBSCRIPTION(CodeTemplateScope.CLIENT, "templates/client_subscription_type.vm.java"), //
3939
SUBSCRIPTION_EXECUTOR(CodeTemplateScope.CLIENT, "templates/client_subscription_executor.vm.java"), //
4040
TYPE_MAPPING(CodeTemplateScope.CLIENT, "templates/client_type_mapping.vm.java"), //
41+
TYPE_MAPPING_CSV(CodeTemplateScope.CLIENT, "templates/client_type_mapping.vm.csv"), // //$NON-NLS-1$
4142

4243
// Server files (alphabetic order)
4344
BATCH_LOADER_DELEGATE_IMPL(CodeTemplateScope.SERVER, "templates/server_BatchLoaderDelegateImpl.vm.java"), //

graphql-maven-plugin-logic/src/main/java/com/graphql_java_generator/plugin/generate_code/GenerateCodeGenerator.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ private int generateClientFiles() throws IOException {
188188
resolveTemplate(CodeTemplate.SUBSCRIPTION), true);
189189
}
190190

191+
logger.debug("Generating client side mapping from graphql type to java type"); //$NON-NLS-1$
192+
i += generateClientTypeMapping();
193+
191194
// Generation of the query/mutation/subscription executor classes
192195
logger.debug("Generating query executors");
193196
i += generateTargetFile(generateCodeDocumentParser.getQueryType(), "executor",
@@ -253,9 +256,6 @@ private int generateClientFiles() throws IOException {
253256
i += generateSpringAutoConfigurationDeclaration();
254257
}
255258

256-
logger.debug("Generating client side mapping from graphql type to java type");
257-
i += generateClientTypeMapping();
258-
259259
return i;
260260
}
261261

@@ -268,10 +268,20 @@ private int generateClientTypeMapping() {
268268
VelocityContext context = getVelocityContext();
269269
context.put("types", generateCodeDocumentParser.getTypes());
270270

271-
generateOneFile(getJavaFile("GraphQLTypeMapping", false), "generating GraphQLTypeMapping", context,
271+
generateOneFile(getJavaFile("GraphQLTypeMapping", true), "generating GraphQLTypeMapping", context,
272272
resolveTemplate(CodeTemplate.TYPE_MAPPING));
273273

274-
return 1;
274+
// Generation of the typeMapping.csv file
275+
String relativePath = "typeMapping" //$NON-NLS-1$
276+
+ ((this.configuration.getSpringBeanSuffix() == null) ? "" : this.configuration.getSpringBeanSuffix())
277+
+ ".csv";
278+
File targetFile = new File(this.configuration.getTargetResourceFolder(), relativePath);
279+
logger.debug("Generating typeMapping.csv into {}", targetFile); //$NON-NLS-1$
280+
targetFile.getParentFile().mkdirs();
281+
generateOneFile(targetFile, "Generating typeMapping.csv", context, //$NON-NLS-1$
282+
resolveTemplate(CodeTemplate.TYPE_MAPPING_CSV));
283+
284+
return 2;
275285
}
276286

277287
/**
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
##
2+
## Velocity template for type GraphQLTypeMapping (client side).
3+
##
4+
## The generated file contains:
5+
## - one line par GraphQL type, that allows to build a map at runtime from the graphql type name to the java class
6+
##
7+
##
8+
## This template has these inputs:
9+
## types A map from graphql name to the java class
10+
##
11+
#foreach ($entry in $types.entrySet())
12+
${entry.key},${entry.value.classFullName}
13+
#end

graphql-maven-plugin-logic/src/main/resources/templates/client_type_mapping.vm.java

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,60 @@
88
## This template has these inputs:
99
## types A map from graphql name to the java class
1010
##
11+
##
12+
##
13+
##
14+
## Maven ignores the default value for springBeanSuffix, and replaces it by a null value. In this case, we replace the value by an empty String
15+
#if (!${configuration.springBeanSuffix}) #set($springBeanSuffix="") #else #set($springBeanSuffix = ${configuration.springBeanSuffix}) #end
16+
##
17+
##
1118
/** Generated by the default template from graphql-java-generator */
12-
package ${configuration.packageName};
19+
package ${packageUtilName};
20+
21+
import java.io.BufferedReader;
22+
import java.io.IOException;
23+
import java.io.InputStreamReader;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
27+
import org.springframework.util.ClassUtils;
1328

1429
public class GraphQLTypeMapping {
1530

16-
public static Class<?> getJavaClass(String typeName) {
17-
switch(typeName) {
18-
#foreach ($entry in $types.entrySet())
19-
case "${entry.key}": return ${entry.value.classFullName}.class;
20-
#end
21-
default:
22-
return null;
31+
private final static Map<String, Class<?>> map = new HashMap<>();
32+
33+
static {
34+
String line;
35+
Class<?> clazz;
36+
ClassLoader classLoader = GraphQLTypeMapping.class.getClassLoader();
37+
38+
final String RESOURCE_PATH = "typeMapping${springBeanSuffix}"
39+
+ ".csv";
40+
41+
try (BufferedReader reader = new BufferedReader(
42+
new InputStreamReader(classLoader.getResourceAsStream(RESOURCE_PATH)))) {
43+
while ((line = reader.readLine()) != null) {
44+
String[] keyValue = line.split(",");
45+
if (keyValue.length != 2) {
46+
throw new RuntimeException("Invalid line in typeMapping.csv: " + line);
47+
}
48+
try {
49+
clazz = ClassUtils.forName(keyValue[1], classLoader);
50+
} catch (Exception e) {
51+
throw new RuntimeException("Error while looking for the class of the type '" + keyValue[0] + "': "
52+
+ e.getClass().getSimpleName() + "-" + e.getMessage(), e);
53+
}
54+
map.put(keyValue[0], clazz);
55+
} // while
56+
} catch (NullPointerException e) {
57+
throw new RuntimeException("NullPointerException while reading type mapping : " + RESOURCE_PATH);
58+
} catch (IOException e) {
59+
throw new RuntimeException("Error while reading type mapping (" + RESOURCE_PATH + "): " + e.getMessage(),
60+
e);
2361
}
2462
}
63+
64+
public static Class<?> getJavaClass(String typeName) {
65+
return map.get(typeName);
66+
}
2567
}

0 commit comments

Comments
 (0)