blob: a72f2ac1a8df108178b1a985817034e38f1b0f94 [file] [log] [blame]
/*
* 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.
*
* @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.exists() || !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.exists() ||
!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.
*
* @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.exists() || !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.exists() || !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 static 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;
}
}
}