/* * #%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.config.impl; import com.adobe.acs.commons.httpcache.config.HttpCacheConfig; import com.adobe.acs.commons.httpcache.config.HttpCacheConfigExtension; import com.adobe.acs.commons.httpcache.exception.HttpCacheKeyCreationException; import com.adobe.acs.commons.httpcache.exception.HttpCacheRepositoryAccessException; import com.adobe.acs.commons.httpcache.keys.AbstractCacheKey; import com.adobe.acs.commons.httpcache.keys.CacheKey; import com.adobe.acs.commons.httpcache.keys.CacheKeyFactory; import com.adobe.acs.commons.util.ParameterUtil; import com.day.cq.commons.jcr.JcrConstants; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.ConfigurationPolicy; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.PropertyUnbounded; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.commons.osgi.PropertiesUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Implementation for custom cache config extension and associated cache key creation based on resource type. This cache * config extension accepts the http request only if at least one of the configured patterns matches the resource type * of the request's resource. */ @Component(label = "ACS AEM Commons - HTTP Cache - ResourceType based extension for HttpCacheConfig and CacheKeyFactory.", metatype = true, configurationFactory = true, policy = ConfigurationPolicy.REQUIRE ) @Properties({ @Property(name = "webconsole.configurationFactory.nameHint", value = "Allowed resource types: {httpcache.config.extension.resource-types.allowed}", propertyPrivate = true) }) @Service public class ResourceTypeHttpCacheConfigExtension implements HttpCacheConfigExtension, CacheKeyFactory { private static final Logger log = LoggerFactory.getLogger(ResourceTypeHttpCacheConfigExtension.class); // Custom cache config attributes @Property(label = "Allowed paths", description = "Regex of content paths that can be cached.", unbounded = PropertyUnbounded.ARRAY) private static final String PROP_PATHS = "httpcache.config.extension.paths.allowed"; private List<Pattern> pathPatterns; @Property(label = "Allowed resource types", description = "Regex of resource types that can be cached.", unbounded = PropertyUnbounded.ARRAY) private static final String PROP_RESOURCE_TYPES = "httpcache.config.extension.resource-types.allowed"; private List<Pattern> resourceTypePatterns; @Property(label = "Check RT of ./jcr:content?", description = "Should the resourceType check be applied to ./jcr:content ?", boolValue = false) public static final String PROP_CHECK_CONTENT_RESOURCE_TYPE = "httpcache.config.extension.resource-types.page-content"; private boolean checkContentResourceType; //-------------------------<HttpCacheConfigExtension methods> @Override public boolean accepts(SlingHttpServletRequest request, HttpCacheConfig cacheConfig) throws HttpCacheRepositoryAccessException { if (log.isDebugEnabled()) { log.debug("ResourceType acceptance check on [ {} ~> {} ]", request.getResource(), request.getResource().getResourceType()); } for (Pattern pattern : pathPatterns) { Matcher m = pattern.matcher(request.getResource().getPath()); if (!m.matches()) { return false; } } // Passed the content path test.. Resource candidateResource = request.getResource(); if (checkContentResourceType) { candidateResource = candidateResource.getChild(JcrConstants.JCR_CONTENT); if (candidateResource == null) { return false; } } log.debug("ResourceHttpCacheConfigExtension checking for resource type matches"); // Match resource types. for (Pattern pattern : resourceTypePatterns) { Matcher m = pattern.matcher(candidateResource.getResourceType()); if (m.matches()) { if (log.isTraceEnabled()) { log.trace("ResourceHttpCacheConfigExtension accepts request [ {} ]", candidateResource); } return true; } } return false; } //-------------------------<CacheKeyFactory methods> @Override public CacheKey build(final SlingHttpServletRequest slingHttpServletRequest, final HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException { return new ResourceTypeCacheKey(slingHttpServletRequest, cacheConfig); } @Override public CacheKey build(final String resourcePath, final HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException { return new ResourceTypeCacheKey(resourcePath, cacheConfig); } @Override public boolean doesKeyMatchConfig(CacheKey key, HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException { // Check if key is instance of ResourceTypeCacheKey. if (!(key instanceof ResourceTypeCacheKey)) { return false; } // Validate if key request uri can be constructed out of uri patterns in cache config. return new ResourceTypeCacheKey(key.getUri(), cacheConfig).equals(key); } /** * The ResourceTypeCacheKey is a custom CacheKey bound to this particular factory. */ static class ResourceTypeCacheKey extends AbstractCacheKey implements CacheKey { public ResourceTypeCacheKey(SlingHttpServletRequest request, HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException { super(request, cacheConfig); } public ResourceTypeCacheKey(String uri, HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException { super(uri, cacheConfig); } @Override public boolean equals(Object o) { if (!super.equals(o)) { return false; } ResourceTypeCacheKey that = (ResourceTypeCacheKey) o; return new EqualsBuilder() .append(getUri(), that.getUri()) .append(getAuthenticationRequirement(), that.getAuthenticationRequirement()) .isEquals(); } @Override public int hashCode() { return new HashCodeBuilder(17, 37) .append(getUri()) .append(getAuthenticationRequirement()).toHashCode(); } @Override public String toString() { return this.resourcePath + " [AUTH_REQ:" + getAuthenticationRequirement() + "]"; } @Override public String getUri() { return this.resourcePath; } } //-------------------------<OSGi Component methods> @Activate protected void activate(Map<String, Object> configs) { resourceTypePatterns = ParameterUtil.toPatterns(PropertiesUtil.toStringArray(configs.get(PROP_RESOURCE_TYPES), new String[]{})); pathPatterns = ParameterUtil.toPatterns(PropertiesUtil.toStringArray(configs.get(PROP_PATHS), new String[]{})); checkContentResourceType = PropertiesUtil.toBoolean(configs.get(PROP_CHECK_CONTENT_RESOURCE_TYPE),false); log.info("ResourceHttpCacheConfigExtension activated/modified."); } }