KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > info > magnolia > cms > core > CacheHandler


1 /**
2  *
3  * Magnolia and its source-code is licensed under the LGPL.
4  * You may copy, adapt, and redistribute this file for commercial or non-commercial use.
5  * When copying, adapting, or redistributing this document in keeping with the guidelines above,
6  * you are required to provide proper attribution to obinary.
7  * If you reproduce or distribute the document without making any substantive modifications to its content,
8  * please use the following attribution line:
9  *
10  * Copyright 1993-2005 obinary Ltd. (http://www.obinary.com) All rights reserved.
11  *
12  */

13 package info.magnolia.cms.core;
14
15 import info.magnolia.cms.Aggregator;
16 import info.magnolia.cms.beans.runtime.Cache;
17 import info.magnolia.cms.security.SecureURI;
18
19 import java.io.File JavaDoc;
20 import java.io.FileInputStream JavaDoc;
21 import java.io.FileOutputStream JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.io.OutputStream JavaDoc;
25 import java.net.URL JavaDoc;
26 import java.net.URLConnection JavaDoc;
27 import java.text.MessageFormat JavaDoc;
28 import java.util.Date JavaDoc;
29 import java.util.zip.GZIPOutputStream JavaDoc;
30
31 import javax.servlet.ServletOutputStream JavaDoc;
32 import javax.servlet.http.HttpServletRequest JavaDoc;
33 import javax.servlet.http.HttpServletResponse JavaDoc;
34
35 import org.apache.commons.lang.StringUtils;
36 import org.apache.log4j.Logger;
37
38
39 /**
40  * CacheHandle checks if the data is already cached, if yes it spools the data back to the requester either compressed
41  * or as is. If not it caches that request in default and optimized (only gzip for now) stores.
42  * @author Sameer Charles
43  * @version $Revision $ ($Author $)
44  */

