001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.util; 020 021import java.io.File; 022import java.io.FilenameFilter; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.MalformedURLException; 026import java.net.URL; 027import java.net.URLClassLoader; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Properties; 032 033import org.apache.commons.logging.Log; 034import org.apache.commons.logging.LogFactory; 035import org.apache.hadoop.classification.InterfaceAudience.Public; 036import org.apache.hadoop.classification.InterfaceStability.Unstable; 037 038/** 039 * A {@link URLClassLoader} for application isolation. Classes from the 040 * application JARs are loaded in preference to the parent loader. 041 */ 042@Public 043@Unstable 044public class ApplicationClassLoader extends URLClassLoader { 045 /** 046 * Default value of the system classes if the user did not override them. 047 * JDK classes, hadoop classes and resources, and some select third-party 048 * classes are considered system classes, and are not loaded by the 049 * application classloader. 050 */ 051 public static final String SYSTEM_CLASSES_DEFAULT; 052 053 private static final String PROPERTIES_FILE = 054 "org.apache.hadoop.application-classloader.properties"; 055 private static final String SYSTEM_CLASSES_DEFAULT_KEY = 056 "system.classes.default"; 057 058 private static final Log LOG = 059 LogFactory.getLog(ApplicationClassLoader.class.getName()); 060 061 private static final FilenameFilter JAR_FILENAME_FILTER = 062 new FilenameFilter() { 063 @Override 064 public boolean accept(File dir, String name) { 065 return name.endsWith(".jar") || name.endsWith(".JAR"); 066 } 067 }; 068 069 static { 070 try (InputStream is = ApplicationClassLoader.class.getClassLoader() 071 .getResourceAsStream(PROPERTIES_FILE);) { 072 if (is == null) { 073 throw new ExceptionInInitializerError("properties file " + 074 PROPERTIES_FILE + " is not found"); 075 } 076 Properties props = new Properties(); 077 props.load(is); 078 // get the system classes default 079 String systemClassesDefault = 080 props.getProperty(SYSTEM_CLASSES_DEFAULT_KEY); 081 if (systemClassesDefault == null) { 082 throw new ExceptionInInitializerError("property " + 083 SYSTEM_CLASSES_DEFAULT_KEY + " is not found"); 084 } 085 SYSTEM_CLASSES_DEFAULT = systemClassesDefault; 086 } catch (IOException e) { 087 throw new ExceptionInInitializerError(e); 088 } 089 } 090 091 private final ClassLoader parent; 092 private final List<String> systemClasses; 093 094 public ApplicationClassLoader(URL[] urls, ClassLoader parent, 095 List<String> systemClasses) { 096 super(urls, parent); 097 if (LOG.isDebugEnabled()) { 098 LOG.debug("urls: " + Arrays.toString(urls)); 099 LOG.debug("system classes: " + systemClasses); 100 } 101 this.parent = parent; 102 if (parent == null) { 103 throw new IllegalArgumentException("No parent classloader!"); 104 } 105 // if the caller-specified system classes are null or empty, use the default 106 this.systemClasses = (systemClasses == null || systemClasses.isEmpty()) ? 107 Arrays.asList(StringUtils.getTrimmedStrings(SYSTEM_CLASSES_DEFAULT)) : 108 systemClasses; 109 LOG.info("system classes: " + this.systemClasses); 110 } 111 112 public ApplicationClassLoader(String classpath, ClassLoader parent, 113 List<String> systemClasses) throws MalformedURLException { 114 this(constructUrlsFromClasspath(classpath), parent, systemClasses); 115 } 116 117 static URL[] constructUrlsFromClasspath(String classpath) 118 throws MalformedURLException { 119 List<URL> urls = new ArrayList<URL>(); 120 for (String element : classpath.split(File.pathSeparator)) { 121 if (element.endsWith("/*")) { 122 String dir = element.substring(0, element.length() - 1); 123 File[] files = new File(dir).listFiles(JAR_FILENAME_FILTER); 124 if (files != null) { 125 for (File file : files) { 126 urls.add(file.toURI().toURL()); 127 } 128 } 129 } else { 130 File file = new File(element); 131 if (file.exists()) { 132 urls.add(new File(element).toURI().toURL()); 133 } 134 } 135 } 136 return urls.toArray(new URL[urls.size()]); 137 } 138 139 @Override 140 public URL getResource(String name) { 141 URL url = null; 142 143 if (!isSystemClass(name, systemClasses)) { 144 url= findResource(name); 145 if (url == null && name.startsWith("/")) { 146 if (LOG.isDebugEnabled()) { 147 LOG.debug("Remove leading / off " + name); 148 } 149 url= findResource(name.substring(1)); 150 } 151 } 152 153 if (url == null) { 154 url= parent.getResource(name); 155 } 156 157 if (url != null) { 158 if (LOG.isDebugEnabled()) { 159 LOG.debug("getResource("+name+")=" + url); 160 } 161 } 162 163 return url; 164 } 165 166 @Override 167 public Class<?> loadClass(String name) throws ClassNotFoundException { 168 return this.loadClass(name, false); 169 } 170 171 @Override 172 protected synchronized Class<?> loadClass(String name, boolean resolve) 173 throws ClassNotFoundException { 174 175 if (LOG.isDebugEnabled()) { 176 LOG.debug("Loading class: " + name); 177 } 178 179 Class<?> c = findLoadedClass(name); 180 ClassNotFoundException ex = null; 181 182 if (c == null && !isSystemClass(name, systemClasses)) { 183 // Try to load class from this classloader's URLs. Note that this is like 184 // the servlet spec, not the usual Java 2 behaviour where we ask the 185 // parent to attempt to load first. 186 try { 187 c = findClass(name); 188 if (LOG.isDebugEnabled() && c != null) { 189 LOG.debug("Loaded class: " + name + " "); 190 } 191 } catch (ClassNotFoundException e) { 192 if (LOG.isDebugEnabled()) { 193 LOG.debug(e); 194 } 195 ex = e; 196 } 197 } 198 199 if (c == null) { // try parent 200 c = parent.loadClass(name); 201 if (LOG.isDebugEnabled() && c != null) { 202 LOG.debug("Loaded class from parent: " + name + " "); 203 } 204 } 205 206 if (c == null) { 207 throw ex != null ? ex : new ClassNotFoundException(name); 208 } 209 210 if (resolve) { 211 resolveClass(c); 212 } 213 214 return c; 215 } 216 217 /** 218 * Checks if a class should be included as a system class. 219 * 220 * A class is a system class if and only if it matches one of the positive 221 * patterns and none of the negative ones. 222 * 223 * @param name the class name to check 224 * @param systemClasses a list of system class configurations. 225 * @return true if the class is a system class 226 */ 227 public static boolean isSystemClass(String name, List<String> systemClasses) { 228 boolean result = false; 229 if (systemClasses != null) { 230 String canonicalName = name.replace('/', '.'); 231 while (canonicalName.startsWith(".")) { 232 canonicalName=canonicalName.substring(1); 233 } 234 for (String c : systemClasses) { 235 boolean shouldInclude = true; 236 if (c.startsWith("-")) { 237 c = c.substring(1); 238 shouldInclude = false; 239 } 240 if (canonicalName.startsWith(c)) { 241 if ( c.endsWith(".") // package 242 || canonicalName.length() == c.length() // class 243 || canonicalName.length() > c.length() // nested 244 && canonicalName.charAt(c.length()) == '$' ) { 245 if (shouldInclude) { 246 result = true; 247 } else { 248 return false; 249 } 250 } 251 } 252 } 253 } 254 return result; 255 } 256}