init
This commit is contained in:
@@ -0,0 +1,315 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
public abstract class AbstractArchiveResource extends AbstractResource {
|
||||
|
||||
private final AbstractArchiveResourceSet archiveResourceSet;
|
||||
private final String baseUrl;
|
||||
private final JarEntry resource;
|
||||
private final String codeBaseUrl;
|
||||
private final String name;
|
||||
private boolean readCerts = false;
|
||||
private Certificate[] certificates;
|
||||
|
||||
protected AbstractArchiveResource(AbstractArchiveResourceSet archiveResourceSet,
|
||||
String webAppPath, String baseUrl, JarEntry jarEntry, String codeBaseUrl) {
|
||||
super(archiveResourceSet.getRoot(), webAppPath);
|
||||
this.archiveResourceSet = archiveResourceSet;
|
||||
this.baseUrl = baseUrl;
|
||||
this.resource = jarEntry;
|
||||
this.codeBaseUrl = codeBaseUrl;
|
||||
|
||||
String resourceName = resource.getName();
|
||||
if (resourceName.charAt(resourceName.length() - 1) == '/') {
|
||||
resourceName = resourceName.substring(0, resourceName.length() - 1);
|
||||
}
|
||||
String internalPath = archiveResourceSet.getInternalPath();
|
||||
if (internalPath.length() > 0 && resourceName.equals(
|
||||
internalPath.subSequence(1, internalPath.length()))) {
|
||||
name = "";
|
||||
} else {
|
||||
int index = resourceName.lastIndexOf('/');
|
||||
if (index == -1) {
|
||||
name = resourceName;
|
||||
} else {
|
||||
name = resourceName.substring(index + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected AbstractArchiveResourceSet getArchiveResourceSet() {
|
||||
return archiveResourceSet;
|
||||
}
|
||||
|
||||
protected final String getBase() {
|
||||
return archiveResourceSet.getBase();
|
||||
}
|
||||
|
||||
protected final String getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
protected final JarEntry getResource() {
|
||||
return resource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return resource.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return resource.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return !resource.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
if (isDirectory()) {
|
||||
return -1;
|
||||
}
|
||||
return resource.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreation() {
|
||||
return resource.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() {
|
||||
String url = baseUrl + resource.getName();
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (MalformedURLException e) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug(sm.getString("fileResource.getUrlFail", url), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getCodeBase() {
|
||||
try {
|
||||
return new URL(codeBaseUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug(sm.getString("fileResource.getUrlFail", codeBaseUrl), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final byte[] getContent() {
|
||||
long len = getContentLength();
|
||||
|
||||
if (len > Integer.MAX_VALUE) {
|
||||
// Can't create an array that big
|
||||
throw new ArrayIndexOutOfBoundsException(sm.getString(
|
||||
"abstractResource.getContentTooLarge", getWebappPath(),
|
||||
Long.valueOf(len)));
|
||||
}
|
||||
|
||||
if (len < 0) {
|
||||
// Content is not applicable here (e.g. is a directory)
|
||||
return null;
|
||||
}
|
||||
|
||||
int size = (int) len;
|
||||
byte[] result = new byte[size];
|
||||
|
||||
int pos = 0;
|
||||
try (JarInputStreamWrapper jisw = getJarInputStreamWrapper()) {
|
||||
if (jisw == null) {
|
||||
// An error occurred, don't return corrupted content
|
||||
return null;
|
||||
}
|
||||
while (pos < size) {
|
||||
int n = jisw.read(result, pos, size - pos);
|
||||
if (n < 0) {
|
||||
break;
|
||||
}
|
||||
pos += n;
|
||||
}
|
||||
// Once the stream has been read, read the certs
|
||||
certificates = jisw.getCertificates();
|
||||
readCerts = true;
|
||||
} catch (IOException ioe) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug(sm.getString("abstractResource.getContentFail",
|
||||
getWebappPath()), ioe);
|
||||
}
|
||||
// Don't return corrupted content
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Certificate[] getCertificates() {
|
||||
if (!readCerts) {
|
||||
// TODO - get content first
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return certificates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() {
|
||||
return archiveResourceSet.getManifest();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final InputStream doGetInputStream() {
|
||||
if (isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
return getJarInputStreamWrapper();
|
||||
}
|
||||
|
||||
protected abstract JarInputStreamWrapper getJarInputStreamWrapper();
|
||||
|
||||
/**
|
||||
* This wrapper assumes that the InputStream was created from a JarFile
|
||||
* obtained from a call to getArchiveResourceSet().openJarFile(). If this is
|
||||
* not the case then the usage counting in AbstractArchiveResourceSet will
|
||||
* break and the JarFile may be unexpectedly closed.
|
||||
*/
|
||||
protected class JarInputStreamWrapper extends InputStream {
|
||||
|
||||
private final JarEntry jarEntry;
|
||||
private final InputStream is;
|
||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||
|
||||
|
||||
public JarInputStreamWrapper(JarEntry jarEntry, InputStream is) {
|
||||
this.jarEntry = jarEntry;
|
||||
this.is = is;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return is.read();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return is.read(b);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return is.read(b, off, len);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return is.skip(n);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return is.available();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (closed.compareAndSet(false, true)) {
|
||||
// Must only call this once else the usage counting will break
|
||||
archiveResourceSet.closeJarFile();
|
||||
}
|
||||
is.close();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
is.mark(readlimit);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
is.reset();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return is.markSupported();
|
||||
}
|
||||
|
||||
public Certificate[] getCertificates() {
|
||||
return jarEntry.getCertificates();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.catalina.util.ResourceSet;
|
||||
import org.apache.tomcat.util.compat.JreCompat;
|
||||
|
||||
public abstract class AbstractArchiveResourceSet extends AbstractResourceSet {
|
||||
|
||||
private URL baseUrl;
|
||||
private String baseUrlString;
|
||||
|
||||
private JarFile archive = null;
|
||||
protected HashMap<String,JarEntry> archiveEntries = null;
|
||||
protected final Object archiveLock = new Object();
|
||||
private long archiveUseCount = 0;
|
||||
|
||||
|
||||
protected final void setBaseUrl(URL baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
if (baseUrl == null) {
|
||||
this.baseUrlString = null;
|
||||
} else {
|
||||
this.baseUrlString = baseUrl.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final URL getBaseUrl() {
|
||||
return baseUrl;
|
||||
}
|
||||
|
||||
protected final String getBaseUrlString() {
|
||||
return baseUrlString;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String[] list(String path) {
|
||||
checkPath(path);
|
||||
String webAppMount = getWebAppMount();
|
||||
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
String pathInJar =
|
||||
getInternalPath() + path.substring(webAppMount.length());
|
||||
// Always strip off the leading '/' to get the JAR path
|
||||
if (pathInJar.length() > 0 && pathInJar.charAt(0) == '/') {
|
||||
pathInJar = pathInJar.substring(1);
|
||||
}
|
||||
for (String name : getArchiveEntries(false).keySet()) {
|
||||
if (name.length() > pathInJar.length() &&
|
||||
name.startsWith(pathInJar)) {
|
||||
if (name.charAt(name.length() - 1) == '/') {
|
||||
name = name.substring(
|
||||
pathInJar.length(), name.length() - 1);
|
||||
} else {
|
||||
name = name.substring(pathInJar.length());
|
||||
}
|
||||
if (name.length() == 0) {
|
||||
continue;
|
||||
}
|
||||
if (name.charAt(0) == '/') {
|
||||
name = name.substring(1);
|
||||
}
|
||||
if (name.length() > 0 && name.lastIndexOf('/') == -1) {
|
||||
result.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
}
|
||||
if (webAppMount.startsWith(path)) {
|
||||
int i = webAppMount.indexOf('/', path.length());
|
||||
if (i == -1) {
|
||||
return new String[] {webAppMount.substring(path.length())};
|
||||
} else {
|
||||
return new String[] {
|
||||
webAppMount.substring(path.length(), i)};
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Set<String> listWebAppPaths(String path) {
|
||||
checkPath(path);
|
||||
String webAppMount = getWebAppMount();
|
||||
|
||||
ResourceSet<String> result = new ResourceSet<>();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
String pathInJar =
|
||||
getInternalPath() + path.substring(webAppMount.length());
|
||||
// Always strip off the leading '/' to get the JAR path and make
|
||||
// sure it ends in '/'
|
||||
if (pathInJar.length() > 0) {
|
||||
if (pathInJar.charAt(pathInJar.length() - 1) != '/') {
|
||||
pathInJar = pathInJar.substring(1) + '/';
|
||||
}
|
||||
if (pathInJar.charAt(0) == '/') {
|
||||
pathInJar = pathInJar.substring(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (String name : getArchiveEntries(false).keySet()) {
|
||||
if (name.length() > pathInJar.length() && name.startsWith(pathInJar)) {
|
||||
int nextSlash = name.indexOf('/', pathInJar.length());
|
||||
if (nextSlash != -1 && nextSlash != name.length() - 1) {
|
||||
name = name.substring(0, nextSlash + 1);
|
||||
}
|
||||
result.add(webAppMount + '/' + name.substring(getInternalPath().length()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
}
|
||||
if (webAppMount.startsWith(path)) {
|
||||
int i = webAppMount.indexOf('/', path.length());
|
||||
if (i == -1) {
|
||||
result.add(webAppMount + "/");
|
||||
} else {
|
||||
result.add(webAppMount.substring(0, i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
result.setLocked(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the map of entries in the archive. May return null in which case
|
||||
* {@link #getArchiveEntry(String)} should be used.
|
||||
*
|
||||
* @param single Is this request being make to support a single lookup? If
|
||||
* false, a map will always be returned. If true,
|
||||
* implementations may use this as a hint in determining the
|
||||
* optimum way to respond.
|
||||
*
|
||||
* @return The archives entries mapped to their names or null if
|
||||
* {@link #getArchiveEntry(String)} should be used.
|
||||
*/
|
||||
protected abstract HashMap<String,JarEntry> getArchiveEntries(boolean single);
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a single entry from the archive. For performance reasons,
|
||||
* {@link #getArchiveEntries(boolean)} should always be called first and the
|
||||
* archive entry looked up in the map if one is returned. Only if that call
|
||||
* returns null should this method be used.
|
||||
*
|
||||
* @param pathInArchive The path in the archive of the entry required
|
||||
*
|
||||
* @return The specified archive entry or null if it does not exist
|
||||
*/
|
||||
protected abstract JarEntry getArchiveEntry(String pathInArchive);
|
||||
|
||||
@Override
|
||||
public final boolean mkdir(String path) {
|
||||
checkPath(path);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean write(String path, InputStream is, boolean overwrite) {
|
||||
checkPath(path);
|
||||
|
||||
if (is == null) {
|
||||
throw new NullPointerException(
|
||||
sm.getString("dirResourceSet.writeNpe"));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final WebResource getResource(String path) {
|
||||
checkPath(path);
|
||||
String webAppMount = getWebAppMount();
|
||||
WebResourceRoot root = getRoot();
|
||||
|
||||
/*
|
||||
* Implementation notes
|
||||
*
|
||||
* The path parameter passed into this method always starts with '/'.
|
||||
*
|
||||
* The path parameter passed into this method may or may not end with a
|
||||
* '/'. JarFile.getEntry() will return a matching directory entry
|
||||
* whether or not the name ends in a '/'. However, if the entry is
|
||||
* requested without the '/' subsequent calls to JarEntry.isDirectory()
|
||||
* will return false.
|
||||
*
|
||||
* Paths in JARs never start with '/'. Leading '/' need to be removed
|
||||
* before any JarFile.getEntry() call.
|
||||
*/
|
||||
|
||||
// If the JAR has been mounted below the web application root, return
|
||||
// an empty resource for requests outside of the mount point.
|
||||
|
||||
if (path.startsWith(webAppMount)) {
|
||||
String pathInJar = getInternalPath() + path.substring(
|
||||
webAppMount.length(), path.length());
|
||||
// Always strip off the leading '/' to get the JAR path
|
||||
if (pathInJar.length() > 0 && pathInJar.charAt(0) == '/') {
|
||||
pathInJar = pathInJar.substring(1);
|
||||
}
|
||||
if (pathInJar.equals("")) {
|
||||
// Special case
|
||||
// This is a directory resource so the path must end with /
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
}
|
||||
return new JarResourceRoot(root, new File(getBase()),
|
||||
baseUrlString, path);
|
||||
} else {
|
||||
JarEntry jarEntry = null;
|
||||
if (isMultiRelease()) {
|
||||
// Calls JarFile.getJarEntry() which is multi-release aware
|
||||
jarEntry = getArchiveEntry(pathInJar);
|
||||
} else {
|
||||
Map<String,JarEntry> jarEntries = getArchiveEntries(true);
|
||||
if (!(pathInJar.charAt(pathInJar.length() - 1) == '/')) {
|
||||
if (jarEntries == null) {
|
||||
jarEntry = getArchiveEntry(pathInJar + '/');
|
||||
} else {
|
||||
jarEntry = jarEntries.get(pathInJar + '/');
|
||||
}
|
||||
if (jarEntry != null) {
|
||||
path = path + '/';
|
||||
}
|
||||
}
|
||||
if (jarEntry == null) {
|
||||
if (jarEntries == null) {
|
||||
jarEntry = getArchiveEntry(pathInJar);
|
||||
} else {
|
||||
jarEntry = jarEntries.get(pathInJar);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (jarEntry == null) {
|
||||
return new EmptyResource(root, path);
|
||||
} else {
|
||||
return createArchiveResource(jarEntry, path, getManifest());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new EmptyResource(root, path);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract boolean isMultiRelease();
|
||||
|
||||
protected abstract WebResource createArchiveResource(JarEntry jarEntry,
|
||||
String webAppPath, Manifest manifest);
|
||||
|
||||
@Override
|
||||
public final boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
if (readOnly) {
|
||||
// This is the hard-coded default - ignore the call
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("abstractArchiveResourceSet.setReadOnlyFalse"));
|
||||
}
|
||||
|
||||
protected JarFile openJarFile() throws IOException {
|
||||
synchronized (archiveLock) {
|
||||
if (archive == null) {
|
||||
archive = JreCompat.getInstance().jarFileNewInstance(getBase());
|
||||
}
|
||||
archiveUseCount++;
|
||||
return archive;
|
||||
}
|
||||
}
|
||||
|
||||
protected void closeJarFile() {
|
||||
synchronized (archiveLock) {
|
||||
archiveUseCount--;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gc() {
|
||||
synchronized (archiveLock) {
|
||||
if (archive != null && archiveUseCount == 0) {
|
||||
try {
|
||||
archive.close();
|
||||
} catch (IOException e) {
|
||||
// Log at least WARN
|
||||
}
|
||||
archive = null;
|
||||
archiveEntries = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.tomcat.util.compat.JrePlatform;
|
||||
import org.apache.tomcat.util.http.RequestUtil;
|
||||
|
||||
public abstract class AbstractFileResourceSet extends AbstractResourceSet {
|
||||
|
||||
protected static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
private File fileBase;
|
||||
private String absoluteBase;
|
||||
private String canonicalBase;
|
||||
private boolean readOnly = false;
|
||||
|
||||
protected AbstractFileResourceSet(String internalPath) {
|
||||
setInternalPath(internalPath);
|
||||
}
|
||||
|
||||
protected final File getFileBase() {
|
||||
return fileBase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return readOnly;
|
||||
}
|
||||
|
||||
protected final File file(String name, boolean mustExist) {
|
||||
|
||||
if (name.equals("/")) {
|
||||
name = "";
|
||||
}
|
||||
File file = new File(fileBase, name);
|
||||
|
||||
// If the requested names ends in '/', the Java File API will return a
|
||||
// matching file if one exists. This isn't what we want as it is not
|
||||
// consistent with the Servlet spec rules for request mapping.
|
||||
if (name.endsWith("/") && file.isFile()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the file/dir must exist but the identified file/dir can't be read
|
||||
// then signal that the resource was not found
|
||||
if (mustExist && !file.canRead()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If allow linking is enabled, files are not limited to being located
|
||||
// under the fileBase so all further checks are disabled.
|
||||
if (getRoot().getAllowLinking()) {
|
||||
return file;
|
||||
}
|
||||
|
||||
// Additional Windows specific checks to handle known problems with
|
||||
// File.getCanonicalPath()
|
||||
if (JrePlatform.IS_WINDOWS && isInvalidWindowsFilename(name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check that this file is located under the WebResourceSet's base
|
||||
String canPath = null;
|
||||
try {
|
||||
canPath = file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
if (canPath == null || !canPath.startsWith(canonicalBase)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Ensure that the file is not outside the fileBase. This should not be
|
||||
// possible for standard requests (the request is normalized early in
|
||||
// the request processing) but might be possible for some access via the
|
||||
// Servlet API (RequestDispatcher, HTTP/2 push etc.) therefore these
|
||||
// checks are retained as an additional safety measure
|
||||
// absoluteBase has been normalized so absPath needs to be normalized as
|
||||
// well.
|
||||
String absPath = normalize(file.getAbsolutePath());
|
||||
if (absoluteBase.length() > absPath.length()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove the fileBase location from the start of the paths since that
|
||||
// was not part of the requested path and the remaining check only
|
||||
// applies to the request path
|
||||
absPath = absPath.substring(absoluteBase.length());
|
||||
canPath = canPath.substring(canonicalBase.length());
|
||||
|
||||
// Case sensitivity check
|
||||
// The normalized requested path should be an exact match the equivalent
|
||||
// canonical path. If it is not, possible reasons include:
|
||||
// - case differences on case insensitive file systems
|
||||
// - Windows removing a trailing ' ' or '.' from the file name
|
||||
//
|
||||
// In all cases, a mis-match here results in the resource not being
|
||||
// found
|
||||
//
|
||||
// absPath is normalized so canPath needs to be normalized as well
|
||||
// Can't normalize canPath earlier as canonicalBase is not normalized
|
||||
if (canPath.length() > 0) {
|
||||
canPath = normalize(canPath);
|
||||
}
|
||||
if (!canPath.equals(absPath)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
private boolean isInvalidWindowsFilename(String name) {
|
||||
final int len = name.length();
|
||||
if (len == 0) {
|
||||
return false;
|
||||
}
|
||||
// This consistently ~10 times faster than the equivalent regular
|
||||
// expression irrespective of input length.
|
||||
for (int i = 0; i < len; i++) {
|
||||
char c = name.charAt(i);
|
||||
if (c == '\"' || c == '<' || c == '>') {
|
||||
// These characters are disallowed in Windows file names and
|
||||
// there are known problems for file names with these characters
|
||||
// when using File#getCanonicalPath().
|
||||
// Note: There are additional characters that are disallowed in
|
||||
// Windows file names but these are not known to cause
|
||||
// problems when using File#getCanonicalPath().
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Windows does not allow file names to end in ' ' unless specific low
|
||||
// level APIs are used to create the files that bypass various checks.
|
||||
// File names that end in ' ' are known to cause problems when using
|
||||
// File#getCanonicalPath().
|
||||
if (name.charAt(len -1) == ' ') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a context-relative path, beginning with a "/", that represents
|
||||
* the canonical version of the specified path after ".." and "." elements
|
||||
* are resolved out. If the specified path attempts to go outside the
|
||||
* boundaries of the current context (i.e. too many ".." path elements
|
||||
* are present), return <code>null</code> instead.
|
||||
*
|
||||
* @param path Path to be normalized
|
||||
*/
|
||||
private String normalize(String path) {
|
||||
return RequestUtil.normalize(path, File.separatorChar == '\\');
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getBaseUrl() {
|
||||
try {
|
||||
return getFileBase().toURI().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This is a NO-OP by default for File based resource sets.
|
||||
*/
|
||||
@Override
|
||||
public void gc() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------- Lifecycle methods
|
||||
|
||||
@Override
|
||||
protected void initInternal() throws LifecycleException {
|
||||
fileBase = new File(getBase(), getInternalPath());
|
||||
checkType(fileBase);
|
||||
|
||||
this.absoluteBase = normalize(fileBase.getAbsolutePath());
|
||||
|
||||
try {
|
||||
this.canonicalBase = fileBase.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected abstract void checkType(File file);
|
||||
}
|
||||
106
java/org/apache/catalina/webresources/AbstractResource.java
Normal file
106
java/org/apache/catalina/webresources/AbstractResource.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.InputStream;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.tomcat.util.http.FastHttpDateFormat;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public abstract class AbstractResource implements WebResource {
|
||||
|
||||
protected static final StringManager sm = StringManager.getManager(AbstractResource.class);
|
||||
|
||||
private final WebResourceRoot root;
|
||||
private final String webAppPath;
|
||||
|
||||
private String mimeType = null;
|
||||
private volatile String weakETag;
|
||||
|
||||
|
||||
protected AbstractResource(WebResourceRoot root, String webAppPath) {
|
||||
this.root = root;
|
||||
this.webAppPath = webAppPath;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final WebResourceRoot getWebResourceRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String getWebappPath() {
|
||||
return webAppPath;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String getLastModifiedHttp() {
|
||||
return FastHttpDateFormat.formatDate(getLastModified());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String getETag() {
|
||||
if (weakETag == null) {
|
||||
synchronized (this) {
|
||||
if (weakETag == null) {
|
||||
long contentLength = getContentLength();
|
||||
long lastModified = getLastModified();
|
||||
if ((contentLength >= 0) || (lastModified >= 0)) {
|
||||
weakETag = "W/\"" + contentLength + "-" +
|
||||
lastModified + "\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return weakETag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final InputStream getInputStream() {
|
||||
InputStream is = doGetInputStream();
|
||||
|
||||
if (is == null || !root.getTrackLockedFiles()) {
|
||||
return is;
|
||||
}
|
||||
|
||||
return new TrackedInputStream(root, getName(), is);
|
||||
}
|
||||
|
||||
protected abstract InputStream doGetInputStream();
|
||||
|
||||
|
||||
protected abstract Log getLog();
|
||||
}
|
||||
140
java/org/apache/catalina/webresources/AbstractResourceSet.java
Normal file
140
java/org/apache/catalina/webresources/AbstractResourceSet.java
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.catalina.WebResourceSet;
|
||||
import org.apache.catalina.util.LifecycleBase;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public abstract class AbstractResourceSet extends LifecycleBase
|
||||
implements WebResourceSet {
|
||||
|
||||
private WebResourceRoot root;
|
||||
private String base;
|
||||
private String internalPath = "";
|
||||
private String webAppMount;
|
||||
private boolean classLoaderOnly;
|
||||
private boolean staticOnly;
|
||||
private Manifest manifest;
|
||||
|
||||
|
||||
protected static final StringManager sm = StringManager.getManager(AbstractResourceSet.class);
|
||||
|
||||
|
||||
protected final void checkPath(String path) {
|
||||
if (path == null || path.length() == 0 || path.charAt(0) != '/') {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("abstractResourceSet.checkPath", path));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void setRoot(WebResourceRoot root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
protected final WebResourceRoot getRoot() {
|
||||
return root;
|
||||
}
|
||||
|
||||
|
||||
protected final String getInternalPath() {
|
||||
return internalPath;
|
||||
}
|
||||
|
||||
public final void setInternalPath(String internalPath) {
|
||||
checkPath(internalPath);
|
||||
// Optimise internal processing
|
||||
if (internalPath.equals("/")) {
|
||||
this.internalPath = "";
|
||||
} else {
|
||||
this.internalPath = internalPath;
|
||||
}
|
||||
}
|
||||
|
||||
public final void setWebAppMount(String webAppMount) {
|
||||
checkPath(webAppMount);
|
||||
// Optimise internal processing
|
||||
if (webAppMount.equals("/")) {
|
||||
this.webAppMount = "";
|
||||
} else {
|
||||
this.webAppMount = webAppMount;
|
||||
}
|
||||
}
|
||||
|
||||
protected final String getWebAppMount() {
|
||||
return webAppMount;
|
||||
}
|
||||
|
||||
public final void setBase(String base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
protected final String getBase() {
|
||||
return base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getClassLoaderOnly() {
|
||||
return classLoaderOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoaderOnly(boolean classLoaderOnly) {
|
||||
this.classLoaderOnly = classLoaderOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getStaticOnly() {
|
||||
return staticOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStaticOnly(boolean staticOnly) {
|
||||
this.staticOnly = staticOnly;
|
||||
}
|
||||
|
||||
protected final void setManifest(Manifest manifest) {
|
||||
this.manifest = manifest;
|
||||
}
|
||||
|
||||
protected final Manifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------- Lifecycle methods
|
||||
@Override
|
||||
protected final void startInternal() throws LifecycleException {
|
||||
setState(LifecycleState.STARTING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void stopInternal() throws LifecycleException {
|
||||
setState(LifecycleState.STOPPING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void destroyInternal() throws LifecycleException {
|
||||
gc();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public abstract class AbstractSingleArchiveResource extends AbstractArchiveResource {
|
||||
|
||||
protected AbstractSingleArchiveResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
|
||||
String baseUrl, JarEntry jarEntry, String codeBaseUrl) {
|
||||
super(archiveResourceSet, webAppPath, baseUrl, jarEntry, codeBaseUrl);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JarInputStreamWrapper getJarInputStreamWrapper() {
|
||||
JarFile jarFile = null;
|
||||
try {
|
||||
jarFile = getArchiveResourceSet().openJarFile();
|
||||
// Need to create a new JarEntry so the certificates can be read
|
||||
JarEntry jarEntry = jarFile.getJarEntry(getResource().getName());
|
||||
InputStream is = jarFile.getInputStream(jarEntry);
|
||||
return new JarInputStreamWrapper(jarEntry, is);
|
||||
} catch (IOException e) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug(sm.getString("jarResource.getInputStreamFail",
|
||||
getResource().getName(), getBaseUrl()), e);
|
||||
}
|
||||
if (jarFile != null) {
|
||||
getArchiveResourceSet().closeJarFile();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.tomcat.util.buf.UriUtil;
|
||||
import org.apache.tomcat.util.compat.JreCompat;
|
||||
|
||||
/**
|
||||
* Base class for a {@link org.apache.catalina.WebResourceSet} based on a
|
||||
* single, rather than nested, archive.
|
||||
*/
|
||||
public abstract class AbstractSingleArchiveResourceSet extends AbstractArchiveResourceSet {
|
||||
|
||||
private volatile Boolean multiRelease;
|
||||
|
||||
/**
|
||||
* A no argument constructor is required for this to work with the digester.
|
||||
*/
|
||||
public AbstractSingleArchiveResourceSet() {
|
||||
}
|
||||
|
||||
|
||||
public AbstractSingleArchiveResourceSet(WebResourceRoot root, String webAppMount, String base,
|
||||
String internalPath) throws IllegalArgumentException {
|
||||
setRoot(root);
|
||||
setWebAppMount(webAppMount);
|
||||
setBase(base);
|
||||
setInternalPath(internalPath);
|
||||
|
||||
if (getRoot().getState().isAvailable()) {
|
||||
try {
|
||||
start();
|
||||
} catch (LifecycleException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected HashMap<String,JarEntry> getArchiveEntries(boolean single) {
|
||||
synchronized (archiveLock) {
|
||||
if (archiveEntries == null && !single) {
|
||||
JarFile jarFile = null;
|
||||
archiveEntries = new HashMap<>();
|
||||
try {
|
||||
jarFile = openJarFile();
|
||||
Enumeration<JarEntry> entries = jarFile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
JarEntry entry = entries.nextElement();
|
||||
archiveEntries.put(entry.getName(), entry);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Should never happen
|
||||
archiveEntries = null;
|
||||
throw new IllegalStateException(ioe);
|
||||
} finally {
|
||||
if (jarFile != null) {
|
||||
closeJarFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
return archiveEntries;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JarEntry getArchiveEntry(String pathInArchive) {
|
||||
JarFile jarFile = null;
|
||||
try {
|
||||
jarFile = openJarFile();
|
||||
return jarFile.getJarEntry(pathInArchive);
|
||||
} catch (IOException ioe) {
|
||||
// Should never happen
|
||||
throw new IllegalStateException(ioe);
|
||||
} finally {
|
||||
if (jarFile != null) {
|
||||
closeJarFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isMultiRelease() {
|
||||
if (multiRelease == null) {
|
||||
synchronized (archiveLock) {
|
||||
if (multiRelease == null) {
|
||||
JarFile jarFile = null;
|
||||
try {
|
||||
jarFile = openJarFile();
|
||||
multiRelease = Boolean.valueOf(
|
||||
JreCompat.getInstance().jarFileIsMultiRelease(jarFile));
|
||||
} catch (IOException ioe) {
|
||||
// Should never happen
|
||||
throw new IllegalStateException(ioe);
|
||||
} finally {
|
||||
if (jarFile != null) {
|
||||
closeJarFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return multiRelease.booleanValue();
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------- Lifecycle methods
|
||||
@Override
|
||||
protected void initInternal() throws LifecycleException {
|
||||
|
||||
try (JarFile jarFile = JreCompat.getInstance().jarFileNewInstance(getBase())) {
|
||||
setManifest(jarFile.getManifest());
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalArgumentException(ioe);
|
||||
}
|
||||
|
||||
try {
|
||||
setBaseUrl(UriUtil.buildJarSafeUrl(new File(getBase())));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
329
java/org/apache/catalina/webresources/Cache.java
Normal file
329
java/org/apache/catalina/webresources/Cache.java
Normal file
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
* 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.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class Cache {
|
||||
|
||||
private static final Log log = LogFactory.getLog(Cache.class);
|
||||
protected static final StringManager sm = StringManager.getManager(Cache.class);
|
||||
|
||||
private static final long TARGET_FREE_PERCENT_GET = 5;
|
||||
private static final long TARGET_FREE_PERCENT_BACKGROUND = 10;
|
||||
|
||||
// objectMaxSize must be < maxSize/20
|
||||
private static final int OBJECT_MAX_SIZE_FACTOR = 20;
|
||||
|
||||
private final StandardRoot root;
|
||||
private final AtomicLong size = new AtomicLong(0);
|
||||
|
||||
private long ttl = 5000;
|
||||
private long maxSize = 10 * 1024 * 1024;
|
||||
private int objectMaxSize = (int) maxSize/OBJECT_MAX_SIZE_FACTOR;
|
||||
|
||||
private AtomicLong lookupCount = new AtomicLong(0);
|
||||
private AtomicLong hitCount = new AtomicLong(0);
|
||||
|
||||
private final ConcurrentMap<String,CachedResource> resourceCache =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
public Cache(StandardRoot root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
protected WebResource getResource(String path, boolean useClassLoaderResources) {
|
||||
|
||||
if (noCache(path)) {
|
||||
return root.getResourceInternal(path, useClassLoaderResources);
|
||||
}
|
||||
|
||||
lookupCount.incrementAndGet();
|
||||
|
||||
CachedResource cacheEntry = resourceCache.get(path);
|
||||
|
||||
if (cacheEntry != null && !cacheEntry.validateResource(useClassLoaderResources)) {
|
||||
removeCacheEntry(path);
|
||||
cacheEntry = null;
|
||||
}
|
||||
|
||||
if (cacheEntry == null) {
|
||||
// Local copy to ensure consistency
|
||||
int objectMaxSizeBytes = getObjectMaxSizeBytes();
|
||||
CachedResource newCacheEntry = new CachedResource(this, root, path, getTtl(),
|
||||
objectMaxSizeBytes, useClassLoaderResources);
|
||||
|
||||
// Concurrent callers will end up with the same CachedResource
|
||||
// instance
|
||||
cacheEntry = resourceCache.putIfAbsent(path, newCacheEntry);
|
||||
|
||||
if (cacheEntry == null) {
|
||||
// newCacheEntry was inserted into the cache - validate it
|
||||
cacheEntry = newCacheEntry;
|
||||
cacheEntry.validateResource(useClassLoaderResources);
|
||||
|
||||
// Even if the resource content larger than objectMaxSizeBytes
|
||||
// there is still benefit in caching the resource metadata
|
||||
|
||||
long delta = cacheEntry.getSize();
|
||||
size.addAndGet(delta);
|
||||
|
||||
if (size.get() > maxSize) {
|
||||
// Process resources unordered for speed. Trades cache
|
||||
// efficiency (younger entries may be evicted before older
|
||||
// ones) for speed since this is on the critical path for
|
||||
// request processing
|
||||
long targetSize = maxSize * (100 - TARGET_FREE_PERCENT_GET) / 100;
|
||||
long newSize = evict(targetSize, resourceCache.values().iterator());
|
||||
if (newSize > maxSize) {
|
||||
// Unable to create sufficient space for this resource
|
||||
// Remove it from the cache
|
||||
removeCacheEntry(path);
|
||||
log.warn(sm.getString("cache.addFail", path, root.getContext().getName()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Another thread added the entry to the cache
|
||||
// Make sure it is validated
|
||||
cacheEntry.validateResource(useClassLoaderResources);
|
||||
}
|
||||
} else {
|
||||
hitCount.incrementAndGet();
|
||||
}
|
||||
|
||||
return cacheEntry;
|
||||
}
|
||||
|
||||
protected WebResource[] getResources(String path, boolean useClassLoaderResources) {
|
||||
lookupCount.incrementAndGet();
|
||||
|
||||
// Don't call noCache(path) since the class loader only caches
|
||||
// individual resources. Therefore, always cache collections here
|
||||
|
||||
CachedResource cacheEntry = resourceCache.get(path);
|
||||
|
||||
if (cacheEntry != null && !cacheEntry.validateResources(useClassLoaderResources)) {
|
||||
removeCacheEntry(path);
|
||||
cacheEntry = null;
|
||||
}
|
||||
|
||||
if (cacheEntry == null) {
|
||||
// Local copy to ensure consistency
|
||||
int objectMaxSizeBytes = getObjectMaxSizeBytes();
|
||||
CachedResource newCacheEntry = new CachedResource(this, root, path, getTtl(),
|
||||
objectMaxSizeBytes, useClassLoaderResources);
|
||||
|
||||
// Concurrent callers will end up with the same CachedResource
|
||||
// instance
|
||||
cacheEntry = resourceCache.putIfAbsent(path, newCacheEntry);
|
||||
|
||||
if (cacheEntry == null) {
|
||||
// newCacheEntry was inserted into the cache - validate it
|
||||
cacheEntry = newCacheEntry;
|
||||
cacheEntry.validateResources(useClassLoaderResources);
|
||||
|
||||
// Content will not be cached but we still need metadata size
|
||||
long delta = cacheEntry.getSize();
|
||||
size.addAndGet(delta);
|
||||
|
||||
if (size.get() > maxSize) {
|
||||
// Process resources unordered for speed. Trades cache
|
||||
// efficiency (younger entries may be evicted before older
|
||||
// ones) for speed since this is on the critical path for
|
||||
// request processing
|
||||
long targetSize = maxSize * (100 - TARGET_FREE_PERCENT_GET) / 100;
|
||||
long newSize = evict(targetSize, resourceCache.values().iterator());
|
||||
if (newSize > maxSize) {
|
||||
// Unable to create sufficient space for this resource
|
||||
// Remove it from the cache
|
||||
removeCacheEntry(path);
|
||||
log.warn(sm.getString("cache.addFail", path));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Another thread added the entry to the cache
|
||||
// Make sure it is validated
|
||||
cacheEntry.validateResources(useClassLoaderResources);
|
||||
}
|
||||
} else {
|
||||
hitCount.incrementAndGet();
|
||||
}
|
||||
|
||||
return cacheEntry.getWebResources();
|
||||
}
|
||||
|
||||
protected void backgroundProcess() {
|
||||
// Create an ordered set of all cached resources with the least recently
|
||||
// used first. This is a background process so we can afford to take the
|
||||
// time to order the elements first
|
||||
TreeSet<CachedResource> orderedResources =
|
||||
new TreeSet<>(new EvictionOrder());
|
||||
orderedResources.addAll(resourceCache.values());
|
||||
|
||||
Iterator<CachedResource> iter = orderedResources.iterator();
|
||||
|
||||
long targetSize =
|
||||
maxSize * (100 - TARGET_FREE_PERCENT_BACKGROUND) / 100;
|
||||
long newSize = evict(targetSize, iter);
|
||||
|
||||
if (newSize > targetSize) {
|
||||
log.info(sm.getString("cache.backgroundEvictFail",
|
||||
Long.valueOf(TARGET_FREE_PERCENT_BACKGROUND),
|
||||
root.getContext().getName(),
|
||||
Long.valueOf(newSize / 1024)));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean noCache(String path) {
|
||||
// Don't cache classes. The class loader handles this.
|
||||
// Don't cache JARs. The ResourceSet handles this.
|
||||
if ((path.endsWith(".class") &&
|
||||
(path.startsWith("/WEB-INF/classes/") || path.startsWith("/WEB-INF/lib/")))
|
||||
||
|
||||
(path.startsWith("/WEB-INF/lib/") && path.endsWith(".jar"))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private long evict(long targetSize, Iterator<CachedResource> iter) {
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
long newSize = size.get();
|
||||
|
||||
while (newSize > targetSize && iter.hasNext()) {
|
||||
CachedResource resource = iter.next();
|
||||
|
||||
// Don't expire anything that has been checked within the TTL
|
||||
if (resource.getNextCheck() > now) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the entry from the cache
|
||||
removeCacheEntry(resource.getWebappPath());
|
||||
|
||||
newSize = size.get();
|
||||
}
|
||||
|
||||
return newSize;
|
||||
}
|
||||
|
||||
void removeCacheEntry(String path) {
|
||||
// With concurrent calls for the same path, the entry is only removed
|
||||
// once and the cache size is only updated (if required) once.
|
||||
CachedResource cachedResource = resourceCache.remove(path);
|
||||
if (cachedResource != null) {
|
||||
long delta = cachedResource.getSize();
|
||||
size.addAndGet(-delta);
|
||||
}
|
||||
}
|
||||
|
||||
public long getTtl() {
|
||||
return ttl;
|
||||
}
|
||||
|
||||
public void setTtl(long ttl) {
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
public long getMaxSize() {
|
||||
// Internally bytes, externally kilobytes
|
||||
return maxSize / 1024;
|
||||
}
|
||||
|
||||
public void setMaxSize(long maxSize) {
|
||||
// Internally bytes, externally kilobytes
|
||||
this.maxSize = maxSize * 1024;
|
||||
}
|
||||
|
||||
public long getLookupCount() {
|
||||
return lookupCount.get();
|
||||
}
|
||||
|
||||
public long getHitCount() {
|
||||
return hitCount.get();
|
||||
}
|
||||
|
||||
public void setObjectMaxSize(int objectMaxSize) {
|
||||
if (objectMaxSize * 1024L > Integer.MAX_VALUE) {
|
||||
log.warn(sm.getString("cache.objectMaxSizeTooBigBytes", Integer.valueOf(objectMaxSize)));
|
||||
this.objectMaxSize = Integer.MAX_VALUE;
|
||||
}
|
||||
// Internally bytes, externally kilobytes
|
||||
this.objectMaxSize = objectMaxSize * 1024;
|
||||
}
|
||||
|
||||
public int getObjectMaxSize() {
|
||||
// Internally bytes, externally kilobytes
|
||||
return objectMaxSize / 1024;
|
||||
}
|
||||
|
||||
public int getObjectMaxSizeBytes() {
|
||||
return objectMaxSize;
|
||||
}
|
||||
|
||||
void enforceObjectMaxSizeLimit() {
|
||||
long limit = maxSize / OBJECT_MAX_SIZE_FACTOR;
|
||||
if (limit > Integer.MAX_VALUE) {
|
||||
return;
|
||||
}
|
||||
if (objectMaxSize > limit) {
|
||||
log.warn(sm.getString("cache.objectMaxSizeTooBig",
|
||||
Integer.valueOf(objectMaxSize / 1024), Integer.valueOf((int)limit / 1024)));
|
||||
objectMaxSize = (int) limit;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
resourceCache.clear();
|
||||
size.set(0);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size.get() / 1024;
|
||||
}
|
||||
|
||||
private static class EvictionOrder implements Comparator<CachedResource> {
|
||||
|
||||
@Override
|
||||
public int compare(CachedResource cr1, CachedResource cr2) {
|
||||
long nc1 = cr1.getNextCheck();
|
||||
long nc2 = cr2.getNextCheck();
|
||||
|
||||
// Oldest resource should be first (so iterator goes from oldest to
|
||||
// youngest.
|
||||
if (nc1 == nc2) {
|
||||
return 0;
|
||||
} else if (nc1 > nc2) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
601
java/org/apache/catalina/webresources/CachedResource.java
Normal file
601
java/org/apache/catalina/webresources/CachedResource.java
Normal file
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
* 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.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.jar.JarEntry;
|
||||
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.res.StringManager;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (webResource == null) {
|
||||
synchronized (this) {
|
||||
if (webResource == null) {
|
||||
webResource = root.getResourceInternal(
|
||||
webAppPath, useClassLoaderResources);
|
||||
getLastModified();
|
||||
getContentLength();
|
||||
nextCheck = ttl + now;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
Long cachedLastModified = this.cachedLastModified;
|
||||
if (cachedLastModified == null) {
|
||||
cachedLastModified =
|
||||
Long.valueOf(webResource.getLastModified());
|
||||
this.cachedLastModified = cachedLastModified;
|
||||
}
|
||||
return cachedLastModified.longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastModifiedHttp() {
|
||||
String cachedLastModifiedHttp = this.cachedLastModifiedHttp;
|
||||
if (cachedLastModifiedHttp == null) {
|
||||
cachedLastModifiedHttp = webResource.getLastModifiedHttp();
|
||||
this.cachedLastModifiedHttp = cachedLastModifiedHttp;
|
||||
}
|
||||
return cachedLastModifiedHttp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
Boolean cachedExists = this.cachedExists;
|
||||
if (cachedExists == null) {
|
||||
cachedExists = Boolean.valueOf(webResource.exists());
|
||||
this.cachedExists = cachedExists;
|
||||
}
|
||||
return cachedExists.booleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
Boolean cachedIsVirtual = this.cachedIsVirtual;
|
||||
if (cachedIsVirtual == null) {
|
||||
cachedIsVirtual = Boolean.valueOf(webResource.isVirtual());
|
||||
this.cachedIsVirtual = cachedIsVirtual;
|
||||
}
|
||||
return cachedIsVirtual.booleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
Boolean cachedIsDirectory = this.cachedIsDirectory;
|
||||
if (cachedIsDirectory == null) {
|
||||
cachedIsDirectory = Boolean.valueOf(webResource.isDirectory());
|
||||
this.cachedIsDirectory = cachedIsDirectory;
|
||||
}
|
||||
return cachedIsDirectory.booleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
Boolean cachedIsFile = this.cachedIsFile;
|
||||
if (cachedIsFile == null) {
|
||||
cachedIsFile = Boolean.valueOf(webResource.isFile());
|
||||
this.cachedIsFile = cachedIsFile;
|
||||
}
|
||||
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() {
|
||||
Long cachedContentLength = this.cachedContentLength;
|
||||
if (cachedContentLength == null) {
|
||||
long result = 0;
|
||||
if (webResource != null) {
|
||||
result = webResource.getContentLength();
|
||||
cachedContentLength = Long.valueOf(result);
|
||||
this.cachedContentLength = cachedContentLength;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
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 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() {
|
||||
byte[] cachedContent = this.cachedContent;
|
||||
if (cachedContent == null) {
|
||||
if (getContentLength() > objectMaxSizeBytes) {
|
||||
return null;
|
||||
}
|
||||
cachedContent = webResource.getContent();
|
||||
this.cachedContent = cachedContent;
|
||||
}
|
||||
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 caused 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);
|
||||
URL result = new URL(null, resourceURL.toExternalForm(), handler);
|
||||
handler.setAssociatedURL(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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
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 \n including the last
|
||||
result.append('\n');
|
||||
}
|
||||
return new ByteArrayInputStream(result.toString().getBytes(Charset.defaultCharset()));
|
||||
}
|
||||
|
||||
|
||||
private static class CachedResourceURLStreamHandler extends URLStreamHandler {
|
||||
|
||||
private final URL resourceURL;
|
||||
private final StandardRoot root;
|
||||
private final String webAppPath;
|
||||
private final boolean usesClassLoaderResources;
|
||||
|
||||
private URL associatedURL = null;
|
||||
|
||||
public CachedResourceURLStreamHandler(URL resourceURL, StandardRoot root, String webAppPath,
|
||||
boolean usesClassLoaderResources) {
|
||||
this.resourceURL = resourceURL;
|
||||
this.root = root;
|
||||
this.webAppPath = webAppPath;
|
||||
this.usesClassLoaderResources = usesClassLoaderResources;
|
||||
}
|
||||
|
||||
protected void setAssociatedURL(URL associatedURL) {
|
||||
this.associatedURL = associatedURL;
|
||||
}
|
||||
|
||||
@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 (associatedURL != null && u == associatedURL) {
|
||||
if ("jar".equals(associatedURL.getProtocol())) {
|
||||
return new CachedResourceJarURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources);
|
||||
} else {
|
||||
return new CachedResourceURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources);
|
||||
}
|
||||
} else {
|
||||
// The stream handler has been inherited by a URL that was
|
||||
// constructed from a cache URL. We need to break that link.
|
||||
URL constructedURL = new URL(u.toExternalForm());
|
||||
return constructedURL.openConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
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 JarEntry getJarEntry() throws IOException {
|
||||
if (getEntryName() == null) {
|
||||
return null;
|
||||
} else {
|
||||
return super.getJarEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class ClasspathURLStreamHandler extends URLStreamHandler {
|
||||
|
||||
private static final StringManager sm =
|
||||
StringManager.getManager(ClasspathURLStreamHandler.class);
|
||||
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) throws IOException {
|
||||
String path = u.getPath();
|
||||
|
||||
// Thread context class loader first
|
||||
URL classpathUrl = Thread.currentThread().getContextClassLoader().getResource(path);
|
||||
if (classpathUrl == null) {
|
||||
// This class's class loader if no joy with the tccl
|
||||
classpathUrl = ClasspathURLStreamHandler.class.getResource(path);
|
||||
}
|
||||
|
||||
if (classpathUrl == null) {
|
||||
throw new FileNotFoundException(sm.getString("classpathUrlStreamHandler.notFound", u));
|
||||
}
|
||||
|
||||
return classpathUrl.openConnection();
|
||||
}
|
||||
}
|
||||
279
java/org/apache/catalina/webresources/DirResourceSet.java
Normal file
279
java/org/apache/catalina/webresources/DirResourceSet.java
Normal file
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.catalina.WebResourceRoot.ResourceSetType;
|
||||
import org.apache.catalina.util.ResourceSet;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Represents a {@link org.apache.catalina.WebResourceSet} based on a directory.
|
||||
*/
|
||||
public class DirResourceSet extends AbstractFileResourceSet {
|
||||
|
||||
private static final Log log = LogFactory.getLog(DirResourceSet.class);
|
||||
|
||||
/**
|
||||
* A no argument constructor is required for this to work with the digester.
|
||||
*/
|
||||
public DirResourceSet() {
|
||||
super("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a
|
||||
* directory.
|
||||
*
|
||||
* @param root The {@link WebResourceRoot} this new
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be added to.
|
||||
* @param webAppMount The path within the web application at which this
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be mounted. For example, to add a directory of
|
||||
* JARs to a web application, the directory would
|
||||
* be mounted at "/WEB-INF/lib/"
|
||||
* @param base The absolute path to the directory on the file
|
||||
* system from which the resources will be served.
|
||||
* @param internalPath The path within this new {@link
|
||||
* org.apache.catalina.WebResourceSet} where
|
||||
* resources will be served from.
|
||||
*/
|
||||
public DirResourceSet(WebResourceRoot root, String webAppMount, String base,
|
||||
String internalPath) {
|
||||
super(internalPath);
|
||||
setRoot(root);
|
||||
setWebAppMount(webAppMount);
|
||||
setBase(base);
|
||||
|
||||
if (root.getContext().getAddWebinfClassesResources()) {
|
||||
File f = new File(base, internalPath);
|
||||
f = new File(f, "/WEB-INF/classes/META-INF/resources");
|
||||
|
||||
if (f.isDirectory()) {
|
||||
root.createWebResourceSet(ResourceSetType.RESOURCE_JAR, "/",
|
||||
f.getAbsolutePath(), null, "/");
|
||||
}
|
||||
}
|
||||
|
||||
if (getRoot().getState().isAvailable()) {
|
||||
try {
|
||||
start();
|
||||
} catch (LifecycleException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebResource getResource(String path) {
|
||||
checkPath(path);
|
||||
String webAppMount = getWebAppMount();
|
||||
WebResourceRoot root = getRoot();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
File f = file(path.substring(webAppMount.length()), false);
|
||||
if (f == null) {
|
||||
return new EmptyResource(root, path);
|
||||
}
|
||||
if (!f.exists()) {
|
||||
return new EmptyResource(root, path, f);
|
||||
}
|
||||
if (f.isDirectory() && path.charAt(path.length() - 1) != '/') {
|
||||
path = path + '/';
|
||||
}
|
||||
return new FileResource(root, path, f, isReadOnly(), getManifest());
|
||||
} else {
|
||||
return new EmptyResource(root, path);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(String path) {
|
||||
checkPath(path);
|
||||
String webAppMount = getWebAppMount();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
File f = file(path.substring(webAppMount.length()), true);
|
||||
if (f == null) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
String[] result = f.list();
|
||||
if (result == null) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
}
|
||||
if (webAppMount.startsWith(path)) {
|
||||
int i = webAppMount.indexOf('/', path.length());
|
||||
if (i == -1) {
|
||||
return new String[] {webAppMount.substring(path.length())};
|
||||
} else {
|
||||
return new String[] {
|
||||
webAppMount.substring(path.length(), i)};
|
||||
}
|
||||
}
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listWebAppPaths(String path) {
|
||||
checkPath(path);
|
||||
String webAppMount = getWebAppMount();
|
||||
ResourceSet<String> result = new ResourceSet<>();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
File f = file(path.substring(webAppMount.length()), true);
|
||||
if (f != null) {
|
||||
File[] list = f.listFiles();
|
||||
if (list != null) {
|
||||
for (File entry : list) {
|
||||
StringBuilder sb = new StringBuilder(path);
|
||||
if (path.charAt(path.length() - 1) != '/') {
|
||||
sb.append('/');
|
||||
}
|
||||
sb.append(entry.getName());
|
||||
if (entry.isDirectory()) {
|
||||
sb.append('/');
|
||||
}
|
||||
result.add(sb.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!path.endsWith("/")) {
|
||||
path = path + "/";
|
||||
}
|
||||
if (webAppMount.startsWith(path)) {
|
||||
int i = webAppMount.indexOf('/', path.length());
|
||||
if (i == -1) {
|
||||
result.add(webAppMount + "/");
|
||||
} else {
|
||||
result.add(webAppMount.substring(0, i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
result.setLocked(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String path) {
|
||||
checkPath(path);
|
||||
if (isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
String webAppMount = getWebAppMount();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
File f = file(path.substring(webAppMount.length()), false);
|
||||
if (f == null) {
|
||||
return false;
|
||||
}
|
||||
return f.mkdir();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(String path, InputStream is, boolean overwrite) {
|
||||
checkPath(path);
|
||||
|
||||
if (is == null) {
|
||||
throw new NullPointerException(
|
||||
sm.getString("dirResourceSet.writeNpe"));
|
||||
}
|
||||
|
||||
if (isReadOnly()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// write() is meant to create a file so ensure that the path doesn't
|
||||
// end in '/'
|
||||
if (path.endsWith("/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
File dest = null;
|
||||
String webAppMount = getWebAppMount();
|
||||
if (path.startsWith(webAppMount)) {
|
||||
dest = file(path.substring(webAppMount.length()), false);
|
||||
if (dest == null) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dest.exists() && !overwrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (overwrite) {
|
||||
Files.copy(is, dest.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
} else {
|
||||
Files.copy(is, dest.toPath());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkType(File file) {
|
||||
if (file.isDirectory() == false) {
|
||||
throw new IllegalArgumentException(sm.getString("dirResourceSet.notDirectory",
|
||||
getBase(), File.separator, getInternalPath()));
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------- Lifecycle methods
|
||||
@Override
|
||||
protected void initInternal() throws LifecycleException {
|
||||
super.initInternal();
|
||||
// Is this an exploded web application?
|
||||
if (getWebAppMount().equals("")) {
|
||||
// Look for a manifest
|
||||
File mf = file("META-INF/MANIFEST.MF", true);
|
||||
if (mf != null && mf.isFile()) {
|
||||
try (FileInputStream fis = new FileInputStream(mf)) {
|
||||
setManifest(new Manifest(fis));
|
||||
} catch (IOException e) {
|
||||
log.warn(sm.getString("dirResourceSet.manifestFail", mf.getAbsolutePath()), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
172
java/org/apache/catalina/webresources/EmptyResource.java
Normal file
172
java/org/apache/catalina/webresources/EmptyResource.java
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
|
||||
public class EmptyResource implements WebResource {
|
||||
|
||||
private final WebResourceRoot root;
|
||||
private final String webAppPath;
|
||||
private final File file;
|
||||
|
||||
public EmptyResource(WebResourceRoot root, String webAppPath) {
|
||||
this(root, webAppPath, null);
|
||||
}
|
||||
|
||||
public EmptyResource(WebResourceRoot root, String webAppPath, File file) {
|
||||
this.root = root;
|
||||
this.webAppPath = webAppPath;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getLastModifiedHttp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
int index = webAppPath.lastIndexOf('/');
|
||||
if (index == -1) {
|
||||
return webAppPath;
|
||||
} else {
|
||||
return webAppPath.substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() {
|
||||
if (file == null) {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
return file.getCanonicalPath();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebappPath() {
|
||||
return webAppPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getETag() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMimeType(String mimeType) {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMimeType() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreation() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getCodeBase() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getCertificates() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceRoot getWebResourceRoot() {
|
||||
return root;
|
||||
}
|
||||
}
|
||||
179
java/org/apache/catalina/webresources/EmptyResourceSet.java
Normal file
179
java/org/apache/catalina/webresources/EmptyResourceSet.java
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* 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.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.catalina.WebResourceSet;
|
||||
import org.apache.catalina.util.LifecycleBase;
|
||||
|
||||
/**
|
||||
* A {@link WebResourceSet} implementation that is not backed by a file system
|
||||
* and behaves as if it has no resources available. This is primarily used in
|
||||
* embedded mode when the web application is configured entirely
|
||||
* programmatically and does not use any static resources from the file system.
|
||||
*/
|
||||
public class EmptyResourceSet extends LifecycleBase implements WebResourceSet {
|
||||
|
||||
private static final String[] EMPTY_STRING_ARRAY = new String[0];
|
||||
|
||||
private WebResourceRoot root;
|
||||
private boolean classLoaderOnly;
|
||||
private boolean staticOnly;
|
||||
|
||||
public EmptyResourceSet(WebResourceRoot root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns an {@link EmptyResource}.
|
||||
*/
|
||||
@Override
|
||||
public WebResource getResource(String path) {
|
||||
return new EmptyResource(root, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns an empty array.
|
||||
*/
|
||||
@Override
|
||||
public String[] list(String path) {
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns an empty set.
|
||||
*/
|
||||
@Override
|
||||
public Set<String> listWebAppPaths(String path) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns false.
|
||||
*/
|
||||
@Override
|
||||
public boolean mkdir(String path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns false.
|
||||
*/
|
||||
@Override
|
||||
public boolean write(String path, InputStream is, boolean overwrite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoot(WebResourceRoot root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getClassLoaderOnly() {
|
||||
return classLoaderOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setClassLoaderOnly(boolean classLoaderOnly) {
|
||||
this.classLoaderOnly = classLoaderOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getStaticOnly() {
|
||||
return staticOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStaticOnly(boolean staticOnly) {
|
||||
this.staticOnly = staticOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns null.
|
||||
*/
|
||||
@Override
|
||||
public URL getBaseUrl() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Calls to this method will be ignored as this implementation always read
|
||||
* only.
|
||||
*/
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* This implementation always returns true.
|
||||
*/
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gc() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initInternal() throws LifecycleException {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startInternal() throws LifecycleException {
|
||||
setState(LifecycleState.STARTING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopInternal() throws LifecycleException {
|
||||
setState(LifecycleState.STOPPING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroyInternal() throws LifecycleException {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
106
java/org/apache/catalina/webresources/ExtractingRoot.java
Normal file
106
java/org/apache/catalina/webresources/ExtractingRoot.java
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.startup.ExpandWar;
|
||||
import org.apache.catalina.util.IOTools;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* If the main resources are packaged as a WAR file then any JARs will be
|
||||
* extracted to the work directory and used from there.
|
||||
*/
|
||||
public class ExtractingRoot extends StandardRoot {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(ExtractingRoot.class);
|
||||
|
||||
private static final String APPLICATION_JARS_DIR = "application-jars";
|
||||
|
||||
@Override
|
||||
protected void processWebInfLib() throws LifecycleException {
|
||||
|
||||
// Don't extract JAR files unless the application is deployed as a
|
||||
// packed WAR file.
|
||||
if (!super.isPackedWarFile()) {
|
||||
super.processWebInfLib();
|
||||
return;
|
||||
}
|
||||
|
||||
File expansionTarget = getExpansionTarget();
|
||||
if (!expansionTarget.isDirectory()) {
|
||||
if (!expansionTarget.mkdirs()) {
|
||||
throw new LifecycleException(
|
||||
sm.getString("extractingRoot.targetFailed", expansionTarget));
|
||||
}
|
||||
}
|
||||
|
||||
WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
|
||||
|
||||
for (WebResource possibleJar : possibleJars) {
|
||||
if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
|
||||
try {
|
||||
File dest = new File(expansionTarget, possibleJar.getName());
|
||||
dest = dest.getCanonicalFile();
|
||||
try (InputStream sourceStream = possibleJar.getInputStream();
|
||||
OutputStream destStream= new FileOutputStream(dest)) {
|
||||
IOTools.flow(sourceStream, destStream);
|
||||
}
|
||||
|
||||
createWebResourceSet(ResourceSetType.CLASSES_JAR,
|
||||
"/WEB-INF/classes", dest.toURI().toURL(), "/");
|
||||
} catch (IOException ioe) {
|
||||
throw new LifecycleException(
|
||||
sm.getString("extractingRoot.jarFailed", possibleJar.getName()), ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File getExpansionTarget() {
|
||||
File tmpDir = (File) getContext().getServletContext().getAttribute(ServletContext.TEMPDIR);
|
||||
File expansionTarget = new File(tmpDir, APPLICATION_JARS_DIR);
|
||||
return expansionTarget;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isPackedWarFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void stopInternal() throws LifecycleException {
|
||||
super.stopInternal();
|
||||
|
||||
if (super.isPackedWarFile()) {
|
||||
// Remove the extracted JARs from the work directory
|
||||
File expansionTarget = getExpansionTarget();
|
||||
ExpandWar.delete(expansionTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
297
java/org/apache/catalina/webresources/FileResource.java
Normal file
297
java/org/apache/catalina/webresources/FileResource.java
Normal file
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Represents a single resource (file or directory) that is located on a file
|
||||
* system.
|
||||
*/
|
||||
public class FileResource extends AbstractResource {
|
||||
|
||||
private static final Log log = LogFactory.getLog(FileResource.class);
|
||||
|
||||
private static final boolean PROPERTIES_NEED_CONVERT;
|
||||
static {
|
||||
boolean isEBCDIC = false;
|
||||
try {
|
||||
String encoding = System.getProperty("file.encoding");
|
||||
if (encoding.contains("EBCDIC")) {
|
||||
isEBCDIC = true;
|
||||
}
|
||||
} catch (SecurityException e) {
|
||||
// Ignore
|
||||
}
|
||||
PROPERTIES_NEED_CONVERT = isEBCDIC;
|
||||
}
|
||||
|
||||
|
||||
private final File resource;
|
||||
private final String name;
|
||||
private final boolean readOnly;
|
||||
private final Manifest manifest;
|
||||
private final boolean needConvert;
|
||||
|
||||
public FileResource(WebResourceRoot root, String webAppPath,
|
||||
File resource, boolean readOnly, Manifest manifest) {
|
||||
super(root,webAppPath);
|
||||
this.resource = resource;
|
||||
|
||||
if (webAppPath.charAt(webAppPath.length() - 1) == '/') {
|
||||
String realName = resource.getName() + '/';
|
||||
if (webAppPath.endsWith(realName)) {
|
||||
name = resource.getName();
|
||||
} else {
|
||||
// This is the root directory of a mounted ResourceSet
|
||||
// Need to return the mounted name, not the real name
|
||||
int endOfName = webAppPath.length() - 1;
|
||||
name = webAppPath.substring(
|
||||
webAppPath.lastIndexOf('/', endOfName - 1) + 1,
|
||||
endOfName);
|
||||
}
|
||||
} else {
|
||||
// Must be a file
|
||||
name = resource.getName();
|
||||
}
|
||||
|
||||
this.readOnly = readOnly;
|
||||
this.manifest = manifest;
|
||||
this.needConvert = PROPERTIES_NEED_CONVERT && name.endsWith(".properties");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return resource.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return resource.exists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return resource.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return resource.isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
if (readOnly) {
|
||||
return false;
|
||||
}
|
||||
return resource.delete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
return getContentLengthInternal(needConvert);
|
||||
}
|
||||
|
||||
private long getContentLengthInternal(boolean convert) {
|
||||
if (convert) {
|
||||
byte[] content = getContent();
|
||||
if (content == null) {
|
||||
return -1;
|
||||
} else {
|
||||
return content.length;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDirectory()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return resource.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() {
|
||||
try {
|
||||
return resource.getCanonicalPath();
|
||||
} catch (IOException ioe) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("fileResource.getCanonicalPathFail",
|
||||
resource.getPath()), ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return resource.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream doGetInputStream() {
|
||||
if (needConvert) {
|
||||
byte[] content = getContent();
|
||||
if (content == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ByteArrayInputStream(content);
|
||||
}
|
||||
}
|
||||
try {
|
||||
return new FileInputStream(resource);
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// Race condition (file has been deleted) - not an error
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final byte[] getContent() {
|
||||
// Use internal version to avoid loop when needConvert is true
|
||||
long len = getContentLengthInternal(false);
|
||||
|
||||
if (len > Integer.MAX_VALUE) {
|
||||
// Can't create an array that big
|
||||
throw new ArrayIndexOutOfBoundsException(sm.getString(
|
||||
"abstractResource.getContentTooLarge", getWebappPath(),
|
||||
Long.valueOf(len)));
|
||||
}
|
||||
|
||||
if (len < 0) {
|
||||
// Content is not applicable here (e.g. is a directory)
|
||||
return null;
|
||||
}
|
||||
|
||||
int size = (int) len;
|
||||
byte[] result = new byte[size];
|
||||
|
||||
int pos = 0;
|
||||
try (InputStream is = new FileInputStream(resource)) {
|
||||
while (pos < size) {
|
||||
int n = is.read(result, pos, size - pos);
|
||||
if (n < 0) {
|
||||
break;
|
||||
}
|
||||
pos += n;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug(sm.getString("abstractResource.getContentFail",
|
||||
getWebappPath()), ioe);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (needConvert) {
|
||||
// Workaround for certain files on platforms that use
|
||||
// EBCDIC encoding, when they are read through FileInputStream.
|
||||
// See commit message of rev.303915 for original details
|
||||
// https://svn.apache.org/viewvc?view=revision&revision=303915
|
||||
String str = new String(result);
|
||||
try {
|
||||
result = str.getBytes(StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getCreation() {
|
||||
try {
|
||||
BasicFileAttributes attrs = Files.readAttributes(resource.toPath(),
|
||||
BasicFileAttributes.class);
|
||||
return attrs.creationTime().toMillis();
|
||||
} catch (IOException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("fileResource.getCreationFail",
|
||||
resource.getPath()), e);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() {
|
||||
if (resource.exists()) {
|
||||
try {
|
||||
return resource.toURI().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("fileResource.getUrlFail",
|
||||
resource.getPath()), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getCodeBase() {
|
||||
if (getWebappPath().startsWith("/WEB-INF/classes/") && name.endsWith(".class")) {
|
||||
return getWebResourceRoot().getResource("/WEB-INF/classes/").getURL();
|
||||
} else {
|
||||
return getURL();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getCertificates() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
176
java/org/apache/catalina/webresources/FileResourceSet.java
Normal file
176
java/org/apache/catalina/webresources/FileResourceSet.java
Normal file
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.catalina.util.ResourceSet;
|
||||
|
||||
/**
|
||||
* Represents a {@link org.apache.catalina.WebResourceSet} based on a single
|
||||
* file.
|
||||
*/
|
||||
public class FileResourceSet extends AbstractFileResourceSet {
|
||||
|
||||
/**
|
||||
* A no argument constructor is required for this to work with the digester.
|
||||
*/
|
||||
public FileResourceSet() {
|
||||
super("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a
|
||||
* file.
|
||||
*
|
||||
* @param root The {@link WebResourceRoot} this new
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be added to.
|
||||
* @param webAppMount The path within the web application at which this
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be mounted. For example, to add a directory of
|
||||
* JARs to a web application, the directory would
|
||||
* be mounted at "WEB-INF/lib/"
|
||||
* @param base The absolute path to the file on the file system
|
||||
* from which the resource will be served.
|
||||
* @param internalPath The path within this new {@link
|
||||
* org.apache.catalina.WebResourceSet} where
|
||||
* resources will be served from.
|
||||
*/
|
||||
public FileResourceSet(WebResourceRoot root, String webAppMount,
|
||||
String base, String internalPath) {
|
||||
super(internalPath);
|
||||
setRoot(root);
|
||||
setWebAppMount(webAppMount);
|
||||
setBase(base);
|
||||
|
||||
if (getRoot().getState().isAvailable()) {
|
||||
try {
|
||||
start();
|
||||
} catch (LifecycleException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebResource getResource(String path) {
|
||||
checkPath(path);
|
||||
|
||||
String webAppMount = getWebAppMount();
|
||||
WebResourceRoot root = getRoot();
|
||||
if (path.equals(webAppMount)) {
|
||||
File f = file("", true);
|
||||
if (f == null) {
|
||||
return new EmptyResource(root, path);
|
||||
}
|
||||
return new FileResource(root, path, f, isReadOnly(), null);
|
||||
}
|
||||
|
||||
if (path.charAt(path.length() - 1) != '/') {
|
||||
path = path + '/';
|
||||
}
|
||||
|
||||
if (webAppMount.startsWith(path)) {
|
||||
String name = path.substring(0, path.length() - 1);
|
||||
name = name.substring(name.lastIndexOf('/') + 1);
|
||||
if (name.length() > 0) {
|
||||
return new VirtualResource(root, path, name);
|
||||
}
|
||||
}
|
||||
return new EmptyResource(root, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(String path) {
|
||||
checkPath(path);
|
||||
|
||||
if (path.charAt(path.length() - 1) != '/') {
|
||||
path = path + '/';
|
||||
}
|
||||
String webAppMount = getWebAppMount();
|
||||
|
||||
if (webAppMount.startsWith(path)) {
|
||||
webAppMount = webAppMount.substring(path.length());
|
||||
if (webAppMount.equals(getFileBase().getName())) {
|
||||
return new String[] {getFileBase().getName()};
|
||||
} else {
|
||||
// Virtual directory
|
||||
int i = webAppMount.indexOf('/');
|
||||
if (i > 0) {
|
||||
return new String[] {webAppMount.substring(0, i)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EMPTY_STRING_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> listWebAppPaths(String path) {
|
||||
checkPath(path);
|
||||
|
||||
ResourceSet<String> result = new ResourceSet<>();
|
||||
|
||||
if (path.charAt(path.length() - 1) != '/') {
|
||||
path = path + '/';
|
||||
}
|
||||
String webAppMount = getWebAppMount();
|
||||
|
||||
if (webAppMount.startsWith(path)) {
|
||||
webAppMount = webAppMount.substring(path.length());
|
||||
if (webAppMount.equals(getFileBase().getName())) {
|
||||
result.add(path + getFileBase().getName());
|
||||
} else {
|
||||
// Virtual directory
|
||||
int i = webAppMount.indexOf('/');
|
||||
if (i > 0) {
|
||||
result.add(path + webAppMount.substring(0, i + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.setLocked(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String path) {
|
||||
checkPath(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(String path, InputStream is, boolean overwrite) {
|
||||
checkPath(path);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkType(File file) {
|
||||
if (file.isFile() == false) {
|
||||
throw new IllegalArgumentException(sm.getString("fileResourceSet.notFile",
|
||||
getBase(), File.separator, getInternalPath()));
|
||||
}
|
||||
}
|
||||
}
|
||||
43
java/org/apache/catalina/webresources/JarResource.java
Normal file
43
java/org/apache/catalina/webresources/JarResource.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.util.jar.JarEntry;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Represents a single resource (file or directory) that is located within a
|
||||
* JAR.
|
||||
*/
|
||||
public class JarResource extends AbstractSingleArchiveResource {
|
||||
|
||||
private static final Log log = LogFactory.getLog(JarResource.class);
|
||||
|
||||
|
||||
public JarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
|
||||
String baseUrl, JarEntry jarEntry) {
|
||||
super(archiveResourceSet, webAppPath, "jar:" + baseUrl + "!/", jarEntry, baseUrl);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
161
java/org/apache/catalina/webresources/JarResourceRoot.java
Normal file
161
java/org/apache/catalina/webresources/JarResourceRoot.java
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.security.cert.Certificate;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
|
||||
public class JarResourceRoot extends AbstractResource {
|
||||
|
||||
private static final Log log = LogFactory.getLog(JarResourceRoot.class);
|
||||
|
||||
private final File base;
|
||||
private final String baseUrl;
|
||||
private final String name;
|
||||
|
||||
public JarResourceRoot(WebResourceRoot root, File base, String baseUrl,
|
||||
String webAppPath) {
|
||||
super(root, webAppPath);
|
||||
// Validate the webAppPath before going any further
|
||||
if (!webAppPath.endsWith("/")) {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"jarResourceRoot.invalidWebAppPath", webAppPath));
|
||||
}
|
||||
this.base = base;
|
||||
this.baseUrl = "jar:" + baseUrl;
|
||||
// Extract the name from the webAppPath
|
||||
// Strip the trailing '/' character
|
||||
String resourceName = webAppPath.substring(0, webAppPath.length() - 1);
|
||||
int i = resourceName.lastIndexOf('/');
|
||||
if (i > -1) {
|
||||
resourceName = resourceName.substring(i + 1);
|
||||
}
|
||||
name = resourceName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return base.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getContentLength() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InputStream doGetInputStream() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getContent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreation() {
|
||||
return base.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getURL() {
|
||||
String url = baseUrl + "!/";
|
||||
try {
|
||||
return new URL(url);
|
||||
} catch (MalformedURLException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("fileResource.getUrlFail", url), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getCodeBase() {
|
||||
try {
|
||||
return new URL(baseUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
if (getLog().isDebugEnabled()) {
|
||||
getLog().debug(sm.getString("fileResource.getUrlFail", baseUrl), e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate[] getCertificates() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Manifest getManifest() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
68
java/org/apache/catalina/webresources/JarResourceSet.java
Normal file
68
java/org/apache/catalina/webresources/JarResourceSet.java
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
|
||||
/**
|
||||
* Represents a {@link org.apache.catalina.WebResourceSet} based on a JAR file.
|
||||
*/
|
||||
public class JarResourceSet extends AbstractSingleArchiveResourceSet {
|
||||
|
||||
/**
|
||||
* A no argument constructor is required for this to work with the digester.
|
||||
*/
|
||||
public JarResourceSet() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a JAR
|
||||
* file.
|
||||
*
|
||||
* @param root The {@link WebResourceRoot} this new
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be added to.
|
||||
* @param webAppMount The path within the web application at which this
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be mounted.
|
||||
* @param base The absolute path to the JAR file on the file system
|
||||
* from which the resources will be served.
|
||||
* @param internalPath The path within this new {@link
|
||||
* org.apache.catalina.WebResourceSet} where
|
||||
* resources will be served from. E.g. for a
|
||||
* resource JAR, this would be "META-INF/resources"
|
||||
*
|
||||
* @throws IllegalArgumentException if the webAppMount or internalPath is
|
||||
* not valid (valid paths must start with '/')
|
||||
*/
|
||||
public JarResourceSet(WebResourceRoot root, String webAppMount, String base,
|
||||
String internalPath) throws IllegalArgumentException {
|
||||
super(root, webAppMount, base, internalPath);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected WebResource createArchiveResource(JarEntry jarEntry,
|
||||
String webAppPath, Manifest manifest) {
|
||||
return new JarResource(this, webAppPath, getBaseUrlString(), jarEntry);
|
||||
}
|
||||
}
|
||||
98
java/org/apache/catalina/webresources/JarWarResource.java
Normal file
98
java/org/apache/catalina/webresources/JarWarResource.java
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarInputStream;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.buf.UriUtil;
|
||||
|
||||
/**
|
||||
* Represents a single resource (file or directory) that is located within a
|
||||
* JAR that in turn is located in a WAR file.
|
||||
*/
|
||||
public class JarWarResource extends AbstractArchiveResource {
|
||||
|
||||
private static final Log log = LogFactory.getLog(JarWarResource.class);
|
||||
|
||||
private final String archivePath;
|
||||
|
||||
public JarWarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
|
||||
String baseUrl, JarEntry jarEntry, String archivePath) {
|
||||
|
||||
super(archiveResourceSet, webAppPath,
|
||||
"jar:war:" + baseUrl + UriUtil.getWarSeparator() + archivePath + "!/",
|
||||
jarEntry, "war:" + baseUrl + UriUtil.getWarSeparator() + archivePath);
|
||||
this.archivePath = archivePath;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JarInputStreamWrapper getJarInputStreamWrapper() {
|
||||
JarFile warFile = null;
|
||||
JarInputStream jarIs = null;
|
||||
JarEntry entry = null;
|
||||
try {
|
||||
warFile = getArchiveResourceSet().openJarFile();
|
||||
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
|
||||
InputStream isInWar = warFile.getInputStream(jarFileInWar);
|
||||
|
||||
jarIs = new JarInputStream(isInWar);
|
||||
entry = jarIs.getNextJarEntry();
|
||||
while (entry != null &&
|
||||
!entry.getName().equals(getResource().getName())) {
|
||||
entry = jarIs.getNextJarEntry();
|
||||
}
|
||||
|
||||
if (entry == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new JarInputStreamWrapper(entry, jarIs);
|
||||
} catch (IOException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug(sm.getString("jarResource.getInputStreamFail",
|
||||
getResource().getName(), getBaseUrl()), e);
|
||||
}
|
||||
// Ensure jarIs is closed if there is an exception
|
||||
entry = null;
|
||||
return null;
|
||||
} finally {
|
||||
if (entry == null) {
|
||||
if (jarIs != null) {
|
||||
try {
|
||||
jarIs.close();
|
||||
} catch (IOException ioe) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
if (warFile != null) {
|
||||
getArchiveResourceSet().closeJarFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
279
java/org/apache/catalina/webresources/JarWarResourceSet.java
Normal file
279
java/org/apache/catalina/webresources/JarWarResourceSet.java
Normal file
@@ -0,0 +1,279 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.tomcat.util.buf.UriUtil;
|
||||
import org.apache.tomcat.util.compat.JreCompat;
|
||||
|
||||
/**
|
||||
* Represents a {@link org.apache.catalina.WebResourceSet} based on a JAR file
|
||||
* that is nested inside a packed WAR file. This is only intended for internal
|
||||
* use within Tomcat and therefore cannot be created via configuration.
|
||||
*/
|
||||
public class JarWarResourceSet extends AbstractArchiveResourceSet {
|
||||
|
||||
private final String archivePath;
|
||||
|
||||
/**
|
||||
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a JAR
|
||||
* file that is nested inside a WAR.
|
||||
*
|
||||
* @param root The {@link WebResourceRoot} this new
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be added to.
|
||||
* @param webAppMount The path within the web application at which this
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be mounted.
|
||||
* @param base The absolute path to the WAR file on the file system
|
||||
* in which the JAR is located.
|
||||
* @param archivePath The path within the WAR file where the JAR file is
|
||||
* located.
|
||||
* @param internalPath The path within this new {@link
|
||||
* org.apache.catalina.WebResourceSet} where
|
||||
* resources will be served from. E.g. for a
|
||||
* resource JAR, this would be "META-INF/resources"
|
||||
*
|
||||
* @throws IllegalArgumentException if the webAppMount or internalPath is
|
||||
* not valid (valid paths must start with '/')
|
||||
*/
|
||||
public JarWarResourceSet(WebResourceRoot root, String webAppMount,
|
||||
String base, String archivePath, String internalPath)
|
||||
throws IllegalArgumentException {
|
||||
setRoot(root);
|
||||
setWebAppMount(webAppMount);
|
||||
setBase(base);
|
||||
this.archivePath = archivePath;
|
||||
setInternalPath(internalPath);
|
||||
|
||||
if (getRoot().getState().isAvailable()) {
|
||||
try {
|
||||
start();
|
||||
} catch (LifecycleException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebResource createArchiveResource(JarEntry jarEntry,
|
||||
String webAppPath, Manifest manifest) {
|
||||
return new JarWarResource(this, webAppPath, getBaseUrlString(), jarEntry, archivePath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* JarWar can't optimise for a single resource so the Map is always
|
||||
* returned.
|
||||
*/
|
||||
@Override
|
||||
protected HashMap<String,JarEntry> getArchiveEntries(boolean single) {
|
||||
synchronized (archiveLock) {
|
||||
if (archiveEntries == null) {
|
||||
JarFile warFile = null;
|
||||
InputStream jarFileIs = null;
|
||||
archiveEntries = new HashMap<>();
|
||||
boolean multiRelease = false;
|
||||
try {
|
||||
warFile = openJarFile();
|
||||
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
|
||||
jarFileIs = warFile.getInputStream(jarFileInWar);
|
||||
|
||||
try (TomcatJarInputStream jarIs = new TomcatJarInputStream(jarFileIs)) {
|
||||
JarEntry entry = jarIs.getNextJarEntry();
|
||||
while (entry != null) {
|
||||
archiveEntries.put(entry.getName(), entry);
|
||||
entry = jarIs.getNextJarEntry();
|
||||
}
|
||||
Manifest m = jarIs.getManifest();
|
||||
setManifest(m);
|
||||
if (m != null && JreCompat.isJre9Available()) {
|
||||
String value = m.getMainAttributes().getValue("Multi-Release");
|
||||
if (value != null) {
|
||||
multiRelease = Boolean.parseBoolean(value);
|
||||
}
|
||||
}
|
||||
// Hack to work-around JarInputStream swallowing these
|
||||
// entries. TomcatJarInputStream is used above which
|
||||
// extends JarInputStream and the method that creates
|
||||
// the entries over-ridden so we can a) tell if the
|
||||
// entries are present and b) cache them so we can
|
||||
// access them here.
|
||||
entry = jarIs.getMetaInfEntry();
|
||||
if (entry != null) {
|
||||
archiveEntries.put(entry.getName(), entry);
|
||||
}
|
||||
entry = jarIs.getManifestEntry();
|
||||
if (entry != null) {
|
||||
archiveEntries.put(entry.getName(), entry);
|
||||
}
|
||||
}
|
||||
if (multiRelease) {
|
||||
processArchivesEntriesForMultiRelease();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Should never happen
|
||||
archiveEntries = null;
|
||||
throw new IllegalStateException(ioe);
|
||||
} finally {
|
||||
if (warFile != null) {
|
||||
closeJarFile();
|
||||
}
|
||||
if (jarFileIs != null) {
|
||||
try {
|
||||
jarFileIs.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return archiveEntries;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void processArchivesEntriesForMultiRelease() {
|
||||
|
||||
int targetVersion = JreCompat.getInstance().jarFileRuntimeMajorVersion();
|
||||
|
||||
Map<String,VersionedJarEntry> versionedEntries = new HashMap<>();
|
||||
Iterator<Entry<String,JarEntry>> iter = archiveEntries.entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Entry<String,JarEntry> entry = iter.next();
|
||||
String name = entry.getKey();
|
||||
if (name.startsWith("META-INF/versions/")) {
|
||||
// Remove the multi-release version
|
||||
iter.remove();
|
||||
|
||||
// Get the base name and version for this versioned entry
|
||||
int i = name.indexOf('/', 18);
|
||||
if (i > 0) {
|
||||
String baseName = name.substring(i + 1);
|
||||
int version = Integer.parseInt(name.substring(18, i));
|
||||
|
||||
// Ignore any entries targeting for a later version than
|
||||
// the target for this runtime
|
||||
if (version <= targetVersion) {
|
||||
VersionedJarEntry versionedJarEntry = versionedEntries.get(baseName);
|
||||
if (versionedJarEntry == null) {
|
||||
// No versioned entry found for this name. Create
|
||||
// one.
|
||||
versionedEntries.put(baseName,
|
||||
new VersionedJarEntry(version, entry.getValue()));
|
||||
} else {
|
||||
// Ignore any entry for which we have already found
|
||||
// a later version
|
||||
if (version > versionedJarEntry.getVersion()) {
|
||||
// Replace the entry targeted at an earlier
|
||||
// version
|
||||
versionedEntries.put(baseName,
|
||||
new VersionedJarEntry(version, entry.getValue()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Entry<String,VersionedJarEntry> versionedJarEntry : versionedEntries.entrySet()) {
|
||||
archiveEntries.put(versionedJarEntry.getKey(),
|
||||
versionedJarEntry.getValue().getJarEntry());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>
|
||||
* Should never be called since {@link #getArchiveEntries(boolean)} always
|
||||
* returns a Map.
|
||||
*/
|
||||
@Override
|
||||
protected JarEntry getArchiveEntry(String pathInArchive) {
|
||||
throw new IllegalStateException(sm.getString("jarWarResourceSet.codingError"));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean isMultiRelease() {
|
||||
// This always returns false otherwise the superclass will call
|
||||
// #getArchiveEntry(String)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------- Lifecycle methods
|
||||
@Override
|
||||
protected void initInternal() throws LifecycleException {
|
||||
|
||||
try (JarFile warFile = new JarFile(getBase())) {
|
||||
JarEntry jarFileInWar = warFile.getJarEntry(archivePath);
|
||||
InputStream jarFileIs = warFile.getInputStream(jarFileInWar);
|
||||
|
||||
try (JarInputStream jarIs = new JarInputStream(jarFileIs)) {
|
||||
setManifest(jarIs.getManifest());
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
throw new IllegalArgumentException(ioe);
|
||||
}
|
||||
|
||||
try {
|
||||
setBaseUrl(UriUtil.buildJarSafeUrl(new File(getBase())));
|
||||
} catch (MalformedURLException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class VersionedJarEntry {
|
||||
private final int version;
|
||||
private final JarEntry jarEntry;
|
||||
|
||||
public VersionedJarEntry(int version, JarEntry jarEntry) {
|
||||
this.version = version;
|
||||
this.jarEntry = jarEntry;
|
||||
}
|
||||
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
|
||||
public JarEntry getJarEntry() {
|
||||
return jarEntry;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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.
|
||||
|
||||
abstractArchiveResourceSet.setReadOnlyFalse=Archive based WebResourceSets such as those based on JARs are hard-coded to be read-only and may not be configured to be read-write
|
||||
|
||||
abstractResource.getContentFail=Unable to return [{0}] as a byte array
|
||||
abstractResource.getContentTooLarge=Unable to return [{0}] as a byte array since the resource is [{1}] bytes in size which is larger than the maximum size of a byte array
|
||||
|
||||
abstractResourceSet.checkPath=The requested path [{0}] is not valid. It must begin with "/".
|
||||
|
||||
cache.addFail=Unable to add the resource at [{0}] to the cache for web application [{1}] because there was insufficient free space available after evicting expired cache entries - consider increasing the maximum size of the cache
|
||||
cache.backgroundEvictFail=The background cache eviction process was unable to free [{0}] percent of the cache for Context [{1}] - consider increasing the maximum size of the cache. After eviction approximately [{2}] KB of data remained in the cache.
|
||||
cache.objectMaxSizeTooBig=The value of [{0}]kB for objectMaxSize is larger than the limit of maxSize/20 so has been reduced to [{1}]kB
|
||||
cache.objectMaxSizeTooBigBytes=The value specified for the maximum object size to cache [{0}]kB is greater than Integer.MAX_VALUE bytes which is the maximum size that can be cached. The limit will be set to Integer.MAX_VALUE bytes.
|
||||
|
||||
cachedResource.invalidURL=Unable to create an instance of CachedResourceURLStreamHandler because the URL [{0}] is malformed
|
||||
|
||||
classpathUrlStreamHandler.notFound=Unable to load the resource [{0}] using the thread context class loader or the current class''s class loader
|
||||
|
||||
dirResourceSet.manifestFail=Failed to read manifest from [{0}]
|
||||
dirResourceSet.notDirectory=The directory specified by base and internal path [{0}]{1}[{2}] does not exist.
|
||||
dirResourceSet.writeNpe=The input stream may not be null
|
||||
|
||||
extractingRoot.jarFailed=Failed to extract the JAR file [{0}]
|
||||
extractingRoot.targetFailed=Failed to create the directory [{0}] for extracted JAR files
|
||||
|
||||
fileResource.getCanonicalPathFail=Unable to determine the canonical path for the resource [{0}]
|
||||
fileResource.getCreationFail=Unable to determine the creation time for the resource [{0}]
|
||||
fileResource.getUrlFail=Unable to determine a URL for the resource [{0}]
|
||||
|
||||
fileResourceSet.notFile=The file specified by base and internal path [{0}]{1}[{2}] does not exist.
|
||||
|
||||
jarResource.getInputStreamFail=Unable to obtain an InputStream for the resource [{0}] located in the JAR [{1}]
|
||||
|
||||
jarResourceRoot.invalidWebAppPath=This resource always refers to a directory so the supplied webAppPath must end with / but the provided webAppPath was [{0}]
|
||||
|
||||
jarWarResourceSet.codingError=Coding error
|
||||
|
||||
standardRoot.checkStateNotStarted=The resources may not be accessed if they are not currently started
|
||||
standardRoot.createInvalidFile=Unable to create WebResourceSet from [{0}]
|
||||
standardRoot.createUnknownType=Unable to create WebResourceSet of unknown type [{0}]
|
||||
standardRoot.invalidPath=The resource path [{0}] is not valid
|
||||
standardRoot.invalidPathNormal=The resource path [{0}] has been normalized to [{1}] which is not valid
|
||||
standardRoot.lockedFile=The web application [{0}] failed to close the file [{1}] opened via the following stack trace
|
||||
standardRoot.noContext=A Context has not been configured for this WebResourceRoot
|
||||
standardRoot.startInvalidMain=The main resource set specified [{0}] is not valid
|
||||
standardRoot.unsupportedProtocol=The URL protocol [{0}] is not supported by this web resources implementation
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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.
|
||||
|
||||
extractingRoot.targetFailed=Konnte Verzeichnis [{0}] zum entpacken einer JAR-Datei nicht anlegen
|
||||
@@ -0,0 +1,22 @@
|
||||
# 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.
|
||||
|
||||
cache.addFail=Imposible adicionar recursos a [{0}] de la cache para la applicación web [{1}] porque no hay suficiente espacio libre luego de eliminar los datos expirados de la caché - considere incrementar el tamaño máximo de la chaché
|
||||
|
||||
dirResourceSet.notDirectory=El directorio especificado por la base y el camino interno [{0}]{1}[{2}] no existe.\n
|
||||
|
||||
extractingRoot.targetFailed=Fallo al crear directorio [{0}] para los archivos JAR extraidos
|
||||
|
||||
standardRoot.createUnknownType=Imposible crear WebResourceSet de tipo desconocido [{0}]\n
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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.
|
||||
|
||||
abstractArchiveResourceSet.setReadOnlyFalse=Les archives basées sur WebResourceSets telles que celles des JARs sont fixées comme étant en lecture seule et ne peuvent être configurées en lecture écriture
|
||||
|
||||
abstractResource.getContentFail=Impossible de retourner [{0}] en tant que tableau d''octets
|
||||
abstractResource.getContentTooLarge=Impossible de retourner [{0}] comme tableau d''octets car la ressource a une taille de [{1}] octets qui est supérieure à la taille maximale d''un tableau d''octets
|
||||
|
||||
abstractResourceSet.checkPath=Le chemin demandé [{0}] n''est pas valide, il doit commencer par ''/''
|
||||
|
||||
cache.addFail=Incapable d''ajouter la ressource située [{0}] au cache de l''application web [{1}] parce qu''il n''y avait pas assez d''espace libre disponible après l''éviction des entrées de cache expirées - envisagez d''augmenter la taille maximale du cache
|
||||
cache.backgroundEvictFail=Le processus d''arrière plan d''éviction du cache n''a pas pu nettoyer [{0}] pourcents du cache pour le contexte [{1}], il faudrait augmenter la taille maximale du cache; après l''éviction, approximativement [{2}] KO de données restaient dans le cache
|
||||
cache.objectMaxSizeTooBig=La valeur [{0}]kB pour l''objectMaxSize est plus grade que la limite de maxSize/20 son elle a été réduite à [{1}]kB\n
|
||||
cache.objectMaxSizeTooBigBytes=La valeur de taille d''objet maximale pouvant être mis en cache de [{0}]kB est supérieure à Integer.MAX_VALUE qui est le maximum, la limite a donc été fixée à Integer.MAX_VALUE octets
|
||||
|
||||
classpathUrlStreamHandler.notFound=Impossible de charger la ressource [{0}] en utilisant le chargeur de classe de contexte du thread ou celui de la classe actuelle
|
||||
|
||||
dirResourceSet.manifestFail=Impossible de lire le manifeste depuis [{0}]
|
||||
dirResourceSet.notDirectory=Le répertoire qui a été spécifié pour la base et le chemin interne [{0}]{1}[{2}] n''existe pas
|
||||
dirResourceSet.writeNpe=Le flux d'entrée ne peut pas être null
|
||||
|
||||
extractingRoot.jarFailed=Echec de l’extraction du fichier JAR [{0}]
|
||||
extractingRoot.targetFailed=Echec de la création du répertoire [{0}] pour l''extraction des fichiers contenus dans le JAR
|
||||
|
||||
fileResource.getCanonicalPathFail=Impossible de déterminer le chemin canonique pour la ressource [{0}]
|
||||
fileResource.getCreationFail=Impossible de déterminer la date de création de la ressource [{0}]
|
||||
fileResource.getUrlFail=Impossible de déterminer l''URL pour la ressource [{0}]
|
||||
|
||||
fileResourceSet.notFile=Le fichier spécifié par ses chemins de base et internes [{0}]{1}[{2}] n''existe pas
|
||||
|
||||
jarResource.getInputStreamFail=Impossible d''obtenir une InputStream pour la ressource [{0}] située dans le JAR [{1}]
|
||||
|
||||
jarResourceRoot.invalidWebAppPath=Cette ressource se réfère toujours à un répertoire donc le webAppPath fourni doit se terminer avec ''/'' mais il était [{0}]
|
||||
|
||||
jarWarResourceSet.codingError=Erreur de programmation
|
||||
|
||||
standardRoot.checkStateNotStarted=Les resources ne peuvent pas être accédées tant qu'elles ne sont pas démarrées
|
||||
standardRoot.createInvalidFile=Impossible de créer WebResourceSet à partir de [{0}]
|
||||
standardRoot.createUnknownType=Impossible de créer un WebResourceSet pour le type inconnu [{0}]
|
||||
standardRoot.invalidPath=Le chemin de ressources [{0}] est invalide
|
||||
standardRoot.invalidPathNormal=Le chemin de ressource [{0}] a été normalisé en [{1}] ce qui est invalide
|
||||
standardRoot.lockedFile=L''application web [{0}] n''a pas fermé le fichier [{1}] ouvert à partir de la trace
|
||||
standardRoot.noContext=Un contexte n'a pas été configuré pour ce WebResourceRoot
|
||||
standardRoot.startInvalidMain=L''ensemble de ressources principal [{0}] est invalide
|
||||
standardRoot.unsupportedProtocol=Le protocole [{0}] de l''URL n''est pas supporté par cette implémentation des ressources web
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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.
|
||||
|
||||
abstractArchiveResourceSet.setReadOnlyFalse=JARに基づくWebResourceSetなどのアーカイブベースのWebResourceSetは、読み取り専用にハードコードされており、読み取り/書き込み可能に構成されていない可能性があります。
|
||||
|
||||
abstractResource.getContentFail=[{0}]をバイト配列として返すことができません。
|
||||
abstractResource.getContentTooLarge=リソースがバイト配列の最大サイズよりも大きいサイズの[{1}]バイトであるため、[{0}]をバイト配列として返すことができません。
|
||||
|
||||
abstractResourceSet.checkPath=リクエストパス[{0}]が無効です。 "/"で始まる必要があります。
|
||||
|
||||
cache.addFail=有効期限切れの項目を破棄しても利用可能な領域が不足するため、Web アプリケーション [{1}] のキャッシュにリソース [{0}] を追加できません。最大キャッシュサイズの増加を検討してください。
|
||||
cache.backgroundEvictFail=コンテキスト [{1}] のバックグラウンドキャッシュ削除処理は全体の [{0}] % を解放できませんでした。キャッシュサイズの最大値の増加を検討してください。現在は約 [{2}] kB のデータがキャッシュに残存しています。
|
||||
cache.objectMaxSizeTooBig=objectMaxSizeの[{0}] kBの値がmaxSize / 20の制限より大きいため、[{1}] kBに減少しました。
|
||||
cache.objectMaxSizeTooBigBytes=キャッシュ可能なオブジェクトサイズの最大値に指定された [{0}]kB は Integer.MAX_VALUE バイトを越えています。最大値に Integer.MAX_VALUE を設定します。
|
||||
|
||||
classpathUrlStreamHandler.notFound=スレッドコンテキストクラスローダー、あるいは、現在のクラスのクラスローダーでリソース [{0}] を読み込みできません。
|
||||
|
||||
dirResourceSet.manifestFail=[{0}]からマニフェストを読み込めませんでした。
|
||||
dirResourceSet.notDirectory=ベースパスと内部パスで指定した [{0}][{1}][{2}] にディレクトリがありません。
|
||||
dirResourceSet.writeNpe=入力ストリームには null を指定できません。
|
||||
|
||||
extractingRoot.jarFailed=JARファイル[{0}]の抽出に失敗しました
|
||||
extractingRoot.targetFailed=JAR ファイルを展開するためのディレクトリ [{0}] を作成できません。
|
||||
|
||||
fileResource.getCanonicalPathFail=リソース [{0}] の正規化パスを取得できません。
|
||||
fileResource.getCreationFail=リソース[{0}]の作成時間を特定できません。
|
||||
fileResource.getUrlFail=リソース [{0}] の URL を取得できません。
|
||||
|
||||
fileResourceSet.notFile=基本パスおよび内部パスで指定されたファイル [{0}]{1}[{2}] がありません。
|
||||
|
||||
jarResource.getInputStreamFail=JAR ファイル [{1}] のリソース [{0}] の入力ストリームを取得できません。
|
||||
|
||||
jarResourceRoot.invalidWebAppPath=このリソースは常にディレクトリを参照するため、指定されたwebAppPathは/で終了する必要がありますが、指定されたwebAppPathは[{0}]です。
|
||||
|
||||
jarWarResourceSet.codingError=コーディングエラー
|
||||
|
||||
standardRoot.checkStateNotStarted=リソースは、現在起動されていない場合はアクセスできない場合があります
|
||||
standardRoot.createInvalidFile=[{0}]からWebResourceSetを作成できません。
|
||||
standardRoot.createUnknownType=未知のクラス [{0}] の WebResourceSet を作成できません。
|
||||
standardRoot.invalidPath=不正なリソースパス [{0}]
|
||||
standardRoot.invalidPathNormal=リソースパス[{0}]は有効ではない[{1}]に正規化されています。
|
||||
standardRoot.lockedFile=Webアプリケーション[{0}]は、次のスタックトレースによって開かれたファイル[{1}]を閉じることに失敗しました。
|
||||
standardRoot.noContext=この WebResourceRoot にはContext が構成されていません。
|
||||
standardRoot.startInvalidMain=指定された主リソースセット[{0}]は無効です。
|
||||
standardRoot.unsupportedProtocol=URLプロトコル[{0}]はこのWebリソース実装ではサポートされていません。
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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.
|
||||
|
||||
abstractArchiveResourceSet.setReadOnlyFalse=JAR 파일들에 기반한 것들과 같은 아카이브 기반 WebResourceSet들은, 읽기 전용으로 하드코드되어 있으며, 읽기 및 쓰기 용으로 설정될 수 없습니다.
|
||||
|
||||
abstractResource.getContentFail=[{0}]을(를) 바이트 배열로 반환할 수 없습니다.
|
||||
abstractResource.getContentTooLarge=리소스의 크기가 [{1}] 바이트로서, 이는 바이트 배열 최대 크기보다 크기 때문에, [{0}]을(를) 바이트 배열로서 반환할 수 없습니다.
|
||||
|
||||
abstractResourceSet.checkPath=요청된 경로 [{0}]은(는) 유효하지 않습니다. 반드시 "/"로 시작해야 합니다.
|
||||
|
||||
cache.addFail=[{0}]에 위치한 리소스를 웹 애플리케이션 [{1}]을(를) 위한 캐시에 추가할 수 없습니다. 왜냐하면 만료된 캐시 엔트리들을 없애버린 이후에도 여유 공간이 충분하지 않기 때문입니다. 캐시의 최대 크기를 증가시키는 것을 고려해 보십시오.
|
||||
cache.backgroundEvictFail=백그라운드 캐시 퇴거 (cache eviction) 프로세스가, 컨텍스트 [{1}]을(를) 위한 캐시의 [{0}] 퍼센트를 해제시킬 수 없었습니다. 캐시의 최대 크기를 증가시킬 것을 고려해 보십시오. 캐시 퇴거 작업 이후, 대략 [{2}] KB의 데이터가 캐시에 남아 있습니다.
|
||||
cache.objectMaxSizeTooBig=objectMaxSize를 위한 값 [{0}]kB이, maxSize/20인 최대한계값 보다 커서, [{1}]kB로 줄여졌습니다.
|
||||
cache.objectMaxSizeTooBigBytes=[{0}]kB를 캐시하기 위해, 최대 객체 크기로서 지정된 값이 Integer.MAX_VALUE 바이트보다 큰데, Integer.MAX_VALUE는 캐시될 수 있는 최대 크기입니다. 한계 값을 Integer.MAX_VALUE 바이트로 설정하겠습니다.
|
||||
|
||||
classpathUrlStreamHandler.notFound=쓰레드 컨텍스트 클래스로더 또는 현재 클래스의 클래스로더를 사용하여, 리소스 [{0}]을(를) 로드할 수 없습니다.
|
||||
|
||||
dirResourceSet.manifestFail=[{0}](으)로부터 manifest를 읽지 못했습니다.
|
||||
dirResourceSet.notDirectory=base와 internal path [{0}]{1}[{2}](으)로 지정된 디렉토리가 존재하지 않습니다.
|
||||
dirResourceSet.writeNpe=입력 스트림이 널일 수는 없습니다.
|
||||
|
||||
extractingRoot.jarFailed=JAR 파일 [{0}]을(를) 추출하지 못했습니다.
|
||||
extractingRoot.targetFailed=JAR 파일들의 압축을 풀기 위한 디렉토리 [{0}]을(를) 생성할 수 없습니다.
|
||||
|
||||
fileResource.getCanonicalPathFail=리소스 [{0}]에 대한 canonical 경로를 결정할 수 없습니다.
|
||||
fileResource.getCreationFail=리소스 [{0}]의 생성 시간을 결정할 수 없습니다.
|
||||
fileResource.getUrlFail=리소스 [{0}]을(를) 위한 URL을 결정할 수 없습니다.
|
||||
|
||||
fileResourceSet.notFile=base와 내부 경로 [{0}]{1}[{2}]에 의해 지정된 파일이 존재하지 않습니다.
|
||||
|
||||
jarResource.getInputStreamFail=JAR [{1}] 내의 리소스 [{0}]을(를) 위한 InputStream을 얻을 수 없습니다.
|
||||
|
||||
jarResourceRoot.invalidWebAppPath=이 리소스는 언제나 디렉토리를 가리켜서, 제공된 webAppPath가 반드시 ''/'' 로 끝나야 하지만, 제공된 webAppPath는 [{0}]이었습니다.
|
||||
|
||||
jarWarResourceSet.codingError=코딩 오류
|
||||
|
||||
standardRoot.checkStateNotStarted=현재 시작되어 있는 상태가 아니라면, 리소스들은 접근될 수 없습니다.
|
||||
standardRoot.createInvalidFile=[{0}](으)로부터 WebResourceSet을 생성할 수 없습니다.
|
||||
standardRoot.createUnknownType=알 수 없는 타입 [{0}]의 WebResourceSet을 생성할 수 없습니다.
|
||||
standardRoot.invalidPath=리소스 경로 [{0}]은(는) 유효하지 않습니다.
|
||||
standardRoot.invalidPathNormal=리소스 경로 [{0}]이(가) [{1}](으)로 정규화되어 있는데, 이는 유효하지 않습니다.
|
||||
standardRoot.lockedFile=웹 애플리케이션 [{0}]이(가) 파일 [{1}]을(를) 닫지 못했습니다. 해당 파일은 다음과 같은 스택 트레이스 내에서 열렸었습니다.
|
||||
standardRoot.noContext=컨텍스트가 이 WebResourceRoot를 위해 설정되지 않았습니다.
|
||||
standardRoot.startInvalidMain=지정된 주요 리소스셋 [{0}]은(는) 유효하지 않습니다.
|
||||
standardRoot.unsupportedProtocol=URL 프로토콜 [{0}]은(는), 이 웹 리소스 구현에 의해 지원되지 않습니다.
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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.
|
||||
|
||||
abstractArchiveResourceSet.setReadOnlyFalse=基于存档的WebResourceSets 如基于jar的WebResourceSets 硬编码为只读,并且不能配置为读写
|
||||
|
||||
cache.addFail=无法将位于[{0}]的资源添加到Web应用程序[{1}]的缓存中,因为在清除过期缓存条目后可用空间仍不足 - 请考虑增加缓存的最大空间。
|
||||
|
||||
dirResourceSet.notDirectory=基本和内部路径[{0}] {1} [{2}]指定的目录不存在。
|
||||
|
||||
extractingRoot.jarFailed=解压JAR文件[{0}]失败
|
||||
extractingRoot.targetFailed=无法为提取的 JAR 文件创建目录 [{0}]
|
||||
|
||||
fileResource.getCanonicalPathFail=不能判断资源的标准路径[{0}]
|
||||
fileResource.getUrlFail=不能决定一个url 为资源[{0}]
|
||||
|
||||
jarResource.getInputStreamFail=无法获取JAR[{1}]中的资源文件[{0}]的一个InputStream
|
||||
|
||||
standardRoot.checkStateNotStarted=如果当前未启动资源,则可能无法访问这些资源
|
||||
standardRoot.createUnknownType=无法为未知类型[{0}]创建WebResourceSet。
|
||||
standardRoot.invalidPathNormal=资源路径[{0}]已规范化为无效的[{1}]
|
||||
standardRoot.lockedFile=Web应用程序[{0}]无法关闭通过以下堆栈跟踪打开的文件[{1}]
|
||||
standardRoot.noContext=尚未为WebResourceRoot配置上下文
|
||||
standardRoot.startInvalidMain=指定的主资源集 [{0}] 无效
|
||||
864
java/org/apache/catalina/webresources/StandardRoot.java
Normal file
864
java/org/apache/catalina/webresources/StandardRoot.java
Normal file
@@ -0,0 +1,864 @@
|
||||
/*
|
||||
* 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.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.management.ObjectName;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Host;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.LifecycleState;
|
||||
import org.apache.catalina.TrackedWebResource;
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
import org.apache.catalina.WebResourceSet;
|
||||
import org.apache.catalina.util.LifecycleMBeanBase;
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.buf.UriUtil;
|
||||
import org.apache.tomcat.util.http.RequestUtil;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides the resources implementation for a web application. The
|
||||
* {@link org.apache.catalina.Lifecycle} of this class should be aligned with
|
||||
* that of the associated {@link Context}.
|
||||
* </p><p>
|
||||
* This implementation assumes that the base attribute supplied to {@link
|
||||
* StandardRoot#createWebResourceSet(
|
||||
* org.apache.catalina.WebResourceRoot.ResourceSetType, String, String, String,
|
||||
* String)} represents the absolute path to a file.
|
||||
* </p>
|
||||
*/
|
||||
public class StandardRoot extends LifecycleMBeanBase implements WebResourceRoot {
|
||||
|
||||
private static final Log log = LogFactory.getLog(StandardRoot.class);
|
||||
protected static final StringManager sm = StringManager.getManager(StandardRoot.class);
|
||||
|
||||
private Context context;
|
||||
private boolean allowLinking = false;
|
||||
private final List<WebResourceSet> preResources = new ArrayList<>();
|
||||
private WebResourceSet main;
|
||||
private final List<WebResourceSet> classResources = new ArrayList<>();
|
||||
private final List<WebResourceSet> jarResources = new ArrayList<>();
|
||||
private final List<WebResourceSet> postResources = new ArrayList<>();
|
||||
|
||||
private final Cache cache = new Cache(this);
|
||||
private boolean cachingAllowed = true;
|
||||
private ObjectName cacheJmxName = null;
|
||||
|
||||
private boolean trackLockedFiles = false;
|
||||
private final Set<TrackedWebResource> trackedResources =
|
||||
Collections.newSetFromMap(new ConcurrentHashMap<TrackedWebResource,Boolean>());
|
||||
|
||||
// Constructs to make iteration over all WebResourceSets simpler
|
||||
private final List<WebResourceSet> mainResources = new ArrayList<>();
|
||||
private final List<List<WebResourceSet>> allResources =
|
||||
new ArrayList<>();
|
||||
{
|
||||
allResources.add(preResources);
|
||||
allResources.add(mainResources);
|
||||
allResources.add(classResources);
|
||||
allResources.add(jarResources);
|
||||
allResources.add(postResources);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new standard implementation of {@link WebResourceRoot}. A no
|
||||
* argument constructor is required for this to work with the digester.
|
||||
* {@link #setContext(Context)} must be called before this component is
|
||||
* initialized.
|
||||
*/
|
||||
public StandardRoot() {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
public StandardRoot(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(String path) {
|
||||
return list(path, true);
|
||||
}
|
||||
|
||||
private String[] list(String path, boolean validate) {
|
||||
if (validate) {
|
||||
path = validate(path);
|
||||
}
|
||||
|
||||
// Set because we don't want duplicates
|
||||
// LinkedHashSet to retain the order. It is the order of the
|
||||
// WebResourceSet that matters but it is simpler to retain the order
|
||||
// over all of the JARs.
|
||||
HashSet<String> result = new LinkedHashSet<>();
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
if (!webResourceSet.getClassLoaderOnly()) {
|
||||
String[] entries = webResourceSet.list(path);
|
||||
for (String entry : entries) {
|
||||
result.add(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toArray(new String[result.size()]);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Set<String> listWebAppPaths(String path) {
|
||||
path = validate(path);
|
||||
|
||||
// Set because we don't want duplicates
|
||||
HashSet<String> result = new HashSet<>();
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
if (!webResourceSet.getClassLoaderOnly()) {
|
||||
result.addAll(webResourceSet.listWebAppPaths(path));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String path) {
|
||||
path = validate(path);
|
||||
|
||||
if (preResourceExists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean mkdirResult = main.mkdir(path);
|
||||
|
||||
if (mkdirResult && isCachingAllowed()) {
|
||||
// Remove the entry from the cache so the new directory is visible
|
||||
cache.removeCacheEntry(path);
|
||||
}
|
||||
return mkdirResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(String path, InputStream is, boolean overwrite) {
|
||||
path = validate(path);
|
||||
|
||||
if (!overwrite && preResourceExists(path)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean writeResult = main.write(path, is, overwrite);
|
||||
|
||||
if (writeResult && isCachingAllowed()) {
|
||||
// Remove the entry from the cache so the new resource is visible
|
||||
cache.removeCacheEntry(path);
|
||||
}
|
||||
|
||||
return writeResult;
|
||||
}
|
||||
|
||||
private boolean preResourceExists(String path) {
|
||||
for (WebResourceSet webResourceSet : preResources) {
|
||||
WebResource webResource = webResourceSet.getResource(path);
|
||||
if (webResource.exists()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResource getResource(String path) {
|
||||
return getResource(path, true, false);
|
||||
}
|
||||
|
||||
protected WebResource getResource(String path, boolean validate,
|
||||
boolean useClassLoaderResources) {
|
||||
if (validate) {
|
||||
path = validate(path);
|
||||
}
|
||||
|
||||
if (isCachingAllowed()) {
|
||||
return cache.getResource(path, useClassLoaderResources);
|
||||
} else {
|
||||
return getResourceInternal(path, useClassLoaderResources);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebResource getClassLoaderResource(String path) {
|
||||
return getResource("/WEB-INF/classes" + path, true, true);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public WebResource[] getClassLoaderResources(String path) {
|
||||
return getResources("/WEB-INF/classes" + path, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Ensures that this object is in a valid state to serve resources, checks
|
||||
* that the path is a String that starts with '/' and checks that the path
|
||||
* can be normalized without stepping outside of the root.
|
||||
*
|
||||
* @param path
|
||||
* @return the normalized path
|
||||
*/
|
||||
private String validate(String path) {
|
||||
if (!getState().isAvailable()) {
|
||||
throw new IllegalStateException(
|
||||
sm.getString("standardRoot.checkStateNotStarted"));
|
||||
}
|
||||
|
||||
if (path == null || path.length() == 0 || !path.startsWith("/")) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("standardRoot.invalidPath", path));
|
||||
}
|
||||
|
||||
String result;
|
||||
if (File.separatorChar == '\\') {
|
||||
// On Windows '\\' is a separator so in case a Windows style
|
||||
// separator has managed to make it into the path, replace it.
|
||||
result = RequestUtil.normalize(path, true);
|
||||
} else {
|
||||
// On UNIX and similar systems, '\\' is a valid file name so do not
|
||||
// convert it to '/'
|
||||
result = RequestUtil.normalize(path, false);
|
||||
}
|
||||
if (result == null || result.length() == 0 || !result.startsWith("/")) {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("standardRoot.invalidPathNormal", path, result));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected final WebResource getResourceInternal(String path,
|
||||
boolean useClassLoaderResources) {
|
||||
WebResource result = null;
|
||||
WebResource virtual = null;
|
||||
WebResource mainEmpty = null;
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
if (!useClassLoaderResources && !webResourceSet.getClassLoaderOnly() ||
|
||||
useClassLoaderResources && !webResourceSet.getStaticOnly()) {
|
||||
result = webResourceSet.getResource(path);
|
||||
if (result.exists()) {
|
||||
return result;
|
||||
}
|
||||
if (virtual == null) {
|
||||
if (result.isVirtual()) {
|
||||
virtual = result;
|
||||
} else if (main.equals(webResourceSet)) {
|
||||
mainEmpty = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use the first virtual result if no real result was found
|
||||
if (virtual != null) {
|
||||
return virtual;
|
||||
}
|
||||
|
||||
// Default is empty resource in main resources
|
||||
return mainEmpty;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResource[] getResources(String path) {
|
||||
return getResources(path, false);
|
||||
}
|
||||
|
||||
private WebResource[] getResources(String path,
|
||||
boolean useClassLoaderResources) {
|
||||
path = validate(path);
|
||||
|
||||
if (isCachingAllowed()) {
|
||||
return cache.getResources(path, useClassLoaderResources);
|
||||
} else {
|
||||
return getResourcesInternal(path, useClassLoaderResources);
|
||||
}
|
||||
}
|
||||
|
||||
protected WebResource[] getResourcesInternal(String path,
|
||||
boolean useClassLoaderResources) {
|
||||
List<WebResource> result = new ArrayList<>();
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
if (useClassLoaderResources || !webResourceSet.getClassLoaderOnly()) {
|
||||
WebResource webResource = webResourceSet.getResource(path);
|
||||
if (webResource.exists()) {
|
||||
result.add(webResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.size() == 0) {
|
||||
result.add(main.getResource(path));
|
||||
}
|
||||
|
||||
return result.toArray(new WebResource[result.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResource[] listResources(String path) {
|
||||
return listResources(path, true);
|
||||
}
|
||||
|
||||
protected WebResource[] listResources(String path, boolean validate) {
|
||||
if (validate) {
|
||||
path = validate(path);
|
||||
}
|
||||
|
||||
String[] resources = list(path, false);
|
||||
WebResource[] result = new WebResource[resources.length];
|
||||
for (int i = 0; i < resources.length; i++) {
|
||||
if (path.charAt(path.length() - 1) == '/') {
|
||||
result[i] = getResource(path + resources[i], false, false);
|
||||
} else {
|
||||
result[i] = getResource(path + '/' + resources[i], false, false);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: Should the createWebResourceSet() methods be removed to some
|
||||
// utility class for file system based resource sets?
|
||||
|
||||
@Override
|
||||
public void createWebResourceSet(ResourceSetType type, String webAppMount,
|
||||
URL url, String internalPath) {
|
||||
BaseLocation baseLocation = new BaseLocation(url);
|
||||
createWebResourceSet(type, webAppMount, baseLocation.getBasePath(),
|
||||
baseLocation.getArchivePath(), internalPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createWebResourceSet(ResourceSetType type, String webAppMount,
|
||||
String base, String archivePath, String internalPath) {
|
||||
List<WebResourceSet> resourceList;
|
||||
WebResourceSet resourceSet;
|
||||
|
||||
switch (type) {
|
||||
case PRE:
|
||||
resourceList = preResources;
|
||||
break;
|
||||
case CLASSES_JAR:
|
||||
resourceList = classResources;
|
||||
break;
|
||||
case RESOURCE_JAR:
|
||||
resourceList = jarResources;
|
||||
break;
|
||||
case POST:
|
||||
resourceList = postResources;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("standardRoot.createUnknownType", type));
|
||||
}
|
||||
|
||||
// This implementation assumes that the base for all resources will be a
|
||||
// file.
|
||||
File file = new File(base);
|
||||
|
||||
if (file.isFile()) {
|
||||
if (archivePath != null) {
|
||||
// Must be a JAR nested inside a WAR if archivePath is non-null
|
||||
resourceSet = new JarWarResourceSet(this, webAppMount, base,
|
||||
archivePath, internalPath);
|
||||
} else if (file.getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
|
||||
resourceSet = new JarResourceSet(this, webAppMount, base,
|
||||
internalPath);
|
||||
} else {
|
||||
resourceSet = new FileResourceSet(this, webAppMount, base,
|
||||
internalPath);
|
||||
}
|
||||
} else if (file.isDirectory()) {
|
||||
resourceSet =
|
||||
new DirResourceSet(this, webAppMount, base, internalPath);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("standardRoot.createInvalidFile", file));
|
||||
}
|
||||
|
||||
if (type.equals(ResourceSetType.CLASSES_JAR)) {
|
||||
resourceSet.setClassLoaderOnly(true);
|
||||
} else if (type.equals(ResourceSetType.RESOURCE_JAR)) {
|
||||
resourceSet.setStaticOnly(true);
|
||||
}
|
||||
|
||||
resourceList.add(resourceSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPreResources(WebResourceSet webResourceSet) {
|
||||
webResourceSet.setRoot(this);
|
||||
preResources.add(webResourceSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceSet[] getPreResources() {
|
||||
return preResources.toArray(new WebResourceSet[preResources.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addJarResources(WebResourceSet webResourceSet) {
|
||||
webResourceSet.setRoot(this);
|
||||
jarResources.add(webResourceSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceSet[] getJarResources() {
|
||||
return jarResources.toArray(new WebResourceSet[jarResources.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPostResources(WebResourceSet webResourceSet) {
|
||||
webResourceSet.setRoot(this);
|
||||
postResources.add(webResourceSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebResourceSet[] getPostResources() {
|
||||
return postResources.toArray(new WebResourceSet[postResources.size()]);
|
||||
}
|
||||
|
||||
protected WebResourceSet[] getClassResources() {
|
||||
return classResources.toArray(new WebResourceSet[classResources.size()]);
|
||||
}
|
||||
|
||||
protected void addClassResources(WebResourceSet webResourceSet) {
|
||||
webResourceSet.setRoot(this);
|
||||
classResources.add(webResourceSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAllowLinking(boolean allowLinking) {
|
||||
if (this.allowLinking != allowLinking && cachingAllowed) {
|
||||
// If allow linking changes, invalidate the cache.
|
||||
cache.clear();
|
||||
}
|
||||
this.allowLinking = allowLinking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAllowLinking() {
|
||||
return allowLinking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCachingAllowed(boolean cachingAllowed) {
|
||||
this.cachingAllowed = cachingAllowed;
|
||||
if (!cachingAllowed) {
|
||||
cache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCachingAllowed() {
|
||||
return cachingAllowed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCacheTtl() {
|
||||
return cache.getTtl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheTtl(long cacheTtl) {
|
||||
cache.setTtl(cacheTtl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCacheMaxSize() {
|
||||
return cache.getMaxSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheMaxSize(long cacheMaxSize) {
|
||||
cache.setMaxSize(cacheMaxSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheObjectMaxSize(int cacheObjectMaxSize) {
|
||||
cache.setObjectMaxSize(cacheObjectMaxSize);
|
||||
// Don't enforce the limit when not running as attributes may get set in
|
||||
// any order.
|
||||
if (getState().isAvailable()) {
|
||||
cache.enforceObjectMaxSizeLimit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCacheObjectMaxSize() {
|
||||
return cache.getObjectMaxSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTrackLockedFiles(boolean trackLockedFiles) {
|
||||
this.trackLockedFiles = trackLockedFiles;
|
||||
if (!trackLockedFiles) {
|
||||
trackedResources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getTrackLockedFiles() {
|
||||
return trackLockedFiles;
|
||||
}
|
||||
|
||||
public List<String> getTrackedResources() {
|
||||
List<String> result = new ArrayList<>(trackedResources.size());
|
||||
for (TrackedWebResource resource : trackedResources) {
|
||||
result.add(resource.toString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class loader resources are handled by treating JARs in WEB-INF/lib as
|
||||
* resource JARs (without the internal META-INF/resources/ prefix) mounted
|
||||
* at WEB-INF/classes (rather than the web app root). This enables reuse
|
||||
* of the resource handling plumbing.
|
||||
*
|
||||
* These resources are marked as class loader only so they are only used in
|
||||
* the methods that are explicitly defined to return class loader resources.
|
||||
* This prevents calls to getResource("/WEB-INF/classes") returning from one
|
||||
* or more of the JAR files.
|
||||
*
|
||||
* @throws LifecycleException If an error occurs that should stop the web
|
||||
* application from starting
|
||||
*/
|
||||
protected void processWebInfLib() throws LifecycleException {
|
||||
WebResource[] possibleJars = listResources("/WEB-INF/lib", false);
|
||||
|
||||
for (WebResource possibleJar : possibleJars) {
|
||||
if (possibleJar.isFile() && possibleJar.getName().endsWith(".jar")) {
|
||||
createWebResourceSet(ResourceSetType.CLASSES_JAR,
|
||||
"/WEB-INF/classes", possibleJar.getURL(), "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For unit testing.
|
||||
* @param main The main resources
|
||||
*/
|
||||
protected final void setMainResources(WebResourceSet main) {
|
||||
this.main = main;
|
||||
mainResources.clear();
|
||||
mainResources.add(main);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void backgroundProcess() {
|
||||
cache.backgroundProcess();
|
||||
gc();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void gc() {
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
webResourceSet.gc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTrackedResource(TrackedWebResource trackedResource) {
|
||||
trackedResources.add(trackedResource);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void deregisterTrackedResource(TrackedWebResource trackedResource) {
|
||||
trackedResources.remove(trackedResource);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<URL> getBaseUrls() {
|
||||
List<URL> result = new ArrayList<>();
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
if (!webResourceSet.getClassLoaderOnly()) {
|
||||
URL url = webResourceSet.getBaseUrl();
|
||||
if (url != null) {
|
||||
result.add(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Returns true if and only if all the resources for this web application
|
||||
* are provided via a packed WAR file. It is used to optimise cache
|
||||
* validation in this case on the basis that the WAR file will not change.
|
||||
*/
|
||||
protected boolean isPackedWarFile() {
|
||||
return main instanceof WarResourceSet && preResources.isEmpty() && postResources.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------- JMX Lifecycle
|
||||
@Override
|
||||
protected String getDomainInternal() {
|
||||
return context.getDomain();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getObjectNameKeyProperties() {
|
||||
StringBuilder keyProperties = new StringBuilder("type=WebResourceRoot");
|
||||
keyProperties.append(context.getMBeanKeyProperties());
|
||||
|
||||
return keyProperties.toString();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- Lifecycle
|
||||
|
||||
@Override
|
||||
protected void initInternal() throws LifecycleException {
|
||||
super.initInternal();
|
||||
|
||||
cacheJmxName = register(cache, getObjectNameKeyProperties() + ",name=Cache");
|
||||
|
||||
registerURLStreamHandlerFactory();
|
||||
|
||||
if (context == null) {
|
||||
throw new IllegalStateException(
|
||||
sm.getString("standardRoot.noContext"));
|
||||
}
|
||||
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
webResourceSet.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void registerURLStreamHandlerFactory() {
|
||||
// Ensure support for jar:war:file:/ URLs will be available (required
|
||||
// for resource JARs in packed WAR files).
|
||||
TomcatURLStreamHandlerFactory.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startInternal() throws LifecycleException {
|
||||
mainResources.clear();
|
||||
|
||||
main = createMainResourceSet();
|
||||
|
||||
mainResources.add(main);
|
||||
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
// Skip class resources since they are started below
|
||||
if (list != classResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
webResourceSet.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This has to be called after the other resources have been started
|
||||
// else it won't find all the matching resources
|
||||
processWebInfLib();
|
||||
// Need to start the newly found resources
|
||||
for (WebResourceSet classResource : classResources) {
|
||||
classResource.start();
|
||||
}
|
||||
|
||||
cache.enforceObjectMaxSizeLimit();
|
||||
|
||||
setState(LifecycleState.STARTING);
|
||||
}
|
||||
|
||||
protected WebResourceSet createMainResourceSet() {
|
||||
String docBase = context.getDocBase();
|
||||
|
||||
WebResourceSet mainResourceSet;
|
||||
if (docBase == null) {
|
||||
mainResourceSet = new EmptyResourceSet(this);
|
||||
} else {
|
||||
File f = new File(docBase);
|
||||
if (!f.isAbsolute()) {
|
||||
f = new File(((Host)context.getParent()).getAppBaseFile(), f.getPath());
|
||||
}
|
||||
if (f.isDirectory()) {
|
||||
mainResourceSet = new DirResourceSet(this, "/", f.getAbsolutePath(), "/");
|
||||
} else if(f.isFile() && docBase.endsWith(".war")) {
|
||||
mainResourceSet = new WarResourceSet(this, "/", f.getAbsolutePath());
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
sm.getString("standardRoot.startInvalidMain",
|
||||
f.getAbsolutePath()));
|
||||
}
|
||||
}
|
||||
|
||||
return mainResourceSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void stopInternal() throws LifecycleException {
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
webResourceSet.stop();
|
||||
}
|
||||
}
|
||||
|
||||
if (main != null) {
|
||||
main.destroy();
|
||||
}
|
||||
mainResources.clear();
|
||||
|
||||
for (WebResourceSet webResourceSet : jarResources) {
|
||||
webResourceSet.destroy();
|
||||
}
|
||||
jarResources.clear();
|
||||
|
||||
for (WebResourceSet webResourceSet : classResources) {
|
||||
webResourceSet.destroy();
|
||||
}
|
||||
classResources.clear();
|
||||
|
||||
for (TrackedWebResource trackedResource : trackedResources) {
|
||||
log.error(sm.getString("standardRoot.lockedFile",
|
||||
context.getName(),
|
||||
trackedResource.getName()),
|
||||
trackedResource.getCreatedBy());
|
||||
try {
|
||||
trackedResource.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
cache.clear();
|
||||
|
||||
setState(LifecycleState.STOPPING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void destroyInternal() throws LifecycleException {
|
||||
for (List<WebResourceSet> list : allResources) {
|
||||
for (WebResourceSet webResourceSet : list) {
|
||||
webResourceSet.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
unregister(cacheJmxName);
|
||||
|
||||
super.destroyInternal();
|
||||
}
|
||||
|
||||
|
||||
// Unit tests need to access this class
|
||||
static class BaseLocation {
|
||||
|
||||
private final String basePath;
|
||||
private final String archivePath;
|
||||
|
||||
BaseLocation(URL url) {
|
||||
File f = null;
|
||||
|
||||
if ("jar".equals(url.getProtocol()) || "war".equals(url.getProtocol())) {
|
||||
String jarUrl = url.toString();
|
||||
int endOfFileUrl = -1;
|
||||
if ("jar".equals(url.getProtocol())) {
|
||||
endOfFileUrl = jarUrl.indexOf("!/");
|
||||
} else {
|
||||
endOfFileUrl = jarUrl.indexOf(UriUtil.getWarSeparator());
|
||||
}
|
||||
String fileUrl = jarUrl.substring(4, endOfFileUrl);
|
||||
try {
|
||||
f = new File(new URL(fileUrl).toURI());
|
||||
} catch (MalformedURLException | URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
int startOfArchivePath = endOfFileUrl + 2;
|
||||
if (jarUrl.length() > startOfArchivePath) {
|
||||
archivePath = jarUrl.substring(startOfArchivePath);
|
||||
} else {
|
||||
archivePath = null;
|
||||
}
|
||||
} else if ("file".equals(url.getProtocol())){
|
||||
try {
|
||||
f = new File(url.toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
archivePath = null;
|
||||
} else {
|
||||
throw new IllegalArgumentException(sm.getString(
|
||||
"standardRoot.unsupportedProtocol", url.getProtocol()));
|
||||
}
|
||||
|
||||
basePath = f.getAbsolutePath();
|
||||
}
|
||||
|
||||
|
||||
String getBasePath() {
|
||||
return basePath;
|
||||
}
|
||||
|
||||
|
||||
String getArchivePath() {
|
||||
return archivePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarInputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
|
||||
/**
|
||||
* The purpose of this sub-class is to obtain references to the JarEntry objects
|
||||
* for META-INF/ and META-INF/MANIFEST.MF that are otherwise swallowed by the
|
||||
* JarInputStream implementation.
|
||||
*/
|
||||
public class TomcatJarInputStream extends JarInputStream {
|
||||
|
||||
private JarEntry metaInfEntry;
|
||||
private JarEntry manifestEntry;
|
||||
|
||||
|
||||
TomcatJarInputStream(InputStream in) throws IOException {
|
||||
super(in);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected ZipEntry createZipEntry(String name) {
|
||||
ZipEntry ze = super.createZipEntry(name);
|
||||
if (metaInfEntry == null && "META-INF/".equals(name)) {
|
||||
metaInfEntry = (JarEntry) ze;
|
||||
} else if (manifestEntry == null && "META-INF/MANIFESR.MF".equals(name)) {
|
||||
manifestEntry = (JarEntry) ze;
|
||||
}
|
||||
return ze;
|
||||
}
|
||||
|
||||
|
||||
JarEntry getMetaInfEntry() {
|
||||
return metaInfEntry;
|
||||
}
|
||||
|
||||
|
||||
JarEntry getManifestEntry() {
|
||||
return manifestEntry;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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.net.URL;
|
||||
import java.net.URLStreamHandler;
|
||||
import java.net.URLStreamHandlerFactory;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import org.apache.catalina.webresources.war.Handler;
|
||||
|
||||
public class TomcatURLStreamHandlerFactory implements URLStreamHandlerFactory {
|
||||
|
||||
private static final String WAR_PROTOCOL = "war";
|
||||
private static final String CLASSPATH_PROTOCOL = "classpath";
|
||||
|
||||
// Singleton instance
|
||||
private static volatile TomcatURLStreamHandlerFactory instance = null;
|
||||
|
||||
/**
|
||||
* Obtain a reference to the singleton instance. It is recommended that
|
||||
* callers check the value of {@link #isRegistered()} before using the
|
||||
* returned instance.
|
||||
*
|
||||
* @return A reference to the singleton instance
|
||||
*/
|
||||
public static TomcatURLStreamHandlerFactory getInstance() {
|
||||
getInstanceInternal(true);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private static TomcatURLStreamHandlerFactory getInstanceInternal(boolean register) {
|
||||
// Double checked locking. OK because instance is volatile.
|
||||
if (instance == null) {
|
||||
synchronized (TomcatURLStreamHandlerFactory.class) {
|
||||
if (instance == null) {
|
||||
instance = new TomcatURLStreamHandlerFactory(register);
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
private final boolean registered;
|
||||
|
||||
// List of factories for application defined stream handler factories.
|
||||
private final List<URLStreamHandlerFactory> userFactories =
|
||||
new CopyOnWriteArrayList<>();
|
||||
|
||||
/**
|
||||
* Register this factory with the JVM. May be called more than once. The
|
||||
* implementation ensures that registration only occurs once.
|
||||
*
|
||||
* @return <code>true</code> if the factory is already registered with the
|
||||
* JVM or was successfully registered as a result of this call.
|
||||
* <code>false</code> if the factory was disabled prior to this
|
||||
* call.
|
||||
*/
|
||||
public static boolean register() {
|
||||
return getInstanceInternal(true).isRegistered();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prevent this this factory from registering with the JVM. May be called
|
||||
* more than once.
|
||||
*
|
||||
* @return <code>true</code> if the factory is already disabled or was
|
||||
* successfully disabled as a result of this call.
|
||||
* <code>false</code> if the factory was already registered prior
|
||||
* to this call.
|
||||
|
||||
*/
|
||||
public static boolean disable() {
|
||||
return !getInstanceInternal(false).isRegistered();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Release references to any user provided factories that have been loaded
|
||||
* using the provided class loader. Called during web application stop to
|
||||
* prevent memory leaks.
|
||||
*
|
||||
* @param classLoader The class loader to release
|
||||
*/
|
||||
public static void release(ClassLoader classLoader) {
|
||||
if (instance == null) {
|
||||
return;
|
||||
}
|
||||
List<URLStreamHandlerFactory> factories = instance.userFactories;
|
||||
for (URLStreamHandlerFactory factory : factories) {
|
||||
ClassLoader factoryLoader = factory.getClass().getClassLoader();
|
||||
while (factoryLoader != null) {
|
||||
if (classLoader.equals(factoryLoader)) {
|
||||
// Implementation note: userFactories is a
|
||||
// CopyOnWriteArrayList, so items are removed with
|
||||
// List.remove() instead of usual Iterator.remove()
|
||||
factories.remove(factory);
|
||||
break;
|
||||
}
|
||||
factoryLoader = factoryLoader.getParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private TomcatURLStreamHandlerFactory(boolean register) {
|
||||
// Hide default constructor
|
||||
// Singleton pattern to ensure there is only one instance of this
|
||||
// factory
|
||||
this.registered = register;
|
||||
if (register) {
|
||||
URL.setURLStreamHandlerFactory(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean isRegistered() {
|
||||
return registered;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Since the JVM only allows a single call to
|
||||
* {@link URL#setURLStreamHandlerFactory(URLStreamHandlerFactory)} and
|
||||
* Tomcat needs to register a handler, provide a mechanism to allow
|
||||
* applications to register their own handlers.
|
||||
*
|
||||
* @param factory The user provided factory to add to the factories Tomcat
|
||||
* has already registered
|
||||
*/
|
||||
public void addUserFactory(URLStreamHandlerFactory factory) {
|
||||
userFactories.add(factory);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public URLStreamHandler createURLStreamHandler(String protocol) {
|
||||
|
||||
// Tomcat's handler always takes priority so applications can't override
|
||||
// it.
|
||||
if (WAR_PROTOCOL.equals(protocol)) {
|
||||
return new Handler();
|
||||
} else if (CLASSPATH_PROTOCOL.equals(protocol)) {
|
||||
return new ClasspathURLStreamHandler();
|
||||
}
|
||||
|
||||
// Application handlers
|
||||
for (URLStreamHandlerFactory factory : userFactories) {
|
||||
URLStreamHandler handler =
|
||||
factory.createURLStreamHandler(protocol);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown protocol
|
||||
return null;
|
||||
}
|
||||
}
|
||||
112
java/org/apache/catalina/webresources/TrackedInputStream.java
Normal file
112
java/org/apache/catalina/webresources/TrackedInputStream.java
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import org.apache.catalina.TrackedWebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
|
||||
class TrackedInputStream extends InputStream implements TrackedWebResource {
|
||||
|
||||
private final WebResourceRoot root;
|
||||
private final String name;
|
||||
private final InputStream is;
|
||||
private final Exception creation;
|
||||
|
||||
TrackedInputStream(WebResourceRoot root, String name, InputStream is) {
|
||||
this.root = root;
|
||||
this.name = name;
|
||||
this.is = is;
|
||||
this.creation = new Exception();
|
||||
|
||||
root.registerTrackedResource(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return is.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
return is.read(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
return is.read(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
return is.skip(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return is.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
root.deregisterTrackedResource(this);
|
||||
is.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void mark(int readlimit) {
|
||||
is.mark(readlimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
is.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return is.markSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception getCreatedBy() {
|
||||
return creation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
sw.append('[');
|
||||
sw.append(name);
|
||||
sw.append(']');
|
||||
sw.append(System.lineSeparator());
|
||||
creation.printStackTrace(pw);
|
||||
pw.flush();
|
||||
|
||||
return sw.toString();
|
||||
}
|
||||
}
|
||||
45
java/org/apache/catalina/webresources/VirtualResource.java
Normal file
45
java/org/apache/catalina/webresources/VirtualResource.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 org.apache.catalina.WebResourceRoot;
|
||||
|
||||
public class VirtualResource extends EmptyResource {
|
||||
|
||||
private final String name;
|
||||
|
||||
public VirtualResource(WebResourceRoot root, String webAppPath,
|
||||
String name) {
|
||||
super(root, webAppPath);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVirtual() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
45
java/org/apache/catalina/webresources/WarResource.java
Normal file
45
java/org/apache/catalina/webresources/WarResource.java
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.util.jar.JarEntry;
|
||||
|
||||
import org.apache.juli.logging.Log;
|
||||
import org.apache.juli.logging.LogFactory;
|
||||
import org.apache.tomcat.util.buf.UriUtil;
|
||||
|
||||
/**
|
||||
* Represents a single resource (file or directory) that is located within a
|
||||
* WAR.
|
||||
*/
|
||||
public class WarResource extends AbstractSingleArchiveResource {
|
||||
|
||||
private static final Log log = LogFactory.getLog(WarResource.class);
|
||||
|
||||
|
||||
public WarResource(AbstractArchiveResourceSet archiveResourceSet, String webAppPath,
|
||||
String baseUrl, JarEntry jarEntry) {
|
||||
super(archiveResourceSet, webAppPath, "war:" + baseUrl + UriUtil.getWarSeparator(),
|
||||
jarEntry, baseUrl);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Log getLog() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
64
java/org/apache/catalina/webresources/WarResourceSet.java
Normal file
64
java/org/apache/catalina/webresources/WarResourceSet.java
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.util.jar.JarEntry;
|
||||
import java.util.jar.Manifest;
|
||||
|
||||
import org.apache.catalina.WebResource;
|
||||
import org.apache.catalina.WebResourceRoot;
|
||||
|
||||
/**
|
||||
* Represents a {@link org.apache.catalina.WebResourceSet} based on a WAR file.
|
||||
*/
|
||||
public class WarResourceSet extends AbstractSingleArchiveResourceSet {
|
||||
|
||||
/**
|
||||
* A no argument constructor is required for this to work with the digester.
|
||||
*/
|
||||
public WarResourceSet() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link org.apache.catalina.WebResourceSet} based on a WAR
|
||||
* file.
|
||||
*
|
||||
* @param root The {@link WebResourceRoot} this new
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be added to.
|
||||
* @param webAppMount The path within the web application at which this
|
||||
* {@link org.apache.catalina.WebResourceSet} will
|
||||
* be mounted.
|
||||
* @param base The absolute path to the WAR file on the file system
|
||||
* from which the resources will be served.
|
||||
*
|
||||
* @throws IllegalArgumentException if the webAppMount is not valid (valid
|
||||
* paths must start with '/')
|
||||
*/
|
||||
public WarResourceSet(WebResourceRoot root, String webAppMount, String base)
|
||||
throws IllegalArgumentException {
|
||||
super(root, webAppMount, base, "/");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected WebResource createArchiveResource(JarEntry jarEntry,
|
||||
String webAppPath, Manifest manifest) {
|
||||
return new WarResource(this, webAppPath, getBaseUrlString(), jarEntry);
|
||||
}
|
||||
}
|
||||
98
java/org/apache/catalina/webresources/mbeans-descriptors.xml
Normal file
98
java/org/apache/catalina/webresources/mbeans-descriptors.xml
Normal file
@@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<mbeans-descriptors>
|
||||
|
||||
<mbean name="StandardRoot"
|
||||
description="Provides the resources implementation for a web application"
|
||||
domain="Catalina"
|
||||
group="WebResourceRoot"
|
||||
type="org.apache.catalina.webresources.StandardRoot">
|
||||
|
||||
<attribute name="allowLinking"
|
||||
description="Does this resources implementation allow the use of symbolic links?"
|
||||
type="boolean"
|
||||
writeable="true"/>
|
||||
|
||||
<attribute name="cachingAllowed"
|
||||
description="Is in memory caching of resource content and metadata enabled?"
|
||||
type="boolean"
|
||||
is="true"
|
||||
writeable="true"/>
|
||||
|
||||
<attribute name="stateName"
|
||||
description="The current Lifecycle state of this object"
|
||||
type="java.lang.String"
|
||||
writeable="false"/>
|
||||
|
||||
<attribute name="trackLockedFiles"
|
||||
description="Does this resources implementation track requests that lock files?"
|
||||
type="boolean"
|
||||
writeable="true"/>
|
||||
|
||||
<attribute name="trackedResources"
|
||||
description="List of resources currently being tracked for possible resource leaks"
|
||||
type="java.util.List"
|
||||
writeable="false"/>
|
||||
|
||||
</mbean>
|
||||
|
||||
<mbean name="Cache"
|
||||
description="Provides caching of resource metadata and content"
|
||||
domain="Catalina"
|
||||
group="WebResourceRoot"
|
||||
type="org.apache.catalina.webresources.Cache">
|
||||
|
||||
<attribute name="hitCount"
|
||||
description="The number of requests for resources that were served from the cache"
|
||||
type="long"
|
||||
writeable="false"/>
|
||||
|
||||
<attribute name="lookupCount"
|
||||
description="The number of requests for resources"
|
||||
type="long"
|
||||
writeable="false"/>
|
||||
|
||||
<attribute name="maxSize"
|
||||
description="The maximum permitted size of the cache in kB"
|
||||
type="long"
|
||||
writeable="true"/>
|
||||
|
||||
<attribute name="objectMaxSize"
|
||||
description="The maximum permitted size for a single object in the cache in kB"
|
||||
type="int"
|
||||
writeable="true"/>
|
||||
|
||||
<attribute name="size"
|
||||
description="The current estimate of the cache size in kB"
|
||||
type="long"
|
||||
writeable="false"/>
|
||||
|
||||
<attribute name="ttl"
|
||||
description="The time-to-live for cache entries in milliseconds"
|
||||
type="long"
|
||||
writeable="true"/>
|
||||
|
||||
<operation name="clear"
|
||||
description="Clears all cached content from the cache."
|
||||
impact="ACTION"
|
||||
returnType="void">
|
||||
</operation>
|
||||
|
||||
</mbean>
|
||||
|
||||
</mbeans-descriptors>
|
||||
46
java/org/apache/catalina/webresources/war/Handler.java
Normal file
46
java/org/apache/catalina/webresources/war/Handler.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.war;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.net.URLStreamHandler;
|
||||
|
||||
public class Handler extends URLStreamHandler {
|
||||
|
||||
@Override
|
||||
protected URLConnection openConnection(URL u) throws IOException {
|
||||
return new WarURLConnection(u);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String path,
|
||||
String query, String ref) {
|
||||
if (path.startsWith("file:") && !path.startsWith("file:/")) {
|
||||
// Work around a problem with the URLs in the security policy file.
|
||||
// On Windows, the use of ${catalina.[home|base]} in the policy file
|
||||
// results in codebase URLs of the form file:C:/... when they should
|
||||
// be file:/C:/...
|
||||
// For file: and jar: URLs, the JRE compensates for this. It does not
|
||||
// compensate for this for war:file:... URLs. Therefore, we do that
|
||||
// here
|
||||
path = "file:/" + path.substring(5);
|
||||
}
|
||||
super.setURL(u, protocol, host, port, authority, userInfo, path, query, ref);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.war;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.security.Permission;
|
||||
|
||||
import org.apache.tomcat.util.buf.UriUtil;
|
||||
|
||||
|
||||
public class WarURLConnection extends URLConnection {
|
||||
|
||||
private final URLConnection wrappedJarUrlConnection;
|
||||
private boolean connected;
|
||||
|
||||
protected WarURLConnection(URL url) throws IOException {
|
||||
super(url);
|
||||
URL innerJarUrl = UriUtil.warToJar(url);
|
||||
wrappedJarUrlConnection = innerJarUrl.openConnection();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void connect() throws IOException {
|
||||
if (!connected) {
|
||||
wrappedJarUrlConnection.connect();
|
||||
connected = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
connect();
|
||||
return wrappedJarUrlConnection.getInputStream();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Permission getPermission() throws IOException {
|
||||
return wrappedJarUrlConnection.getPermission();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return wrappedJarUrlConnection.getLastModified();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getContentLength() {
|
||||
return wrappedJarUrlConnection.getContentLength();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public long getContentLengthLong() {
|
||||
return wrappedJarUrlConnection.getContentLengthLong();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user