45 public class CacheHandler extends Thread JavaDoc {
46
47     /**
48      * Cache directory file system path
49      */

50     public static final String JavaDoc CACHE_DIRECTORY = Path.getCacheDirectoryPath();
51
52     /**
53      * Default cache files location under main cache directory
54      */

55     private static final String JavaDoc DEFAULT_STORE = "/default"; //$NON-NLS-1$
56

57     /**
58      * Optimized cache files location under main cache directory
59      */

60     private static final String JavaDoc COMPRESSED_STORE = "/optimized"; //$NON-NLS-1$
61

62     /**
63      * Logger
64      */

65     private static Logger log = Logger.getLogger(CacheHandler.class);
66
67     /**
68      * Cache this request in default and optimized stores
69      * @param request duplicate request created for cache
70      */

71     public static synchronized void cacheURI(HttpServletRequest JavaDoc request) {
72
73         if (Cache.isCached(request) || CacheHandler.hasRedirect(request)) {
74             return;
75         }
76
77         String JavaDoc uri = request.getRequestURI();
78
79         // strip the context path
80
if (uri.startsWith(request.getContextPath())) {
81             uri = StringUtils.substringAfter(uri, request.getContextPath());
82         }
83
84         String JavaDoc repositoryURI = Path.getURI(request);
85
86         FileOutputStream JavaDoc out = null;
87         int size = 0;
88         int compressedSize = 0;
89         try {
90             if (!info.magnolia.cms.beans.config.Cache.isCacheable(request)) {
91                 if (log.isDebugEnabled())
92                     log.debug("Request:" + request.getServletPath() + " not cacheable");
93                 return;
94             }
95             File JavaDoc file = getDestinationFile(repositoryURI, DEFAULT_STORE);
96
97             if (!file.exists()) {
98                 file.createNewFile();
99                 out = new FileOutputStream JavaDoc(file);
100                 boolean success = streamURI(uri, out, request);
101                 out.flush();
102                 out.close();
103                 if (!success) {
104                     // don't leave bad or incomplete files!
105
file.delete();
106                     log.error(MessageFormat.format("NOT Caching uri [{0}] due to a previous error", //$NON-NLS-1$
107
new Object JavaDoc[]{uri}));
108                 }
109                 else {
110                     if (log.isInfoEnabled()) {
111                         log.info(MessageFormat.format("Successfully cached URI [{0}]", //$NON-NLS-1$
112
new Object JavaDoc[]{uri}));
113                     }
114                 }
115             }
116
117             size = (int) file.length();
118
119             if (info.magnolia.cms.beans.config.Cache.applyCompression(Path.getExtension(request))) {
120                 File JavaDoc gzipFile = getDestinationFile(repositoryURI, COMPRESSED_STORE);
121                 if (!gzipFile.exists()) {
122                     gzipFile.createNewFile();
123                     out = new FileOutputStream JavaDoc(gzipFile);
124                     GZIPOutputStream JavaDoc gzipOut = new GZIPOutputStream JavaDoc(out);
125                     streamURI(uri, gzipOut, request);
126                     gzipOut.close();
127                 }
128                 compressedSize = (new Long JavaDoc(gzipFile.length())).intValue();
129             }
130             Cache.addToCachedURIList(repositoryURI, new Date JavaDoc().getTime(), size, compressedSize);
131         }
132         catch (IOException JavaDoc e) {
133             log.error(e.getMessage(), e);
134         }
135         finally {
136             if (out != null) {
137                 try {
138                     out.close();
139                 }
140                 catch (IOException JavaDoc e) {
141                     // ignore
142
}
143             }
144             Cache.removeFromInProcessURIList(repositoryURI);
145         }
146     }
147
148     /**
149      * Checks if the page has "redirectURL" property set
150      * @param request HttpServletRequest
151      * @return true if it has redirect
152      */

153     private static boolean hasRedirect(HttpServletRequest JavaDoc request) {
154         Object JavaDoc obj = request.getAttribute(Aggregator.ACTPAGE);
155         if (obj == null) {
156             return false; // some other resource
157
}
158         Content page = (Content) obj;
159
160         if (StringUtils.isEmpty(page.getNodeData("redirectURL").getString())) { //$NON-NLS-1$
161
return false;
162         }
163         return true;
164
165     }
166
167     /**
168      * Stream given URI
169      * @param uri to be streamed
170      * @param out this could be any stream type inherited from java.io.OutputStream
171      * @param request HttpServletRequest
172      * @return <code>true</code> if resource is successfully returned to the client, <code>false</code> otherwise
173      */

174     private static boolean streamURI(String JavaDoc uri, OutputStream JavaDoc out, HttpServletRequest JavaDoc request) {
175
176         String JavaDoc domain = info.magnolia.cms.beans.config.Cache.getDomain();
177
178         if (StringUtils.isEmpty(domain)) {
179             domain = getAppURL(request);
180         }
181
182         try {
183             URL JavaDoc url = new URL JavaDoc(domain + uri);
184             if (log.isDebugEnabled()) {
185                 log.debug("Streaming uri:" + url.toExternalForm()); //$NON-NLS-1$
186
}
187             URLConnection JavaDoc urlConnection = url.openConnection();
188             if (SecureURI.isProtected(uri)) {
189                 urlConnection.setRequestProperty("Authorization", request.getHeader("Authorization")); //$NON-NLS-1$ //$NON-NLS-2$
190
}
191             byte[] buffer = new byte[8192];
192             int read = 0;
193             InputStream JavaDoc in = urlConnection.getInputStream();
194             while ((read = in.read(buffer)) > 0) {
195                 out.write(buffer, 0, read);
196             }
197             return true;
198         }
199         catch (IOException JavaDoc e) {
200             log.error(MessageFormat.format("Failed to stream [{0}] due to a {1}: {2}", //$NON-NLS-1$
201
new Object JavaDoc[]{uri, e.getClass().getName(), e.getMessage()}), e);
202         }
203
204         return false;
205     }
206
207     /**
208      * Returns the server url for the web application. Used when a cache domain is not configured.
209      * @param request HttpServletRequest
210      * @return the root webapp url [scheme]://[server]:[port]/[context]
211      */

212     private static String JavaDoc getAppURL(HttpServletRequest JavaDoc request) {
213         StringBuffer JavaDoc url = new StringBuffer JavaDoc();
214         int port = request.getServerPort();
215         if (port < 0) {
216             port = 80; // Work around java.net.URL bug
217
}
218         String JavaDoc scheme = request.getScheme();
219         url.append(scheme);
220         url.append("://"); //$NON-NLS-1$
221
url.append(request.getServerName());
222         if ((scheme.equals("http") && (port != 80)) || (scheme.equals("https") && (port != 443))) { //$NON-NLS-1$ //$NON-NLS-2$
223
url.append(':');
224             url.append(port);
225         }
226         String JavaDoc contextPath = request.getContextPath();
227         if (!(contextPath.length() > 0 && contextPath.charAt(0) == '/')) {
228             url.append("/"); //$NON-NLS-1$
229
}
230         url.append(contextPath);
231
232         return url.toString();
233     }
234
235     /**
236      * Creates file hierarchy for the given URI in cache store
237      * @param uri request uri
238      * @param type could be either CacheHandler.DEFAULT_STORE or CacheHandler.COMPRESSED_STORE
239      * @return newly created file
240      */

241     private static File JavaDoc getDestinationFile(String JavaDoc uri, String JavaDoc type) {
242         validatePath(CACHE_DIRECTORY);
243         validatePath(CACHE_DIRECTORY + type);
244         String JavaDoc[] items = uri.split("/"); //$NON-NLS-1$
245
StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
246         int i = 0;
247         for (; i < (items.length - 1); i++) {
248             if (StringUtils.isEmpty(items[i])) {
249                 continue;
250             }
251             buffer.append("/" + items[i]); //$NON-NLS-1$
252
validatePath(CACHE_DIRECTORY + type + buffer.toString());
253         }
254         buffer.append("/" + items[i]); //$NON-NLS-1$
255
return (new File JavaDoc(CACHE_DIRECTORY + type + buffer.toString()));
256     }
257
258     /**
259      * Create a directory specified by the path
260      * @param path to the directory
261      */

262     public static void validatePath(String JavaDoc path) {
263
264         File JavaDoc file = new File JavaDoc(path);
265         if (!file.isDirectory()) {
266             if (!file.mkdir()) {
267                 log.error("Can not create directory - " + path); //$NON-NLS-1$
268
}
269         }
270     }
271
272     /**
273      * Spools cached data back to the client. This only works if specified request is a GET request and does not have
274      * any request parameter, else it wont write anything on the output stream.
275      * @param request HttpServletRequest
276      * @param response HttpServletResponse
277      * @throws IOException
278      * @return <code>true</code> is successful
279      */

280     public static boolean streamFromCache(HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response) {
281
282         // by now don't cache anything if server is admin
283
// if (Server.isAdmin()) {
284
// return false;
285
// }
286

287         // make sure not to stream anything from cache if it's a POST request or if it has parameters
288
if (request.getMethod().toLowerCase().equals("post") || !request.getParameterMap().isEmpty()) { //$NON-NLS-1$
289
return false;
290         }
291
292         boolean compress = canCompress(request);
293         FileInputStream JavaDoc fin = null;
294         try {
295             File JavaDoc file;
296             if (compress) {
297                 file = new File JavaDoc(CACHE_DIRECTORY + COMPRESSED_STORE + Path.getURI(request));
298             }
299             else {
300                 file = new File JavaDoc(CACHE_DIRECTORY + DEFAULT_STORE + Path.getURI(request));
301             }
302             if (!file.exists()) {
303                 return false;
304             }
305             if (file.length() < 4) {
306                 return false;
307             }
308
309             if (log.isDebugEnabled()) {
310                 log.debug("Streaming from cache the file:" + file.getAbsolutePath()); //$NON-NLS-1$
311
}
312
313             fin = new FileInputStream JavaDoc(file);
314             if (compress) {
315                 response.setContentLength(Cache.getCompressedSize(request));
316                 sendCompressed(fin, response);
317             }
318             else {
319                 response.setContentLength(Cache.getSize(request));
320                 send(fin, response);
321             }
322         }
323         catch (IOException JavaDoc e) {
324             if (log.isDebugEnabled()) {
325                 log.debug("Error while reading cache for " + e.getMessage(), e); //$NON-NLS-1$
326
}
327         }
328         finally {
329             if (fin != null) {
330                 try {
331                     fin.close();
332                 }
333                 catch (IOException JavaDoc e) {
334                     // ignore
335
}
336             }
337         }
338         return true;
339     }
340
341     /**
342      * Send data as GZIP output stream
343      * @param is Input stream for the resource
344      * @param res HttpServletResponse as received by the service method
345      * @throws IOException for errors while writing content to the output stream
346      */

347     private static void sendCompressed(InputStream JavaDoc is, HttpServletResponse JavaDoc res) throws IOException JavaDoc {
348         res.setHeader("Content-Encoding", "gzip"); //$NON-NLS-1$ //$NON-NLS-2$
349
send(is, res);
350     }
351
352     /**
353      * Send data as is
354      * @param is Input stream for the resource
355      * @param res HttpServletResponse as received by the service method
356      * @throws IOException for errors while writing content to the output stream
357      */

358     private static void send(InputStream JavaDoc is, HttpServletResponse JavaDoc res) throws IOException JavaDoc {
359         ServletOutputStream JavaDoc os = res.getOutputStream();
360         byte[] buffer = new byte[8192];
361         int read = 0;
362         while ((read = is.read(buffer)) > 0) {
363             os.write(buffer, 0, read);
364         }
365         os.flush();
366         os.close();
367     }
368
369     /**
370      * Checks if the client from whom this request originated accept GZIP compression
371      * @param request HttpServletRequest
372      * @return true if client sends value "gzip" in Accept-Encoding header
373      */

374     private static boolean canCompress(HttpServletRequest JavaDoc request) {
375         if (!info.magnolia.cms.beans.config.Cache.applyCompression(Path.getExtension(request))) {
376             return false;
377         }
378         String JavaDoc encoding = request.getHeader("Accept-Encoding"); //$NON-NLS-1$
379
if (encoding != null) {
380             return StringUtils.contains(encoding.toLowerCase(), "gzip"); //$NON-NLS-1$
381
}
382         return false;
383     }
384
385     /**
386      * Empties the cache for the specified resource. Currenty it expects the entire path, including cache location.
387      * @param uri request URI
388      */

389     public static void flushResource(String JavaDoc uri) {
390         File JavaDoc file = new File JavaDoc(uri);
391         try {
392             if (file.isDirectory()) {
393                 emptyDirectory(file);
394                 file.delete();
395             }
396             else {
397                 if (log.isDebugEnabled()) {
398                     log.debug("Flushing - " + uri); //$NON-NLS-1$
399
}
400                 file.delete();
401                 Cache.removeFromCachedURIList(uri);
402                 Cache.removeFromInProcessURIList(uri);
403             }
404         }
405         catch (Exception JavaDoc e) {
406             log.error("Failed to flush [" + uri + "]: " + e.getMessage(), e); //$NON-NLS-1$ //$NON-NLS-2$
407
}
408     }
409
410     /**
411      * Recursively deletes all files under the specified directory
412      * @param directory directory where files should be deleted
413      */

414     private static void emptyDirectory(File JavaDoc directory) {
415         File JavaDoc[] children = directory.listFiles();
416         // children can be null if File is not a directory or if it has been already deleted
417
if (children != null) {
418             for (int i = 0; i < children.length; i++) {
419                 if (children[i] != null && children[i].isDirectory()) {
420                     emptyDirectory(children[i]);
421                     children[i].delete();
422                 }
423                 else {
424                     if (log.isDebugEnabled()) {
425                         log.debug("Flushing - " + children[i].getPath()); //$NON-NLS-1$
426
}
427                     String JavaDoc path = StringUtils.substringAfter(children[i].getPath(), Path.getCacheDirectoryPath());
428                     Cache.removeFromCachedURIList(path);
429                     Cache.removeFromInProcessURIList(path);
430                     children[i].delete();
431                 }
432             }
433         }
434     }
435
436     /**
437      * Flushes entire cache
438      */

439     public static void flushCache() {
440         log.debug("Flushing entire cache"); //$NON-NLS-1$
441
try {
442             CacheHandler.flushResource(CACHE_DIRECTORY);
443             // this will create cache start directory again
444
CacheHandler.validatePath(CACHE_DIRECTORY);
445             CacheHandler.validatePath(CACHE_DIRECTORY + DEFAULT_STORE);
446             CacheHandler.validatePath(CACHE_DIRECTORY + COMPRESSED_STORE);
447
448             // clear in-memory cache also
449
Cache.clearCachedURIList();
450         }
451         catch (Exception JavaDoc e) {
452             log.error(e.getMessage(), e);
453         }
454     }
455 }
456
Popular Tags