From 4d294b9b5ea646e38f2ae6072e6f7f83277d65aa Mon Sep 17 00:00:00 2001 From: Otto Fowler Date: Fri, 8 Sep 2017 01:35:05 -0400 Subject: [PATCH 1/2] implement ability to add new bundles to system --- .../metron/bundles/BundleClassLoaders.java | 260 ++---------- .../bundles/BundleClassLoadersContext.java | 373 ++++++++++++++++++ .../apache/metron/bundles/BundleSystem.java | 49 ++- .../metron/bundles/ExtensionManager.java | 259 +++--------- .../bundles/ExtensionManagerContext.java | 336 ++++++++++++++++ .../apache/metron/bundles/AbstractFoo2.java | 27 ++ .../BundleClassLoadersContextTest.java | 144 +++++++ .../metron/bundles/BundleSystemTest.java | 88 ++++- .../bundles/ExtensionManagerContextTest.java | 110 ++++++ .../apache/metron/parsers/BasicParser.java | 21 + .../metron-parser-foo-bundle-0.4.1.bundle | Bin 0 -> 21983 bytes .../src/test/resources/bundle.properties | 2 +- 12 files changed, 1215 insertions(+), 454 deletions(-) create mode 100644 bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java create mode 100644 bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java create mode 100644 bundles-lib/src/test/java/org/apache/metron/bundles/AbstractFoo2.java create mode 100644 bundles-lib/src/test/java/org/apache/metron/bundles/BundleClassLoadersContextTest.java create mode 100644 bundles-lib/src/test/java/org/apache/metron/bundles/ExtensionManagerContextTest.java create mode 100644 bundles-lib/src/test/java/org/apache/metron/parsers/BasicParser.java create mode 100644 bundles-lib/src/test/resources/BundleMapper/metron-parser-foo-bundle-0.4.1.bundle diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoaders.java b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoaders.java index 946c71ac16..525caf6ed4 100644 --- a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoaders.java +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoaders.java @@ -14,26 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.metron.bundles; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import java.lang.invoke.MethodHandles; import java.net.URISyntaxException; import java.util.*; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.vfs2.FileObject; import org.apache.commons.vfs2.FileSystemException; import org.apache.commons.vfs2.FileSystemManager; import org.apache.metron.bundles.bundle.Bundle; -import org.apache.metron.bundles.bundle.BundleCoordinates; -import org.apache.metron.bundles.bundle.BundleDetails; import org.apache.metron.bundles.util.BundleProperties; -import org.apache.metron.bundles.util.BundleSelector; -import org.apache.metron.bundles.util.FileUtils; -import org.apache.metron.bundles.util.BundleUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,33 +36,9 @@ public final class BundleClassLoaders { private static volatile BundleClassLoaders bundleClassLoaders; - private static volatile InitContext initContext; + private static volatile BundleClassLoadersContext initContext; private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); - /** - * Holds the context from {@code BundleClassLoaders} initialization, - * being the coordinate to bundle mapping. - * - * After initialization these are not changed, and as such they - * are immutable. - * - */ - private final static class InitContext { - - private final List extensionDirs; - private final Map bundles; - private final BundleProperties properties; - - private InitContext( - final List extensionDirs, - final Map bundles, - final BundleProperties properties) { - this.extensionDirs = ImmutableList.copyOf(extensionDirs); - this.bundles = ImmutableMap.copyOf(bundles); - this.properties = properties; - } - } - private BundleClassLoaders() { } @@ -122,199 +91,18 @@ public static void init(final FileSystemManager fileSystemManager, final List extensionsDirs, BundleProperties props) + private BundleClassLoadersContext load(final FileSystemManager fileSystemManager, + final List extensionsDirs, BundleProperties properties) throws FileSystemException, ClassNotFoundException, URISyntaxException { - // get the system classloader - final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); - - // find all bundle files and create class loaders for them. - final Map directoryBundleLookup = new LinkedHashMap<>(); - final Map coordinateClassLoaderLookup = new HashMap<>(); - final Map> idBundleLookup = new HashMap<>(); - - for (FileObject extensionsDir : extensionsDirs) { - // make sure the bundle directory is there and accessible - FileUtils.ensureDirectoryExistAndCanRead(extensionsDir); - - final List bundleDirContents = new ArrayList<>(); - FileObject[] dirFiles = extensionsDir.findFiles(new BundleSelector(props.getArchiveExtension())); - if (dirFiles != null) { - List fileList = Arrays.asList(dirFiles); - bundleDirContents.addAll(fileList); - } - - if (!bundleDirContents.isEmpty()) { - final List bundleDetails = new ArrayList<>(); - final Map bundleCoordinatesToBundleFile = new HashMap<>(); - - // load the bundle details which includes bundle dependencies - for (final FileObject bundleFile : bundleDirContents) { - if(!bundleFile.exists() || !bundleFile.isFile()) { - continue; - } - BundleDetails bundleDetail = null; - try { - bundleDetail = getBundleDetails(bundleFile, props); - } catch (IllegalStateException e) { - logger.warn("Unable to load BUNDLE {} due to {}, skipping...", - new Object[]{bundleFile.getURL(), e.getMessage()}); - } - - // prevent the application from starting when there are two BUNDLEs with same group, id, and version - final String bundleCoordinate = bundleDetail.getCoordinates().getCoordinates(); - if (bundleCoordinatesToBundleFile.containsKey(bundleCoordinate)) { - final String existingBundleWorkingDir = bundleCoordinatesToBundleFile - .get(bundleCoordinate); - throw new IllegalStateException( - "Unable to load BUNDLE with coordinates " + bundleCoordinate - + " and bundle file " + bundleDetail.getBundleFile() - + " because another BUNDLE with the same coordinates already exists at " - + existingBundleWorkingDir); - } - - bundleDetails.add(bundleDetail); - bundleCoordinatesToBundleFile.put(bundleCoordinate, - bundleDetail.getBundleFile().getURL().toURI().toString()); - } - - for (final Iterator bundleDetailsIter = bundleDetails.iterator(); - bundleDetailsIter.hasNext(); ) { - final BundleDetails bundleDetail = bundleDetailsIter.next(); - // populate bundle lookup - idBundleLookup.computeIfAbsent(bundleDetail.getCoordinates().getId(), - id -> new HashSet<>()).add(bundleDetail.getCoordinates()); - } - - int bundleCount; - do { - // record the number of bundles to be loaded - bundleCount = bundleDetails.size(); - - // attempt to create each bundle class loader - for (final Iterator bundleDetailsIter = bundleDetails.iterator(); - bundleDetailsIter.hasNext(); ) { - final BundleDetails bundleDetail = bundleDetailsIter.next(); - final BundleCoordinates bundleDependencyCoordinate = bundleDetail - .getDependencyCoordinates(); - - // see if this class loader is eligible for loading - ClassLoader potentialBundleClassLoader = null; - if (bundleDependencyCoordinate == null) { - potentialBundleClassLoader = createBundleClassLoader(fileSystemManager, - bundleDetail.getBundleFile(), ClassLoader.getSystemClassLoader()); - } else { - final String dependencyCoordinateStr = bundleDependencyCoordinate - .getCoordinates(); - - // if the declared dependency has already been loaded - if (coordinateClassLoaderLookup.containsKey(dependencyCoordinateStr)) { - final ClassLoader bundleDependencyClassLoader = coordinateClassLoaderLookup - .get(dependencyCoordinateStr); - potentialBundleClassLoader = createBundleClassLoader( - fileSystemManager, bundleDetail.getBundleFile(), - bundleDependencyClassLoader); - } else { - // get all bundles that match the declared dependency id - final Set coordinates = idBundleLookup - .get(bundleDependencyCoordinate.getId()); - - // ensure there are known bundles that match the declared dependency id - if (coordinates != null && !coordinates - .contains(bundleDependencyCoordinate)) { - // ensure the declared dependency only has one possible bundle - if (coordinates.size() == 1) { - // get the bundle with the matching id - final BundleCoordinates coordinate = coordinates.stream() - .findFirst().get(); - - // if that bundle is loaded, use it - if (coordinateClassLoaderLookup - .containsKey(coordinate.getCoordinates())) { - logger.warn(String.format( - "While loading '%s' unable to locate exact BUNDLE dependency '%s'. Only found one possible match '%s'. Continuing...", - bundleDetail.getCoordinates().getCoordinates(), - dependencyCoordinateStr, - coordinate.getCoordinates())); - - final ClassLoader bundleDependencyClassLoader = coordinateClassLoaderLookup - .get(coordinate.getCoordinates()); - potentialBundleClassLoader = createBundleClassLoader( - fileSystemManager, bundleDetail.getBundleFile(), - bundleDependencyClassLoader); - } - } - } - } - } - - // if we were able to create the bundle class loader, store it and remove the details - final ClassLoader bundleClassLoader = potentialBundleClassLoader; - if (bundleClassLoader != null) { - directoryBundleLookup - .put(bundleDetail.getBundleFile().getURL().toURI().toString(), - new Bundle(bundleDetail, bundleClassLoader)); - coordinateClassLoaderLookup - .put(bundleDetail.getCoordinates().getCoordinates(), - bundleClassLoader); - bundleDetailsIter.remove(); - } - } - - // attempt to load more if some were successfully loaded this iteration - } while (bundleCount != bundleDetails.size()); - - // see if any bundle couldn't be loaded - for (final BundleDetails bundleDetail : bundleDetails) { - logger.warn(String - .format("Unable to resolve required dependency '%s'. Skipping BUNDLE '%s'", - bundleDetail.getDependencyCoordinates().getId(), - bundleDetail.getBundleFile().getURL().toURI().toString())); - } - } - } - return new InitContext(extensionsDirs, new LinkedHashMap<>(directoryBundleLookup), props); - } - - /** - * Creates a new BundleClassLoader. The parentClassLoader may be null. - * - * @param bundleFile the Bundle File - * @param parentClassLoader parent classloader of bundle - * @return the bundle classloader - * @throws FileSystemException ioe - * @throws ClassNotFoundException cfne - */ - private static ClassLoader createBundleClassLoader(final FileSystemManager fileSystemManager, - final FileObject bundleFile, final ClassLoader parentClassLoader) - throws FileSystemException, ClassNotFoundException { - logger.debug("Loading Bundle file: " + bundleFile.getURL()); - final ClassLoader bundleClassLoader = new VFSBundleClassLoader.Builder() - .withFileSystemManager(fileSystemManager) - .withBundleFile(bundleFile) - .withParentClassloader(parentClassLoader).build(); - logger.info( - "Loaded Bundle file: " + bundleFile.getURL() + " as class loader " + bundleClassLoader); - return bundleClassLoader; - } - - /** - * Loads the details for the specified BUNDLE. The details will be extracted from the manifest - * file. - * - * @param bundleFile the bundle file - * @return details about the Bundle - * @throws FileSystemException ioe - */ - private static BundleDetails getBundleDetails(final FileObject bundleFile, BundleProperties props) - throws FileSystemException { - return BundleUtil.fromBundleFile(bundleFile, props); + return new BundleClassLoadersContext.Builder().withFileSystemManager(fileSystemManager) + .withExtensionDirs(extensionsDirs) + .withBundleProperties(properties).build(); } /** @@ -328,7 +116,7 @@ public Bundle getBundle(final FileObject extensionFile) { } try { - return initContext.bundles.get(extensionFile.getURL().toURI().toString()); + return initContext.getBundles().get(extensionFile.getURL().toURI().toString()); } catch (URISyntaxException | FileSystemException e) { if (logger.isDebugEnabled()) { logger.debug("Unable to get extension classloader for bundle '{}'", @@ -346,8 +134,34 @@ public Set getBundles() { if (initContext == null) { throw new IllegalStateException("Bundles have not been loaded."); } - - return new LinkedHashSet<>(initContext.bundles.values()); + return new LinkedHashSet<>(initContext.getBundles().values()); } + /** + * Add a bundle to the BundleClassLoaders. + * Post initialization with will load a bundle and merge + * it's information into the context. + * + * This method has limited access, only package classes that + * can ensure thread saftey and control should call. + * @param bundleName the file name of the bundle. This is the name not the path, and the file + * should exist in the configured library directories + * @return The {@link Bundle} that is created + * @throws FileSystemException + * @throws URISyntaxException + * @throws ClassNotFoundException + */ + protected Bundle addBundle(String bundleName) + throws FileSystemException, URISyntaxException, ClassNotFoundException { + if (initContext == null) { + throw new IllegalStateException("Bundles have not been loaded."); + } + BundleClassLoadersContext newContext = new BundleClassLoadersContext.Builder() + .withBundleProperties(initContext.getProperties()) + .withExtensionDirs(initContext.getExtensionDirs()) + .withFileSystemManager(initContext.getFileSystemManager()).build(bundleName); + + initContext.merge(newContext); + return initContext.getBundles().values().stream().findFirst().get(); + } } diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java new file mode 100644 index 0000000000..079f018d6c --- /dev/null +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java @@ -0,0 +1,373 @@ +/* + * 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.metron.bundles; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.lang.invoke.MethodHandles; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.metron.bundles.bundle.Bundle; +import org.apache.metron.bundles.bundle.BundleCoordinates; +import org.apache.metron.bundles.bundle.BundleDetails; +import org.apache.metron.bundles.util.BundleProperties; +import org.apache.metron.bundles.util.BundleSelector; +import org.apache.metron.bundles.util.BundleUtil; +import org.apache.metron.bundles.util.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Context object used by the BundleClassLoaders + */ +public class BundleClassLoadersContext { + + private static final Logger logger = LoggerFactory + .getLogger(MethodHandles.lookup().lookupClass()); + + public static class Builder { + + FileSystemManager fileSystemManager; + List extensionsDirs; + FileObject bundleFile; + BundleProperties properties; + + public Builder() { + } + + public Builder withFileSystemManager(FileSystemManager fileSystemManager) { + this.fileSystemManager = fileSystemManager; + return this; + } + + public Builder withExtensionDirs(List extensionDirs) { + this.extensionsDirs = extensionDirs; + return this; + } + + public Builder withBundleProperties(BundleProperties properties) { + this.properties = properties; + return this; + } + + public BundleClassLoadersContext build() + throws FileSystemException, ClassNotFoundException, URISyntaxException { + return build(null); + } + + public BundleClassLoadersContext build(String explicitBundleToLoad) + throws FileSystemException, ClassNotFoundException, URISyntaxException { + + if(extensionsDirs == null || extensionsDirs.size() == 0) { + throw new IllegalArgumentException("missing extensionDirs"); + } + + if(properties == null) { + throw new IllegalArgumentException("properties are required"); + } + + if (fileSystemManager == null) { + throw new IllegalArgumentException("fileSystemManager is required"); + } + + // get the system classloader + final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); + + // find all bundle files and create class loaders for them. + final Map directoryBundleLookup = new LinkedHashMap<>(); + final Map coordinateClassLoaderLookup = new HashMap<>(); + final Map> idBundleLookup = new HashMap<>(); + boolean foundExplicitLoadBundle = false; + boolean explicitBundleIsNotFile = false; + for (FileObject extensionsDir : extensionsDirs) { + // make sure the bundle directory is there and accessible + FileUtils.ensureDirectoryExistAndCanRead(extensionsDir); + + final List bundleDirContents = new ArrayList<>(); + FileObject[] dirFiles = null; + + // are we loading all bundles into this context or one explicit bundle? + // if it is explicit, we need to flag finding it, since for explict loads + // a bundle that doesn't exist or is not a file is an error + if (explicitBundleToLoad == null) { + dirFiles = extensionsDir + .findFiles(new BundleSelector(properties.getArchiveExtension())); + } else { + FileObject explicitBundleFileObject = extensionsDir.resolveFile(explicitBundleToLoad); + if (explicitBundleFileObject.exists()) { + foundExplicitLoadBundle = true; + if (!explicitBundleFileObject.isFile()) { + explicitBundleIsNotFile = true; + } + dirFiles = new FileObject[]{explicitBundleFileObject}; + } + } + + if (dirFiles != null) { + List fileList = Arrays.asList(dirFiles); + bundleDirContents.addAll(fileList); + } + + if (!bundleDirContents.isEmpty()) { + final List bundleDetails = new ArrayList<>(); + final Map bundleCoordinatesToBundleFile = new HashMap<>(); + + // load the bundle details which includes bundle dependencies + for (final FileObject bundleFile : bundleDirContents) { + if (!bundleFile.exists() || !bundleFile.isFile()) { + continue; + } + BundleDetails bundleDetail = null; + try { + bundleDetail = getBundleDetails(bundleFile, properties); + } catch (IllegalStateException e) { + logger.warn("Unable to load BUNDLE {} due to {}, skipping...", + new Object[]{bundleFile.getURL(), e.getMessage()}); + } + + // prevent the application from starting when there are two BUNDLEs with same group, id, and version + final String bundleCoordinate = bundleDetail.getCoordinates().getCoordinates(); + if (bundleCoordinatesToBundleFile.containsKey(bundleCoordinate)) { + final String existingBundleWorkingDir = bundleCoordinatesToBundleFile + .get(bundleCoordinate); + throw new IllegalStateException( + "Unable to load BUNDLE with coordinates " + bundleCoordinate + + " and bundle file " + bundleDetail.getBundleFile() + + " because another BUNDLE with the same coordinates already exists at " + + existingBundleWorkingDir); + } + + bundleDetails.add(bundleDetail); + bundleCoordinatesToBundleFile.put(bundleCoordinate, + bundleDetail.getBundleFile().getURL().toURI().toString()); + } + + for (final Iterator bundleDetailsIter = bundleDetails.iterator(); + bundleDetailsIter.hasNext(); ) { + final BundleDetails bundleDetail = bundleDetailsIter.next(); + // populate bundle lookup + idBundleLookup.computeIfAbsent(bundleDetail.getCoordinates().getId(), + id -> new HashSet<>()).add(bundleDetail.getCoordinates()); + } + + int bundleCount; + do { + // record the number of bundles to be loaded + bundleCount = bundleDetails.size(); + + // attempt to create each bundle class loader + for (final Iterator bundleDetailsIter = bundleDetails.iterator(); + bundleDetailsIter.hasNext(); ) { + final BundleDetails bundleDetail = bundleDetailsIter.next(); + final BundleCoordinates bundleDependencyCoordinate = bundleDetail + .getDependencyCoordinates(); + + // see if this class loader is eligible for loading + ClassLoader potentialBundleClassLoader = null; + if (bundleDependencyCoordinate == null) { + potentialBundleClassLoader = createBundleClassLoader(fileSystemManager, + bundleDetail.getBundleFile(), ClassLoader.getSystemClassLoader()); + } else { + final String dependencyCoordinateStr = bundleDependencyCoordinate + .getCoordinates(); + + // if the declared dependency has already been loaded + if (coordinateClassLoaderLookup.containsKey(dependencyCoordinateStr)) { + final ClassLoader bundleDependencyClassLoader = coordinateClassLoaderLookup + .get(dependencyCoordinateStr); + potentialBundleClassLoader = createBundleClassLoader( + fileSystemManager, bundleDetail.getBundleFile(), + bundleDependencyClassLoader); + } else { + // get all bundles that match the declared dependency id + final Set coordinates = idBundleLookup + .get(bundleDependencyCoordinate.getId()); + + // ensure there are known bundles that match the declared dependency id + if (coordinates != null && !coordinates + .contains(bundleDependencyCoordinate)) { + // ensure the declared dependency only has one possible bundle + if (coordinates.size() == 1) { + // get the bundle with the matching id + final BundleCoordinates coordinate = coordinates.stream() + .findFirst().get(); + + // if that bundle is loaded, use it + if (coordinateClassLoaderLookup + .containsKey(coordinate.getCoordinates())) { + logger.warn(String.format( + "While loading '%s' unable to locate exact BUNDLE dependency '%s'. Only found one possible match '%s'. Continuing...", + bundleDetail.getCoordinates().getCoordinates(), + dependencyCoordinateStr, + coordinate.getCoordinates())); + + final ClassLoader bundleDependencyClassLoader = coordinateClassLoaderLookup + .get(coordinate.getCoordinates()); + potentialBundleClassLoader = createBundleClassLoader( + fileSystemManager, bundleDetail.getBundleFile(), + bundleDependencyClassLoader); + } + } + } + } + } + + // if we were able to create the bundle class loader, store it and remove the details + final ClassLoader bundleClassLoader = potentialBundleClassLoader; + if (bundleClassLoader != null) { + directoryBundleLookup + .put(bundleDetail.getBundleFile().getURL().toURI().toString(), + new Bundle(bundleDetail, bundleClassLoader)); + coordinateClassLoaderLookup + .put(bundleDetail.getCoordinates().getCoordinates(), + bundleClassLoader); + bundleDetailsIter.remove(); + } + } + + // attempt to load more if some were successfully loaded this iteration + } while (bundleCount != bundleDetails.size()); + + // see if any bundle couldn't be loaded + for (final BundleDetails bundleDetail : bundleDetails) { + logger.warn(String + .format("Unable to resolve required dependency '%s'. Skipping BUNDLE '%s'", + bundleDetail.getDependencyCoordinates().getId(), + bundleDetail.getBundleFile().getURL().toURI().toString())); + } + } + } + // did we find it, and if we did was it a file? + if (StringUtils.isNotEmpty(explicitBundleToLoad)) { + if (!foundExplicitLoadBundle) { + StringBuilder builder = new StringBuilder(); + builder.append(String.format("Bundle File %s does not exist in ", explicitBundleToLoad)); + for (FileObject extDir : extensionsDirs) { + builder.append(extDir.getURL()).append(" "); + } + throw new IllegalArgumentException(builder.toString()); + } else if (explicitBundleIsNotFile) { + throw new IllegalArgumentException( + String.format("%s was found, but is not a file", explicitBundleToLoad)); + } + } + return new BundleClassLoadersContext(fileSystemManager, extensionsDirs, + new LinkedHashMap<>(directoryBundleLookup), properties); + } + + /** + * Loads the details for the specified BUNDLE. The details will be extracted from the manifest + * file. + * + * @param bundleFile the bundle file + * @return details about the Bundle + * @throws FileSystemException ioe + */ + private BundleDetails getBundleDetails(final FileObject bundleFile, BundleProperties props) + throws FileSystemException { + return BundleUtil.fromBundleFile(bundleFile, props); + } + + /** + * Creates a new BundleClassLoader. The parentClassLoader may be null. + * + * @param bundleFile the Bundle File + * @param parentClassLoader parent classloader of bundle + * @return the bundle classloader + * @throws FileSystemException ioe + * @throws ClassNotFoundException cfne + */ + private ClassLoader createBundleClassLoader(final FileSystemManager fileSystemManager, + final FileObject bundleFile, final ClassLoader parentClassLoader) + throws FileSystemException, ClassNotFoundException { + logger.debug("Loading Bundle file: " + bundleFile.getURL()); + final ClassLoader bundleClassLoader = new VFSBundleClassLoader.Builder() + .withFileSystemManager(fileSystemManager) + .withBundleFile(bundleFile) + .withParentClassloader(parentClassLoader).build(); + logger.info( + "Loaded Bundle file: " + bundleFile.getURL() + " as class loader " + bundleClassLoader); + return bundleClassLoader; + } + } + + private List extensionDirs; + private Map bundles; + private final BundleProperties properties; + private final FileSystemManager fileSystemManager; + + private BundleClassLoadersContext( + final FileSystemManager fileSystemManager, + final List extensionDirs, + final Map bundles, + final BundleProperties properties) { + this.extensionDirs = ImmutableList.copyOf(extensionDirs); + this.bundles = ImmutableMap.copyOf(bundles); + this.properties = properties; + this.fileSystemManager = fileSystemManager; + } + + /** + * Merges another BundleClassLoadersContext into this one, + * creating a union of the two. + * Responsibility for synchronization of access to this context is up to + * the holder of it's reference + * @param other a BundleClassLoadersContext instance to merge into this one + */ + public void merge(BundleClassLoadersContext other) { + + extensionDirs = ImmutableList.copyOf(Stream.concat(extensionDirs.stream(), other.extensionDirs.stream().filter((x)-> !extensionDirs.contains(x))).collect( + Collectors.toList())); + bundles = ImmutableMap.copyOf(Stream.of(bundles,other.bundles).map(Map::entrySet).flatMap( + Collection::stream).collect( + Collectors.toMap(Entry::getKey, Entry::getValue, (s,a) -> s))); + } + + public List getExtensionDirs() { + return extensionDirs; + } + + public Map getBundles() { + return bundles; + } + + public BundleProperties getProperties() { + return properties; + } + + public FileSystemManager getFileSystemManager() { + return fileSystemManager; + } +} diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleSystem.java b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleSystem.java index 7e93044879..71cf42bff7 100644 --- a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleSystem.java +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleSystem.java @@ -20,12 +20,15 @@ import com.google.common.annotations.VisibleForTesting; import java.lang.invoke.MethodHandles; import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; +import org.apache.commons.lang.StringUtils; import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemException; import org.apache.commons.vfs2.FileSystemManager; import org.apache.metron.bundles.bundle.Bundle; import org.apache.metron.bundles.util.BundleProperties; @@ -145,26 +148,26 @@ public BundleSystem build() throws NotInitializedException { BundleClassLoaders.init(fileSystemManager, libFileObjects, properties); ExtensionManager .init(extensionClasses, systemBundle, BundleClassLoaders.getInstance().getBundles()); - return new BundleSystem(fileSystemManager, extensionClasses, systemBundle, properties); + return new BundleSystem(fileSystemManager, extensionClasses, libFileObjects, systemBundle, properties); } catch (Exception e) { throw new NotInitializedException(e); } } - - } private final BundleProperties properties; private final FileSystemManager fileSystemManager; private final List extensionClasses; + private final List extensionDirectories; private final Bundle systemBundle; - private BundleSystem(FileSystemManager fileSystemManager, List extensionClasses, - Bundle systemBundle, BundleProperties properties) { + private BundleSystem(FileSystemManager fileSystemManager, List extensionClasses,List + extensionDirectories, Bundle systemBundle, BundleProperties properties) { this.properties = properties; this.fileSystemManager = fileSystemManager; this.extensionClasses = extensionClasses; this.systemBundle = systemBundle; + this.extensionDirectories = extensionDirectories; } /** @@ -180,23 +183,47 @@ private BundleSystem(FileSystemManager fileSystemManager, List extensionC public T createInstance(final String specificClassName, final Class clazz) throws ClassNotFoundException, InstantiationException, NotInitializedException, IllegalAccessException { - return BundleThreadContextClassLoader.createInstance(specificClassName, clazz, this.properties); + synchronized (BundleSystem.class) { + return BundleThreadContextClassLoader + .createInstance(specificClassName, clazz, this.properties); + } } @SuppressWarnings("unchecked") public Set> getExtensionsClassesForExtensionType(final Class extensionType) throws NotInitializedException { Set> set = new HashSet>(); - ExtensionManager.getInstance().getExtensions(extensionType).forEach((x) -> { - set.add((Class)x); - }); + synchronized (BundleSystem.class) { + ExtensionManager.getInstance().getExtensions(extensionType).forEach((x) -> { + set.add((Class) x); + }); + } return set; } + /** + * Loads a Bundle into the system. + * + * @param bundleFileName the name of a Bundle file to load into the system. This file must exist + * in one of the library directories + */ + public void addBundle(String bundleFileName) + throws NotInitializedException, ClassNotFoundException, FileSystemException, URISyntaxException { + if (StringUtils.isEmpty(bundleFileName)) { + throw new IllegalArgumentException("bundleFileName cannot be null or empty"); + } + synchronized (BundleSystem.class) { + Bundle bundle = BundleClassLoaders.getInstance().addBundle(bundleFileName); + ExtensionManager.getInstance().addBundle(bundle); + } + } + @VisibleForTesting() public static void reset() { - BundleClassLoaders.reset(); - ExtensionManager.reset(); + synchronized (BundleSystem.class) { + BundleClassLoaders.reset(); + ExtensionManager.reset(); + } } } diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManager.java b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManager.java index 5eb82c6525..c87ae2e780 100644 --- a/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManager.java +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManager.java @@ -19,13 +19,15 @@ import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import java.io.IOException; import java.lang.invoke.MethodHandles; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -39,22 +41,10 @@ import org.apache.metron.bundles.util.BundleProperties; import org.apache.metron.bundles.util.DummyFileObject; import org.apache.metron.bundles.util.FileUtils; -import org.apache.metron.bundles.util.ImmutableCollectionUtils; import org.apache.metron.bundles.util.StringUtils; -import org.apache.metron.bundles.annotation.behavior.RequiresInstanceClassLoading; - -import org.atteo.classindex.ClassIndex; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.lang.reflect.Modifier; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.concurrent.ConcurrentHashMap; - /** * A Singleton class for scanning through the classpath to load all extension components using * the ClassIndex and running through all classloaders (root, BUNDLEs). @@ -66,40 +56,13 @@ public class ExtensionManager { private static volatile ExtensionManager extensionManager; - private static volatile InitContext initContext; + private static volatile ExtensionManagerContext initContext; private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); public static final BundleCoordinates SYSTEM_BUNDLE_COORDINATE = new BundleCoordinates( BundleCoordinates.DEFAULT_GROUP, "system", BundleCoordinates.DEFAULT_VERSION); - private static final class InitContext { - - // Maps a service definition (interface) to those classes that implement the interface - private final Map> definitionMap; - private final Map> classNameBundleLookup; - private final Map bundleCoordinateBundleLookup; - private final Map classLoaderBundleLookup; - private final Set requiresInstanceClassLoading; - private final Map instanceClassloaderLookup; - - private InitContext(Map> definitionMap, - Map> classNameBundleLookup, - Map bundleCoordinateBundleLookup, - Map classLoaderBundleLookup, - Set requiresInstanceClassLoading, - Map instanceClassloaderLookup) { - - this.definitionMap = ImmutableCollectionUtils.immutableMapOfSets(definitionMap); - this.classNameBundleLookup = ImmutableCollectionUtils - .immutableMapOfLists(classNameBundleLookup); - this.bundleCoordinateBundleLookup = ImmutableMap.copyOf(bundleCoordinateBundleLookup); - this.classLoaderBundleLookup = ImmutableMap.copyOf(classLoaderBundleLookup); - this.requiresInstanceClassLoading = ImmutableSet.copyOf(requiresInstanceClassLoading); - this.instanceClassloaderLookup = new ConcurrentHashMap<>(instanceClassloaderLookup); - } - } - private ExtensionManager(){} /** @@ -143,57 +106,15 @@ public static void init(final List classes, final Bundle systemBundle, fi throw new IllegalStateException("ExtensionManager already exists"); } ExtensionManager em = new ExtensionManager(); - InitContext ic = em.discoverExtensions(classes, systemBundle, bundles); + ExtensionManagerContext ic = new ExtensionManagerContext.Builder() + .withClasses(classes) + .withSystemBundle(systemBundle) + .withBundles(bundles).build(); initContext = ic; extensionManager = em; } } - private InitContext discoverExtensions(final List classes, final Bundle systemBundle, final Set bundles) { - - if (classes == null || classes.size() == 0) { - throw new IllegalArgumentException("classes must be defined"); - } - // get the current context class loader - ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); - - final Map> definitionMap = new HashMap<>(); - final Map> classNameBundleLookup = new HashMap<>(); - final Map bundleCoordinateBundleLookup = new HashMap<>(); - final Map classLoaderBundleLookup = new HashMap<>(); - final Set requiresInstanceClassLoading = new HashSet<>(); - final Map instanceClassloaderLookup = new HashMap<>(); - - for(Class c : classes) { - definitionMap.put(c,new HashSet<>()); - } - // load the system bundle first so that any extensions found in JARs directly in lib will be registered as - // being from the system bundle and not from all the other Bundles - loadExtensions(systemBundle, definitionMap, classNameBundleLookup, requiresInstanceClassLoading); - bundleCoordinateBundleLookup.put(systemBundle.getBundleDetails().getCoordinates(), systemBundle); - classLoaderBundleLookup.put(systemBundle.getClassLoader(),systemBundle); - // consider each bundle class loader - for (final Bundle bundle : bundles) { - // Must set the context class loader to the bundle classloader itself - // so that static initialization techniques that depend on the context class loader will work properly - final ClassLoader bcl = bundle.getClassLoader(); - // store in the lookup - classLoaderBundleLookup.put(bcl,bundle); - - Thread.currentThread().setContextClassLoader(bcl); - loadExtensions(bundle, definitionMap, classNameBundleLookup, requiresInstanceClassLoading); - - // Create a look-up from withCoordinates to bundle - bundleCoordinateBundleLookup.put(bundle.getBundleDetails().getCoordinates(), bundle); - } - - // restore the current context class loader if appropriate - if (currentContextClassLoader != null) { - Thread.currentThread().setContextClassLoader(currentContextClassLoader); - } - return new InitContext(definitionMap, classNameBundleLookup, bundleCoordinateBundleLookup, - classLoaderBundleLookup, requiresInstanceClassLoading, instanceClassloaderLookup); - } /** * Returns a bundle representing the system class loader. @@ -228,115 +149,6 @@ public static Bundle createSystemBundle(final FileSystemManager fileSystemManage return new Bundle(systemBundleDetails, systemClassLoader); } - /** - * Loads extensions from the specified bundle. - * - * @param bundle from which to load extensions - */ - @SuppressWarnings("unchecked") - private static void loadExtensions(final Bundle bundle, - Map> definitionMap, - Map> classNameBundleLookup, - Set requiresInstanceClassLoading) { - - for (final Map.Entry> entry : definitionMap.entrySet()) { - // this is another extention point - // what we care about here is getting the right classes from the classloader for the bundle - // this *could* be as a 'service' itself with different implementations - // The NAR system uses the ServiceLoader, but this chokes on abstract classes, because for some - // reason it feels compelled to instantiate the class, - // which there may be in the system. - // Changed to ClassIndex - Class clazz = entry.getKey(); - ClassLoader cl = bundle.getClassLoader(); - Iterable> it = ClassIndex.getSubclasses(clazz, cl); - for (Class c : it) { - if (cl.equals(c.getClassLoader())) { - // check for abstract - if (!Modifier.isAbstract(c.getModifiers())) { - registerServiceClass(c, classNameBundleLookup, requiresInstanceClassLoading, bundle, - entry.getValue()); - } - } - } - it = ClassIndex.getAnnotated(clazz, cl); - for (Class c : it) { - if (cl.equals(clazz.getClassLoader())) { - // check for abstract - if (!Modifier.isAbstract(c.getModifiers())) { - registerServiceClass(c, classNameBundleLookup, requiresInstanceClassLoading, bundle, - entry.getValue()); - } - } - } - - } - } - - - /** - * Registers extension for the specified type from the specified Bundle. - * - * @param type the extension type - * @param classNameBundleMap mapping of classname to Bundle - * @param bundle the Bundle being mapped to - * @param classes to map to this classloader but which come from its ancestors - */ - private static void registerServiceClass(final Class type, - final Map> classNameBundleMap, - final Set requiresInstanceClassLoading, - final Bundle bundle, - final Set classes) { - final String className = type.getName(); - - // get the bundles that have already been registered for the class name - List registeredBundles = classNameBundleMap - .computeIfAbsent(className, (x) -> new ArrayList<>()); - - boolean alreadyRegistered = false; - for (final Bundle registeredBundle : registeredBundles) { - final BundleCoordinates registeredCoordinate = registeredBundle.getBundleDetails() - .getCoordinates(); - - // if the incoming bundle has the same withCoordinates as one of the registered bundles - // then consider it already registered - if (registeredCoordinate.equals(bundle.getBundleDetails().getCoordinates())) { - alreadyRegistered = true; - break; - } - - // if the type wasn't loaded from an ancestor, and the type isn't a parsers, cs, or reporting task, then - // fail registration because we don't support multiple versions of any other types - if (!multipleVersionsAllowed(type)) { - throw new IllegalStateException("Attempt was made to load " + className + " from " - + bundle.getBundleDetails().getCoordinates().getCoordinates() - + " but that class name is already loaded/registered from " + registeredBundle - .getBundleDetails().getCoordinates() - + " and multiple versions are not supported for this type" - ); - } - } - - // if none of the above was true then register the new bundle - if (!alreadyRegistered) { - registeredBundles.add(bundle); - classes.add(type); - - if (type.isAnnotationPresent(RequiresInstanceClassLoading.class)) { - requiresInstanceClassLoading.add(className); - } - } - } - - /** - * @param type a Class that we found from a service loader - * @return true if the given class is a parsers, controller service, or reporting task - */ - private static boolean multipleVersionsAllowed(Class type) { - // we don't really need to support multiple versions at this time - return false; - } - /** * Determines the effective ClassLoader for the instance of the given type. * @@ -369,7 +181,7 @@ public ClassLoader createInstanceClassLoader(final String classType, // then make a new InstanceClassLoader that is a full copy of the BUNDLE Class Loader, otherwise create an empty // InstanceClassLoader that has the Bundle ClassLoader as a parent ClassLoader instanceClassLoader; - if (initContext.requiresInstanceClassLoading.contains(classType) + if (initContext.getRequiresInstanceClassLoading().contains(classType) && (bundleClassLoader instanceof URLClassLoader)) { final URLClassLoader registeredUrlClassLoader = (URLClassLoader) bundleClassLoader; instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, @@ -379,7 +191,7 @@ public ClassLoader createInstanceClassLoader(final String classType, bundleClassLoader); } - initContext.instanceClassloaderLookup.put(instanceIdentifier, instanceClassLoader); + initContext.getInstanceClassloaderLookup().put(instanceIdentifier, instanceClassLoader); return instanceClassLoader; } @@ -392,7 +204,7 @@ public ClassLoader createInstanceClassLoader(final String classType, public ClassLoader getInstanceClassLoader(final String instanceIdentifier) throws NotInitializedException { checkInitialized(); - return initContext.instanceClassloaderLookup.get(instanceIdentifier); + return initContext.getInstanceClassloaderLookup().get(instanceIdentifier); } /** @@ -402,7 +214,7 @@ public ClassLoader getInstanceClassLoader(final String instanceIdentifier) */ public Set getExtensionClasses() throws NotInitializedException { checkInitialized(); - return ImmutableSet.copyOf(initContext.definitionMap.keySet()); + return ImmutableSet.copyOf(initContext.getDefinitionMap().keySet()); } /** @@ -417,7 +229,7 @@ public ClassLoader removeInstanceClassLoaderIfExists(final String instanceIdenti return null; } checkInitialized(); - final ClassLoader classLoader = initContext.instanceClassloaderLookup.remove(instanceIdentifier); + final ClassLoader classLoader = initContext.getInstanceClassloaderLookup().remove(instanceIdentifier); if (classLoader != null && (classLoader instanceof URLClassLoader)) { final URLClassLoader urlClassLoader = (URLClassLoader) classLoader; try { @@ -442,7 +254,7 @@ public boolean requiresInstanceClassLoading(final String classType) throws NotIn throw new IllegalArgumentException("Class type cannot be null"); } checkInitialized(); - return initContext.requiresInstanceClassLoading.contains(classType); + return initContext.getRequiresInstanceClassLoading().contains(classType); } /** @@ -456,7 +268,7 @@ public List getBundles(final String classType) throws NotInitializedExce throw new IllegalArgumentException("Class type cannot be null"); } checkInitialized(); - final List bundles = initContext.classNameBundleLookup.get(classType); + final List bundles = initContext.getClassNameBundleLookup().get(classType); return bundles == null ? Collections.emptyList() : new ArrayList<>(bundles); } @@ -471,7 +283,7 @@ public Bundle getBundle(final BundleCoordinates bundleCoordinates) throws NotIni throw new IllegalArgumentException("BundleCoordinates cannot be null"); } checkInitialized(); - return initContext.bundleCoordinateBundleLookup.get(bundleCoordinates); + return initContext.getBundleCoordinateBundleLookup().get(bundleCoordinates); } /** @@ -485,7 +297,7 @@ public Bundle getBundle(final ClassLoader classLoader) throws NotInitializedExce throw new IllegalArgumentException("ClassLoader cannot be null"); } checkInitialized(); - return initContext.classLoaderBundleLookup.get(classLoader); + return initContext.getClassLoaderBundleLookup().get(classLoader); } public Set getExtensions(final Class definition) throws NotInitializedException { @@ -493,7 +305,7 @@ public Set getExtensions(final Class definition) throws NotInitialized throw new IllegalArgumentException("Class cannot be null"); } checkInitialized(); - final Set extensions = initContext.definitionMap.get(definition); + final Set extensions = initContext.getDefinitionMap().get(definition); return (extensions == null) ? Collections.emptySet() : extensions; } @@ -502,12 +314,12 @@ public void logClassLoaderMapping() throws NotInitializedException { final StringBuilder builder = new StringBuilder(); builder.append("Extension Type Mapping to Bundle:"); - for (final Map.Entry> entry : initContext.definitionMap.entrySet()) { + for (final Map.Entry> entry : initContext.getDefinitionMap().entrySet()) { builder.append("\n\t=== ").append(entry.getKey().getSimpleName()).append(" Type ==="); for (final Class type : entry.getValue()) { - final List bundles = initContext.classNameBundleLookup.containsKey(type.getName()) - ? initContext.classNameBundleLookup.get(type.getName()) : Collections.emptyList(); + final List bundles = initContext.getClassNameBundleLookup().containsKey(type.getName()) + ? initContext.getClassNameBundleLookup().get(type.getName()) : Collections.emptyList(); builder.append("\n\t").append(type.getName()); @@ -524,8 +336,31 @@ public void logClassLoaderMapping() throws NotInitializedException { logger.info(builder.toString()); } + /** + * Add a new {@link Bundle} and it's extensions to the system + * This is an operation that would happen after initialization. + * This method has limited access, only package classes that + * can ensure thread saftey and control should call. + * + * + * @param bundle the {@link Bundle} to load + * @throws NotInitializedException If we are not initialized yet + */ + protected void addBundle(Bundle bundle) throws NotInitializedException { + checkInitialized(); + + Set bundles = new HashSet<>(); + bundles.add(bundle); + ExtensionManagerContext newContext = new ExtensionManagerContext.Builder().withBundles(bundles) + .withClasses(new ArrayList(initContext.getDefinitionMap().keySet())) + .withSystemBundle(initContext.getSystemBundle()) + .build(); + + initContext.merge(newContext); + } + public void checkInitialized() throws NotInitializedException { - InitContext ic = initContext; + ExtensionManagerContext ic = initContext; if (ic == null) { throw new NotInitializedException(); } diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java new file mode 100644 index 0000000000..e721ff7f7c --- /dev/null +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java @@ -0,0 +1,336 @@ +/* + * 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.metron.bundles; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.metron.bundles.annotation.behavior.RequiresInstanceClassLoading; +import org.apache.metron.bundles.bundle.Bundle; +import org.apache.metron.bundles.bundle.BundleCoordinates; +import org.apache.metron.bundles.util.ImmutableCollectionUtils; +import org.atteo.classindex.ClassIndex; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExtensionManagerContext { + + private static final Logger logger = LoggerFactory + .getLogger(MethodHandles.lookup().lookupClass()); + + public static class Builder { + + List classes; + Bundle systemBundle; + Set bundles; + + public Builder withClasses(List classes) { + this.classes = classes; + return this; + } + + public Builder withSystemBundle(Bundle systemBundle) { + this.systemBundle = systemBundle; + return this; + } + + public Builder withBundles(Set bundles) { + this.bundles = bundles; + return this; + } + + public Builder() { + } + + public ExtensionManagerContext build() { + if (systemBundle == null) { + throw new IllegalArgumentException("systemBundle must be defined"); + } + if (classes == null || classes.size() == 0) { + throw new IllegalArgumentException("classes must be defined"); + } + if (bundles == null) { + throw new IllegalArgumentException("bundles must be defined"); + } + + // get the current context class loader + ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader(); + + final Map> definitionMap = new HashMap<>(); + final Map> classNameBundleLookup = new HashMap<>(); + final Map bundleCoordinateBundleLookup = new HashMap<>(); + final Map classLoaderBundleLookup = new HashMap<>(); + final Set requiresInstanceClassLoading = new HashSet<>(); + final Map instanceClassloaderLookup = new HashMap<>(); + + for (Class c : classes) { + definitionMap.put(c, new HashSet<>()); + } + // load the system bundle first so that any extensions found in JARs directly in lib will be registered as + // being from the system bundle and not from all the other Bundles + loadExtensions(systemBundle, definitionMap, classNameBundleLookup, + requiresInstanceClassLoading); + bundleCoordinateBundleLookup + .put(systemBundle.getBundleDetails().getCoordinates(), systemBundle); + classLoaderBundleLookup.put(systemBundle.getClassLoader(), systemBundle); + // consider each bundle class loader + for (final Bundle bundle : bundles) { + // Must set the context class loader to the bundle classloader itself + // so that static initialization techniques that depend on the context class loader will work properly + final ClassLoader bcl = bundle.getClassLoader(); + // store in the lookup + classLoaderBundleLookup.put(bcl, bundle); + + Thread.currentThread().setContextClassLoader(bcl); + loadExtensions(bundle, definitionMap, classNameBundleLookup, requiresInstanceClassLoading); + + // Create a look-up from withCoordinates to bundle + bundleCoordinateBundleLookup.put(bundle.getBundleDetails().getCoordinates(), bundle); + } + + // restore the current context class loader if appropriate + if (currentContextClassLoader != null) { + Thread.currentThread().setContextClassLoader(currentContextClassLoader); + } + return new ExtensionManagerContext(systemBundle, definitionMap, classNameBundleLookup, + bundleCoordinateBundleLookup, + classLoaderBundleLookup, requiresInstanceClassLoading, instanceClassloaderLookup); + } + + /** + * Loads extensions from the specified bundle. + * + * @param bundle from which to load extensions + */ + @SuppressWarnings("unchecked") + private static void loadExtensions(final Bundle bundle, + Map> definitionMap, + Map> classNameBundleLookup, + Set requiresInstanceClassLoading) { + + for (final Entry> entry : definitionMap.entrySet()) { + // this is another extention point + // what we care about here is getting the right classes from the classloader for the bundle + // this *could* be as a 'service' itself with different implementations + // The NAR system uses the ServiceLoader, but this chokes on abstract classes, because for some + // reason it feels compelled to instantiate the class, + // which there may be in the system. + // Changed to ClassIndex + Class clazz = entry.getKey(); + ClassLoader cl = bundle.getClassLoader(); + Iterable> it = ClassIndex.getSubclasses(clazz, cl); + for (Class c : it) { + if (cl.equals(c.getClassLoader())) { + // check for abstract + if (!Modifier.isAbstract(c.getModifiers())) { + registerServiceClass(c, classNameBundleLookup, requiresInstanceClassLoading, bundle, + entry.getValue()); + } + } + } + it = ClassIndex.getAnnotated(clazz, cl); + for (Class c : it) { + if (cl.equals(clazz.getClassLoader())) { + // check for abstract + if (!Modifier.isAbstract(c.getModifiers())) { + registerServiceClass(c, classNameBundleLookup, requiresInstanceClassLoading, bundle, + entry.getValue()); + } + } + } + } + } + + /** + * Registers extension for the specified type from the specified Bundle. + * + * @param type the extension type + * @param classNameBundleMap mapping of classname to Bundle + * @param bundle the Bundle being mapped to + * @param classes to map to this classloader but which come from its ancestors + */ + private static void registerServiceClass(final Class type, + final Map> classNameBundleMap, + final Set requiresInstanceClassLoading, + final Bundle bundle, + final Set classes) { + final String className = type.getName(); + + // get the bundles that have already been registered for the class name + List registeredBundles = classNameBundleMap + .computeIfAbsent(className, (x) -> new ArrayList<>()); + + boolean alreadyRegistered = false; + for (final Bundle registeredBundle : registeredBundles) { + final BundleCoordinates registeredCoordinate = registeredBundle.getBundleDetails() + .getCoordinates(); + + // if the incoming bundle has the same withCoordinates as one of the registered bundles + // then consider it already registered + if (registeredCoordinate.equals(bundle.getBundleDetails().getCoordinates())) { + alreadyRegistered = true; + break; + } + + // if the type wasn't loaded from an ancestor, and the type isn't a parsers, cs, or reporting task, then + // fail registration because we don't support multiple versions of any other types + if (!multipleVersionsAllowed(type)) { + throw new IllegalStateException("Attempt was made to load " + className + " from " + + bundle.getBundleDetails().getCoordinates().getCoordinates() + + " but that class name is already loaded/registered from " + registeredBundle + .getBundleDetails().getCoordinates() + + " and multiple versions are not supported for this type" + ); + } + } + + // if none of the above was true then register the new bundle + if (!alreadyRegistered) { + registeredBundles.add(bundle); + classes.add(type); + + if (type.isAnnotationPresent(RequiresInstanceClassLoading.class)) { + requiresInstanceClassLoading.add(className); + } + } + } + + /** + * @param type a Class that we found from a service loader + * @return true if the given class is a parsers, controller service, or reporting task + */ + private static boolean multipleVersionsAllowed(Class type) { + // we don't really need to support multiple versions at this time + return false; + } + } + + // Maps a service definition (interface) to those classes that implement the interface + private Map> definitionMap; + private Map> classNameBundleLookup; + private Map bundleCoordinateBundleLookup; + private Map classLoaderBundleLookup; + private Set requiresInstanceClassLoading; + private Map instanceClassloaderLookup; + private Bundle systemBundle; + + + private ExtensionManagerContext(Bundle systemBundle, Map> definitionMap, + Map> classNameBundleLookup, + Map bundleCoordinateBundleLookup, + Map classLoaderBundleLookup, + Set requiresInstanceClassLoading, + Map instanceClassloaderLookup) { + this.systemBundle = systemBundle; + this.definitionMap = ImmutableCollectionUtils.immutableMapOfSets(definitionMap); + this.classNameBundleLookup = ImmutableCollectionUtils + .immutableMapOfLists(classNameBundleLookup); + this.bundleCoordinateBundleLookup = ImmutableMap.copyOf(bundleCoordinateBundleLookup); + this.classLoaderBundleLookup = ImmutableMap.copyOf(classLoaderBundleLookup); + this.requiresInstanceClassLoading = ImmutableSet.copyOf(requiresInstanceClassLoading); + this.instanceClassloaderLookup = new ConcurrentHashMap<>(instanceClassloaderLookup); + } + + /** + * Merges another ExtensionManagerContext into this one, creating a union of the two. + * Responsibility for synchronization of access to this context is up to the holder of it's + * reference + * + * @param other a ExtensionManagerContext instance to merge into this one + */ + public void merge(ExtensionManagerContext other) { + + // merge everything together + // not on key matches, we merge the collection values + this.classNameBundleLookup = ImmutableCollectionUtils.immutableMapOfLists( + Stream.of(this.classNameBundleLookup, other.classNameBundleLookup) + .map(Map::entrySet) + .flatMap(Collection::stream) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (entry1, entry2) -> { + return Stream.concat(((List)entry1).stream(),((List)entry2).stream()).filter((x) ->!((List)entry1).contains(x)) + .collect(Collectors.toList()); + }))); + this.definitionMap = ImmutableCollectionUtils.immutableMapOfSets( + Stream.of(this.definitionMap, other.definitionMap) + .map(Map::entrySet) + .flatMap(Collection::stream) + .collect(Collectors.toMap(Entry::getKey, Entry::getValue, (entry1, entry2) -> { + return Stream.concat(((Set)entry1).stream(),((Set)entry2).stream()).filter((x) -> !((Set)entry2).contains(x)) + .collect(Collectors.toSet()); + }))); + + this.bundleCoordinateBundleLookup = ImmutableMap.copyOf(Stream.of(bundleCoordinateBundleLookup, other.bundleCoordinateBundleLookup).map(Map::entrySet).flatMap( + Collection::stream).collect( + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (s, a) -> s))); + + this.classLoaderBundleLookup = ImmutableMap.copyOf(Stream.of(classLoaderBundleLookup, other.classLoaderBundleLookup).map(Map::entrySet).flatMap( + Collection::stream).collect( + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (s, a) -> s))); + + this.requiresInstanceClassLoading = ImmutableSet.copyOf( + Stream.concat(requiresInstanceClassLoading.stream(), + other.requiresInstanceClassLoading.stream().filter((x) -> !requiresInstanceClassLoading.contains(x))).collect( + Collectors.toSet())); + + this.instanceClassloaderLookup = new ConcurrentHashMap<>(Stream.of(instanceClassloaderLookup, other.instanceClassloaderLookup).map(Map::entrySet).flatMap( + Collection::stream).collect( + Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (s, a) -> s))); + } + + + public Map> getDefinitionMap() { + return definitionMap; + } + + public Map> getClassNameBundleLookup() { + return classNameBundleLookup; + } + + public Map getBundleCoordinateBundleLookup() { + return bundleCoordinateBundleLookup; + } + + public Map getClassLoaderBundleLookup() { + return classLoaderBundleLookup; + } + + public Set getRequiresInstanceClassLoading() { + return requiresInstanceClassLoading; + } + + public Map getInstanceClassloaderLookup() { + return instanceClassloaderLookup; + } + + public Bundle getSystemBundle() { + return systemBundle; + } + +} diff --git a/bundles-lib/src/test/java/org/apache/metron/bundles/AbstractFoo2.java b/bundles-lib/src/test/java/org/apache/metron/bundles/AbstractFoo2.java new file mode 100644 index 0000000000..35dac4244b --- /dev/null +++ b/bundles-lib/src/test/java/org/apache/metron/bundles/AbstractFoo2.java @@ -0,0 +1,27 @@ +/* + * 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.metron.bundles; + +import org.atteo.classindex.IndexSubclasses; + +@IndexSubclasses +public abstract class AbstractFoo2 { + + public void Do(){ + System.out.println("Foo"); + } +} diff --git a/bundles-lib/src/test/java/org/apache/metron/bundles/BundleClassLoadersContextTest.java b/bundles-lib/src/test/java/org/apache/metron/bundles/BundleClassLoadersContextTest.java new file mode 100644 index 0000000000..d28e4de423 --- /dev/null +++ b/bundles-lib/src/test/java/org/apache/metron/bundles/BundleClassLoadersContextTest.java @@ -0,0 +1,144 @@ +/* + * 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.metron.bundles; + +import static org.apache.metron.bundles.util.TestUtil.loadSpecifiedProperties; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.vfs2.FileObject; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.metron.bundles.bundle.Bundle; +import org.apache.metron.bundles.util.BundleProperties; +import org.apache.metron.bundles.util.FileSystemManagerFactory; +import org.apache.metron.bundles.util.ResourceCopier; +import org.apache.metron.bundles.util.TestUtil; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +public class BundleClassLoadersContextTest { + static final Map EMPTY_MAP = new HashMap(); + + @AfterClass + public static void after() { + BundleClassLoaders.reset(); + } + + @BeforeClass + public static void copyResources() throws IOException { + ResourceCopier.copyResources(Paths.get("./src/test/resources"), Paths.get("./target")); + } + + @Test + public void merge() throws Exception { + BundleProperties properties = loadSpecifiedProperties("/BundleMapper/conf/bundle.properties", + EMPTY_MAP); + + assertEquals("./target/BundleMapper/lib/", + properties.getProperty("bundle.library.directory")); + assertEquals("./target/BundleMapper/lib2/", + properties.getProperty("bundle.library.directory.alt")); + + String altLib = properties.getProperty("bundle.library.directory.alt"); + String lib = properties.getProperty("bundle.library.directory"); + properties.unSetProperty("bundle.library.directory.alt"); + + FileSystemManager fileSystemManager = FileSystemManagerFactory + .createFileSystemManager(new String[] {properties.getArchiveExtension()}); + + BundleClassLoadersContext firstContext = new BundleClassLoadersContext.Builder().withFileSystemManager(fileSystemManager) + .withExtensionDirs(TestUtil.getExtensionLibs(fileSystemManager,properties)).withBundleProperties(properties).build(); + + Assert.assertEquals(1, firstContext.getBundles().size()); + for (Bundle thisBundle : firstContext.getBundles().values()) { + Assert.assertEquals("org.apache.metron:metron-parser-bar-bundle:0.4.1", + thisBundle.getBundleDetails().getCoordinates().getCoordinates()); + } + + // set the lib again so the utils will pickup the other directory + properties.setProperty("bundle.library.directory", altLib); + + BundleClassLoadersContext secondContext = new BundleClassLoadersContext.Builder().withFileSystemManager(fileSystemManager) + .withExtensionDirs(TestUtil.getExtensionLibs(fileSystemManager,properties)).withBundleProperties(properties).build(); + + + Assert.assertEquals(1, secondContext.getBundles().size()); + for (Bundle thisBundle : secondContext.getBundles().values()) { + Assert.assertEquals("org.apache.metron:metron-parser-foo-bundle:0.4.1", + thisBundle.getBundleDetails().getCoordinates().getCoordinates()); + } + + // ok merge together + + firstContext.merge(secondContext); + Assert.assertEquals(2, firstContext.getBundles().size()); + for (Bundle thisBundle : firstContext.getBundles().values()) { + Assert.assertTrue( + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-bar-bundle:0.4.1") + || + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-foo-bundle:0.4.1") + + ); + } + + // merge a thirds, with duplicates + // set both dirs + properties.setProperty("bundle.library.directory.alt",lib); + + BundleClassLoadersContext thirdContext = new BundleClassLoadersContext.Builder().withFileSystemManager(fileSystemManager) + .withExtensionDirs(TestUtil.getExtensionLibs(fileSystemManager,properties)).withBundleProperties(properties).build(); + + Assert.assertEquals(2, thirdContext.getBundles().size()); + for (Bundle thisBundle : thirdContext.getBundles().values()) { + Assert.assertTrue( + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-bar-bundle:0.4.1") + || + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-foo-bundle:0.4.1") + + ); + } + + // merge them + firstContext.merge(thirdContext); + Assert.assertEquals(2, firstContext.getBundles().size()); + for (Bundle thisBundle : firstContext.getBundles().values()) { + Assert.assertTrue( + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-bar-bundle:0.4.1") + || + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-foo-bundle:0.4.1") + + ); + } + } +} \ No newline at end of file diff --git a/bundles-lib/src/test/java/org/apache/metron/bundles/BundleSystemTest.java b/bundles-lib/src/test/java/org/apache/metron/bundles/BundleSystemTest.java index ee0fd409b9..e455c7f7bf 100644 --- a/bundles-lib/src/test/java/org/apache/metron/bundles/BundleSystemTest.java +++ b/bundles-lib/src/test/java/org/apache/metron/bundles/BundleSystemTest.java @@ -19,11 +19,24 @@ import static org.junit.Assert.*; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.apache.commons.vfs2.FileSystemManager; import org.apache.metron.bundles.BundleThreadContextClassLoaderTest.WithPropertiesConstructor; +import org.apache.metron.bundles.bundle.Bundle; import org.apache.metron.bundles.util.BundleProperties; +import org.apache.metron.bundles.util.FileSystemManagerFactory; +import org.apache.metron.bundles.util.ResourceCopier; +import org.apache.metron.parsers.interfaces.MessageParser; +import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; public class BundleSystemTest { @@ -31,17 +44,33 @@ public class BundleSystemTest { @AfterClass public static void after() { BundleClassLoaders.reset(); - ExtensionManager.reset();; + ExtensionManager.reset(); + File t = new File("target/BundleMapper/lib/metron-parser-foo-bundle-0.4.1.bundle"); + if (t.exists()) { + t.delete(); + } } - + + @After + public void afterTest() { + ExtensionManager.reset(); + BundleClassLoaders.reset(); + } + + @BeforeClass + public static void copyResources() throws IOException { + ResourceCopier.copyResources(Paths.get("./src/test/resources"), Paths.get("./target")); + } + @Test public void createInstance() throws Exception { BundleProperties properties = BundleProperties - .createBasicBundleProperties("src/test/resources/bundle.properties", null); + .createBasicBundleProperties("target/bundle.properties", null); - properties.setProperty(BundleProperties.BUNDLE_LIBRARY_DIRECTORY,"src/test/resources/BundleMapper/lib"); - BundleSystem bundleSystem = new BundleSystem.Builder().withBundleProperties(properties).withExtensionClasses( - Arrays.asList(AbstractFoo.class)).build(); + properties.setProperty(BundleProperties.BUNDLE_LIBRARY_DIRECTORY, "target/BundleMapper/lib"); + BundleSystem bundleSystem = new BundleSystem.Builder().withBundleProperties(properties) + .withExtensionClasses( + Arrays.asList(AbstractFoo.class)).build(); Assert.assertTrue( bundleSystem.createInstance(WithPropertiesConstructor.class.getName(), WithPropertiesConstructor.class) instanceof WithPropertiesConstructor); @@ -53,4 +82,49 @@ public void createInstanceFail() throws Exception { BundleSystem bundleSystem = new BundleSystem.Builder().build(); } -} \ No newline at end of file + @Test + public void testAddBundle() throws Exception { + BundleProperties properties = BundleProperties + .createBasicBundleProperties("target/bundle.properties", null); + + properties.setProperty(BundleProperties.BUNDLE_LIBRARY_DIRECTORY, + "target/BundleMapper/lib"); + File f = new File("target/BundleMapper/metron-parser-foo-bundle-0.4.1.bundle"); + File t = new File( + "target/BundleMapper/lib/metron-parser-foo-bundle-0.4.1.bundle"); + if (t.exists()) { + t.delete(); + } + FileSystemManager fileSystemManager = FileSystemManagerFactory + .createFileSystemManager(new String[]{properties.getArchiveExtension()}); + BundleSystem bundleSystem = new BundleSystem.Builder() + .withFileSystemManager(fileSystemManager) + .withBundleProperties(properties).withExtensionClasses( + Arrays.asList(AbstractFoo.class, MessageParser.class)).build(); + Assert.assertTrue( + bundleSystem.createInstance(WithPropertiesConstructor.class.getName(), + WithPropertiesConstructor.class) instanceof WithPropertiesConstructor); + // copy the file into bundles library + FileUtils.copyFile(f, t); + Assert.assertEquals(1, BundleClassLoaders.getInstance().getBundles().size()); + for (Bundle thisBundle : BundleClassLoaders.getInstance().getBundles()) { + Assert.assertTrue( + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-bar-bundle:0.4.1") + ); + } + bundleSystem.addBundle("metron-parser-foo-bundle-0.4.1.bundle"); + + Assert.assertEquals(2, BundleClassLoaders.getInstance().getBundles().size()); + for (Bundle thisBundle : BundleClassLoaders.getInstance().getBundles()) { + Assert.assertTrue( + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-bar-bundle:0.4.1") + || + thisBundle.getBundleDetails().getCoordinates().getCoordinates() + .equals("org.apache.metron:metron-parser-foo-bundle:0.4.1") + + ); + } + } +} diff --git a/bundles-lib/src/test/java/org/apache/metron/bundles/ExtensionManagerContextTest.java b/bundles-lib/src/test/java/org/apache/metron/bundles/ExtensionManagerContextTest.java new file mode 100644 index 0000000000..eef6bbb150 --- /dev/null +++ b/bundles-lib/src/test/java/org/apache/metron/bundles/ExtensionManagerContextTest.java @@ -0,0 +1,110 @@ +/* + * 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.metron.bundles; + +import static org.apache.metron.bundles.util.TestUtil.loadSpecifiedProperties; +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.vfs2.FileSystemManager; +import org.apache.metron.bundles.bundle.Bundle; +import org.apache.metron.bundles.util.BundleProperties; +import org.apache.metron.bundles.util.FileSystemManagerFactory; +import org.apache.metron.bundles.util.TestUtil; +import org.apache.metron.parsers.interfaces.MessageParser; +import org.atteo.classindex.IndexSubclasses; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Test; + +public class ExtensionManagerContextTest { + static final Map EMPTY_MAP = new HashMap(); + + @AfterClass + public static void after() { + BundleClassLoaders.reset(); + } + + @Test + public void merge() throws Exception { + BundleProperties properties = loadSpecifiedProperties("/BundleMapper/conf/bundle.properties", + EMPTY_MAP); + + assertEquals("./target/BundleMapper/lib/", + properties.getProperty("bundle.library.directory")); + assertEquals("./target/BundleMapper/lib2/", + properties.getProperty("bundle.library.directory.alt")); + + FileSystemManager fileSystemManager = FileSystemManagerFactory + .createFileSystemManager(new String[] {properties.getArchiveExtension()}); + List classes = Arrays.asList(AbstractFoo.class); + + BundleClassLoaders + .init(fileSystemManager, TestUtil.getExtensionLibs(fileSystemManager, properties), + properties); + + Bundle systemBundle = ExtensionManager.createSystemBundle(fileSystemManager, properties); + ExtensionManagerContext context = new ExtensionManagerContext.Builder().withClasses(classes).withSystemBundle(systemBundle).withBundles(BundleClassLoaders.getInstance().getBundles()).build(); + + List bundles = context.getClassNameBundleLookup().get(BundleThreadContextClassLoaderTest.WithPropertiesConstructor.class.getName()); + Assert.assertTrue(bundles.size() == 1); + Assert.assertEquals(bundles.get(0), context.getClassLoaderBundleLookup().get(bundles.get(0).getClassLoader())); + Assert.assertEquals(bundles.get(0), context.getBundleCoordinateBundleLookup().get(bundles.get(0).getBundleDetails().getCoordinates())); + + + BundleClassLoaders.reset(); + + classes = Arrays.asList(AbstractFoo2.class); + + BundleClassLoaders + .init(fileSystemManager, TestUtil.getExtensionLibs(fileSystemManager, properties), + properties); + ExtensionManagerContext context2 = new ExtensionManagerContext.Builder().withClasses(classes).withSystemBundle(systemBundle).withBundles(BundleClassLoaders.getInstance().getBundles()).build(); + bundles = context2.getClassNameBundleLookup().get(WithPropertiesConstructor2.class.getName()); + Assert.assertTrue(bundles.size() == 1); + Assert.assertEquals(bundles.get(0), context2.getClassLoaderBundleLookup().get(bundles.get(0).getClassLoader())); + Assert.assertEquals(bundles.get(0), context2.getBundleCoordinateBundleLookup().get(bundles.get(0).getBundleDetails().getCoordinates())); + + context.merge(context2); + + bundles = context.getClassNameBundleLookup().get(BundleThreadContextClassLoaderTest.WithPropertiesConstructor.class.getName()); + Assert.assertTrue(bundles.size() == 1); + List bundles2 = context2.getClassNameBundleLookup().get(WithPropertiesConstructor2.class.getName()); + Assert.assertTrue(bundles2.size() == 1); + Assert.assertEquals(bundles.get(0), context.getClassLoaderBundleLookup().get(bundles.get(0).getClassLoader())); + Assert.assertEquals(bundles.get(0), context.getBundleCoordinateBundleLookup().get(bundles.get(0).getBundleDetails().getCoordinates())); + Assert.assertEquals(bundles2.get(0), context.getClassLoaderBundleLookup().get(bundles2.get(0).getClassLoader())); + Assert.assertEquals(bundles2.get(0), context.getBundleCoordinateBundleLookup().get(bundles2.get(0).getBundleDetails().getCoordinates())); + + + } + public static class WithPropertiesConstructor2 extends AbstractFoo2 { + + public WithPropertiesConstructor2() { + } + + public WithPropertiesConstructor2(BundleProperties properties) { + if (properties.getProperty("fail") != null) { + throw new RuntimeException("Intentional failure"); + } + } + } +} \ No newline at end of file diff --git a/bundles-lib/src/test/java/org/apache/metron/parsers/BasicParser.java b/bundles-lib/src/test/java/org/apache/metron/parsers/BasicParser.java new file mode 100644 index 0000000000..b2a5273194 --- /dev/null +++ b/bundles-lib/src/test/java/org/apache/metron/parsers/BasicParser.java @@ -0,0 +1,21 @@ +/* + * 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.metron.parsers; + +public abstract class BasicParser { +} diff --git a/bundles-lib/src/test/resources/BundleMapper/metron-parser-foo-bundle-0.4.1.bundle b/bundles-lib/src/test/resources/BundleMapper/metron-parser-foo-bundle-0.4.1.bundle new file mode 100644 index 0000000000000000000000000000000000000000..1fa85196f6a10ff4d6465844f6b2ec59bd7bdd8b GIT binary patch literal 21983 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 literal 0 HcmV?d00001 diff --git a/bundles-lib/src/test/resources/bundle.properties b/bundles-lib/src/test/resources/bundle.properties index d1a944ad98..9d94aa5de5 100644 --- a/bundles-lib/src/test/resources/bundle.properties +++ b/bundles-lib/src/test/resources/bundle.properties @@ -15,7 +15,7 @@ # Core Properties # bundle.library.directory=./target/lib/ -bundle.archive.extension=foo +bundle.archive.extension=bundle bundle.meta.id.prefix=Bundle bundle.extension.type.parser=org.apache.metron.parsers.interfaces.MessageParser From 0a38fc4146e513f2d989fa3cfddd1356eab1c40c Mon Sep 17 00:00:00 2001 From: Otto Fowler Date: Fri, 8 Sep 2017 14:07:44 -0400 Subject: [PATCH 2/2] add some javadoc --- .../bundles/BundleClassLoadersContext.java | 50 ++++++++++++++++++- .../bundles/ExtensionManagerContext.java | 38 +++++++++++++- 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java index 079f018d6c..ea2c77e765 100644 --- a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java @@ -50,13 +50,16 @@ /** - * Context object used by the BundleClassLoaders + * Context object for the {@link BundleClassLoaders}. */ public class BundleClassLoadersContext { private static final Logger logger = LoggerFactory .getLogger(MethodHandles.lookup().lookupClass()); + /** + * Builder class for BundleClassLoadersContext + */ public static class Builder { FileSystemManager fileSystemManager; @@ -67,26 +70,71 @@ public static class Builder { public Builder() { } + /** + * Provides a {@link FileSystemManager}. + * @param fileSystemManager + * @return + */ public Builder withFileSystemManager(FileSystemManager fileSystemManager) { this.fileSystemManager = fileSystemManager; return this; } + /** + * Provides the extension library directories. + * @param extensionDirs + * @return + */ public Builder withExtensionDirs(List extensionDirs) { this.extensionsDirs = extensionDirs; return this; } + /** + * Provides the BundleProperties. + * @param properties + * @return + */ public Builder withBundleProperties(BundleProperties properties) { this.properties = properties; return this; } + /** + * Builds a BundleClassLoaderContext. + * When built the context will be loaded from the provided + * library directories, using the {@link FileSystemManager} and {@BundleProperties}. + * + * An IllegalArgumentException will be thrown if any of the FileSystemManager, + * BundleProperties, or Extension Directories are missing or invalid. + * + * @return A loaded BundleClassLoaderContext + * @throws FileSystemException if there is a problem reading the bundles + * @throws ClassNotFoundException if there is a problem creating the classloaders + * @throws URISyntaxException if there is an invalid configuration + */ public BundleClassLoadersContext build() throws FileSystemException, ClassNotFoundException, URISyntaxException { return build(null); } + /** + * Builds a BundleClassLoaderContext. + * When built the context will be loaded from the provided + * explicitBundleToLoad, using the {@link FileSystemManager} and {@BundleProperties}. + * + * If the explicteBundleToLoad is null or empty, then the extensionDirs will be used. + * + * This method can be used as a means to build a context for a single bundle. + * + * An IllegalArgumentException will be thrown if any of the FileSystemManager, + * BundleProperties, or Extension Directories are missing or invalid. + * + * @return A loaded BundleClassLoaderContext + * @throws FileSystemException if there is a problem reading the bundles + * @throws ClassNotFoundException if there is a problem creating the classloaders + * @throws URISyntaxException if there is an invalid configuration + */ public BundleClassLoadersContext build(String explicitBundleToLoad) throws FileSystemException, ClassNotFoundException, URISyntaxException { diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java index e721ff7f7c..853bdd7671 100644 --- a/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java +++ b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java @@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.vfs2.FileSystemManager; import org.apache.metron.bundles.annotation.behavior.RequiresInstanceClassLoading; import org.apache.metron.bundles.bundle.Bundle; import org.apache.metron.bundles.bundle.BundleCoordinates; @@ -40,27 +41,49 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + * Context object for the {@link ExtensionManager}. + */ public class ExtensionManagerContext { private static final Logger logger = LoggerFactory .getLogger(MethodHandles.lookup().lookupClass()); - + /** + * Builder class for ExtensionManagerContext + */ public static class Builder { List classes; Bundle systemBundle; Set bundles; + /** + * Provides the {@link Class} definitions that will specify what extensions + * are to be loaded + * @param classes + * @return + */ public Builder withClasses(List classes) { this.classes = classes; return this; } + /** + * Provides the SystemBundle. + * This bundle represents the system or main classloader + * @param systemBundle + * @return + */ public Builder withSystemBundle(Bundle systemBundle) { this.systemBundle = systemBundle; return this; } + /** + * Provides the Bundles used to load the extensions + * @param bundles + * @return + */ public Builder withBundles(Set bundles) { this.bundles = bundles; return this; @@ -69,6 +92,19 @@ public Builder withBundles(Set bundles) { public Builder() { } + /** + * * Builds a BundleClassLoaderContext. + * When built the context will be loaded from the provided + * explicitBundleToLoad, using the {@link FileSystemManager} and {@BundleProperties}. + * + * If the explicteBundleToLoad is null or empty, then the extensionDirs will be used. + * + * This method can be used as a means to build a context for a single bundle. + * + * An IllegalArgumentException will be thrown if any of the SystemBundle, + * Classes, or Bundles parameters are missing or invalid + * @return + */ public ExtensionManagerContext build() { if (systemBundle == null) { throw new IllegalArgumentException("systemBundle must be defined");