/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.yarn.util; import java.io.File; import java.io.FilenameFilter; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience.Public; import org.apache.hadoop.classification.InterfaceStability.Unstable; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Splitter; /** * A {@link URLClassLoader} for YARN application isolation. Classes from * the application JARs are loaded in preference to the parent loader. */ @Public @Unstable public class ApplicationClassLoader extends URLClassLoader { private static final Log LOG = LogFactory.getLog(ApplicationClassLoader.class.getName()); private static final FilenameFilter JAR_FILENAME_FILTER = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".jar") || name.endsWith(".JAR"); } }; private ClassLoader parent; private List<String> systemClasses; public ApplicationClassLoader(URL[] urls, ClassLoader parent, List<String> systemClasses) { super(urls, parent); this.parent = parent; if (parent == null) { throw new IllegalArgumentException("No parent classloader!"); } this.systemClasses = systemClasses; } public ApplicationClassLoader(String classpath, ClassLoader parent, List<String> systemClasses) throws MalformedURLException { this(constructUrlsFromClasspath(classpath), parent, systemClasses); } @VisibleForTesting static URL[] constructUrlsFromClasspath(String classpath) throws MalformedURLException { List<URL> urls = new ArrayList<URL>(); for (String element : Splitter.on(File.pathSeparator).split(classpath)) { if (element.endsWith("/*")) { String dir = element.substring(0, element.length() - 1); File[] files = new File(dir).listFiles(JAR_FILENAME_FILTER); if (files != null) { for (File file : files) { urls.add(file.toURI().toURL()); } } } else { File file = new File(element); if (file.exists()) { urls.add(new File(element).toURI().toURL()); } } } return urls.toArray(new URL[urls.size()]); } @Override public URL getResource(String name) { URL url = null; if (!isSystemClass(name, systemClasses)) { url= findResource(name); if (url == null && name.startsWith("/")) { if (LOG.isDebugEnabled()) { LOG.debug("Remove leading / off " + name); } url= findResource(name.substring(1)); } } if (url == null) { url= parent.getResource(name); } if (url != null) { if (LOG.isDebugEnabled()) { LOG.debug("getResource("+name+")=" + url); } } return url; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return this.loadClass(name, false); } @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (LOG.isDebugEnabled()) { LOG.debug("Loading class: " + name); } Class<?> c = findLoadedClass(name); ClassNotFoundException ex = null; if (c == null && !isSystemClass(name, systemClasses)) { // Try to load class from this classloader's URLs. Note that this is like // the servlet spec, not the usual Java 2 behaviour where we ask the // parent to attempt to load first. try { c = findClass(name); if (LOG.isDebugEnabled() && c != null) { LOG.debug("Loaded class: " + name + " "); } } catch (ClassNotFoundException e) { if (LOG.isDebugEnabled()) { LOG.debug(e); } ex = e; } } if (c == null) { // try parent c = parent.loadClass(name); if (LOG.isDebugEnabled() && c != null) { LOG.debug("Loaded class from parent: " + name + " "); } } if (c == null) { throw ex != null ? ex : new ClassNotFoundException(name); } if (resolve) { resolveClass(c); } return c; } @VisibleForTesting static boolean isSystemClass(String name, List<String> systemClasses) { if (systemClasses != null) { String canonicalName = name.replace('/', '.'); while (canonicalName.startsWith(".")) { canonicalName=canonicalName.substring(1); } for (String c : systemClasses) { boolean result = true; if (c.startsWith("-")) { c = c.substring(1); result = false; } if (c.endsWith(".") && canonicalName.startsWith(c)) { return result; } else if (canonicalName.equals(c)) { return result; } } } return false; } }