ttomcat-1778514358873.zip-extract/apache-tomcat-11.0.18-src/java/org/apache/catalina/webresources/CachedResource.java

Path
ttomcat-1778514358873.zip-extract/apache-tomcat-11.0.18-src/java/org/apache/catalina/webresources/CachedResource.java
Status
scanned
Type
file
Name
CachedResource.java
Extension
.java
Programming language
Java
Mime type
text/plain
File type
ASCII text, with CRLF line terminators
Tag

      
    
Rootfs path

      
    
Size
25038 (24.5 KB)
MD5
e2ee93e8adbde0417fcf0b2ec47190de
SHA1
8836f7d238be0e2852b47e2489156455b3a10f13
SHA256
f9435e3d63fa2b2ff6eaae3417b2f7a588bd4fe241590583532536d1672ca6df
SHA512

      
    
SHA1_git
99e5c8554a9777bb63d1b011c63ed8df883ea520
Is binary

      
    
Is text
True
Is archive

      
    
Is media

      
    
Is legal

      
    
Is manifest

      
    
Is readme

      
    
Is top level

      
    
Is key file

      
    
CachedResource.java | 24.5 KB |

/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.catalina.webresources; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.nio.charset.Charset; import java.security.Permission; import java.security.cert.Certificate; import java.text.Collator; import java.util.Arrays; import java.util.Locale; import java.util.Objects; import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.catalina.WebResource; import org.apache.catalina.WebResourceRoot; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.HexUtils; import org.apache.tomcat.util.res.StringManager; import org.apache.tomcat.util.security.ConcurrentMessageDigest; /** * This class is designed to wrap a 'raw' WebResource and providing caching for expensive operations. Inexpensive * operations may be passed through to the underlying resource. */ public class CachedResource implements WebResource { private static final Log log = LogFactory.getLog(CachedResource.class); private static final StringManager sm = StringManager.getManager(CachedResource.class); // Estimate (on high side to be safe) of average size excluding content // based on profiler data. private static final long CACHE_ENTRY_SIZE = 500; private final Cache cache; private final StandardRoot root; private final String webAppPath; private final long ttl; private final int objectMaxSizeBytes; private final boolean usesClassLoaderResources; private volatile WebResource webResource; private volatile WebResource[] webResources; private volatile long nextCheck; private volatile Long cachedLastModified = null; private volatile String cachedLastModifiedHttp = null; private volatile byte[] cachedContent = null; private volatile Boolean cachedIsFile = null; private volatile Boolean cachedIsDirectory = null; private volatile Boolean cachedExists = null; private volatile Boolean cachedIsVirtual = null; private volatile Long cachedContentLength = null; private volatile String cachedStrongETag = null; public CachedResource(Cache cache, StandardRoot root, String path, long ttl, int objectMaxSizeBytes, boolean usesClassLoaderResources) { this.cache = cache; this.root = root; this.webAppPath = path; this.ttl = ttl; nextCheck = ttl + System.currentTimeMillis(); this.objectMaxSizeBytes = objectMaxSizeBytes; this.usesClassLoaderResources = usesClassLoaderResources; } protected boolean validateResource(boolean useClassLoaderResources) { // It is possible that some resources will only be visible for a given // value of useClassLoaderResources. Therefore, if the lookup is made // with a different value of useClassLoaderResources than was used when // creating the cache entry, invalidate the entry. This should have // minimal performance impact as it would be unusual for a resource to // be looked up both as a static resource and as a class loader // resource. if (usesClassLoaderResources != useClassLoaderResources) { return false; } if (webResource == null) { synchronized (this) { if (webResource == null) { webResource = root.getResourceInternal(webAppPath, useClassLoaderResources); getLastModified(); getContentLength(); // exists() is a relatively expensive check for a file so // use the fact that we know if it exists at this point if (webResource instanceof EmptyResource) { cachedExists = Boolean.FALSE; } else { cachedExists = Boolean.TRUE; } return true; } } } long now = System.currentTimeMillis(); if (now < nextCheck) { return true; } // Assume resources inside WARs will not change if (!root.isPackedWarFile()) { WebResource webResourceInternal = root.getResourceInternal(webAppPath, useClassLoaderResources); if (!webResource.exists() && webResourceInternal.exists()) { return false; } // If modified date or length change - resource has changed / been // removed etc. if (webResource.getLastModified() != getLastModified() || webResource.getContentLength() != getContentLength()) { return false; } // Has a resource been inserted / removed in a different resource set if (webResource.getLastModified() != webResourceInternal.getLastModified() || webResource.getContentLength() != webResourceInternal.getContentLength()) { return false; } } nextCheck = ttl + now; return true; } protected boolean validateResources(boolean useClassLoaderResources) { long now = System.currentTimeMillis(); if (webResources == null) { synchronized (this) { if (webResources == null) { webResources = root.getResourcesInternal(webAppPath, useClassLoaderResources); nextCheck = ttl + now; return true; } } } if (now < nextCheck) { return true; } // Assume resources inside WARs will not change if (root.isPackedWarFile()) { nextCheck = ttl + now; return true; } else { // At this point, always expire the entry and re-populating it is // likely to be as expensive as validating it. return false; } } protected long getNextCheck() { return nextCheck; } @Override public long getLastModified() { if (cachedLastModified == null) { cachedLastModified = Long.valueOf(webResource.getLastModified()); } return cachedLastModified.longValue(); } @Override public String getLastModifiedHttp() { if (cachedLastModifiedHttp == null) { cachedLastModifiedHttp = webResource.getLastModifiedHttp(); } return cachedLastModifiedHttp; } @Override public boolean exists() { if (cachedExists == null) { cachedExists = Boolean.valueOf(webResource.exists()); } return cachedExists.booleanValue(); } @Override public boolean isVirtual() { if (cachedIsVirtual == null) { cachedIsVirtual = Boolean.valueOf(webResource.isVirtual()); } return cachedIsVirtual.booleanValue(); } @Override public boolean isDirectory() { if (cachedIsDirectory == null) { cachedIsDirectory = Boolean.valueOf(webResource.isDirectory()); } return cachedIsDirectory.booleanValue(); } @Override public boolean isFile() { if (cachedIsFile == null) { cachedIsFile = Boolean.valueOf(webResource.isFile()); } return cachedIsFile.booleanValue(); } @Override public boolean delete() { boolean deleteResult = webResource.delete(); if (deleteResult) { cache.removeCacheEntry(webAppPath); } return deleteResult; } @Override public String getName() { return webResource.getName(); } @Override public long getContentLength() { /* * Cache the content length for two reasons. * * 1. It is relatively expensive to calculate, it shouldn't change and this method is called multiple times * during cache validation. Caching, therefore, offers a performance benefit. * * 2. There is a race condition if concurrent threads are trying to PUT and DELETE the same resource. See BZ * 69527 (https://bz.apache.org/bugzilla/show_bug.cgi?id=69527#c14) for full details. The short version is that * getContentLength() must always return the same value for any one CachedResource instance else the cache size * will be corrupted. */ if (cachedContentLength == null) { if (webResource == null) { synchronized (this) { if (webResource == null) { webResource = root.getResourceInternal(webAppPath, usesClassLoaderResources); } } } cachedContentLength = Long.valueOf(webResource.getContentLength()); } return cachedContentLength.longValue(); } @Override public String getCanonicalPath() { return webResource.getCanonicalPath(); } @Override public boolean canRead() { return webResource.canRead(); } @Override public String getWebappPath() { return webAppPath; } @Override public String getETag() { return webResource.getETag(); } @Override public String getStrongETag() { if (cachedStrongETag == null) { byte[] buf = getContent(); if (buf != null) { buf = ConcurrentMessageDigest.digestSHA256(buf); cachedStrongETag = "\"" + HexUtils.toHexString(buf) + "\""; } else { cachedStrongETag = webResource.getStrongETag(); } } return cachedStrongETag; } @Override public void setMimeType(String mimeType) { webResource.setMimeType(mimeType); } @Override public String getMimeType() { return webResource.getMimeType(); } @Override public InputStream getInputStream() { byte[] content = getContent(); if (content == null) { // Can't cache InputStreams return webResource.getInputStream(); } return new ByteArrayInputStream(content); } @Override public byte[] getContent() { if (cachedContent == null) { if (getContentLength() > objectMaxSizeBytes) { return null; } cachedContent = webResource.getContent(); } return cachedContent; } @Override public long getCreation() { return webResource.getCreation(); } @Override public URL getURL() { /* * We don't want applications using this URL to access the resource directly as that could lead to inconsistent * results when the resource is updated on the file system but the cache entry has not yet expired. We saw this, * for example, in JSP compilation. * * - last modified time was obtained via ServletContext.getResource("path").openConnection().getLastModified() * * - JSP content was obtained via ServletContext.getResourceAsStream("path") * * The result was that the JSP modification was detected but the JSP content was read from the cache so the * non-updated JSP page was used to generate the .java and .class file * * One option to resolve this issue is to use a custom URL scheme for resource URLs. This would allow us, via * registration of a URLStreamHandlerFactory, to control how the resources are accessed and ensure that all * access go via the cache. We took this approach for war: URLs so we can use jar:war:file: URLs to reference * resources in unpacked WAR files. However, because URL.setURLStreamHandlerFactory() may only be called once, * this can cause problems when using other libraries that also want to use a custom URL scheme. * * The approach below allows us to insert a custom URLStreamHandler without registering a custom protocol. The * only limitation (compared to registering a custom protocol) is that if the application constructs the same * URL from a String, they will access the resource directly and not via the cache. */ URL resourceURL = webResource.getURL(); if (resourceURL == null) { return null; } try { CachedResourceURLStreamHandler handler = new CachedResourceURLStreamHandler(resourceURL, root, webAppPath, usesClassLoaderResources); @SuppressWarnings("deprecation") URL result = new URL(null, resourceURL.toExternalForm(), handler); handler.setCacheURL(result); return result; } catch (MalformedURLException e) { log.error(sm.getString("cachedResource.invalidURL", resourceURL.toExternalForm()), e); return null; } } @Override public URL getCodeBase() { return webResource.getCodeBase(); } @Override public Certificate[] getCertificates() { return webResource.getCertificates(); } @Override public Manifest getManifest() { return webResource.getManifest(); } @Override public WebResourceRoot getWebResourceRoot() { return webResource.getWebResourceRoot(); } WebResource getWebResource() { return webResource; } WebResource[] getWebResources() { return webResources; } boolean usesClassLoaderResources() { return usesClassLoaderResources; } // Assume that the cache entry will always include the content unless the // resource content is larger than objectMaxSizeBytes. This isn't always the // case but it makes tracking the current cache size easier. long getSize() { long result = CACHE_ENTRY_SIZE; // Longer paths use a noticeable amount of memory so account for this in // the cache size. The fixed component of a String instance's memory // usage is accounted for in the 500 bytes above. result += getWebappPath().length() * 2L; if (getContentLength() <= objectMaxSizeBytes) { result += getContentLength(); } return result; } /* * Mimics the behaviour of FileURLConnection.getInputStream for a directory. Deliberately uses default locale. */ private static InputStream buildInputStream(String[] files) { Arrays.sort(files, Collator.getInstance(Locale.getDefault())); StringBuilder result = new StringBuilder(); for (String file : files) { result.append(file); // Every entry is followed by including the last result.append(' '); } return new ByteArrayInputStream(result.toString().getBytes(Charset.defaultCharset())); } /** * URLStreamHandler to handle a URL for a cached resource, delegating reads to the Cache. * <ul> * <li>delegates reads to the Cache, to ensure consistent invalidation behavior</li> * <li>delegates hashCode()/ equals() behavior to the underlying Resource URL. (Equinox/ OSGi compatibility)</li> * <li>detects the case where a new relative URL is created from the wrapped URL, inheriting its handler; in this * case reverts to default behavior</li> * </ul> */ private static class CachedResourceURLStreamHandler extends URLStreamHandler { private final URL resourceURL; private final StandardRoot root; private final String webAppPath; private final boolean usesClassLoaderResources; private URL cacheURL = null; CachedResourceURLStreamHandler(URL resourceURL, StandardRoot root, String webAppPath, boolean usesClassLoaderResources) { this.resourceURL = resourceURL; this.root = root; this.webAppPath = webAppPath; this.usesClassLoaderResources = usesClassLoaderResources; } protected void setCacheURL(URL cacheURL) { this.cacheURL = cacheURL; } @Override protected URLConnection openConnection(URL u) throws IOException { // This deliberately uses ==. If u isn't the URL object this // URLStreamHandler was constructed for we do not want to use this // URLStreamHandler to create a connection. if (cacheURL != null && u == cacheURL) { if ("jar".equals(cacheURL.getProtocol())) { return new CachedResourceJarURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources); } else { return new CachedResourceURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources); } } else { // This stream handler has been inherited by a URL that was constructed from a cache URL. // We need to break that link. URI constructedURI; try { constructedURI = new URI(u.toExternalForm()); } catch (URISyntaxException e) { // Not ideal but consistent with API throw new IOException(e); } URL constructedURL = constructedURI.toURL(); return constructedURL.openConnection(); } } /** * {@inheritDoc} * <p> * We don't know what the requirements are for equals for the wrapped resourceURL so if u1 is the cacheURL, * delegate to the resourceURL and it's handler. Otherwise, use the default implementation from * URLStreamHandler. */ @Override protected boolean equals(URL u1, URL u2) { // Deliberate use of == if (cacheURL == u1) { return resourceURL.equals(u2); } // Not the cacheURL. This stream handler has been inherited by a URL that was constructed from a cache URL. // Use the default implementation from URLStreamHandler. return super.equals(u1, u2); } /** * {@inheritDoc} * <p> * We don't know what the requirements are for hashcode for the wrapped resourceURL so if u1 is the cacheURL, * delegate to the resourceURL and it's handler. Otherwise, use the default implementation from * URLStreamHandler. */ @Override protected int hashCode(URL u) { // Deliberate use of == if (cacheURL == u) { return resourceURL.hashCode(); } // Not the cacheURL. This stream handler has been inherited by a URL that was constructed from a cache URL. // Use the default implementation from URLStreamHandler. return super.hashCode(u); } } /* * Keep this in sync with CachedResourceJarURLConnection. */ private static class CachedResourceURLConnection extends URLConnection { private final StandardRoot root; private final String webAppPath; private final boolean usesClassLoaderResources; private final URL resourceURL; protected CachedResourceURLConnection(URL resourceURL, StandardRoot root, String webAppPath, boolean usesClassLoaderResources) { super(resourceURL); this.root = root; this.webAppPath = webAppPath; this.usesClassLoaderResources = usesClassLoaderResources; this.resourceURL = resourceURL; } @Override public void connect() throws IOException { // NO-OP } @Override public InputStream getInputStream() throws IOException { WebResource resource = getResource(); if (resource.isDirectory()) { return buildInputStream(resource.getWebResourceRoot().list(webAppPath)); } else { return getResource().getInputStream(); } } @Override @Deprecated public Permission getPermission() throws IOException { // Doesn't trigger a call to connect for file:// URLs return resourceURL.openConnection().getPermission(); } @Override public long getLastModified() { return getResource().getLastModified(); } @Override public long getContentLengthLong() { return getResource().getContentLength(); } private WebResource getResource() { return root.getResource(webAppPath, false, usesClassLoaderResources); } @Override public String getContentType() { // "content/unknown" is the value used by sun.net.www.URLConnection. It is used here for consistency. return Objects.requireNonNullElse(getResource().getMimeType(), "content/unknown"); } } /* * Keep this in sync with CachedResourceURLConnection. */ private static class CachedResourceJarURLConnection extends JarURLConnection { private final StandardRoot root; private final String webAppPath; private final boolean usesClassLoaderResources; private final URL resourceURL; protected CachedResourceJarURLConnection(URL resourceURL, StandardRoot root, String webAppPath, boolean usesClassLoaderResources) throws IOException { super(resourceURL); this.root = root; this.webAppPath = webAppPath; this.usesClassLoaderResources = usesClassLoaderResources; this.resourceURL = resourceURL; } @Override public void connect() throws IOException { // NO-OP } @Override public InputStream getInputStream() throws IOException { WebResource resource = getResource(); if (resource.isDirectory()) { return buildInputStream(resource.getWebResourceRoot().list(webAppPath)); } else { return getResource().getInputStream(); } } @Override @Deprecated public Permission getPermission() throws IOException { // Doesn't trigger a call to connect for jar:// URLs return resourceURL.openConnection().getPermission(); } @Override public long getLastModified() { return getResource().getLastModified(); } @Override public long getContentLengthLong() { return getResource().getContentLength(); } private WebResource getResource() { return root.getResource(webAppPath, false, usesClassLoaderResources); } @Override public JarFile getJarFile() throws IOException { return ((JarURLConnection) resourceURL.openConnection()).getJarFile(); } @Override public String getContentType() { // "content/unknown" is the value used by sun.net.www.URLConnection. It is used here for consistency. return Objects.requireNonNullElse(getResource().getMimeType(), "content/unknown"); } } }
Detected license expression
apache-2.0
Detected license expression (SPDX)
Apache-2.0
Percentage of license text
5.42
Copyrights

      
    
Holders

      
    
Authors

      
    
License detections License expression License expression SPDX
apache_2_0-4bde3f57-78aa-4201-96bf-531cba09e7de apache-2.0 Apache-2.0
URL Start line End line
http://www.apache.org/licenses/LICENSE-2.0 9 9
https://bz.apache.org/bugzilla/show_bug.cgi?id=69527#c14 257 257