You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by ot...@apache.org on 2017/09/09 03:21:02 UTC
[1/2] metron git commit: METRON-1165 Add ability for BundeSystem to
add bundles after initialization ( bundle added to lib dir ) (ottobackwards)
closes apache/metron#739
Repository: metron
Updated Branches:
refs/heads/feature/METRON-1136-extensions-parsers 09a62c54f -> 1c63c1eb3
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/java/org/apache/metron/bundles/ExtensionManagerContextTest.java
----------------------------------------------------------------------
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 0000000..eef6bbb
--- /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<String, String> EMPTY_MAP = new HashMap<String, String>();
+
+ @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<Class> 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<Bundle> 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<Bundle> 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
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/java/org/apache/metron/parsers/BasicParser.java
----------------------------------------------------------------------
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 0000000..b2a5273
--- /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 {
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/resources/BundleMapper/metron-parser-foo-bundle-0.4.1.bundle
----------------------------------------------------------------------
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 0000000..1fa8519
Binary files /dev/null and b/bundles-lib/src/test/resources/BundleMapper/metron-parser-foo-bundle-0.4.1.bundle differ
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/resources/bundle.properties
----------------------------------------------------------------------
diff --git a/bundles-lib/src/test/resources/bundle.properties b/bundles-lib/src/test/resources/bundle.properties
index d1a944a..9d94aa5 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
[2/2] metron git commit: METRON-1165 Add ability for BundeSystem to
add bundles after initialization ( bundle added to lib dir ) (ottobackwards)
closes apache/metron#739
Posted by ot...@apache.org.
METRON-1165 Add ability for BundeSystem to add bundles after initialization ( bundle added to lib dir ) (ottobackwards) closes apache/metron#739
Project: http://git-wip-us.apache.org/repos/asf/metron/repo
Commit: http://git-wip-us.apache.org/repos/asf/metron/commit/1c63c1eb
Tree: http://git-wip-us.apache.org/repos/asf/metron/tree/1c63c1eb
Diff: http://git-wip-us.apache.org/repos/asf/metron/diff/1c63c1eb
Branch: refs/heads/feature/METRON-1136-extensions-parsers
Commit: 1c63c1eb31ac5b2f6e3b6fcd1c8964ce78124697
Parents: 09a62c5
Author: ottobackwards <ot...@gmail.com>
Authored: Fri Sep 8 22:23:00 2017 -0400
Committer: otto <ot...@apache.org>
Committed: Fri Sep 8 22:23:00 2017 -0400
----------------------------------------------------------------------
.../metron/bundles/BundleClassLoaders.java | 260 ++----------
.../bundles/BundleClassLoadersContext.java | 421 +++++++++++++++++++
.../org/apache/metron/bundles/BundleSystem.java | 49 ++-
.../apache/metron/bundles/ExtensionManager.java | 259 +++---------
.../metron/bundles/ExtensionManagerContext.java | 372 ++++++++++++++++
.../org/apache/metron/bundles/AbstractFoo2.java | 27 ++
.../bundles/BundleClassLoadersContextTest.java | 144 +++++++
.../apache/metron/bundles/BundleSystemTest.java | 88 +++-
.../bundles/ExtensionManagerContextTest.java | 110 +++++
.../org/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, 1299 insertions(+), 454 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoaders.java
----------------------------------------------------------------------
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 946c71a..525caf6 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 @@ import org.slf4j.LoggerFactory;
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<FileObject> extensionDirs;
- private final Map<String, Bundle> bundles;
- private final BundleProperties properties;
-
- private InitContext(
- final List<FileObject> extensionDirs,
- final Map<String, Bundle> bundles,
- final BundleProperties properties) {
- this.extensionDirs = ImmutableList.copyOf(extensionDirs);
- this.bundles = ImmutableMap.copyOf(bundles);
- this.properties = properties;
- }
- }
-
private BundleClassLoaders() {
}
@@ -122,199 +91,18 @@ public final class BundleClassLoaders {
throw new IllegalStateException("BundleClassloader already exists");
}
BundleClassLoaders b = new BundleClassLoaders();
- InitContext ic = b.load(fileSystemManager, extensionsDirs, props);
+ BundleClassLoadersContext ic = b.load(fileSystemManager, extensionsDirs, props);
initContext = ic;
bundleClassLoaders = b;
}
}
- private InitContext load(final FileSystemManager fileSystemManager,
- final List<FileObject> extensionsDirs, BundleProperties props)
+ private BundleClassLoadersContext load(final FileSystemManager fileSystemManager,
+ final List<FileObject> 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<String, Bundle> directoryBundleLookup = new LinkedHashMap<>();
- final Map<String, ClassLoader> coordinateClassLoaderLookup = new HashMap<>();
- final Map<String, Set<BundleCoordinates>> idBundleLookup = new HashMap<>();
-
- for (FileObject extensionsDir : extensionsDirs) {
- // make sure the bundle directory is there and accessible
- FileUtils.ensureDirectoryExistAndCanRead(extensionsDir);
-
- final List<FileObject> bundleDirContents = new ArrayList<>();
- FileObject[] dirFiles = extensionsDir.findFiles(new BundleSelector(props.getArchiveExtension()));
- if (dirFiles != null) {
- List<FileObject> fileList = Arrays.asList(dirFiles);
- bundleDirContents.addAll(fileList);
- }
-
- if (!bundleDirContents.isEmpty()) {
- final List<BundleDetails> bundleDetails = new ArrayList<>();
- final Map<String, String> 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<BundleDetails> 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<BundleDetails> 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<BundleCoordinates> 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 final class BundleClassLoaders {
}
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 final class BundleClassLoaders {
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();
+ }
}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java
----------------------------------------------------------------------
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 0000000..ea2c77e
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleClassLoadersContext.java
@@ -0,0 +1,421 @@
+/*
+ * 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 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;
+ List<FileObject> extensionsDirs;
+ FileObject bundleFile;
+ BundleProperties properties;
+
+ 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<FileObject> 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 {
+
+ 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<String, Bundle> directoryBundleLookup = new LinkedHashMap<>();
+ final Map<String, ClassLoader> coordinateClassLoaderLookup = new HashMap<>();
+ final Map<String, Set<BundleCoordinates>> 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<FileObject> 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<FileObject> fileList = Arrays.asList(dirFiles);
+ bundleDirContents.addAll(fileList);
+ }
+
+ if (!bundleDirContents.isEmpty()) {
+ final List<BundleDetails> bundleDetails = new ArrayList<>();
+ final Map<String, String> 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<BundleDetails> 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<BundleDetails> 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<BundleCoordinates> 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<FileObject> extensionDirs;
+ private Map<String, Bundle> bundles;
+ private final BundleProperties properties;
+ private final FileSystemManager fileSystemManager;
+
+ private BundleClassLoadersContext(
+ final FileSystemManager fileSystemManager,
+ final List<FileObject> extensionDirs,
+ final Map<String, Bundle> 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<FileObject> getExtensionDirs() {
+ return extensionDirs;
+ }
+
+ public Map<String, Bundle> getBundles() {
+ return bundles;
+ }
+
+ public BundleProperties getProperties() {
+ return properties;
+ }
+
+ public FileSystemManager getFileSystemManager() {
+ return fileSystemManager;
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/main/java/org/apache/metron/bundles/BundleSystem.java
----------------------------------------------------------------------
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 7e93044..71cf42b 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 @@ package org.apache.metron.bundles;
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 class BundleSystem {
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<Class> extensionClasses;
+ private final List<FileObject> extensionDirectories;
private final Bundle systemBundle;
- private BundleSystem(FileSystemManager fileSystemManager, List<Class> extensionClasses,
- Bundle systemBundle, BundleProperties properties) {
+ private BundleSystem(FileSystemManager fileSystemManager, List<Class> extensionClasses,List<FileObject>
+ extensionDirectories, Bundle systemBundle, BundleProperties properties) {
this.properties = properties;
this.fileSystemManager = fileSystemManager;
this.extensionClasses = extensionClasses;
this.systemBundle = systemBundle;
+ this.extensionDirectories = extensionDirectories;
}
/**
@@ -180,23 +183,47 @@ public class BundleSystem {
public <T> T createInstance(final String specificClassName, final Class<T> 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 <T> Set<Class<? extends T>> getExtensionsClassesForExtensionType(final Class<T> extensionType)
throws NotInitializedException {
Set<Class<? extends T>> set = new HashSet<Class<? extends T>>();
- ExtensionManager.getInstance().getExtensions(extensionType).forEach((x) -> {
- set.add((Class<T>)x);
- });
+ synchronized (BundleSystem.class) {
+ ExtensionManager.getInstance().getExtensions(extensionType).forEach((x) -> {
+ set.add((Class<T>) 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();
+ }
}
}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManager.java
----------------------------------------------------------------------
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 5eb82c6..c87ae2e 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 @@ package org.apache.metron.bundles;
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.bundle.BundleDetails;
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 @@ import java.util.concurrent.ConcurrentHashMap;
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<Class, Set<Class>> definitionMap;
- private final Map<String, List<Bundle>> classNameBundleLookup;
- private final Map<BundleCoordinates, Bundle> bundleCoordinateBundleLookup;
- private final Map<ClassLoader, Bundle> classLoaderBundleLookup;
- private final Set<String> requiresInstanceClassLoading;
- private final Map<String, ClassLoader> instanceClassloaderLookup;
-
- private InitContext(Map<Class, Set<Class>> definitionMap,
- Map<String, List<Bundle>> classNameBundleLookup,
- Map<BundleCoordinates, Bundle> bundleCoordinateBundleLookup,
- Map<ClassLoader, Bundle> classLoaderBundleLookup,
- Set<String> requiresInstanceClassLoading,
- Map<String, ClassLoader> 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 class ExtensionManager {
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<Class> classes, final Bundle systemBundle, final Set<Bundle> 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<Class, Set<Class>> definitionMap = new HashMap<>();
- final Map<String, List<Bundle>> classNameBundleLookup = new HashMap<>();
- final Map<BundleCoordinates, Bundle> bundleCoordinateBundleLookup = new HashMap<>();
- final Map<ClassLoader, Bundle> classLoaderBundleLookup = new HashMap<>();
- final Set<String> requiresInstanceClassLoading = new HashSet<>();
- final Map<String, ClassLoader> 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.
@@ -229,115 +150,6 @@ public class ExtensionManager {
}
/**
- * Loads extensions from the specified bundle.
- *
- * @param bundle from which to load extensions
- */
- @SuppressWarnings("unchecked")
- private static void loadExtensions(final Bundle bundle,
- Map<Class, Set<Class>> definitionMap,
- Map<String, List<Bundle>> classNameBundleLookup,
- Set<String> requiresInstanceClassLoading) {
-
- for (final Map.Entry<Class, Set<Class>> 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<Class<?>> 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<String, List<Bundle>> classNameBundleMap,
- final Set<String> requiresInstanceClassLoading,
- final Bundle bundle,
- final Set<Class> classes) {
- final String className = type.getName();
-
- // get the bundles that have already been registered for the class name
- List<Bundle> 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.
*
* @param classType the type of class to lookup the ClassLoader for
@@ -369,7 +181,7 @@ public class ExtensionManager {
// 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 class ExtensionManager {
bundleClassLoader);
}
- initContext.instanceClassloaderLookup.put(instanceIdentifier, instanceClassLoader);
+ initContext.getInstanceClassloaderLookup().put(instanceIdentifier, instanceClassLoader);
return instanceClassLoader;
}
@@ -392,7 +204,7 @@ public class ExtensionManager {
public ClassLoader getInstanceClassLoader(final String instanceIdentifier)
throws NotInitializedException {
checkInitialized();
- return initContext.instanceClassloaderLookup.get(instanceIdentifier);
+ return initContext.getInstanceClassloaderLookup().get(instanceIdentifier);
}
/**
@@ -402,7 +214,7 @@ public class ExtensionManager {
*/
public Set<Class> getExtensionClasses() throws NotInitializedException {
checkInitialized();
- return ImmutableSet.copyOf(initContext.definitionMap.keySet());
+ return ImmutableSet.copyOf(initContext.getDefinitionMap().keySet());
}
/**
@@ -417,7 +229,7 @@ public class ExtensionManager {
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 class ExtensionManager {
throw new IllegalArgumentException("Class type cannot be null");
}
checkInitialized();
- return initContext.requiresInstanceClassLoading.contains(classType);
+ return initContext.getRequiresInstanceClassLoading().contains(classType);
}
/**
@@ -456,7 +268,7 @@ public class ExtensionManager {
throw new IllegalArgumentException("Class type cannot be null");
}
checkInitialized();
- final List<Bundle> bundles = initContext.classNameBundleLookup.get(classType);
+ final List<Bundle> bundles = initContext.getClassNameBundleLookup().get(classType);
return bundles == null ? Collections.emptyList() : new ArrayList<>(bundles);
}
@@ -471,7 +283,7 @@ public class ExtensionManager {
throw new IllegalArgumentException("BundleCoordinates cannot be null");
}
checkInitialized();
- return initContext.bundleCoordinateBundleLookup.get(bundleCoordinates);
+ return initContext.getBundleCoordinateBundleLookup().get(bundleCoordinates);
}
/**
@@ -485,7 +297,7 @@ public class ExtensionManager {
throw new IllegalArgumentException("ClassLoader cannot be null");
}
checkInitialized();
- return initContext.classLoaderBundleLookup.get(classLoader);
+ return initContext.getClassLoaderBundleLookup().get(classLoader);
}
public Set<Class> getExtensions(final Class<?> definition) throws NotInitializedException {
@@ -493,7 +305,7 @@ public class ExtensionManager {
throw new IllegalArgumentException("Class cannot be null");
}
checkInitialized();
- final Set<Class> extensions = initContext.definitionMap.get(definition);
+ final Set<Class> extensions = initContext.getDefinitionMap().get(definition);
return (extensions == null) ? Collections.<Class>emptySet() : extensions;
}
@@ -502,12 +314,12 @@ public class ExtensionManager {
final StringBuilder builder = new StringBuilder();
builder.append("Extension Type Mapping to Bundle:");
- for (final Map.Entry<Class, Set<Class>> entry : initContext.definitionMap.entrySet()) {
+ for (final Map.Entry<Class, Set<Class>> entry : initContext.getDefinitionMap().entrySet()) {
builder.append("\n\t=== ").append(entry.getKey().getSimpleName()).append(" Type ===");
for (final Class type : entry.getValue()) {
- final List<Bundle> bundles = initContext.classNameBundleLookup.containsKey(type.getName())
- ? initContext.classNameBundleLookup.get(type.getName()) : Collections.emptyList();
+ final List<Bundle> 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 class ExtensionManager {
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<Bundle> bundles = new HashSet<>();
+ bundles.add(bundle);
+ ExtensionManagerContext newContext = new ExtensionManagerContext.Builder().withBundles(bundles)
+ .withClasses(new ArrayList<Class>(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();
}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java
----------------------------------------------------------------------
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 0000000..853bdd7
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManagerContext.java
@@ -0,0 +1,372 @@
+/*
+ * 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.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;
+import org.apache.metron.bundles.util.ImmutableCollectionUtils;
+import org.atteo.classindex.ClassIndex;
+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<Class> classes;
+ Bundle systemBundle;
+ Set<Bundle> bundles;
+
+ /**
+ * Provides the {@link Class} definitions that will specify what extensions
+ * are to be loaded
+ * @param classes
+ * @return
+ */
+ public Builder withClasses(List<Class> 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<Bundle> bundles) {
+ this.bundles = bundles;
+ return this;
+ }
+
+ 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");
+ }
+ 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<Class, Set<Class>> definitionMap = new HashMap<>();
+ final Map<String, List<Bundle>> classNameBundleLookup = new HashMap<>();
+ final Map<BundleCoordinates, Bundle> bundleCoordinateBundleLookup = new HashMap<>();
+ final Map<ClassLoader, Bundle> classLoaderBundleLookup = new HashMap<>();
+ final Set<String> requiresInstanceClassLoading = new HashSet<>();
+ final Map<String, ClassLoader> 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<Class, Set<Class>> definitionMap,
+ Map<String, List<Bundle>> classNameBundleLookup,
+ Set<String> requiresInstanceClassLoading) {
+
+ for (final Entry<Class, Set<Class>> 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<Class<?>> 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<String, List<Bundle>> classNameBundleMap,
+ final Set<String> requiresInstanceClassLoading,
+ final Bundle bundle,
+ final Set<Class> classes) {
+ final String className = type.getName();
+
+ // get the bundles that have already been registered for the class name
+ List<Bundle> 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<Class, Set<Class>> definitionMap;
+ private Map<String, List<Bundle>> classNameBundleLookup;
+ private Map<BundleCoordinates, Bundle> bundleCoordinateBundleLookup;
+ private Map<ClassLoader, Bundle> classLoaderBundleLookup;
+ private Set<String> requiresInstanceClassLoading;
+ private Map<String, ClassLoader> instanceClassloaderLookup;
+ private Bundle systemBundle;
+
+
+ private ExtensionManagerContext(Bundle systemBundle, Map<Class, Set<Class>> definitionMap,
+ Map<String, List<Bundle>> classNameBundleLookup,
+ Map<BundleCoordinates, Bundle> bundleCoordinateBundleLookup,
+ Map<ClassLoader, Bundle> classLoaderBundleLookup,
+ Set<String> requiresInstanceClassLoading,
+ Map<String, ClassLoader> 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<Bundle>)entry1).stream(),((List<Bundle>)entry2).stream()).filter((x) ->!((List<Bundle>)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<Class>)entry1).stream(),((Set<Class>)entry2).stream()).filter((x) -> !((Set<Class>)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<Class, Set<Class>> getDefinitionMap() {
+ return definitionMap;
+ }
+
+ public Map<String, List<Bundle>> getClassNameBundleLookup() {
+ return classNameBundleLookup;
+ }
+
+ public Map<BundleCoordinates, Bundle> getBundleCoordinateBundleLookup() {
+ return bundleCoordinateBundleLookup;
+ }
+
+ public Map<ClassLoader, Bundle> getClassLoaderBundleLookup() {
+ return classLoaderBundleLookup;
+ }
+
+ public Set<String> getRequiresInstanceClassLoading() {
+ return requiresInstanceClassLoading;
+ }
+
+ public Map<String, ClassLoader> getInstanceClassloaderLookup() {
+ return instanceClassloaderLookup;
+ }
+
+ public Bundle getSystemBundle() {
+ return systemBundle;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/java/org/apache/metron/bundles/AbstractFoo2.java
----------------------------------------------------------------------
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 0000000..35dac42
--- /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");
+ }
+}
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/java/org/apache/metron/bundles/BundleClassLoadersContextTest.java
----------------------------------------------------------------------
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 0000000..d28e4de
--- /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<String, String> EMPTY_MAP = new HashMap<String, String>();
+
+ @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
http://git-wip-us.apache.org/repos/asf/metron/blob/1c63c1eb/bundles-lib/src/test/java/org/apache/metron/bundles/BundleSystemTest.java
----------------------------------------------------------------------
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 ee0fd40..e455c7f 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 @@ package org.apache.metron.bundles;
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 class BundleSystemTest {
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")
+
+ );
+ }
+ }
+}