| /* |
| * 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; |
| } |
| } |
| } |