SLING-9655 - Caching support for the GraphQL core

* switched the default GraphQLCacheProvider implementation
to an in-memory LRU cache
diff --git a/src/main/java/org/apache/sling/graphql/core/cache/GraphQLCacheProviderImpl.java b/src/main/java/org/apache/sling/graphql/core/cache/GraphQLCacheProviderImpl.java
deleted file mode 100644
index dcfb4ae..0000000
--- a/src/main/java/org/apache/sling/graphql/core/cache/GraphQLCacheProviderImpl.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ~ Licensed to the Apache Software Foundation (ASF) under one
- ~ or more contributor license agreements.  See the NOTICE file
- ~ distributed with this work for additional information
- ~ regarding copyright ownership.  The ASF licenses this file
- ~ to you under the Apache License, Version 2.0 (the
- ~ "License"); you may not use this file except in compliance
- ~ with the License.  You may obtain a copy of the License at
- ~
- ~   http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing,
- ~ software distributed under the License is distributed on an
- ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- ~ KIND, either express or implied.  See the License for the
- ~ specific language governing permissions and limitations
- ~ under the License.
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-package org.apache.sling.graphql.core.cache;
-
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.Hashtable;
-
-import javax.cache.Cache;
-import javax.cache.CacheManager;
-import javax.cache.configuration.MutableConfiguration;
-import javax.cache.spi.CachingProvider;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.sling.graphql.api.cache.GraphQLCacheProvider;
-import org.apache.sling.graphql.core.engine.SlingGraphQLException;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Deactivate;
-import org.osgi.service.component.annotations.Reference;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Component(
-        immediate = true
-)
-public class GraphQLCacheProviderImpl implements GraphQLCacheProvider {
-
-    private static final Logger LOGGER = LoggerFactory.getLogger(GraphQLCacheProviderImpl.class);
-
-    @Reference
-    private CachingProvider cachingProvider;
-
-    private Cache<String, String> persistedQueriesCache;
-
-    private ServiceRegistration<GraphQLCacheProvider> registration;
-
-    @Override
-    @Nullable
-    public String getQuery(@NotNull String hash, @NotNull String resourceType, @Nullable String selectorString) {
-        return persistedQueriesCache.get(getCacheKey(hash, resourceType, selectorString));
-    }
-
-    @Override
-    @NotNull
-    public String cacheQuery(@NotNull String query, @NotNull String resourceType, @Nullable String selectorString) {
-        String hash = getHash(query);
-        String key = getCacheKey(hash, resourceType, selectorString);
-        persistedQueriesCache.put(key, query);
-        return hash;
-    }
-
-
-    @Activate
-    private void activate(BundleContext bundleContext) {
-        if (cachingProvider != null) {
-            try {
-                CacheManager cacheManager = cachingProvider.getCacheManager();
-                persistedQueriesCache = cacheManager
-                        .createCache(bundleContext.getBundle().getSymbolicName() + "_persistedQueries", new MutableConfiguration<>());
-                registration = bundleContext.registerService(GraphQLCacheProvider.class, this, new Hashtable<>());
-            } catch (Exception e) {
-                LOGGER.error("Unable to configure a cache for persisted queries.", e);
-            }
-        } else {
-            LOGGER.error("Cannot activate the " + GraphQLCacheProvider.class.getName() + " without a cachingProvider.");
-        }
-    }
-
-    @Deactivate
-    private void deactivate() {
-        if (registration != null) {
-            registration.unregister();
-        }
-        cachingProvider.close();
-    }
-
-    @NotNull
-    private String getCacheKey(@NotNull String hash, @NotNull String resourceType, @Nullable String selectorString) {
-        StringBuilder key = new StringBuilder(resourceType);
-        if (StringUtils.isNotEmpty(selectorString)) {
-            key.append("_").append(selectorString);
-        }
-        key.append("_").append(hash);
-        return key.toString();
-    }
-
-    @NotNull String getHash(@NotNull String query) {
-        StringBuilder buffer = new StringBuilder();
-        try {
-            MessageDigest digest = MessageDigest.getInstance("SHA-256");
-            byte[] hash = digest.digest(query.getBytes(StandardCharsets.UTF_8));
-
-            for (byte b : hash) {
-                String hex = Integer.toHexString(0xff & b);
-                if (hex.length() == 1) {
-                    buffer.append('0');
-                }
-                buffer.append(hex);
-            }
-        } catch (NoSuchAlgorithmException e) {
-            throw new SlingGraphQLException("Failed hashing query - " + e.getMessage());
-        }
-        return buffer.toString();
-    }
-
-}
diff --git a/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java b/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
new file mode 100644
index 0000000..e68977d
--- /dev/null
+++ b/src/main/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProvider.java
@@ -0,0 +1,155 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements.  See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership.  The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License.  You may obtain a copy of the License at
+ ~
+ ~   http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied.  See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.graphql.core.cache;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.graphql.api.cache.GraphQLCacheProvider;
+import org.apache.sling.graphql.core.engine.SlingGraphQLException;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.AttributeType;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component()
+@Designate(ocd = SimpleGraphQLCacheProvider.Config.class)
+public class SimpleGraphQLCacheProvider implements GraphQLCacheProvider {
+
+    @ObjectClassDefinition(
+            name = "Apache Sling GraphQL Simple Cache Provider",
+            description = "The Apache Sling GraphQL Simple Cache Provider provides an in-memory size bound cache for persisted GraphQL " +
+                    "queries."
+    )
+    public @interface Config {
+
+        @AttributeDefinition(
+                name = "Capacity",
+                description = "The number of persisted queries to cache.",
+                type = AttributeType.INTEGER,
+                min = "0"
+        )
+        int capacity() default DEFAULT_CACHE_SIZE;
+    }
+
+    private static final int DEFAULT_CACHE_SIZE = 1024;
+    private static final Logger LOGGER = LoggerFactory.getLogger(SimpleGraphQLCacheProvider.class);
+
+    private InMemoryLRUCache persistedQueriesCache;
+    private ReadWriteLock readWriteLock;
+    private Lock readLock;
+    private Lock writeLock;
+
+    @Activate
+    public SimpleGraphQLCacheProvider(Config config) {
+        readWriteLock = new ReentrantReadWriteLock();
+        readLock = readWriteLock.readLock();
+        writeLock = readWriteLock.writeLock();
+        int cacheSize = config.capacity();
+        if (cacheSize < 0) {
+            cacheSize = 0;
+            LOGGER.debug("Cache capacity set to {}. Defaulting to 0.", config.capacity());
+        }
+        persistedQueriesCache = new InMemoryLRUCache(cacheSize);
+        LOGGER.debug("Initialized the in-memory cache for a maximum of {} queries.", config.capacity());
+    }
+
+
+    @Override
+    @Nullable
+    public String getQuery(@NotNull String hash, @NotNull String resourceType, @Nullable String selectorString) {
+        readLock.lock();
+        try {
+            return persistedQueriesCache.get(getCacheKey(hash, resourceType, selectorString));
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    @Override
+    @NotNull
+    public String cacheQuery(@NotNull String query, @NotNull String resourceType, @Nullable String selectorString) {
+        writeLock.lock();
+        try {
+            String hash = getHash(query);
+            String key = getCacheKey(hash, resourceType, selectorString);
+            persistedQueriesCache.put(key, query);
+            return hash;
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    @NotNull
+    private String getCacheKey(@NotNull String hash, @NotNull String resourceType, @Nullable String selectorString) {
+        StringBuilder key = new StringBuilder(resourceType);
+        if (StringUtils.isNotEmpty(selectorString)) {
+            key.append("_").append(selectorString);
+        }
+        key.append("_").append(hash);
+        return key.toString();
+    }
+
+    @NotNull String getHash(@NotNull String query) {
+        StringBuilder buffer = new StringBuilder();
+        try {
+            MessageDigest digest = MessageDigest.getInstance("SHA-256");
+            byte[] hash = digest.digest(query.getBytes(StandardCharsets.UTF_8));
+
+            for (byte b : hash) {
+                String hex = Integer.toHexString(0xff & b);
+                if (hex.length() == 1) {
+                    buffer.append('0');
+                }
+                buffer.append(hex);
+            }
+        } catch (NoSuchAlgorithmException e) {
+            throw new SlingGraphQLException("Failed hashing query - " + e.getMessage());
+        }
+        return buffer.toString();
+    }
+
+    private static class InMemoryLRUCache extends LinkedHashMap<String, String> {
+
+        private final int capacity;
+
+        public InMemoryLRUCache(int capacity) {
+            this.capacity = capacity;
+        }
+
+        @Override
+        protected boolean removeEldestEntry(Map.Entry eldest) {
+            return size() > capacity;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java b/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
index a54b0e4..537875e 100644
--- a/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
+++ b/src/main/java/org/apache/sling/graphql/core/servlet/GraphQLServlet.java
@@ -21,7 +21,6 @@
 package org.apache.sling.graphql.core.servlet;
 
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
diff --git a/src/test/java/org/apache/sling/graphql/core/cache/GraphQLCacheProviderImplTest.java b/src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java
similarity index 71%
rename from src/test/java/org/apache/sling/graphql/core/cache/GraphQLCacheProviderImplTest.java
rename to src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java
index 19c0d3a..8da286a 100644
--- a/src/test/java/org/apache/sling/graphql/core/cache/GraphQLCacheProviderImplTest.java
+++ b/src/test/java/org/apache/sling/graphql/core/cache/SimpleGraphQLCacheProviderTest.java
@@ -18,15 +18,28 @@
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
 package org.apache.sling.graphql.core.cache;
 
+import java.lang.annotation.Annotation;
+
 import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 
-public class GraphQLCacheProviderImplTest {
+public class SimpleGraphQLCacheProviderTest {
 
     @Test
-    public void getHash() throws Exception {
-        GraphQLCacheProviderImpl provider = new GraphQLCacheProviderImpl();
+    public void getHash() {
+        SimpleGraphQLCacheProvider provider = new SimpleGraphQLCacheProvider(new SimpleGraphQLCacheProvider.Config() {
+            @Override
+            public Class<? extends Annotation> annotationType() {
+                return null;
+            }
+
+            @Override
+            public int capacity() {
+                return 0;
+            }
+        });
+
         assertEquals("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", provider.getHash("hello world"));
     }
 }
diff --git a/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java b/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java
index 604f218..60d10f0 100644
--- a/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java
+++ b/src/test/java/org/apache/sling/graphql/core/it/GraphQLCoreTestSupport.java
@@ -94,10 +94,6 @@
                 .asOption(),
             mavenBundle().groupId("org.apache.sling").artifactId("org.apache.sling.servlet-helpers").versionAsInProject(),
             mavenBundle().groupId("com.cedarsoftware").artifactId("json-io").versionAsInProject(),
-            mavenBundle().groupId("javax.cache").artifactId("cache-api").versionAsInProject(),
-            mavenBundle().groupId("com.typesafe").artifactId("config").versionAsInProject(),
-            mavenBundle().groupId("com.github.ben-manes.caffeine").artifactId("caffeine").versionAsInProject(),
-            mavenBundle().groupId("com.github.ben-manes.caffeine").artifactId("jcache").versionAsInProject(),
             slingResourcePresence(),
             jsonPath(),
             junitBundles()
diff --git a/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java b/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java
index 9f2d45e..e110f45 100644
--- a/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java
+++ b/src/test/java/org/apache/sling/graphql/core/it/GraphQLServletIT.java
@@ -26,7 +26,6 @@
 import org.apache.sling.graphql.core.mocks.ReplacingSchemaProvider;
 import org.apache.sling.resource.presence.ResourcePresence;
 import org.apache.sling.servlethelpers.MockSlingHttpServletResponse;
-import org.apache.sling.servlethelpers.internalrequests.SlingInternalRequest;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.Configuration;