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}