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.yarn.util;
020    
021    import java.io.File;
022    import java.io.FilenameFilter;
023    import java.net.MalformedURLException;
024    import java.net.URL;
025    import java.net.URLClassLoader;
026    import java.util.ArrayList;
027    import java.util.List;
028    
029    import org.apache.commons.logging.Log;
030    import org.apache.commons.logging.LogFactory;
031    import org.apache.hadoop.classification.InterfaceAudience.Public;
032    import org.apache.hadoop.classification.InterfaceStability.Unstable;
033    
034    import com.google.common.annotations.VisibleForTesting;
035    import com.google.common.base.Splitter;
036    
037    /**
038     * A {@link URLClassLoader} for YARN application isolation. Classes from
039     * the application JARs are loaded in preference to the parent loader.
040     */
041    @Public
042    @Unstable
043    public class ApplicationClassLoader extends URLClassLoader {
044    
045      private static final Log LOG =
046        LogFactory.getLog(ApplicationClassLoader.class.getName());
047      
048      private static final FilenameFilter JAR_FILENAME_FILTER =
049        new FilenameFilter() {
050          @Override
051          public boolean accept(File dir, String name) {
052            return name.endsWith(".jar") || name.endsWith(".JAR");
053          }
054      };
055      
056      private ClassLoader parent;
057      private List<String> systemClasses;
058    
059      public ApplicationClassLoader(URL[] urls, ClassLoader parent,
060          List<String> systemClasses) {
061        super(urls, parent);
062        this.parent = parent;
063        if (parent == null) {
064          throw new IllegalArgumentException("No parent classloader!");
065        }
066        this.systemClasses = systemClasses;
067      }
068      
069      public ApplicationClassLoader(String classpath, ClassLoader parent,
070          List<String> systemClasses) throws MalformedURLException {
071        this(constructUrlsFromClasspath(classpath), parent, systemClasses);
072      }
073      
074      @VisibleForTesting
075      static URL[] constructUrlsFromClasspath(String classpath)
076          throws MalformedURLException {
077        List<URL> urls = new ArrayList<URL>();
078        for (String element : Splitter.on(File.pathSeparator).split(classpath)) {
079          if (element.endsWith("/*")) {
080            String dir = element.substring(0, element.length() - 1);
081            File[] files = new File(dir).listFiles(JAR_FILENAME_FILTER);
082            if (files != null) {
083              for (File file : files) {
084                urls.add(file.toURI().toURL());
085              }
086            }
087          } else {
088            File file = new File(element);
089            if (file.exists()) {
090              urls.add(new File(element).toURI().toURL());
091            }
092          }
093        }
094        return urls.toArray(new URL[urls.size()]);
095      }
096    
097      @Override
098      public URL getResource(String name) {
099        URL url = null;
100        
101        if (!isSystemClass(name, systemClasses)) {
102          url= findResource(name);
103          if (url == null && name.startsWith("/")) {
104            if (LOG.isDebugEnabled()) {
105              LOG.debug("Remove leading / off " + name);
106            }
107            url= findResource(name.substring(1));
108          }
109        }
110    
111        if (url == null) {
112          url= parent.getResource(name);
113        }
114    
115        if (url != null) {
116          if (LOG.isDebugEnabled()) {
117            LOG.debug("getResource("+name+")=" + url);
118          }
119        }
120        
121        return url;
122      }
123    
124      @Override
125      public Class<?> loadClass(String name) throws ClassNotFoundException {
126        return this.loadClass(name, false);
127      }
128    
129      @Override
130      protected synchronized Class<?> loadClass(String name, boolean resolve)
131          throws ClassNotFoundException {
132        
133        if (LOG.isDebugEnabled()) {
134          LOG.debug("Loading class: " + name);
135        }
136    
137        Class<?> c = findLoadedClass(name);
138        ClassNotFoundException ex = null;
139    
140        if (c == null && !isSystemClass(name, systemClasses)) {
141          // Try to load class from this classloader's URLs. Note that this is like
142          // the servlet spec, not the usual Java 2 behaviour where we ask the
143          // parent to attempt to load first.
144          try {
145            c = findClass(name);
146            if (LOG.isDebugEnabled() && c != null) {
147              LOG.debug("Loaded class: " + name + " ");
148            }
149          } catch (ClassNotFoundException e) {
150            if (LOG.isDebugEnabled()) {
151              LOG.debug(e);
152            }
153            ex = e;
154          }
155        }
156    
157        if (c == null) { // try parent
158          c = parent.loadClass(name);
159          if (LOG.isDebugEnabled() && c != null) {
160            LOG.debug("Loaded class from parent: " + name + " ");
161          }
162        }
163    
164        if (c == null) {
165          throw ex != null ? ex : new ClassNotFoundException(name);
166        }
167    
168        if (resolve) {
169          resolveClass(c);
170        }
171    
172        return c;
173      }
174    
175      @VisibleForTesting
176      public static boolean isSystemClass(String name, List<String> systemClasses) {
177        if (systemClasses != null) {
178          String canonicalName = name.replace('/', '.');
179          while (canonicalName.startsWith(".")) {
180            canonicalName=canonicalName.substring(1);
181          }
182          for (String c : systemClasses) {
183            boolean result = true;
184            if (c.startsWith("-")) {
185              c = c.substring(1);
186              result = false;
187            }
188            if (c.endsWith(".") && canonicalName.startsWith(c)) {
189              return result;
190            } else if (canonicalName.equals(c)) {
191              return result;
192            }
193          }
194        }
195        return false;
196      }
197    }