1
17 package org.apache.catalina.webresources;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.IOException;
21 import java.io.InputStream;
22 import java.net.JarURLConnection;
23 import java.net.MalformedURLException;
24 import java.net.URL;
25 import java.net.URLConnection;
26 import java.net.URLStreamHandler;
27 import java.nio.charset.Charset;
28 import java.security.Permission;
29 import java.security.cert.Certificate;
30 import java.text.Collator;
31 import java.util.Arrays;
32 import java.util.Locale;
33 import java.util.jar.JarEntry;
34 import java.util.jar.JarFile;
35 import java.util.jar.Manifest;
36
37 import org.apache.catalina.WebResource;
38 import org.apache.catalina.WebResourceRoot;
39 import org.apache.juli.logging.Log;
40 import org.apache.juli.logging.LogFactory;
41 import org.apache.tomcat.util.res.StringManager;
42
43
48 public class CachedResource implements WebResource {
49
50 private static final Log log = LogFactory.getLog(CachedResource.class);
51 private static final StringManager sm = StringManager.getManager(CachedResource.class);
52
53
54
55 private static final long CACHE_ENTRY_SIZE = 500;
56
57 private final Cache cache;
58 private final StandardRoot root;
59 private final String webAppPath;
60 private final long ttl;
61 private final int objectMaxSizeBytes;
62 private final boolean usesClassLoaderResources;
63
64 private volatile WebResource webResource;
65 private volatile WebResource[] webResources;
66 private volatile long nextCheck;
67
68 private volatile Long cachedLastModified = null;
69 private volatile String cachedLastModifiedHttp = null;
70 private volatile byte[] cachedContent = null;
71 private volatile Boolean cachedIsFile = null;
72 private volatile Boolean cachedIsDirectory = null;
73 private volatile Boolean cachedExists = null;
74 private volatile Boolean cachedIsVirtual = null;
75 private volatile Long cachedContentLength = null;
76
77
78 public CachedResource(Cache cache, StandardRoot root, String path, long ttl,
79 int objectMaxSizeBytes, boolean usesClassLoaderResources) {
80 this.cache = cache;
81 this.root = root;
82 this.webAppPath = path;
83 this.ttl = ttl;
84 this.objectMaxSizeBytes = objectMaxSizeBytes;
85 this.usesClassLoaderResources = usesClassLoaderResources;
86 }
87
88 protected boolean validateResource(boolean useClassLoaderResources) {
89
90
91
92
93
94
95
96 if (usesClassLoaderResources != useClassLoaderResources) {
97 return false;
98 }
99
100 long now = System.currentTimeMillis();
101
102 if (webResource == null) {
103 synchronized (this) {
104 if (webResource == null) {
105 webResource = root.getResourceInternal(
106 webAppPath, useClassLoaderResources);
107 getLastModified();
108 getContentLength();
109 nextCheck = ttl + now;
110
111
112 if (webResource instanceof EmptyResource) {
113 cachedExists = Boolean.FALSE;
114 } else {
115 cachedExists = Boolean.TRUE;
116 }
117 return true;
118 }
119 }
120 }
121
122 if (now < nextCheck) {
123 return true;
124 }
125
126
127 if (!root.isPackedWarFile()) {
128 WebResource webResourceInternal = root.getResourceInternal(
129 webAppPath, useClassLoaderResources);
130 if (!webResource.exists() && webResourceInternal.exists()) {
131 return false;
132 }
133
134
135
136 if (webResource.getLastModified() != getLastModified() ||
137 webResource.getContentLength() != getContentLength()) {
138 return false;
139 }
140
141
142 if (webResource.getLastModified() != webResourceInternal.getLastModified() ||
143 webResource.getContentLength() != webResourceInternal.getContentLength()) {
144 return false;
145 }
146 }
147
148 nextCheck = ttl + now;
149 return true;
150 }
151
152 protected boolean validateResources(boolean useClassLoaderResources) {
153 long now = System.currentTimeMillis();
154
155 if (webResources == null) {
156 synchronized (this) {
157 if (webResources == null) {
158 webResources = root.getResourcesInternal(
159 webAppPath, useClassLoaderResources);
160 nextCheck = ttl + now;
161 return true;
162 }
163 }
164 }
165
166 if (now < nextCheck) {
167 return true;
168 }
169
170
171 if (root.isPackedWarFile()) {
172 nextCheck = ttl + now;
173 return true;
174 } else {
175
176
177 return false;
178 }
179 }
180
181 protected long getNextCheck() {
182 return nextCheck;
183 }
184
185 @Override
186 public long getLastModified() {
187 Long cachedLastModified = this.cachedLastModified;
188 if (cachedLastModified == null) {
189 cachedLastModified =
190 Long.valueOf(webResource.getLastModified());
191 this.cachedLastModified = cachedLastModified;
192 }
193 return cachedLastModified.longValue();
194 }
195
196 @Override
197 public String getLastModifiedHttp() {
198 String cachedLastModifiedHttp = this.cachedLastModifiedHttp;
199 if (cachedLastModifiedHttp == null) {
200 cachedLastModifiedHttp = webResource.getLastModifiedHttp();
201 this.cachedLastModifiedHttp = cachedLastModifiedHttp;
202 }
203 return cachedLastModifiedHttp;
204 }
205
206 @Override
207 public boolean exists() {
208 Boolean cachedExists = this.cachedExists;
209 if (cachedExists == null) {
210 cachedExists = Boolean.valueOf(webResource.exists());
211 this.cachedExists = cachedExists;
212 }
213 return cachedExists.booleanValue();
214 }
215
216 @Override
217 public boolean isVirtual() {
218 Boolean cachedIsVirtual = this.cachedIsVirtual;
219 if (cachedIsVirtual == null) {
220 cachedIsVirtual = Boolean.valueOf(webResource.isVirtual());
221 this.cachedIsVirtual = cachedIsVirtual;
222 }
223 return cachedIsVirtual.booleanValue();
224 }
225
226 @Override
227 public boolean isDirectory() {
228 Boolean cachedIsDirectory = this.cachedIsDirectory;
229 if (cachedIsDirectory == null) {
230 cachedIsDirectory = Boolean.valueOf(webResource.isDirectory());
231 this.cachedIsDirectory = cachedIsDirectory;
232 }
233 return cachedIsDirectory.booleanValue();
234 }
235
236 @Override
237 public boolean isFile() {
238 Boolean cachedIsFile = this.cachedIsFile;
239 if (cachedIsFile == null) {
240 cachedIsFile = Boolean.valueOf(webResource.isFile());
241 this.cachedIsFile = cachedIsFile;
242 }
243 return cachedIsFile.booleanValue();
244 }
245
246 @Override
247 public boolean delete() {
248 boolean deleteResult = webResource.delete();
249 if (deleteResult) {
250 cache.removeCacheEntry(webAppPath);
251 }
252 return deleteResult;
253 }
254
255 @Override
256 public String getName() {
257 return webResource.getName();
258 }
259
260 @Override
261 public long getContentLength() {
262 Long cachedContentLength = this.cachedContentLength;
263 if (cachedContentLength == null) {
264 long result = 0;
265 if (webResource != null) {
266 result = webResource.getContentLength();
267 cachedContentLength = Long.valueOf(result);
268 this.cachedContentLength = cachedContentLength;
269 }
270 return result;
271 }
272 return cachedContentLength.longValue();
273 }
274
275 @Override
276 public String getCanonicalPath() {
277 return webResource.getCanonicalPath();
278 }
279
280 @Override
281 public boolean canRead() {
282 return webResource.canRead();
283 }
284
285 @Override
286 public String getWebappPath() {
287 return webAppPath;
288 }
289
290 @Override
291 public String getETag() {
292 return webResource.getETag();
293 }
294
295 @Override
296 public void setMimeType(String mimeType) {
297 webResource.setMimeType(mimeType);
298 }
299
300 @Override
301 public String getMimeType() {
302 return webResource.getMimeType();
303 }
304
305 @Override
306 public InputStream getInputStream() {
307 byte[] content = getContent();
308 if (content == null) {
309
310 return webResource.getInputStream();
311 }
312 return new ByteArrayInputStream(content);
313 }
314
315 @Override
316 public byte[] getContent() {
317 byte[] cachedContent = this.cachedContent;
318 if (cachedContent == null) {
319 if (getContentLength() > objectMaxSizeBytes) {
320 return null;
321 }
322 cachedContent = webResource.getContent();
323 this.cachedContent = cachedContent;
324 }
325 return cachedContent;
326 }
327
328 @Override
329 public long getCreation() {
330 return webResource.getCreation();
331 }
332
333 @Override
334 public URL getURL() {
335
363 URL resourceURL = webResource.getURL();
364 if (resourceURL == null) {
365 return null;
366 }
367 try {
368 CachedResourceURLStreamHandler handler =
369 new CachedResourceURLStreamHandler(resourceURL, root, webAppPath, usesClassLoaderResources);
370 URL result = new URL(null, resourceURL.toExternalForm(), handler);
371 handler.setAssociatedURL(result);
372 return result;
373 } catch (MalformedURLException e) {
374 log.error(sm.getString("cachedResource.invalidURL", resourceURL.toExternalForm()), e);
375 return null;
376 }
377 }
378
379 @Override
380 public URL getCodeBase() {
381 return webResource.getCodeBase();
382 }
383
384 @Override
385 public Certificate[] getCertificates() {
386 return webResource.getCertificates();
387 }
388
389 @Override
390 public Manifest getManifest() {
391 return webResource.getManifest();
392 }
393
394 @Override
395 public WebResourceRoot getWebResourceRoot() {
396 return webResource.getWebResourceRoot();
397 }
398
399 WebResource getWebResource() {
400 return webResource;
401 }
402
403 WebResource[] getWebResources() {
404 return webResources;
405 }
406
407
408
409
410 long getSize() {
411 long result = CACHE_ENTRY_SIZE;
412 if (getContentLength() <= objectMaxSizeBytes) {
413 result += getContentLength();
414 }
415 return result;
416 }
417
418
419
423 private static InputStream buildInputStream(String[] files) {
424 Arrays.sort(files, Collator.getInstance(Locale.getDefault()));
425 StringBuilder result = new StringBuilder();
426 for (String file : files) {
427 result.append(file);
428
429 result.append('\n');
430 }
431 return new ByteArrayInputStream(result.toString().getBytes(Charset.defaultCharset()));
432 }
433
434
435 private static class CachedResourceURLStreamHandler extends URLStreamHandler {
436
437 private final URL resourceURL;
438 private final StandardRoot root;
439 private final String webAppPath;
440 private final boolean usesClassLoaderResources;
441
442 private URL associatedURL = null;
443
444 public CachedResourceURLStreamHandler(URL resourceURL, StandardRoot root, String webAppPath,
445 boolean usesClassLoaderResources) {
446 this.resourceURL = resourceURL;
447 this.root = root;
448 this.webAppPath = webAppPath;
449 this.usesClassLoaderResources = usesClassLoaderResources;
450 }
451
452 protected void setAssociatedURL(URL associatedURL) {
453 this.associatedURL = associatedURL;
454 }
455
456 @Override
457 protected URLConnection openConnection(URL u) throws IOException {
458
459
460
461 if (associatedURL != null && u == associatedURL) {
462 if ("jar".equals(associatedURL.getProtocol())) {
463 return new CachedResourceJarURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources);
464 } else {
465 return new CachedResourceURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources);
466 }
467 } else {
468
469
470 URL constructedURL = new URL(u.toExternalForm());
471 return constructedURL.openConnection();
472 }
473 }
474 }
475
476
477
480 private static class CachedResourceURLConnection extends URLConnection {
481
482 private final StandardRoot root;
483 private final String webAppPath;
484 private final boolean usesClassLoaderResources;
485 private final URL resourceURL;
486
487 protected CachedResourceURLConnection(URL resourceURL, StandardRoot root, String webAppPath,
488 boolean usesClassLoaderResources) {
489 super(resourceURL);
490 this.root = root;
491 this.webAppPath = webAppPath;
492 this.usesClassLoaderResources = usesClassLoaderResources;
493 this.resourceURL = resourceURL;
494 }
495
496 @Override
497 public void connect() throws IOException {
498
499 }
500
501 @Override
502 public InputStream getInputStream() throws IOException {
503 WebResource resource = getResource();
504 if (resource.isDirectory()) {
505 return buildInputStream(resource.getWebResourceRoot().list(webAppPath));
506 } else {
507 return getResource().getInputStream();
508 }
509 }
510
511 @Override
512 public Permission getPermission() throws IOException {
513
514 return resourceURL.openConnection().getPermission();
515 }
516
517 @Override
518 public long getLastModified() {
519 return getResource().getLastModified();
520 }
521
522 @Override
523 public long getContentLengthLong() {
524 return getResource().getContentLength();
525 }
526
527 private WebResource getResource() {
528 return root.getResource(webAppPath, false, usesClassLoaderResources);
529 }
530 }
531
532
533
536 private static class CachedResourceJarURLConnection extends JarURLConnection {
537
538 private final StandardRoot root;
539 private final String webAppPath;
540 private final boolean usesClassLoaderResources;
541 private final URL resourceURL;
542
543 protected CachedResourceJarURLConnection(URL resourceURL, StandardRoot root, String webAppPath,
544 boolean usesClassLoaderResources) throws IOException {
545 super(resourceURL);
546 this.root = root;
547 this.webAppPath = webAppPath;
548 this.usesClassLoaderResources = usesClassLoaderResources;
549 this.resourceURL = resourceURL;
550 }
551
552 @Override
553 public void connect() throws IOException {
554
555 }
556
557 @Override
558 public InputStream getInputStream() throws IOException {
559 WebResource resource = getResource();
560 if (resource.isDirectory()) {
561 return buildInputStream(resource.getWebResourceRoot().list(webAppPath));
562 } else {
563 return getResource().getInputStream();
564 }
565 }
566
567 @Override
568 public Permission getPermission() throws IOException {
569
570 return resourceURL.openConnection().getPermission();
571 }
572
573 @Override
574 public long getLastModified() {
575 return getResource().getLastModified();
576 }
577
578 @Override
579 public long getContentLengthLong() {
580 return getResource().getContentLength();
581 }
582
583 private WebResource getResource() {
584 return root.getResource(webAppPath, false, usesClassLoaderResources);
585 }
586
587 @Override
588 public JarFile getJarFile() throws IOException {
589 return ((JarURLConnection) resourceURL.openConnection()).getJarFile();
590 }
591
592 @Override
593 public JarEntry getJarEntry() throws IOException {
594 if (getEntryName() == null) {
595 return null;
596 } else {
597 return super.getJarEntry();
598 }
599 }
600 }
601 }
602