Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bundle/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@
<groupId>org.apache.sling</groupId>
<artifactId>org.apache.sling.models.api</artifactId>
</dependency>
<dependency>
<groupId>com.adobe.acs</groupId>
<artifactId>acs-aem-commons-bundle</artifactId>
</dependency>
<dependency>
<groupId>com.adobe.aem</groupId>
<artifactId>uber-jar</artifactId>
Expand Down Expand Up @@ -187,6 +191,14 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package com.adobe.acs.samples.httpcache;

/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2015 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%
*/

import com.adobe.acs.commons.httpcache.engine.HttpCacheEngine;
import com.adobe.acs.commons.httpcache.exception.HttpCacheKeyCreationException;
import com.adobe.acs.commons.httpcache.exception.HttpCachePersistenceException;
import com.day.cq.commons.jcr.JcrConstants;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.resource.observation.ResourceChange;
import org.apache.sling.api.resource.observation.ResourceChangeListener;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Automated Memory Cache Flusher
* <p>
* Helps reducing maintenance by automatically flushing the ACS commons memory cache by implementing ResourceChangeListener.
* </p>
*/
@Component(
configurationPolicy = ConfigurationPolicy.OPTIONAL,
immediate = true,
property = {
ResourceChangeListener.PATHS + "=/apps/acs-samples/components/structure/page/layout",
ResourceChangeListener.PATHS + "=/content/acs-samples",
ResourceChangeListener.CHANGES + "=ADDED",
ResourceChangeListener.CHANGES + "=CHANGED",
ResourceChangeListener.CHANGES + "=REMOVED",
}
)
// @formatter:on
public class AutomatedLayoutPageInvalidator implements ResourceChangeListener {

private static final Logger LOG = LoggerFactory.getLogger(AutomatedLayoutPageInvalidator.class);
private static final String PATH_SUFFIX = "/" + JcrConstants.JCR_CONTENT + ".content.html";

private static final Pattern PATTERN = Pattern.compile("((/content/acs-samples/(headers|footers)/[a-zA-Z0-9_-]{1,99}))(.*)/(.*)");

@Reference
private HttpCacheEngine engine;

@Override
public void onChange(List<ResourceChange> changes) {
for (ResourceChange change : changes) {

LOG.debug("Attempting to extract header path from: {}", change.getPath());
String layoutPagePath = extractHeaderPath(change.getPath());

if (StringUtils.isNotEmpty(layoutPagePath)) {
LOG.debug("Extracted header path: {}", layoutPagePath);
try {
LOG.debug("Flushing path {}", layoutPagePath + PATH_SUFFIX);
engine.invalidateCache(layoutPagePath + PATH_SUFFIX);
} catch (HttpCachePersistenceException | HttpCacheKeyCreationException e) {
LOG.error("Error flushing path!", e);
}
}

}
}

private String extractHeaderPath(String headerChildResourcePath) {
Matcher matcher = PATTERN.matcher(headerChildResourcePath);

if (matcher.matches()) {
return matcher.group(1);
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package com.adobe.acs.samples.httpcache;

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.CacheKey;
import com.adobe.acs.commons.httpcache.keys.CacheKeyFactory;
import com.adobe.acs.samples.httpcache.definitions.CookieCacheExtensionConfig;
import com.adobe.acs.samples.httpcache.key.CookieCacheKey;
import com.adobe.acs.samples.httpcache.key.CookieKeyValueMap;
import com.adobe.acs.samples.httpcache.key.CookieKeyValueMapBuilder;
import com.google.common.collect.ImmutableSet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import java.util.Set;

/**
* CookieCacheExtension
* <p>
* This extension on the HTTP cache allows for specific cookie combinations to create seperated cache entries.
* This so we can present a different header based on cookie values, which tell us if a user is logged in and what type of user it is.
* </p>
*/
@Component(configurationPolicy = ConfigurationPolicy.REQUIRE , service = {HttpCacheConfigExtension.class, CacheKeyFactory.class})
@Designate(ocd = CookieCacheExtensionConfig.class, factory = true)
public class CookieCacheExtension implements HttpCacheConfigExtension, CacheKeyFactory {
private static final Logger log = LoggerFactory.getLogger(CookieCacheExtension.class);

private boolean emptyAllowed;
private String configName;
private Set<String> cookieKeys;

//-------------------------<HttpCacheConfigExtension methods>

@Override
public boolean accepts(SlingHttpServletRequest request, HttpCacheConfig cacheConfig) throws
HttpCacheRepositoryAccessException {

if(emptyAllowed){
return true;
}else{
Set<Cookie> presentCookies = ImmutableSet.copyOf(request.getCookies());
return containsAtLeastOneMatch(presentCookies);
}
}

private boolean containsAtLeastOneMatch(Set<Cookie> presentCookies){
CookieKeyValueMapBuilder builder = new CookieKeyValueMapBuilder(cookieKeys, presentCookies);
CookieKeyValueMap map = builder.build();
return !map.isEmpty();
}


//-------------------------<CacheKeyFactory methods>

@Override
public CacheKey build(final SlingHttpServletRequest slingHttpServletRequest, final HttpCacheConfig cacheConfig)
throws HttpCacheKeyCreationException {

ImmutableSet<Cookie> presentCookies = ImmutableSet.copyOf(slingHttpServletRequest.getCookies());
CookieKeyValueMapBuilder builder = new CookieKeyValueMapBuilder(cookieKeys, presentCookies);
return new CookieCacheKey(slingHttpServletRequest, cacheConfig, builder.build());
}


public CacheKey build(String resourcePath, HttpCacheConfig httpCacheConfig) throws HttpCacheKeyCreationException {
return new CookieCacheKey(resourcePath, httpCacheConfig, new CookieKeyValueMap());
}

@Override
public boolean doesKeyMatchConfig(CacheKey key, HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException {

// Check if key is instance of GroupCacheKey.
if (!(key instanceof CookieCacheKey)) {
return false;
}

CookieCacheKey thatKey = (CookieCacheKey) key;

return new CookieCacheKey(thatKey.getUri(), cacheConfig,thatKey.getKeyValueMap()).equals(key);
}


//-------------------------<OSGi Component methods>

@Activate
protected void activate(CookieCacheExtensionConfig config) {
this.cookieKeys = ImmutableSet.copyOf(config.allowedCookieKeys());
this.configName = config.configName();
this.emptyAllowed = config.emptyAllowed();
log.info("GroupHttpCacheConfigExtension activated/modified.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.adobe.acs.samples.httpcache;

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.CacheKey;
import com.adobe.acs.commons.httpcache.keys.CacheKeyFactory;
import com.adobe.acs.samples.httpcache.definitions.ResourcePathCacheExtensionConfig;
import com.adobe.acs.samples.httpcache.key.ResourcePathCacheKey;
import org.apache.commons.lang.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.metatype.annotations.Designate;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

/**
* ResourcePathCacheExtension
* <p>
* Simple cache extension to only create keys based on the resource path.
* </p>
*/
@Component(configurationPolicy = ConfigurationPolicy.REQUIRE , service = {HttpCacheConfigExtension.class, CacheKeyFactory.class})
@Designate(ocd = ResourcePathCacheExtensionConfig.class, factory = true)
public class ResourcePathCacheExtension implements HttpCacheConfigExtension, CacheKeyFactory {

private List<Pattern> resourcePathPatterns;
private List<Pattern> selectorPatterns;
private List<Pattern> extensionPatterns;

private String configName;

@Override
public boolean accepts(SlingHttpServletRequest request, HttpCacheConfig cacheConfig) throws
HttpCacheRepositoryAccessException {
String resourcePath = request.getRequestPathInfo().getResourcePath();

if(!matches(resourcePathPatterns, resourcePath)){
return false;
}

if(!matches(selectorPatterns, request.getRequestPathInfo().getSelectorString())){
return false;
}

if(!matches(extensionPatterns, request.getRequestPathInfo().getExtension())){
return false;
}

return true;
}

private boolean matches(List<Pattern> source, String query) {
if(StringUtils.isNotBlank(query)){
for(Pattern pattern : source){
if(pattern.matcher(query).find()){
return true;
}
}
}
return false;
}

@Override
public CacheKey build(final SlingHttpServletRequest slingHttpServletRequest, final HttpCacheConfig cacheConfig)
throws HttpCacheKeyCreationException {
return new ResourcePathCacheKey(slingHttpServletRequest, cacheConfig);
}


public CacheKey build(String resourcePath, HttpCacheConfig httpCacheConfig) throws HttpCacheKeyCreationException {
return new ResourcePathCacheKey(resourcePath, httpCacheConfig);
}

@Override
public boolean doesKeyMatchConfig(CacheKey key, HttpCacheConfig cacheConfig) throws HttpCacheKeyCreationException {

// Check if key is instance of GroupCacheKey.
if (!(key instanceof ResourcePathCacheKey)) {
return false;
}

ResourcePathCacheKey thatKey = (ResourcePathCacheKey) key;

return new ResourcePathCacheKey(thatKey.getUri(), cacheConfig).equals(key);
}

@Activate
protected void activate(ResourcePathCacheExtensionConfig config){
this.configName = config.configName();
this.resourcePathPatterns = compileToPatterns(config.resourcePathPatterns());
this.extensionPatterns = compileToPatterns(config.extensions());
this.selectorPatterns = compileToPatterns(config.selectors());
}

private List<Pattern> compileToPatterns(final String[] regexes) {
final List<Pattern> patterns = new ArrayList<Pattern>();

for (String regex : regexes) {
if (StringUtils.isNotBlank(regex)) {
patterns.add(Pattern.compile(regex));
}
}

return patterns;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.adobe.acs.samples.httpcache.definitions;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

/**
* CookieCacheExtensionConfig
* <p>
* Configuration OCD object for the CookiecCacheExtension
* </p>
*/
@ObjectClassDefinition(
name = "CookieCacheExtensionConfig - Configuration OCD object for the CookiecCacheExtension",
description = "Extension for the ACS commons HTTP Cache. Leverages cookies."
)
public @interface CookieCacheExtensionConfig {

@AttributeDefinition(
name = "Configuration Name",
description = "The unique identifier of this extension"
)
String configName() default "";

@AttributeDefinition(
name = "Allowed Cookies",
description = "Cookie keys that will used to generate a cache key."
)
String[] allowedCookieKeys() default {};

@AttributeDefinition(
name = "Empty is allowed",
description = "Cookie keys that will used to generate a cache key."
)
boolean emptyAllowed() default false;

}
Loading