NIFI-2909 Fix logic in AbstractConfiguredComponent setProperties() for setting classpath resources in the InstanceClassLoader

This closes #1232
This commit is contained in:
Bryan Bende 2016-11-16 11:00:56 -05:00 committed by Matt Burgess
parent 49afacc3ab
commit 2f9ec03242
3 changed files with 98 additions and 7 deletions

View File

@ -115,23 +115,34 @@ public abstract class AbstractConfiguredComponent implements ConfigurableCompone
verifyModifiable();
try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(component.getClass(), id)) {
final Set<String> modulePaths = new LinkedHashSet<>();
boolean classpathChanged = false;
for (final Map.Entry<String, String> entry : properties.entrySet()) {
// determine if any of the property changes require resetting the InstanceClassLoader
final PropertyDescriptor descriptor = component.getPropertyDescriptor(entry.getKey());
if (descriptor.isDynamicClasspathModifier()) {
classpathChanged = true;
}
if (entry.getKey() != null && entry.getValue() == null) {
removeProperty(entry.getKey());
} else if (entry.getKey() != null) {
setProperty(entry.getKey(), entry.getValue());
}
}
// for any properties that dynamically modify the classpath, attempt to evaluate them for expression language
final PropertyDescriptor descriptor = component.getPropertyDescriptor(entry.getKey());
// if at least one property with dynamicallyModifiesClasspath(true) was set, then re-calculate the module paths
// and reset the InstanceClassLoader to the new module paths
if (classpathChanged) {
final Set<String> modulePaths = new LinkedHashSet<>();
for (final Map.Entry<PropertyDescriptor, String> entry : this.properties.entrySet()) {
final PropertyDescriptor descriptor = entry.getKey();
if (descriptor.isDynamicClasspathModifier() && !StringUtils.isEmpty(entry.getValue())) {
final StandardPropertyValue propertyValue = new StandardPropertyValue(entry.getValue(), null, variableRegistry);
modulePaths.add(propertyValue.evaluateAttributeExpressions().getValue());
}
}
processClasspathModifiers(modulePaths);
}
processClasspathModifiers(modulePaths);
}
} finally {
lock.unlock();

View File

@ -162,6 +162,81 @@ public class TestStandardProcessorNode {
}
}
@Test
public void testUpdateOtherPropertyDoesNotImpactClasspath() throws MalformedURLException {
final PropertyDescriptor classpathProp = new PropertyDescriptor.Builder().name("Classpath Resources")
.dynamicallyModifiesClasspath(true).addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
final PropertyDescriptor otherProp = new PropertyDescriptor.Builder().name("My Property")
.addValidator(StandardValidators.NON_EMPTY_VALIDATOR).build();
final ModifiesClasspathProcessor processor = new ModifiesClasspathProcessor(Arrays.asList(classpathProp, otherProp));
final StandardProcessorNode procNode = createProcessorNode(processor);
final Set<ClassLoader> classLoaders = new HashSet<>();
classLoaders.add(procNode.getProcessor().getClass().getClassLoader());
// Load all of the extensions in src/test/java of this project
ExtensionManager.discoverExtensions(classLoaders);
try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(procNode.getProcessor().getClass(), procNode.getIdentifier())){
// Should have an InstanceClassLoader here
final ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
assertTrue(contextClassLoader instanceof InstanceClassLoader);
final InstanceClassLoader instanceClassLoader = (InstanceClassLoader) contextClassLoader;
// Should not have any of the test resources loaded at this point
final URL[] testResources = getTestResources();
for (URL testResource : testResources) {
if (containsResource(instanceClassLoader.getInstanceResources(), testResource)) {
fail("found resource that should not have been loaded");
}
}
// Simulate setting the properties of the processor to point to the test resources directory
final Map<String, String> properties = new HashMap<>();
properties.put(classpathProp.getName(), "src/test/resources/TestClasspathResources");
procNode.setProperties(properties);
// Should have all of the resources loaded into the InstanceClassLoader now
for (URL testResource : testResources) {
assertTrue(containsResource(instanceClassLoader.getInstanceResources(), testResource));
}
// Should pass validation
assertTrue(procNode.isValid());
// Simulate setting updating the other property which should not change the classpath
final Map<String, String> otherProperties = new HashMap<>();
otherProperties.put(otherProp.getName(), "foo");
procNode.setProperties(otherProperties);
// Should STILL have all of the resources loaded into the InstanceClassLoader now
for (URL testResource : testResources) {
assertTrue(containsResource(instanceClassLoader.getInstanceResources(), testResource));
}
// Should STILL pass validation
assertTrue(procNode.isValid());
// Lets update the classpath property and make sure the resources get updated
final Map<String, String> newClasspathProperties = new HashMap<>();
newClasspathProperties.put(classpathProp.getName(), "src/test/resources/TestClasspathResources/resource1.txt");
procNode.setProperties(newClasspathProperties);
// Should only have resource1 loaded now
assertTrue(containsResource(instanceClassLoader.getInstanceResources(), testResources[0]));
assertFalse(containsResource(instanceClassLoader.getInstanceResources(), testResources[1]));
assertFalse(containsResource(instanceClassLoader.getInstanceResources(), testResources[2]));
// Should STILL pass validation
assertTrue(procNode.isValid());
} finally {
ExtensionManager.removeInstanceClassLoaderIfExists(procNode.getIdentifier());
}
}
@Test
public void testMultiplePropertiesDynamicallyModifyClasspathWithExpressionLanguage() throws MalformedURLException {
final PropertyDescriptor classpathProp1 = new PropertyDescriptor.Builder().name("Classpath Resource 1")

View File

@ -47,8 +47,13 @@ public class NarCloseable implements Closeable {
*/
public static NarCloseable withComponentNarLoader(final Class componentClass, final String componentIdentifier) {
final ClassLoader current = Thread.currentThread().getContextClassLoader();
final ClassLoader instanceClassLoader = ExtensionManager.getClassLoader(componentClass.getName(), componentIdentifier);
Thread.currentThread().setContextClassLoader(instanceClassLoader);
ClassLoader componentClassLoader = ExtensionManager.getClassLoader(componentClass.getName(), componentIdentifier);
if (componentClassLoader == null) {
componentClassLoader = componentClass.getClassLoader();
}
Thread.currentThread().setContextClassLoader(componentClassLoader);
return new NarCloseable(current);
}