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 InputStream is = null; 071 try { 072 is = ApplicationClassLoader.class.getClassLoader(). 073 getResourceAsStream(PROPERTIES_FILE); 074 if (is == null) { 075 throw new ExceptionInInitializerError("properties file " + 076 PROPERTIES_FILE + " is not found"); 077 } 078 Properties props = new Properties(); 079 props.load(is); 080 // get the system classes default 081 String systemClassesDefault = 082 props.getProperty(SYSTEM_CLASSES_DEFAULT_KEY); 083 if (systemClassesDefault == null) { 084 throw new ExceptionInInitializerError("property " + 085 SYSTEM_CLASSES_DEFAULT_KEY + " is not found"); 086 } 087 SYSTEM_CLASSES_DEFAULT = systemClassesDefault; 088 } catch (IOException e) { 089 throw new ExceptionInInitializerError(e); 090 } 091 } 092 093 private final ClassLoader parent; 094 private final List<String> systemClasses; 095 096 public ApplicationClassLoader(URL[] urls, ClassLoader parent, 097 List<String> systemClasses) { 098 super(urls, parent); 099 if (LOG.isDebugEnabled()) { 100 LOG.debug("urls: " + Arrays.toString(urls)); 101 LOG.debug("system classes: " + systemClasses); 102 } 103 this.parent = parent; 104 if (parent == null) { 105 throw new IllegalArgumentException("No parent classloader!"); 106 } 107 // if the caller-specified system classes are null or empty, use the default 108 this.systemClasses = (systemClasses == null || systemClasses.isEmpty()) ? 109 Arrays.asList(StringUtils.getTrimmedStrings(SYSTEM_CLASSES_DEFAULT)) : 110 systemClasses; 111 LOG.info("system classes: " + this.systemClasses); 112 } 113 114 public ApplicationClassLoader(String classpath, ClassLoader parent, 115 List<String> systemClasses) throws MalformedURLException { 116 this(constructUrlsFromClasspath(classpath), parent, systemClasses); 117 } 118 119 static URL[] constructUrlsFromClasspath(String classpath) 120 throws MalformedURLException { 121 List<URL> urls = new ArrayList<URL>(); 122 for (String element : classpath.split(File.pathSeparator)) { 123 if (element.endsWith("/*")) { 124 String dir = element.substring(0, element.length() - 1); 125 File[] files = new File(dir).listFiles(JAR_FILENAME_FILTER); 126 if (files != null) { 127 for (File file : files) { 128 urls.add(file.toURI().toURL()); 129 } 130 } 131 } else { 132 File file = new File(element); 133 if (file.exists()) { 134 urls.add(new File(element).toURI().toURL()); 135 } 136 } 137 } 138 return urls.toArray(new URL[urls.size()]); 139 } 140 141 @Override 142 public URL getResource(String name) { 143 URL url = null; 144 145 if (!isSystemClass(name, systemClasses)) { 146 url= findResource(name); 147 if (url == null && name.startsWith("/")) { 148 if (LOG.isDebugEnabled()) { 149 LOG.debug("Remove leading / off " + name); 150 } 151 url= findResource(name.substring(1)); 152 } 153 } 154 155 if (url == null) { 156 url= parent.getResource(name); 157 } 158 159 if (url != null) { 160 if (LOG.isDebugEnabled()) { 161 LOG.debug("getResource("+name+")=" + url); 162 } 163 } 164 165 return url; 166 } 167 168 @Override 169 public Class<?> loadClass(String name) throws ClassNotFoundException { 170 return this.loadClass(name, false); 171 } 172 173 @Override 174 protected synchronized Class<?> loadClass(String name, boolean resolve) 175 throws ClassNotFoundException { 176 177 if (LOG.isDebugEnabled()) { 178 LOG.debug("Loading class: " + name); 179 } 180 181 Class<?> c = findLoadedClass(name); 182 ClassNotFoundException ex = null; 183 184 if (c == null && !isSystemClass(name, systemClasses)) { 185 // Try to load class from this classloader's URLs. Note that this is like 186 // the servlet spec, not the usual Java 2 behaviour where we ask the 187 // parent to attempt to load first. 188 try { 189 c = findClass(name); 190 if (LOG.isDebugEnabled() && c != null) { 191 LOG.debug("Loaded class: " + name + " "); 192 } 193 } catch (ClassNotFoundException e) { 194 if (LOG.isDebugEnabled()) { 195 LOG.debug(e); 196 } 197 ex = e; 198 } 199 } 200 201 if (c == null) { // try parent 202 c = parent.loadClass(name); 203 if (LOG.isDebugEnabled() && c != null) { 204 LOG.debug("Loaded class from parent: " + name + " "); 205 } 206 } 207 208 if (c == null) { 209 throw ex != null ? ex : new ClassNotFoundException(name); 210 } 211 212 if (resolve) { 213 resolveClass(c); 214 } 215 216 return c; 217 } 218 219 public static boolean isSystemClass(String name, List<String> systemClasses) { 220 if (systemClasses != null) { 221 String canonicalName = name.replace('/', '.'); 222 while (canonicalName.startsWith(".")) { 223 canonicalName=canonicalName.substring(1); 224 } 225 for (String c : systemClasses) { 226 boolean result = true; 227 if (c.startsWith("-")) { 228 c = c.substring(1); 229 result = false; 230 } 231 if (c.endsWith(".") && canonicalName.startsWith(c)) { 232 return result; 233 } else if (canonicalName.equals(c)) { 234 return result; 235 } 236 } 237 } 238 return false; 239 } 240}