View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.coprocessor;
19  
20  import java.net.URL;
21  import java.net.URLClassLoader;
22  import java.util.List;
23  import java.util.regex.Pattern;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  
28  /**
29   * ClassLoader used to load Coprocessor instances.
30   * 
31   * This ClassLoader always tries to load classes from the Coprocessor jar first 
32   * before delegating to the parent ClassLoader, thus avoiding dependency 
33   * conflicts between HBase's classpath and classes in the coprocessor's jar.  
34   * Certain classes are exempt from being loaded by this ClassLoader because it 
35   * would prevent them from being cast to the equivalent classes in the region 
36   * server.  For example, the Coprocessor interface needs to be loaded by the 
37   * region server's ClassLoader to prevent a ClassCastException when casting the 
38   * coprocessor implementation.
39   * 
40   * This ClassLoader also handles resource loading.  In most cases this 
41   * ClassLoader will attempt to load resources from the coprocessor jar first 
42   * before delegating to the parent.  However, like in class loading, 
43   * some resources need to be handled differently.  For all of the Hadoop 
44   * default configurations (e.g. hbase-default.xml) we will check the parent 
45   * ClassLoader first to prevent issues such as failing the HBase default 
46   * configuration version check.
47   */
48  public class CoprocessorClassLoader extends URLClassLoader {
49    private static final Log LOG = 
50        LogFactory.getLog(CoprocessorClassLoader.class);
51    
52    /**
53     * If the class being loaded starts with any of these strings, we will skip
54     * trying to load it from the coprocessor jar and instead delegate 
55     * directly to the parent ClassLoader.
56     */
57    private static final String[] CLASS_PREFIX_EXEMPTIONS = new String[] {
58      // Java standard library:
59      "com.sun.",
60      "launcher.",
61      "java.",
62      "javax.",
63      "org.ietf",
64      "org.omg",
65      "org.w3c",
66      "org.xml",
67      "sunw.",
68      // logging
69      "org.apache.commons.logging",
70      "org.apache.log4j",
71      "com.hadoop",
72      // Hadoop/HBase/ZK:
73      "org.apache.hadoop",
74      "org.apache.zookeeper",
75    };
76    
77    /**
78     * If the resource being loaded matches any of these patterns, we will first 
79     * attempt to load the resource with the parent ClassLoader.  Only if the 
80     * resource is not found by the parent do we attempt to load it from the 
81     * coprocessor jar.
82     */
83    private static final Pattern[] RESOURCE_LOAD_PARENT_FIRST_PATTERNS = 
84        new Pattern[] {
85      Pattern.compile("^[^-]+-default\\.xml$")
86    };
87  
88    /**
89     * Parent classloader used to load any class not matching the exemption list.
90     */
91    private final ClassLoader parent;
92  
93    /**
94     * Creates a CoprocessorClassLoader that loads classes from the given paths.
95     * @param paths paths from which to load classes.
96     * @param parent the parent ClassLoader to set.
97     */
98    public CoprocessorClassLoader(List<URL> paths, ClassLoader parent) {
99      super(paths.toArray(new URL[]{}), parent);
100     this.parent = parent;
101     if (parent == null) {
102       throw new IllegalArgumentException("No parent classloader!");
103     }
104   }
105 
106   @Override
107   synchronized public Class<?> loadClass(String name) 
108       throws ClassNotFoundException {
109     // Delegate to the parent immediately if this class is exempt
110     if (isClassExempt(name)) {
111       if (LOG.isDebugEnabled()) {
112         LOG.debug("Skipping exempt class " + name + 
113             " - delegating directly to parent");
114       }
115       return parent.loadClass(name);
116     }
117 
118     // Check whether the class has already been loaded:
119     Class<?> clasz = findLoadedClass(name);
120     if (clasz != null) {
121       if (LOG.isDebugEnabled()) {
122         LOG.debug("Class " + name + " already loaded");
123       }
124     }
125     else {
126       try {
127         // Try to find this class using the URLs passed to this ClassLoader, 
128         // which includes the coprocessor jar
129         if (LOG.isDebugEnabled()) {
130           LOG.debug("Finding class: " + name);
131         }
132         clasz = findClass(name);
133       } catch (ClassNotFoundException e) {
134         // Class not found using this ClassLoader, so delegate to parent
135         if (LOG.isDebugEnabled()) {
136           LOG.debug("Class " + name + " not found - delegating to parent");
137         }
138         try {
139           clasz = parent.loadClass(name);
140         } catch (ClassNotFoundException e2) {
141           // Class not found in this ClassLoader or in the parent ClassLoader
142           // Log some debug output before rethrowing ClassNotFoundException
143           if (LOG.isDebugEnabled()) {
144             LOG.debug("Class " + name + " not found in parent loader");
145           }
146           throw e2;
147         }
148       }
149     }
150     
151     return clasz;
152   }
153   
154   @Override
155   synchronized public URL getResource(String name) {
156     URL resource = null;
157     boolean parentLoaded = false;
158     
159     // Delegate to the parent first if necessary
160     if (loadResourceUsingParentFirst(name)) {
161       if (LOG.isDebugEnabled()) {
162         LOG.debug("Checking parent first for resource " + name);
163       }
164       resource = super.getResource(name);
165       parentLoaded = true;
166     }
167     
168     if (resource == null) {
169       // Try to find the resource in the coprocessor jar
170       resource = findResource(name);
171       if ((resource == null) && !parentLoaded) {
172         // Not found in the coprocessor jar and we haven't attempted to load 
173         // the resource in the parent yet; fall back to the parent
174         resource = super.getResource(name);
175       }
176     }
177 
178     return resource;
179   }
180   
181   /**
182    * Determines whether the given class should be exempt from being loaded 
183    * by this ClassLoader.
184    * @param name the name of the class to test.
185    * @return true if the class should *not* be loaded by this ClassLoader; 
186    * false otherwise.
187    */
188   protected boolean isClassExempt(String name) {
189     for (String exemptPrefix : CLASS_PREFIX_EXEMPTIONS) {
190       if (name.startsWith(exemptPrefix)) {
191         return true;
192       }
193     }
194     return false;
195   }
196   
197   /**
198    * Determines whether we should attempt to load the given resource using the  
199    * parent first before attempting to load the resource using this ClassLoader.
200    * @param name the name of the resource to test.
201    * @return true if we should attempt to load the resource using the parent 
202    * first; false if we should attempt to load the resource using this 
203    * ClassLoader first.
204    */
205   protected boolean loadResourceUsingParentFirst(String name) {
206     for (Pattern resourcePattern : RESOURCE_LOAD_PARENT_FIRST_PATTERNS) {
207       if (resourcePattern.matcher(name).matches()) {
208         return true;
209       }
210     }
211     return false;
212   }
213 }