diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 59efc4f48..7b0a02b2e 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -222,6 +222,14 @@ public static class Builder { private ExecutionId executionId; private AtomicBoolean cancelled = new AtomicBoolean(false); + /** + * Package level access to the graphql context + * @return shhh but it's the graphql context + */ + GraphQLContext graphQLContext() { + return graphQLContext; + } + public Builder query(String query) { this.query = assertNotNull(query, () -> "query can't be null"); return this; diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index a279dab2f..d4d8fb3b6 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -85,6 +85,72 @@ @PublicApi public class GraphQL { + /** + * This allows you to control "unusual" aspects of the GraphQL system + * including some JVM wide settings + *
+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration} object + */ + public static GraphQLUnusualConfiguration unusualConfiguration() { + return new GraphQLUnusualConfiguration(); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *
+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(ExecutionInput executionInput) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(executionInput.getGraphQLContext()); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *
+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(ExecutionInput.Builder executionInputBuilder) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(executionInputBuilder.graphQLContext()); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *
+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(GraphQLContext graphQLContext) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(graphQLContext); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *
+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(GraphQLContext.Builder graphQLContextBuilder) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(graphQLContextBuilder); + } + private final GraphQLSchema graphQLSchema; private final ExecutionStrategy queryStrategy; private final ExecutionStrategy mutationStrategy; diff --git a/src/main/java/graphql/GraphQLContext.java b/src/main/java/graphql/GraphQLContext.java index 48f79ce77..64ec5c406 100644 --- a/src/main/java/graphql/GraphQLContext.java +++ b/src/main/java/graphql/GraphQLContext.java @@ -326,6 +326,15 @@ public Builder put( ); } + public Object get(Object key) { + return map.get(key); + } + + public boolean getBoolean(Object key) { + return Boolean.parseBoolean(String.valueOf(get(key))); + } + + public Builder of( Object key1, Object value1 ) { diff --git a/src/main/java/graphql/GraphQLUnusualConfiguration.java b/src/main/java/graphql/GraphQLUnusualConfiguration.java new file mode 100644 index 000000000..1f99b5848 --- /dev/null +++ b/src/main/java/graphql/GraphQLUnusualConfiguration.java @@ -0,0 +1,396 @@ +package graphql; + +import graphql.execution.instrumentation.dataloader.DelayedDataLoaderDispatcherExecutorFactory; +import graphql.introspection.GoodFaithIntrospection; +import graphql.parser.ParserOptions; +import graphql.schema.PropertyDataFetcherHelper; + +import java.time.Duration; + +import static graphql.Assert.assertNotNull; +import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS; +import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY; +import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING; + +/** + * This allows you to control "unusual" aspects of the GraphQL system + * including some JVM wide settings and some per execution settings + * as well as experimental ones. + *
+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + */ +public class GraphQLUnusualConfiguration { + GraphQLUnusualConfiguration() { + } + + /** + * @return an element that allows you to control JVM wide parsing configuration + */ + public ParserConfig parsing() { + return new ParserConfig(this); + } + + /** + * @return an element that allows you to control JVM wide {@link graphql.schema.PropertyDataFetcher} configuration + */ + public PropertyDataFetcherConfig propertyDataFetching() { + return new PropertyDataFetcherConfig(this); + } + + /** + * @return an element that allows you to control JVM wide configuration + * of {@link graphql.introspection.GoodFaithIntrospection} + */ + public GoodFaithIntrospectionConfig goodFaithIntrospection() { + return new GoodFaithIntrospectionConfig(this); + } + + private static class BaseConfig { + protected final GraphQLUnusualConfiguration configuration; + + private BaseConfig(GraphQLUnusualConfiguration configuration) { + this.configuration = configuration; + } + + /** + * @return an element that allows you to chain multiple configuration elements + */ + public GraphQLUnusualConfiguration then() { + return configuration; + } + } + + public static class ParserConfig extends BaseConfig { + + private ParserConfig(GraphQLUnusualConfiguration configuration) { + super(configuration); + } + + /** + * By default, the Parser will not capture ignored characters. A static holds this default + * value in a JVM wide basis options object. + *
+ * Significant memory savings can be made if we do NOT capture ignored characters, + * especially in SDL parsing. + * + * @return the static default JVM value + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserOptions getDefaultParserOptions() { + return ParserOptions.getDefaultParserOptions(); + } + + /** + * By default, the Parser will not capture ignored characters. A static holds this default + * value in a JVM wide basis options object. + *
+ * Significant memory savings can be made if we do NOT capture ignored characters, + * especially in SDL parsing. So we have set this to false by default. + *
+ * This static can be set to true to allow the behavior of version 16.x or before. + * + * @param options - the new default JVM parser options + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserConfig setDefaultParserOptions(ParserOptions options) { + ParserOptions.setDefaultParserOptions(options); + return this; + } + + + /** + * By default, for operation parsing, the Parser will not capture ignored characters, and it will not capture line comments into AST + * elements . A static holds this default value for operation parsing in a JVM wide basis options object. + * + * @return the static default JVM value for operation parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserOptions getDefaultOperationParserOptions() { + return ParserOptions.getDefaultOperationParserOptions(); + } + + /** + * By default, the Parser will not capture ignored characters or line comments. A static holds this default + * value in a JVM wide basis options object for operation parsing. + *
+ * This static can be set to true to allow the behavior of version 16.x or before. + * + * @param options - the new default JVM parser options for operation parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserConfig setDefaultOperationParserOptions(ParserOptions options) { + ParserOptions.setDefaultOperationParserOptions(options); + return this; + } + + /** + * By default, for SDL parsing, the Parser will not capture ignored characters, but it will capture line comments into AST + * elements. The SDL default options allow unlimited tokens and whitespace, since a DOS attack vector is + * not commonly available via schema SDL parsing. + *
+ * A static holds this default value for SDL parsing in a JVM wide basis options object. + * + * @return the static default JVM value for SDL parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + * @see graphql.schema.idl.SchemaParser + */ + public ParserOptions getDefaultSdlParserOptions() { + return ParserOptions.getDefaultSdlParserOptions(); + } + + /** + * By default, for SDL parsing, the Parser will not capture ignored characters, but it will capture line comments into AST + * elements . A static holds this default value for operation parsing in a JVM wide basis options object. + *
+ * This static can be set to true to allow the behavior of version 16.x or before. + * + * @param options - the new default JVM parser options for SDL parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserConfig setDefaultSdlParserOptions(ParserOptions options) { + ParserOptions.setDefaultSdlParserOptions(options); + return this; + } + } + + public static class PropertyDataFetcherConfig extends BaseConfig { + private PropertyDataFetcherConfig(GraphQLUnusualConfiguration configuration) { + super(configuration); + } + + /** + * PropertyDataFetcher caches the methods and fields that map from a class to a property for runtime performance reasons + * as well as negative misses. + *
+ * However during development you might be using an assistance tool like JRebel to allow you to tweak your code base and this
+ * caching may interfere with this. So you can call this method to clear the cache. A JRebel plugin could
+ * be developed to do just that.
+ */
+ @SuppressWarnings("unused")
+ public PropertyDataFetcherConfig clearReflectionCache() {
+ PropertyDataFetcherHelper.clearReflectionCache();
+ return this;
+ }
+
+ /**
+ * This can be used to control whether PropertyDataFetcher will use {@link java.lang.reflect.Method#setAccessible(boolean)} to gain access to property
+ * values. By default, it PropertyDataFetcher WILL use setAccessible.
+ *
+ * @param flag whether to use setAccessible
+ *
+ * @return the previous value of the flag
+ */
+ public boolean setUseSetAccessible(boolean flag) {
+ return PropertyDataFetcherHelper.setUseSetAccessible(flag);
+ }
+
+ /**
+ * This can be used to control whether PropertyDataFetcher will cache negative lookups for a property for performance reasons. By default it PropertyDataFetcher WILL cache misses.
+ *
+ * @param flag whether to cache misses
+ *
+ * @return the previous value of the flag
+ */
+ public boolean setUseNegativeCache(boolean flag) {
+ return PropertyDataFetcherHelper.setUseNegativeCache(flag);
+ }
+ }
+
+ public static class GoodFaithIntrospectionConfig extends BaseConfig {
+ private GoodFaithIntrospectionConfig(GraphQLUnusualConfiguration configuration) {
+ super(configuration);
+ }
+
+ /**
+ * @return true if good faith introspection is enabled
+ */
+ public boolean isEnabledJvmWide() {
+ return GoodFaithIntrospection.isEnabledJvmWide();
+ }
+
+ /**
+ * This allows you to disable good faith introspection, which is on by default.
+ *
+ * @param enabled the desired state
+ *
+ * @return the previous state
+ */
+ public GoodFaithIntrospectionConfig enabledJvmWide(boolean enabled) {
+ GoodFaithIntrospection.enabledJvmWide(enabled);
+ return this;
+ }
+ }
+
+ /*
+ * ===============================================
+ * Per GraphqlContext code down here
+ * ===============================================
+ */
+
+ @SuppressWarnings("DataFlowIssue")
+ public static class GraphQLContextConfiguration {
+ // it will be one or the other types of GraphQLContext
+ private final GraphQLContext graphQLContext;
+ private final GraphQLContext.Builder graphQLContextBuilder;
+
+ GraphQLContextConfiguration(GraphQLContext graphQLContext) {
+ this.graphQLContext = graphQLContext;
+ this.graphQLContextBuilder = null;
+ }
+
+ GraphQLContextConfiguration(GraphQLContext.Builder graphQLContextBuilder) {
+ this.graphQLContextBuilder = graphQLContextBuilder;
+ this.graphQLContext = null;
+ }
+
+ /**
+ * @return an element that allows you to control incremental support, that is @defer configuration
+ */
+ public IncrementalSupportConfig incrementalSupport() {
+ return new IncrementalSupportConfig(this);
+ }
+
+ /**
+ * @return an element that allows you to precisely control {@link org.dataloader.DataLoader} behavior
+ * in graphql-java.
+ */
+ public DataloaderConfig dataloaderConfig() {
+ return new DataloaderConfig(this);
+ }
+
+ private void put(String named, Object value) {
+ if (graphQLContext != null) {
+ graphQLContext.put(named, value);
+ } else {
+ assertNotNull(graphQLContextBuilder).put(named, value);
+ }
+ }
+
+ private boolean getBoolean(String named) {
+ if (graphQLContext != null) {
+ return graphQLContext.getBoolean(named);
+ } else {
+ return assertNotNull(graphQLContextBuilder).getBoolean(named);
+ }
+ }
+
+ private