/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ package org.apache.cocoon.acting; 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.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; import java.util.Calendar; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * 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>s * <map:action name="xyz" src="org.apache.cocoon.acting.HttpCacheAction>" * <days>1</day>s * <hours>2</hour>s * <minutes>3</minute>s * <seconds>4</second>s * </map:actio>n * </map:action>s * </pre> * <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> * * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a> * @version CVS $Id: HttpCacheAction.java 30941 2004-07-29 19:56:58Z vgritsenko $ */ public class HttpCacheAction extends AbstractConfigurableAction implements ThreadSafe { private FastDateFormat formatter = null; int days = 0; int hours = 0; int minutes = 0; int seconds = 0; 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); } public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception { 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)); } }