/* * 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<URL>(); // 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<URL>(); 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 String location; private RepositoryType type; public Repository(String location, RepositoryType type) { this.location = location; this.type = type; } public String getLocation() { return location; } public RepositoryType getType() { return type; } } }