1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.jetspeed.cache.disk;
18
19
20
21 import org.apache.jetspeed.util.URIEncoder;
22 import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
23 import org.apache.jetspeed.services.logging.JetspeedLogger;
24 import org.apache.jetspeed.services.urlmanager.URLFetcher;
25 import org.apache.jetspeed.services.resources.JetspeedResources;
26
27
28 import java.io.*;
29 import java.net.*;
30
31 /***
32 *<p>A cache entry represents a data source that can be stored locally for
33 *efficiency.
34 *
35 *<p>It can deliver a string with its contents, but the preferred
36 *way to access to the entry contents is through a Reader
37 *that will get characters from it.
38 *
39 *<p>There are two kinds of entries:
40 *
41 *<ul>
42 * <li>Local: It is not cached.
43 * <li>Remote: It can be cached.
44 *</ul>
45 *
46 *<p>Remote entries can be in the following states:
47 *
48 *<ul>
49 * <li>Invalid: It has no local reference, and the source is transiently or
50 * permanently delivering errors
51 * <li>Stale: It has no local reference, and it is quiet
52 * <li>Loading: It has no local ref yet, and it is loading
53 * <li>Refreshing: It has a local ref (current or expired),
54 * and it is refreshing it
55 * <li>Expired: It has a local ref, but its content is no longer valid
56 * <li>Current: It has a valid local ref
57 *</ul>
58 *
59 *<p>TODO: Some data sources need to be written (i. e., are writable). For those,
60 * a mechanism need to be provided to write back the resource. We currently think
61 * about HTTP PUT as a mechanism.
62 *
63 *@author <a href="mailto:burton@apache.org">Kevin A. Burton</a>
64 *@author <a href="mailto:sgala@hisitech.com">Santiago Gala</a>
65 *@version $Id: JetspeedDiskCacheEntry.java,v 1.33 2004/02/23 02:45:29 jford Exp $
66 **/
67 public class JetspeedDiskCacheEntry implements DiskCacheEntry {
68
69 /***
70 * <p>Expiration interval that will be used it the remote URL does not
71 * specify one. The contract here is:
72 * <ul>
73 * <li>If we have no hits, we will hit our entry every time DiskCacheDaemon is run to revalidate.</li>
74 * <li>No matter how many hits we get, we will reach our entry at most once per defaultExpirationInterval.</li>
75 * </ul>
76 */
77 private static long defaultExpirationInterval = 1000 *
78 JetspeedResources.getInt( JetspeedResources.DEFAULT_DCE_EXPIRATION_TIME_KEY, 15 * 60 );
79
80
81 static String encoding = JetspeedResources.getString(
82 JetspeedResources.CONTENT_ENCODING_KEY, "iso-8859-1" );
83
84 private File file = null;
85 private String url = null;
86 private String sourceURL = null;
87
88 /***
89 Date (ms since epoch) it was last Modified
90 */
91 private long lastModified = 0;
92 /***
93 Date (ms since epoch) it expires
94 */
95 private long expires = 0;
96
97 /***
98 * Static initialization of the logger for this class
99 */
100 private static final JetspeedLogger logger = JetspeedLogFactoryService.getLogger(JetspeedDiskCacheEntry.class.getName());
101
102 /***
103 *<p>Build a DiskCacheEntry that is based on a cached filesystem entry
104 *
105 *<p>This is used to reconstruct the entries from the cache at boot time
106 *
107 *
108 */
109 protected JetspeedDiskCacheEntry( File file ) {
110 this.setFile( file );
111 this.setURL( this.getSourceURL() );
112 }
113
114 /***
115 *Build a DiskCacheEntry that is based on a remote URL and may be (or not)
116 *backed on disk.
117 *
118 */
119 public JetspeedDiskCacheEntry( String url ) {
120
121 this.setURL( url );
122 this.init();
123 }
124
125 /***
126 * Initialize the file variable, if it is null & the url
127 * is not local
128 *
129 */
130 public void init() {
131
132 URL url = null;
133
134
135 try {
136 url = new URL(this.getURL());
137 } catch (MalformedURLException e) {
138 logger.error("Error in URL", e);
139 return;
140 }
141
142
143 if ( (this.file == null || this.file.exists() == false )
144 && "file".equals( url.getProtocol() ) )
145 {
146 try {
147 File newfile = new File( url.getFile() );
148
149 this.setFile( newfile );
150 if( newfile.exists() == false ) {
151 JetspeedDiskCache.getInstance().add( this.getURL(), true );
152 }
153 } catch ( IOException e ) {
154 logger.error("Error building from file", e);
155 return;
156 }
157
158 }
159
160 }
161
162 /***
163 */
164 public String getURL() {
165 return this.url;
166 }
167
168 /***
169 Reconstruct the original URL based on this URL.
170 */
171 public String getSourceURL() {
172
173 if ( this.getFile() == null ) {
174 return this.getURL();
175 } else {
176 return URIEncoder.decode( this.getFile().getName() );
177 }
178 }
179
180 /***
181 Get the File that this URL obtains within the cache.
182 */
183 public File getFile() {
184 return this.file;
185 }
186
187 /***
188 Set the file.
189 */
190 public void setFile( File file ) {
191
192 if ( file.exists() == false ) {
193 String message = "The following file does not exist: " + file.getAbsolutePath();
194 logger.error( message );
195 try {
196 JetspeedDiskCache.getInstance().add( this.url, true );
197 } catch (Throwable e) {
198 logger.error("Error setting file", e );
199 }
200 }
201
202 this.file = file;
203 this.lastModified = file.lastModified();
204 this.expires = System.currentTimeMillis() +
205 defaultExpirationInterval;
206 }
207
208 /***
209 Open this URL and read its data, then return it as a string
210 */
211 public String getData() throws IOException {
212
213 Reader is = this.getReader();
214 StringWriter bos = new StringWriter();
215
216
217 char chars[] = new char[200];
218
219 int readCount = 0;
220 while( ( readCount = is.read( chars )) > 0 ) {
221 bos.write(chars, 0, readCount);
222 }
223
224 is.close();
225
226 return bos.toString();
227
228 }
229
230 /***
231 Get an input stream from this entry
232 */
233 public InputStream getInputStream() throws IOException {
234 logger.info( "CacheEntry getInputStream() called: " + this.getURL() );
235 if(this.getFile() != null)
236 {
237 return new FileInputStream( this.getFile() );
238 }
239
240 if(DiskCacheUtils.isLocal( this.getURL() ) )
241 {
242 return new URL( this.getURL() ).openConnection().getInputStream();
243 }
244
245 this.lastModified = 0;
246 this.expires = 0;
247 URLFetcher.refresh( this.getURL() );
248 if(this.getFile() != null)
249 return new FileInputStream( this.getFile() );
250 throw new IOException( this.getURL() +
251 ": is not in cache after forcing" );
252 }
253
254 /***
255 Get a Reader from this entry.
256 ( Patch for handling character encoding sent by
257 Yoshihiro KANNA <y-kanna@bl.jp.nec.com> )
258 For local entries, we assume that the URL coming
259 from the WEB server is allright WRT encoding
260 For remote entries, we assume that the cache saved them in the local store
261 using UTF8 encoding
262 */
263 public Reader getReader() throws IOException {
264
265 if(DiskCacheUtils.isLocal( this.getURL() ) )
266 {
267 URLConnection conn = new URL( this.getURL() ).openConnection();
268
269 String encoding = conn.getContentEncoding();
270 if(encoding == null) {
271
272 encoding = JetspeedResources.getString( JetspeedResources.CONTENT_ENCODING_KEY,
273 "iso-8859-1" );
274 }
275
276
277
278
279 return new InputStreamReader(conn.getInputStream(),
280 encoding );
281 }
282
283 if(this.getFile() != null)
284 {
285 InputStreamReader reader = null;
286 try {
287
288
289 reader = new InputStreamReader( new FileInputStream( this.getFile() ), "UTF8" );
290 } catch (UnsupportedEncodingException e) {
291 logger.error("Encoding error", e);
292 reader = new FileReader( this.getFile() );
293 }
294
295
296
297
298 return reader;
299 }
300
301 this.lastModified = 0;
302 this.expires = 0;
303 URLFetcher.refresh( this.getURL() );
304
305 if(this.getFile() != null)
306 return this.getReader();
307 throw new IOException( this.getURL() +
308 ": is not in cache after forcing" );
309
310 }
311
312 /***
313 Get a Writer to update this entry.
314 For local entries, we assume that the URL coming
315 from the WEB server allows PUT
316 For remote entries, we throws a IOException
317
318 */
319 public Writer getWriter() throws IOException {
320
321 if( DiskCacheUtils.isRemote( this.getURL() ) ) {
322 throw new IOException("Cannot write to remote URLs!");
323 }
324
325 if(DiskCacheUtils.isLocal( this.getURL() ) )
326 {
327 URL url = new URL( this.getURL() );
328
329 if (url.getProtocol().equalsIgnoreCase("http"))
330 {
331 HttpURLConnection conn = (HttpURLConnection) url.openConnection();
332 conn.setDoOutput(true);
333 conn.setRequestMethod("PUT");
334 return new HttpURLWriter( conn );
335 }
336 else
337 {
338 File file = new File( url.getFile() );
339 file.getParentFile().mkdirs();
340 return new FileURLWriter( file );
341 }
342
343
344 }
345
346 throw new IOException( this.getURL() +
347 ": is not local or remote" );
348
349 }
350
351 /***
352 Return the last modified date of this entry.
353 */
354 public long getLastModified() {
355 if( isLocal() ) {
356 try {
357 String localfile = this.getURL().substring(5);
358 this.lastModified = new File( localfile ).lastModified();
359 } catch ( Exception e ) {
360 if( logger.isDebugEnabled() ) {
361 logger.debug("Error getLastModified ", e);
362 }
363 e.printStackTrace();
364 }
365 }
366 return this.lastModified;
367
368 }
369
370 /***
371 Set the last modified date of this entry.
372 */
373 public void setLastModified(long time) {
374 this.lastModified = time;
375
376 }
377
378 /***
379 Set the url on which this is based.
380 */
381 public void setURL( String url ) {
382
383 if ( DiskCacheUtils.isVirtual( url ) ) {
384 url = DiskCacheUtils.getLocalURL( url );
385 }
386
387 this.url = url;
388 }
389
390 /***
391 Set the expiration date of this entry.
392 */
393 public long getExpirationTime() {
394 return this.expires;
395 }
396
397 /***
398 Set the expiration date of this entry.
399 */
400 public void setExpirationTime(long time) {
401 this.expires = time;
402 if(this.expires < System.currentTimeMillis())
403 {
404 this.expires = System.currentTimeMillis() +
405 defaultExpirationInterval;
406 }
407
408 }
409
410 /***
411 */
412 public boolean hasExpired() {
413 return this.expires <= 0 ||
414 this.expires < System.currentTimeMillis();
415 }
416
417 /***
418 */
419 public boolean isLocal() {
420
421 return DiskCacheUtils.isLocal(this.getSourceURL());
422 }
423
424 class HttpURLWriter extends OutputStreamWriter
425 {
426 private HttpURLConnection conn;
427
428 public HttpURLWriter( HttpURLConnection conn )
429 throws UnsupportedEncodingException, IOException
430 {
431 super( conn.getOutputStream(), encoding );
432 this.conn = conn;
433 logger.info("HttpURLWriter encoding -> " +
434 encoding + " method -> " + this.conn.getRequestMethod() );
435 }
436
437 public void close() throws IOException
438 {
439
440 super.close();
441
442 this.conn.getResponseCode();
443 logger.info("HttpURLWriter close encoding -> " +
444 encoding + " method -> " + this.conn.getRequestMethod() +
445 " Status -> " + this.conn.getResponseCode() );
446
447 }
448 }
449
450 class FileURLWriter extends FileWriter
451 {
452 private String filename;
453
454 public FileURLWriter( File file )
455 throws UnsupportedEncodingException, IOException
456 {
457 super( file );
458 this.filename = file.getPath();
459 logger.info("FileURLWriter opening file -> " + filename );
460 }
461
462 public void close() throws IOException
463 {
464
465 super.close();
466 logger.info("FileURLWriter closing file -> " + filename );
467
468 }
469 }
470 }