| /* |
| * 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.nifi.nar; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.IOException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| |
| /** |
| * <p> |
| * A <tt>ClassLoader</tt> for loading NARs (NiFi archives). NARs are designed to |
| * allow isolating bundles of code (comprising one-or-more NiFi |
| * <tt>FlowFileProcessor</tt>s, <tt>FlowFileComparator</tt>s 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.</p> |
| * |
| * <p> |
| * <tt>NarClassLoader</tt> follows the delegation model described in |
| * {@link ClassLoader#findClass(String) ClassLoader.findClass(...)}; |
| * classes are first loaded from the parent <tt>ClassLoader</tt>, and only if |
| * they cannot be found there does the <tt>NarClassLoader</tt> provide a |
| * definition. Specifically, this means that resources are loaded from NiFi's |
| * <tt>conf</tt> |
| * and <tt>lib</tt> directories first, and if they cannot be found there, are |
| * loaded from the NAR.</p> |
| * |
| * <p> |
| * The packaging of a NAR is such that it is a ZIP file with the following |
| * directory structure: |
| * |
| * <pre> |
| * +META-INF/ |
| * +-- bundled-dependencies/ |
| * +-- <JAR files> |
| * +-- MANIFEST.MF |
| * </pre> |
| * </p> |
| * |
| * <p> |
| * 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}. |
| * </p> |
| * |
| * <p> |
| * The {@code Nar-Id} provides a unique identifier for this NAR. |
| * </p> |
| * |
| * <p> |
| * 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. |
| * </p> |
| * |
| * <p> |
| * 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: |
| * |
| * <pre> |
| * ... |
| * <artifactId>nar-a</artifactId> |
| * <packaging>nar</packaging> |
| * ... |
| * <dependencies> |
| * <dependency> |
| * <groupId>group</groupId> |
| * <artifactId>nar-z</artifactId> |
| * <b><type>nar</type></b> |
| * </dependency> |
| * </dependencies> |
| * </pre> |
| * </p> |
| * |
| * |
| * <p> |
| * Then the MANIFEST.MF file that is created for NAR A will have the following |
| * properties set: |
| * <ul> |
| * <li>{@code Nar-Id: nar-a}</li> |
| * <li>{@code Nar-Dependency-Id: nar-z}</li> |
| * </ul> |
| * </p> |
| * |
| * <p> |
| * Note, above, that the {@code type} of the dependency is set to {@code nar}. |
| * </p> |
| * |
| * <p> |
| * If the NAR has more than one dependency of {@code type} {@code nar}, then the |
| * Maven NAR plugin will fail to build the NAR. |
| * </p> |
| */ |
| public class NarClassLoader extends URLClassLoader { |
| |
| private static final Logger LOGGER = LoggerFactory.getLogger(NarClassLoader.class); |
| |
| private static final FileFilter JAR_FILTER = new FileFilter() { |
| @Override |
| public boolean accept(File pathname) { |
| final String nameToTest = pathname.getName().toLowerCase(); |
| return nameToTest.endsWith(".jar") && pathname.isFile(); |
| } |
| }; |
| |
| /** |
| * The NAR for which this <tt>ClassLoader</tt> is responsible. |
| */ |
| private final File narWorkingDirectory; |
| |
| /** |
| * Construct a nar class loader. |
| * |
| * @param narWorkingDirectory directory to explode nar contents to |
| * @throws IllegalArgumentException if the NAR is missing the Java Services |
| * API file for <tt>FlowFileProcessor</tt> implementations. |
| * @throws ClassNotFoundException if any of the <tt>FlowFileProcessor</tt> |
| * implementations defined by the Java Services API cannot be loaded. |
| * @throws IOException if an error occurs while loading the NAR. |
| */ |
| public NarClassLoader(final File narWorkingDirectory) throws ClassNotFoundException, IOException { |
| super(new URL[0]); |
| this.narWorkingDirectory = narWorkingDirectory; |
| |
| // process the classpath |
| updateClasspath(narWorkingDirectory); |
| } |
| |
| /** |
| * Construct a nar class loader with the specific parent. |
| * |
| * @param narWorkingDirectory directory to explode nar contents to |
| * @param parentClassLoader parent class loader of this nar |
| * @throws IllegalArgumentException if the NAR is missing the Java Services |
| * API file for <tt>FlowFileProcessor</tt> implementations. |
| * @throws ClassNotFoundException if any of the <tt>FlowFileProcessor</tt> |
| * implementations defined by the Java Services API cannot be loaded. |
| * @throws IOException if an error occurs while loading the NAR. |
| */ |
| public NarClassLoader(final File narWorkingDirectory, final ClassLoader parentClassLoader) throws ClassNotFoundException, IOException { |
| super(new URL[0], parentClassLoader); |
| this.narWorkingDirectory = narWorkingDirectory; |
| |
| // process the classpath |
| updateClasspath(narWorkingDirectory); |
| } |
| |
| public File getWorkingDirectory() { |
| return narWorkingDirectory; |
| } |
| |
| /** |
| * Adds URLs for the resources unpacked from this NAR: |
| * <ul><li>the root: for classes, <tt>META-INF</tt>, etc.</li> |
| * <li><tt>META-INF/dependencies</tt>: for config files, <tt>.so</tt>s, |
| * etc.</li> |
| * <li><tt>META-INF/dependencies/*.jar</tt>: for dependent |
| * libraries</li></ul> |
| * |
| * @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 { |
| addURL(root.toURI().toURL()); // for compiled classes, META-INF/, etc. |
| |
| File dependencies = new File(root, "META-INF/bundled-dependencies"); |
| if (!dependencies.isDirectory()) { |
| LOGGER.warn(narWorkingDirectory + " does not contain META-INF/bundled-dependencies!"); |
| } |
| addURL(dependencies.toURI().toURL()); |
| if (dependencies.isDirectory()) { |
| for (File libJar : dependencies.listFiles(JAR_FILTER)) { |
| addURL(libJar.toURI().toURL()); |
| } |
| } |
| } |
| |
| @Override |
| protected String findLibrary(final String libname) { |
| File dependencies = new File(narWorkingDirectory, "META-INF/bundled-dependencies"); |
| if (!dependencies.isDirectory()) { |
| LOGGER.warn(narWorkingDirectory + " does not contain META-INF/bundled-dependencies!"); |
| } |
| |
| 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() + "]"; |
| } |
| } |