KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openlaszlo > cache > RequestCache


1 /******************************************************************************
2  * RequestCache.java
3  * ****************************************************************************/

4
5 /* J_LZ_COPYRIGHT_BEGIN *******************************************************
6 * Copyright 2001-2004 Laszlo Systems, Inc. All Rights Reserved. *
7 * Use is subject to license terms. *
8 * J_LZ_COPYRIGHT_END *********************************************************/

9
10 package org.openlaszlo.cache;
11
12 import javax.servlet.http.*;
13 import java.io.InputStream JavaDoc;
14 import java.io.IOException JavaDoc;
15 import java.io.File JavaDoc;
16 import java.io.Serializable JavaDoc;
17 import java.io.OutputStream JavaDoc;
18 import java.util.Properties JavaDoc;
19 import java.net.MalformedURLException JavaDoc;
20 import org.openlaszlo.data.*;
21 import org.openlaszlo.utils.LZHttpUtils;
22 import org.openlaszlo.utils.FileUtils;
23 import org.apache.log4j.*;
24
25 /**
26  * A class for maintaining a disk-backed cache of HTTP requests.
27  *
28  * The main entry point is the <code>getAsSWF</code> method.
29  * Given a specific HTTP request and response, the item returns
30  * an InputStream for a possibly-converted SWF that represents
31  * the item requested. This method may return NULL, indicating
32  * that a special HTTP status code has been stuck in the response.
33  *
34  * @author <a HREF="mailto:bloch@laszlosystems.com">Eric Bloch</a>
35  */

