SLING-11716 ability to cache the results of a caconfig lookup
diff --git a/pom.xml b/pom.xml
index 8305fda..1cfdf3c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -126,7 +126,7 @@
<dependency>
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.api</artifactId>
- <version>2.16.4</version>
+ <version>2.24.0</version>
<scope>provided</scope>
</dependency>
<dependency>
diff --git a/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java b/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java
index 77d60f4..ddc9ccf 100644
--- a/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java
+++ b/src/main/java/org/apache/sling/caconfig/resource/impl/ConfigurationResourceResolverImpl.java
@@ -21,45 +21,97 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.caconfig.management.multiplexer.ConfigurationResourceResolvingStrategyMultiplexer;
import org.apache.sling.caconfig.management.multiplexer.ContextPathStrategyMultiplexer;
import org.apache.sling.caconfig.resource.ConfigurationResourceResolver;
import org.apache.sling.caconfig.resource.impl.util.ConfigNameUtil;
import org.apache.sling.caconfig.resource.spi.ContextResource;
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.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@Component(service=ConfigurationResourceResolver.class, immediate=true)
+@Designate(ocd=ConfigurationResourceResolverImpl.Config.class)
public class ConfigurationResourceResolverImpl implements ConfigurationResourceResolver {
-
+
+
+ @ObjectClassDefinition
+ public static @interface Config {
+
+ @AttributeDefinition(name="enable caching", description="Enable caching of resolved results per ResourceResolver")
+ boolean enableCaching() default false;
+ }
+
+ private static final Logger LOG = LoggerFactory.getLogger(ConfigurationResourceResolverImpl.class);
+ private static final String MAP_KEY = ConfigurationResourceResolverImpl.class.getName() + "_Cache";
+
@Reference
private ContextPathStrategyMultiplexer contextPathStrategy;
@Reference
private ConfigurationResourceResolvingStrategyMultiplexer configurationResourceResolvingStrategy;
+ private boolean enableCaching;
+
+ @Activate
+ @Modified
+ public void modified(Config config) {
+ enableCaching = config.enableCaching();
+ LOG.debug("CaConfig caching = {}", enableCaching);
+ }
+
@Override
public Resource getResource(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) {
ConfigNameUtil.ensureValidConfigName(configName);
- return configurationResourceResolvingStrategy.getResource(resource, Collections.singleton(bucketName), configName);
+
+ Resource cacheResult = getResourceFromCache(resource, bucketName, configName);
+ if (cacheResult != null) {
+ LOG.trace("getResource (cached) resource={}, bucketName={}, configName={}",resource.getPath(),bucketName, configName);
+ return cacheResult;
+ }
+
+ Resource result = configurationResourceResolvingStrategy.getResource(resource, Collections.singleton(bucketName), configName);
+ LOG.trace("getResource (resolved) resource={}, bucketName={}, configName={}",resource.getPath(),bucketName, configName);
+ putResourceToCache(resource, bucketName, configName, result);
+ return result;
}
@Override
public @NotNull Collection<Resource> getResourceCollection(@NotNull Resource resource, @NotNull String bucketName, @NotNull String configName) {
ConfigNameUtil.ensureValidConfigName(configName);
+
+ Collection<Resource> cacheResult = getCollectionFromCache(resource,bucketName,configName);
+ if (cacheResult != null) {
+ LOG.trace("getResourceCollection (cached) resource={}, bucketName={}, configName={}",resource.getPath(),bucketName, configName);
+ return cacheResult;
+ }
+
Collection<Resource> result = configurationResourceResolvingStrategy.getResourceCollection(resource, Collections.singleton(bucketName), configName);
if (result == null) {
result = Collections.emptyList();
}
+ LOG.trace("getResourceCollection (resolved) resource={}, bucketName={}, configName={}",resource.getPath(),bucketName, configName);
+ putCollectionToCache(resource, bucketName, configName, result);
return result;
}
@Override
public String getContextPath(@NotNull Resource resource) {
+ LOG.trace("getContextPath resource={}", resource.getPath());
Iterator<ContextResource> it = contextPathStrategy.findContextResources(resource);
if (it.hasNext()) {
return it.next().getResource().getPath();
@@ -71,6 +123,7 @@
@Override
public @NotNull Collection<String> getAllContextPaths(@NotNull Resource resource) {
+ LOG.trace("getAllContextPaths resource={}", resource.getPath());
final List<String> contextPaths = new ArrayList<>();
Iterator<ContextResource> contextResources = contextPathStrategy.findContextResources(resource);
while (contextResources.hasNext()) {
@@ -78,5 +131,65 @@
}
return contextPaths;
}
+
+
+ /**
+ * Caching implementation
+ *
+ * The cache is not threadsafe (as the resourceResolver is not threadsafe as well), thus a naive
+ * implementation should suffice.
+ *
+ */
+
+ // Calculate the cache key for a caconfig entry
+ private String createCaConfigCacheKey(Resource resource, String bucketName, String configName) {
+ return resource.getPath() + "--" + bucketName + "--" + configName;
+ }
+
+ // Retrieve the caconfig map from the ResourceResolver (and create it if required)
+ private Map<String,Object> getCacheMap(@NotNull Resource resource) {
+ ResourceResolver resourceResolver = resource.getResourceResolver();
+ if (resourceResolver.getPropertyMap().containsKey(MAP_KEY)) {
+ return (Map<String,Object>) resourceResolver.getPropertyMap().get(MAP_KEY);
+ } else {
+ Map<String,Object> map = new HashMap<>();
+ resourceResolver.getPropertyMap().put(MAP_KEY, map);
+ return map;
+ }
+ }
+
+ /**
+ * Retrieves a result from the cache
+ * @param resource the resource
+ * @param bucketName the bucketName
+ * @param configName the configName
+ * @return the cache result or null
+ */
+ @SuppressWarnings("unchecked")
+ private @Nullable Collection<Resource> getCollectionFromCache(Resource resource, String bucketName, String configName) {
+ if (!enableCaching) {
+ return null;
+ }
+ String cacheKey = "collection--" + createCaConfigCacheKey(resource, bucketName, configName);
+ return (Collection<Resource>) getCacheMap(resource).get(cacheKey);
+ }
+
+ private void putCollectionToCache(Resource resource, String bucketName, String configName, Collection<Resource> value) {
+ String cacheKey = "collection--" + createCaConfigCacheKey(resource, bucketName, configName);
+ getCacheMap(resource).put(cacheKey, value);
+ }
+
+ private @Nullable Resource getResourceFromCache(Resource resource, String bucketName, String configName) {
+ if (!enableCaching) {
+ return null;
+ }
+ String cacheKey = "resource--" + createCaConfigCacheKey(resource, bucketName, configName);
+ return (Resource) getCacheMap(resource).get(cacheKey);
+ }
+
+ private void putResourceToCache(Resource resource, String bucketName, String configName, Resource value) {
+ String cacheKey = "resource--" + createCaConfigCacheKey(resource, bucketName, configName);
+ getCacheMap(resource).put(cacheKey, value);
+ }
}