diff --git a/.gitignore b/.gitignore index 9b5c92f4..cdecd3b5 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ target/ .project .settings bin +.DS_Store diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/AbstractGraphQLHttpServlet.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/AbstractGraphQLHttpServlet.java index 60b735f6..a786cfdd 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/AbstractGraphQLHttpServlet.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/AbstractGraphQLHttpServlet.java @@ -7,6 +7,7 @@ import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.kickstart.execution.GraphQLRequest; import graphql.kickstart.execution.input.GraphQLSingleInvocationInput; +import graphql.kickstart.servlet.cache.CachingHttpRequestHandlerImpl; import graphql.schema.GraphQLFieldDefinition; import graphql.kickstart.servlet.core.GraphQLMBean; import graphql.kickstart.servlet.core.GraphQLServletListener; @@ -83,7 +84,11 @@ protected GraphQLConfiguration getConfiguration() { public void init() { if (configuration == null) { this.configuration = getConfiguration(); - this.requestHandler = HttpRequestHandlerFactory.create(configuration); + if (configuration.getResponseCacheManager() != null) { + this.requestHandler = new CachingHttpRequestHandlerImpl(configuration); + } else { + this.requestHandler = HttpRequestHandlerFactory.create(configuration); + } } } diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/BatchedQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/BatchedQueryResponseWriter.java index 0cfcb733..39811a1f 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/BatchedQueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/BatchedQueryResponseWriter.java @@ -2,14 +2,17 @@ import graphql.ExecutionResult; import graphql.kickstart.execution.GraphQLObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; +@Slf4j @RequiredArgsConstructor class BatchedQueryResponseWriter implements QueryResponseWriter { @@ -34,6 +37,7 @@ public void write(HttpServletRequest request, HttpServletResponse response) thro String responseContent = responseBuilder.toString(); byte[] contentBytes = responseContent.getBytes(StandardCharsets.UTF_8); + response.setContentLength(contentBytes.length); response.getOutputStream().write(contentBytes); } diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/GraphQLConfiguration.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/GraphQLConfiguration.java index 93a33b23..12311dfd 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/GraphQLConfiguration.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/GraphQLConfiguration.java @@ -4,6 +4,7 @@ import graphql.kickstart.execution.GraphQLObjectMapper; import graphql.kickstart.execution.GraphQLQueryInvoker; import graphql.kickstart.execution.context.ContextSetting; +import graphql.kickstart.servlet.cache.GraphQLResponseCacheManager; import graphql.kickstart.servlet.config.DefaultGraphQLSchemaServletProvider; import graphql.kickstart.servlet.config.GraphQLSchemaServletProvider; import graphql.kickstart.servlet.context.GraphQLServletContextBuilder; @@ -36,12 +37,13 @@ public class GraphQLConfiguration { @Getter private final long asyncTimeout; private final ContextSetting contextSetting; + private final GraphQLResponseCacheManager responseCacheManager; private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactory, GraphQLQueryInvoker queryInvoker, GraphQLObjectMapper objectMapper, List listeners, boolean asyncServletModeEnabled, Executor asyncExecutor, long subscriptionTimeout, long asyncTimeout, ContextSetting contextSetting, - Supplier batchInputPreProcessor) { + Supplier batchInputPreProcessor, GraphQLResponseCacheManager responseCacheManager) { this.invocationInputFactory = invocationInputFactory; this.queryInvoker = queryInvoker; this.graphQLInvoker = queryInvoker.toGraphQLInvoker(); @@ -53,6 +55,7 @@ private GraphQLConfiguration(GraphQLInvocationInputFactory invocationInputFactor this.asyncTimeout = asyncTimeout; this.contextSetting = contextSetting; this.batchInputPreProcessor = batchInputPreProcessor; + this.responseCacheManager = responseCacheManager; } public static GraphQLConfiguration.Builder with(GraphQLSchema schema) { @@ -113,6 +116,10 @@ public BatchInputPreProcessor getBatchInputPreProcessor() { return batchInputPreProcessor.get(); } + public GraphQLResponseCacheManager getResponseCacheManager() { + return responseCacheManager; + } + public static class Builder { private GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder; @@ -126,6 +133,7 @@ public static class Builder { private long asyncTimeout = 30; private ContextSetting contextSetting = ContextSetting.PER_QUERY_WITH_INSTRUMENTATION; private Supplier batchInputPreProcessorSupplier = NoOpBatchInputPreProcessor::new; + private GraphQLResponseCacheManager responseCacheManager; private Builder(GraphQLInvocationInputFactory.Builder invocationInputFactoryBuilder) { this.invocationInputFactoryBuilder = invocationInputFactoryBuilder; @@ -209,6 +217,11 @@ public Builder with(Supplier batchInputPreProcessor) { return this; } + public Builder with(GraphQLResponseCacheManager responseCache) { + this.responseCacheManager = responseCache; + return this; + } + public GraphQLConfiguration build() { return new GraphQLConfiguration( this.invocationInputFactory != null ? this.invocationInputFactory : invocationInputFactoryBuilder.build(), @@ -220,7 +233,8 @@ public GraphQLConfiguration build() { subscriptionTimeout, asyncTimeout, contextSetting, - batchInputPreProcessorSupplier + batchInputPreProcessorSupplier, + responseCacheManager ); } diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java index 6582456d..293720cd 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/HttpRequestHandlerImpl.java @@ -14,12 +14,12 @@ import lombok.extern.slf4j.Slf4j; @Slf4j -class HttpRequestHandlerImpl implements HttpRequestHandler { +public class HttpRequestHandlerImpl implements HttpRequestHandler { private final GraphQLConfiguration configuration; private final GraphQLInvoker graphQLInvoker; - HttpRequestHandlerImpl(GraphQLConfiguration configuration) { + public HttpRequestHandlerImpl(GraphQLConfiguration configuration) { this.configuration = configuration; graphQLInvoker = configuration.getGraphQLInvoker(); } @@ -46,15 +46,19 @@ public void handle(HttpServletRequest request, HttpServletResponse response) thr } } - private void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request, + protected void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request, HttpServletResponse response) throws IOException { GraphQLQueryResult queryResult = invoke(invocationInput, request, response); - QueryResponseWriter queryResponseWriter = QueryResponseWriter.createWriter(queryResult, configuration.getObjectMapper(), - configuration.getSubscriptionTimeout()); + QueryResponseWriter queryResponseWriter = createWriter(invocationInput, queryResult); queryResponseWriter.write(request, response); } + protected QueryResponseWriter createWriter(GraphQLInvocationInput invocationInput, GraphQLQueryResult queryResult) { + return QueryResponseWriter.createWriter(queryResult, configuration.getObjectMapper(), + configuration.getSubscriptionTimeout()); + } + private GraphQLQueryResult invoke(GraphQLInvocationInput invocationInput, HttpServletRequest request, HttpServletResponse response) { if (invocationInput instanceof GraphQLSingleInvocationInput) { diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/QueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/QueryResponseWriter.java index 7139b2fa..41550a95 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/QueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/QueryResponseWriter.java @@ -1,13 +1,14 @@ package graphql.kickstart.servlet; -import graphql.kickstart.execution.GraphQLQueryResult; import graphql.kickstart.execution.GraphQLObjectMapper; -import java.io.IOException; -import java.util.Objects; +import graphql.kickstart.execution.GraphQLQueryResult; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Objects; -interface QueryResponseWriter { +public interface QueryResponseWriter { static QueryResponseWriter createWriter( GraphQLQueryResult result, diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleQueryResponseWriter.java index fa166d3e..4245c19d 100644 --- a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleQueryResponseWriter.java +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/SingleQueryResponseWriter.java @@ -4,10 +4,10 @@ import graphql.kickstart.execution.GraphQLObjectMapper; import lombok.RequiredArgsConstructor; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; @RequiredArgsConstructor class SingleQueryResponseWriter implements QueryResponseWriter { diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/BufferedHttpServletResponse.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/BufferedHttpServletResponse.java new file mode 100644 index 00000000..d34dbb1d --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/BufferedHttpServletResponse.java @@ -0,0 +1,142 @@ +package graphql.kickstart.servlet.cache; + +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; + +@Slf4j +public class BufferedHttpServletResponse extends HttpServletResponseWrapper { + + private static final class BufferedOutputStream extends ServletOutputStream { + + private final OutputStream delegate; + private final ByteArrayOutputStream buf = new ByteArrayOutputStream(); + + public BufferedOutputStream(OutputStream delegate) { + this.delegate = delegate; + } + + public void write(int b) throws IOException { + buf.write(b); + delegate.write(b); + } + + @Override + public void flush() throws IOException { + buf.flush(); + delegate.flush(); + } + + @Override + public void close() throws IOException { + buf.close(); + delegate.close(); + } + + @Override + public boolean isReady() { + return true; + } + + @Override + public void setWriteListener(WriteListener writeListener) { + } + + public byte[] toByteArray() { + return buf.toByteArray(); + } + + } + + private BufferedOutputStream copier; + + private ServletOutputStream outputStream; + private PrintWriter writer; + private String errorMessage; + + public BufferedHttpServletResponse(HttpServletResponse response) { + super(response); + } + + @Override + public void sendError(int sc, String msg) throws IOException { + errorMessage = msg; + super.sendError(sc, msg); + } + + @Override + public void sendError(int sc) throws IOException { + sendError(sc, null); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + if (writer != null) { + throw new IllegalStateException("getWriter() has already been called on this response."); + } + + if (outputStream == null) { + outputStream = getResponse().getOutputStream(); + copier = new BufferedOutputStream(outputStream); + } + + return copier; + } + + @Override + public PrintWriter getWriter() throws IOException { + if (outputStream != null) { + throw new IllegalStateException("getOutputStream() has already been called on this response."); + } + + if (writer == null) { + copier = new BufferedOutputStream(getResponse().getOutputStream()); + writer = new PrintWriter(new OutputStreamWriter(copier, getResponse().getCharacterEncoding()), true); + } + + return writer; + } + + @Override + public void flushBuffer() throws IOException { + if (writer != null) { + writer.flush(); + } else if (copier != null) { + copier.flush(); + } + } + + @Override + public boolean isCommitted() { + return false; + } + + public void close() throws IOException { + if (writer != null) { + writer.close(); + } else if (copier != null) { + copier.close(); + } + } + + public String getErrorMessage() { + return errorMessage; + } + + public byte[] getContentAsByteArray() { + if (copier != null) { + return copier.toByteArray(); + } else { + return new byte[0]; + } + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CacheReader.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CacheReader.java new file mode 100644 index 00000000..52ffaae5 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CacheReader.java @@ -0,0 +1,57 @@ +package graphql.kickstart.servlet.cache; + +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.servlet.HttpRequestHandler; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +@Slf4j +public final class CacheReader { + + private CacheReader() { + } + + /** + * Response from cache if possible, if nothing in cache will not produce any response + * + * @return {@literal true} if response was fulfilled from cache, {@literal false} is cache not found or an error + * occurred while reading value from cache + * @throws IOException if can not read value from the cache + */ + public static boolean responseFromCache(GraphQLInvocationInput invocationInput, + HttpServletRequest request, + HttpServletResponse response, + GraphQLResponseCacheManager cacheManager) throws IOException { + CachedResponse cachedResponse = null; + try { + cachedResponse = cacheManager.get(request, invocationInput); + } catch (Throwable t) { + log.warn("Ignore read from cache, unexpected error happened", t); + } + + if (cachedResponse != null) { + write(response, cachedResponse); + return true; + } + + return false; + } + + private static void write(HttpServletResponse response, CachedResponse cachedResponse) + throws IOException { + if (cachedResponse.isError()) { + response.sendError(cachedResponse.getErrorStatusCode(), cachedResponse.getErrorMessage()); + } else { + response.setContentType(HttpRequestHandler.APPLICATION_JSON_UTF8); + response.setStatus(HttpRequestHandler.STATUS_OK); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentLength(cachedResponse.getContentBytes().length); + response.getOutputStream().write(cachedResponse.getContentBytes()); + } + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachedResponse.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachedResponse.java new file mode 100644 index 00000000..1d1b288b --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachedResponse.java @@ -0,0 +1,72 @@ +package graphql.kickstart.servlet.cache; + +import java.io.Serializable; +import java.util.Objects; + +public class CachedResponse implements Serializable { + + private static final long serialVersionUID = 5894555791705575139L; + + private final byte[] contentBytes; + + private final boolean error; + private final Integer errorStatusCode; + private final String errorMessage; + + private CachedResponse(byte[] contentBytes, boolean error, Integer errorStatusCode, String errorMessage) { + this.contentBytes = contentBytes; + this.error = error; + this.errorStatusCode = errorStatusCode; + this.errorMessage = errorMessage; + } + + /** + * Constructor for success response + * + * @param contentBytes bytes array of graphql json response + */ + public static CachedResponse ofContent(byte[] contentBytes) { + Objects.requireNonNull(contentBytes, "contentBytes can not be null"); + + return new CachedResponse(contentBytes, false, null, null); + } + + /** + * Constructor for error response + * + * @param errorStatusCode the status code for the error response + * @param errorMessage the error message for the error response + */ + public static CachedResponse ofError(int errorStatusCode, String errorMessage) { + return new CachedResponse(null, true, errorStatusCode, errorMessage); + } + + /** + * @return {@literal true} when this request was failed + */ + public boolean isError() { + return error; + } + + /** + * @return the response body for success requests, {@literal null} when {@link #isError()} is {@literal true} + */ + public byte[] getContentBytes() { + return contentBytes; + } + + /** + * @return the response error code + */ + public Integer getErrorStatusCode() { + return errorStatusCode; + } + + /** + * @return the response error message + */ + public String getErrorMessage() { + return errorMessage; + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingHttpRequestHandlerImpl.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingHttpRequestHandlerImpl.java new file mode 100644 index 00000000..2e8d11b7 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingHttpRequestHandlerImpl.java @@ -0,0 +1,57 @@ +package graphql.kickstart.servlet.cache; + +import graphql.kickstart.execution.GraphQLQueryResult; +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.servlet.GraphQLConfiguration; +import graphql.kickstart.servlet.HttpRequestHandlerImpl; +import graphql.kickstart.servlet.QueryResponseWriter; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Objects; + +@Slf4j +public class CachingHttpRequestHandlerImpl extends HttpRequestHandlerImpl { + + private final GraphQLConfiguration configuration; + + public CachingHttpRequestHandlerImpl(GraphQLConfiguration configuration) { + super(configuration); + Objects.requireNonNull(configuration.getResponseCacheManager(), "Response Cache Manager cannot be null"); + this.configuration = configuration; + } + + @Override + protected void execute(GraphQLInvocationInput invocationInput, HttpServletRequest request, + HttpServletResponse response) throws IOException { + // try to return value from cache if cache exists, otherwise processed the query + boolean returnedFromCache; + + try { + returnedFromCache = !CacheReader.responseFromCache( + invocationInput, request, response, configuration.getResponseCacheManager() + ); + } catch (IOException e) { + response.setStatus(STATUS_BAD_REQUEST); + log.warn("unexpected error happened during response from cache", e); + return; + } + + if (!returnedFromCache) { + super.execute(invocationInput, request, response); + } + } + + protected QueryResponseWriter createWriter(GraphQLInvocationInput invocationInput, GraphQLQueryResult queryResult) { + return CachingQueryResponseWriter.createCacheWriter( + queryResult, + configuration.getObjectMapper(), + configuration.getSubscriptionTimeout(), + invocationInput, + configuration.getResponseCacheManager() + ); + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingQueryResponseWriter.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingQueryResponseWriter.java new file mode 100644 index 00000000..0b5513ce --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/CachingQueryResponseWriter.java @@ -0,0 +1,72 @@ +package graphql.kickstart.servlet.cache; + +import graphql.kickstart.execution.GraphQLObjectMapper; +import graphql.kickstart.execution.GraphQLQueryResult; +import graphql.kickstart.execution.input.GraphQLInvocationInput; +import graphql.kickstart.servlet.QueryResponseWriter; +import lombok.extern.slf4j.Slf4j; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class CachingQueryResponseWriter implements QueryResponseWriter { + + static QueryResponseWriter createCacheWriter( + GraphQLQueryResult result, + GraphQLObjectMapper graphQLObjectMapper, + long subscriptionTimeout, + GraphQLInvocationInput invocationInput, + GraphQLResponseCacheManager responseCache + ) { + QueryResponseWriter writer = QueryResponseWriter.createWriter(result, graphQLObjectMapper, subscriptionTimeout); + if (responseCache != null) { + return new CachingQueryResponseWriter(writer, responseCache, invocationInput, result.isError()); + } + return writer; + } + + private final QueryResponseWriter delegate; + private final GraphQLResponseCacheManager responseCache; + private final GraphQLInvocationInput invocationInput; + private final boolean error; + + public CachingQueryResponseWriter(QueryResponseWriter delegate, GraphQLResponseCacheManager responseCache, + GraphQLInvocationInput invocationInput, boolean error) { + this.delegate = delegate; + this.responseCache = responseCache; + this.invocationInput = invocationInput; + this.error = error; + } + + @Override + public void write(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (responseCache.isCacheable(request, invocationInput)) { + BufferedHttpServletResponse cachingResponseWrapper = new BufferedHttpServletResponse(response); + + delegate.write(request, cachingResponseWrapper); + + try { + if (error) { + int errorStatusCode = cachingResponseWrapper.getStatus(); + String errorMessage = cachingResponseWrapper.getErrorMessage(); + + responseCache.put(request, invocationInput, CachedResponse.ofError(errorStatusCode, errorMessage)); + } else { + byte[] contentBytes = cachingResponseWrapper.getContentAsByteArray(); + + responseCache.put(request, invocationInput, CachedResponse.ofContent(contentBytes)); + } + } catch (Throwable t) { + log.warn("Ignore read from cache, unexpected error happened", t); + } + + cachingResponseWrapper.flushBuffer(); + cachingResponseWrapper.close(); + } else { + delegate.write(request, response); + } + } + +} diff --git a/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/GraphQLResponseCacheManager.java b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/GraphQLResponseCacheManager.java new file mode 100644 index 00000000..1e71a801 --- /dev/null +++ b/graphql-java-servlet/src/main/java/graphql/kickstart/servlet/cache/GraphQLResponseCacheManager.java @@ -0,0 +1,36 @@ +package graphql.kickstart.servlet.cache; + +import graphql.kickstart.execution.input.GraphQLInvocationInput; + +import javax.servlet.http.HttpServletRequest; +import java.util.Optional; + +public interface GraphQLResponseCacheManager { + + /** + * Retrieve the cache by input data. If this query was not cached before, will return empty {@link Optional}. + * + * @param request the http request + * @param invocationInput input data + * @return cached response if something available in cache or {@literal null} if nothing cached + */ + CachedResponse get(HttpServletRequest request, GraphQLInvocationInput invocationInput); + + /** + * Decide to cache or not this response. It depends on the implementation. + * + * @param request the http request + * @param invocationInput input data + */ + boolean isCacheable(HttpServletRequest request, GraphQLInvocationInput invocationInput); + + /** + * Cache this response. It depends on the implementation. + * + * @param request the http request + * @param invocationInput input data + * @param cachedResponse response to cache + */ + void put(HttpServletRequest request, GraphQLInvocationInput invocationInput, CachedResponse cachedResponse); + +}