From c8e68e653d30355121bf5e017a7bb1e69c0f058f Mon Sep 17 00:00:00 2001 From: Andrea Di Cesare Date: Fri, 30 Aug 2024 17:28:00 +0200 Subject: [PATCH] :sparkles: GQL app cache can be disabled setting configuration option /graphql/app-cache-enabled->false. This helps during testing or development --- .../restheart-default-config-no-mongodb.yml | 3 + .../resources/restheart-default-config.yml | 2 + .../cache/AppDefinitionLoadingCache.java | 59 +++++++++++++++---- .../initializers/GraphAppsInitializer.java | 29 +++++---- .../initializers/GraphAppsUpdater.java | 19 +++++- .../GraphAppDefinitionEscaper.java | 16 ++++- .../GraphAppDefinitionGetUnescaper.java | 16 ++++- .../GraphAppDefinitionPatchChecker.java | 11 +++- .../GraphAppDefinitionPutPostChecker.java | 11 +++- 9 files changed, 132 insertions(+), 34 deletions(-) diff --git a/core/src/main/resources/restheart-default-config-no-mongodb.yml b/core/src/main/resources/restheart-default-config-no-mongodb.yml index 86750e643..76ae7fa7f 100644 --- a/core/src/main/resources/restheart-default-config-no-mongodb.yml +++ b/core/src/main/resources/restheart-default-config-no-mongodb.yml @@ -331,6 +331,9 @@ graphql: uri: /graphql db: restheart collection: gql-apps + app-cache-enabled: true + # app cache entries are automatically revalidated every TTR milliseconds + app-cache-ttr: 60_000 # in msecs # default-limit is used for queries that don't not specify a limit default-limit: 100 # max-limit is the maximum value for a Query limit diff --git a/core/src/main/resources/restheart-default-config.yml b/core/src/main/resources/restheart-default-config.yml index d7f9239d7..362d8faaf 100644 --- a/core/src/main/resources/restheart-default-config.yml +++ b/core/src/main/resources/restheart-default-config.yml @@ -318,6 +318,8 @@ graphql: uri: /graphql db: restheart collection: gql-apps + # app cache can be disabled if needed, such as during testing or development + app-cache-enabled: true # app cache entries are automatically revalidated every TTR milliseconds app-cache-ttr: 60_000 # in msecs # default-limit is used for queries that don't not specify a limit diff --git a/graphql/src/main/java/org/restheart/graphql/cache/AppDefinitionLoadingCache.java b/graphql/src/main/java/org/restheart/graphql/cache/AppDefinitionLoadingCache.java index 4fe287829..b5848f89b 100644 --- a/graphql/src/main/java/org/restheart/graphql/cache/AppDefinitionLoadingCache.java +++ b/graphql/src/main/java/org/restheart/graphql/cache/AppDefinitionLoadingCache.java @@ -20,13 +20,19 @@ */ package org.restheart.graphql.cache; +import java.util.Map; + import org.restheart.cache.Cache; import org.restheart.cache.CacheFactory; import org.restheart.cache.LoadingCache; +import org.restheart.configuration.Configuration; import org.restheart.graphql.GraphQLAppDefNotFoundException; import org.restheart.graphql.GraphQLIllegalAppDefinitionException; import org.restheart.graphql.models.GraphQLApp; +import org.restheart.plugins.Inject; +import org.restheart.plugins.OnInit; import org.restheart.plugins.PluginRecord; +import org.restheart.plugins.PluginsRegistry; import org.restheart.plugins.Provider; import org.restheart.plugins.RegisterPlugin; import org.restheart.utils.LambdaUtils; @@ -34,6 +40,28 @@ @RegisterPlugin(name="gql-app-definition-cache", description="provides access to the GQL App Definition cache") public class AppDefinitionLoadingCache implements Provider { private static final long MAX_CACHE_SIZE = 100_000; + private static boolean enabled = true; + + @Inject("rh-config") + private Configuration config; + + @Inject("registry") + private PluginsRegistry registry; + + @OnInit + public void onInit() { + Map graphqlArgs = config.getOrDefault("graphql", null); + if (graphqlArgs != null) { + enabled = isGQLSrvEnabled() && argOrDefault(graphqlArgs, "app-cache-enabled", true);; + } else { + enabled = false; + } + } + + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } private static final LoadingCache CACHE = CacheFactory.createLocalLoadingCache(MAX_CACHE_SIZE, Cache.EXPIRE_POLICY.NEVER, 0, appURI -> { @@ -52,25 +80,32 @@ public static LoadingCache getCache() { } public static GraphQLApp getLoading(String appURI) throws GraphQLAppDefNotFoundException, GraphQLIllegalAppDefinitionException { - var _app = CACHE.get(appURI); + if (enabled) { + var _app = CACHE.get(appURI); - if (_app != null && _app.isPresent()){ - return _app.get(); - } else { - try { - _app = CACHE.getLoading(appURI); - } catch (Exception e) { - throw new GraphQLIllegalAppDefinitionException(e.getMessage(), e); - } - - if (_app != null && _app.isPresent()) { + if (_app != null && _app.isPresent()){ return _app.get(); } else { - throw new GraphQLAppDefNotFoundException("GQL App Definition for uri " + appURI + " not found. "); + try { + _app = CACHE.getLoading(appURI); + } catch (Exception e) { + throw new GraphQLIllegalAppDefinitionException(e.getMessage(), e); + } + + if (_app != null && _app.isPresent()) { + return _app.get(); + } else { + throw new GraphQLAppDefNotFoundException("GQL App Definition for uri " + appURI + " not found. "); + } } + } else { + return AppDefinitionLoader.load(appURI); } } + /** + * Implementation of the get() method of the Provider interface + */ @Override public LoadingCache get(PluginRecord caller) { return CACHE; diff --git a/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsInitializer.java b/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsInitializer.java index e8fcdccbb..951e196b0 100644 --- a/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsInitializer.java +++ b/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsInitializer.java @@ -20,26 +20,25 @@ */ package org.restheart.graphql.initializers; -import org.restheart.configuration.Configuration; -import org.restheart.configuration.ConfigurationException; -import org.restheart.graphql.GraphQLService; -import org.restheart.plugins.Inject; -import org.restheart.plugins.OnInit; -import org.restheart.plugins.RegisterPlugin; - import java.util.Map; import org.bson.BsonDocument; +import org.restheart.configuration.Configuration; +import org.restheart.configuration.ConfigurationException; import org.restheart.graphql.GraphQLIllegalAppDefinitionException; +import org.restheart.graphql.GraphQLService; import org.restheart.graphql.cache.AppDefinitionLoadingCache; import org.restheart.graphql.models.builder.AppBuilder; import org.restheart.plugins.Initializer; - -import com.mongodb.client.MongoClient; - +import org.restheart.plugins.Inject; +import org.restheart.plugins.OnInit; +import org.restheart.plugins.PluginsRegistry; +import org.restheart.plugins.RegisterPlugin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.mongodb.client.MongoClient; + @RegisterPlugin(name="graphAppsInitializer", description = "initializes and caches all GQL Apps at boot timeGraphQL", enabledByDefault = true @@ -58,6 +57,9 @@ public class GraphAppsInitializer implements Initializer { @Inject("mclient") private MongoClient mclient; + @Inject("registry") + private PluginsRegistry registry; + @OnInit public void onInit() { try { @@ -65,7 +67,7 @@ public void onInit() { if (graphqlArgs != null) { this.db = arg(graphqlArgs, "db"); this.coll = arg(graphqlArgs, "collection"); - this.enabled = true; + this.enabled = isGQLSrvEnabled() && argOrDefault(graphqlArgs, "app-cache-enabled", true);; } else { this.enabled = false; } @@ -74,6 +76,11 @@ public void onInit() { } } + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } + @Override public void init() { if (this.enabled) { diff --git a/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsUpdater.java b/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsUpdater.java index 8e967b3fd..6217d11ed 100644 --- a/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsUpdater.java +++ b/graphql/src/main/java/org/restheart/graphql/initializers/GraphAppsUpdater.java @@ -34,6 +34,7 @@ import org.restheart.plugins.Initializer; import org.restheart.plugins.Inject; import org.restheart.plugins.OnInit; +import org.restheart.plugins.PluginsRegistry; import org.restheart.plugins.RegisterPlugin; import org.restheart.utils.ThreadsUtils; import org.slf4j.Logger; @@ -47,6 +48,7 @@ public class GraphAppsUpdater implements Initializer { private static final Logger LOGGER = LoggerFactory.getLogger(GraphAppsUpdater.class); private static final long DEFAULT_TTR = 60_000; // in milliseconds private long TTR = DEFAULT_TTR; + private boolean enabled = true; @Inject("rh-config") private Configuration config; @@ -54,22 +56,33 @@ public class GraphAppsUpdater implements Initializer { @Inject("gql-app-definition-cache") LoadingCache gqlAppDefCache; + @Inject("registry") + private PluginsRegistry registry; + @OnInit public void onInit() { Map graphqlArgs = config.getOrDefault("graphql", null); if (graphqlArgs != null) { + this.enabled = isGQLSrvEnabled() && argOrDefault(graphqlArgs, "app-cache-enabled", true); this.TTR = argOrDefault(graphqlArgs, "app-cache-ttr", 60_000); } else { this.TTR = DEFAULT_TTR; } } + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } + @Override public void init() { - Executors.newSingleThreadScheduledExecutor() - .scheduleAtFixedRate(() -> ThreadsUtils.virtualThreadsExecutor() - .execute(() -> this.revalidateCacheEntries()), TTR, TTR, TimeUnit.MILLISECONDS); + if (this.enabled) { + Executors.newSingleThreadScheduledExecutor() + .scheduleAtFixedRate(() -> ThreadsUtils.virtualThreadsExecutor() + .execute(() -> this.revalidateCacheEntries()), TTR, TTR, TimeUnit.MILLISECONDS); + } } private void revalidateCacheEntries() { diff --git a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionEscaper.java b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionEscaper.java index 7ae470411..9dc66d9ad 100644 --- a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionEscaper.java +++ b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionEscaper.java @@ -20,18 +20,20 @@ */ package org.restheart.graphql.interceptors; +import java.util.Map; + import org.restheart.configuration.Configuration; import org.restheart.configuration.ConfigurationException; import org.restheart.exchange.MongoRequest; import org.restheart.exchange.MongoResponse; import org.restheart.graphql.GraphQLService; import org.restheart.plugins.Inject; +import static org.restheart.plugins.InterceptPoint.REQUEST_AFTER_AUTH; import org.restheart.plugins.MongoInterceptor; import org.restheart.plugins.OnInit; +import org.restheart.plugins.PluginsRegistry; import org.restheart.plugins.RegisterPlugin; import org.restheart.utils.BsonUtils; -import java.util.Map; -import static org.restheart.plugins.InterceptPoint.REQUEST_AFTER_AUTH; @RegisterPlugin(name="graphAppDefinitionEscaper", @@ -48,6 +50,9 @@ public class GraphAppDefinitionEscaper implements MongoInterceptor { @Inject("rh-config") private Configuration config; + @Inject("registry") + private PluginsRegistry registry; + @OnInit public void init() { try { @@ -55,7 +60,7 @@ public void init() { if (graphqlArgs != null) { this.db = arg(graphqlArgs, "db"); this.coll = arg(graphqlArgs, "collection"); - this.enabled = true; + this.enabled = isGQLSrvEnabled(); } else { this.enabled = false; } @@ -64,6 +69,11 @@ public void init() { } } + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } + @Override public void handle(MongoRequest request, MongoResponse response) throws Exception { request.setContent(BsonUtils.escapeKeys(request.getContent(), true, true)); diff --git a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionGetUnescaper.java b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionGetUnescaper.java index 6acbb5523..6d0647f2a 100644 --- a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionGetUnescaper.java +++ b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionGetUnescaper.java @@ -20,18 +20,20 @@ */ package org.restheart.graphql.interceptors; +import java.util.Map; + import org.restheart.configuration.Configuration; import org.restheart.configuration.ConfigurationException; import org.restheart.exchange.MongoRequest; import org.restheart.exchange.MongoResponse; import org.restheart.graphql.GraphQLService; import org.restheart.plugins.Inject; +import static org.restheart.plugins.InterceptPoint.RESPONSE; import org.restheart.plugins.MongoInterceptor; import org.restheart.plugins.OnInit; +import org.restheart.plugins.PluginsRegistry; import org.restheart.plugins.RegisterPlugin; import org.restheart.utils.BsonUtils; -import java.util.Map; -import static org.restheart.plugins.InterceptPoint.RESPONSE; @RegisterPlugin(name="gaphAppDefinitionGetUnescaper", description = "unescapes $ prefixed keys in GraphQL application definitions", @@ -47,6 +49,9 @@ public class GraphAppDefinitionGetUnescaper implements MongoInterceptor { @Inject("rh-config") private Configuration config; + @Inject("registry") + private PluginsRegistry registry; + @OnInit public void init() { try { @@ -54,7 +59,7 @@ public void init() { if (graphqlArgs != null) { this.db = arg(graphqlArgs, "db"); this.coll = arg(graphqlArgs, "collection"); - this.enabled = true; + this.enabled = isGQLSrvEnabled(); } else { this.enabled = false; } @@ -63,6 +68,11 @@ public void init() { } } + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } + @Override public void handle(MongoRequest request, MongoResponse response) throws Exception { response.setContent(BsonUtils.unescapeKeys(response.getContent())); diff --git a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPatchChecker.java b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPatchChecker.java index 95fa9ed1c..cf2c87495 100644 --- a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPatchChecker.java +++ b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPatchChecker.java @@ -34,6 +34,7 @@ import static org.restheart.plugins.InterceptPoint.RESPONSE; import org.restheart.plugins.MongoInterceptor; import org.restheart.plugins.OnInit; +import org.restheart.plugins.PluginsRegistry; import org.restheart.plugins.RegisterPlugin; import org.restheart.utils.HttpStatus; import org.slf4j.Logger; @@ -60,6 +61,9 @@ public class GraphAppDefinitionPatchChecker implements MongoInterceptor { @Inject("rh-config") private Configuration config; + @Inject("registry") + private PluginsRegistry registry; + @OnInit public void init() { try { @@ -69,13 +73,18 @@ public void init() { this.coll = arg(graphqlArgs, "collection"); this.enabled = true; } else { - this.enabled = false; + this.enabled = isGQLSrvEnabled(); } } catch(ConfigurationException ce) { // nothing to do, using default values } } + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } + @Override public void handle(MongoRequest request, MongoResponse response) throws Exception { if (request.isBulkDocuments()) { diff --git a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPutPostChecker.java b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPutPostChecker.java index aa3b16390..f3d6c7b65 100644 --- a/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPutPostChecker.java +++ b/graphql/src/main/java/org/restheart/graphql/interceptors/GraphAppDefinitionPutPostChecker.java @@ -34,6 +34,7 @@ import static org.restheart.plugins.InterceptPoint.REQUEST_AFTER_AUTH; import org.restheart.plugins.MongoInterceptor; import org.restheart.plugins.OnInit; +import org.restheart.plugins.PluginsRegistry; import org.restheart.plugins.RegisterPlugin; import org.restheart.utils.BsonUtils; import org.restheart.utils.HttpStatus; @@ -57,6 +58,9 @@ public class GraphAppDefinitionPutPostChecker implements MongoInterceptor { @Inject("rh-config") private Configuration config; + @Inject("registry") + private PluginsRegistry registry; + @OnInit public void init() { try { @@ -64,7 +68,7 @@ public void init() { if (graphqlArgs != null) { this.db = arg(graphqlArgs, "db"); this.coll = arg(graphqlArgs, "collection"); - this.enabled = true; + this.enabled = isGQLSrvEnabled(); } else { this.enabled = false; } @@ -73,6 +77,11 @@ public void init() { } } + private boolean isGQLSrvEnabled() { + var gql$ = registry.getServices().stream().filter(s -> s.getName().equals("graphql")).findFirst(); + return gql$.isPresent() && gql$.get().isEnabled(); + } + @Override public void handle(MongoRequest request, MongoResponse response) throws Exception { var content = request.getContent();