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 868a6c84d4 NIFI-14682 Improved JDBC Driver error message in Connection
Pool Services (#10039)
868a6c84d4 is described below
commit 868a6c84d4e378dba6eed0bb52b5249c8de4bb28
Author: Pierre Villard <[email protected]>
AuthorDate: Fri Aug 22 00:01:25 2025 +0200
NIFI-14682 Improved JDBC Driver error message in Connection Pool Services
(#10039)
Signed-off-by: David Handermann <[email protected]>
---
.../nifi-extension-utils/nifi-dbcp-base/pom.xml | 5 +
.../nifi/dbcp/AbstractDBCPConnectionPool.java | 30 ++-
.../nifi-extension-utils/nifi-dbcp-utils/pom.xml | 26 +++
.../org/apache/nifi/dbcp/utils/DriverUtils.java | 209 +++++++++++++++++++++
.../nifi-extension-utils/pom.xml | 1 +
.../nifi-dbcp-service/pom.xml | 6 +-
.../org/apache/nifi/dbcp/DBCPConnectionPool.java | 32 +++-
.../java/org/apache/nifi/dbcp/DBCPServiceTest.java | 41 ++++
.../nifi-hikari-dbcp-service/pom.xml | 5 +
.../apache/nifi/dbcp/HikariCPConnectionPool.java | 28 ++-
10 files changed, 369 insertions(+), 14 deletions(-)
diff --git a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/pom.xml
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/pom.xml
index f57d2235d8..0d8fc61c40 100644
--- a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/pom.xml
+++ b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/pom.xml
@@ -34,6 +34,11 @@
<artifactId>nifi-dbcp-service-api</artifactId>
<version>2.6.0-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-dbcp-utils</artifactId>
+ <version>2.6.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-kerberos</artifactId>
diff --git
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPool.java
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPool.java
index e3e924e2ff..babdb9b1de 100644
---
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPool.java
+++
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPool.java
@@ -31,10 +31,12 @@ import org.apache.nifi.annotation.lifecycle.OnEnabled;
import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.components.resource.ResourceReferences;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.VerifiableControllerService;
import org.apache.nifi.dbcp.utils.DataSourceConfiguration;
+import org.apache.nifi.dbcp.utils.DriverUtils;
import org.apache.nifi.kerberos.KerberosUserService;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.exception.ProcessException;
@@ -45,6 +47,7 @@ import org.apache.nifi.security.krb.KerberosUser;
import static
org.apache.nifi.components.ConfigVerificationResult.Outcome.FAILED;
import static
org.apache.nifi.components.ConfigVerificationResult.Outcome.SUCCESSFUL;
+import static org.apache.nifi.dbcp.utils.DBCPProperties.DB_DRIVERNAME;
import static org.apache.nifi.dbcp.utils.DBCPProperties.DB_DRIVER_LOCATION;
import static org.apache.nifi.dbcp.utils.DBCPProperties.KERBEROS_USER_SERVICE;
@@ -58,6 +61,7 @@ public abstract class AbstractDBCPConnectionPool extends
AbstractControllerServi
List<ConfigVerificationResult> results = new ArrayList<>();
KerberosUser kerberosUser = null;
+
try {
kerberosUser = getKerberosUser(context);
if (kerberosUser != null) {
@@ -101,16 +105,30 @@ public abstract class AbstractDBCPConnectionPool extends
AbstractControllerServi
.build());
}
} catch (final Exception e) {
- String message = "Failed to configure Data Source.";
- if (e.getCause() instanceof ClassNotFoundException) {
- message += String.format(" Ensure changes to the '%s'
property are applied before verifying",
- DB_DRIVER_LOCATION.getDisplayName());
+ StringBuilder messageBuilder = new StringBuilder("Failed to
configure Data Source.");
+ verificationLogger.error(messageBuilder.toString(), e);
+
+ final String driverName =
context.getProperty(DB_DRIVERNAME).evaluateAttributeExpressions().getValue();
+ final ResourceReferences driverResources =
context.getProperty(DB_DRIVER_LOCATION).evaluateAttributeExpressions().asResources();
+
+ if (StringUtils.isNotBlank(driverName) &&
driverResources.getCount() != 0) {
+ List<String> availableDrivers =
DriverUtils.findDriverClassNames(driverResources);
+ if (!availableDrivers.isEmpty() &&
!availableDrivers.contains(driverName)) {
+ messageBuilder.append(String.format(" Driver class [%s]
not found in provided resources. Available driver classes found: %s",
+ driverName, String.join(", ", availableDrivers)));
+ } else if (e.getCause() instanceof ClassNotFoundException &&
availableDrivers.contains(driverName)) {
+ messageBuilder.append(" Driver Class found but not loaded:
Apply configuration before verifying.");
+ } else {
+ messageBuilder.append(String.format(" Exception: %s",
e.getMessage()));
+ }
+ } else {
+ messageBuilder.append(String.format(" No driver name specified
or no driver resources provided. Exception: %s", e.getMessage()));
}
- verificationLogger.error(message, e);
+
results.add(new ConfigVerificationResult.Builder()
.verificationStepName("Configure Data Source")
.outcome(FAILED)
- .explanation(message + ": " + e.getMessage())
+ .explanation(messageBuilder.toString())
.build());
} finally {
try {
diff --git
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/pom.xml
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/pom.xml
new file mode 100644
index 0000000000..68bfdb2c7a
--- /dev/null
+++ b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/pom.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>nifi-extension-utils</artifactId>
+ <groupId>org.apache.nifi</groupId>
+ <version>2.6.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>nifi-dbcp-utils</artifactId>
+
+</project>
diff --git
a/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/src/main/java/org/apache/nifi/dbcp/utils/DriverUtils.java
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/src/main/java/org/apache/nifi/dbcp/utils/DriverUtils.java
new file mode 100644
index 0000000000..68834e3197
--- /dev/null
+++
b/nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/src/main/java/org/apache/nifi/dbcp/utils/DriverUtils.java
@@ -0,0 +1,209 @@
+/*
+ * 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.dbcp.utils;
+
+import org.apache.nifi.components.resource.ResourceReference;
+import org.apache.nifi.components.resource.ResourceReferences;
+import org.apache.nifi.components.resource.ResourceType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.sql.Driver;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+
+public class DriverUtils {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(DriverUtils.class);
+ private static final String DRIVER_CLASS_PATTERN = "(?i).*(driver|jdbc).*";
+ private static final Pattern DRIVER_PATTERN =
Pattern.compile(DRIVER_CLASS_PATTERN);
+
+ /**
+ * Discovers JDBC driver classes using static JAR scanning only (no class
+ * loading) This is used during validation when resources aren't loaded
into
+ * classpath yet
+ */
+ public static List<String> findDriverClassNames(final ResourceReferences
driverResources) {
+ final Set<String> driverClasses = new TreeSet<>();
+
+ if (driverResources == null || driverResources.getCount() == 0) {
+ LOGGER.debug("No driver resources provided for static discovery");
+ return new ArrayList<>();
+ }
+ LOGGER.debug("Starting static driver discovery for {} resources",
driverResources.getCount());
+
+ for (final ResourceReference resource :
driverResources.flattenRecursively().asList()) {
+ try {
+ LOGGER.debug("Processing resource: {} (type: {})",
resource.getLocation(), resource.getResourceType());
+ final List<File> jarFiles = getJarFilesFromResource(resource);
+ LOGGER.debug("Found {} JAR files in resource {}",
jarFiles.size(), resource.getLocation());
+ for (final File jarFile : jarFiles) {
+ LOGGER.debug("Scanning JAR file: {}",
jarFile.getAbsolutePath());
+ final Set<String> foundDrivers =
scanJarForDriverClassesUsingMetadata(jarFile);
+ LOGGER.debug("Found {} potential driver classes in {}",
foundDrivers.size(), jarFile.getName());
+ driverClasses.addAll(foundDrivers);
+ }
+ } catch (final Exception e) {
+ LOGGER.warn("Error processing resource {} for static driver
discovery", resource.getLocation(), e);
+ }
+ }
+
+ LOGGER.debug("Static driver discovery completed. Found {} potential
driver classes", driverClasses.size());
+ return new ArrayList<>(driverClasses);
+ }
+
+ /**
+ * Scans a JAR file for potential JDBC driver class names using static
analysis
+ * only This uses improved heuristics including checking META-INF/services
and
+ * common patterns
+ */
+ private static Set<String> scanJarForDriverClassesUsingMetadata(final File
jarFile) {
+ final Set<String> driverClasses = new TreeSet<>();
+ try (final JarFile jar = new JarFile(jarFile)) {
+ // Check META-INF/services/java.sql.Driver for registered drivers
+ // This is the most reliable method
+ final JarEntry servicesEntry =
jar.getJarEntry("META-INF/services/java.sql.Driver");
+ if (servicesEntry != null) {
+ try (Scanner scanner = new
Scanner(jar.getInputStream(servicesEntry))) {
+ while (scanner.hasNextLine()) {
+ final String line = scanner.nextLine().trim();
+ if (!line.isEmpty() && !line.startsWith("#")) {
+ driverClasses.add(line);
+ LOGGER.debug("Found driver in META-INF/services:
{}", line);
+ }
+ }
+ }
+ }
+ return driverClasses;
+ } catch (final Exception e) {
+ LOGGER.warn("Error scanning JAR file {} for driver classes",
jarFile.getAbsolutePath(), e);
+ }
+ return driverClasses;
+ }
+
+ /**
+ * Gets JAR files from a ResourceReference for scanning. It is expected
that the
+ * submitted resource is coming from the listing of
+ * driverResources.flattenRecursively().asList() so that this method does
not
+ * need to be recursive
+ */
+ private static List<File> getJarFilesFromResource(final ResourceReference
resource) {
+ final List<File> jarFiles = new ArrayList<>();
+ if (resource.getResourceType() == ResourceType.FILE) {
+ final File file = new File(resource.getLocation());
+ if (file.exists() && file.canRead() &&
file.getName().toLowerCase().endsWith(".jar")) {
+ jarFiles.add(file);
+ }
+ }
+ // we don't need to scan for directory since we already did a
recursive flatten
+ // and we don't want to deal with URLs in this case
+ return jarFiles;
+ }
+
+ /**
+ * Discovers all potential JDBC driver classes in the provided resources
Since
+ * dynamicallyModifiesClasspath=true, we scan the resources directly
rather than
+ * trying to load classes, as NiFi handles classpath modification
+ */
+ public static List<String> discoverDriverClasses(final ResourceReferences
driverResources) {
+ final Set<String> driverClasses = new TreeSet<>(); // Use TreeSet for
sorted results
+
+ if (driverResources == null || driverResources.getCount() == 0) {
+ return new ArrayList<>();
+ }
+
+ for (final ResourceReference resource :
driverResources.flattenRecursively().asList()) {
+ try {
+ final List<File> jarFiles =
DriverUtils.getJarFilesFromResource(resource);
+ for (final File jarFile : jarFiles) {
+ driverClasses.addAll(scanJarForDriverClasses(jarFile));
+ }
+ } catch (final Exception e) {
+ LOGGER.warn("Error processing resource {} for driver
discovery", resource.getLocation(), e);
+ }
+ }
+
+ return new ArrayList<>(driverClasses);
+ }
+
+ /**
+ * Scans a JAR file for potential JDBC driver class names (without loading
them)
+ */
+ private static Set<String> scanJarForDriverClasses(final File jarFile) {
+ final Set<String> actualDriverClasses = new TreeSet<>();
+
+ try (JarFile jar = new JarFile(jarFile)) {
+ final Enumeration<JarEntry> entries = jar.entries();
+ while (entries.hasMoreElements()) {
+ final JarEntry entry = entries.nextElement();
+ final String entryName = entry.getName();
+
+ // Look for .class files (exclude inner classes)
+ if (entryName.endsWith(".class") && !entryName.contains("$")) {
+ String className = entryName.substring(0,
entryName.length() - 6).replace('/', '.');
+
+ // First filter with heuristics to avoid loading every
class
+ if (isPotentialDriverClass(className)) {
+ // Now actually verify it's a JDBC driver
+ if (isActualDriverClass(className)) {
+ actualDriverClasses.add(className);
+ }
+ }
+ }
+ }
+ } catch (final Exception e) {
+ LOGGER.warn("Error scanning JAR file {} for driver classes",
jarFile.getAbsolutePath(), e);
+ }
+
+ return actualDriverClasses;
+ }
+
+ /**
+ * Uses heuristics to identify potential JDBC driver classes without
loading
+ * them This is a first-pass filter to avoid loading every single class
+ */
+ private static boolean isPotentialDriverClass(final String className) {
+ return DRIVER_PATTERN.matcher(className).matches();
+ }
+
+ /**
+ * Actually loads and checks if a class implements java.sql.Driver Since
+ * dynamicallyModifiesClasspath=true, the class should be available
+ */
+ private static boolean isActualDriverClass(final String className) {
+ try {
+ final Class<?> clazz = Class.forName(className);
+
+ // Check if it implements Driver interface and is not
abstract/interface
+ return Driver.class.isAssignableFrom(clazz)
+ && !clazz.isInterface()
+ &&
!java.lang.reflect.Modifier.isAbstract(clazz.getModifiers());
+ } catch (final Throwable e) {
+ // Class couldn't be loaded or has issues - not a valid driver
+ return false;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-extension-utils/pom.xml
b/nifi-extension-bundles/nifi-extension-utils/pom.xml
index 327f0a95c0..95399087a9 100644
--- a/nifi-extension-bundles/nifi-extension-utils/pom.xml
+++ b/nifi-extension-bundles/nifi-extension-utils/pom.xml
@@ -32,6 +32,7 @@
<module>nifi-database-utils</module>
<module>nifi-database-test-utils</module>
<module>nifi-dbcp-base</module>
+ <module>nifi-dbcp-utils</module>
<module>nifi-event-listen</module>
<module>nifi-event-put</module>
<module>nifi-event-transport</module>
diff --git
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/pom.xml
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/pom.xml
index 38ad2df65a..7286d64b65 100644
---
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/pom.xml
+++
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/pom.xml
@@ -35,7 +35,11 @@
<artifactId>nifi-dbcp-base</artifactId>
<version>2.6.0-SNAPSHOT</version>
</dependency>
-
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-dbcp-utils</artifactId>
+ <version>2.6.0-SNAPSHOT</version>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-service-utils</artifactId>
diff --git
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/main/java/org/apache/nifi/dbcp/DBCPConnectionPool.java
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/main/java/org/apache/nifi/dbcp/DBCPConnectionPool.java
index ee430e8621..28fef941f3 100644
---
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/main/java/org/apache/nifi/dbcp/DBCPConnectionPool.java
+++
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/main/java/org/apache/nifi/dbcp/DBCPConnectionPool.java
@@ -37,9 +37,13 @@ import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.RequiredPermission;
+import org.apache.nifi.components.resource.ResourceReference;
+import org.apache.nifi.components.resource.ResourceReferences;
+import org.apache.nifi.components.resource.ResourceType;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.VerifiableControllerService;
import org.apache.nifi.dbcp.utils.DataSourceConfiguration;
+import org.apache.nifi.dbcp.utils.DriverUtils;
import org.apache.nifi.expression.AttributeExpression;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.migration.PropertyConfiguration;
@@ -203,7 +207,33 @@ public class DBCPConnectionPool extends
AbstractDBCPConnectionPool implements DB
try {
clazz = Class.forName(driverName);
} catch (final ClassNotFoundException e) {
- throw new ProcessException("Driver class " + driverName + " is not
found", e);
+ // Enhanced error message with discovery
+ ResourceReferences driverResources = null;
+ try {
+ // Try to get driver resources from current context if
available
+ if (getConfigurationContext() != null) {
+ driverResources =
getConfigurationContext().getProperty(DB_DRIVER_LOCATION).evaluateAttributeExpressions().asResources();
+ }
+ } catch (Exception ignored) {
+ // Context might not be available, continue without it
+ }
+
+ final List<String> availableDrivers = (driverResources != null &&
driverResources.getCount() != 0) ?
DriverUtils.discoverDriverClasses(driverResources) : List.of();
+
+ StringBuilder errorMessage = new StringBuilder("JDBC driver class
'%s' not found.".formatted(driverName));
+
+ if (!availableDrivers.isEmpty()) {
+ errorMessage.append(" Available driver classes found in
resources: %s.".formatted(String.join(", ", availableDrivers)));
+ } else if (driverResources != null && driverResources.getCount()
!= 0) {
+ final List<ResourceReference> resourcesList =
driverResources.asList();
+ if (resourcesList.stream().filter(r -> r.getResourceType() !=
ResourceType.URL).count() != 0) {
+ errorMessage.append(" No JDBC driver classes found in the
provided resources.");
+ }
+ } else if (driverResources == null) {
+ errorMessage.append(" The property 'Database Driver
Location(s)' should be set.");
+ }
+
+ throw new ProcessException(errorMessage.toString(), e);
}
try {
diff --git
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/test/java/org/apache/nifi/dbcp/DBCPServiceTest.java
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/test/java/org/apache/nifi/dbcp/DBCPServiceTest.java
index 490dba71bd..3c6e976ea3 100644
---
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/test/java/org/apache/nifi/dbcp/DBCPServiceTest.java
+++
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/test/java/org/apache/nifi/dbcp/DBCPServiceTest.java
@@ -18,13 +18,19 @@ package org.apache.nifi.dbcp;
import java.io.File;
import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.Collection;
+import java.util.Map;
import java.util.UUID;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.dbcp.utils.DBCPProperties;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.reporting.InitializationException;
@@ -205,6 +211,41 @@ public class DBCPServiceTest {
service.getDataSource().close();
}
+ @Test
+ public void testInvalidDriverDerby() throws URISyntaxException {
+ final URL driverURL =
org.apache.derby.client.ClientAutoloadedDriver.class
+ .getProtectionDomain()
+ .getCodeSource()
+ .getLocation();
+
+ final File jarFile = new File(driverURL.toURI());
+
+ runner.setProperty(service, DBCPProperties.DB_DRIVER_LOCATION,
jarFile.getAbsolutePath());
+ runner.setProperty(service, DBCPProperties.DB_DRIVERNAME,
"a.very.bad.jdbc.Driver");
+
+ final Collection<ConfigVerificationResult> verificationResults =
runner.verify(service, Map.of());
+ assertEquals(1, verificationResults.size());
+
+ final ConfigVerificationResult result =
verificationResults.stream().filter(r ->
r.getVerificationStepName().equals("Configure Data Source")).findFirst().get();
+
assertTrue(result.getExplanation().contains("org.apache.derby.client.ClientAutoloadedDriver"));
+ }
+
+ @Test
+ public void testInvalidDriverNoResource() {
+ runner.setProperty(service, DBCPProperties.DB_DRIVERNAME,
"a.very.bad.jdbc.Driver");
+
+ final Collection<ValidationResult> validationResults =
runner.validate(service);
+ assertEquals(0, validationResults.size());
+
+ AssertionFailedError thrown = assertThrows(AssertionFailedError.class,
() -> {
+ runner.enableControllerService(service);
+ });
+
+ // Verify the underlying cause is what we expect
+ assertTrue(thrown.getMessage().contains("ProcessException"));
+ assertTrue(thrown.getMessage().contains("JDBC driver class
'a.very.bad.jdbc.Driver' not found. The property 'Database Driver Location(s)'
should be set."));
+ }
+
private void assertConnectionNotNullDynamicProperty(final String
propertyName, final String propertyValue) throws SQLException {
runner.setProperty(service, propertyName, propertyValue);
diff --git
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/pom.xml
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/pom.xml
index 56a4ac976b..f9ce52f864 100644
---
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/pom.xml
+++
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/pom.xml
@@ -48,5 +48,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-dbcp-utils</artifactId>
+ <version>2.6.0-SNAPSHOT</version>
+ </dependency>
</dependencies>
</project>
diff --git
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/src/main/java/org/apache/nifi/dbcp/HikariCPConnectionPool.java
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/src/main/java/org/apache/nifi/dbcp/HikariCPConnectionPool.java
index 6c7ec2312e..58e65c7aff 100644
---
a/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/src/main/java/org/apache/nifi/dbcp/HikariCPConnectionPool.java
+++
b/nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-hikari-dbcp-service/src/main/java/org/apache/nifi/dbcp/HikariCPConnectionPool.java
@@ -32,10 +32,12 @@ import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.RequiredPermission;
import org.apache.nifi.components.resource.ResourceCardinality;
+import org.apache.nifi.components.resource.ResourceReferences;
import org.apache.nifi.components.resource.ResourceType;
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.VerifiableControllerService;
+import org.apache.nifi.dbcp.utils.DriverUtils;
import org.apache.nifi.expression.AttributeExpression;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.kerberos.KerberosUserService;
@@ -47,6 +49,7 @@ import org.apache.nifi.security.krb.KerberosLoginException;
import org.apache.nifi.security.krb.KerberosUser;
import javax.security.auth.login.LoginException;
+
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
@@ -361,16 +364,29 @@ public class HikariCPConnectionPool extends
AbstractControllerService implements
.build());
}
} catch (final Exception e) {
- String message = "Failed to configure Data Source.";
- if (e.getCause() instanceof ClassNotFoundException) {
- message += String.format(" Ensure changes to the '%s'
property are applied before verifying",
- DB_DRIVER_LOCATION.getDisplayName());
+ StringBuilder messageBuilder = new StringBuilder("Failed to
configure Data Source.");
+ verificationLogger.error(messageBuilder.toString(), e);
+
+ final String driverName =
context.getProperty(DB_DRIVERNAME).evaluateAttributeExpressions().getValue();
+ final ResourceReferences driverResources =
context.getProperty(DB_DRIVER_LOCATION).evaluateAttributeExpressions().asResources();
+
+ if (StringUtils.isNotBlank(driverName) &&
driverResources.getCount() != 0) {
+ List<String> availableDrivers =
DriverUtils.findDriverClassNames(driverResources);
+ if (!availableDrivers.isEmpty() &&
!availableDrivers.contains(driverName)) {
+ messageBuilder.append(" Driver class [%s] not found in
provided resources. Available driver classes found: %s".formatted(driverName,
String.join(", ", availableDrivers)));
+ } else if (e.getCause() instanceof ClassNotFoundException &&
availableDrivers.contains(driverName)) {
+ messageBuilder.append(" Driver Class found but not loaded:
Apply configuration before verifying.");
+ } else {
+ messageBuilder.append(" Exception:
%s".formatted(e.getMessage()));
+ }
+ } else {
+ messageBuilder.append(" No driver name specified or no driver
resources provided. Exception: %s".formatted(e.getMessage()));
}
- verificationLogger.error(message, e);
+
results.add(new ConfigVerificationResult.Builder()
.verificationStepName("Configure Data Source")
.outcome(FAILED)
- .explanation(message + ": " + e.getMessage())
+ .explanation(messageBuilder.toString())
.build());
} finally {
try {