All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.pulsar.common.nar.NarClassLoader Maven / Gradle / Ivy

There is a newer version: 4.0.0-preview.1
Show newest version
/*
 * 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.
 */
/**
 * This class was adapted from NiFi NAR Utils
 * https://github.com/apache/nifi/tree/master/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils
 */
package org.apache.pulsar.common.nar;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 

* A ClassLoader for loading NARs (NiFi archives). NARs are designed to allow isolating bundles of code * (comprising one-or-more NiFi FlowFileProcessors, FlowFileComparators and their dependencies) from * other such bundles; this allows for dependencies and processors that require conflicting, incompatible versions of * the same dependency to run in a single instance of NiFi. *

* *

* NarClassLoader follows the delegation model described in {@link ClassLoader#findClass(java.lang.String) * ClassLoader.findClass(...)}; classes are first loaded from the parent ClassLoader, and only if they cannot * be found there does the NarClassLoader provide a definition. Specifically, this means that resources are * loaded from NiFi's conf and lib directories first, and if they cannot be found there, are loaded * from the NAR. *

* *

* The packaging of a NAR is such that it is a ZIP file with the following directory structure: * *

 *   +META-INF/
 *   +-- bundled-dependencies/
 *   +-- <JAR files>
 *   +-- MANIFEST.MF
 * 
*

* *

* The MANIFEST.MF file contains the same information as a typical JAR file but also includes two additional NiFi * properties: {@code Nar-Id} and {@code Nar-Dependency-Id}. *

* *

* The {@code Nar-Id} provides a unique identifier for this NAR. *

* *

* The {@code Nar-Dependency-Id} is optional. If provided, it indicates that this NAR should inherit all of the * dependencies of the NAR with the provided ID. Often times, the NAR that is depended upon is referred to as the * Parent. This is because its ClassLoader will be the parent ClassLoader of the dependent NAR. *

* *

* If a NAR is built using NiFi's Maven NAR Plugin, the {@code Nar-Id} property will be set to the artifactId of the * NAR. The {@code Nar-Dependency-Id} will be set to the artifactId of the NAR that is depended upon. For example, if * NAR A is defined as such: * *

 * ...
 * <artifactId>nar-a</artifactId>
 * <packaging>nar</packaging>
 * ...
 * <dependencies>
 *   <dependency>
 *     <groupId>group</groupId>
 *     <artifactId>nar-z</artifactId>
 *     <type>nar</type>
 *   </dependency>
 * </dependencies>
 * 
*

* * *

* Then the MANIFEST.MF file that is created for NAR A will have the following properties set: *

    *
  • {@code Nar-Id: nar-a}
  • *
  • {@code Nar-Dependency-Id: nar-z}
  • *
*

* *

* Note, above, that the {@code type} of the dependency is set to {@code nar}. *

* *

* If the NAR has more than one dependency of {@code type} {@code nar}, then the Maven NAR plugin will fail to build the * NAR. *

*/ @Slf4j public class NarClassLoader extends URLClassLoader { private static final FileFilter JAR_FILTER = pathname -> { final String nameToTest = pathname.getName().toLowerCase(); return nameToTest.endsWith(".jar") && pathname.isFile(); }; /** * The NAR for which this ClassLoader is responsible. */ private final File narWorkingDirectory; private final AtomicBoolean closed = new AtomicBoolean(); private static final String TMP_DIR_PREFIX = "pulsar-nar"; public static final String DEFAULT_NAR_EXTRACTION_DIR = System.getProperty("nar.extraction.tmpdir") != null ? System.getProperty("nar.extraction.tmpdir") : System.getProperty("java.io.tmpdir"); static NarClassLoader getFromArchive(File narPath, Set additionalJars, ClassLoader parent, String narExtractionDirectory) throws IOException { File unpacked = NarUnpacker.unpackNar(narPath, getNarExtractionDirectory(narExtractionDirectory)); return AccessController.doPrivileged(new PrivilegedAction() { @SneakyThrows @Override public NarClassLoader run() { return new NarClassLoader(unpacked, additionalJars, parent); } }); } public static List getClasspathFromArchive(File narPath, String narExtractionDirectory) throws IOException { File unpacked = NarUnpacker.unpackNar(narPath, getNarExtractionDirectory(narExtractionDirectory)); return getClassPathEntries(unpacked); } private static File getNarExtractionDirectory(String configuredDirectory) { return new File(configuredDirectory + "/" + TMP_DIR_PREFIX); } /** * Construct a nar class loader. * * @param narWorkingDirectory * directory to explode nar contents to * @param parent * @throws IOException * if an error occurs while loading the NAR. */ private NarClassLoader(final File narWorkingDirectory, Set additionalJars, ClassLoader parent) throws IOException { super(new URL[0], parent); this.narWorkingDirectory = narWorkingDirectory; // process the classpath updateClasspath(narWorkingDirectory); if (additionalJars != null) { for (String jar : additionalJars) { addURL(Paths.get(jar).toUri().toURL()); } } if (log.isDebugEnabled()) { log.debug("Created class loader with paths: {}", Arrays.toString(getURLs())); } } public File getWorkingDirectory() { return narWorkingDirectory; } /** * Read a service definition as a String. */ public String getServiceDefinition(String serviceName) throws IOException { String serviceDefPath = narWorkingDirectory + "/META-INF/services/" + serviceName; return new String(Files.readAllBytes(Paths.get(serviceDefPath)), StandardCharsets.UTF_8); } public List getServiceImplementation(String serviceName) throws IOException { List impls = new ArrayList<>(); String serviceDefPath = narWorkingDirectory + "/META-INF/services/" + serviceName; try (BufferedReader reader = new BufferedReader( new InputStreamReader(new FileInputStream(serviceDefPath), StandardCharsets.UTF_8))) { String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (!line.isEmpty() && !line.startsWith("#")) { final int indexOfPound = line.indexOf("#"); final String effectiveLine = (indexOfPound > 0) ? line.substring(0, indexOfPound) : line; impls.add(effectiveLine); } } } return impls; } /** * Adds URLs for the resources unpacked from this NAR: *
    *
  • the root: for classes, META-INF, etc.
  • *
  • META-INF/dependencies: for config files, .sos, etc.
  • *
  • META-INF/dependencies/*.jar: for dependent libraries
  • *
* * @param root * the root directory of the unpacked NAR. * @throws IOException * if the URL list could not be updated. */ private void updateClasspath(File root) throws IOException { getClassPathEntries(root).forEach(f -> { try { addURL(f.toURI().toURL()); } catch (IOException e) { log.error("Failed to add entry to classpath: {}", f, e); } }); } static List getClassPathEntries(File root) { List classPathEntries = new ArrayList<>(); classPathEntries.add(root); File dependencies = new File(root, "META-INF/bundled-dependencies"); if (!dependencies.isDirectory()) { log.warn("{} does not contain META-INF/bundled-dependencies!", root); } classPathEntries.add(dependencies); if (dependencies.isDirectory()) { final File[] jarFiles = dependencies.listFiles(JAR_FILTER); if (jarFiles != null) { Arrays.sort(jarFiles, Comparator.comparing(File::getName)); classPathEntries.addAll(Arrays.asList(jarFiles)); } } return classPathEntries; } @Override protected String findLibrary(final String libname) { File dependencies = new File(narWorkingDirectory, "META-INF/bundled-dependencies"); if (!dependencies.isDirectory()) { log.warn("{} does not contain META-INF/bundled-dependencies!", narWorkingDirectory); } final File nativeDir = new File(dependencies, "native"); final File libsoFile = new File(nativeDir, "lib" + libname + ".so"); final File dllFile = new File(nativeDir, libname + ".dll"); final File soFile = new File(nativeDir, libname + ".so"); if (libsoFile.exists()) { return libsoFile.getAbsolutePath(); } else if (dllFile.exists()) { return dllFile.getAbsolutePath(); } else if (soFile.exists()) { return soFile.getAbsolutePath(); } // not found in the nar. try system native dir return null; } @Override public String toString() { return NarClassLoader.class.getName() + "[" + narWorkingDirectory.getPath() + "]"; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (closed.get()) { log.warn("Loading class {} from a closed classloader ({})", name, this); } return super.loadClass(name, resolve); } @Override public void close() throws IOException { closed.set(true); super.close(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy