001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.acting;
018:
019: import org.apache.avalon.framework.configuration.Configuration;
020: import org.apache.avalon.framework.configuration.ConfigurationException;
021: import org.apache.avalon.framework.parameters.Parameters;
022: import org.apache.avalon.framework.thread.ThreadSafe;
023: import org.apache.cocoon.environment.ObjectModelHelper;
024: import org.apache.cocoon.environment.Redirector;
025: import org.apache.cocoon.environment.Response;
026: import org.apache.cocoon.environment.SourceResolver;
027: import org.apache.commons.lang.time.DateUtils;
028: import org.apache.commons.lang.time.FastDateFormat;
029:
030: import java.util.Calendar;
031: import java.util.Collections;
032: import java.util.HashMap;
033: import java.util.Map;
034:
035: /**
036: * This action adds the <code>Last-Modified</code>, <code>Expires</code> and
037: * <code>Cache-Control</code> HTTP headers to the response.
038: *
039: * <p>
040: * This action will add the <code>Last-Modified</code> header to the response
041: * with the time in which the request was executed, and an <code>Expires</code>
042: * header at a specified time difference. Additionally, it will provide an
043: * extra <code>Cache-Control</code> indicating the maximum age of the request
044: * as a delta between the expiration and last modification dates.
045: * </p>
046: * <p>
047: * This is useful (for example) when Cocoon is proxyied by a Web Server such
048: * as Apache HTTPD running mod_cache, to indicate for each request how long
049: * the output should be cached for.
050: * </p>
051: * <p>
052: * To configure the difference between <code>Last-Modified</code> and
053: * <code>Expires</code> this <code>Action</code> can be configured specifying
054: * days, hours, minutes, and seconds in this way:
055: * </p>
056: * <pre>
057: * <map:action>s
058: * <map:action name="xyz" src="org.apache.cocoon.acting.HttpCacheAction>"
059: * <days>1</day>s
060: * <hours>2</hour>s
061: * <minutes>3</minute>s
062: * <seconds>4</second>s
063: * </map:actio>n
064: * </map:action>s
065: * </pre>
066: * <p>
067: * Using this example configuration, the <code>Expires</code> header will
068: * specify a date one day, two hours, three minutes and four seconds after
069: * the time of the request (which will be in <code>Last-Modified</code>).
070: * </p>
071: * <p>
072: * Note that if any of the parameters mentioned above is <b>zero</b> or
073: * <b>less than zero</b> this action will modify the behaviour of the
074: * resulting <code>Cache-Control</code> header to emit the keyword
075: * <code>no-cache</code>.
076: * </p>
077: * <p>
078: * This action will also return the three headers it added as sitemap
079: * parameters called <code>last-modified</code>, <code>expires</code> and
080: * <code>cache-control</code> (all lowercase).
081: * </p>
082: *
083: * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>
084: * @version CVS $Id: HttpCacheAction.java 30941 2004-07-29 19:56:58Z vgritsenko $
085: */
086: public class HttpCacheAction extends AbstractConfigurableAction
087: implements ThreadSafe {
088:
089: private FastDateFormat formatter = null;
090: int days = 0;
091: int hours = 0;
092: int minutes = 0;
093: int seconds = 0;
094:
095: public void configure(Configuration configuration)
096: throws ConfigurationException {
097: super .configure(configuration);
098:
099: // RFC-822 Date with a GMT based time zone
100: this .formatter = FastDateFormat.getInstance(
101: "EEE, dd MMM yyyy kk:mm:ss zzz",
102: DateUtils.UTC_TIME_ZONE);
103: this .days = configuration.getChild("days").getValueAsInteger(0);
104: this .hours = configuration.getChild("hours").getValueAsInteger(
105: 0);
106: this .minutes = configuration.getChild("minutes")
107: .getValueAsInteger(0);
108: this .seconds = configuration.getChild("seconds")
109: .getValueAsInteger(0);
110: }
111:
112: public Map act(Redirector redirector, SourceResolver resolver,
113: Map objectModel, String source, Parameters parameters)
114: throws Exception {
115: Response response = ObjectModelHelper.getResponse(objectModel);
116: Calendar calendar = Calendar
117: .getInstance(DateUtils.UTC_TIME_ZONE);
118: Map values = new HashMap(3);
119:
120: /* Get the current time and output as the last modified header */
121: String value = this .formatter.format(calendar);
122: long maxage = calendar.getTime().getTime();
123: response.setHeader("Last-Modified", value);
124: values.put("last-modified", value);
125:
126: /* Advance the time as much as required */
127: calendar.add(Calendar.DATE, this .days);
128: calendar.add(Calendar.HOUR, this .hours);
129: calendar.add(Calendar.MINUTE, this .minutes);
130: calendar.add(Calendar.SECOND, this .seconds);
131:
132: /* Recalculate time and age to see what changed */
133: maxage = calendar.getTime().getTime() - maxage;
134:
135: /* If we got more than one second everything is quite normal */
136: if (maxage > 1000) {
137: value = this .formatter.format(calendar);
138: response.setHeader("Expires", value);
139: values.put("expires", value);
140:
141: value = "max-age=" + Long.toString(maxage / 1000l);
142: response.setHeader("Cache-Control", value);
143: values.put("cache-control", value);
144:
145: /* If we got less than one second (even negatives) no cache */
146: } else {
147: /* We still hold the old value from Last-Modified here */
148: response.setHeader("Expires", value);
149: values.put("expires", value);
150:
151: response.setHeader("Cache-Control", "no-cache");
152: values.put("cache-control", "no-cache");
153: }
154:
155: /* Return the headers */
156: return (Collections.unmodifiableMap(values));
157: }
158: }
|