metron-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From o...@apache.org
Subject [42/43] metron git commit: METRON-1136 Metron Extensions System and Parser Extensions Feature Branch (ottobackwards) closes apache/metron#720
Date Wed, 30 Aug 2017 15:38:24 GMT
http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/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
new file mode 100644
index 0000000..7e93044
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleSystem.java
@@ -0,0 +1,202 @@
+/*
+ * 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.annotations.VisibleForTesting;
+import java.lang.invoke.MethodHandles;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * High level interface to the Bundle System.  While you may want to use the lower level classes it
+ * is not required, as BundleSystem provides the base required interface for initializing the system
+ * and instantiating classes
+ */
+public class BundleSystem {
+
+  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  /**
+   * Builder for a BundleSystem. only {@link BundleProperties} are required. Beyond that, the
+   * BundleProperties, if they are the only parameter must have archive extension and bundle
+   * extension types properties present.
+   */
+  public static class Builder {
+
+    private BundleProperties properties;
+    private FileSystemManager fileSystemManager;
+    private List<Class> extensionClasses = new LinkedList<>();
+    private Bundle systemBundle;
+
+    /**
+     * The BundleProperties to use.  Unless other builder parameters override options
+     * (withExtensionClasses ), they must have archive extension and bundle extensions types
+     * specified
+     *
+     * @param properties The BundleProperties
+     * @return Builder
+     */
+    public Builder withBundleProperties(BundleProperties properties) {
+      this.properties = properties;
+      return this;
+    }
+
+    /**
+     * Provide a {@link FileSystemManager} to overide the default
+     *
+     * @param fileSystemManager override
+     * @return Builder
+     */
+    public Builder withFileSystemManager(FileSystemManager fileSystemManager) {
+      this.fileSystemManager = fileSystemManager;
+      return this;
+    }
+
+    /**
+     * Provide Extension Classes.  If not provided with this override then the classes will be
+     * configured from the BundleProperties. If provided, the properties file will not be used for
+     * classes.
+     *
+     * @param extensionClasses override
+     * @return Builder
+     */
+    public Builder withExtensionClasses(List<Class> extensionClasses) {
+      this.extensionClasses.addAll(extensionClasses);
+      return this;
+    }
+
+    /**
+     * Provide a SystemBundle.  If not provided with this override then the default SystemBundle
+     * will be created.
+     */
+    public Builder withSystemBundle(Bundle systemBundle) {
+      this.systemBundle = systemBundle;
+      return this;
+    }
+
+    /**
+     * Builds a new BundleSystem.
+     *
+     * @return BundleSystem
+     * @throws NotInitializedException if any errors happen during build
+     */
+    public BundleSystem build() throws NotInitializedException {
+      if (this.properties == null) {
+        throw new IllegalArgumentException("BundleProperties are required");
+      }
+      try {
+        if (this.fileSystemManager == null) {
+          this.fileSystemManager = FileSystemManagerFactory
+              .createFileSystemManager(new String[]{properties.getArchiveExtension()});
+        }
+        if (this.extensionClasses.isEmpty()) {
+          properties.getBundleExtensionTypes().forEach((x, y) -> {
+            try {
+              this.extensionClasses.add(Class.forName(y));
+            } catch (ClassNotFoundException e) {
+              throw new IllegalStateException(e);
+            }
+          });
+        }
+        if (this.systemBundle == null) {
+          this.systemBundle = ExtensionManager
+              .createSystemBundle(this.fileSystemManager, this.properties);
+        }
+        List<URI> libDirs = properties.getBundleLibraryDirectories();
+        List<FileObject> libFileObjects = new ArrayList<>();
+        libDirs.forEach((x) -> {
+          try {
+            FileObject fileObject = fileSystemManager.resolveFile(x);
+            if (fileObject.exists()) {
+              libFileObjects.add(fileObject);
+            }
+          } catch (Exception e) {
+            throw new IllegalStateException(e);
+          }
+        });
+
+        // initialize the Bundle System
+        BundleClassLoaders.init(fileSystemManager, libFileObjects, properties);
+        ExtensionManager
+            .init(extensionClasses, systemBundle, BundleClassLoaders.getInstance().getBundles());
+        return new BundleSystem(fileSystemManager, extensionClasses, systemBundle, properties);
+      } catch (Exception e) {
+        throw new NotInitializedException(e);
+      }
+    }
+
+
+  }
+
+  private final BundleProperties properties;
+  private final FileSystemManager fileSystemManager;
+  private final List<Class> extensionClasses;
+  private final Bundle systemBundle;
+
+  private BundleSystem(FileSystemManager fileSystemManager, List<Class> extensionClasses,
+      Bundle systemBundle, BundleProperties properties) {
+    this.properties = properties;
+    this.fileSystemManager = fileSystemManager;
+    this.extensionClasses = extensionClasses;
+    this.systemBundle = systemBundle;
+  }
+
+  /**
+   * Constructs an instance of the given type using either default no args constructor or a
+   * constructor which takes a BundleProperties object.
+   *
+   * @param specificClassName the implementation class name
+   * @param clazz the type (T) to create an instance for
+   * @return an instance of specificClassName which extends T
+   * @throws ClassNotFoundException if the class cannot be found
+   * @throws InstantiationException if the class cannot be instantiated
+   */
+  public <T> T createInstance(final String specificClassName, final Class<T> clazz)
+      throws ClassNotFoundException, InstantiationException,
+      NotInitializedException, IllegalAccessException {
+    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);
+    });
+    return set;
+  }
+
+  @VisibleForTesting()
+  public static void reset() {
+    BundleClassLoaders.reset();
+    ExtensionManager.reset();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/BundleThreadContextClassLoader.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/BundleThreadContextClassLoader.java b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleThreadContextClassLoader.java
new file mode 100644
index 0000000..6d3d104
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/BundleThreadContextClassLoader.java
@@ -0,0 +1,211 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.metron.bundles.bundle.Bundle;
+import org.apache.metron.bundles.util.BundleProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BundleThreadContextClassLoader extends URLClassLoader {
+    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+    static final ContextSecurityManager contextSecurityManager = new ContextSecurityManager();
+    private final ClassLoader forward = ClassLoader.getSystemClassLoader();
+
+    private BundleThreadContextClassLoader() {
+        super(new URL[0]);
+    }
+
+    @Override
+    public void clearAssertionStatus() {
+        lookupClassLoader().clearAssertionStatus();
+    }
+
+    @Override
+    public URL getResource(String name) {
+        return lookupClassLoader().getResource(name);
+    }
+
+    @Override
+    public InputStream getResourceAsStream(String name) {
+        return lookupClassLoader().getResourceAsStream(name);
+    }
+
+    @Override
+    public Enumeration<URL> getResources(String name) throws IOException {
+        return lookupClassLoader().getResources(name);
+    }
+
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        return lookupClassLoader().loadClass(name);
+    }
+
+    @Override
+    public void setClassAssertionStatus(String className, boolean enabled) {
+        lookupClassLoader().setClassAssertionStatus(className, enabled);
+    }
+
+    @Override
+    public void setDefaultAssertionStatus(boolean enabled) {
+        lookupClassLoader().setDefaultAssertionStatus(enabled);
+    }
+
+    @Override
+    public void setPackageAssertionStatus(String packageName, boolean enabled) {
+        lookupClassLoader().setPackageAssertionStatus(packageName, enabled);
+    }
+
+    private ClassLoader lookupClassLoader() {
+        final Class<?>[] classStack = contextSecurityManager.getExecutionStack();
+
+        for (Class<?> currentClass : classStack) {
+            final Class<?> bundleClass = findBundleClass(currentClass);
+            if (bundleClass != null) {
+                final ClassLoader desiredClassLoader = bundleClass.getClassLoader();
+
+                // When new Threads are created, the new Thread inherits the ClassLoaderContext of
+                // the caller. However, the call stack of that new Thread may not trace back to any app-specific
+                // code. Therefore, the BundleThreadContextClassLoader will be unable to find the appropriate Bundle
+                // ClassLoader. As a result, we want to set the ContextClassLoader to the Bundle ClassLoader that
+                // contains the class or resource that we are looking for.
+                // This locks the current Thread into the appropriate Bundle ClassLoader Context. The framework will change
+                // the ContextClassLoader back to the BundleThreadContextClassLoader as appropriate via the
+                //
+                // TL;DR
+                // We need to make sure the classloader for the thread is setup correctly to use the bundle classloader
+                // before we return the class.
+                // Just looking the class up is not enough.
+                // 
+                // {@link FlowEngine.beforeExecute(Thread, Runnable)} and
+                // {@link FlowEngine.afterExecute(Thread, Runnable)} methods.
+                if (desiredClassLoader instanceof VFSBundleClassLoader) {
+                    Thread.currentThread().setContextClassLoader(desiredClassLoader);
+                }
+                return desiredClassLoader;
+            }
+        }
+        return forward;
+    }
+
+    private Class<?> findBundleClass(final Class<?> cls) {
+        try {
+            for (final Class<?> bundleClass : ExtensionManager.getInstance()
+                .getExtensionClasses()) {
+                if (bundleClass.isAssignableFrom(cls)) {
+                    return cls;
+                } else if (cls.getEnclosingClass() != null) {
+                    return findBundleClass(cls.getEnclosingClass());
+                }
+            }
+        }catch(NotInitializedException e){
+            LOG.error("ExtensionManager not initialized",e);
+        }
+
+        return null;
+    }
+
+    private static class SingletonHolder {
+
+        public static final BundleThreadContextClassLoader instance = new BundleThreadContextClassLoader();
+    }
+
+    public static BundleThreadContextClassLoader getInstance() {
+        return SingletonHolder.instance;
+    }
+
+    static class ContextSecurityManager extends SecurityManager {
+
+        Class<?>[] getExecutionStack() {
+            return getClassContext();
+        }
+    }
+
+    /**
+     * Constructs an instance of the given type using either default no args
+     * constructor or a constructor which takes a BundleProperties object
+     * (preferred).
+     *
+     * @param <T> the type to create an instance for
+     * @param implementationClassName the implementation class name
+     * @param typeDefinition the type definition
+     * @param bundleProperties the BundleProperties instance
+     * @return constructed instance
+     * @throws InstantiationException if there is an error instantiating the class
+     * @throws IllegalAccessException if there is an error accessing the type
+     * @throws ClassNotFoundException if the class cannot be found
+     */
+    public static <T> T createInstance(final String implementationClassName, final Class<T> typeDefinition, final BundleProperties bundleProperties)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException, NotInitializedException {
+        final ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
+        Thread.currentThread().setContextClassLoader(BundleThreadContextClassLoader.getInstance());
+        try {
+            final List<Bundle> bundles = ExtensionManager.getInstance().getBundles(implementationClassName);
+            if (bundles.size() == 0) {
+                throw new IllegalStateException(String.format("The specified implementation class '%s' is not known.", implementationClassName));
+            }
+            if (bundles.size() > 1) {
+                throw new IllegalStateException(String.format("More than one bundle was found for the specified implementation class '%s', only one is allowed.", implementationClassName));
+            }
+
+            final Bundle bundle = bundles.get(0);
+            final ClassLoader detectedClassLoaderForType = bundle.getClassLoader();
+            final Class<?> rawClass = Class.forName(implementationClassName, true, detectedClassLoaderForType);
+
+            Thread.currentThread().setContextClassLoader(detectedClassLoaderForType);
+            final Class<?> desiredClass = rawClass.asSubclass(typeDefinition);
+            if(bundleProperties == null){
+                return typeDefinition.cast(desiredClass.newInstance());
+            }
+            Constructor<?> constructor = null;
+
+            try {
+                constructor = desiredClass.getConstructor(BundleProperties.class);
+            } catch (NoSuchMethodException nsme) {
+                try {
+                    constructor = desiredClass.getConstructor();
+                } catch (NoSuchMethodException nsme2) {
+                    throw new IllegalStateException("Failed to find constructor which takes BundleProperties as argument as well as the default constructor on "
+                            + desiredClass.getName(), nsme2);
+                }
+            }
+            try {
+                if (constructor.getParameterTypes().length == 0) {
+                    return typeDefinition.cast(constructor.newInstance());
+                } else {
+                    return typeDefinition.cast(constructor.newInstance(bundleProperties));
+                }
+            } catch (InvocationTargetException ite) {
+                throw new IllegalStateException("Failed to instantiate a component due to (see target exception)", ite);
+            }
+        } finally {
+            Thread.currentThread().setContextClassLoader(originalClassLoader);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/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
new file mode 100644
index 0000000..5eb82c6
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionManager.java
@@ -0,0 +1,534 @@
+/*
+ * 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.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.lang.invoke.MethodHandles;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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.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).
+ *
+ *
+ *
+ */
+@SuppressWarnings("rawtypes")
+public class ExtensionManager {
+
+  private static volatile ExtensionManager extensionManager;
+  private static volatile InitContext 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(){}
+
+  /**
+   * @return The singleton instance of the ExtensionManager
+   */
+  public static ExtensionManager getInstance() throws NotInitializedException {
+    ExtensionManager result = extensionManager;
+    if (result == null) {
+      throw new NotInitializedException("ExtensionManager not initialized");
+    }
+    return result;
+  }
+
+  /**
+   * Uninitializes the ExtensionManager.
+   * TESTING ONLY
+   */
+  @VisibleForTesting
+  public static void reset() {
+    synchronized (ExtensionManager.class) {
+      initContext = null;
+      extensionManager = null;
+    }
+  }
+
+  /**
+   * Loads all extension class types that can be found on the bootstrap classloader and by creating
+   * classloaders for all BUNDLES found within the classpath.
+   *
+   * @param bundles the bundles to scan through in search of extensions
+   */
+  public static void init(final List<Class> classes, final Bundle systemBundle, final Set<Bundle> bundles)
+      throws NotInitializedException {
+
+    if (systemBundle == null) {
+      throw new IllegalArgumentException("systemBundle is required");
+    }
+
+    synchronized (ExtensionManager.class) {
+      if (extensionManager != null) {
+        throw new IllegalStateException("ExtensionManager already exists");
+      }
+      ExtensionManager em = new ExtensionManager();
+      InitContext ic = em.discoverExtensions(classes, systemBundle, bundles);
+      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.
+   *
+   * @param bundleProperties a BundleProperties instance which will be used to obtain the default
+   * Bundle library path, which will become the working directory of the returned bundle
+   * @return a bundle for the system class loader
+   */
+  public static Bundle createSystemBundle(final FileSystemManager fileSystemManager,
+      final BundleProperties bundleProperties) throws FileSystemException, URISyntaxException {
+    final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+
+    final String bundlesLibraryDirectory = bundleProperties
+        .getProperty(BundleProperties.BUNDLE_LIBRARY_DIRECTORY);
+    if (StringUtils.isBlank(bundlesLibraryDirectory)) {
+      throw new IllegalStateException(
+          "Unable to create system bundle because " + BundleProperties.BUNDLE_LIBRARY_DIRECTORY
+              + " was null or empty");
+    }
+    final URI bundlesLibraryDirURI = bundleProperties.getBundleLibraryDirectory();
+    FileObject bundleDir = fileSystemManager.resolveFile(bundlesLibraryDirURI);
+
+    // Test if the source Bundles can be read
+    FileUtils.ensureDirectoryExistAndCanRead(bundleDir);
+
+    // the system bundle file object is never accessed, we use a dummy to stand in
+    final BundleDetails systemBundleDetails = new BundleDetails.Builder()
+        .withBundleFile(new DummyFileObject())
+        .withCoordinates(SYSTEM_BUNDLE_COORDINATE)
+        .build();
+
+    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<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
+   * @param instanceIdentifier the identifier of the specific instance of the classType to look up
+   * the ClassLoader for
+   * @param bundle the bundle where the classType exists
+   * @return the ClassLoader for the given instance of the given type, or null if the type is not a
+   * detected extension type
+   */
+  public ClassLoader createInstanceClassLoader(final String classType,
+      final String instanceIdentifier, final Bundle bundle) throws NotInitializedException{
+    if (StringUtils.isEmpty(classType)) {
+      throw new IllegalArgumentException("Class-Type is required");
+    }
+
+    if (StringUtils.isEmpty(instanceIdentifier)) {
+      throw new IllegalArgumentException("Instance Identifier is required");
+    }
+
+    if (bundle == null) {
+      throw new IllegalArgumentException("Bundle is required");
+    }
+
+    checkInitialized();
+
+    final ClassLoader bundleClassLoader = bundle.getClassLoader();
+
+    // If the class is annotated with @RequiresInstanceClassLoading and the registered ClassLoader is a URLClassLoader
+    // 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)
+        && (bundleClassLoader instanceof URLClassLoader)) {
+      final URLClassLoader registeredUrlClassLoader = (URLClassLoader) bundleClassLoader;
+      instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType,
+          registeredUrlClassLoader.getURLs(), registeredUrlClassLoader.getParent());
+    } else {
+      instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, new URL[0],
+          bundleClassLoader);
+    }
+
+    initContext.instanceClassloaderLookup.put(instanceIdentifier, instanceClassLoader);
+    return instanceClassLoader;
+  }
+
+  /**
+   * Retrieves the InstanceClassLoader for the component with the given identifier.
+   *
+   * @param instanceIdentifier the identifier of a component
+   * @return the instance class loader for the component
+   */
+  public ClassLoader getInstanceClassLoader(final String instanceIdentifier)
+      throws NotInitializedException {
+    checkInitialized();
+    return initContext.instanceClassloaderLookup.get(instanceIdentifier);
+  }
+
+  /**
+   * Retrieves the Set of Classes registered with the ExtensionManager
+   * @return Set of Class
+   * @throws NotInitializedException
+   */
+  public Set<Class> getExtensionClasses() throws NotInitializedException {
+    checkInitialized();
+    return ImmutableSet.copyOf(initContext.definitionMap.keySet());
+  }
+
+  /**
+   * Removes the ClassLoader for the given instance and closes it if necessary.
+   *
+   * @param instanceIdentifier the identifier of a component to remove the ClassLoader for
+   * @return the removed ClassLoader for the given instance, or null if not found
+   */
+  public ClassLoader removeInstanceClassLoaderIfExists(final String instanceIdentifier)
+      throws NotInitializedException {
+    if (instanceIdentifier == null) {
+      return null;
+    }
+    checkInitialized();
+    final ClassLoader classLoader = initContext.instanceClassloaderLookup.remove(instanceIdentifier);
+    if (classLoader != null && (classLoader instanceof URLClassLoader)) {
+      final URLClassLoader urlClassLoader = (URLClassLoader) classLoader;
+      try {
+        urlClassLoader.close();
+      } catch (IOException e) {
+        logger.warn("Unable to class URLClassLoader for " + instanceIdentifier);
+      }
+    }
+    return classLoader;
+  }
+
+  /**
+   * Checks if the given class type requires per-instance class loading (i.e. contains the
+   * @RequiresInstanceClassLoading annotation)
+   *
+   * @param classType the class to check
+   * @return true if the class is found in the set of classes requiring instance level class
+   * loading, false otherwise
+   */
+  public boolean requiresInstanceClassLoading(final String classType) throws NotInitializedException {
+    if (classType == null) {
+      throw new IllegalArgumentException("Class type cannot be null");
+    }
+    checkInitialized();
+    return initContext.requiresInstanceClassLoading.contains(classType);
+  }
+
+  /**
+   * Retrieves the bundles that have a class with the given name.
+   *
+   * @param classType the class name of an extension
+   * @return the list of bundles that contain an extension with the given class name
+   */
+  public List<Bundle> getBundles(final String classType) throws NotInitializedException{
+    if (classType == null) {
+      throw new IllegalArgumentException("Class type cannot be null");
+    }
+    checkInitialized();
+    final List<Bundle> bundles = initContext.classNameBundleLookup.get(classType);
+    return bundles == null ? Collections.emptyList() : new ArrayList<>(bundles);
+  }
+
+  /**
+   * Retrieves the bundle with the given withCoordinates.
+   *
+   * @param bundleCoordinates a withCoordinates to look up
+   * @return the bundle with the given withCoordinates, or null if none exists
+   */
+  public Bundle getBundle(final BundleCoordinates bundleCoordinates) throws NotInitializedException {
+    if (bundleCoordinates == null) {
+      throw new IllegalArgumentException("BundleCoordinates cannot be null");
+    }
+    checkInitialized();
+    return initContext.bundleCoordinateBundleLookup.get(bundleCoordinates);
+  }
+
+  /**
+   * Retrieves the bundle for the given class loader.
+   *
+   * @param classLoader the class loader to look up the bundle for
+   * @return the bundle for the given class loader
+   */
+  public Bundle getBundle(final ClassLoader classLoader) throws NotInitializedException {
+    if (classLoader == null) {
+      throw new IllegalArgumentException("ClassLoader cannot be null");
+    }
+    checkInitialized();
+    return initContext.classLoaderBundleLookup.get(classLoader);
+  }
+
+  public Set<Class> getExtensions(final Class<?> definition) throws NotInitializedException {
+    if (definition == null) {
+      throw new IllegalArgumentException("Class cannot be null");
+    }
+    checkInitialized();
+    final Set<Class> extensions = initContext.definitionMap.get(definition);
+    return (extensions == null) ? Collections.<Class>emptySet() : extensions;
+  }
+
+  public void logClassLoaderMapping() throws NotInitializedException {
+    checkInitialized();
+    final StringBuilder builder = new StringBuilder();
+
+    builder.append("Extension Type Mapping to Bundle:");
+    for (final Map.Entry<Class, Set<Class>> entry : initContext.definitionMap.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();
+
+        builder.append("\n\t").append(type.getName());
+
+        for (final Bundle bundle : bundles) {
+          final String coordinate = bundle.getBundleDetails().getCoordinates().getCoordinates();
+          final String workingDir = bundle.getBundleDetails().getBundleFile().getName().toString();
+          builder.append("\n\t\t").append(coordinate).append(" || ").append(workingDir);
+        }
+      }
+
+      builder.append("\n\t=== End ").append(entry.getKey().getSimpleName()).append(" types ===");
+    }
+
+    logger.info(builder.toString());
+  }
+
+  public void checkInitialized() throws NotInitializedException {
+    InitContext ic = initContext;
+    if (ic == null) {
+      throw new NotInitializedException();
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionMapping.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionMapping.java b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionMapping.java
new file mode 100644
index 0000000..5737f6c
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/ExtensionMapping.java
@@ -0,0 +1,156 @@
+/*
+ * 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.apache.metron.bundles.bundle.Bundle;
+import org.apache.metron.bundles.bundle.BundleCoordinates;
+
+import java.util.*;
+import java.util.function.BiFunction;
+
+/**
+ * The ExtensionMapping represents a mapping of the extensions available to the system.
+ * It is the product of the BundleMapper.
+ *
+ * It is NOT used at runtime for loading extensions, rather it may be used by a system to
+ * have details about the Extensions that exist in a system
+ *
+ * Runtime extension loading happens in the {@link ExtensionManager#init}
+ *
+ */
+public class ExtensionMapping {
+
+  /*
+    The extensionNameMap is a map of the following
+    Extension Type -> Map of Extension Class Types to a set of BundleCoordinates
+
+    For example :
+    Parser -> MessageParser -> [ bundles with parsers]
+
+    BundleProperties files define with Property names the type and class types, such as:
+
+    bundle.extension.type.parser=org.apache.metron.parsers.interfaces.MessageParser
+
+    This is done to give a namespace to extensions, while supporting future extension types
+    and classes.  This is different from the inspirational Nar system, which defined an explicit set
+    of supported classes, and a separate map for each.
+
+   */
+  private final Map<String, Map<String, Set<BundleCoordinates>>> extensionNameMap = new HashMap<>();
+
+  private final BiFunction<Set<BundleCoordinates>, Set<BundleCoordinates>, Set<BundleCoordinates>> merger = (oldValue, newValue) -> {
+    final Set<BundleCoordinates> merged = new HashSet<>();
+    merged.addAll(oldValue);
+    merged.addAll(newValue);
+    return merged;
+  };
+
+  void addExtension(final String extensionName, final BundleCoordinates coordinate,
+      final String type) {
+    if (!extensionNameMap.containsKey(extensionName)) {
+      Map<String, Set<BundleCoordinates>> bundles = new HashMap<>();
+      bundles.put(type, new HashSet<>());
+      extensionNameMap.put(extensionName, bundles);
+    }
+    extensionNameMap.get(extensionName).computeIfAbsent(type, name -> new HashSet<>())
+        .add(coordinate);
+  }
+
+  void addAllExtensions(final String extensionName, final BundleCoordinates coordinate,
+      final Collection<String> types) {
+    if (!extensionNameMap.containsKey(extensionName)) {
+      Map<String, Set<BundleCoordinates>> bundles = new HashMap<>();
+      extensionNameMap.put(extensionName, bundles);
+    }
+    types.forEach(name -> {
+      addExtension(extensionName, coordinate, name);
+    });
+  }
+
+  /**
+   * Returns a Map of the extension class types to a Set of BundleCoordinates for a given
+   * extension type.
+   * @param extensionTypeName the extension type name, such as parser, stellar, indexing
+   * @return Map of extension class name to a Set of BundleCoordinates
+   */
+  public Map<String, Set<BundleCoordinates>> getExtensionNames(String extensionTypeName) {
+    if (extensionNameMap.containsKey(extensionTypeName)) {
+      return Collections.unmodifiableMap(extensionNameMap.get(extensionTypeName));
+    } else {
+      return new HashMap<>();
+    }
+  }
+
+  /**
+   * Returns all the extensions in the system, mapped by extention type
+   * @return Map of extension types to a map of extension class to BundleCoordinates
+   */
+  public Map<String, Map<String, Set<BundleCoordinates>>> getAllExtensions() {
+    return Collections.unmodifiableMap(extensionNameMap);
+  }
+
+  /**
+   * Returns a Map of extension class types to a Set of BundleCoordinates for the system.
+   * This merges all the extension types into one map
+   * @return Map of extension class name to a Set of BundleCoordinates
+   */
+  public Map<String, Set<BundleCoordinates>> getAllExtensionNames() {
+    final Map<String, Set<BundleCoordinates>> extensionNames = new HashMap<>();
+    for (final Map<String, Set<BundleCoordinates>> bundleSets : extensionNameMap.values()) {
+      extensionNames.putAll(bundleSets);
+    }
+    return extensionNames;
+  }
+
+  void merge(final ExtensionMapping other) {
+    other.getAllExtensions().forEach((ex, set) -> {
+      set.forEach((name, otherCoordinates) -> {
+        if (!extensionNameMap.containsKey(ex)) {
+          extensionNameMap.put(ex, new HashMap<>());
+        }
+        extensionNameMap.get(ex).merge(name, otherCoordinates, merger);
+      });
+    });
+  }
+
+
+  /**
+   * Returns the number of all the bundles mapped to types.
+   * Bundles that map multiple types will be counted multiple times, once
+   * for each extension class exposed.
+   *
+   * @return raw count of the number of bundles
+   */
+  public int size() {
+    int size = 0;
+
+    for (final Map<String, Set<BundleCoordinates>> bundleSets : extensionNameMap.values()) {
+      for (final Set<BundleCoordinates> coordinates : bundleSets.values()) {
+        size += coordinates.size();
+      }
+    }
+    return size;
+  }
+
+
+  /**
+   * @return true if there are no extension types in the system
+   */
+  public boolean isEmpty() {
+    return extensionNameMap.isEmpty();
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/InstanceClassLoader.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/InstanceClassLoader.java b/bundles-lib/src/main/java/org/apache/metron/bundles/InstanceClassLoader.java
new file mode 100644
index 0000000..cb65605
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/InstanceClassLoader.java
@@ -0,0 +1,161 @@
+/*
+ * 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 java.lang.invoke.MethodHandles;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+/**
+ * A ClassLoader created for an instance of a component which lets a client add resources to an intermediary ClassLoader
+ * that will be checked first when loading/finding classes.
+ *
+ * Typically an instance of this ClassLoader will be created by passing in the URLs and parent from a BundleClassLoader in
+ * order to create a copy of the BundleClassLoader without modifying it.
+ */
+public class InstanceClassLoader extends URLClassLoader {
+
+    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+    private final String identifier;
+    private final String instanceType;
+    private ShimClassLoader shimClassLoader;
+
+    /**
+     * @param identifier the id of the component this ClassLoader was created for
+     * @param urls the URLs for the ClassLoader
+     * @param parent the parent ClassLoader
+     */
+    public InstanceClassLoader(final String identifier, final String type, final URL[] urls, final ClassLoader parent) {
+        super(urls, parent);
+        this.identifier = identifier;
+        this.instanceType = type;
+    }
+
+    /**
+     * Initializes a new ShimClassLoader for the provided resources, closing the previous ShimClassLoader if one existed.
+     *
+     * @param urls the URLs for the ShimClassLoader
+     * @throws IOException if the previous ShimClassLoader existed and couldn't be closed
+     */
+    public synchronized void setInstanceResources(final URL[] urls) {
+        if (shimClassLoader != null) {
+            try {
+                shimClassLoader.close();
+            } catch (IOException e) {
+                logger.warn("Unable to close inner URLClassLoader for " + identifier);
+            }
+        }
+
+        shimClassLoader = new ShimClassLoader(urls, getParent());
+    }
+
+    /**
+     * @return the URLs for the instance resources that have been set
+     */
+    public synchronized URL[] getInstanceResources() {
+        if (shimClassLoader != null) {
+            return shimClassLoader.getURLs();
+        }
+        return new URL[0];
+    }
+
+    @Override
+    public Class<?> loadClass(String name) throws ClassNotFoundException {
+        return this.loadClass(name, false);
+    }
+
+    @Override
+    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        Class<?> c = null;
+        // first try the shim
+        if (shimClassLoader != null) {
+            try {
+                c = shimClassLoader.loadClass(name, resolve);
+            } catch (ClassNotFoundException e) {
+                c = null;
+            }
+        }
+        // if it wasn't in the shim try our self
+        if (c == null) {
+            return super.loadClass(name, resolve);
+        } else {
+            return c;
+        }
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        Class<?> c = null;
+        // first try the shim
+        if (shimClassLoader != null) {
+            try {
+                c = shimClassLoader.findClass(name);
+            } catch (ClassNotFoundException cnf) {
+                c = null;
+            }
+        }
+        // if it wasn't in the shim try our self
+        if (c == null) {
+            return super.findClass(name);
+        } else {
+            return c;
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (shimClassLoader != null) {
+            try {
+                shimClassLoader.close();
+            } catch (IOException e) {
+                logger.warn("Unable to close inner URLClassLoader for " + identifier);
+            }
+        }
+        super.close();
+    }
+
+    /**
+     * Extend URLClassLoader to increase visibility of protected methods so that InstanceClassLoader can delegate.
+     */
+    private static class ShimClassLoader extends URLClassLoader {
+
+        public ShimClassLoader(URL[] urls, ClassLoader parent) {
+            super(urls, parent);
+        }
+
+        public ShimClassLoader(URL[] urls) {
+            super(urls);
+        }
+
+        @Override
+        public Class<?> findClass(String name) throws ClassNotFoundException {
+            return super.findClass(name);
+        }
+
+        @Override
+        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+            return super.loadClass(name, resolve);
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/NotInitializedException.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/NotInitializedException.java b/bundles-lib/src/main/java/org/apache/metron/bundles/NotInitializedException.java
new file mode 100644
index 0000000..9857a0e
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/NotInitializedException.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+public class NotInitializedException extends Exception {
+  public NotInitializedException() {
+  }
+
+  public NotInitializedException(String message) {
+    super(message);
+  }
+
+  public NotInitializedException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public NotInitializedException(Throwable cause) {
+    super(cause);
+  }
+
+  public NotInitializedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+    super(message, cause, enableSuppression, writableStackTrace);
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoader.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoader.java b/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoader.java
new file mode 100644
index 0000000..888e7b3
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoader.java
@@ -0,0 +1,520 @@
+/*
+ * 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 java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.net.URL;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.SecureClassLoader;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.Attributes.Name;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemManager;
+import org.apache.commons.vfs2.NameScope;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * <p>
+ * A <tt>ClassLoader</tt> for loading BUNDLES (plugin archives). BUNDLEs are designed to
+ * allow isolating bundles of code (comprising one-or-more
+ * plugin classes and their
+ * dependencies) from other such bundles; this allows for dependencies and
+ * processors that require conflicting, incompatible versions of the same
+ * dependency to run in a single instance of a given process.</p>
+ *
+ * <p>
+ * <tt>BundleClassLoader</tt> follows the delegation model described in
+ * {@link ClassLoader#findClass(java.lang.String) ClassLoader.findClass(...)};
+ * classes are first loaded from the parent <tt>ClassLoader</tt>, and only if
+ * they cannot be found there does the <tt>BundleClassLoader</tt> provide a
+ * definition. Specifically, this means that resources are loaded from the application's
+ * <tt>conf</tt>
+ * and <tt>lib</tt> directories first, and if they cannot be found there, are
+ * loaded from the BUNDLE.</p>
+ *
+ * <p>
+ * The packaging of a BUNDLE is such that it is a ZIP file with the following
+ * directory structure:
+ *
+ * <pre>
+ *   +META-INF/
+ *   +-- bundled-dependencies/
+ *   +-- &lt;JAR files&gt;
+ *   +-- MANIFEST.MF
+ * </pre>
+ * </p>
+ *
+ * <p>
+ * The MANIFEST.MF file contains the same information as a typical JAR file but
+ * also includes two additional bundle properties: {@code Bundle-Id} and
+ * {@code Bundle-Dependency-Id}.
+ * </p>
+ *
+ * <p>
+ * The {@code Bundle-Id} provides a unique identifier for this BUNDLE.
+ * </p>
+ *
+ * <p>
+ * The {@code Bundle-Dependency-Id} is optional. If provided, it indicates that
+ * this BUNDLE should inherit all of the dependencies of the BUNDLE with the provided
+ * ID. Often times, the BUNDLE that is depended upon is referred to as the Parent.
+ * This is because its ClassLoader will be the parent ClassLoader of the
+ * dependent BUNDLE.
+ * </p>
+ *
+ * <p>
+ * If a BUNDLE is built using the Bundles Maven Plugin, the {@code Bundle-Id} property
+ * will be set to the artifactId of the BUNDLE. The {@code Bundle-Dependency-Id} will
+ * be set to the artifactId of the BUNDLE that is depended upon. For example, if
+ * BUNDLE A is defined as such:
+ *
+ * <pre>
+ * ...
+ * &lt;artifactId&gt;bundle-a&lt;/artifactId&gt;
+ * &lt;packaging&gt;bundle&lt;/packaging&gt;
+ * ...
+ * &lt;dependencies&gt;
+ *   &lt;dependency&gt;
+ *     &lt;groupId&gt;group&lt;/groupId&gt;
+ *     &lt;artifactId&gt;bundle-z&lt;/artifactId&gt;
+ *     <b>&lt;type&gt;bundle&lt;/type&gt;</b>
+ *   &lt;/dependency&gt;
+ * &lt;/dependencies&gt;
+ * </pre>
+ * </p>
+ *
+ *
+ * <p>
+ * Then the MANIFEST.MF file that is created for Bundle A will have the following
+ * properties set:
+ * <ul>
+ * <li>{@code {Foo}-Id: bundle-a}</li>
+ * <li>{@code {Foo}-Dependency-Id: bundle-z}</li>
+ * </ul>
+ * Where is configurable by BundleProperty META_ID_PREFIX [ default Bundle ]
+ * </p>
+ *
+ * <p>
+ * Note, above, that the {@code type} of the dependency is set to {@code foo}.
+ * </p>
+ *
+ * <p>
+ * If the Bundle has more than one dependency of {@code type} {@code Foo}, then the
+ * Maven Bundle plugin will fail to build the Bundle.
+ * </p>
+ *
+ * This classloader is Metron Bundle aware, in that it understands that the passed in root
+ * FileObjects may be Bundles,
+ * This class is adapted from Apache Commons VFS
+ * VFSClassLoader class v.2.1
+ * And the Apache Nifi NarClassLoader v. 1.2
+ *
+ * @see FileSystemManager#createFileSystem
+ */
+public class VFSBundleClassLoader extends SecureClassLoader {
+  private static final Logger LOGGER = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static class Builder {
+
+    private FileSystemManager fileSystemManager;
+    private FileObject bundleFile;
+    private ClassLoader parentClassLoader;
+
+    public VFSBundleClassLoader.Builder withFileSystemManager(FileSystemManager fileSystemManager) {
+      this.fileSystemManager = fileSystemManager;
+      return this;
+    }
+
+    public VFSBundleClassLoader.Builder withBundleFile(FileObject bundleFile) {
+      this.bundleFile = bundleFile;
+      return this;
+    }
+
+    public VFSBundleClassLoader.Builder withParentClassloader(ClassLoader parentClassloader) {
+      this.parentClassLoader = parentClassloader;
+      return this;
+    }
+
+    public VFSBundleClassLoader build() throws FileSystemException {
+      return new VFSBundleClassLoader(bundleFile, fileSystemManager, parentClassLoader);
+    }
+  }
+
+  private final ArrayList<FileObject> resources = new ArrayList<FileObject>();
+  private FileObject nativeDir;
+  public static final String DEPENDENCY_PATH = "META-INF/bundled-dependencies";
+  /**
+   * Constructs a new VFSClassLoader for the given Bundle file.
+   *
+   * @param file the file to load the classes and resources from.
+   * @param manager the FileManager to use when trying create a layered Jar file system.
+   * @throws FileSystemException if an error occurs.
+   */
+  public VFSBundleClassLoader(final FileObject file,
+      final FileSystemManager manager)
+      throws FileSystemException {
+    this(new FileObject[]{file}, manager, null);
+  }
+
+  /**
+   * Constructs a new VFSClassLoader for the given file.
+   *
+   * @param file the Bundle FileObject to load the classes and resources from.
+   * @param manager the FileManager to use when trying create a layered Jar file system.
+   * @param parent the parent class loader for delegation.
+   * @throws FileSystemException if an error occurs.
+   */
+  public VFSBundleClassLoader(final FileObject file,
+      final FileSystemManager manager,
+      final ClassLoader parent)
+      throws FileSystemException {
+    this(new FileObject[]{file}, manager, parent);
+  }
+
+  /**
+   * Constructs a new VFSClassLoader for the given files.  The files will be searched in the order
+   * specified.
+   *
+   * @param files the Bundle FileObjects to load the classes and resources from.
+   * @param manager the FileManager to use when trying create a layered Jar file system.
+   * @throws FileSystemException if an error occurs.
+   */
+  public VFSBundleClassLoader(final FileObject[] files,
+      final FileSystemManager manager)
+      throws FileSystemException {
+    this(files, manager, null);
+  }
+
+  /**
+   * Constructs a new VFSClassLoader for the given FileObjects. The FileObjects will be searched
+   * in the order specified.
+   *
+   * @param files the Bundle FileObjects to load the classes and resources from.
+   * @param manager the FileManager to use when trying create a layered Jar file system.
+   * @param parent the parent class loader for delegation.
+   * @throws FileSystemException if an error occurs.
+   */
+  public VFSBundleClassLoader(final FileObject[] files,
+      final FileSystemManager manager,
+      final ClassLoader parent) throws FileSystemException {
+    super(parent);
+    addFileObjects(manager, files);
+  }
+
+  /**
+   * Provide access to the file objects this class loader represents.
+   *
+   * @return An array of FileObjects.
+   * @since 2.0
+   */
+  public FileObject[] getFileObjects() {
+    return resources.toArray(new FileObject[resources.size()]);
+  }
+
+  /**
+   * Appends the specified FileObjects to the list of FileObjects to search for classes and
+   * resources.  If the FileObjects represent Bundles, then the Bundle dependencies will also
+   * be added as resources to the classloader.
+   *
+   * This is the equivelent of unzipping the Bundle and adding each jar in the dependency directory
+   * by uri.  The ability of VFS to create filesystems from jar files, allows the VFSBundleClassLoader
+   * to create a composite filesystem out of the Bundle zip.
+   *
+   * @param manager The FileSystemManager.
+   * @param files the FileObjects to append to the search path.
+   * @throws FileSystemException if an error occurs.
+   */
+  private void addFileObjects(final FileSystemManager manager,
+      final FileObject[] files) throws FileSystemException {
+    for (FileObject file : files) {
+      if (!file.exists()) {
+        // Does not exist - skip
+        continue;
+      }
+
+      if (manager.canCreateFileSystem(file)) {
+        // create a Jar filesystem from the bundle
+        FileObject bundleFile = manager.createFileSystem(file);
+
+        // resolve the dependency directory within the bundle
+        FileObject deps = bundleFile.resolveFile(DEPENDENCY_PATH);
+        if(deps.exists() && deps.isFolder()) {
+          nativeDir = deps.resolveFile("native");
+          FileObject[] depJars = deps.getChildren();
+          for (FileObject jarFileObject : depJars) {
+            // create a filesystem from each jar and add it as
+            // a resource
+            jarFileObject = manager.createFileSystem(jarFileObject);
+            resources.add(jarFileObject);
+          }
+        }
+      } else {
+        continue;
+      }
+      resources.add(file);
+    }
+  }
+
+  @Override
+  protected String findLibrary(final String libname) {
+    try {
+      final FileObject libsoFile = nativeDir.resolveFile( "lib" + libname + ".so");
+      final FileObject dllFile = nativeDir.resolveFile(libname + ".dll");
+      final FileObject soFile = nativeDir.resolveFile(libname + ".so");
+      if (libsoFile.exists()) {
+        return libsoFile.getURL().toString();
+      } else if (dllFile.exists()) {
+        return dllFile.getURL().toString();
+      } else if (soFile.exists()) {
+        return soFile.getURL().toString();
+      }
+    }catch(FileSystemException fse){
+      LOGGER.error("Failed to get dependencies",fse);
+    }
+    // not found in the bundle. try system native dir
+    return null;
+  }
+
+
+  /**
+   * Finds and loads the class with the specified name from the search path.
+   *
+   * @throws ClassNotFoundException if the class is not found.
+   */
+  @Override
+  protected Class<?> findClass(final String name) throws ClassNotFoundException {
+    try {
+      final String path = name.replace('.', '/').concat(".class");
+      final VFSBundleClassLoaderResource res = loadResource(path);
+      if (res == null) {
+        throw new ClassNotFoundException(name);
+      }
+      return defineClass(name, res);
+    } catch (final IOException ioe) {
+      throw new ClassNotFoundException(name, ioe);
+    }
+  }
+
+  /**
+   * Loads and verifies the class with name and located with res.
+   */
+  private Class<?> defineClass(final String name, final VFSBundleClassLoaderResource res)
+      throws IOException {
+    final URL url = res.getCodeSourceURL();
+    final String pkgName = res.getPackageName();
+    if (pkgName != null) {
+      final Package pkg = getPackage(pkgName);
+      if (pkg != null) {
+        if (pkg.isSealed()) {
+          if (!pkg.isSealed(url)) {
+            throw new FileSystemException("vfs.impl/pkg-sealed-other-url", pkgName);
+          }
+        } else {
+          if (isSealed(res)) {
+            throw new FileSystemException("vfs.impl/pkg-sealing-unsealed", pkgName);
+          }
+        }
+      } else {
+        definePackage(pkgName, res);
+      }
+    }
+
+    final byte[] bytes = res.getBytes();
+    final Certificate[] certs =
+        res.getFileObject().getContent().getCertificates();
+    final CodeSource cs = new CodeSource(url, certs);
+    return defineClass(name, bytes, 0, bytes.length, cs);
+  }
+
+  /**
+   * Returns true if the we should seal the package where res resides.
+   */
+  private boolean isSealed(final VFSBundleClassLoaderResource res)
+      throws FileSystemException {
+    final String sealed = res.getPackageAttribute(Attributes.Name.SEALED);
+    return "true".equalsIgnoreCase(sealed);
+  }
+
+  /**
+   * Reads attributes for the package and defines it.
+   */
+  private Package definePackage(final String name,
+      final VFSBundleClassLoaderResource res)
+      throws FileSystemException {
+    // TODO - check for MANIFEST_ATTRIBUTES capability first
+    final String specTitle = res.getPackageAttribute(Name.SPECIFICATION_TITLE);
+    final String specVendor = res.getPackageAttribute(Attributes.Name.SPECIFICATION_VENDOR);
+    final String specVersion = res.getPackageAttribute(Name.SPECIFICATION_VERSION);
+    final String implTitle = res.getPackageAttribute(Name.IMPLEMENTATION_TITLE);
+    final String implVendor = res.getPackageAttribute(Name.IMPLEMENTATION_VENDOR);
+    final String implVersion = res.getPackageAttribute(Name.IMPLEMENTATION_VERSION);
+
+    final URL sealBase;
+    if (isSealed(res)) {
+      sealBase = res.getCodeSourceURL();
+    } else {
+      sealBase = null;
+    }
+
+    return definePackage(name, specTitle, specVersion, specVendor,
+        implTitle, implVersion, implVendor, sealBase);
+  }
+
+  /**
+   * Calls super.getPermissions both for the code source and also adds the permissions granted to
+   * the parent layers.
+   *
+   * @param cs the CodeSource.
+   * @return The PermissionCollections.
+   */
+  @Override
+  protected PermissionCollection getPermissions(final CodeSource cs) {
+    try {
+      final String url = cs.getLocation().toString();
+      final FileObject file = lookupFileObject(url);
+      if (file == null) {
+        return super.getPermissions(cs);
+      }
+
+      final FileObject parentLayer = file.getFileSystem().getParentLayer();
+      if (parentLayer == null) {
+        return super.getPermissions(cs);
+      }
+
+      final Permissions combi = new Permissions();
+      PermissionCollection permCollect = super.getPermissions(cs);
+      copyPermissions(permCollect, combi);
+
+      for (FileObject parent = parentLayer;
+          parent != null;
+          parent = parent.getFileSystem().getParentLayer()) {
+        final CodeSource parentcs =
+            new CodeSource(parent.getURL(),
+                parent.getContent().getCertificates());
+        permCollect = super.getPermissions(parentcs);
+        copyPermissions(permCollect, combi);
+      }
+
+      return combi;
+    } catch (final FileSystemException fse) {
+      throw new SecurityException(fse.getMessage());
+    }
+  }
+
+  /**
+   * Copies the permissions from src to dest.
+   *
+   * @param src The source PermissionCollection.
+   * @param dest The destination PermissionCollection.
+   */
+  protected void copyPermissions(final PermissionCollection src,
+      final PermissionCollection dest) {
+    for (final Enumeration<Permission> elem = src.elements(); elem.hasMoreElements(); ) {
+      final Permission permission = elem.nextElement();
+      dest.add(permission);
+    }
+  }
+
+  /**
+   * Does a reverse lookup to find the FileObject when we only have the URL.
+   */
+  private FileObject lookupFileObject(final String name) {
+    final Iterator<FileObject> it = resources.iterator();
+    while (it.hasNext()) {
+      final FileObject object = it.next();
+      if (name.equals(object.getName().getURI())) {
+        return object;
+      }
+    }
+    return null;
+  }
+
+  /**
+   * Finds the resource with the specified name from the search path. This returns null if the
+   * resource is not found.
+   *
+   * @param name The resource name.
+   * @return The URL that matches the resource.
+   */
+  @Override
+  protected URL findResource(final String name) {
+    try {
+      final VFSBundleClassLoaderResource res = loadResource(name);
+      if (res != null) {
+        return res.getURL();
+      }
+      return null;
+    } catch (final Exception ignored) {
+      return null; // TODO: report?
+    }
+  }
+
+  /**
+   * Returns an Enumeration of all the resources in the search path with the specified name. <p>
+   * Gets called from {@link ClassLoader#getResources(String)} after parent class loader was
+   * questioned.
+   *
+   * @param name The resources to find.
+   * @return An Enumeration of the resources associated with the name.
+   * @throws FileSystemException if an error occurs.
+   */
+  @Override
+  protected Enumeration<URL> findResources(final String name) throws IOException {
+    final List<URL> result = new ArrayList<URL>(2);
+
+    for (FileObject baseFile : resources) {
+      final FileObject file = baseFile.resolveFile(name, NameScope.DESCENDENT_OR_SELF);
+      if (file.exists()) {
+        result.add(new VFSBundleClassLoaderResource(name, baseFile, file).getURL());
+      }
+    }
+
+    return Collections.enumeration(result);
+  }
+
+  /**
+   * Searches through the search path of for the first class or resource with specified name.
+   *
+   * @param name The resource to load.
+   * @return The Resource.
+   * @throws FileSystemException if an error occurs.
+   */
+  private VFSBundleClassLoaderResource loadResource(final String name) throws FileSystemException {
+    for (final FileObject baseFile : resources) {
+      final FileObject file = baseFile.resolveFile(name, NameScope.DESCENDENT_OR_SELF);
+      if (file.exists()) {
+        return new VFSBundleClassLoaderResource(name, baseFile, file);
+      }
+    }
+    return null;
+  }
+}
+

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoaderResource.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoaderResource.java b/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoaderResource.java
new file mode 100644
index 0000000..934d1fc
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/VFSBundleClassLoaderResource.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 java.io.IOException;
+import java.net.URL;
+import java.util.jar.Attributes;
+
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileUtil;
+
+/**
+ * Helper class for VFSBundleClassLoader. This represents a resource loaded with the classloader.
+ * This class is adapted from Apache Commons VFS Resource class v.2.1
+ *
+ * @see VFSBundleClassLoader
+ */
+class VFSBundleClassLoaderResource {
+
+  private final FileObject root;
+  private final FileObject resource;
+  private final FileObject packageFolder;
+  private final String packageName;
+
+  /**
+   * Creates a new instance.
+   *
+   * @param root The code source FileObject.
+   * @param resource The resource of the FileObject.
+   */
+  public VFSBundleClassLoaderResource(final String name,
+      final FileObject root,
+      final FileObject resource)
+      throws FileSystemException {
+    this.root = root;
+    this.resource = resource;
+    packageFolder = resource.getParent();
+    final int pos = name.lastIndexOf('/');
+    if (pos == -1) {
+      packageName = null;
+    } else {
+      packageName = name.substring(0, pos).replace('/', '.');
+    }
+  }
+
+  /**
+   * Returns the URL of the resource.
+   */
+  public URL getURL() throws FileSystemException {
+    return resource.getURL();
+  }
+
+  /**
+   * Returns the name of the package containing the resource.
+   */
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /**
+   * Returns an attribute of the package containing the resource.
+   */
+  public String getPackageAttribute(final Attributes.Name attrName) throws FileSystemException {
+    return (String) packageFolder.getContent().getAttribute(attrName.toString());
+  }
+
+  /**
+   * Returns the folder for the package containing the resource.
+   */
+  public FileObject getPackageFolder() {
+    return packageFolder;
+  }
+
+  /**
+   * Returns the FileObject of the resource.
+   */
+  public FileObject getFileObject() {
+    return resource;
+  }
+
+  /**
+   * Returns the code source as an URL.
+   */
+  public URL getCodeSourceURL() throws FileSystemException {
+    return root.getURL();
+  }
+
+  /**
+   * Returns the data for this resource as a byte array.
+   */
+  public byte[] getBytes() throws IOException {
+    return FileUtil.getContent(resource);
+  }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/annotation/behavior/RequiresInstanceClassLoading.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/annotation/behavior/RequiresInstanceClassLoading.java b/bundles-lib/src/main/java/org/apache/metron/bundles/annotation/behavior/RequiresInstanceClassLoading.java
new file mode 100644
index 0000000..a6f4b15
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/annotation/behavior/RequiresInstanceClassLoading.java
@@ -0,0 +1,37 @@
+/*
+ * 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.annotation.behavior;
+
+import java.lang.annotation.*;
+
+/**
+ *  Marker annotation a component can use to indicate that the framework should create a new ClassLoader
+ *  for each instance of the component, copying all resources from the component's BundleClassLoader to a
+ *  new ClassLoader which will only be used by a given instance of the component.
+ *
+ *  This annotation is typically used when a component has one or more PropertyDescriptors which set
+ *  dynamicallyModifiesClasspath(boolean) to true.
+ *
+ *  When this annotation is used it is important to note that each added instance of the component will increase
+ *  the overall memory footprint more than that of a component without this annotation.
+ */
+@Documented
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Inherited
+public @interface RequiresInstanceClassLoading {
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/Bundle.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/Bundle.java b/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/Bundle.java
new file mode 100644
index 0000000..e06fb0b
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/Bundle.java
@@ -0,0 +1,48 @@
+/*
+ * 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.bundle;
+
+/**
+ * Represents a bundle that contains one or more extensions.
+ */
+public class Bundle {
+
+    private final BundleDetails bundleDetails;
+
+    private final ClassLoader classLoader;
+
+    public Bundle(final BundleDetails bundleDetails, final ClassLoader classLoader) {
+        this.bundleDetails = bundleDetails;
+        this.classLoader = classLoader;
+
+        if (this.bundleDetails == null) {
+            throw new IllegalStateException("BundleDetails cannot be null");
+        }
+
+        if (this.classLoader == null) {
+            throw new IllegalStateException("ClassLoader cannot be null");
+        }
+    }
+
+    public BundleDetails getBundleDetails() {
+        return bundleDetails;
+    }
+
+    public ClassLoader getClassLoader() {
+        return classLoader;
+    }
+}

http://git-wip-us.apache.org/repos/asf/metron/blob/5f7454e4/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/BundleCoordinates.java
----------------------------------------------------------------------
diff --git a/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/BundleCoordinates.java b/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/BundleCoordinates.java
new file mode 100644
index 0000000..c031ea9
--- /dev/null
+++ b/bundles-lib/src/main/java/org/apache/metron/bundles/bundle/BundleCoordinates.java
@@ -0,0 +1,96 @@
+/*
+ * 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.bundle;
+
+/**
+ * The coordinates of a bundle (group, artifact, version).
+ */
+public class BundleCoordinates {
+
+  public static final String DEFAULT_GROUP = "default";
+  public static final String DEFAULT_VERSION = "unversioned";
+
+  public static final BundleCoordinates UNKNOWN_COORDINATE = new BundleCoordinates(DEFAULT_GROUP,
+      "unknown", DEFAULT_VERSION);
+
+  private final String group;
+  private final String id;
+  private final String version;
+  private final String coordinates;
+
+  public BundleCoordinates(final String group, final String id, final String version) {
+    this.group = isBlank(group) ? DEFAULT_GROUP : group;
+    this.id = id;
+    this.version = isBlank(version) ? DEFAULT_VERSION : version;
+
+    if (isBlank(id)) {
+      throw new IllegalStateException("Id is required for BundleCoordinates");
+    }
+
+    if (this.group.contains(":") || this.id.contains(":") || this.version.contains(":")) {
+      throw new IllegalStateException(String
+          .format("Invalid coordinates: cannot contain : character group[%s] id[%s] version[%s]",
+              this.group, this.id, this.version));
+    }
+    this.coordinates = this.group + ":" + this.id + ":" + this.version;
+  }
+
+  private boolean isBlank(String str) {
+    return str == null || str.trim().length() == 0;
+  }
+
+  public String getGroup() {
+    return group;
+  }
+
+  public String getId() {
+    return id;
+  }
+
+  public String getVersion() {
+    return version;
+  }
+
+  public final String getCoordinates() {
+    return coordinates;
+  }
+
+  @Override
+  public String toString() {
+    return coordinates;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null) {
+      return false;
+    }
+
+    if (!(obj instanceof BundleCoordinates)) {
+      return false;
+    }
+
+    final BundleCoordinates other = (BundleCoordinates) obj;
+    return getCoordinates().equals(other.getCoordinates());
+  }
+
+  @Override
+  public int hashCode() {
+    return 37 * this.coordinates.hashCode();
+  }
+
+}


Mime
View raw message