diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/HttpCacheConfig.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/HttpCacheConfig.java index 2e650f1eeb..d0df6e5280 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/HttpCacheConfig.java +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/HttpCacheConfig.java @@ -25,6 +25,7 @@ import com.adobe.acs.commons.httpcache.keys.CacheKey; import org.apache.sling.api.SlingHttpServletRequest; +import java.util.Collections; import java.util.List; import java.util.regex.Pattern; @@ -37,7 +38,7 @@ @ProviderType public interface HttpCacheConfig { - public enum FilterScope { + enum FilterScope { REQUEST, INCLUDE } @@ -77,6 +78,15 @@ public enum FilterScope { */ List getJCRInvalidationPathPatterns(); + /** + * Get a list of headers (as regex pattern) that should NOT be put in the cached response, to be served to the output. + * This is useful for example with systems that put a login cookie in each response. + * @return + */ + default List getExcludedResponseHeaderPatterns() { + return Collections.emptyList(); + } + /** * Determine if this cache config is applicable for the given request. Calls HttpCacheConfigExtension * .accept() for providing share of control to the custom code. diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImpl.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImpl.java index 8e07d2f3f4..70b09b1175 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImpl.java +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImpl.java @@ -28,6 +28,7 @@ import com.adobe.acs.commons.httpcache.keys.CacheKeyFactory; import com.adobe.acs.commons.httpcache.store.HttpCacheStore; import com.adobe.acs.commons.httpcache.util.UserUtils; +import com.adobe.acs.commons.util.ParameterUtil; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import org.apache.felix.scr.annotations.Activate; @@ -216,6 +217,15 @@ public class HttpCacheConfigImpl implements HttpCacheConfig { static final String PROP_CACHE_HANDLING_RULES_PID = "httpcache.config.cache-handling-rules.pid"; private List cacheHandlingRulesPid; + + + @Property(label = "Config-specific Excluded Response headers", + description = "List of header keys (as regex) that should NOT be put in the cached response, to be served to the output.", + unbounded = PropertyUnbounded.ARRAY + ) + static final String PROP_RESPONSE_HEADER_EXCLUSIONS = "httpcache.config.excluded.response.headers"; + private List responseHeaderExclusions; + @Property(label = "Expiry on create", description = "Specifies a custom expiry on create. Overrules the global expiry, unless the value is 0.") static final String PROP_EXPIRY_ON_CREATE = "httpcache.config.expiry.on.create"; @@ -249,6 +259,9 @@ protected void activate(Map configs) { String[]{})); requestUriPatternsAsRegEx = compileToPatterns(requestUriPatterns); + responseHeaderExclusions = ParameterUtil.toPatterns(PropertiesUtil.toStringArray(configs.get(PROP_RESPONSE_HEADER_EXCLUSIONS), new String[]{})); + + // Request URIs - Blacklisted. blacklistedRequestUriPatterns = Arrays.asList(PropertiesUtil.toStringArray(configs .get(PROP_BLACKLISTED_REQUEST_URI_PATTERNS), new String[]{})); @@ -452,4 +465,9 @@ public boolean acceptsRule(String servicePid) { public FilterScope getFilterScope() { return this.filterScope; } + + @Override + public List getExcludedResponseHeaderPatterns() { + return responseHeaderExclusions; + } } diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/package-info.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/package-info.java index 08bf712e0d..aebadd80df 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/package-info.java +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/config/package-info.java @@ -18,6 +18,6 @@ * #L% */ -@org.osgi.annotation.versioning.Version("2.2.0") +@org.osgi.annotation.versioning.Version("2.3.0") package com.adobe.acs.commons.httpcache.config; diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/CacheContent.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/CacheContent.java index 8330c9a8c7..974f40b2c3 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/CacheContent.java +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/CacheContent.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Represents response content to be cached. @@ -41,7 +42,7 @@ public class CacheContent { /** Response content type */ private String contentType; /** Response headers */ - private Map> headers = new HashMap>(); + private Map> headers = new HashMap<>(); /** Response content as input stream */ private InputStream dataInputStream; /** Temp sink attached to this cache content */ @@ -132,24 +133,37 @@ public CacheContent() { * Construct from the custom servlet response wrapper.. * * @param responseWrapper + * @deprecated Use build(HttpCacheServletResponseWrapper responseWrapper,int status, String charEncoding, String contentType, Map> headers) throws HttpCacheDataStreamException * @return */ + @Deprecated public CacheContent build(HttpCacheServletResponseWrapper responseWrapper) throws HttpCacheDataStreamException { - this.status = responseWrapper.getStatus(); + // Extracting HTTP Response Header Names and Values + Map> extractedHeaders = responseWrapper.getHeaderNames().stream().collect( + Collectors.toMap(headerName -> headerName, headerName -> + new ArrayList<>(responseWrapper.getHeaders(headerName) + ) + )); + + return build(responseWrapper, responseWrapper.getStatus(), responseWrapper.getCharacterEncoding(), responseWrapper. getContentType(), extractedHeaders); + } + + /** + * Construct from the custom servlet response wrapper.. + * + * @param responseWrapper + * @param headers + * @return + */ + public CacheContent build(HttpCacheServletResponseWrapper responseWrapper,int status, String charEncoding, String contentType, Map> headers) throws HttpCacheDataStreamException { + this.status = status; // Extract information from response and populate state of the instance. - this.charEncoding = responseWrapper.getCharacterEncoding(); - this.contentType = responseWrapper.getContentType(); + this.charEncoding = charEncoding; + this.contentType = contentType; // Extracting header K,V. - List headerNames = new ArrayList(); - - headerNames.addAll(responseWrapper.getHeaderNames()); - for (String headerName: headerNames) { - List values = new ArrayList(); - values.addAll(responseWrapper.getHeaders(headerName)); - headers.put(headerName, values); - } + this.headers.putAll(headers); // Get hold of the temp sink. this.tempSink = responseWrapper.getTempSink(); diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImpl.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImpl.java index e258c5e7ef..d8d8df9c4b 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImpl.java +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImpl.java @@ -19,11 +19,14 @@ */ package com.adobe.acs.commons.httpcache.engine.impl; +import com.adobe.acs.commons.fam.ThrottledTaskRunner; import com.adobe.acs.commons.httpcache.config.HttpCacheConfig; -import com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigComparator; import com.adobe.acs.commons.httpcache.engine.CacheContent; import com.adobe.acs.commons.httpcache.engine.HttpCacheEngine; import com.adobe.acs.commons.httpcache.engine.HttpCacheServletResponseWrapper; +import com.adobe.acs.commons.httpcache.engine.impl.delegate.HttpCacheEngineBindingsDelegate; +import com.adobe.acs.commons.httpcache.engine.impl.delegate.HttpCacheEngineMBeanDelegate; +import com.adobe.acs.commons.httpcache.exception.HttpCacheException; import com.adobe.acs.commons.httpcache.exception.HttpCacheConfigConflictException; import com.adobe.acs.commons.httpcache.exception.HttpCacheDataStreamException; import com.adobe.acs.commons.httpcache.exception.HttpCacheKeyCreationException; @@ -32,6 +35,7 @@ import com.adobe.acs.commons.httpcache.keys.CacheKey; import com.adobe.acs.commons.httpcache.rule.HttpCacheHandlingRule; import com.adobe.acs.commons.httpcache.store.HttpCacheStore; +import com.adobe.acs.commons.util.ParameterUtil; import com.adobe.granite.jmx.annotation.AnnotatedStandardMBean; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; @@ -51,32 +55,23 @@ import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.commons.osgi.PropertiesUtil; -import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.management.DynamicMBean; import javax.management.NotCompliantMBeanException; -import javax.management.openmbean.CompositeDataSupport; -import javax.management.openmbean.CompositeType; import javax.management.openmbean.OpenDataException; -import javax.management.openmbean.OpenType; -import javax.management.openmbean.SimpleType; import javax.management.openmbean.TabularData; -import javax.management.openmbean.TabularDataSupport; -import javax.management.openmbean.TabularType; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; import java.util.List; import java.util.ListIterator; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Function; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Default implementation for {@link HttpCacheEngine}. Binds multiple {@link HttpCacheConfig}. Multiple {@link @@ -123,29 +118,11 @@ public class HttpCacheEngineImpl extends AnnotatedStandardMBean implements HttpC /** Method name that binds cache configs */ static final String METHOD_NAME_TO_BIND_CONFIG = "httpCacheConfig"; - /** jmx property labels */ - static final String JMX_PN_ORDER = "Order"; - static final String JMX_PN_OSGICOMPONENT = "OSGi Component"; - static final String JMX_PN_HTTPCACHE_CONFIGS = "HTTP Cache Configs"; - static final String JMX_PN_HTTPCACHE_CONFIG = "HTTP Cache Config"; - static final String JMX_PN_HTTPCACHE_STORE = "HTTP Cache Store"; - static final String JMX_PN_HTTPCACHE_STORES = "HTTP Cache Stores"; - static final String JMX_HTTPCACHE_HANDLING_RULE = "HTTP Cache Handling Rule"; - static final String JMX_PN_HTTPCACHE_HANDLING_RULES = "HTTP Cache Handling Rules"; - - /** Thread safe list to contain the registered HttpCacheConfig references. */ - private CopyOnWriteArrayList cacheConfigs = new CopyOnWriteArrayList(); - /** Method name that binds cache store */ static final String METHOD_NAME_TO_BIND_CACHE_STORE = "httpCacheStore"; - /** Thread safe hash map to contain the registered cache store references. */ - private static final ConcurrentHashMap cacheStoresMap = new ConcurrentHashMap(); /** Method name that binds cache handling rules */ static final String METHOD_NAME_TO_BIND_CACHE_HANDLING_RULES = "httpCacheHandlingRule"; - /** Thread safe map to contain the registered HttpCacheHandlingRule references. */ - private static final ConcurrentHashMap cacheHandlingRules = new - ConcurrentHashMap(); // formatter:off @Property(label = "Global HttpCacheHandlingRules", @@ -156,144 +133,34 @@ public class HttpCacheEngineImpl extends AnnotatedStandardMBean implements HttpC "com.adobe.acs.commons.httpcache.rule.impl.HonorCacheControlHeaders", "com.adobe.acs.commons.httpcache.rule.impl.DoNotCacheZeroSizeResponse" }) - // formatter:on - private static final String PROP_GLOBAL_CACHE_HANDLING_RULES_PID = "httpcache.engine.cache-handling-rules.global"; - private List globalCacheHandlingRulesPid; - - /** Thread safe list containing the OSGi configurations for the registered httpCacheConfigs. Used only for mbean.*/ - private final ConcurrentHashMap> cacheConfigConfigs = new - ConcurrentHashMap>(); - - //----------------------------------// - - /** - * Binds cache config. Cache config could come and go at run time. - * - * @param cacheConfig - * @param configs - */ - protected void bindHttpCacheConfig(final HttpCacheConfig cacheConfig, final Map configs) { - - // Validate cache config object - if (!cacheConfig.isValid()) { - log.info("Http cache config rejected as the request uri is absent."); - return; - } - - // Check if the same object is already there in the map. - if (cacheConfigs.contains(cacheConfig)) { - log.trace("Http cache config object already exists in the cacheConfigs map and hence ignored."); - return; - } - - // Sort cacheConfigs by order - final CopyOnWriteArrayList tmp = new CopyOnWriteArrayList(this.cacheConfigs); - tmp.add(cacheConfig); - - Collections.sort(tmp, new HttpCacheConfigComparator()); - this.cacheConfigs = tmp; - this.cacheConfigConfigs.put(cacheConfig, configs); - - log.debug("Total number of cache configs added: {}", cacheConfigs.size()); - } - - /** - * Unbinds cache config. - * - * @param cacheConfig - * @param config - */ - protected void unbindHttpCacheConfig(final HttpCacheConfig cacheConfig, final Map config) { - - if (cacheConfigs.contains(cacheConfig)) { - // Remove the associated cached items from the cache store. - if (cacheStoresMap.containsKey(cacheConfig.getCacheStoreName())) { - cacheStoresMap.get(cacheConfig.getCacheStoreName()).invalidate(cacheConfig); - } else { - log.debug("Configured cache store is unavailable and hence nothing to invalidate."); - } - - // Remove the entry from the map. - cacheConfigs.remove(cacheConfig); - cacheConfigConfigs.remove(cacheConfig); - - log.debug("Total number of cache configs after removal: {}", cacheConfigs.size()); - return; - } - log.debug("This cache config entry was not bound and hence nothing to unbind."); - } - - /** - * Binds cache store implementation - * - * @param cacheStore - */ - protected void bindHttpCacheStore(final HttpCacheStore cacheStore) { - final String cacheStoreType = cacheStore.getStoreType(); - if (cacheStoreType != null && cacheStoresMap.putIfAbsent(cacheStoreType, cacheStore) == null) { - log.debug("HTTP Cache Store [ {} -> ADDED ] for a total of [ {} ]", cacheStore.getStoreType(), cacheStoresMap.size()); - } - } - - /** - * Unbinds cache store. - * - * @param cacheStore - */ - protected void unbindHttpCacheStore(final HttpCacheStore cacheStore) { - final String cacheStoreType = cacheStore.getStoreType(); - if (cacheStoreType != null && cacheStoresMap.remove(cacheStoreType) != null) { - log.debug("HTTP Cache Store [ {} -> REMOVED ] for a total of [ {} ]", cacheStore.getStoreType(), cacheStoresMap.size()); - } - } - - /** - * Binds cache handling rule - * - * @param cacheHandlingRule - * @param properties - */ - protected void bindHttpCacheHandlingRule(final HttpCacheHandlingRule cacheHandlingRule, final Map - properties) { - - // Get the service pid and make it as key. - if (cacheHandlingRules.putIfAbsent(getServicePid(properties), cacheHandlingRule) == null) { - log.debug("Cache handling rule implementation {} has been added", cacheHandlingRule.getClass().getName()); - log.debug("Total number of cache handling rule available after addition: {}", cacheHandlingRules.size()); - } - } - - /** - * Unbinds handling rule. - * - * @param cacheHandlingRule - * @param configs - */ - protected void unbindHttpCacheHandlingRule(final HttpCacheHandlingRule cacheHandlingRule, final Map configs) { + static final String PROP_GLOBAL_CACHE_HANDLING_RULES_PID = "httpcache.engine.cache-handling-rules.global"; + private List globalCacheHandlingRulesPid; - if (cacheHandlingRules.remove(getServicePid(configs) ) != null) { - log.debug("Cache handling rule removed - {}.", cacheHandlingRule.getClass().getName()); - log.debug("Total number of cache handling rules available after removal: {}", cacheHandlingRules.size()); - } - } + @Property(label = "Global HttpCacheHandlingRules", + description = "List of header keys (as regex statements) that should NOT be put in the cached response, to be served to the output.", + unbounded = PropertyUnbounded.ARRAY + ) + static final String PROP_GLOBAL_RESPONSE_HEADER_EXCLUSIONS = "httpcache.engine.excluded.response.headers.global"; + private List globalHeaderExclusions; + // formatter:on - private String getServicePid(Map configs) { - String servicePid = PropertiesUtil.toString(configs.get("service.pid"), StringUtils.EMPTY); + @Reference + private ThrottledTaskRunner throttledTaskRunner; - if(StringUtils.isBlank(servicePid)){ - servicePid =PropertiesUtil.toString(configs.get("component.name"), StringUtils.EMPTY); - } - return servicePid; - } + private final HttpCacheEngineMBeanDelegate mBeanDelegate = new HttpCacheEngineMBeanDelegate(); + private final HttpCacheEngineBindingsDelegate bindingsDelegate = new HttpCacheEngineBindingsDelegate(); + //----------------------------------// @Activate protected void activate(Map configs) { // PIDs of global cache handling rules. - globalCacheHandlingRulesPid = new ArrayList(Arrays.asList(PropertiesUtil.toStringArray(configs.get( + globalCacheHandlingRulesPid = new ArrayList<>(Arrays.asList(PropertiesUtil.toStringArray(configs.get( PROP_GLOBAL_CACHE_HANDLING_RULES_PID), new String[]{}))); + + globalHeaderExclusions = ParameterUtil.toPatterns(PropertiesUtil.toStringArray(configs.get(PROP_GLOBAL_RESPONSE_HEADER_EXCLUSIONS), new String[]{})); + ListIterator listIterator = globalCacheHandlingRulesPid.listIterator(); while (listIterator.hasNext()) { String value = listIterator.next(); @@ -301,6 +168,7 @@ protected void activate(Map configs) { listIterator.remove(); } } + log.info("HttpCacheEngineImpl activated."); } @@ -315,7 +183,7 @@ public boolean isRequestCacheable(SlingHttpServletRequest request, HttpCacheConf HttpCacheRepositoryAccessException { // Execute custom rules. - for (final Map.Entry entry : cacheHandlingRules.entrySet()) { + for (final Map.Entry entry : bindingsDelegate.getCacheHandlingRules().entrySet()) { // Apply rule if it's a configured global or cache-config tied rule. if (globalCacheHandlingRulesPid.contains(entry.getKey()) || cacheConfig.acceptsRule(entry.getKey())) { HttpCacheHandlingRule rule = entry.getValue(); @@ -346,7 +214,7 @@ public HttpCacheConfig getCacheConfig(SlingHttpServletRequest request, HttpCache // Get the first accepting cache config based on the cache config order. HttpCacheConfig bestCacheConfig = null; - for (HttpCacheConfig cacheConfig : cacheConfigs) { + for (HttpCacheConfig cacheConfig : bindingsDelegate.getCacheConfigs()) { if (bestCacheConfig != null) { // A matching HttpCacheConfig has been found, so check for order + acceptance conflicts if (bestCacheConfig.getOrder() == cacheConfig.getOrder()) { @@ -397,7 +265,7 @@ public boolean deliverCacheContent(SlingHttpServletRequest request, SlingHttpSer @Override public HttpCacheServletResponseWrapper wrapResponse(SlingHttpServletRequest request, SlingHttpServletResponse response, HttpCacheConfig cacheConfig) throws HttpCacheDataStreamException, - HttpCacheKeyCreationException, HttpCachePersistenceException { + HttpCachePersistenceException { // Wrap the response to get the copy of the stream. // Temp sink for the duplicate stream is chosen based on the cache store configured at cache config. try { @@ -409,42 +277,64 @@ public HttpCacheServletResponseWrapper wrapResponse(SlingHttpServletRequest requ @Override public void cacheResponse(SlingHttpServletRequest request, SlingHttpServletResponse response, HttpCacheConfig - cacheConfig) throws HttpCacheKeyCreationException, HttpCacheDataStreamException, - HttpCachePersistenceException { + cacheConfig) { - // TODO - This can be made asynchronous to avoid performance penalty on response cache. + final HttpCacheServletResponseWrapper responseWrapper; + if (response instanceof HttpCacheServletResponseWrapper) { + responseWrapper = (HttpCacheServletResponseWrapper) response; + } else { + throw new AssertionError("Programming error."); + } + final Map> extractedHeaders = extractHeaders(responseWrapper, cacheConfig); + final int status = responseWrapper.getStatus(); + final String charEncoding = responseWrapper.getCharacterEncoding(); + final String contentType = responseWrapper.getContentType(); - CacheContent cacheContent = null; - try { + throttledTaskRunner.scheduleWork(() -> { + CacheContent cacheContent = null; // Construct the cache content. - HttpCacheServletResponseWrapper responseWrapper = null; - if (response instanceof HttpCacheServletResponseWrapper) { - responseWrapper = (HttpCacheServletResponseWrapper) response; - } else { - throw new AssertionError("Programming error."); - } - CacheKey cacheKey = cacheConfig.buildCacheKey(request); - cacheContent = new CacheContent().build(responseWrapper); + try { + CacheKey cacheKey = cacheConfig.buildCacheKey(request); + cacheContent = new CacheContent().build(responseWrapper, status, charEncoding, contentType, extractedHeaders); - // Persist in cache. - if (isRequestCachableAccordingToHandlingRules(request, response, cacheConfig, cacheContent)) { - getCacheStore(cacheConfig).put(cacheKey, cacheContent); - log.debug("Response for the URI cached - {}", request.getRequestURI()); - } - } finally { - // Close the temp sink input stream. - if (null != cacheContent) { - IOUtils.closeQuietly(cacheContent.getInputDataStream()); + // Persist in cache. + if (isRequestCachableAccordingToHandlingRules(request, response, cacheConfig, cacheContent)) { + getCacheStore(cacheConfig).put(cacheKey, cacheContent); + log.debug("Response for the URI cached - {}", request.getRequestURI()); + } + } catch (HttpCacheException e) { + log.error("Error storing http response in httpcache", e); + } finally { + // Close the temp sink input stream. + if (null != cacheContent) { + IOUtils.closeQuietly(cacheContent.getInputDataStream()); + } } - } + }); } + private Map> extractHeaders(SlingHttpServletResponse response, HttpCacheConfig cacheConfig) { + + List excludedHeaders = Stream.concat(globalHeaderExclusions.stream(), cacheConfig.getExcludedResponseHeaderPatterns().stream()) + .collect(Collectors.toList()); + + return response.getHeaderNames().stream() + .filter(headerName -> excludedHeaders.stream() + .noneMatch(pattern -> pattern.matcher(headerName).matches()) + ).collect( + Collectors.toMap(headerName -> headerName, headerName -> + new ArrayList<>(response.getHeaders(headerName) + ) + )); + } + + @Override public boolean isPathPotentialToInvalidate(String path) { // Check all the configs to see if this path is of interest. - for (HttpCacheConfig config : cacheConfigs) { + for (HttpCacheConfig config : bindingsDelegate.getCacheConfigs()) { if (config.canInvalidate(path)) { return true; } @@ -456,7 +346,7 @@ public boolean isPathPotentialToInvalidate(String path) { @Override public void invalidateCache(String path) throws HttpCachePersistenceException, HttpCacheKeyCreationException { // Find out all the cache config which has this path applicable for invalidation. - for (HttpCacheConfig cacheConfig : cacheConfigs) { + for (HttpCacheConfig cacheConfig : bindingsDelegate.getCacheConfigs()) { if (cacheConfig.canInvalidate(path)) { // Execute custom rules. executeCustomRuleInvalidations(path, cacheConfig); @@ -474,8 +364,8 @@ public void invalidateCache(String path) throws HttpCachePersistenceException, H * @throws HttpCachePersistenceException */ private HttpCacheStore getCacheStore(HttpCacheConfig cacheConfig) throws HttpCachePersistenceException { - if (cacheStoresMap.containsKey(cacheConfig.getCacheStoreName())) { - return cacheStoresMap.get(cacheConfig.getCacheStoreName()); + if (bindingsDelegate.getCacheStoresMap().containsKey(cacheConfig.getCacheStoreName())) { + return bindingsDelegate.getCacheStoresMap().get(cacheConfig.getCacheStoreName()); } else { throw new HttpCachePersistenceException("Configured cache store unavailable " + cacheConfig .getCacheStoreName()); @@ -491,94 +381,77 @@ public HttpCacheEngineImpl() throws NotCompliantMBeanException { @Override public TabularData getRegisteredHttpCacheRules() throws OpenDataException { - // @formatter:off - final CompositeType cacheEntryType = new CompositeType( - JMX_HTTPCACHE_HANDLING_RULE, - JMX_HTTPCACHE_HANDLING_RULE, - new String[]{JMX_HTTPCACHE_HANDLING_RULE}, - new String[]{JMX_HTTPCACHE_HANDLING_RULE}, - new OpenType[]{SimpleType.STRING}); - - final TabularDataSupport tabularData = new TabularDataSupport( - new TabularType( - JMX_PN_HTTPCACHE_HANDLING_RULES, - JMX_PN_HTTPCACHE_HANDLING_RULES, - cacheEntryType, - new String[]{JMX_HTTPCACHE_HANDLING_RULE})); - // @formatter:on - - for (final Map.Entry entry : cacheHandlingRules.entrySet()) { - final Map row = new HashMap(); - - row.put(JMX_HTTPCACHE_HANDLING_RULE, entry.getValue().getClass().getName()); - tabularData.put(new CompositeDataSupport(cacheEntryType, row)); - } - - return tabularData; + return mBeanDelegate.getRegisteredHttpCacheRules(bindingsDelegate.getCacheHandlingRules()); } @Override public TabularData getRegisteredHttpCacheConfigs() throws OpenDataException { - // @formatter:off - // Exposing all google guava stats. - final CompositeType cacheEntryType = new CompositeType( - JMX_PN_HTTPCACHE_CONFIG, - JMX_PN_HTTPCACHE_CONFIG, - new String[]{JMX_PN_ORDER, JMX_PN_OSGICOMPONENT }, - new String[]{ JMX_PN_ORDER, JMX_PN_OSGICOMPONENT }, - new OpenType[]{ SimpleType.INTEGER, SimpleType.STRING }); - - final TabularDataSupport tabularData = new TabularDataSupport( - new TabularType( - JMX_PN_HTTPCACHE_CONFIGS, - JMX_PN_HTTPCACHE_CONFIGS, - cacheEntryType, - new String[]{ JMX_PN_OSGICOMPONENT })); - - // @formatter:on + return mBeanDelegate.getRegisteredHttpCacheConfigs(bindingsDelegate.getCacheConfigs(), bindingsDelegate.getCacheConfigConfigs()); + } - for (HttpCacheConfig cacheConfig : this.cacheConfigs) { - final Map row = new HashMap(); + @Override + public TabularData getRegisteredPersistenceStores() throws OpenDataException { + return mBeanDelegate.getRegisteredPersistenceStores(bindingsDelegate.getCacheStoresMap()); + } - Map osgiConfig = cacheConfigConfigs.get(cacheConfig); + /** + * Binds cache config. Cache config could come and go at run time. + * + * @param cacheConfig + * @param configs + */ + protected void bindHttpCacheConfig(final HttpCacheConfig cacheConfig, final Map configs) { + bindingsDelegate.bindHttpCacheConfig(cacheConfig, configs); + } - row.put(JMX_PN_ORDER, cacheConfig.getOrder()); - row.put(JMX_PN_OSGICOMPONENT, (String) osgiConfig.get(Constants.SERVICE_PID)); + /** + * Unbinds cache config. + * + * @param cacheConfig + * @param config + */ + protected void unbindHttpCacheConfig(final HttpCacheConfig cacheConfig, final Map config) { + bindingsDelegate.unbindHttpCacheConfig(cacheConfig); + } - tabularData.put(new CompositeDataSupport(cacheEntryType, row)); - } + /** + * Binds cache store implementation + * + * @param cacheStore + */ + protected void bindHttpCacheStore(final HttpCacheStore cacheStore) { + bindingsDelegate.bindHttpCacheStore(cacheStore); + } - return tabularData; + /** + * Unbinds cache store. + * + * @param cacheStore + */ + protected void unbindHttpCacheStore(final HttpCacheStore cacheStore) { + bindingsDelegate.unbindHttpCacheStore(cacheStore); } - @Override - public TabularData getRegisteredPersistenceStores() throws OpenDataException { - // @formatter:off - final CompositeType cacheEntryType = new CompositeType( - JMX_PN_HTTPCACHE_STORE, - JMX_PN_HTTPCACHE_STORE, - new String[]{JMX_PN_HTTPCACHE_STORE}, - new String[]{JMX_PN_HTTPCACHE_STORE}, - new OpenType[]{ SimpleType.STRING}); - - final TabularDataSupport tabularData = new TabularDataSupport( - new TabularType( - JMX_PN_HTTPCACHE_STORES, - JMX_PN_HTTPCACHE_STORES, - cacheEntryType, - new String[]{JMX_PN_HTTPCACHE_STORE})); - // @formatter:on - - Enumeration storeNames = cacheStoresMap.keys(); - while (storeNames.hasMoreElements()) { - final String storeName = storeNames.nextElement(); - final Map row = new HashMap(); - - row.put(JMX_PN_HTTPCACHE_STORE, storeName); - tabularData.put(new CompositeDataSupport(cacheEntryType, row)); - } + /** + * Binds cache handling rule + * + * @param cacheHandlingRule + * @param properties + */ + protected void bindHttpCacheHandlingRule(final HttpCacheHandlingRule cacheHandlingRule, final Map + properties) { + bindingsDelegate.bindHttpCacheHandlingRule(cacheHandlingRule, properties); + } - return tabularData; + /** + * Unbinds handling rule. + * + * @param cacheHandlingRule + * @param configs + */ + protected void unbindHttpCacheHandlingRule(final HttpCacheHandlingRule cacheHandlingRule, final Map configs) { + bindingsDelegate.unbindHttpCacheHandlingRule(cacheHandlingRule, configs); } private boolean isRequestCachableAccordingToHandlingRules(SlingHttpServletRequest request, SlingHttpServletResponse response, HttpCacheConfig cacheConfig, CacheContent cacheContent){ @@ -590,7 +463,7 @@ private boolean isRequestDeliverableFromCacheAccordingToHandlingRules(SlingHttpS } private boolean checkOnHandlingRule(SlingHttpServletRequest request, HttpCacheConfig cacheConfig, Function check,String onFailLogMessage){ - for (final Map.Entry entry : cacheHandlingRules.entrySet()) { + for (final Map.Entry entry : bindingsDelegate.getCacheHandlingRules().entrySet()) { // Apply rule if it's a configured global or cache-config tied rule. if (globalCacheHandlingRulesPid.contains(entry.getKey()) || cacheConfig.acceptsRule(entry.getKey())) { HttpCacheHandlingRule rule = entry.getValue(); @@ -620,6 +493,7 @@ private void prepareCachedResponse(SlingHttpServletResponse response, CacheConte response.setCharacterEncoding(cacheContent.getCharEncoding()); } + private boolean executeCacheContentDeliver(SlingHttpServletRequest request, SlingHttpServletResponse response, CacheContent cacheContent) throws HttpCacheDataStreamException { // Copy the cached data into the servlet output stream. try { @@ -649,7 +523,7 @@ private void serveCacheContentIntoResponse(SlingHttpServletResponse response, Ca } private void executeCustomRuleInvalidations(String path, HttpCacheConfig cacheConfig) throws HttpCachePersistenceException, HttpCacheKeyCreationException { - for (final Map.Entry entry : cacheHandlingRules.entrySet()) { + for (final Map.Entry entry : bindingsDelegate.getCacheHandlingRules().entrySet()) { // Apply rule if it's a configured global or cache-config tied rule. if (globalCacheHandlingRulesPid.contains(entry.getKey()) || cacheConfig.acceptsRule(entry.getKey())) { HttpCacheHandlingRule rule = entry.getValue(); @@ -662,4 +536,6 @@ private void executeCustomRuleInvalidations(String path, HttpCacheConfig cacheCo } } } + + } diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/delegate/HttpCacheEngineBindingsDelegate.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/delegate/HttpCacheEngineBindingsDelegate.java new file mode 100644 index 0000000000..2c60803867 --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/delegate/HttpCacheEngineBindingsDelegate.java @@ -0,0 +1,202 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2016 Adobe + * %% + * Licensed 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. + * #L% + */ +package com.adobe.acs.commons.httpcache.engine.impl.delegate; + +import com.adobe.acs.commons.httpcache.config.HttpCacheConfig; +import com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigComparator; +import com.adobe.acs.commons.httpcache.engine.impl.HttpCacheEngineImpl; +import com.adobe.acs.commons.httpcache.rule.HttpCacheHandlingRule; +import com.adobe.acs.commons.httpcache.store.HttpCacheStore; +import org.apache.commons.lang.StringUtils; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * HttpCacheEngineBindingsDelegate + *

+ * Handles the bulk of the bindings logic for the HttpCacheEngineImpl + *

+ */ +public class HttpCacheEngineBindingsDelegate { + + private static final Logger log = LoggerFactory.getLogger(HttpCacheEngineImpl.class); + + /** Thread safe list to contain the registered HttpCacheConfig references. */ + private CopyOnWriteArrayList cacheConfigs = new CopyOnWriteArrayList<>(); + + /** Thread safe hash map to contain the registered cache store references. */ + private final ConcurrentHashMap cacheStoresMap = new ConcurrentHashMap<>(); + + /** Thread safe map to contain the registered HttpCacheHandlingRule references. */ + private final ConcurrentHashMap cacheHandlingRules = new + ConcurrentHashMap<>(); + + /** Thread safe list containing the OSGi configurations for the registered httpCacheConfigs. Used only for mbean.*/ + private final ConcurrentHashMap> cacheConfigConfigs = new + ConcurrentHashMap<>(); + + /** + * Binds cache config. Cache config could come and go at run time. + * + * @param cacheConfig + * @param configs + */ + public void bindHttpCacheConfig(final HttpCacheConfig cacheConfig, final Map configs) { + + // Validate cache config object + if (!cacheConfig.isValid()) { + log.info("Http cache config rejected as the request uri is absent."); + return; + } + + // Check if the same object is already there in the map. + if (cacheConfigs.contains(cacheConfig)) { + log.trace("Http cache config object already exists in the cacheConfigs map and hence ignored."); + return; + } + + // Sort cacheConfigs by order + final CopyOnWriteArrayList tmp = new CopyOnWriteArrayList(this.cacheConfigs); + tmp.add(cacheConfig); + + Collections.sort(tmp, new HttpCacheConfigComparator()); + this.cacheConfigs = tmp; + + this.cacheConfigConfigs.put(cacheConfig, configs); + + log.debug("Total number of cache configs added: {}", cacheConfigs.size()); + } + + /** + * Unbinds cache config. + * + * @param cacheConfig + * @param config + */ + public void unbindHttpCacheConfig(final HttpCacheConfig cacheConfig) { + + if (cacheConfigs.contains(cacheConfig)) { + // Remove the associated cached items from the cache store. + if (cacheStoresMap.containsKey(cacheConfig.getCacheStoreName())) { + cacheStoresMap.get(cacheConfig.getCacheStoreName()).invalidate(cacheConfig); + } else { + log.debug("Configured cache store is unavailable and hence nothing to invalidate."); + } + + // Remove the entry from the map. + cacheConfigs.remove(cacheConfig); + cacheConfigConfigs.remove(cacheConfig); + + log.debug("Total number of cache configs after removal: {}", cacheConfigs.size()); + return; + } + log.debug("This cache config entry was not bound and hence nothing to unbind."); + } + + /** + * Binds cache store implementation + * + * @param cacheStore + */ + public void bindHttpCacheStore(final HttpCacheStore cacheStore) { + final String cacheStoreType = cacheStore.getStoreType(); + if (cacheStoreType != null && cacheStoresMap.putIfAbsent(cacheStoreType, cacheStore) == null) { + log.debug("HTTP Cache Store [ {} -> ADDED ] for a total of [ {} ]", cacheStore.getStoreType(), cacheStoresMap.size()); + } + } + + + + /** + * Unbinds cache store. + * + * @param cacheStore + */ + public void unbindHttpCacheStore(final HttpCacheStore cacheStore) { + final String cacheStoreType = cacheStore.getStoreType(); + if (cacheStoreType != null && cacheStoresMap.remove(cacheStoreType) != null) { + log.debug("HTTP Cache Store [ {} -> REMOVED ] for a total of [ {} ]", cacheStore.getStoreType(), cacheStoresMap.size()); + } + } + + /** + * Binds cache handling rule + * + * @param cacheHandlingRule + * @param properties + */ + public void bindHttpCacheHandlingRule(final HttpCacheHandlingRule cacheHandlingRule, final Map + properties) { + + // Get the service pid and make it as key. + if (cacheHandlingRules.putIfAbsent(getServicePid(properties), cacheHandlingRule) == null) { + log.debug("Cache handling rule implementation {} has been added", cacheHandlingRule.getClass().getName()); + log.debug("Total number of cache handling rule available after addition: {}", cacheHandlingRules.size()); + } + } + + /** + * Unbinds handling rule. + * + * @param cacheHandlingRule + * @param configs + */ + public void unbindHttpCacheHandlingRule(final HttpCacheHandlingRule cacheHandlingRule, final Map configs) { + + if (cacheHandlingRules.remove(getServicePid(configs) ) != null) { + log.debug("Cache handling rule removed - {}.", cacheHandlingRule.getClass().getName()); + log.debug("Total number of cache handling rules available after removal: {}", cacheHandlingRules.size()); + } + } + + public List getCacheConfigs() { + return cacheConfigs; + } + + public Map getCacheStoresMap() { + return cacheStoresMap; + } + + public Map getCacheHandlingRules() { + return cacheHandlingRules; + } + + public Map> getCacheConfigConfigs() { + return cacheConfigConfigs; + } + + private String getServicePid(Map configs) { + String servicePid = PropertiesUtil.toString(configs.get("service.pid"), StringUtils.EMPTY); + + if(StringUtils.isBlank(servicePid)){ + servicePid =PropertiesUtil.toString(configs.get("component.name"), StringUtils.EMPTY); + } + return servicePid; + } + +} diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/delegate/HttpCacheEngineMBeanDelegate.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/delegate/HttpCacheEngineMBeanDelegate.java new file mode 100644 index 0000000000..97586073af --- /dev/null +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/impl/delegate/HttpCacheEngineMBeanDelegate.java @@ -0,0 +1,154 @@ +/* + * #%L + * ACS AEM Commons Bundle + * %% + * Copyright (C) 2016 Adobe + * %% + * Licensed 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. + * #L% + */ +package com.adobe.acs.commons.httpcache.engine.impl.delegate; + +import com.adobe.acs.commons.httpcache.config.HttpCacheConfig; +import com.adobe.acs.commons.httpcache.rule.HttpCacheHandlingRule; +import com.adobe.acs.commons.httpcache.store.HttpCacheStore; +import org.osgi.framework.Constants; + +import javax.management.openmbean.CompositeDataSupport; +import javax.management.openmbean.CompositeType; +import javax.management.openmbean.OpenDataException; +import javax.management.openmbean.OpenType; +import javax.management.openmbean.SimpleType; +import javax.management.openmbean.TabularData; +import javax.management.openmbean.TabularDataSupport; +import javax.management.openmbean.TabularType; + +import java.util.List; +import java.util.Map; +import java.util.HashMap; +import java.util.Iterator; + +/** + * HttpCacheEngineMBeanDelegate + *

+ * Handles the bulk of the mbean logic for the HttpCacheEngineImpl. + *

+ * + */ +public class HttpCacheEngineMBeanDelegate { + + static final String JMX_PN_ORDER = "Order"; + static final String JMX_PN_STORE_TYPE = "Type"; + static final String JMX_PN_OSGICOMPONENT = "OSGi Component"; + static final String JMX_PN_HTTPCACHE_CONFIGS = "HTTP Cache Configs"; + static final String JMX_PN_HTTPCACHE_CONFIG = "HTTP Cache Config"; + static final String JMX_PN_HTTPCACHE_STORE = "HTTP Cache Store"; + static final String JMX_PN_HTTPCACHE_STORES = "HTTP Cache Stores"; + static final String JMX_HTTPCACHE_HANDLING_RULE = "HTTP Cache Handling Rule"; + static final String JMX_PN_HTTPCACHE_HANDLING_RULES = "HTTP Cache Handling Rules"; + + public TabularData getRegisteredHttpCacheRules(Map cacheHandlingRules) throws OpenDataException { + // @formatter:off + final CompositeType cacheEntryType = new CompositeType( + JMX_HTTPCACHE_HANDLING_RULE, + JMX_HTTPCACHE_HANDLING_RULE, + new String[]{JMX_HTTPCACHE_HANDLING_RULE}, + new String[]{JMX_HTTPCACHE_HANDLING_RULE}, + new OpenType[]{SimpleType.STRING}); + + final TabularDataSupport tabularData = new TabularDataSupport( + new TabularType( + JMX_PN_HTTPCACHE_HANDLING_RULES, + JMX_PN_HTTPCACHE_HANDLING_RULES, + cacheEntryType, + new String[]{JMX_HTTPCACHE_HANDLING_RULE})); + // @formatter:on + + for (final Map.Entry entry : cacheHandlingRules.entrySet()) { + final Map row = new HashMap(); + + row.put(JMX_HTTPCACHE_HANDLING_RULE, entry.getValue().getClass().getName()); + tabularData.put(new CompositeDataSupport(cacheEntryType, row)); + } + + return tabularData; + } + + + public TabularData getRegisteredHttpCacheConfigs(List configs, Map> cacheConfigConfigs) throws OpenDataException { + // @formatter:off + // Exposing all google guava stats. + final CompositeType cacheEntryType = new CompositeType( + JMX_PN_HTTPCACHE_CONFIG, + JMX_PN_HTTPCACHE_CONFIG, + new String[]{JMX_PN_ORDER, JMX_PN_STORE_TYPE, JMX_PN_OSGICOMPONENT }, + new String[]{ JMX_PN_ORDER, JMX_PN_STORE_TYPE, JMX_PN_OSGICOMPONENT }, + new OpenType[]{ SimpleType.INTEGER, SimpleType.STRING, SimpleType.STRING }); + + final TabularDataSupport tabularData = new TabularDataSupport( + new TabularType( + JMX_PN_HTTPCACHE_CONFIGS, + JMX_PN_HTTPCACHE_CONFIGS, + cacheEntryType, + new String[]{ JMX_PN_OSGICOMPONENT })); + + // @formatter:on + + for (HttpCacheConfig cacheConfig : configs) { + final Map row = new HashMap(); + + Map osgiConfig = cacheConfigConfigs.get(cacheConfig); + + row.put(JMX_PN_ORDER, cacheConfig.getOrder()); + row.put(JMX_PN_STORE_TYPE, cacheConfig.getCacheStoreName()); + row.put(JMX_PN_OSGICOMPONENT, osgiConfig.get(Constants.SERVICE_PID)); + + tabularData.put(new CompositeDataSupport(cacheEntryType, row)); + } + + return tabularData; + } + + + public TabularData getRegisteredPersistenceStores(Map cacheStoresMap) throws OpenDataException { + // @formatter:off + final CompositeType cacheEntryType = new CompositeType( + JMX_PN_HTTPCACHE_STORE, + JMX_PN_HTTPCACHE_STORE, + new String[]{JMX_PN_HTTPCACHE_STORE}, + new String[]{JMX_PN_HTTPCACHE_STORE}, + new OpenType[]{ SimpleType.STRING}); + + final TabularDataSupport tabularData = new TabularDataSupport( + new TabularType( + JMX_PN_HTTPCACHE_STORES, + JMX_PN_HTTPCACHE_STORES, + cacheEntryType, + new String[]{JMX_PN_HTTPCACHE_STORE})); + // @formatter:on + + for(Iterator> entrySet = cacheStoresMap.entrySet().iterator(); entrySet.hasNext();){ + + Map.Entry entry = entrySet.next(); + final String storeName = entry.getKey(); + final Map row = new HashMap<>(); + + row.put(JMX_PN_HTTPCACHE_STORE, storeName); + tabularData.put(new CompositeDataSupport(cacheEntryType, row)); + + } + + return tabularData; + } + +} diff --git a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/package-info.java b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/package-info.java index b5ed06cdd8..a4ecd347e6 100644 --- a/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/package-info.java +++ b/bundle/src/main/java/com/adobe/acs/commons/httpcache/engine/package-info.java @@ -18,6 +18,6 @@ * #L% */ -@org.osgi.annotation.versioning.Version("3.3.0") +@org.osgi.annotation.versioning.Version("3.4.0") package com.adobe.acs.commons.httpcache.engine; diff --git a/bundle/src/test/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImplTest.java b/bundle/src/test/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImplTest.java index 35a0a5d30d..255878ff05 100644 --- a/bundle/src/test/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImplTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/httpcache/config/impl/HttpCacheConfigImplTest.java @@ -40,25 +40,7 @@ import static com.adobe.acs.commons.httpcache.config.AuthenticationStatusConfigConstants.ANONYMOUS_REQUEST; import static com.adobe.acs.commons.httpcache.config.AuthenticationStatusConfigConstants.AUTHENTICATED_REQUEST; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_AUTHENTICATION_REQUIREMENT; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_CACHE_STORE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_EXPIRY_ON_ACCESS; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_EXPIRY_ON_CREATE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_EXPIRY_ON_UPDATE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_FILTER_SCOPE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.DEFAULT_ORDER; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.FILTER_SCOPE_INCLUDE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_AUTHENTICATION_REQUIREMENT; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_BLACKLISTED_REQUEST_URI_PATTERNS; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_CACHE_HANDLING_RULES_PID; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_CACHE_INVALIDATION_PATH_PATTERNS; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_CACHE_STORE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_EXPIRY_ON_ACCESS; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_EXPIRY_ON_CREATE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_EXPIRY_ON_UPDATE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_FILTER_SCOPE; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_ORDER; -import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.PROP_REQUEST_URI_PATTERNS; +import static com.adobe.acs.commons.httpcache.config.impl.HttpCacheConfigImpl.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -109,6 +91,7 @@ private void activateWithDefaultValues(Map specifiedProps){ properties.put(PROP_EXPIRY_ON_ACCESS,DEFAULT_EXPIRY_ON_ACCESS); properties.put(PROP_EXPIRY_ON_UPDATE,DEFAULT_EXPIRY_ON_UPDATE); properties.put(PROP_CACHE_HANDLING_RULES_PID,new String[]{}); + properties.put(PROP_RESPONSE_HEADER_EXCLUSIONS, new String[]{}); properties.putAll(specifiedProps); context.registerInjectActivateService(systemUnderTest, properties); @@ -147,6 +130,8 @@ public void test_specified_values(){ properties.put(PROP_EXPIRY_ON_ACCESS,10L); properties.put(PROP_EXPIRY_ON_UPDATE,15L); properties.put(PROP_CACHE_HANDLING_RULES_PID,new String[]{"handling-rule"}); + properties.put(PROP_RESPONSE_HEADER_EXCLUSIONS, new String[]{"my-login-header"}); + context.registerInjectActivateService(systemUnderTest, properties); assertEquals(22, systemUnderTest.getOrder()); @@ -159,6 +144,7 @@ public void test_specified_values(){ assertEquals(5L, systemUnderTest.getExpiryOnCreate()); assertEquals(10L, systemUnderTest.getExpiryForAccess()); assertEquals(15L, systemUnderTest.getExpiryForUpdate()); + assertEquals("my-login-header", systemUnderTest.getExcludedResponseHeaderPatterns().get(0).pattern()); assertFalse( systemUnderTest.acceptsRule("nonexisting")); assertTrue( systemUnderTest.acceptsRule("handling-rule")); } diff --git a/bundle/src/test/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImplTest.java b/bundle/src/test/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImplTest.java index 3a24d5ad23..744b093f03 100644 --- a/bundle/src/test/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImplTest.java +++ b/bundle/src/test/java/com/adobe/acs/commons/httpcache/engine/impl/HttpCacheEngineImplTest.java @@ -19,6 +19,7 @@ */ package com.adobe.acs.commons.httpcache.engine.impl; +import com.adobe.acs.commons.fam.ThrottledTaskRunner; import com.adobe.acs.commons.httpcache.config.HttpCacheConfig; import com.adobe.acs.commons.httpcache.engine.CacheContent; import com.adobe.acs.commons.httpcache.engine.HttpCacheServletResponseWrapper; @@ -27,6 +28,7 @@ import com.adobe.acs.commons.httpcache.store.HttpCacheStore; import com.adobe.acs.commons.httpcache.store.mem.impl.MemTempSinkImpl; import com.day.cq.commons.feed.StringResponseWrapper; +import org.apache.commons.collections.map.SingletonMap; import org.apache.commons.io.IOUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; @@ -38,8 +40,10 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.stubbing.Answer; import javax.management.NotCompliantMBeanException; import java.io.ByteArrayOutputStream; @@ -49,7 +53,12 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.List; +import java.util.Arrays; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import static com.adobe.acs.commons.httpcache.engine.impl.HttpCacheEngineImpl.PROP_GLOBAL_RESPONSE_HEADER_EXCLUSIONS; import static com.adobe.acs.commons.httpcache.store.HttpCacheStore.VALUE_JCR_CACHE_STORE_TYPE; import static com.adobe.acs.commons.httpcache.store.HttpCacheStore.VALUE_MEM_CACHE_STORE_TYPE; import static java.util.Collections.emptyMap; @@ -60,7 +69,7 @@ @RunWith(MockitoJUnitRunner.class) public class HttpCacheEngineImplTest { - HttpCacheEngineImpl systemUnderTest; + @Mock HttpCacheConfig memCacheConfig; @@ -74,6 +83,12 @@ public class HttpCacheEngineImplTest { @Mock HttpCacheStore jcrCacheStore; + @Mock + ThrottledTaskRunner throttledTaskRunner; + + @InjectMocks + HttpCacheEngineImpl systemUnderTest; + @Captor ArgumentCaptor cacheContentCaptor; @@ -82,7 +97,6 @@ public class HttpCacheEngineImplTest { @Before public void init() throws NotCompliantMBeanException { - systemUnderTest = new HttpCacheEngineImpl(); systemUnderTest.activate(Collections.emptyMap()); @@ -95,6 +109,12 @@ public void init() throws NotCompliantMBeanException { when(memCacheStore.getStoreType()).thenReturn(VALUE_MEM_CACHE_STORE_TYPE); when(jcrCacheStore.getStoreType()).thenReturn(VALUE_JCR_CACHE_STORE_TYPE); + doAnswer((Answer) invocationOnMock -> { + Runnable runnable = invocationOnMock.getArgumentAt(0, Runnable.class); + runnable.run(); + return null; + }).when(throttledTaskRunner).scheduleWork(any(Runnable.class)); + systemUnderTest.bindHttpCacheConfig(memCacheConfig, sharedMemConfigProps); systemUnderTest.bindHttpCacheConfig(jcrCacheConfig, sharedJcrConfigProps); systemUnderTest.bindHttpCacheStore(memCacheStore); @@ -196,45 +216,75 @@ public void test_deliver_cache_content_outputstream() throws HttpCacheException, @Test public void test_cache_response() throws HttpCacheException, IOException { + //prepare and mock + systemUnderTest.activate(new SingletonMap(PROP_GLOBAL_RESPONSE_HEADER_EXCLUSIONS, new String[]{"ignoredResponseHeaderGlobal"})); + + SlingHttpServletRequest request = new MockSlingHttpServletRequest("/content/acs-commons/home", "my-selector", "html", "", ""); SlingHttpServletResponse response = mock(SlingHttpServletResponse.class); ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream(); + Map headers = new HashMap<>(); when(response.getStatus()).thenReturn(200); when(response.getCharacterEncoding()).thenReturn("utf-8"); when(response.getContentType()).thenReturn("text/html"); + when(response.getHeaderNames()).thenAnswer((Answer) invocationOnMock -> headers.entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList())); + + when(response.getHeader(anyString())).thenAnswer((Answer) invocationOnMock -> headers.get( invocationOnMock.getArgumentAt(0, String.class))[0]); + when(response.getWriter()).thenReturn(new PrintWriter(byteOutputStream)); when(jcrCacheConfig.getFilterScope()).thenReturn(HttpCacheConfig.FilterScope.REQUEST); when(jcrCacheConfig.accepts(request)).thenReturn(true); - HttpCacheConfig foundConfig = systemUnderTest.getCacheConfig(request, HttpCacheConfig.FilterScope.REQUEST); - assertSame(jcrCacheConfig, foundConfig); + when(jcrCacheConfig.getExcludedResponseHeaderPatterns()).thenReturn( Arrays.asList(Pattern.compile("ignoredResponseHeaderConfigSpecific"))); + - CacheKey mockedCacheKey = mock(CacheKey.class); CacheContent mockedCacheContent = mock(CacheContent.class); when(mockedCacheContent.getWriteMethod()).thenReturn(HttpCacheServletResponseWrapper.ResponseWriteMethod.PRINTWRITER); when(mockedCacheContent.getInputDataStream()).thenReturn(getClass().getResourceAsStream("cachecontent.html")); when(mockedCacheContent.getCharEncoding()).thenReturn("utf-8"); //cacheConfig.buildCacheKey(request) + + headers.put("someResponseHeader", new String[]{"SomeValue"}); + headers.put("ignoredResponseHeaderGlobal", new String[]{"SomeValue"}); + headers.put("ignoredResponseHeaderConfigSpecific", new String[]{"SomeValue"}); + + CacheKey mockedCacheKey = mock(CacheKey.class); when(jcrCacheConfig.buildCacheKey(request)).thenReturn(mockedCacheKey); when(jcrCacheStore.contains(mockedCacheKey)).thenReturn(true); when(jcrCacheStore.getIfPresent(mockedCacheKey)).thenReturn(mockedCacheContent); when(jcrCacheStore.createTempSink()).thenReturn(new MemTempSinkImpl()); + + + //execute code + + HttpCacheServletResponseWrapper wrappedResponse = systemUnderTest.wrapResponse(request,response,jcrCacheConfig); wrappedResponse.getWriter().write("rendered-html"); systemUnderTest.cacheResponse(request, wrappedResponse, jcrCacheConfig); + + //assertions + HttpCacheConfig foundConfig = systemUnderTest.getCacheConfig(request, HttpCacheConfig.FilterScope.REQUEST); + assertSame(jcrCacheConfig, foundConfig); + verify(jcrCacheStore,atLeastOnce()).put(eq(mockedCacheKey), cacheContentCaptor.capture()); - assertEquals("utf-8",cacheContentCaptor.getValue().getCharEncoding()); - assertEquals("text/html",cacheContentCaptor.getValue().getContentType()); - assertEquals(200,cacheContentCaptor.getValue().getStatus()); + final CacheContent capturedContent = cacheContentCaptor.getValue(); + assertEquals("utf-8", capturedContent.getCharEncoding()); + assertEquals("text/html", capturedContent.getContentType()); + assertEquals(200, capturedContent.getStatus()); + assertTrue(capturedContent.getHeaders().containsKey("someResponseHeader")); + assertFalse(capturedContent.getHeaders().containsKey("ignoredResponseHeaderGlobal")); + assertFalse(capturedContent.getHeaders().containsKey("ignoredResponseHeaderConfigSpecific")); + + - String cachedHTML = IOUtils.toString(cacheContentCaptor.getValue().getInputDataStream(), StandardCharsets.UTF_8); + String cachedHTML = IOUtils.toString(capturedContent.getInputDataStream(), StandardCharsets.UTF_8); assertEquals("rendered-html", cachedHTML); }