| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.accumulo.start.classloader.vfs; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.UncheckedIOException; |
| import java.lang.management.ManagementFactory; |
| import java.lang.ref.WeakReference; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Supplier; |
| |
| import org.apache.accumulo.start.classloader.AccumuloClassLoader; |
| import org.apache.commons.io.FileUtils; |
| import org.apache.commons.vfs2.CacheStrategy; |
| import org.apache.commons.vfs2.FileObject; |
| import org.apache.commons.vfs2.FileSystemException; |
| import org.apache.commons.vfs2.FileSystemManager; |
| import org.apache.commons.vfs2.FileType; |
| import org.apache.commons.vfs2.cache.SoftRefFilesCache; |
| import org.apache.commons.vfs2.impl.DefaultFileSystemManager; |
| import org.apache.commons.vfs2.impl.FileContentInfoFilenameFactory; |
| import org.apache.commons.vfs2.impl.VFSClassLoader; |
| import org.apache.commons.vfs2.provider.FileReplicator; |
| import org.apache.commons.vfs2.provider.hdfs.HdfsFileObject; |
| import org.apache.commons.vfs2.provider.hdfs.HdfsFileProvider; |
| import org.apache.hadoop.conf.Configuration; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; |
| |
| /** |
| * This class builds a hierarchy of Classloaders in the form of: |
| * |
| * <pre> |
| * SystemClassLoader that loads JVM classes |
| * ^ |
| * | |
| * AccumuloClassLoader loads jars from locations in general.classpaths. |
| * Usually the URLs for HADOOP_HOME, ZOOKEEPER_HOME, ACCUMULO_HOME and their associated directories |
| * ^ |
| * | |
| * VFSClassLoader that loads jars from locations in general.vfs.classpaths. |
| * Can be used to load accumulo jar from HDFS |
| * ^ |
| * | |
| * AccumuloReloadingVFSClassLoader That loads jars from locations in general.dynamic.classpaths. |
| * Used to load jar dynamically. |
| * </pre> |
| */ |
| @Deprecated |
| public class AccumuloVFSClassLoader { |
| |
| public static class AccumuloVFSClassLoaderShutdownThread implements Runnable { |
| |
| @Override |
| public void run() { |
| try { |
| AccumuloVFSClassLoader.close(); |
| } catch (Exception e) { |
| // do nothing, we are shutting down anyway |
| } |
| } |
| |
| } |
| |
| private static List<WeakReference<DefaultFileSystemManager>> vfsInstances = |
| Collections.synchronizedList(new ArrayList<>()); |
| |
| public static final String DYNAMIC_CLASSPATH_PROPERTY_NAME = "general.dynamic.classpaths"; |
| |
| public static final String DEFAULT_DYNAMIC_CLASSPATH_VALUE = ""; |
| |
| public static final String VFS_CLASSLOADER_SYSTEM_CLASSPATH_PROPERTY = "general.vfs.classpaths"; |
| |
| public static final String VFS_CONTEXT_CLASSPATH_PROPERTY = "general.vfs.context.classpath."; |
| |
| public static final String VFS_CACHE_DIR = "general.vfs.cache.dir"; |
| |
| private static ClassLoader parent = null; |
| private static volatile ReloadingClassLoader loader = null; |
| private static final Object lock = new Object(); |
| |
| private static ContextManager contextManager; |
| |
| private static final Logger log = LoggerFactory.getLogger(AccumuloVFSClassLoader.class); |
| |
| static { |
| // Register the shutdown hook |
| Runtime.getRuntime().addShutdownHook(new Thread(new AccumuloVFSClassLoaderShutdownThread())); |
| } |
| |
| static FileObject[] resolve(FileSystemManager vfs, String uris) throws FileSystemException { |
| return resolve(vfs, uris, new ArrayList<>()); |
| } |
| |
| static FileObject[] resolve(FileSystemManager vfs, String uris, |
| ArrayList<FileObject> pathsToMonitor) throws FileSystemException { |
| if (uris == null) { |
| return new FileObject[0]; |
| } |
| |
| ArrayList<FileObject> classpath = new ArrayList<>(); |
| |
| pathsToMonitor.clear(); |
| |
| for (String path : uris.split(",")) { |
| |
| path = path.trim(); |
| |
| if (path.equals("")) { |
| continue; |
| } |
| |
| path = AccumuloClassLoader.replaceEnvVars(path, System.getenv()); |
| |
| log.debug("Resolving path element: {}", path); |
| FileObject fo = vfs.resolveFile(path); |
| |
| switch (fo.getType()) { |
| case FILE: |
| case FOLDER: |
| classpath.add(fo); |
| pathsToMonitor.add(fo); |
| break; |
| case IMAGINARY: |
| // assume its a pattern |
| String pattern = fo.getName().getBaseName(); |
| if (fo.getParent() != null) { |
| // still monitor the parent |
| pathsToMonitor.add(fo.getParent()); |
| if (fo.getParent().getType() == FileType.FOLDER) { |
| FileObject[] children = fo.getParent().getChildren(); |
| for (FileObject child : children) { |
| if (child.getType() == FileType.FILE |
| && child.getName().getBaseName().matches(pattern)) { |
| classpath.add(child); |
| } |
| } |
| } else { |
| log.debug("classpath entry " + fo.getParent() + " is " + fo.getParent().getType()); |
| } |
| } else { |
| log.warn("ignoring classpath entry {}", fo); |
| } |
| break; |
| default: |
| log.warn("ignoring classpath entry {}", fo); |
| break; |
| } |
| |
| } |
| |
| return classpath.toArray(new FileObject[classpath.size()]); |
| } |
| |
| private static ReloadingClassLoader createDynamicClassloader(final ClassLoader parent) |
| throws IOException { |
| String dynamicCPath = AccumuloClassLoader.getAccumuloProperty(DYNAMIC_CLASSPATH_PROPERTY_NAME, |
| DEFAULT_DYNAMIC_CLASSPATH_VALUE); |
| |
| ReloadingClassLoader wrapper = () -> parent; |
| |
| if (dynamicCPath == null || dynamicCPath.equals("")) { |
| return wrapper; |
| } |
| |
| // TODO monitor time for lib/ext was 1 sec... should this be configurable? - ACCUMULO-1301 |
| return new AccumuloReloadingVFSClassLoader(dynamicCPath, generateVfs(), wrapper, 1000, true); |
| } |
| |
| public static ClassLoader getClassLoader() { |
| try { |
| return getClassLoader_Internal(); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| private static ClassLoader getClassLoader_Internal() throws IOException { |
| ReloadingClassLoader localLoader = loader; |
| while (localLoader == null) { |
| synchronized (lock) { |
| if (loader == null) { |
| |
| FileSystemManager vfs = generateVfs(); |
| |
| // Set up the 2nd tier class loader |
| if (parent == null) { |
| parent = AccumuloClassLoader.getClassLoader(); |
| } |
| |
| FileObject[] vfsCP = resolve(vfs, AccumuloClassLoader |
| .getAccumuloProperty(VFS_CLASSLOADER_SYSTEM_CLASSPATH_PROPERTY, "")); |
| |
| if (vfsCP.length == 0) { |
| localLoader = createDynamicClassloader(parent); |
| loader = localLoader; |
| return localLoader.getClassLoader(); |
| } |
| |
| // Create the Accumulo Context ClassLoader using the DEFAULT_CONTEXT |
| localLoader = createDynamicClassloader(new VFSClassLoader(vfsCP, vfs, parent)); |
| loader = localLoader; |
| |
| // An HDFS FileSystem and Configuration object were created for each unique HDFS namespace |
| // in the call to resolve above. |
| // The HDFS Client did us a favor and cached these objects so that the next time someone |
| // calls FileSystem.get(uri), they |
| // get the cached object. However, these objects were created not with the system VFS |
| // classloader, but the classloader above |
| // it. We need to override the classloader on the Configuration objects. Ran into an issue |
| // were log recovery was being attempted |
| // and SequenceFile$Reader was trying to instantiate the key class via |
| // WritableName.getClass(String, Configuration) |
| for (FileObject fo : vfsCP) { |
| if (fo instanceof HdfsFileObject) { |
| String uri = fo.getName().getRootURI(); |
| Configuration c = new Configuration(true); |
| c.set(FileSystem.FS_DEFAULT_NAME_KEY, uri); |
| FileSystem fs = FileSystem.get(c); |
| fs.getConf().setClassLoader(loader.getClassLoader()); |
| } |
| } |
| |
| } |
| } |
| } |
| |
| return localLoader.getClassLoader(); |
| } |
| |
| public static FileSystemManager generateVfs() throws FileSystemException { |
| DefaultFileSystemManager vfs = new DefaultFileSystemManager(); |
| vfs.addProvider("res", new org.apache.commons.vfs2.provider.res.ResourceFileProvider()); |
| vfs.addProvider("zip", new org.apache.commons.vfs2.provider.zip.ZipFileProvider()); |
| vfs.addProvider("gz", new org.apache.commons.vfs2.provider.gzip.GzipFileProvider()); |
| vfs.addProvider("ram", new org.apache.commons.vfs2.provider.ram.RamFileProvider()); |
| vfs.addProvider("file", new org.apache.commons.vfs2.provider.local.DefaultLocalFileProvider()); |
| vfs.addProvider("jar", new org.apache.commons.vfs2.provider.jar.JarFileProvider()); |
| vfs.addProvider("http", new org.apache.commons.vfs2.provider.http.HttpFileProvider()); |
| vfs.addProvider("https", new org.apache.commons.vfs2.provider.https.HttpsFileProvider()); |
| vfs.addProvider("ftp", new org.apache.commons.vfs2.provider.ftp.FtpFileProvider()); |
| vfs.addProvider("ftps", new org.apache.commons.vfs2.provider.ftps.FtpsFileProvider()); |
| vfs.addProvider("war", new org.apache.commons.vfs2.provider.jar.JarFileProvider()); |
| vfs.addProvider("par", new org.apache.commons.vfs2.provider.jar.JarFileProvider()); |
| vfs.addProvider("ear", new org.apache.commons.vfs2.provider.jar.JarFileProvider()); |
| vfs.addProvider("sar", new org.apache.commons.vfs2.provider.jar.JarFileProvider()); |
| vfs.addProvider("ejb3", new org.apache.commons.vfs2.provider.jar.JarFileProvider()); |
| vfs.addProvider("tmp", new org.apache.commons.vfs2.provider.temp.TemporaryFileProvider()); |
| vfs.addProvider("tar", new org.apache.commons.vfs2.provider.tar.TarFileProvider()); |
| vfs.addProvider("tbz2", new org.apache.commons.vfs2.provider.tar.TarFileProvider()); |
| vfs.addProvider("tgz", new org.apache.commons.vfs2.provider.tar.TarFileProvider()); |
| vfs.addProvider("bz2", new org.apache.commons.vfs2.provider.bzip2.Bzip2FileProvider()); |
| vfs.addProvider("hdfs", new HdfsFileProvider()); |
| vfs.addExtensionMap("jar", "jar"); |
| vfs.addExtensionMap("zip", "zip"); |
| vfs.addExtensionMap("gz", "gz"); |
| vfs.addExtensionMap("tar", "tar"); |
| vfs.addExtensionMap("tbz2", "tar"); |
| vfs.addExtensionMap("tgz", "tar"); |
| vfs.addExtensionMap("bz2", "bz2"); |
| vfs.addMimeTypeMap("application/x-tar", "tar"); |
| vfs.addMimeTypeMap("application/x-gzip", "gz"); |
| vfs.addMimeTypeMap("application/zip", "zip"); |
| vfs.setFileContentInfoFactory(new FileContentInfoFilenameFactory()); |
| vfs.setFilesCache(new SoftRefFilesCache()); |
| File cacheDir = computeTopCacheDir(); |
| vfs.setReplicator(new UniqueFileReplicator(cacheDir)); |
| vfs.setCacheStrategy(CacheStrategy.ON_RESOLVE); |
| vfs.init(); |
| vfsInstances.add(new WeakReference<>(vfs)); |
| return vfs; |
| } |
| |
| @SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", |
| justification = "tmpdir is controlled by admin, not unchecked user input") |
| private static File computeTopCacheDir() { |
| String cacheDirPath = AccumuloClassLoader.getAccumuloProperty(VFS_CACHE_DIR, |
| System.getProperty("java.io.tmpdir")); |
| String procName = ManagementFactory.getRuntimeMXBean().getName(); |
| return new File(cacheDirPath, |
| "accumulo-vfs-cache-" + procName + "-" + System.getProperty("user.name", "nouser")); |
| } |
| |
| public interface Printer { |
| void print(String s); |
| } |
| |
| public static void printClassPath(boolean debug) { |
| printClassPath(System.out::print, debug); |
| } |
| |
| public static String getClassPath(boolean debug) { |
| StringBuilder cp = new StringBuilder(); |
| printClassPath(cp::append, debug); |
| return cp.toString(); |
| } |
| |
| private static void printJar(Printer out, String jarPath, boolean debug, boolean sawFirst) { |
| if (debug) { |
| out.print("\t"); |
| } |
| if (!debug && sawFirst) { |
| out.print(":"); |
| } |
| out.print(jarPath); |
| if (debug) { |
| out.print("\n"); |
| } |
| } |
| |
| public static void printClassPath(Printer out, boolean debug) { |
| try { |
| ClassLoader cl = getClassLoader(); |
| ArrayList<ClassLoader> classloaders = new ArrayList<>(); |
| |
| while (cl != null) { |
| classloaders.add(cl); |
| cl = cl.getParent(); |
| } |
| |
| Collections.reverse(classloaders); |
| |
| int level = 0; |
| |
| for (ClassLoader classLoader : classloaders) { |
| |
| level++; |
| |
| if (debug && level > 1) { |
| out.print("\n"); |
| } |
| if (!debug && level < 2) { |
| continue; |
| } |
| |
| boolean sawFirst = false; |
| String classLoaderDescription = "Level: " + level + ", Name: " + classLoader.getName() |
| + ", class: " + classLoader.getClass().getName(); |
| if (classLoader.getClass().getName().startsWith("jdk.internal")) { |
| if (debug) { |
| out.print(classLoaderDescription + ": configuration not inspectable.\n"); |
| } |
| } else if (classLoader instanceof URLClassLoader) { |
| if (debug) { |
| out.print(classLoaderDescription + ": URL classpath items are:\n"); |
| } |
| for (URL u : ((URLClassLoader) classLoader).getURLs()) { |
| printJar(out, u.getFile(), debug, sawFirst); |
| sawFirst = true; |
| } |
| } else if (classLoader instanceof VFSClassLoader) { |
| if (debug) { |
| out.print(classLoaderDescription + ": VFS classpaths items are:\n"); |
| } |
| VFSClassLoader vcl = (VFSClassLoader) classLoader; |
| for (FileObject f : vcl.getFileObjects()) { |
| printJar(out, f.getURL().getFile(), debug, sawFirst); |
| sawFirst = true; |
| } |
| } else { |
| if (debug) { |
| out.print( |
| classLoaderDescription + ": Unknown classloader: " + classLoader.getClass() + "\n"); |
| } |
| } |
| } |
| out.print("\n"); |
| } catch (Exception t) { |
| throw new RuntimeException(t); |
| } |
| } |
| |
| public static void setContextConfig(Supplier<Map<String,String>> contextConfigSupplier) { |
| var config = new ContextManager.DefaultContextsConfig(contextConfigSupplier); |
| try { |
| getContextManager().setContextConfig(config); |
| } catch (IOException e) { |
| throw new UncheckedIOException(e); |
| } |
| } |
| |
| public static void removeUnusedContexts(Set<String> contextsInUse) { |
| try { |
| getContextManager().removeUnusedContexts(contextsInUse); |
| } catch (IOException e) { |
| log.warn("{}", e.getMessage(), e); |
| } |
| } |
| |
| public static ClassLoader getContextClassLoader(String context) { |
| try { |
| return getContextManager().getClassLoader(context); |
| } catch (IOException e) { |
| throw new UncheckedIOException("Error getting context class loader for context: " + context, |
| e); |
| } |
| } |
| |
| private static synchronized ContextManager getContextManager() throws IOException { |
| if (contextManager == null) { |
| getClassLoader(); |
| contextManager = new ContextManager(generateVfs(), () -> { |
| return getClassLoader(); |
| }); |
| } |
| |
| return contextManager; |
| } |
| |
| public static void close() { |
| for (WeakReference<DefaultFileSystemManager> vfsInstance : vfsInstances) { |
| DefaultFileSystemManager ref = vfsInstance.get(); |
| if (ref != null) { |
| FileReplicator replicator; |
| try { |
| replicator = ref.getReplicator(); |
| if (replicator instanceof UniqueFileReplicator) { |
| ((UniqueFileReplicator) replicator).close(); |
| } |
| } catch (FileSystemException e) { |
| log.error("FileSystemException", e); |
| } |
| ref.close(); |
| } |
| } |
| try { |
| FileUtils.deleteDirectory(computeTopCacheDir()); |
| } catch (IOException e) { |
| log.error("IOException", e); |
| } |
| } |
| } |