36 public abstract class RequestCache extends Cache {
37
38     /** logger */
39     private static Logger mLogger = Logger.getLogger(Cache.class);
40
41     /** converter */
42     protected Converter mConverter;
43
44     /**
45      * Creates a new <code>RequestCache</code> instance.
46      *
47      * @param cacheDirectory a <code>File</code> naming a directory
48      * where cache files should be kept
49      * @param dataSource back end data source for the cache
50      */

51     public RequestCache(String JavaDoc name, File JavaDoc cacheDirectory, Converter converter,
52             Properties JavaDoc props)
53         throws IOException JavaDoc {
54
55         super(name, cacheDirectory, props);
56         mConverter = converter;
57     }
58
59     /**
60      * @return a serializable cache key for the given request
61      */

62     public Serializable JavaDoc getKey(HttpServletRequest req)
63         throws MalformedURLException JavaDoc {
64
65         // This is a nice, easy to read key
66
String JavaDoc enc = mConverter.chooseEncoding(req);
67         if (enc == null)
68             enc = "";
69         StringBuffer JavaDoc key = new StringBuffer JavaDoc();
70         key.append(DataSource.getURL(req));
71         // note: space not allowed in URLS so it's good to
72
// use here as a separator to distinguish encoded keys
73
key.append(" ");
74         key.append(enc);
75         return key.toString();
76     }
77
78     /**
79      * Using the given datasource from the incoming request,
80      * get the data if it's out of data from the cache, convert it
81      * to SWF, and write it out to the given response.
82      *
83      * @param app absolute path name to app
84      * @param req servlet request that triggered the get
85      * @param res servlet response to fill out
86      * @param dataSource source of data
87      * @throws IOException if there's an IOException while creating
88      * the response
89      * @throws DataSourceException if there's an error getting the data
90      * @throws ConversionException if there's an error convering the data
91      */

92     public void getAsSWF(
93             String JavaDoc app,
94             HttpServletRequest req,
95             HttpServletResponse res,
96             DataSource dataSource)
97         throws IOException JavaDoc, DataSourceException, ConversionException {
98
99         // Skip the cache if it's 0 size
100
if (getMaxMemSize() == 0 && getMaxDiskSize() == 0) {
101             dataSource.getAsSWF(app, req, res, mConverter);
102             return;
103         }
104
105         StringBuffer JavaDoc kb = new StringBuffer JavaDoc();
106         kb.append(dataSource.name());
107         kb.append(" ");
108         String JavaDoc rk = getKey(req).toString();
109         kb.append(rk);
110         String JavaDoc key = kb.toString();
111         String JavaDoc enc = mConverter.chooseEncoding(req);
112
113         mLogger.info("requesting '" + rk + "'");
114         if (enc != null) {
115             mLogger.debug("encoding " + enc);
116         }
117
118         Item item = null;
119         InputStream JavaDoc input = null;
120         OutputStream JavaDoc output = null;
121         try {
122             item = findItem(key, enc, /* lock it and leave active */ true);
123
124             // Get an input stream for a SWF version of this item;
125
try {
126                 input = getItemStreamAsSWF(item, app, req, res, dataSource);
127             } finally {
128                 // Unlock it while we send out the response
129
// FIXME: [2003-08-27 bloch] there is a slight race here since the source
130
// of the stream could be removed before we are finished; this would
131
// be rare since we're MRU now.
132
item.unlock();
133             }
134
135             // Send out the response
136
if (input != null) {
137                 try {
138                     output = res.getOutputStream();
139                     long n = FileUtils.sendToStream(input, output);
140                     mLogger.info(n + " bytes sent");
141                 } catch (FileUtils.StreamWritingException e) {
142                     mLogger.warn("StreamWritingException while responding: "
143                             + e.getMessage());
144                 }
145             } else {
146                 mLogger.info("Cache responding with NOT_MODIFIED");
147             }
148         } finally {
149             FileUtils.close(output);
150             FileUtils.close(input);
151
152             // If there's an item, unlock it and update the cache
153
if (item != null) {
154                 updateCacheAndDeactivateItem(item);
155             }
156         }
157     }
158
159     /**
160      * @return true if the request is cacheable.
161      *
162      * If response headers are to be sentback, then the request is
163      * not cacheable on the server.
164      *
165      * If req headers are sent, then the request is not cacheable
166      * on the server.
167      *
168      * If the cache parameter is present and set to true,
169      * the request is cacheable. Otherwise it's not.
170      */

171     public boolean isCacheable(HttpServletRequest req) {
172         String JavaDoc hds = req.getParameter("sendheaders");
173         if (hds != null) {
174             if (hds.equals("true")) {
175                 return false;
176             }
177         }
178         hds = req.getParameter("headers");
179         if (hds != null) {
180             return false;
181         }
182         String JavaDoc c = req.getParameter("cache");
183         if (c == null)
184             return false;
185         return c.equals("true");
186     }
187
188     /**
189      * Return the converter for this cache
190      */

191     public Converter getConverter() {
192         return mConverter;
193     }
194
195     /**
196      * Get the item if it's been updated since the given time. Item
197      * must be locked when you call this.
198      * @return null if the url hasn't been modified since the given time
199      * or an input stream that can be used to read the item's content
200      * as SWF.
201      */

202     InputStream JavaDoc getItemStreamAsSWF(Item item,
203             String JavaDoc app,
204             HttpServletRequest req,
205             HttpServletResponse res,
206             DataSource dataSource)
207         throws IOException JavaDoc, DataSourceException, ConversionException {
208
209         long ifModifiedSince = -1;
210         long lastModified = -1;
211
212         CachedInfo info = item.getInfo();
213         String JavaDoc enc = info.getEncoding();
214
215         String JavaDoc hdr = req.getHeader(LZHttpUtils.IF_MODIFIED_SINCE);
216         if (hdr != null) {
217            mLogger.debug("req last modified time: " + hdr);
218            lastModified = LZHttpUtils.getDate(hdr);
219         }
220
221         boolean doClientCache = DataSource.isClientCacheable(req);
222
223         // FIXME[2003-05-21 bloch]: Max and I worked through the
224
// logic in this comment below and it seemed correct
225
// but as I paste it in here it's missing an else clause...
226
//
227
// If (there is an entry in the cache)
228
// If there's no timestamp in the request
229
// Use the timestamp from the cache.
230
// else
231
// If request time is <= cache time
232
// use cache time
233
// else (no entry in the cache)
234
// use -1
235
//
236
// Max prefers the following code to implement but I'm leaving the extant code
237
// because it's too close to release for me to be making changes
238
// like that. Here's the code Max, liked:
239
//
240
// if (info.getLastModified() != -1) {
241
// if (ifModifiedSince == -1) {
242
// ifModifiedSince = info.getLastModified();
243
// } else {
244
// if (ifModifiedSince <= info.getLastModified()) {
245
// ifModifiedSince = info.getLastModified();
246
// }
247
// }
248
// } else {
249
// ifModifiedSince = -1;
250
// }
251
//
252
//
253
// The code below has existed for a while and actually implements the logic above
254
// in a slightly convoluted way because of the special dual meaning of -1; it means no
255
// last modified time at all when coming from data or the cache and it
256
// means get fresh data when making a data request (as the 3rd parameter to
257
// the getData() method below.
258

259         if (info.getLastModified() > lastModified || info.getLastModified() == -1) {
260             ifModifiedSince = info.getLastModified();
261             mLogger.debug("using cached last modified time: " + ifModifiedSince);
262         } else {
263             ifModifiedSince = lastModified;
264             mLogger.debug("using req last modified time: " + ifModifiedSince);
265         }
266
267         Data data = null;
268
269         try {
270
271             try {
272                 data = dataSource.getData(app, req, res, ifModifiedSince);
273             } catch (DataSourceException e) {
274                 // When we get an error from the back end,
275
// we must nuke the item from the cache.
276
item.markDirty();
277                 throw e;
278             } catch (IOException JavaDoc e) {
279                 item.markDirty();
280                 throw e;
281             } catch (RuntimeException JavaDoc e) {
282                 item.markDirty();
283                 throw e;
284             }
285
286             if (data.notModified()) {
287
288                 mLogger.debug("Remote response: NOT_MODIFIED");
289                 if (lastModified >= info.getLastModified() && doClientCache) {
290                     res.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
291                     return null;
292                 }
293
294                 if (item.validForData(data)) {
295
296                     if (enc != null) {
297                         res.setHeader(LZHttpUtils.CONTENT_ENCODING, enc);
298                     }
299                     if (doClientCache) {
300                         long l = info.getLastModified();
301                         if (l != -1) {
302                             res.setDateHeader(LZHttpUtils.LAST_MODIFIED, l);
303                         }
304                     } else {
305                         LZHttpUtils.noStore(res);
306                     }
307                     res.setContentLength( (int) info.getSize());
308                     return item.getStream();
309                 }
310             }
311
312             mLogger.debug("path name: " + item.getPathName());
313
314             // We know the cached item is dirty
315
item.markDirty();
316
317             // Mark the info with the new last modified time
318
info.setLastModified(data.lastModified());
319
320             // Convert the data to SWF
321
InputStream JavaDoc input = mConverter.convertToSWF(data, req, res);
322             // TODO: [2003-09-22 bloch] add content length when
323
// converter api provides this info; input.available() is not reliable
324
// TODO: [2003-09-22 bloch] could handle case when conversion is a no-op from swf,
325
// without the above
326

327             // Update the item with the data
328
try {
329                 item.update(input);
330                 item.updateInfo();
331                 item.markClean();
332             } finally {
333                 FileUtils.close(input);
334             }
335
336             // FIXME: [2003-03-13 bloch] hope that no one
337
// removes the file before we're done with this
338
// input stream. This would happen only
339
// when the cache is full and small; a rare
340
// case in production.
341

342             InputStream JavaDoc str = item.getStream();
343
344             if (enc != null) {
345                 res.setHeader(LZHttpUtils.CONTENT_ENCODING, enc);
346             }
347
348             if (doClientCache) {
349                 long l = info.getLastModified();
350                 if (l != -1) {
351                     res.setDateHeader(LZHttpUtils.LAST_MODIFIED, l);
352                 }
353                                       
354             } else {
355                 LZHttpUtils.noStore(res);
356             }
357
358             res.setContentLength( (int) info.getSize());
359
360             return str;
361             
362         } finally {
363             if (data != null) {
364                 data.release();
365             }
366         }
367     }
368 }
369
Popular Tags