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}