001// -------------------------------------------------------------------------------- 002// Copyright 2002-2025 Echo Three, LLC 003// 004// Licensed under the Apache License, Version 2.0 (the "License"); 005// you may not use this file except in compliance with the License. 006// You may obtain a copy of the License at 007// 008// http://www.apache.org/licenses/LICENSE-2.0 009// 010// Unless required by applicable law or agreed to in writing, software 011// distributed under the License is distributed on an "AS IS" BASIS, 012// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013// See the License for the specific language governing permissions and 014// limitations under the License. 015// -------------------------------------------------------------------------------- 016 017package com.echothree.control.user.graphql.server.cache; 018 019import com.github.benmanes.caffeine.cache.Cache; 020import com.github.benmanes.caffeine.cache.Caffeine; 021import graphql.ExecutionInput; 022import graphql.execution.preparsed.PreparsedDocumentEntry; 023import graphql.execution.preparsed.PreparsedDocumentProvider; 024import java.util.concurrent.CompletableFuture; 025import java.util.concurrent.TimeUnit; 026import java.util.function.Function; 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030public class GraphQlDocumentCache 031 implements PreparsedDocumentProvider { 032 033 private final Log log = LogFactory.getLog(this.getClass()); 034 private final Cache<String, PreparsedDocumentEntry> cache = Caffeine.newBuilder() 035 .maximumSize(1_000) 036 .expireAfterAccess(5, TimeUnit.MINUTES) 037 .recordStats() 038 .build(); 039 040 private GraphQlDocumentCache() { 041 super(); 042 } 043 044 private static class StringUtilsHolder { 045 static GraphQlDocumentCache instance = new GraphQlDocumentCache(); 046 } 047 048 public static PreparsedDocumentProvider getInstance() { 049 return GraphQlDocumentCache.StringUtilsHolder.instance; 050 } 051 052 @Override 053 public PreparsedDocumentEntry getDocument(final ExecutionInput executionInput, 054 final Function<ExecutionInput, PreparsedDocumentEntry> parseAndValidateFunction) { 055 return null; 056 } 057 058 @Override 059 public CompletableFuture<PreparsedDocumentEntry> getDocumentAsync(ExecutionInput executionInput, 060 Function<ExecutionInput, PreparsedDocumentEntry> computeFunction) { 061 Function<String, PreparsedDocumentEntry> mapCompute = key -> computeFunction.apply(executionInput); 062 var completableFuture = CompletableFuture.completedFuture(cache.get(executionInput.getQuery(), mapCompute)); 063 064 var cacheStats = cache.stats(); 065 if(cacheStats.requestCount() % 1000 == 0) { 066 log.info(cacheStats.toString()); 067 } 068 069 return completableFuture; 070 } 071 072 073}