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
019 package org.apache.hadoop.util;
020
021 import java.io.File;
022 import java.io.FilenameFilter;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.net.MalformedURLException;
026 import java.net.URL;
027 import java.net.URLClassLoader;
028 import java.util.ArrayList;
029 import java.util.Arrays;
030 import java.util.List;
031 import java.util.Properties;
032
033 import org.apache.commons.logging.Log;
034 import org.apache.commons.logging.LogFactory;
035 import org.apache.hadoop.classification.InterfaceAudience.Public;
036 import 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
044 public 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 }