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.IOException; 023import java.io.InputStream; 024import java.net.MalformedURLException; 025import java.net.URL; 026import java.net.URLClassLoader; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.List; 030import java.util.Properties; 031 032import org.apache.commons.logging.Log; 033import org.apache.commons.logging.LogFactory; 034import org.apache.hadoop.classification.InterfaceAudience.Public; 035import org.apache.hadoop.classification.InterfaceStability.Unstable; 036import org.apache.hadoop.fs.FileUtil; 037import org.apache.hadoop.fs.Path; 038 039/** 040 * A {@link URLClassLoader} for application isolation. Classes from the 041 * application JARs are loaded in preference to the parent loader. 042 */ 043@Public 044@Unstable 045public class ApplicationClassLoader extends URLClassLoader { 046 /** 047 * Default value of the system classes if the user did not override them. 048 * JDK classes, hadoop classes and resources, and some select third-party 049 * classes are considered system classes, and are not loaded by the 050 * application classloader. 051 */ 052 public static final String SYSTEM_CLASSES_DEFAULT; 053 054 private static final String PROPERTIES_FILE = 055 "org.apache.hadoop.application-classloader.properties"; 056 private static final String SYSTEM_CLASSES_DEFAULT_KEY = 057 "system.classes.default"; 058 059 private static final Log LOG = 060 LogFactory.getLog(ApplicationClassLoader.class.getName()); 061 062 static { 063 try (InputStream is = ApplicationClassLoader.class.getClassLoader() 064 .getResourceAsStream(PROPERTIES_FILE);) { 065 if (is == null) { 066 throw new ExceptionInInitializerError("properties file " + 067 PROPERTIES_FILE + " is not found"); 068 } 069 Properties props = new Properties(); 070 props.load(is); 071 // get the system classes default 072 String systemClassesDefault = 073 props.getProperty(SYSTEM_CLASSES_DEFAULT_KEY); 074 if (systemClassesDefault == null) { 075 throw new ExceptionInInitializerError("property " + 076 SYSTEM_CLASSES_DEFAULT_KEY + " is not found"); 077 } 078 SYSTEM_CLASSES_DEFAULT = systemClassesDefault; 079 } catch (IOException e) { 080 throw new ExceptionInInitializerError(e); 081 } 082 } 083 084 private final ClassLoader parent; 085 private final List<String> systemClasses; 086 087 public ApplicationClassLoader(URL[] urls, ClassLoader parent, 088 List<String> systemClasses) { 089 super(urls, parent); 090 this.parent = parent; 091 if (parent == null) { 092 throw new IllegalArgumentException("No parent classloader!"); 093 } 094 // if the caller-specified system classes are null or empty, use the default 095 this.systemClasses = (systemClasses == null || systemClasses.isEmpty()) ? 096 Arrays.asList(StringUtils.getTrimmedStrings(SYSTEM_CLASSES_DEFAULT)) : 097 systemClasses; 098 LOG.info("classpath: " + Arrays.toString(urls)); 099 LOG.info("system classes: " + this.systemClasses); 100 } 101 102 public ApplicationClassLoader(String classpath, ClassLoader parent, 103 List<String> systemClasses) throws MalformedURLException { 104 this(constructUrlsFromClasspath(classpath), parent, systemClasses); 105 } 106 107 static URL[] constructUrlsFromClasspath(String classpath) 108 throws MalformedURLException { 109 List<URL> urls = new ArrayList<URL>(); 110 for (String element : classpath.split(File.pathSeparator)) { 111 if (element.endsWith("/*")) { 112 List<Path> jars = FileUtil.getJarsInDirectory(element); 113 if (!jars.isEmpty()) { 114 for (Path jar: jars) { 115 urls.add(jar.toUri().toURL()); 116 } 117 } 118 } else { 119 File file = new File(element); 120 if (file.exists()) { 121 urls.add(new File(element).toURI().toURL()); 122 } 123 } 124 } 125 return urls.toArray(new URL[urls.size()]); 126 } 127 128 @Override 129 public URL getResource(String name) { 130 URL url = null; 131 132 if (!isSystemClass(name, systemClasses)) { 133 url= findResource(name); 134 if (url == null && name.startsWith("/")) { 135 if (LOG.isDebugEnabled()) { 136 LOG.debug("Remove leading / off " + name); 137 } 138 url= findResource(name.substring(1)); 139 } 140 } 141 142 if (url == null) { 143 url= parent.getResource(name); 144 } 145 146 if (url != null) { 147 if (LOG.isDebugEnabled()) { 148 LOG.debug("getResource("+name+")=" + url); 149 } 150 } 151 152 return url; 153 } 154 155 @Override 156 public Class<?> loadClass(String name) throws ClassNotFoundException { 157 return this.loadClass(name, false); 158 } 159 160 @Override 161 protected synchronized Class<?> loadClass(String name, boolean resolve) 162 throws ClassNotFoundException { 163 164 if (LOG.isDebugEnabled()) { 165 LOG.debug("Loading class: " + name); 166 } 167 168 Class<?> c = findLoadedClass(name); 169 ClassNotFoundException ex = null; 170 171 if (c == null && !isSystemClass(name, systemClasses)) { 172 // Try to load class from this classloader's URLs. Note that this is like 173 // the servlet spec, not the usual Java 2 behaviour where we ask the 174 // parent to attempt to load first. 175 try { 176 c = findClass(name); 177 if (LOG.isDebugEnabled() && c != null) { 178 LOG.debug("Loaded class: " + name + " "); 179 } 180 } catch (ClassNotFoundException e) { 181 if (LOG.isDebugEnabled()) { 182 LOG.debug(e); 183 } 184 ex = e; 185 } 186 } 187 188 if (c == null) { // try parent 189 c = parent.loadClass(name); 190 if (LOG.isDebugEnabled() && c != null) { 191 LOG.debug("Loaded class from parent: " + name + " "); 192 } 193 } 194 195 if (c == null) { 196 throw ex != null ? ex : new ClassNotFoundException(name); 197 } 198 199 if (resolve) { 200 resolveClass(c); 201 } 202 203 return c; 204 } 205 206 /** 207 * Checks if a class should be included as a system class. 208 * 209 * A class is a system class if and only if it matches one of the positive 210 * patterns and none of the negative ones. 211 * 212 * @param name the class name to check 213 * @param systemClasses a list of system class configurations. 214 * @return true if the class is a system class 215 */ 216 public static boolean isSystemClass(String name, List<String> systemClasses) { 217 boolean result = false; 218 if (systemClasses != null) { 219 String canonicalName = name.replace('/', '.'); 220 while (canonicalName.startsWith(".")) { 221 canonicalName=canonicalName.substring(1); 222 } 223 for (String c : systemClasses) { 224 boolean shouldInclude = true; 225 if (c.startsWith("-")) { 226 c = c.substring(1); 227 shouldInclude = false; 228 } 229 if (canonicalName.startsWith(c)) { 230 if ( c.endsWith(".") // package 231 || canonicalName.length() == c.length() // class 232 || canonicalName.length() > c.length() // nested 233 && canonicalName.charAt(c.length()) == '$' ) { 234 if (shouldInclude) { 235 result = true; 236 } else { 237 return false; 238 } 239 } 240 } 241 } 242 } 243 return result; 244 } 245}