KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > oreilly > servlet > CacheHttpServlet


1 // Copyright (C) 1999-2001 by Jason Hunter <jhunter_AT_acm_DOT_org>.
2
// All rights reserved. Use of this class is limited.
3
// Please see the LICENSE for more information.
4

5 package com.oreilly.servlet;
6
7 import java.io.*;
8 import java.util.*;
9 import javax.servlet.*;
10 import javax.servlet.http.*;
11
12 /**
13  * <p>A superclass for HTTP servlets</p>
14  * Use it when the servlet must have its output
15  * cached and automatically resent as appropriate according to the
16  * servlet's getLastModified() method. To take advantage of this class,
17  * a servlet must:<br>
18  * <ul>
19  * <li>Extend <tt>CacheHttpServlet</tt> instead of <tt>HttpServlet</tt>
20  * <li>Implement a <tt>getLastModified(HttpServletRequest)</tt> method as usual
21  * </ul>
22  * This class uses the value returned by <tt>getLastModified()</tt> to manage
23  * an internal cache of the servlet's output. Before handling a request,
24  * this class checks the value of <tt>getLastModified()</tt>, and if the
25  * output cache is at least as current as the servlet's last modified time,
26  * the cached output is sent without calling the servlet's <tt>doGet()</tt>
27  * method.
28  * <p>
29  * In order to be safe, if this class detects that the servlet's query
30  * string, extra path info, or servlet path has changed, the cache is
31  * invalidated and recreated. However, this class does not invalidate
32  * the cache based on differing request headers or cookies; for
33  * servlets that vary their output based on these values (i.e. a session
34  * tracking servlet) this class should probably not be used.
35  * <p>
36  * No caching is performed for POST requests.
37  * <p>
38  * <tt>CacheHttpServletResponse</tt> and <tt>CacheServletOutputStream</tt>
39  * are helper classes to this class and should not be used directly.
40  * <p>
41  * This class has been built against Servlet API 2.2. Using it with previous
42  * Servlet API versions should work; using it with future API versions likely
43  * won't work.
44  * <p>
45  * If you get the error Cannot resolve symbol method getContentType()
46  * Then include library /lib/compile/servlet.jar when compiling the project.
47  *
48  * @author <b>Jason Hunter</b>, Copyright &#169; 1999
49  * @version 0.92, 00/03/16, added synchronization blocks to make thread safe
50  * @version 0.91, 99/12/28, made support classes package protected
51  * @version 0.90, 99/12/19
52  */

53
54 public abstract class CacheHttpServlet extends HttpServlet {
55
56   // ---------------------------------------------------------------------------
57

58   CacheHttpServletResponse cacheResponse;
59   long cacheLastMod = -1;
60   String JavaDoc cacheQueryString = null;
61   String JavaDoc cachePathInfo = null;
62   String JavaDoc cacheServletPath = null;
63   Object JavaDoc lock = new Object JavaDoc();
64
65   // ---------------------------------------------------------------------------
66

67   protected void service(HttpServletRequest req, HttpServletResponse res)
68       throws ServletException, IOException {
69     // Only do caching for GET requests
70
String JavaDoc method = req.getMethod();
71     if (!method.equals("GET")) {
72       super.service(req, res);
73       return;
74     }
75
76     // Check the last modified time for this servlet
77
long servletLastMod = getLastModified(req);
78
79     // A last modified of -1 means we shouldn't use any cache logic
80
if (servletLastMod == -1) {
81       super.service(req, res);
82       return;
83     }
84
85     // If the client sent an If-Modified-Since header equal or after the
86
// servlet's last modified time, send a short "Not Modified" status code
87
// Round down to the nearest second since client headers are in seconds
88
if ((servletLastMod / 1000 * 1000) <=
89              req.getDateHeader("If-Modified-Since")) {
90       res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
91       return;
92     }
93
94     // Use the existing cache if it's current and valid
95
CacheHttpServletResponse localResponseCopy = null;
96     synchronized (lock) {
97       if (servletLastMod <= cacheLastMod &&
98                cacheResponse.isValid() &&
99                equal(cacheQueryString, req.getQueryString()) &&
100                equal(cachePathInfo, req.getPathInfo()) &&
101                equal(cacheServletPath, req.getServletPath())) {
102         localResponseCopy = cacheResponse;
103       }
104     }
105     if (localResponseCopy != null) {
106       localResponseCopy.writeTo(res);
107       return;
108     }
109
110     // Otherwise make a new cache to capture the response
111
localResponseCopy = new CacheHttpServletResponse(res);
112     super.service(req, localResponseCopy);
113     synchronized (lock) {
114       cacheResponse = localResponseCopy;
115       cacheLastMod = servletLastMod;
116       cacheQueryString = req.getQueryString();
117       cachePathInfo = req.getPathInfo();
118       cacheServletPath = req.getServletPath();
119     }
120   }
121
122   // ---------------------------------------------------------------------------
123

124   private static boolean equal(String JavaDoc s1, String JavaDoc s2) {
125     if (s1 == null && s2 == null) {
126       return true;
127     }
128     else if (s1 == null || s2 == null) {
129       return false;
130     }
131     else {
132       return s1.equals(s2);
133     }
134   }
135 }
136
137 // -----------------------------------------------------------------------------
138

