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 this.parent = parent; 098 if (parent == null) { 099 throw new IllegalArgumentException("No parent classloader!"); 100 } 101 // if the caller-specified system classes are null or empty, use the default 102 this.systemClasses = (systemClasses == null || systemClasses.isEmpty()) ? 103 Arrays.asList(StringUtils.getTrimmedStrings(SYSTEM_CLASSES_DEFAULT)) : 104 systemClasses; 105 LOG.info("classpath: " + Arrays.toString(urls)); 106 LOG.info("system classes: " + this.systemClasses); 107 } 108 109 public ApplicationClassLoader(String classpath, ClassLoader parent, 110 List<String> systemClasses) throws MalformedURLException { 111 this(constructUrlsFromClasspath(classpath), parent, systemClasses); 112 } 113 114 static URL[] constructUrlsFromClasspath(String classpath) 115 throws MalformedURLException { 116 List<URL> urls = new ArrayList<URL>(); 117 for (String element : classpath.split(File.pathSeparator)) { 118 if (element.endsWith("/*")) { 119 String dir = element.substring(0, element.length() - 1); 120 File[] files = new File(dir).listFiles(JAR_FILENAME_FILTER); 121 if (files != null) { 122 for (File file : files) { 123 urls.add(file.toURI().toURL()); 124 } 125 } 126 } else { 127 File file = new File(element); 128 if (file.exists()) { 129 urls.add(new File(element).toURI().toURL()); 130 } 131 } 132 } 133 return urls.toArray(new URL[urls.size()]); 134 } 135 136 @Override 137 public URL getResource(String name) { 138 URL url = null; 139 140 if (!isSystemClass(name, systemClasses)) { 141 url= findResource(name); 142 if (url == null && name.startsWith("/")) { 143 if (LOG.isDebugEnabled()) { 144 LOG.debug("Remove leading / off " + name); 145 } 146 url= findResource(name.substring(1)); 147 } 148 } 149 150 if (url == null) { 151 url= parent.getResource(name); 152 } 153 154 if (url != null) { 155 if (LOG.isDebugEnabled()) { 156 LOG.debug("getResource("+name+")=" + url); 157 } 158 } 159 160 return url; 161 } 162 163 @Override 164 public Class<?> loadClass(String name) throws ClassNotFoundException { 165 return this.loadClass(name, false); 166 } 167 168 @Override 169 protected synchronized Class<?> loadClass(String name, boolean resolve) 170 throws ClassNotFoundException { 171 172 if (LOG.isDebugEnabled()) { 173 LOG.debug("Loading class: " + name); 174 } 175 176 Class<?> c = findLoadedClass(name); 177 ClassNotFoundException ex = null; 178 179 if (c == null && !isSystemClass(name, systemClasses)) { 180 // Try to load class from this classloader's URLs. Note that this is like 181 // the servlet spec, not the usual Java 2 behaviour where we ask the 182 // parent to attempt to load first. 183 try { 184 c = findClass(name); 185 if (LOG.isDebugEnabled() && c != null) { 186 LOG.debug("Loaded class: " + name + " "); 187 } 188 } catch (ClassNotFoundException e) { 189 if (LOG.isDebugEnabled()) { 190 LOG.debug(e); 191 } 192 ex = e; 193 } 194 } 195 196 if (c == null) { // try parent 197 c = parent.loadClass(name); 198 if (LOG.isDebugEnabled() && c != null) { 199 LOG.debug("Loaded class from parent: " + name + " "); 200 } 201 } 202 203 if (c == null) { 204 throw ex != null ? ex : new ClassNotFoundException(name); 205 } 206 207 if (resolve) { 208 resolveClass(c); 209 } 210 211 return c; 212 } 213 214 /** 215 * Checks if a class should be included as a system class. 216 * 217 * A class is a system class if and only if it matches one of the positive 218 * patterns and none of the negative ones. 219 * 220 * @param name the class name to check 221 * @param systemClasses a list of system class configurations. 222 * @return true if the class is a system class 223 */ 224 public static boolean isSystemClass(String name, List<String> systemClasses) { 225 boolean result = false; 226 if (systemClasses != null) { 227 String canonicalName = name.replace('/', '.'); 228 while (canonicalName.startsWith(".")) { 229 canonicalName=canonicalName.substring(1); 230 } 231 for (String c : systemClasses) { 232 boolean shouldInclude = true; 233 if (c.startsWith("-")) { 234 c = c.substring(1); 235 shouldInclude = false; 236 } 237 if (canonicalName.startsWith(c)) { 238 if ( c.endsWith(".") // package 239 || canonicalName.length() == c.length() // class 240 || canonicalName.length() > c.length() // nested 241 && canonicalName.charAt(c.length()) == '$' ) { 242 if (shouldInclude) { 243 result = true; 244 } else { 245 return false; 246 } 247 } 248 } 249 } 250 } 251 return result; 252 } 253}