Repository: nifi
Updated Branches:
  refs/heads/master 2f0d9a34f -> d1d053725


http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/ModifiesClasspathProcessor.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/ModifiesClasspathProcessor.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/ModifiesClasspathProcessor.java
new file mode 100644
index 0000000..3bcbe0d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/test/processors/ModifiesClasspathProcessor.java
@@ -0,0 +1,50 @@
+/*
+ * 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.test.processors;
+
+import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.exception.ProcessException;
+
+import java.util.List;
+
+@RequiresInstanceClassLoading
+public class ModifiesClasspathProcessor extends AbstractProcessor {
+
+    private List<PropertyDescriptor> properties;
+
+    public ModifiesClasspathProcessor() {
+
+    }
+
+    public ModifiesClasspathProcessor(List<PropertyDescriptor> properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+        return properties;
+    }
+
+    @Override
+    public void onTrigger(ProcessContext context, ProcessSession session) 
throws ProcessException {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
new file mode 100644
index 0000000..fca1c19
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -0,0 +1,16 @@
+# 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.
+org.apache.nifi.test.processors.ModifiesClasspathProcessor
+org.apache.nifi.test.processors.ModifiesClasspathNoAnnotationProcessor
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource1.txt
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource1.txt
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource1.txt
new file mode 100644
index 0000000..77c8758
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource1.txt
@@ -0,0 +1,15 @@
+# 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.
+resource1
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource2.txt
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource2.txt
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource2.txt
new file mode 100644
index 0000000..fec550b
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource2.txt
@@ -0,0 +1,15 @@
+# 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.
+resource2
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource3.txt
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource3.txt
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource3.txt
new file mode 100644
index 0000000..695c47b
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/TestClasspathResources/resource3.txt
@@ -0,0 +1,15 @@
+# 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.
+resource3
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml
index 09cc037..4d47050 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/resources/logback-test.xml
@@ -30,9 +30,12 @@
     
     
     <logger name="org.apache.nifi" level="INFO"/>
+    <logger name="org.apache.nifi.controller.service.mock" level="ERROR"/>
+
+    <logger name="StandardProcessSession.claims" level="INFO" />
+
     <root level="INFO">
         <appender-ref ref="CONSOLE"/>
     </root>
-    
-    <logger name="StandardProcessSession.claims" level="DEBUG" />
+
 </configuration>

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/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 221fd22..f9cf9eb 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
@@ -16,6 +16,7 @@
  */
 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.controller.ControllerService;
@@ -27,15 +28,20 @@ import org.apache.nifi.flowfile.FlowFilePrioritizer;
 import org.apache.nifi.processor.Processor;
 import org.apache.nifi.provenance.ProvenanceRepository;
 import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.ServiceLoader;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Scans through the classpath to load all FlowFileProcessors, 
