You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2017/03/24 15:10:56 UTC

[09/17] nifi git commit: NIFI-3380 Bumping NAR plugin to 1.2.0-SNAPSHOT development to leverage changes from master, adding buildnumber-maven-plugin to nifi-nar-bundles to properly set build info in MANIFEST of NARs - Refactoring NarDetails to include al

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskingInitializer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskingInitializer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskingInitializer.java
new file mode 100644
index 0000000..546e67c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/init/ReportingTaskingInitializer.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.init;
+
+import org.apache.nifi.annotation.lifecycle.OnShutdown;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.mock.MockComponentLogger;
+import org.apache.nifi.mock.MockConfigurationContext;
+import org.apache.nifi.mock.MockReportingInitializationContext;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarCloseable;
+import org.apache.nifi.reporting.InitializationException;
+import org.apache.nifi.reporting.ReportingInitializationContext;
+import org.apache.nifi.reporting.ReportingTask;
+
+/**
+ * Initializes a ReportingTask using a MockReportingInitializationContext;
+ *
+ *
+ */
+public class ReportingTaskingInitializer implements ConfigurableComponentInitializer {
+
+    @Override
+    public void initialize(ConfigurableComponent component) throws InitializationException {
+        ReportingTask reportingTask = (ReportingTask) component;
+        ReportingInitializationContext context = new MockReportingInitializationContext();
+        try (NarCloseable narCloseable = NarCloseable.withComponentNarLoader(component.getClass(), context.getIdentifier())) {
+            reportingTask.initialize(context);
+        }
+    }
+
+    @Override
+    public void teardown(ConfigurableComponent component) {
+        ReportingTask reportingTask = (ReportingTask) component;
+        try (NarCloseable narCloseable = NarCloseable.withComponentNarLoader(component.getClass(), component.getIdentifier())) {
+
+            final MockConfigurationContext context = new MockConfigurationContext();
+            ReflectionUtils.quietlyInvokeMethodsWithAnnotation(OnShutdown.class, reportingTask, new MockComponentLogger(), context);
+        } finally {
+            ExtensionManager.removeInstanceClassLoaderIfExists(component.getIdentifier());
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockComponentLogger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockComponentLogger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockComponentLogger.java
new file mode 100644
index 0000000..920d7eb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockComponentLogger.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.logging.LogLevel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Stubs out the functionality of a ComponentLog so that it can
+ * be used during initialization of a component.
+ *
+ */
+public class MockComponentLogger implements ComponentLog {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(MockComponentLogger.class);
+
+    @Override
+    public void warn(String msg, Throwable t) {
+        logger.warn(msg, t);
+    }
+
+    @Override
+    public void warn(String msg, Object[] os) {
+        logger.warn(msg, os);
+    }
+
+    @Override
+    public void warn(String msg, Object[] os, Throwable t) {
+        logger.warn(msg, os);
+        logger.warn("", t);
+    }
+
+    @Override
+    public void warn(String msg) {
+        logger.warn(msg);
+    }
+
+    @Override
+    public void trace(String msg, Throwable t) {
+        logger.trace(msg, t);
+    }
+
+    @Override
+    public void trace(String msg, Object[] os) {
+        logger.trace(msg, os);
+    }
+
+    @Override
+    public void trace(String msg) {
+        logger.trace(msg);
+    }
+
+    @Override
+    public void trace(String msg, Object[] os, Throwable t) {
+        logger.trace(msg, os);
+        logger.trace("", t);
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return logger.isWarnEnabled();
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return logger.isTraceEnabled();
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return logger.isInfoEnabled();
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return logger.isErrorEnabled();
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        return logger.isDebugEnabled();
+    }
+
+    @Override
+    public void info(String msg, Throwable t) {
+        logger.info(msg, t);
+    }
+
+    @Override
+    public void info(String msg, Object[] os) {
+        logger.info(msg, os);
+    }
+
+    @Override
+    public void info(String msg) {
+        logger.info(msg);
+
+    }
+
+    @Override
+    public void info(String msg, Object[] os, Throwable t) {
+        logger.trace(msg, os);
+        logger.trace("", t);
+
+    }
+
+    @Override
+    public String getName() {
+        return logger.getName();
+    }
+
+    @Override
+    public void error(String msg, Throwable t) {
+        logger.error(msg, t);
+    }
+
+    @Override
+    public void error(String msg, Object[] os) {
+        logger.error(msg, os);
+    }
+
+    @Override
+    public void error(String msg) {
+        logger.error(msg);
+    }
+
+    @Override
+    public void error(String msg, Object[] os, Throwable t) {
+        logger.error(msg, os);
+        logger.error("", t);
+    }
+
+    @Override
+    public void debug(String msg, Throwable t) {
+        logger.debug(msg, t);
+    }
+
+    @Override
+    public void debug(String msg, Object[] os) {
+        logger.debug(msg, os);
+    }
+
+    @Override
+    public void debug(String msg, Object[] os, Throwable t) {
+        logger.debug(msg, os);
+        logger.debug("", t);
+    }
+
+    @Override
+    public void debug(String msg) {
+        logger.debug(msg);
+    }
+
+    @Override
+    public void log(LogLevel level, String msg, Throwable t) {
+        switch (level) {
+            case DEBUG:
+                debug(msg, t);
+                break;
+            case ERROR:
+            case FATAL:
+                error(msg, t);
+                break;
+            case INFO:
+                info(msg, t);
+                break;
+            case TRACE:
+                trace(msg, t);
+                break;
+            case WARN:
+                warn(msg, t);
+                break;
+        }
+    }
+
+    @Override
+    public void log(LogLevel level, String msg, Object[] os) {
+        switch (level) {
+            case DEBUG:
+                debug(msg, os);
+                break;
+            case ERROR:
+            case FATAL:
+                error(msg, os);
+                break;
+            case INFO:
+                info(msg, os);
+                break;
+            case TRACE:
+                trace(msg, os);
+                break;
+            case WARN:
+                warn(msg, os);
+                break;
+        }
+    }
+
+    @Override
+    public void log(LogLevel level, String msg) {
+        switch (level) {
+            case DEBUG:
+                debug(msg);
+                break;
+            case ERROR:
+            case FATAL:
+                error(msg);
+                break;
+            case INFO:
+                info(msg);
+                break;
+            case TRACE:
+                trace(msg);
+                break;
+            case WARN:
+                warn(msg);
+                break;
+        }
+    }
+
+    @Override
+    public void log(LogLevel level, String msg, Object[] os, Throwable t) {
+        switch (level) {
+            case DEBUG:
+                debug(msg, os, t);
+                break;
+            case ERROR:
+            case FATAL:
+                error(msg, os, t);
+                break;
+            case INFO:
+                info(msg, os, t);
+                break;
+            case TRACE:
+                trace(msg, os, t);
+                break;
+            case WARN:
+                warn(msg, os, t);
+                break;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java
new file mode 100644
index 0000000..d1e73fb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.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.nifi.mock;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.controller.ConfigurationContext;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class MockConfigurationContext implements ConfigurationContext {
+
+    @Override
+    public PropertyValue getProperty(PropertyDescriptor property) {
+        return null;
+    }
+
+    @Override
+    public Map<PropertyDescriptor, String> getProperties() {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public String getSchedulingPeriod() {
+        return "0 secs";
+    }
+
+    @Override
+    public Long getSchedulingPeriod(final TimeUnit timeUnit) {
+        return 0L;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceInitializationContext.java
new file mode 100644
index 0000000..b111ad2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceInitializationContext.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.controller.ControllerServiceInitializationContext;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.logging.ComponentLog;
+
+import java.io.File;
+
+/**
+ * A Mock ControllerServiceInitializationContext so that ControllerServices can
+ * be initialized for the purpose of generating documentation.
+ *
+ *
+ */
+public class MockControllerServiceInitializationContext implements ControllerServiceInitializationContext {
+
+    @Override
+    public String getIdentifier() {
+        return "mock-controller-service";
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return new MockControllerServiceLookup();
+    }
+
+    @Override
+    public ComponentLog getLogger() {
+        return new MockComponentLogger();
+    }
+
+    @Override
+    public StateManager getStateManager() {
+        return null;
+    }
+
+    @Override
+    public String getKerberosServicePrincipal() {
+        return null;
+    }
+
+    @Override
+    public File getKerberosServiceKeytab() {
+        return null;
+    }
+
+    @Override
+    public File getKerberosConfigurationFile() {
+        return null;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceLookup.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceLookup.java
new file mode 100644
index 0000000..5307ac4
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockControllerServiceLookup.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.controller.ControllerService;
+import org.apache.nifi.controller.ControllerServiceLookup;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * A Mock ControllerServiceLookup that can be used so that
+ * ConfigurableComponents can be initialized for the purpose of generating
+ * documentation
+ *
+ *
+ */
+public class MockControllerServiceLookup implements ControllerServiceLookup {
+
+    @Override
+    public ControllerService getControllerService(final String serviceIdentifier) {
+        return null;
+    }
+
+    @Override
+    public boolean isControllerServiceEnabled(final String serviceIdentifier) {
+        return false;
+    }
+
+    @Override
+    public boolean isControllerServiceEnabled(final ControllerService service) {
+        return false;
+    }
+
+    @Override
+    public Set<String> getControllerServiceIdentifiers(final Class<? extends ControllerService> serviceType) throws IllegalArgumentException {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public boolean isControllerServiceEnabling(final String serviceIdentifier) {
+        return false;
+    }
+
+    @Override
+    public String getControllerServiceName(final String serviceIdentifier) {
+        return serviceIdentifier;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockNodeTypeProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockNodeTypeProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockNodeTypeProvider.java
new file mode 100644
index 0000000..61390e1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockNodeTypeProvider.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.controller.NodeTypeProvider;
+
+/**
+ * A Mock NodeTypeProvider that can be used so that
+ * ConfigurableComponents can be initialized for the purpose of generating
+ * documentation
+ *
+ *
+ */
+public class MockNodeTypeProvider implements NodeTypeProvider {
+
+    @Override
+    public boolean isClustered() {
+        return false;
+    }
+
+    @Override
+    public boolean isPrimary() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessContext.java
new file mode 100644
index 0000000..cf2e2cf
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessContext.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.components.state.StateManager;
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.Relationship;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class MockProcessContext implements ProcessContext {
+
+    @Override
+    public PropertyValue getProperty(PropertyDescriptor descriptor) {
+        return null;
+    }
+
+    @Override
+    public PropertyValue getProperty(String propertyName) {
+        return null;
+    }
+
+    @Override
+    public PropertyValue newPropertyValue(String rawValue) {
+        return null;
+    }
+
+    @Override
+    public void yield() {
+
+    }
+
+    @Override
+    public int getMaxConcurrentTasks() {
+        return 0;
+    }
+
+    @Override
+    public String getAnnotationData() {
+        return "";
+    }
+
+    @Override
+    public Map<PropertyDescriptor, String> getProperties() {
+        return Collections.emptyMap();
+    }
+
+    @Override
+    public String encrypt(String unencrypted) {
+        return unencrypted;
+    }
+
+    @Override
+    public String decrypt(String encrypted) {
+        return encrypted;
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return new MockControllerServiceLookup();
+    }
+
+    @Override
+    public Set<Relationship> getAvailableRelationships() {
+        return Collections.emptySet();
+    }
+
+    @Override
+    public boolean hasIncomingConnection() {
+        return true;
+    }
+
+    @Override
+    public boolean hasNonLoopConnection() {
+        return true;
+    }
+
+    @Override
+    public boolean hasConnection(Relationship relationship) {
+        return false;
+    }
+
+    @Override
+    public boolean isExpressionLanguagePresent(PropertyDescriptor property) {
+        return false;
+    }
+
+    @Override
+    public StateManager getStateManager() {
+        return null;
+    }
+
+    @Override
+    public String getName() {
+        return null;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessorInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessorInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessorInitializationContext.java
new file mode 100644
index 0000000..d9320b2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockProcessorInitializationContext.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.controller.NodeTypeProvider;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.processor.ProcessorInitializationContext;
+
+import java.io.File;
+
+/**
+ * A Mock ProcessorInitializationContext that can be used so that Processors can
+ * be initialized for the purpose of generating documentation.
+ *
+ *
+ */
+public class MockProcessorInitializationContext implements ProcessorInitializationContext {
+
+    @Override
+    public String getIdentifier() {
+        return "mock-processor";
+    }
+
+    @Override
+    public ComponentLog getLogger() {
+        return new MockComponentLogger();
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return new MockControllerServiceLookup();
+    }
+
+    @Override
+    public NodeTypeProvider getNodeTypeProvider() {
+        return new MockNodeTypeProvider();
+    }
+
+    @Override
+    public String getKerberosServicePrincipal() {
+        return null;
+    }
+
+    @Override
+    public File getKerberosServiceKeytab() {
+        return null;
+    }
+
+    @Override
+    public File getKerberosConfigurationFile() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockReportingInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockReportingInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockReportingInitializationContext.java
new file mode 100644
index 0000000..630c657
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/mock/MockReportingInitializationContext.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.mock;
+
+import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.reporting.ReportingInitializationContext;
+import org.apache.nifi.scheduling.SchedulingStrategy;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Mock ReportingInitializationContext that can be used to initialize a
+ * ReportingTask for the purposes of documentation generation.
+ *
+ */
+public class MockReportingInitializationContext implements ReportingInitializationContext {
+
+    @Override
+    public String getIdentifier() {
+        return "mock-reporting-task";
+    }
+
+    @Override
+    public String getName() {
+        return "";
+    }
+
+    @Override
+    public long getSchedulingPeriod(TimeUnit timeUnit) {
+        return 0;
+    }
+
+    @Override
+    public ControllerServiceLookup getControllerServiceLookup() {
+        return new MockControllerServiceLookup();
+    }
+
+    @Override
+    public String getSchedulingPeriod() {
+        return "";
+    }
+
+    @Override
+    public SchedulingStrategy getSchedulingStrategy() {
+        return SchedulingStrategy.TIMER_DRIVEN;
+    }
+
+    @Override
+    public ComponentLog getLogger() {
+        return new MockComponentLogger();
+    }
+
+    @Override
+    public String getKerberosServicePrincipal() {
+        return null;
+    }
+
+    @Override
+    public File getKerberosServiceKeytab() {
+        return null;
+    }
+
+    @Override
+    public File getKerberosConfigurationFile() {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
index 745ed9c..1cff3af 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
@@ -19,29 +19,43 @@ package org.apache.nifi.nar;
 import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
 import org.apache.nifi.authentication.LoginIdentityProvider;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.components.ConfigurableComponent;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.state.StateProvider;
 import org.apache.nifi.controller.ControllerService;
 import org.apache.nifi.controller.repository.ContentRepository;
 import org.apache.nifi.controller.repository.FlowFileRepository;
 import org.apache.nifi.controller.repository.FlowFileSwapManager;
 import org.apache.nifi.controller.status.history.ComponentStatusRepository;
 import org.apache.nifi.flowfile.FlowFilePrioritizer;
+import org.apache.nifi.init.ConfigurableComponentInitializer;
+import org.apache.nifi.init.ConfigurableComponentInitializerFactory;
 import org.apache.nifi.processor.Processor;
 import org.apache.nifi.provenance.ProvenanceRepository;
+import org.apache.nifi.reporting.InitializationException;
 import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.File;
 import java.io.IOException;
 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;
 import java.util.ServiceLoader;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
 
 /**
  * Scans through the classpath to load all FlowFileProcessors, FlowFileComparators, and ReportingTasks using the service provider API and running through all classloaders (root, NARs).
@@ -53,10 +67,15 @@ public class ExtensionManager {
 
     private static final Logger logger = LoggerFactory.getLogger(ExtensionManager.class);
 
+    public static final BundleCoordinate SYSTEM_BUNDLE_COORDINATE = new BundleCoordinate(
+            BundleCoordinate.DEFAULT_GROUP, "system", BundleCoordinate.DEFAULT_VERSION);
+
     // Maps a service definition (interface) to those classes that implement the interface
     private static final Map<Class, Set<Class>> definitionMap = new HashMap<>();
 
-    private static final Map<String, ClassLoader> extensionClassloaderLookup = new HashMap<>();
+    private static final Map<String, List<Bundle>> classNameBundleLookup = new HashMap<>();
+    private static final Map<BundleCoordinate, Bundle> bundleCoordinateBundleLookup = new HashMap<>();
+    private static final Map<ClassLoader, Bundle> classLoaderBundleLookup = new HashMap<>();
 
     private static final Set<String> requiresInstanceClassLoading = new HashSet<>();
     private static final Map<String, ClassLoader> instanceClassloaderLookup = new ConcurrentHashMap<>();
@@ -73,28 +92,32 @@ public class ExtensionManager {
         definitionMap.put(FlowFileRepository.class, new HashSet<>());
         definitionMap.put(FlowFileSwapManager.class, new HashSet<>());
         definitionMap.put(ContentRepository.class, new HashSet<>());
+        definitionMap.put(StateProvider.class, new HashSet<>());
     }
 
     /**
      * Loads all FlowFileProcessor, FlowFileComparator, ReportingTask class types that can be found on the bootstrap classloader and by creating classloaders for all NARs found within the classpath.
-     * @param extensionLoaders the loaders to scan through in search of extensions
+     * @param narBundles the bundles to scan through in search of extensions
      */
-    public static void discoverExtensions(final Set<ClassLoader> extensionLoaders) {
-        final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
-
+    public static void discoverExtensions(final Bundle systemBundle, final Set<Bundle> narBundles) {
         // get the current context class loader
         ClassLoader currentContextClassLoader = Thread.currentThread().getContextClassLoader();
 
-        // consider the system class loader
-        loadExtensions(systemClassLoader);
+        // 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 NARs
+        loadExtensions(systemBundle);
+        bundleCoordinateBundleLookup.put(systemBundle.getBundleDetails().getCoordinate(), systemBundle);
 
         // consider each nar class loader
-        for (final ClassLoader ncl : extensionLoaders) {
-
+        for (final Bundle bundle : narBundles) {
             // Must set the context class loader to the nar classloader itself
             // so that static initialization techniques that depend on the context class loader will work properly
+            final ClassLoader ncl = bundle.getClassLoader();
             Thread.currentThread().setContextClassLoader(ncl);
-            loadExtensions(ncl);
+            loadExtensions(bundle);
+
+            // Create a look-up from coordinate to bundle
+            bundleCoordinateBundleLookup.put(bundle.getBundleDetails().getCoordinate(), bundle);
         }
 
         // restore the current context class loader if appropriate
@@ -104,73 +127,195 @@ public class ExtensionManager {
     }
 
     /**
-     * Loads extensions from the specified class loader.
+     * Returns a bundle representing the system class loader.
+     *
+     * @param niFiProperties a NiFiProperties instance which will be used to obtain the default NAR 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 NiFiProperties niFiProperties) {
+        final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+
+        final String narLibraryDirectory = niFiProperties.getProperty(NiFiProperties.NAR_LIBRARY_DIRECTORY);
+        if (StringUtils.isBlank(narLibraryDirectory)) {
+            throw new IllegalStateException("Unable to create system bundle because " + NiFiProperties.NAR_LIBRARY_DIRECTORY + " was null or empty");
+        }
+
+        final BundleDetails systemBundleDetails = new BundleDetails.Builder()
+                .workingDir(new File(narLibraryDirectory))
+                .coordinate(SYSTEM_BUNDLE_COORDINATE)
+                .build();
+
+        return new Bundle(systemBundleDetails, systemClassLoader);
+    }
+
+    /**
+     * Loads extensions from the specified bundle.
      *
-     * @param classLoader from which to load extensions
+     * @param bundle from which to load extensions
      */
     @SuppressWarnings("unchecked")
-    private static void loadExtensions(final ClassLoader classLoader) {
+    private static void loadExtensions(final Bundle bundle) {
         for (final Map.Entry<Class, Set<Class>> entry : definitionMap.entrySet()) {
-            final ServiceLoader<?> serviceLoader = ServiceLoader.load(entry.getKey(), classLoader);
+            final boolean isControllerService = ControllerService.class.equals(entry.getKey());
+            final boolean isProcessor = Processor.class.equals(entry.getKey());
+            final boolean isReportingTask = ReportingTask.class.equals(entry.getKey());
 
+            final ServiceLoader<?> serviceLoader = ServiceLoader.load(entry.getKey(), bundle.getClassLoader());
             for (final Object o : serviceLoader) {
-                registerServiceClass(o.getClass(), extensionClassloaderLookup, classLoader, entry.getValue());
+                // only consider extensions discovered directly in this bundle
+                boolean registerExtension = bundle.getClassLoader().equals(o.getClass().getClassLoader());
+
+                if (registerExtension) {
+                    final Class extensionType = o.getClass();
+                    if (isControllerService && !checkControllerServiceEligibility(extensionType)) {
+                        registerExtension = false;
+                        logger.error(String.format(
+                                "Skipping Controller Service %s because it is bundled with its supporting APIs and requires instance class loading.", extensionType.getName()));
+                    }
+
+                    final boolean canReferenceControllerService = (isControllerService || isProcessor || isReportingTask) && o instanceof ConfigurableComponent;
+                    if (canReferenceControllerService && !checkControllerServiceReferenceEligibility((ConfigurableComponent) o, bundle.getClassLoader())) {
+                        registerExtension = false;
+                        logger.error(String.format(
+                                "Skipping component %s because it is bundled with its referenced Controller Service APIs and requires instance class loading.", extensionType.getName()));
+                    }
+
+                    if (registerExtension) {
+                        registerServiceClass(o.getClass(), classNameBundleLookup, bundle, entry.getValue());
+                    }
+                }
             }
+
+            classLoaderBundleLookup.put(bundle.getClassLoader(), bundle);
         }
     }
 
+    private static boolean checkControllerServiceReferenceEligibility(final ConfigurableComponent component, final ClassLoader classLoader) {
+        // if the extension does not require instance classloading, its eligible
+        final boolean requiresInstanceClassLoading = component.getClass().isAnnotationPresent(RequiresInstanceClassLoading.class);
+
+        ConfigurableComponentInitializer initializer = null;
+        try {
+            initializer = ConfigurableComponentInitializerFactory.createComponentInitializer(component.getClass());
+            initializer.initialize(component);
+
+            final Set<Class> cobundledApis = new HashSet<>();
+            try (final NarCloseable closeable = NarCloseable.withComponentNarLoader(component.getClass().getClassLoader())) {
+                final List<PropertyDescriptor> descriptors = component.getPropertyDescriptors();
+                if (descriptors != null && !descriptors.isEmpty()) {
+                    for (final PropertyDescriptor descriptor : descriptors) {
+                        final Class<? extends ControllerService> serviceApi = descriptor.getControllerServiceDefinition();
+                        if (serviceApi != null && classLoader.equals(serviceApi.getClassLoader())) {
+                            cobundledApis.add(serviceApi);
+                        }
+                    }
+                }
+            }
+
+            if (!cobundledApis.isEmpty()) {
+                logger.warn(String.format(
+                        "Component %s is bundled with its referenced Controller Service APIs %s. The service APIs should not be bundled with component implementations that reference it.",
+                        component.getClass().getName(), StringUtils.join(cobundledApis.stream().map(cls -> cls.getName()).collect(Collectors.toSet()), ", ")));
+            }
+
+            // the component is eligible when it does not require instance classloading or when the supporting APIs are bundled in a parent NAR
+            return requiresInstanceClassLoading == false || cobundledApis.isEmpty();
+        } catch (final InitializationException e) {
+            logger.warn(String.format("Unable to verify if component %s references any bundled Controller Service APIs due to %s", component.getClass().getName(), e.getMessage()));
+            return true;
+        } finally {
+            if (initializer != null) {
+                initializer.teardown(component);
+            }
+        }
+    }
+
+    private static boolean checkControllerServiceEligibility(Class extensionType) {
+        final Class originalExtensionType = extensionType;
+        final ClassLoader originalExtensionClassLoader = extensionType.getClassLoader();
+
+        // if the extension does not require instance classloading, its eligible
+        final boolean requiresInstanceClassLoading = extensionType.isAnnotationPresent(RequiresInstanceClassLoading.class);
+
+        final Set<Class> cobundledApis = new HashSet<>();
+        while (extensionType != null) {
+            for (final Class i : extensionType.getInterfaces()) {
+                if (originalExtensionClassLoader.equals(i.getClassLoader())) {
+                    cobundledApis.add(i);
+                }
+            }
+
+            extensionType = extensionType.getSuperclass();
+        }
+
+        if (!cobundledApis.isEmpty()) {
+            logger.warn(String.format("Controller Service %s is bundled with its supporting APIs %s. The service APIs should not be bundled with the implementations.",
+                    originalExtensionType.getName(), StringUtils.join(cobundledApis.stream().map(cls -> cls.getName()).collect(Collectors.toSet()), ", ")));
+        }
+
+        // the service is eligible when it does not require instance classloading or when the supporting APIs are bundled in a parent NAR
+        return requiresInstanceClassLoading == false || cobundledApis.isEmpty();
+    }
+
     /**
-     * Registers extension for the specified type from the specified ClassLoader.
+     * Registers extension for the specified type from the specified Bundle.
      *
      * @param type the extension type
-     * @param classloaderMap mapping of classname to classloader
-     * @param classLoader the classloader being mapped to
+     * @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, ClassLoader> classloaderMap, final ClassLoader classLoader, final Set<Class> classes) {
+    private static void registerServiceClass(final Class<?> type, final Map<String, List<Bundle>> classNameBundleMap, final Bundle bundle, final Set<Class> classes) {
         final String className = type.getName();
-        final ClassLoader registeredClassLoader = classloaderMap.get(className);
 
-        // see if this class is already registered (this should happen when the class is loaded by an ancestor of the specified classloader)
-        if (registeredClassLoader == null) {
-            classloaderMap.put(className, classLoader);
-            classes.add(type);
+        // get the bundles that have already been registered for the class name
+        List<Bundle> registeredBundles = classNameBundleMap.get(className);
 
-            // keep track of which classes require a class loader per component instance
-            if (type.isAnnotationPresent(RequiresInstanceClassLoading.class)) {
-                requiresInstanceClassLoading.add(className);
+        if (registeredBundles == null) {
+            registeredBundles = new ArrayList<>();
+            classNameBundleMap.put(className, registeredBundles);
+        }
+
+        boolean alreadyRegistered = false;
+        for (final Bundle registeredBundle : registeredBundles) {
+            final BundleCoordinate registeredCoordinate = registeredBundle.getBundleDetails().getCoordinate();
+
+            // if the incoming bundle has the same coordinate as one of the registered bundles then consider it already registered
+            if (registeredCoordinate.equals(bundle.getBundleDetails().getCoordinate())) {
+                alreadyRegistered = true;
+                break;
             }
 
-        } else {
-            boolean loadedFromAncestor = false;
-
-            // determine if this class was loaded from an ancestor
-            ClassLoader ancestorClassLoader = classLoader.getParent();
-            while (ancestorClassLoader != null) {
-                if (ancestorClassLoader == registeredClassLoader) {
-                    loadedFromAncestor = true;
-                    break;
-                }
-                ancestorClassLoader = ancestorClassLoader.getParent();
+            // if the type wasn't loaded from an ancestor, and the type isn't a processor, 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().getCoordinate().getCoordinate()
+                        + " but that class name is already loaded/registered from " + registeredBundle.getBundleDetails().getCoordinate()
+                        + " and multiple versions are not supported for this type"
+                );
             }
+        }
 
-            // if this class was loaded from a non ancestor class loader, report potential unexpected behavior
-            if (!loadedFromAncestor) {
-                logger.warn("Attempt was made to load " + className + " from " + classLoader
-                        + " but that class name is already loaded/registered from " + registeredClassLoader
-                        + ".  This may cause unpredictable behavior.  Order of NARs is not guaranteed.");
+        // 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);
             }
         }
+
     }
 
     /**
-     * Determines the effective classloader for classes of the given type. If returns null it indicates the given type is not known or was not detected.
-     *
-     * @param classType to lookup the classloader of
-     * @return String of fully qualified class name; null if not a detected type
+     * @param type a Class that we found from a service loader
+     * @return true if the given class is a processor, controller service, or reporting task
      */
-    public static ClassLoader getClassLoader(final String classType) {
-        return extensionClassloaderLookup.get(classType);
+    private static boolean multipleVersionsAllowed(Class<?> type) {
+        return Processor.class.isAssignableFrom(type) || ControllerService.class.isAssignableFrom(type) || ReportingTask.class.isAssignableFrom(type);
     }
 
     /**
@@ -178,40 +323,50 @@ public class ExtensionManager {
      *
      * @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 static ClassLoader getClassLoader(final String classType, final String instanceIdentifier) {
-        if (StringUtils.isEmpty(classType) || StringUtils.isEmpty(instanceIdentifier)) {
-            throw new IllegalArgumentException("Class Type and Instance Identifier must be provided");
+    public static ClassLoader createInstanceClassLoader(final String classType, final String instanceIdentifier, final Bundle bundle) {
+        if (StringUtils.isEmpty(classType)) {
+            throw new IllegalArgumentException("Class-Type is required");
         }
 
-        // Check if we already have a ClassLoader for this instance
-        ClassLoader instanceClassLoader = instanceClassloaderLookup.get(instanceIdentifier);
+        if (StringUtils.isEmpty(instanceIdentifier)) {
+            throw new IllegalArgumentException("Instance Identifier is required");
+        }
 
-        // If we don't then we'll create a new ClassLoader for this instance and add it to the map for future lookups
-        if (instanceClassLoader == null) {
-            final ClassLoader registeredClassLoader = getClassLoader(classType);
-            if (registeredClassLoader == null) {
-                return null;
-            }
+        if (bundle == null) {
+            throw new IllegalArgumentException("Bundle is required");
+        }
 
-            // 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 NAR Class Loader, otherwise create an empty
-            // InstanceClassLoader that has the NAR ClassLoader as a parent
-            if (requiresInstanceClassLoading.contains(classType) && (registeredClassLoader instanceof URLClassLoader)) {
-                final URLClassLoader registeredUrlClassLoader = (URLClassLoader) registeredClassLoader;
-                instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, registeredUrlClassLoader.getURLs(), registeredUrlClassLoader.getParent());
-            } else {
-                instanceClassLoader = new InstanceClassLoader(instanceIdentifier, classType, new URL[0], registeredClassLoader);
-            }
+        final ClassLoader bundleClassLoader = bundle.getClassLoader();
 
-            instanceClassloaderLookup.put(instanceIdentifier, instanceClassLoader);
+        // 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 NAR Class Loader, otherwise create an empty
+        // InstanceClassLoader that has the NAR ClassLoader as a parent
+        ClassLoader instanceClassLoader;
+        if (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);
         }
 
+        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 static ClassLoader getInstanceClassLoader(final String instanceIdentifier) {
+        return instanceClassloaderLookup.get(instanceIdentifier);
+    }
+
+    /**
      * Removes the ClassLoader for the given instance and closes it if necessary.
      *
      * @param instanceIdentifier the identifier of a component to remove the ClassLoader for
@@ -241,10 +396,56 @@ public class ExtensionManager {
      * @return true if the class is found in the set of classes requiring instance level class loading, false otherwise
      */
     public static boolean requiresInstanceClassLoading(final String classType) {
+        if (classType == null) {
+            throw new IllegalArgumentException("Class type cannot be null");
+        }
         return 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 static List<Bundle> getBundles(final String classType) {
+        if (classType == null) {
+            throw new IllegalArgumentException("Class type cannot be null");
+        }
+        final List<Bundle> bundles = classNameBundleLookup.get(classType);
+        return bundles == null ? Collections.emptyList() : new ArrayList<>(bundles);
+    }
+
+    /**
+     * Retrieves the bundle with the given coordinate.
+     *
+     * @param bundleCoordinate a coordinate to look up
+     * @return the bundle with the given coordinate, or null if none exists
+     */
+    public static Bundle getBundle(final BundleCoordinate bundleCoordinate) {
+        if (bundleCoordinate == null) {
+            throw new IllegalArgumentException("BundleCoordinate cannot be null");
+        }
+        return bundleCoordinateBundleLookup.get(bundleCoordinate);
+    }
+
+    /**
+     * 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 static Bundle getBundle(final ClassLoader classLoader) {
+        if (classLoader == null) {
+            throw new IllegalArgumentException("ClassLoader cannot be null");
+        }
+        return classLoaderBundleLookup.get(classLoader);
+    }
+
     public static Set<Class> getExtensions(final Class<?> definition) {
+        if (definition == null) {
+            throw new IllegalArgumentException("Class cannot be null");
+        }
         final Set<Class> extensions = definitionMap.get(definition);
         return (extensions == null) ? Collections.<Class>emptySet() : extensions;
     }
@@ -252,12 +453,21 @@ public class ExtensionManager {
     public static void logClassLoaderMapping() {
         final StringBuilder builder = new StringBuilder();
 
-        builder.append("Extension Type Mapping to Classloader:");
+        builder.append("Extension Type Mapping to Bundle:");
         for (final Map.Entry<Class, Set<Class>> entry : definitionMap.entrySet()) {
-            builder.append("\n\t=== ").append(entry.getKey().getSimpleName()).append(" type || Classloader ===");
+            builder.append("\n\t=== ").append(entry.getKey().getSimpleName()).append(" Type ===");
 
             for (final Class type : entry.getValue()) {
-                builder.append("\n\t").append(type.getName()).append(" || ").append(getClassLoader(type.getName()));
+                final List<Bundle> bundles = classNameBundleLookup.containsKey(type.getName())
+                        ? classNameBundleLookup.get(type.getName()) : Collections.emptyList();
+
+                builder.append("\n\t").append(type.getName());
+
+                for (final Bundle bundle : bundles) {
+                    final String coordinate = bundle.getBundleDetails().getCoordinate().getCoordinate();
+                    final String workingDir = bundle.getBundleDetails().getWorkingDirectory().getPath();
+                    builder.append("\n\t\t").append(coordinate).append(" || ").append(workingDir);
+                }
             }
 
             builder.append("\n\t=== End ").append(entry.getKey().getSimpleName()).append(" types ===");

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionMapping.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionMapping.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionMapping.java
index c478d97..698eb57 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionMapping.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionMapping.java
@@ -16,58 +16,108 @@
  */
 package org.apache.nifi.nar;
 
-import java.util.ArrayList;
+import org.apache.nifi.bundle.BundleCoordinate;
+
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
 
 public class ExtensionMapping {
 
-    private final List<String> processorNames = new ArrayList<>();
-    private final List<String> controllerServiceNames = new ArrayList<>();
-    private final List<String> reportingTaskNames = new ArrayList<>();
+    private final Map<String, Set<BundleCoordinate>> processorNames = new HashMap<>();
+    private final Map<String, Set<BundleCoordinate>> controllerServiceNames = new HashMap<>();
+    private final Map<String, Set<BundleCoordinate>> reportingTaskNames = new HashMap<>();
+
+    private final BiFunction<Set<BundleCoordinate>, Set<BundleCoordinate>, Set<BundleCoordinate>> merger = (oldValue, newValue) -> {
+        final Set<BundleCoordinate> merged = new HashSet<>();
+        merged.addAll(oldValue);
+        merged.addAll(newValue);
+        return merged;
+    };
 
-    void addProcessor(final String processorName) {
-        processorNames.add(processorName);
+    void addProcessor(final BundleCoordinate coordinate, final String processorName) {
+        processorNames.computeIfAbsent(processorName, name -> new HashSet<>()).add(coordinate);
     }
 
-    void addAllProcessors(final Collection<String> processorNames) {
-        this.processorNames.addAll(processorNames);
+    void addAllProcessors(final BundleCoordinate coordinate, final Collection<String> processorNames) {
+        processorNames.forEach(name -> {
+            addProcessor(coordinate, name);
+        });
     }
 
-    void addControllerService(final String controllerServiceName) {
-        controllerServiceNames.add(controllerServiceName);
+    void addControllerService(final BundleCoordinate coordinate, final String controllerServiceName) {
+        controllerServiceNames.computeIfAbsent(controllerServiceName, name -> new HashSet<>()).add(coordinate);
     }
 
-    void addAllControllerServices(final Collection<String> controllerServiceNames) {
-        this.controllerServiceNames.addAll(controllerServiceNames);
+    void addAllControllerServices(final BundleCoordinate coordinate, final Collection<String> controllerServiceNames) {
+        controllerServiceNames.forEach(name -> {
+            addControllerService(coordinate, name);
+        });
     }
 
-    void addReportingTask(final String reportingTaskName) {
-        reportingTaskNames.add(reportingTaskName);
+    void addReportingTask(final BundleCoordinate coordinate, final String reportingTaskName) {
+        reportingTaskNames.computeIfAbsent(reportingTaskName, name -> new HashSet<>()).add(coordinate);
     }
 
-    void addAllReportingTasks(final Collection<String> reportingTaskNames) {
-        this.reportingTaskNames.addAll(reportingTaskNames);
+    void addAllReportingTasks(final BundleCoordinate coordinate, final Collection<String> reportingTaskNames) {
+        reportingTaskNames.forEach(name -> {
+            addReportingTask(coordinate, name);
+        });
     }
 
-    public List<String> getProcessorNames() {
-        return Collections.unmodifiableList(processorNames);
+    void merge(final ExtensionMapping other) {
+        other.getProcessorNames().forEach((name, otherCoordinates) -> {
+            processorNames.merge(name, otherCoordinates, merger);
+        });
+        other.getControllerServiceNames().forEach((name, otherCoordinates) -> {
+            controllerServiceNames.merge(name, otherCoordinates, merger);
+        });
+        other.getReportingTaskNames().forEach((name, otherCoordinates) -> {
+            reportingTaskNames.merge(name, otherCoordinates, merger);
+        });
     }
 
-    public List<String> getControllerServiceNames() {
-        return Collections.unmodifiableList(controllerServiceNames);
+    public Map<String, Set<BundleCoordinate>> getProcessorNames() {
+        return Collections.unmodifiableMap(processorNames);
     }
 
-    public List<String> getReportingTaskNames() {
-        return Collections.unmodifiableList(reportingTaskNames);
+    public Map<String, Set<BundleCoordinate>> getControllerServiceNames() {
+        return Collections.unmodifiableMap(controllerServiceNames);
     }
 
-    public List<String> getAllExtensionNames() {
-        final List<String> extensionNames = new ArrayList<>();
-        extensionNames.addAll(processorNames);
-        extensionNames.addAll(controllerServiceNames);
-        extensionNames.addAll(reportingTaskNames);
+    public Map<String, Set<BundleCoordinate>> getReportingTaskNames() {
+        return Collections.unmodifiableMap(reportingTaskNames);
+    }
+
+    public Map<String, Set<BundleCoordinate>> getAllExtensionNames() {
+        final Map<String, Set<BundleCoordinate>> extensionNames = new HashMap<>();
+        extensionNames.putAll(processorNames);
+        extensionNames.putAll(controllerServiceNames);
+        extensionNames.putAll(reportingTaskNames);
         return extensionNames;
     }
+
+    public int size() {
+        int size = 0;
+
+        for (final Set<BundleCoordinate> coordinates : processorNames.values()) {
+            size += coordinates.size();
+        }
+        for (final Set<BundleCoordinate> coordinates : controllerServiceNames.values()) {
+            size += coordinates.size();
+        }
+        for (final Set<BundleCoordinate> coordinates : reportingTaskNames.values()) {
+            size += coordinates.size();
+        }
+
+        return size;
+    }
+
+    public boolean isEmpty() {
+        return processorNames.isEmpty() && controllerServiceNames.isEmpty() && reportingTaskNames.isEmpty();
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarBundleUtil.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarBundleUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarBundleUtil.java
new file mode 100644
index 0000000..62afb37
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarBundleUtil.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.nar;
+
+import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.bundle.BundleDetails;
+import org.apache.nifi.util.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+public class NarBundleUtil {
+
+    /**
+     * Creates a BundleDetails from the given NAR working directory.
+     *
+     * @param narDirectory the directory of an exploded NAR which contains a META-INF/MANIFEST.MF
+     *
+     * @return the BundleDetails constructed from the information in META-INF/MANIFEST.MF
+     */
+    public static BundleDetails fromNarDirectory(final File narDirectory) throws IOException, IllegalStateException {
+        if (narDirectory == null) {
+            throw new IllegalArgumentException("NAR Directory cannot be null");
+        }
+
+        final File manifestFile = new File(narDirectory, "META-INF/MANIFEST.MF");
+        try (final FileInputStream fis = new FileInputStream(manifestFile)) {
+            final Manifest manifest = new Manifest(fis);
+            final Attributes attributes = manifest.getMainAttributes();
+
+            final BundleDetails.Builder builder = new BundleDetails.Builder();
+            builder.workingDir(narDirectory);
+
+            final String group = attributes.getValue(NarManifestEntry.NAR_GROUP.getManifestName());
+            final String id = attributes.getValue(NarManifestEntry.NAR_ID.getManifestName());
+            final String version = attributes.getValue(NarManifestEntry.NAR_VERSION.getManifestName());
+            builder.coordinate(new BundleCoordinate(group, id, version));
+
+            final String dependencyGroup = attributes.getValue(NarManifestEntry.NAR_DEPENDENCY_GROUP.getManifestName());
+            final String dependencyId = attributes.getValue(NarManifestEntry.NAR_DEPENDENCY_ID.getManifestName());
+            final String dependencyVersion = attributes.getValue(NarManifestEntry.NAR_DEPENDENCY_VERSION.getManifestName());
+            if (!StringUtils.isBlank(dependencyId)) {
+                builder.dependencyCoordinate(new BundleCoordinate(dependencyGroup, dependencyId, dependencyVersion));
+            }
+
+            builder.buildBranch(attributes.getValue(NarManifestEntry.BUILD_BRANCH.getManifestName()));
+            builder.buildTag(attributes.getValue(NarManifestEntry.BUILD_TAG.getManifestName()));
+            builder.buildRevision(attributes.getValue(NarManifestEntry.BUILD_REVISION.getManifestName()));
+            builder.buildTimestamp(attributes.getValue(NarManifestEntry.BUILD_TIMESTAMP.getManifestName()));
+            builder.buildJdk(attributes.getValue(NarManifestEntry.BUILD_JDK.getManifestName()));
+            builder.builtBy(attributes.getValue(NarManifestEntry.BUILT_BY.getManifestName()));
+
+            return builder.build();
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d90cf846/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
index 8d55169..eb4539c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarClassLoader.java
@@ -21,6 +21,8 @@ import java.io.FileFilter;
 import java.io.IOException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Comparator;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -192,8 +194,12 @@ public class NarClassLoader extends URLClassLoader {
         }
         addURL(dependencies.toURI().toURL());
         if (dependencies.isDirectory()) {
-            for (File libJar : dependencies.listFiles(JAR_FILTER)) {
-                addURL(libJar.toURI().toURL());
+            final File[] jarFiles = dependencies.listFiles(JAR_FILTER);
+            if (jarFiles != null) {
+                Arrays.sort(jarFiles, Comparator.comparing(File::getName));
+                for (File libJar : jarFiles) {
+                    addURL(libJar.toURI().toURL());
+                }
             }
         }
     }