This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new e0ec9780a5 NIFI-10449 Fixed ScriptedLookupServices reloading
e0ec9780a5 is described below

commit e0ec9780a5dc61db2f6482f3ea41f83065920f81
Author: Matthew Burgess <[email protected]>
AuthorDate: Tue Sep 6 17:33:12 2022 -0400

    NIFI-10449 Fixed ScriptedLookupServices reloading
    
    - Reload script after disabled and first validation after changes made
    
    This closes #6371
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../nifi/util/StandardProcessorTestRunner.java     |  5 +-
 .../lookup/script/BaseScriptedLookupService.java   | 97 ++++++++++++++++++++--
 .../nifi/script/ScriptingComponentHelper.java      |  2 +-
 .../lookup/script/TestScriptedLookupService.groovy | 25 +++---
 .../groovy/test_simple_lookup_inline.groovy        |  4 +-
 5 files changed, 114 insertions(+), 19 deletions(-)

diff --git 
a/nifi-mock/src/main/java/org/apache/nifi/util/StandardProcessorTestRunner.java 
b/nifi-mock/src/main/java/org/apache/nifi/util/StandardProcessorTestRunner.java
index 0aca359bdb..b2b0557dad 100644
--- 
a/nifi-mock/src/main/java/org/apache/nifi/util/StandardProcessorTestRunner.java
+++ 
b/nifi-mock/src/main/java/org/apache/nifi/util/StandardProcessorTestRunner.java
@@ -721,7 +721,10 @@ public class StandardProcessorTestRunner implements 
TestRunner {
         }
 
         try {
-            ReflectionUtils.invokeMethodsWithAnnotation(OnDisabled.class, 
service);
+            // Create a config context to pass into the controller service's 
OnDisabled method (it will be ignored if the controller service has no 
arguments)
+            final MockConfigurationContext configContext = new 
MockConfigurationContext(service, configuration.getProperties(), context, 
variableRegistry);
+            configContext.setValidateExpressions(validateExpressionUsage);
+            ReflectionUtils.invokeMethodsWithAnnotation(OnDisabled.class, 
service, configContext);
         } catch (final Exception e) {
             e.printStackTrace();
             Assertions.fail("Failed to disable Controller Service " + service 
+ " due to " + e);
diff --git 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/BaseScriptedLookupService.java
 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/BaseScriptedLookupService.java
index 3c34af2ad8..709a93f725 100644
--- 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/BaseScriptedLookupService.java
+++ 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/lookup/script/BaseScriptedLookupService.java
@@ -21,7 +21,9 @@ import org.apache.nifi.annotation.lifecycle.OnEnabled;
 import org.apache.nifi.components.AllowableValue;
 import org.apache.nifi.components.ConfigurableComponent;
 import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.resource.ResourceReferences;
 import org.apache.nifi.components.state.StateManager;
 import org.apache.nifi.controller.ConfigurationContext;
 import org.apache.nifi.controller.ControllerServiceInitializationContext;
@@ -35,6 +37,7 @@ import org.apache.nifi.processor.util.StandardValidators;
 import org.apache.nifi.script.AbstractScriptedControllerService;
 import org.apache.nifi.script.ScriptingComponentHelper;
 import org.apache.nifi.script.ScriptingComponentUtils;
+import org.apache.nifi.script.impl.FilteredPropertiesValidationContextAdapter;
 
 import javax.script.Invocable;
 import javax.script.ScriptContext;
@@ -46,6 +49,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
 
@@ -105,7 +109,7 @@ public class BaseScriptedLookupService extends 
AbstractScriptedControllerService
                 }
             } catch (final Throwable t) {
                 final ComponentLog logger = getLogger();
-                final String message = "Unable to get property descriptors 
from Processor: " + t;
+                final String message = "Unable to get property descriptors 
from LookupService: " + t;
 
                 logger.error(message);
                 if (logger.isDebugEnabled()) {
@@ -148,16 +152,29 @@ public class BaseScriptedLookupService extends 
AbstractScriptedControllerService
      */
     @Override
     public void onPropertyModified(final PropertyDescriptor descriptor, final 
String oldValue, final String newValue) {
+        validationResults.set(new HashSet<>());
+
         final ComponentLog logger = getLogger();
-        final ConfigurableComponent instance = lookupService.get();
+        final LookupService<?> instance = lookupService.get();
 
         if (ScriptingComponentUtils.SCRIPT_FILE.equals(descriptor)
                 || ScriptingComponentUtils.SCRIPT_BODY.equals(descriptor)
                 || ScriptingComponentUtils.MODULES.equals(descriptor)
                 || scriptingComponentHelper.SCRIPT_ENGINE.equals(descriptor)) {
+
+            // Update the ScriptingComponentHelper's value(s)
+            if (ScriptingComponentUtils.SCRIPT_FILE.equals(descriptor)) {
+                scriptingComponentHelper.setScriptPath(newValue);
+            } else if (ScriptingComponentUtils.SCRIPT_BODY.equals(descriptor)) 
{
+                scriptingComponentHelper.setScriptBody(newValue);
+            } else if 
(scriptingComponentHelper.SCRIPT_ENGINE.equals(descriptor)) {
+                scriptingComponentHelper.setScriptEngineName(newValue);
+            }
+
             scriptNeedsReload.set(true);
+            scriptRunner = null; //reset engine. This happens only when the 
controller service is disabled, so there won't be any performance impact in 
run-time.
         } else if (instance != null) {
-            // If the script provides a ConfigurableComponent, call its 
onPropertyModified() method
+            // If the script provides a LookupService, call its 
onPropertyModified() method
             try {
                 instance.onPropertyModified(descriptor, oldValue, newValue);
             } catch (final Exception e) {
@@ -167,6 +184,71 @@ public class BaseScriptedLookupService extends 
AbstractScriptedControllerService
         }
     }
 
+    @Override
+    protected Collection<ValidationResult> customValidate(ValidationContext 
context) {
+        Collection<ValidationResult> commonValidationResults = 
super.customValidate(context);
+        if (!commonValidationResults.isEmpty()) {
+            return commonValidationResults;
+        }
+
+        // do not try to build processor/compile/etc until onPropertyModified 
clear the validation error/s
+        // and don't print anything into log.
+        if (!validationResults.get().isEmpty()) {
+            return validationResults.get();
+        }
+
+        Collection<ValidationResult> scriptingComponentHelperResults = 
scriptingComponentHelper.customValidate(context);
+        if (scriptingComponentHelperResults != null && 
!scriptingComponentHelperResults.isEmpty()) {
+            validationResults.set(scriptingComponentHelperResults);
+            return scriptingComponentHelperResults;
+        }
+
+        
scriptingComponentHelper.setScriptEngineName(context.getProperty(scriptingComponentHelper.SCRIPT_ENGINE).getValue());
+        
scriptingComponentHelper.setScriptPath(context.getProperty(ScriptingComponentUtils.SCRIPT_FILE).evaluateAttributeExpressions().getValue());
+        
scriptingComponentHelper.setScriptBody(context.getProperty(ScriptingComponentUtils.SCRIPT_BODY).getValue());
+        final ResourceReferences resourceReferences = 
context.getProperty(ScriptingComponentUtils.MODULES).evaluateAttributeExpressions().asResources();
+        scriptingComponentHelper.setModules(resourceReferences);
+        setup();
+
+        // Now that the component is validated, we can call validate on the 
scripted lookup service
+        final LookupService<?> instance = lookupService.get();
+        final Collection<ValidationResult> currentValidationResults = 
validationResults.get();
+
+        // if there was existing validation errors and the processor loaded 
successfully
+        if (currentValidationResults.isEmpty() && instance != null) {
+            try {
+                // defer to the underlying controller service for validation, 
without the
+                // lookup service's properties
+                final Set<PropertyDescriptor> innerPropertyDescriptor = new 
HashSet<>(scriptingComponentHelper.getDescriptors());
+
+                ValidationContext innerValidationContext = new 
FilteredPropertiesValidationContextAdapter(context, innerPropertyDescriptor);
+                final Collection<ValidationResult> instanceResults = 
instance.validate(innerValidationContext);
+
+                if (instanceResults != null && instanceResults.size() > 0) {
+                    // return the validation results from the underlying 
instance
+                    return instanceResults;
+                }
+            } catch (final Exception e) {
+                final ComponentLog logger = getLogger();
+                final String message = "Unable to validate the scripted 
LookupService: " + e;
+                logger.error(message, e);
+
+                // return a new validation message
+                final Collection<ValidationResult> results = new HashSet<>();
+                results.add(new ValidationResult.Builder()
+                        .subject("Validation")
+                        .valid(false)
+                        .explanation("An error occurred calling validate in 
the configured scripted LookupService.")
+                        
.input(context.getProperty(ScriptingComponentUtils.SCRIPT_FILE).getValue())
+                        .build());
+                return results;
+            }
+        }
+
+        return currentValidationResults;
+
+    }
+
     @Override
     @OnEnabled
     public void onEnabled(final ConfigurationContext context) {
@@ -226,7 +308,8 @@ public class BaseScriptedLookupService extends 
AbstractScriptedControllerService
                             }
                         }
                     } else {
-                        throw new ScriptException("No LookupService was 
defined by the script.");
+                        // This might be due to an error during compilation, 
log it rather than throwing an exception
+                        getLogger().warn("No LookupService was defined by the 
script.");
                     }
                 } catch (ScriptException se) {
                     throw new ProcessException("Error executing 
onDisabled(context) method", se);
@@ -235,6 +318,10 @@ public class BaseScriptedLookupService extends 
AbstractScriptedControllerService
         } else {
             throw new ProcessException("Error creating ScriptRunner");
         }
+
+        scriptingComponentHelper.stop();
+        lookupService.set(null);
+        scriptRunner = null;
     }
 
     @Override
@@ -262,7 +349,7 @@ public class BaseScriptedLookupService extends 
AbstractScriptedControllerService
         final Collection<ValidationResult> results = new HashSet<>();
 
         try {
-            // Create a single script engine, the Processor object is reused 
by each task
+            // Create a single script engine, the LookupService object is 
reused by each task
             if (scriptRunner == null) {
                 scriptingComponentHelper.setupScriptRunners(1, scriptBody, 
getLogger());
                 scriptRunner = scriptingComponentHelper.scriptRunnerQ.poll();
diff --git 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/ScriptingComponentHelper.java
 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/ScriptingComponentHelper.java
index b7e5e1730e..a485cd640b 100644
--- 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/ScriptingComponentHelper.java
+++ 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/main/java/org/apache/nifi/script/ScriptingComponentHelper.java
@@ -236,7 +236,7 @@ public class ScriptingComponentHelper {
             }
 
             // Get a list of URLs from the configurator (if present), or just 
convert modules from Strings to URLs
-            final String[] locations = modules.asLocations().toArray(new 
String[0]);
+            final String[] locations = (modules == null) ? new String[0] : 
modules.asLocations().toArray(new String[0]);
             final URL[] additionalClasspathURLs = 
ScriptRunnerFactory.getInstance().getModuleURLsForClasspath(scriptEngineName, 
locations, log);
 
 
diff --git 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/groovy/org/apache/nifi/lookup/script/TestScriptedLookupService.groovy
 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/groovy/org/apache/nifi/lookup/script/TestScriptedLookupService.groovy
index 01f4454032..c2a4c5b252 100644
--- 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/groovy/org/apache/nifi/lookup/script/TestScriptedLookupService.groovy
+++ 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/groovy/org/apache/nifi/lookup/script/TestScriptedLookupService.groovy
@@ -112,19 +112,10 @@ class TestScriptedLookupService {
         runner.addControllerService("lookupService", scriptedLookupService)
         runner.setProperty(scriptedLookupService, "Script Engine", "Groovy")
         runner.setProperty(scriptedLookupService, 
ScriptingComponentUtils.SCRIPT_BODY, (String) null)
-        runner.setProperty(scriptedLookupService, 
ScriptingComponentUtils.SCRIPT_FILE, ALTERNATE_TARGET_PATH.toString())
+        runner.setProperty(scriptedLookupService, 
ScriptingComponentUtils.SCRIPT_FILE, TARGET_PATH.toString())
         runner.setProperty(scriptedLookupService, 
ScriptingComponentUtils.MODULES, (String) null)
-        // This call to setup should fail loading the script but should mark 
that the script should be reloaded
-        scriptedLookupService.setup()
-        // This prevents the (lookupService == null) check from passing, in 
order to force the reload from the
-        // scriptNeedsReload variable specifically
-        scriptedLookupService.lookupService.set(new 
MockScriptedLookupService())
-
         runner.enableControllerService(scriptedLookupService)
 
-        MockFlowFile mockFlowFile = new MockFlowFile(1L)
-        InputStream inStream = new ByteArrayInputStream('Flow file content not 
used'.bytes)
-
         Optional opt = scriptedLookupService.lookup(['key':'Hello'])
         assertTrue(opt.present)
         assertEquals('Hi', opt.get())
@@ -133,6 +124,20 @@ class TestScriptedLookupService {
         assertEquals('there', opt.get())
         opt = scriptedLookupService.lookup(['key':'Not There'])
         assertFalse(opt.present)
+
+        // Disable and load different script
+        runner.disableControllerService(scriptedLookupService)
+        runner.setProperty(scriptedLookupService, 
ScriptingComponentUtils.SCRIPT_FILE, ALTERNATE_TARGET_PATH.toString())
+        runner.enableControllerService(scriptedLookupService)
+
+        opt = scriptedLookupService.lookup(['key':'Hello'])
+        assertTrue(opt.present)
+        assertEquals('Goodbye', opt.get())
+        opt = scriptedLookupService.lookup(['key':'World'])
+        assertTrue(opt.present)
+        assertEquals('Stranger', opt.get())
+        opt = scriptedLookupService.lookup(['key':'Not There'])
+        assertFalse(opt.present)
     }
 
     class MockScriptedLookupService extends ScriptedLookupService implements 
AccessibleScriptingComponentHelper {
diff --git 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/groovy/test_simple_lookup_inline.groovy
 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/groovy/test_simple_lookup_inline.groovy
index ca10baab3f..81792672f7 100644
--- 
a/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/groovy/test_simple_lookup_inline.groovy
+++ 
b/nifi-nar-bundles/nifi-scripting-bundle/nifi-scripting-processors/src/test/resources/groovy/test_simple_lookup_inline.groovy
@@ -24,8 +24,8 @@ import org.apache.nifi.reporting.InitializationException
 class SimpleGroovyLookupService implements StringLookupService {
 
     def lookupTable = [
-            'Hello': 'Hi',
-            'World': 'there'
+            'Hello': 'Goodbye',
+            'World': 'Stranger'
     ]
 
 

Reply via email to