FlowFileComparators, and ReportingTasks using the service provider API and 
running through all classloaders (root, NARs).
@@ -52,6 +58,9 @@ public class ExtensionManager {
 
     private static final Map<String, ClassLoader> extensionClassloaderLookup = 
new HashMap<>();
 
+    private static final Set<String> requiresInstanceClassLoading = new 
HashSet<>();
+    private static final Map<String, ClassLoader> instanceClassloaderLookup = 
new ConcurrentHashMap<>();
+
     static {
         definitionMap.put(Processor.class, new HashSet<>());
         definitionMap.put(FlowFilePrioritizer.class, new HashSet<>());
@@ -126,6 +135,12 @@ public class ExtensionManager {
         if (registeredClassLoader == null) {
             classloaderMap.put(className, classLoader);
             classes.add(type);
+
+            // keep track of which classes require a class loader per 
component instance
+            if (type.isAnnotationPresent(RequiresInstanceClassLoading.class)) {
+                requiresInstanceClassLoading.add(className);
+            }
+
         } else {
             boolean loadedFromAncestor = false;
 
@@ -158,6 +173,73 @@ public class ExtensionManager {
         return extensionClassloaderLookup.get(classType);
     }
 
+    /**
+     * 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
+     * @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");
+        }
+
+        // Check if we already have a ClassLoader for this instance
+        ClassLoader instanceClassLoader = 
instanceClassloaderLookup.get(instanceIdentifier);
+
+        // 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 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, registeredUrlClassLoader.getURLs(), 
registeredUrlClassLoader.getParent());
+            } else {
+                instanceClassLoader = new 
InstanceClassLoader(instanceIdentifier, new URL[0], registeredClassLoader);
+            }
+
+            instanceClassloaderLookup.put(instanceIdentifier, 
instanceClassLoader);
+        }
+
+        return instanceClassLoader;
+    }
+
+    /**
+     * 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 static ClassLoader removeInstanceClassLoaderIfExists(final String 
instanceIdentifier) {
+        ClassLoader classLoader = 
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 static boolean requiresInstanceClassLoading(final String classType) 
{
+        return requiresInstanceClassLoading.contains(classType);
+    }
+
     public static Set<Class> getExtensions(final Class<?> definition) {
         final Set<Class> extensions = definitionMap.get(definition);
         return (extensions == null) ? Collections.<Class>emptySet() : 
extensions;

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
new file mode 100644
index 0000000..42d273a
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/InstanceClassLoader.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.nar;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.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 NARClassLoader in
+ * order to create a copy of the NARClassLoader without modifying it.
+ */
+public class InstanceClassLoader extends URLClassLoader {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(InstanceClassLoader.class);
+
+    private final String identifier;
+    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 URL[] urls, 
final ClassLoader parent) {
+        super(urls, parent);
+        this.identifier = identifier;
+    }
+
+    /**
+     * 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 URLClassLoader for " + 
identifier);
+            }
+        }
+
+        // don't set a parent here b/c otherwise it will create an infinite 
loop
+        shimClassLoader = new ShimClassLoader(urls, null);
+    }
+
+    /**
+     * @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 cnf) {
+                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;
+        }
+    }
+
+    /**
+     * 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/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarCloseable.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarCloseable.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarCloseable.java
index 116b069..d252f19 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarCloseable.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-nar-utils/src/main/java/org/apache/nifi/nar/NarCloseable.java
@@ -35,18 +35,20 @@ public class NarCloseable implements Closeable {
     }
 
     /**
-     * Sets the current thread context class loader to the specific appropriate
-     * Nar class loader for the given configurable component. Restores to the
-     * previous classloader once complete. If the given class is not assignable
-     * from ConfigurableComponent then the NarThreadContextClassLoader is used.
+     * Sets the current thread context class loader to the specific 
appropriate class loader for the given
+     * component. If the component requires per-instance class loading then 
the class loader will be the
+     * specific class loader for instance with the given identifier, otherwise 
the class loader will be
+     * the NARClassLoader.
      *
-     * @param componentClass componentClass
-     * @return NarCloseable with current thread context classloader jailed to
-     * the nar of the component
+     * @param componentClass the component class
+     * @param componentIdentifier the identifier of the component
+     * @return NarCloseable with the current thread context classloader jailed 
to the Nar
+     *              or instance class loader of the component
      */
-    public static NarCloseable withComponentNarLoader(final Class 
componentClass) {
+    public static NarCloseable withComponentNarLoader(final Class 
componentClass, final String componentIdentifier) {
         final ClassLoader current = 
Thread.currentThread().getContextClassLoader();
-        
Thread.currentThread().setContextClassLoader(componentClass.getClassLoader());
+        final ClassLoader instanceClassLoader = 
ExtensionManager.getClassLoader(componentClass.getName(), componentIdentifier);
+        Thread.currentThread().setContextClassLoader(instanceClassLoader);
         return new NarCloseable(current);
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index c89521c..e250231 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -1647,7 +1647,7 @@ public class ControllerFacade implements Authorizable {
             final SearchContext context = new StandardSearchContext(searchStr, 
procNode, flowController, variableRegistry);
 
             // search the processor using the appropriate thread context 
classloader
-            try (final NarCloseable x = 
NarCloseable.withComponentNarLoader(processor.getClass())) {
+            try (final NarCloseable x = 
NarCloseable.withComponentNarLoader(processor.getClass(), 
processor.getIdentifier())) {
                 final Collection<SearchResult> searchResults = 
searchable.search(context);
                 if (CollectionUtils.isNotEmpty(searchResults)) {
                     for (final SearchResult searchResult : searchResults) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
index 44e6996..169cd50 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
@@ -275,15 +275,7 @@ public class StandardControllerServiceDAO extends 
ComponentDAO implements Contro
             controllerService.setComments(comments);
         }
         if (isNotNull(properties)) {
-            for (final Map.Entry<String, String> entry : 
properties.entrySet()) {
-                final String propName = entry.getKey();
-                final String propVal = entry.getValue();
-                if (isNotNull(propName) && propVal == null) {
-                    controllerService.removeProperty(propName);
-                } else if (isNotNull(propName)) {
-                    controllerService.setProperty(propName, propVal);
-                }
-            }
+            controllerService.setProperties(properties);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
index 6a6f175..f42bc7b 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
@@ -161,15 +161,7 @@ public class StandardProcessorDAO extends ComponentDAO 
implements ProcessorDAO {
                 processor.setLossTolerant(config.isLossTolerant());
             }
             if (isNotNull(configProperties)) {
-                for (final Map.Entry<String, String> entry : 
configProperties.entrySet()) {
-                    final String propName = entry.getKey();
-                    final String propVal = entry.getValue();
-                    if (isNotNull(propName) && propVal == null) {
-                        processor.removeProperty(propName);
-                    } else if (isNotNull(propName)) {
-                        processor.setProperty(propName, propVal);
-                    }
-                }
+                processor.setProperties(configProperties);
             }
 
             if (isNotNull(undefinedRelationshipsToTerminate)) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
index ac3d9d5..1f2b155 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
@@ -285,15 +285,7 @@ public class StandardReportingTaskDAO extends ComponentDAO 
implements ReportingT
             reportingTask.setComments(comments);
         }
         if (isNotNull(properties)) {
-            for (final Map.Entry<String, String> entry : 
properties.entrySet()) {
-                final String propName = entry.getKey();
-                final String propVal = entry.getValue();
-                if (isNotNull(propName) && propVal == null) {
-                    reportingTask.removeProperty(propName);
-                } else if (isNotNull(propName)) {
-                    reportingTask.setProperty(propName, propVal);
-                }
-            }
+            reportingTask.setProperties(properties);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-reporting-tasks/src/test/java/org/apache/nifi/controller/MonitorMemoryTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-reporting-tasks/src/test/java/org/apache/nifi/controller/MonitorMemoryTest.java
 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-reporting-tasks/src/test/java/org/apache/nifi/controller/MonitorMemoryTest.java
index 3a10c1c..7e8b793 100644
--- 
a/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-reporting-tasks/src/test/java/org/apache/nifi/controller/MonitorMemoryTest.java
+++ 
b/nifi-nar-bundles/nifi-standard-bundle/nifi-standard-reporting-tasks/src/test/java/org/apache/nifi/controller/MonitorMemoryTest.java
@@ -62,7 +62,10 @@ public class MonitorMemoryTest {
     @Test(expected = IllegalStateException.class)
     public void validatevalidationKicksInOnWrongPoolNames() throws Exception {
         ReportingTaskNode reportingTask = 
fc.createReportingTask(MonitorMemory.class.getName());
-        
reportingTask.setProperty(MonitorMemory.MEMORY_POOL_PROPERTY.getName(), "foo");
+
+        Map<String,String> props = new HashMap<>();
+        props.put(MonitorMemory.MEMORY_POOL_PROPERTY.getName(), "foo");
+        reportingTask.setProperties(props);
         ProcessScheduler ps = fc.getProcessScheduler();
         ps.schedule(reportingTask);
     }
@@ -91,9 +94,12 @@ public class MonitorMemoryTest {
         CapturingLogger capturingLogger = this.wrapAndReturnCapturingLogger();
         ReportingTaskNode reportingTask = 
fc.createReportingTask(MonitorMemory.class.getName());
         reportingTask.setSchedulingPeriod("1 sec");
-        
reportingTask.setProperty(MonitorMemory.MEMORY_POOL_PROPERTY.getName(), "PS Old 
Gen");
-        reportingTask.setProperty(MonitorMemory.REPORTING_INTERVAL.getName(), 
"100 millis");
-        reportingTask.setProperty(MonitorMemory.THRESHOLD_PROPERTY.getName(), 
threshold);
+
+        Map<String,String> props = new HashMap<>();
+        props.put(MonitorMemory.MEMORY_POOL_PROPERTY.getName(), "PS Old Gen");
+        props.put(MonitorMemory.REPORTING_INTERVAL.getName(), "100 millis");
+        props.put(MonitorMemory.THRESHOLD_PROPERTY.getName(), threshold);
+        reportingTask.setProperties(props);
 
         ProcessScheduler ps = fc.getProcessScheduler();
         ps.schedule(reportingTask);

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-standard-services/nifi-hbase-client-service-api/src/main/java/org/apache/nifi/hbase/HBaseClientService.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-hbase-client-service-api/src/main/java/org/apache/nifi/hbase/HBaseClientService.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-hbase-client-service-api/src/main/java/org/apache/nifi/hbase/HBaseClientService.java
index f9f5bfb..9408b17 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-hbase-client-service-api/src/main/java/org/apache/nifi/hbase/HBaseClientService.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-hbase-client-service-api/src/main/java/org/apache/nifi/hbase/HBaseClientService.java
@@ -67,6 +67,14 @@ public interface HBaseClientService extends 
ControllerService {
             .defaultValue("1")
             .build();
 
+    PropertyDescriptor PHOENIX_CLIENT_JAR_LOCATION = new 
PropertyDescriptor.Builder()
+            .name("Phoenix Client JAR Location")
+            .description("The full path to the Phoenix client JAR. Required if 
Phoenix is installed on top of HBase.")
+            .addValidator(StandardValidators.FILE_EXISTS_VALIDATOR)
+            .expressionLanguageSupported(true)
+            .dynamicallyModifiesClasspath(true)
+            .build();
+
     /**
      * Puts a batch of mutations to the given table.
      *

http://git-wip-us.apache.org/repos/asf/nifi/blob/d1d05372/nifi-nar-bundles/nifi-standard-services/nifi-hbase_1_1_2-client-service-bundle/nifi-hbase_1_1_2-client-service/src/main/java/org/apache/nifi/hbase/HBase_1_1_2_ClientService.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-standard-services/nifi-hbase_1_1_2-client-service-bundle/nifi-hbase_1_1_2-client-service/src/main/java/org/apache/nifi/hbase/HBase_1_1_2_ClientService.java
 
b/nifi-nar-bundles/nifi-standard-services/nifi-hbase_1_1_2-client-service-bundle/nifi-hbase_1_1_2-client-service/src/main/java/org/apache/nifi/hbase/HBase_1_1_2_ClientService.java
index 97a0d66..4a9fc0e 100644
--- 
a/nifi-nar-bundles/nifi-standard-services/nifi-hbase_1_1_2-client-service-bundle/nifi-hbase_1_1_2-client-service/src/main/java/org/apache/nifi/hbase/HBase_1_1_2_ClientService.java
+++ 
b/nifi-nar-bundles/nifi-standard-services/nifi-hbase_1_1_2-client-service-bundle/nifi-hbase_1_1_2-client-service/src/main/java/org/apache/nifi/hbase/HBase_1_1_2_ClientService.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.hbase;
 
-import java.io.File;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
@@ -36,6 +35,7 @@ import org.apache.hadoop.hbase.filter.ParseFilter;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.apache.nifi.annotation.behavior.DynamicProperty;
+import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.Tags;
 import org.apache.nifi.annotation.lifecycle.OnDisabled;
@@ -57,6 +57,7 @@ import org.apache.nifi.hbase.scan.ResultHandler;
 import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.reporting.InitializationException;
 
+import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.security.PrivilegedExceptionAction;
@@ -68,6 +69,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 
+@RequiresInstanceClassLoading
 @Tags({ "hbase", "client"})
 @CapabilityDescription("Implementation of HBaseClientService for HBase 1.1.2. 
This service can be configured by providing " +
         "a comma-separated list of configuration files, or by specifying 
values for the other properties. If configuration files " +
@@ -109,6 +111,7 @@ public class HBase_1_1_2_ClientService extends 
AbstractControllerService impleme
         props.add(ZOOKEEPER_CLIENT_PORT);
         props.add(ZOOKEEPER_ZNODE_PARENT);
         props.add(HBASE_CLIENT_RETRIES);
+        props.add(PHOENIX_CLIENT_JAR_LOCATION);
         this.properties = Collections.unmodifiableList(props);
     }
 

Reply via email to