139 class CacheHttpServletResponse implements HttpServletResponse {
140   // Store key response variables so they can be set later
141
private int status;
142   private Hashtable headers;
143   private int contentLength;
144   private String JavaDoc contentType;
145   private Locale locale;
146   private Vector cookies;
147   private boolean didError;
148   private boolean didRedirect;
149   private boolean gotStream;
150   private boolean gotWriter;
151
152   private HttpServletResponse delegate;
153   private CacheServletOutputStream out;
154   private PrintWriter writer;
155
156   // ---------------------------------------------------------------------------
157

158   CacheHttpServletResponse(HttpServletResponse res) {
159     delegate = res;
160     try {
161       out = new CacheServletOutputStream(res.getOutputStream());
162     }
163     catch (IOException e) {
164       System.out.println(
165         "Got IOException constructing cached response: " + e.getMessage());
166     }
167     internalReset();
168   }
169
170   // ---------------------------------------------------------------------------
171

172   private void internalReset() {
173     status = 200;
174     headers = new Hashtable();
175     contentLength = -1;
176     contentType = null;
177     locale = null;
178     cookies = new Vector();
179     didError = false;
180     didRedirect = false;
181     gotStream = false;
182     gotWriter = false;
183     out.getBuffer().reset();
184   }
185
186   public boolean isValid() {
187     // We don't cache error pages or redirects
188
return (!didError) && (!didRedirect);
189   }
190
191   // ---------------------------------------------------------------------------
192

193   private void internalSetHeader(String JavaDoc name, Object JavaDoc value) {
194     Vector v = new Vector();
195     v.addElement(value);
196     headers.put(name, v);
197   }
198
199   // ---------------------------------------------------------------------------
200

201   private void internalAddHeader(String JavaDoc name, Object JavaDoc value) {
202     Vector v = (Vector) headers.get(name);
203     if (v == null) {
204       v = new Vector();
205     }
206     v.addElement(value);
207     headers.put(name, v);
208   }
209
210   // ---------------------------------------------------------------------------
211

212   public void writeTo(HttpServletResponse res) {
213     // Write status code
214
res.setStatus(status);
215     // Write convenience headers
216
if (contentType != null) res.setContentType(contentType);
217     if (locale != null) res.setLocale(locale);
218     // Write cookies
219
Enumeration cookieenum = cookies.elements();
220     while (cookieenum.hasMoreElements()) {
221       Cookie c = (Cookie) cookieenum.nextElement();
222       res.addCookie(c);
223     }
224     // Write standard headers
225
Enumeration headerenum = headers.keys();
226     while (headerenum.hasMoreElements()) {
227       String JavaDoc name = (String JavaDoc) headerenum.nextElement();
228       Vector values = (Vector) headers.get(name); // may have multiple values
229
Enumeration enum2 = values.elements();
230       while (enum2.hasMoreElements()) {
231         Object JavaDoc value = enum2.nextElement();
232         if (value instanceof String JavaDoc) {
233           res.setHeader(name, (String JavaDoc)value);
234         }
235         if (value instanceof Integer JavaDoc) {
236           res.setIntHeader(name, ((Integer JavaDoc)value).intValue());
237         }
238         if (value instanceof Long JavaDoc) {
239           res.setDateHeader(name, ((Long JavaDoc)value).longValue());
240         }
241       }
242     }
243     // Write content length
244
res.setContentLength(out.getBuffer().size());
245     // Write body
246
try {
247       out.getBuffer().writeTo(res.getOutputStream());
248     }
249     catch (IOException e) {
250       System.out.println(
251         "Got IOException writing cached response: " + e.getMessage());
252     }
253   }
254
255   // ---------------------------------------------------------------------------
256

257   public String JavaDoc getContentType() {
258     return delegate.getContentType();
259   }
260
261   // ---------------------------------------------------------------------------
262

263   public ServletOutputStream getOutputStream()
264     throws IOException,IllegalStateException JavaDoc {
265     if (gotWriter) {
266       throw new IllegalStateException JavaDoc(
267         "Cannot get output stream after getting writer");
268     }
269     gotStream = true;
270     return out;
271   }
272
273   // ---------------------------------------------------------------------------
274

275   public PrintWriter getWriter()
276     throws UnsupportedEncodingException,IllegalStateException JavaDoc {
277     if (gotStream) {
278       throw new IllegalStateException JavaDoc(
279         "Cannot get writer after getting output stream");
280     }
281     gotWriter = true;
282     if (writer == null) {
283       OutputStreamWriter w =
284         new OutputStreamWriter(out, getCharacterEncoding());
285       writer = new PrintWriter(w, true); // autoflush is necessary
286
}
287     return writer;
288   }
289
290   // ---------------------------------------------------------------------------
291

292   public void setContentLength(int len) {
293     delegate.setContentLength(len);
294     // No need to save the length; we can calculate it later
295
}
296
297   // ---------------------------------------------------------------------------
298

299   public void setContentType(String JavaDoc type) {
300     delegate.setContentType(type);
301     contentType = type;
302   }
303
304   // ---------------------------------------------------------------------------
305

306   public String JavaDoc getCharacterEncoding() {
307     return delegate.getCharacterEncoding();
308   }
309
310   // ---------------------------------------------------------------------------
311

312   public void setBufferSize(int size) throws IllegalStateException JavaDoc {
313     delegate.setBufferSize(size);
314   }
315
316   // ---------------------------------------------------------------------------
317

318   public int getBufferSize() {
319     return delegate.getBufferSize();
320   }
321
322   // ---------------------------------------------------------------------------
323

324   public void resetBuffer() throws IllegalStateException JavaDoc {
325     delegate.reset();
326     internalReset();
327   }
328
329   // ---------------------------------------------------------------------------
330

331   public void reset() throws IllegalStateException JavaDoc {
332     delegate.reset();
333     internalReset();
334   }
335
336   // ---------------------------------------------------------------------------
337

338   /*
339   public void resetBuffer() throws IllegalStateException {
340     delegate.resetBuffer();
341     contentLength = -1;
342     out.getBuffer().reset();
343   }
344  */

345
346 // ---------------------------------------------------------------------------
347

348   public boolean isCommitted() {
349     return delegate.isCommitted();
350   }
351
352   // ---------------------------------------------------------------------------
353

354   public void flushBuffer() throws IOException {
355     delegate.flushBuffer();
356   }
357
358   // ---------------------------------------------------------------------------
359

360   public void setLocale(Locale loc) {
361     delegate.setLocale(loc);
362     locale = loc;
363   }
364
365   // ---------------------------------------------------------------------------
366

367   public Locale getLocale() {
368     return delegate.getLocale();
369   }
370
371   // ---------------------------------------------------------------------------
372

373   public void addCookie(Cookie cookie) {
374     delegate.addCookie(cookie);
375     cookies.addElement(cookie);
376   }
377
378   // ---------------------------------------------------------------------------
379

380   public boolean containsHeader(String JavaDoc name) {
381     return delegate.containsHeader(name);
382   }
383
384   // ---------------------------------------------------------------------------
385

386   public void setCharacterEncoding(String JavaDoc enc) {
387     delegate.setCharacterEncoding(enc);
388   }
389
390   // ---------------------------------------------------------------------------
391

392   /** @deprecated */
393   public void setStatus(int sc, String JavaDoc sm) {
394     delegate.setStatus(sc, sm);
395     status = sc;
396   }
397
398   // ---------------------------------------------------------------------------
399

400   public void setStatus(int sc) {
401     delegate.setStatus(sc);
402     status = sc;
403   }
404
405   // ---------------------------------------------------------------------------
406

407   public void setHeader(String JavaDoc name, String JavaDoc value) {
408     delegate.setHeader(name, value);
409     internalSetHeader(name, value);
410   }
411
412   // ---------------------------------------------------------------------------
413

414   public void setIntHeader(String JavaDoc name, int value) {
415     delegate.setIntHeader(name, value);
416     internalSetHeader(name, new Integer JavaDoc(value));
417   }
418
419   // ---------------------------------------------------------------------------
420

421   public void setDateHeader(String JavaDoc name, long date) {
422     delegate.setDateHeader(name, date);
423     internalSetHeader(name, new Long JavaDoc(date));
424   }
425
426   // ---------------------------------------------------------------------------
427

428   public void sendError(int sc, String JavaDoc msg) throws IOException {
429     delegate.sendError(sc, msg);
430     didError = true;
431   }
432
433   // ---------------------------------------------------------------------------
434

435   public void sendError(int sc) throws IOException {
436     delegate.sendError(sc);
437     didError = true;
438   }
439
440   // ---------------------------------------------------------------------------
441

442   public void sendRedirect(String JavaDoc location) throws IOException {
443     delegate.sendRedirect(location);
444     didRedirect = true;
445   }
446
447   // ---------------------------------------------------------------------------
448

449   public String JavaDoc encodeURL(String JavaDoc url) {
450     return delegate.encodeURL(url);
451   }
452
453   // ---------------------------------------------------------------------------
454

455   public String JavaDoc encodeRedirectURL(String JavaDoc url) {
456     return delegate.encodeRedirectURL(url);
457   }
458
459   // ---------------------------------------------------------------------------
460

461   public void addHeader(String JavaDoc name, String JavaDoc value) {
462     internalAddHeader(name, value);
463   }
464
465   // ---------------------------------------------------------------------------
466

467   public void addIntHeader(String JavaDoc name, int value) {
468     internalAddHeader(name, new Integer JavaDoc(value));
469   }
470
471   // ---------------------------------------------------------------------------
472

473   public void addDateHeader(String JavaDoc name, long value) {
474     internalAddHeader(name, new Long JavaDoc(value));
475   }
476
477   // ---------------------------------------------------------------------------
478

479   /** @deprecated */
480   public String JavaDoc encodeUrl(String JavaDoc url) {
481     return this.encodeURL(url);
482   }
483
484   // ---------------------------------------------------------------------------
485

486   /** @deprecated */
487   public String JavaDoc encodeRedirectUrl(String JavaDoc url) {
488     return this.encodeRedirectURL(url);
489   }
490 }
491
492 // -----------------------------------------------------------------------------
493

494 class CacheServletOutputStream extends ServletOutputStream {
495
496   ServletOutputStream delegate;
497   ByteArrayOutputStream cache;
498
499   CacheServletOutputStream(ServletOutputStream out) {
500     delegate = out;
501     cache = new ByteArrayOutputStream(4096);
502   }
503
504   public ByteArrayOutputStream getBuffer() {
505     return cache;
506   }
507
508   public void write(int b) throws IOException {
509     delegate.write(b);
510     cache.write(b);
511   }
512
513   public void write(byte b[]) throws IOException {
514     delegate.write(b);
515     cache.write(b);
516   }
517
518   public void write(byte buf[], int offset, int len) throws IOException {
519     delegate.write(buf, offset, len);
520     cache.write(buf, offset, len);
521   }
522 } // CacheServletOutputStream
523
Popular Tags