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    }