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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package org
.apache
.hadoop
.hbase
.util
;
21 import java
.io
.IOException
;
22 import java
.net
.MalformedURLException
;
24 import java
.util
.HashMap
;
26 import org
.apache
.hadoop
.conf
.Configuration
;
27 import org
.apache
.hadoop
.fs
.FileStatus
;
28 import org
.apache
.hadoop
.fs
.FileSystem
;
29 import org
.apache
.hadoop
.fs
.Path
;
30 import org
.apache
.yetus
.audience
.InterfaceAudience
;
31 import org
.slf4j
.Logger
;
32 import org
.slf4j
.LoggerFactory
;
35 * This is a class loader that can load classes dynamically from new
36 * jar files under a configured folder. The paths to the jar files are
37 * converted to URLs, and URLClassLoader logic is actually used to load
38 * classes. This class loader always uses its parent class loader
39 * to load a class at first. Only if its parent class loader
40 * can not load a class, we will try to load it using the logic here.
42 * The configured folder can be a HDFS path. In this case, the jar files
43 * under that folder will be copied to local at first under ${hbase.local.dir}/jars/.
44 * The local copy will be updated if the remote copy is updated, according to its
45 * last modified timestamp.
47 * We can't unload a class already loaded. So we will use the existing
48 * jar files we already know to load any class which can't be loaded
49 * using the parent class loader. If we still can't load the class from
50 * the existing jar files, we will check if any new jar file is added,
51 * if so, we will load the new jar file and try to load the class again.
52 * If still failed, a class not found exception will be thrown.
54 * Be careful in uploading new jar files and make sure all classes
55 * are consistent, otherwise, we may not be able to load your
58 @InterfaceAudience.Private
59 public class DynamicClassLoader
extends ClassLoaderBase
{
60 private static final Logger LOG
= LoggerFactory
.getLogger(DynamicClassLoader
.class);
62 // Dynamic jars are put under ${hbase.local.dir}/jars/
63 private static final String DYNAMIC_JARS_DIR
= File
.separator
64 + "jars" + File
.separator
;
66 private static final String DYNAMIC_JARS_DIR_KEY
= "hbase.dynamic.jars.dir";
68 private static final String DYNAMIC_JARS_OPTIONAL_CONF_KEY
= "hbase.use.dynamic.jars";
69 private static final boolean DYNAMIC_JARS_OPTIONAL_DEFAULT
= true;
71 // The user-provided value for using the DynamicClassLoader
72 private final boolean userConfigUseDynamicJars
;
73 // The current state of whether to use the DynamicClassLoader
74 private final boolean useDynamicJars
;
76 private File localDir
;
78 // FileSystem of the remote path, set only if remoteDir != null
79 private FileSystem remoteDirFs
;
80 private Path remoteDir
;
82 // Last modified time of local jars
83 private HashMap
<String
, Long
> jarModifiedTime
;
86 * Creates a DynamicClassLoader that can load classes dynamically
87 * from jar files under a specific folder.
89 * @param conf the configuration for the cluster.
90 * @param parent the parent ClassLoader to set.
92 public DynamicClassLoader(final Configuration conf
, final ClassLoader parent
) {
95 // Save off the user's original configuration value for the DynamicClassLoader
96 userConfigUseDynamicJars
= conf
.getBoolean(
97 DYNAMIC_JARS_OPTIONAL_CONF_KEY
, DYNAMIC_JARS_OPTIONAL_DEFAULT
);
99 boolean dynamicJarsEnabled
= userConfigUseDynamicJars
;
100 if (dynamicJarsEnabled
) {
103 dynamicJarsEnabled
= true;
104 } catch (Exception e
) {
105 LOG
.error("Disabling the DynamicClassLoader as it failed to initialize its temp directory."
106 + " Check your configuration and filesystem permissions. Custom coprocessor code may"
107 + " not be loaded as a result of this failure.", e
);
108 dynamicJarsEnabled
= false;
111 useDynamicJars
= dynamicJarsEnabled
;
114 // FindBugs: Making synchronized to avoid IS2_INCONSISTENT_SYNC complaints about
115 // remoteDirFs and jarModifiedTime being part synchronized protected.
116 private synchronized void initTempDir(final Configuration conf
) {
117 jarModifiedTime
= new HashMap
<>();
118 String localDirPath
= conf
.get(
119 LOCAL_DIR_KEY
, DEFAULT_LOCAL_DIR
) + DYNAMIC_JARS_DIR
;
120 localDir
= new File(localDirPath
);
121 if (!localDir
.mkdirs() && !localDir
.isDirectory()) {
122 throw new RuntimeException("Failed to create local dir " + localDir
.getPath()
123 + ", DynamicClassLoader failed to init");
126 String remotePath
= conf
.get(DYNAMIC_JARS_DIR_KEY
);
127 if (remotePath
== null || remotePath
.equals(localDirPath
)) {
128 remoteDir
= null; // ignore if it is the same as the local path
130 remoteDir
= new Path(remotePath
);
132 remoteDirFs
= remoteDir
.getFileSystem(conf
);
133 } catch (IOException ioe
) {
134 LOG
.warn("Failed to identify the fs of dir "
135 + remoteDir
+ ", ignored", ioe
);
142 public Class
<?
> loadClass(String name
)
143 throws ClassNotFoundException
{
145 return parent
.loadClass(name
);
146 } catch (ClassNotFoundException e
) {
147 if (useDynamicJars
) {
148 LOG
.debug("Class {} not found - using dynamical class loader", name
);
149 return tryRefreshClass(name
);
150 } else if (userConfigUseDynamicJars
) {
151 // If the user tried to enable the DCL, then warn again.
152 LOG
.debug("Not checking DynamicClassLoader for missing class because it is disabled."
153 + " See the log for previous errors.");
159 private Class
<?
> tryRefreshClass(String name
) throws ClassNotFoundException
{
160 synchronized (getClassLoadingLock(name
)) {
161 // Check whether the class has already been loaded:
162 Class
<?
> clasz
= findLoadedClass(name
);
165 if (LOG
.isDebugEnabled()) {
166 LOG
.debug("Class {} already loaded", name
);
170 if (LOG
.isDebugEnabled()) {
171 LOG
.debug("Finding class: {}", name
);
174 clasz
= findClass(name
);
175 } catch (ClassNotFoundException cnfe
) {
176 // Load new jar files if any
177 if (LOG
.isDebugEnabled()) {
178 LOG
.debug("Loading new jar files, if any");
183 if (LOG
.isDebugEnabled()) {
184 LOG
.debug("Finding class again: {}", name
);
187 clasz
= findClass(name
);
195 private synchronized void loadNewJars() {
196 // Refresh local jar file lists
197 File
[] files
= localDir
== null ?
null : localDir
.listFiles();
199 for (File file
: files
) {
200 String fileName
= file
.getName();
201 if (jarModifiedTime
.containsKey(fileName
)) {
204 if (file
.isFile() && fileName
.endsWith(".jar")) {
205 jarModifiedTime
.put(fileName
, file
.lastModified());
207 URL url
= file
.toURI().toURL();
209 } catch (MalformedURLException mue
) {
210 // This should not happen, just log it
211 LOG
.warn("Failed to load new jar " + fileName
, mue
);
217 // Check remote files
218 FileStatus
[] statuses
= null;
219 if (remoteDir
!= null) {
221 statuses
= remoteDirFs
.listStatus(remoteDir
);
222 } catch (IOException ioe
) {
223 LOG
.warn("Failed to check remote dir status " + remoteDir
, ioe
);
226 if (statuses
== null || statuses
.length
== 0) {
227 return; // no remote files at all
230 for (FileStatus status
: statuses
) {
231 if (status
.isDirectory()) {
232 continue; // No recursive lookup
235 Path path
= status
.getPath();
236 String fileName
= path
.getName();
237 if (!fileName
.endsWith(".jar")) {
238 if (LOG
.isDebugEnabled()) {
239 LOG
.debug("Ignored non-jar file {}", fileName
);
241 continue; // Ignore non-jar files
243 Long cachedLastModificationTime
= jarModifiedTime
.get(fileName
);
244 if (cachedLastModificationTime
!= null) {
245 long lastModified
= status
.getModificationTime();
246 if (lastModified
< cachedLastModificationTime
) {
247 // There could be some race, for example, someone uploads
248 // a new one right in the middle the old one is copied to
249 // local. We can check the size as well. But it is still
250 // not guaranteed. This should be rare. Most likely,
251 // we already have the latest one.
252 // If you are unlucky to hit this race issue, you have
253 // to touch the remote jar to update its last modified time
259 File dst
= new File(localDir
, fileName
);
260 remoteDirFs
.copyToLocalFile(path
, new Path(dst
.getPath()));
261 jarModifiedTime
.put(fileName
, dst
.lastModified());
262 URL url
= dst
.toURI().toURL();
264 } catch (IOException ioe
) {
265 LOG
.warn("Failed to load new jar " + fileName
, ioe
);