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