326 lines
13 KiB
Java
326 lines
13 KiB
Java
/*
|
|
* 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.startup;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.net.MalformedURLException;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
import java.security.AccessController;
|
|
import java.security.PrivilegedAction;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Set;
|
|
|
|
import org.apache.juli.logging.Log;
|
|
import org.apache.juli.logging.LogFactory;
|
|
|
|
/**
|
|
* <p>Utility class for building class loaders for Catalina. The factory
|
|
* method requires the following parameters in order to build a new class
|
|
* loader (with suitable defaults in all cases):</p>
|
|
* <ul>
|
|
* <li>A set of directories containing unpacked classes (and resources)
|
|
* that should be included in the class loader's
|
|
* repositories.</li>
|
|
* <li>A set of directories containing classes and resources in JAR files.
|
|
* Each readable JAR file discovered in these directories will be
|
|
* added to the class loader's repositories.</li>
|
|
* <li><code>ClassLoader</code> instance that should become the parent of
|
|
* the new class loader.</li>
|
|
* </ul>
|
|
*
|
|
* @author Craig R. McClanahan
|
|
*/
|
|
public final class ClassLoaderFactory {
|
|
|
|
|
|
private static final Log log = LogFactory.getLog(ClassLoaderFactory.class);
|
|
|
|
// --------------------------------------------------------- Public Methods
|
|
|
|
|
|
/**
|
|
* Create and return a new class loader, based on the configuration
|
|
* defaults and the specified directory paths:
|
|
*
|
|
* @param unpacked Array of pathnames to unpacked directories that should
|
|
* be added to the repositories of the class loader, or <code>null</code>
|
|
* for no unpacked directories to be considered
|
|
* @param packed Array of pathnames to directories containing JAR files
|
|
* that should be added to the repositories of the class loader,
|
|
* or <code>null</code> for no directories of JAR files to be considered
|
|
* @param parent Parent class loader for the new class loader, or
|
|
* <code>null</code> for the system class loader.
|
|
* @return the new class loader
|
|
*
|
|
* @exception Exception if an error occurs constructing the class loader
|
|
*/
|
|
public static ClassLoader createClassLoader(File unpacked[],
|
|
File packed[],
|
|
final ClassLoader parent)
|
|
throws Exception {
|
|
|
|
if (log.isDebugEnabled())
|
|
log.debug("Creating new class loader");
|
|
|
|
// Construct the "class path" for this class loader
|
|
Set<URL> set = new LinkedHashSet<>();
|
|
|
|
// Add unpacked directories
|
|
if (unpacked != null) {
|
|
for (int i = 0; i < unpacked.length; i++) {
|
|
File file = unpacked[i];
|
|
if (!file.canRead())
|
|
continue;
|
|
file = new File(file.getCanonicalPath() + File.separator);
|
|
URL url = file.toURI().toURL();
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including directory " + url);
|
|
set.add(url);
|
|
}
|
|
}
|
|
|
|
// Add packed directory JAR files
|
|
if (packed != null) {
|
|
for (int i = 0; i < packed.length; i++) {
|
|
File directory = packed[i];
|
|
if (!directory.isDirectory() || !directory.canRead())
|
|
continue;
|
|
String filenames[] = directory.list();
|
|
if (filenames == null) {
|
|
continue;
|
|
}
|
|
for (int j = 0; j < filenames.length; j++) {
|
|
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
|
|
if (!filename.endsWith(".jar"))
|
|
continue;
|
|
File file = new File(directory, filenames[j]);
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including jar file " + file.getAbsolutePath());
|
|
URL url = file.toURI().toURL();
|
|
set.add(url);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct the class loader itself
|
|
final URL[] array = set.toArray(new URL[set.size()]);
|
|
return AccessController.doPrivileged(
|
|
new PrivilegedAction<URLClassLoader>() {
|
|
@Override
|
|
public URLClassLoader run() {
|
|
if (parent == null)
|
|
return new URLClassLoader(array);
|
|
else
|
|
return new URLClassLoader(array, parent);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Create and return a new class loader, based on the configuration
|
|
* defaults and the specified directory paths:
|
|
*
|
|
* @param repositories List of class directories, jar files, jar directories
|
|
* or URLS that should be added to the repositories of
|
|
* the class loader.
|
|
* @param parent Parent class loader for the new class loader, or
|
|
* <code>null</code> for the system class loader.
|
|
* @return the new class loader
|
|
*
|
|
* @exception Exception if an error occurs constructing the class loader
|
|
*/
|
|
public static ClassLoader createClassLoader(List<Repository> repositories,
|
|
final ClassLoader parent)
|
|
throws Exception {
|
|
|
|
if (log.isDebugEnabled())
|
|
log.debug("Creating new class loader");
|
|
|
|
// Construct the "class path" for this class loader
|
|
Set<URL> set = new LinkedHashSet<>();
|
|
|
|
if (repositories != null) {
|
|
for (Repository repository : repositories) {
|
|
if (repository.getType() == RepositoryType.URL) {
|
|
URL url = buildClassLoaderUrl(repository.getLocation());
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including URL " + url);
|
|
set.add(url);
|
|
} else if (repository.getType() == RepositoryType.DIR) {
|
|
File directory = new File(repository.getLocation());
|
|
directory = directory.getCanonicalFile();
|
|
if (!validateFile(directory, RepositoryType.DIR)) {
|
|
continue;
|
|
}
|
|
URL url = buildClassLoaderUrl(directory);
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including directory " + url);
|
|
set.add(url);
|
|
} else if (repository.getType() == RepositoryType.JAR) {
|
|
File file=new File(repository.getLocation());
|
|
file = file.getCanonicalFile();
|
|
if (!validateFile(file, RepositoryType.JAR)) {
|
|
continue;
|
|
}
|
|
URL url = buildClassLoaderUrl(file);
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including jar file " + url);
|
|
set.add(url);
|
|
} else if (repository.getType() == RepositoryType.GLOB) {
|
|
File directory=new File(repository.getLocation());
|
|
directory = directory.getCanonicalFile();
|
|
if (!validateFile(directory, RepositoryType.GLOB)) {
|
|
continue;
|
|
}
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including directory glob "
|
|
+ directory.getAbsolutePath());
|
|
String filenames[] = directory.list();
|
|
if (filenames == null) {
|
|
continue;
|
|
}
|
|
for (int j = 0; j < filenames.length; j++) {
|
|
String filename = filenames[j].toLowerCase(Locale.ENGLISH);
|
|
if (!filename.endsWith(".jar"))
|
|
continue;
|
|
File file = new File(directory, filenames[j]);
|
|
file = file.getCanonicalFile();
|
|
if (!validateFile(file, RepositoryType.JAR)) {
|
|
continue;
|
|
}
|
|
if (log.isDebugEnabled())
|
|
log.debug(" Including glob jar file "
|
|
+ file.getAbsolutePath());
|
|
URL url = buildClassLoaderUrl(file);
|
|
set.add(url);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Construct the class loader itself
|
|
final URL[] array = set.toArray(new URL[set.size()]);
|
|
if (log.isDebugEnabled())
|
|
for (int i = 0; i < array.length; i++) {
|
|
log.debug(" location " + i + " is " + array[i]);
|
|
}
|
|
|
|
return AccessController.doPrivileged(
|
|
new PrivilegedAction<URLClassLoader>() {
|
|
@Override
|
|
public URLClassLoader run() {
|
|
if (parent == null)
|
|
return new URLClassLoader(array);
|
|
else
|
|
return new URLClassLoader(array, parent);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static boolean validateFile(File file,
|
|
RepositoryType type) throws IOException {
|
|
if (RepositoryType.DIR == type || RepositoryType.GLOB == type) {
|
|
if (!file.isDirectory() || !file.canRead()) {
|
|
String msg = "Problem with directory [" + file +
|
|
"], exists: [" + file.exists() +
|
|
"], isDirectory: [" + file.isDirectory() +
|
|
"], canRead: [" + file.canRead() + "]";
|
|
|
|
File home = new File (Bootstrap.getCatalinaHome());
|
|
home = home.getCanonicalFile();
|
|
File base = new File (Bootstrap.getCatalinaBase());
|
|
base = base.getCanonicalFile();
|
|
File defaultValue = new File(base, "lib");
|
|
|
|
// Existence of ${catalina.base}/lib directory is optional.
|
|
// Hide the warning if Tomcat runs with separate catalina.home
|
|
// and catalina.base and that directory is absent.
|
|
if (!home.getPath().equals(base.getPath())
|
|
&& file.getPath().equals(defaultValue.getPath())
|
|
&& !file.exists()) {
|
|
log.debug(msg);
|
|
} else {
|
|
log.warn(msg);
|
|
}
|
|
return false;
|
|
}
|
|
} else if (RepositoryType.JAR == type) {
|
|
if (!file.canRead()) {
|
|
log.warn("Problem with JAR file [" + file +
|
|
"], exists: [" + file.exists() +
|
|
"], canRead: [" + file.canRead() + "]");
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*
|
|
* These two methods would ideally be in the utility class
|
|
* org.apache.tomcat.util.buf.UriUtil but that class is not visible until
|
|
* after the class loaders have been constructed.
|
|
*/
|
|
private static URL buildClassLoaderUrl(String urlString) throws MalformedURLException {
|
|
// URLs passed to class loaders may point to directories that contain
|
|
// JARs. If these URLs are used to construct URLs for resources in a JAR
|
|
// the URL will be used as is. It is therefore necessary to ensure that
|
|
// the sequence "!/" is not present in a class loader URL.
|
|
String result = urlString.replaceAll("!/", "%21/");
|
|
return new URL(result);
|
|
}
|
|
|
|
|
|
private static URL buildClassLoaderUrl(File file) throws MalformedURLException {
|
|
// Could be a directory or a file
|
|
String fileUrlString = file.toURI().toString();
|
|
fileUrlString = fileUrlString.replaceAll("!/", "%21/");
|
|
return new URL(fileUrlString);
|
|
}
|
|
|
|
|
|
public enum RepositoryType {
|
|
DIR,
|
|
GLOB,
|
|
JAR,
|
|
URL
|
|
}
|
|
|
|
public static class Repository {
|
|
private final String location;
|
|
private final RepositoryType type;
|
|
|
|
public Repository(String location, RepositoryType type) {
|
|
this.location = location;
|
|
this.type = type;
|
|
}
|
|
|
|
public String getLocation() {
|
|
return location;
|
|
}
|
|
|
|
public RepositoryType getType() {
|
|
return type;
|
|
}
|
|
}
|
|
}
|