Skip to content

Commit

Permalink
Add a dispatcher filter that calculates the ttl header based on a pag… (
Browse files Browse the repository at this point in the history
#2465)

* Add a dispatcher filter that calculates the ttl header based on a page property

* Change the logic so that it can work both on pages and custom resources

* Make it possible to use sling http filters instead of the http whiteboard

Co-authored-by: Roy Teeuwen <roy.teeuwen@kbc.be>
  • Loading branch information
royteeuwen and Roy Teeuwen authored Nov 19, 2020
1 parent 14d769c commit 5bad843
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 23 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com)
<!-- Keep this up to date! After a release, change the tag name to the latest release -->
[unreleased changes details]: https://github.com/Adobe-Consulting-Services/acs-aem-commons/compare/acs-aem-commons-4.7.2...HEAD

### Added
- Add possibility to do page property based dispatcher ttl cache headers

## 4.9.2 - 2020-11-10

### Fixed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.component.ComponentContext;
Expand Down Expand Up @@ -64,6 +65,10 @@ public abstract class AbstractDispatcherCacheHeaderFilter implements Filter {

protected static final String DISPATCHER_AGENT_HEADER_VALUE = "Communique-Dispatcher";

public static final String PROP_DISPATCHER_FILTER_ENGINE = "dispatcher.filter.engine";
public static final String PROP_DISPATCHER_FILTER_ENGINE_SLING = "sling";
public static final String PROP_DISPATCHER_FILTER_ENGINE_HTTP_WHITEBOARD = "http-whiteboard";

private List<ServiceRegistration> filterRegistrations = new ArrayList<ServiceRegistration>();

private boolean allowAllParams = false;
Expand All @@ -82,7 +87,7 @@ public abstract class AbstractDispatcherCacheHeaderFilter implements Filter {
*
* @return the value of the Cache-Control header
*/
protected abstract String getHeaderValue();
protected abstract String getHeaderValue(HttpServletRequest request);


/*
Expand Down Expand Up @@ -111,7 +116,7 @@ public final void doFilter(final ServletRequest servletRequest, final ServletRes

if (this.accepts(request)) {
String header = getHeaderName();
String val = getHeaderValue();
String val = getHeaderValue(request);
String attributeName = AbstractDispatcherCacheHeaderFilter.class.getName() + ".header." + header;
if (request.getAttribute(attributeName) == null) {
log.debug("Adding header {}: {}", header, val);
Expand Down Expand Up @@ -168,6 +173,8 @@ protected final void activate(ComponentContext context) throws Exception {
throw new ConfigurationException(PROP_FILTER_PATTERN, "At least one filter pattern must be specified.");
}

String filterEngine = PropertiesUtil.toString(properties.get(PROP_DISPATCHER_FILTER_ENGINE), PROP_DISPATCHER_FILTER_ENGINE_HTTP_WHITEBOARD);

allowAllParams = PropertiesUtil.toBoolean(properties.get(PROP_ALLOW_ALL_PARAMS), false);
passThroughParams = Arrays.asList(PropertiesUtil.toStringArray(properties.get(PROP_PASS_THROUGH_PARAMS), new String[0]));
blockParams = Arrays.asList(PropertiesUtil.toStringArray(properties.get(PROP_BLOCK_PARAMS), new String[0]));
Expand All @@ -176,8 +183,18 @@ protected final void activate(ComponentContext context) throws Exception {
Dictionary<String, Object> filterProps = new Hashtable<String, Object>();

log.debug("Adding filter ({}) to pattern: {}", this, pattern);
filterProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_REGEX, pattern);
filterProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=*)");
filterProps.put(Constants.SERVICE_RANKING, PropertiesUtil.toInteger(properties.get(Constants.SERVICE_RANKING), 0));

// If you want the filter ranking to work, all dispatcher filters have to be type "sling",
// else the http-whiteboard will always have precedence
if ("sling".equals(filterEngine)) {
filterProps.put("sling.filter.scope", "REQUEST");
filterProps.put("sling.filter.request.pattern", pattern);
} else {
filterProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_FILTER_REGEX, pattern);
filterProps.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=org.apache.sling)");
}

ServiceRegistration filterReg = context.getBundleContext().registerService(Filter.class.getName(), this, filterProps);
filterRegistrations.add(filterReg);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* 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.
Expand All @@ -34,7 +34,7 @@
import java.util.TimeZone;

/**
* Provides standard functionality to specify an Expires header for Dispatcher Cache rules.
* Provides standard functionality to specify an Expires header for Dispatcher Cache rules.
*/
public abstract class AbstractExpiresHeaderFilter extends AbstractDispatcherCacheHeaderFilter {

Expand All @@ -48,12 +48,12 @@ public abstract class AbstractExpiresHeaderFilter extends AbstractDispatcherCach
private Calendar expiresTime = Calendar.getInstance();

/**
* Sublcass implementations will adjust the date of the specified calendar to the
* Sublcass implementations will adjust the date of the specified calendar to the
* next point at which content should expire.
*
*
* The calendar passed will be the set to the correct time the current day.
* Concrete implementations are required to update the Calendar to the correct <i>next</i> expiration time.
*
*
* @param nextExpiration a {@link Calendar} to adjust to next expiration date.
*/
protected abstract void adjustExpires(Calendar nextExpiration);
Expand All @@ -64,7 +64,7 @@ protected String getHeaderName() {
}

@Override
protected String getHeaderValue() {
protected String getHeaderValue(HttpServletRequest request) {
Calendar next = Calendar.getInstance();
next.set(Calendar.HOUR_OF_DAY, expiresTime.get(Calendar.HOUR_OF_DAY));
next.set(Calendar.MINUTE, expiresTime.get(Calendar.MINUTE));
Expand All @@ -80,7 +80,7 @@ protected String getHeaderValue() {
@Override
@SuppressWarnings("unchecked")
protected boolean accepts(HttpServletRequest request) {

if (super.accepts(request)) {
Enumeration<String> expiresheaders = request.getHeaders(EXPIRES_NAME);
return expiresheaders == null || !expiresheaders.hasMoreElements();
Expand All @@ -98,14 +98,14 @@ protected void doActivate(ComponentContext context) throws Exception {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm");
sdf.setLenient(false);
try {
Date date = sdf.parse(time);
expiresTime.setTime(date);
Date date = sdf.parse(time);
expiresTime.setTime(date);
} catch (ParseException ex) {
throw new ConfigurationException(PROP_EXPIRES_TIME, "Expires Time must be specified.");
}
}

public String toString() {
return this.getClass().getName() + "[" + getHeaderValue() + "]";
return this.getClass().getName() + "[" + getHeaderValue(null) + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.component.ComponentContext;
Expand Down Expand Up @@ -60,7 +61,7 @@ public class DispatcherMaxAgeHeaderFilter extends AbstractDispatcherCacheHeaderF
description = "Max age value (in seconds) to put in Cache Control header.")
public static final String PROP_MAX_AGE = "max.age";

private static final String HEADER_PREFIX = "max-age=";
protected static final String HEADER_PREFIX = "max-age=";

private long maxage;

Expand All @@ -70,7 +71,7 @@ protected String getHeaderName() {
}

@Override
protected String getHeaderValue() {
protected String getHeaderValue(HttpServletRequest request) {
return HEADER_PREFIX + maxage;
}

Expand All @@ -84,6 +85,6 @@ protected void doActivate(ComponentContext context) throws Exception {
}

public String toString() {
return this.getClass().getName() + "[" + getHeaderValue() + "]";
return this.getClass().getName() + "[" + getHeaderValue(null) + "]";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2013 - 2020 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.http.headers.impl;

import com.day.cq.commons.PathInfo;
import org.apache.commons.lang.StringUtils;
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.jackrabbit.JcrConstants;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.util.Dictionary;

import static com.adobe.acs.commons.http.headers.impl.AbstractDispatcherCacheHeaderFilter.PROP_DISPATCHER_FILTER_ENGINE;
import static com.adobe.acs.commons.http.headers.impl.AbstractDispatcherCacheHeaderFilter.PROP_DISPATCHER_FILTER_ENGINE_SLING;

@Component(
label = "ACS AEM Commons - Dispacher Cache Control Header Property Based - Max Age",
description = "Adds a Cache-Control max-age header to content based on property value to enable Dispatcher TTL support.",
metatype = true,
configurationFactory = true,
policy = ConfigurationPolicy.REQUIRE)
@Properties({
@Property(
name = "webconsole.configurationFactory.nameHint",
value = "Property name: {property.name}. Fallback Max Age: {max.age} ",
propertyPrivate = true),
@Property(
name = PROP_DISPATCHER_FILTER_ENGINE,
value = PROP_DISPATCHER_FILTER_ENGINE_SLING,
propertyPrivate = true)
})
public class PropertyBasedDispatcherMaxAgeHeaderFilter extends DispatcherMaxAgeHeaderFilter {

private static final Logger log = LoggerFactory.getLogger(PropertyBasedDispatcherMaxAgeHeaderFilter.class);

@Property(label = "Property name",
description = "Property to check on how long you want to cache this request")
public static final String PROP_PROPERTY_NAME = "property.name";

@Property(label = "Inherit property value",
description = "Property value to skip this filter and inherit another lower service ranking dispatcher ttl filter")
public static final String PROP_INHERIT_PROPERTY_VALUE = "inherit.property.value";

private String propertyName;
private String inheritPropertyValue;

@Override
@SuppressWarnings("unchecked")
protected boolean accepts(final HttpServletRequest request) {
if (!super.accepts(request)) {
log.debug("Not accepting request because it is not coming from the dispatcher.");
return false;
}
if (request instanceof SlingHttpServletRequest) {
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
Resource resource = getResource(request, slingRequest.getResourceResolver());
if (resource == null) {
log.debug("Could not find resource for request, not accepting");
return false;
}
String headerValue = resource.getValueMap().get(propertyName, String.class);
if (!StringUtils.isBlank(headerValue) && !inheritPropertyValue.equals(headerValue)) {
log.debug("Found a max age header value for request {} that is not the inherit value, accepting", resource.getPath());
return true;
}
log.debug("PResource property is blank or INHERIT, not taking this filter ");
return false;
}
return false;
}

@Override
protected String getHeaderValue(HttpServletRequest request) {
if (request instanceof SlingHttpServletRequest) {
SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;
Resource resource = getResource(request, slingRequest.getResourceResolver());
if (resource != null) {
String headerValue = resource.getValueMap().get(propertyName, String.class);
return HEADER_PREFIX + headerValue;
}
}
log.debug("An error occurred, falling back to the default max age value of this filter");
return super.getHeaderValue(request);
}

private Resource getResource(HttpServletRequest request, ResourceResolver resourceResolver) {
PathInfo pathInfo = new PathInfo(request.getRequestURI());
Resource reqResource = resourceResolver.getResource(pathInfo.getResourcePath());
if (reqResource != null) {
if (reqResource.isResourceType("cq:Page")) {
return reqResource.getChild(JcrConstants.JCR_CONTENT);
}
return reqResource;
}
return null;
}

@SuppressWarnings("squid:S1149")
protected final void doActivate(ComponentContext context) throws Exception {
super.doActivate(context);
Dictionary<?, ?> properties = context.getProperties();

propertyName = PropertiesUtil.toString(properties.get(PROP_PROPERTY_NAME), null);
if (propertyName == null) {
throw new ConfigurationException(PROP_PROPERTY_NAME, "Property name should be specified.");
}
inheritPropertyValue = PropertiesUtil.toString(properties.get(PROP_INHERIT_PROPERTY_VALUE), "INHERIT");
}

public String toString() {
return this.getClass().getName() + "[property-name:" + propertyName + ",fallback-max-age:" + super.getHeaderValue(null) + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ protected String getHeaderName() {
}

@Override
protected String getHeaderValue() {
protected String getHeaderValue(HttpServletRequest request) {
return headerValue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void testGetHeaderValue() throws Exception {
expected.set(Calendar.MILLISECOND, 0);

filter.doActivate(componentContext);
String header = filter.getHeaderValue();
String header = filter.getHeaderValue(request);
Date date = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z").parse(header);
Calendar actual = Calendar.getInstance();
actual.setTime(date);
Expand Down Expand Up @@ -188,7 +188,7 @@ public void testDoActivateSuccess() throws Exception {
when(componentContext.getProperties()).thenReturn(properties);

filter.doActivate(componentContext);
assertNotNull(filter.getHeaderValue());
assertNotNull(filter.getHeaderValue(request));
verify(componentContext).getProperties();
verifyNoMoreInteractions(componentContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public void testGetHeaderValue() throws Exception {
when(componentContext.getProperties()).thenReturn(properties);

filter.doActivate(componentContext);
assertEquals("max-age=" + maxage, filter.getHeaderValue());
assertEquals("max-age=" + maxage, filter.getHeaderValue(request));
}

@Test(expected = ConfigurationException.class)
Expand All @@ -120,7 +120,7 @@ public void testDoActivateSuccess() throws Exception {
when(componentContext.getProperties()).thenReturn(properties);

filter.doActivate(componentContext);
assertEquals("max-age=" + maxage, filter.getHeaderValue());
assertEquals("max-age=" + maxage, filter.getHeaderValue(request));
verify(componentContext).getProperties();
verifyNoMoreInteractions(componentContext);

Expand Down

0 comments on commit 5bad843

Please sign in to comment.