/* * Copyright (c) 2002-2009 Juwi MacMillan Group GmbH (JuwiMM) * Bockhorn 1, 29664 Walsrode, Germany * All rights reserved. * * This software is the confidential and proprietary information of JuwiMM * ("Confidential Information"). You shall not disclose such * Confidential Information and shall use it only in accordance with the * terms of the license agreement you entered into with JuwiMM. */ package org.tizzit.cocoon.generic.acting; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.thread.ThreadSafe; import org.apache.cocoon.acting.AbstractConfigurableAction; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Redirector; import org.apache.cocoon.environment.Response; import org.apache.cocoon.environment.SourceResolver; import org.apache.commons.lang.time.DateUtils; import org.apache.commons.lang.time.FastDateFormat; /** * This action adds the <code>Last-Modified</code>, <code>Expires</code> and * <code>Cache-Control</code> HTTP headers to the response. * * <p> * This action will add the <code>Last-Modified</code> header to the response * with the time in which the request was executed, and an <code>Expires</code> * header at a specified time difference. Additionally, it will provide an * extra <code>Cache-Control</code> indicating the maximum age of the request * as a delta between the expiration and last modification dates. * </p> * <p> * This is useful (for example) when Cocoon is proxyied by a Web Server such * as Apache HTTPD running mod_cache, to indicate for each request how long * the output should be cached for. * </p> * <p> * To configure the difference between <code>Last-Modified</code> and * <code>Expires</code> this <code>Action</code> can be configured specifying * days, hours, minutes, and seconds in this way: * </p> * <pre> * <map:action> * <map:action name="xyz" src="de.juwimm.cocoon.components.acting.HttpCacheAction>" * <days>1</day> * <hours>2</hour> * <minutes>3</minute> * <seconds>4</second> * </map:actio> * </map:action> * </pre> * <p> * The configuration can be overridden specifying days, hours, minutes, and seconds in the following way. * The associated global setting will be used if one the parameter is missing. * </p> * <pre> <map:act type="xyz"> * <map:parameter name="days" value="1337" /> * <map:parameter name="hours" value="1" /> * <map:parameter name="minutes" value="33" /> * <map:parameter name="seconds" value="7" /> * </map:act></pre> * <p> * If the * </p> * <p> * Using this example configuration, the <code>Expires</code> header will * specify a date one day, two hours, three minutes and four seconds after * the time of the request (which will be in <code>Last-Modified</code>). * </p> * <p> * Note that if any of the parameters mentioned above is <b>zero</b> or * <b>less than zero</b> this action will modify the behaviour of the * resulting <code>Cache-Control</code> header to emit the keyword * <code>no-cache</code>. * </p> * <p> * This action will also return the three headers it added as sitemap * parameters called <code>last-modified</code>, <code>expires</code> and * <code>cache-control</code> (all lowercase). * </p> * * @cocoon.sitemap.component.documentation * This action adds the <code>Last-Modified</code>, <code>Expires</code> and * <code>Cache-Control</code> HTTP headers to the response. * * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a> * @author <a href="mailto:eduard.siebert@juwimm.com">Eduard Siebert</a> * company Juwi MacMillan Group GmbH, Walsrode, Germany * @version $Id$ * @since tizzit-cocoon-components 02.11.2009 */ public class HttpCacheAction extends AbstractConfigurableAction implements ThreadSafe { private FastDateFormat formatter = null; int days = 0; int hours = 0; int minutes = 0; int seconds = 0; @Override public void configure(Configuration configuration) throws ConfigurationException { super.configure(configuration); // RFC-822 Date with a GMT based time zone this.formatter = FastDateFormat.getInstance("EEE, dd MMM yyyy kk:mm:ss zzz", DateUtils.UTC_TIME_ZONE); this.days = configuration.getChild("days").getValueAsInteger(0); this.hours = configuration.getChild("hours").getValueAsInteger(0); this.minutes = configuration.getChild("minutes").getValueAsInteger(0); this.seconds = configuration.getChild("seconds").getValueAsInteger(0); } @SuppressWarnings("unchecked") public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception { this.overrideConfiguration(parameters); Response response = ObjectModelHelper.getResponse(objectModel); Calendar calendar = Calendar.getInstance(DateUtils.UTC_TIME_ZONE); Map values = new HashMap(3); /* Get the current time and output as the last modified header */ String value = this.formatter.format(calendar); long maxage = calendar.getTime().getTime(); response.setHeader("Last-Modified", value); values.put("last-modified", value); /* Advance the time as much as required */ calendar.add(Calendar.DATE, this.days); calendar.add(Calendar.HOUR, this.hours); calendar.add(Calendar.MINUTE, this.minutes); calendar.add(Calendar.SECOND, this.seconds); /* Recalculate time and age to see what changed */ maxage = calendar.getTime().getTime() - maxage; /* If we got more than one second everything is quite normal */ if (maxage > 1000) { value = this.formatter.format(calendar); response.setHeader("Expires", value); values.put("expires", value); value = "max-age=" + Long.toString(maxage / 1000l); response.setHeader("Cache-Control", value); values.put("cache-control", value); /* If we got less than one second (even negatives) no cache */ } else { /* We still hold the old value from Last-Modified here */ response.setHeader("Expires", value); values.put("expires", value); response.setHeader("Cache-Control", "no-cache"); values.put("cache-control", "no-cache"); } /* Return the headers */ return (Collections.unmodifiableMap(values)); } private void overrideConfiguration(Parameters parameters) { this.days = parameters.getParameterAsInteger("days", this.days); this.hours = parameters.getParameterAsInteger("hours", this.hours); this.minutes = parameters.getParameterAsInteger("minutes", this.minutes); this.seconds = parameters.getParameterAsInteger("seconds", this.seconds); } }