You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@nifi.apache.org by "pvillard31 (via GitHub)" <gi...@apache.org> on 2025/06/21 20:22:09 UTC

[PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

pvillard31 opened a new pull request, #10039:
URL: https://github.com/apache/nifi/pull/10039

   # Summary
   
   [NIFI-14682](https://issues.apache.org/jira/browse/NIFI-14682) - Help user with JDBC Driver configuration in DBCP controller service
   
   The goal is to improve the `verify` method in the `Abstract` class as well as the error handling in the `getDriver` method of the controller service implementation.
   
   We cannot use a `customValidate` approach as we may be in situation where one of those situations happen:
   
   - specified resources are not loaded in the classpath
   - some specified resources are using the URL approach and we may not have the resource locally available at the time
   - we may have some weirdly packaged JARs with very custom Drivers
   
   Given the above, we don't want to take the risk of making the component invalid and unusable.
   
   Instead we improve things in two places:
   
   - the `verify()` method but we cannot expect all of the specified resources to be loaded in the classpath at the time the method is called so the best approach is to have a static analysis of whatever is available to us and provide a non exhaustive list of what drivers can be used.
   - the `getDriver()` method - in this case, resources are loaded in the classpath so we can also verify that the classes that we suspect are potential drivers could indeed be loaded as drivers.
   
   The changes have been tested with many drivers, specified in different ways (asset, directory, file, URL) with many different vendors (H2, Derby, MS SQL, Postgres, ClickHouse, Snowflake).
   
   While this does not cover all possible scenarios of how the component has been configured, it does provide a more helpful error message in most cases when the specified driver class is not correct.
   
   # Tracking
   
   Please complete the following tracking steps prior to pull request creation.
   
   ### Issue Tracking
   
   - [ ] [Apache NiFi Jira](https://issues.apache.org/jira/browse/NIFI) issue created
   
   ### Pull Request Tracking
   
   - [ ] Pull Request title starts with Apache NiFi Jira issue number, such as `NIFI-00000`
   - [ ] Pull Request commit message starts with Apache NiFi Jira issue number, as such `NIFI-00000`
   
   ### Pull Request Formatting
   
   - [ ] Pull Request based on current revision of the `main` branch
   - [ ] Pull Request refers to a feature branch with one commit containing changes
   
   # Verification
   
   Please indicate the verification steps performed prior to pull request creation.
   
   ### Build
   
   - [ ] Build completed using `mvn clean install -P contrib-check`
     - [ ] JDK 21
   
   ### Licensing
   
   - [ ] New dependencies are compatible with the [Apache License 2.0](https://apache.org/licenses/LICENSE-2.0) according to the [License Policy](https://www.apache.org/legal/resolved.html)
   - [ ] New dependencies are documented in applicable `LICENSE` and `NOTICE` files
   
   ### Documentation
   
   - [ ] Documentation formatting appears as expected in rendered files
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

Posted by "exceptionfactory (via GitHub)" <gi...@apache.org>.
exceptionfactory commented on code in PR #10039:
URL: https://github.com/apache/nifi/pull/10039#discussion_r2279905336


##########
nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/src/main/java/org/apache/nifi/dbcp/utils/DriverUtils.java:
##########
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+public class DriverUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DriverUtils.class);
+
+    /**
+     * 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> discoverDriverClassesStatic(final ResourceReferences driverResources) {

Review Comment:
   The word `Static` can be removed from this and other methods.
   ```suggestion
       public static List<String> findDriverClassNames(final ResourceReferences driverResources) {
   ```



##########
nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/src/main/java/org/apache/nifi/dbcp/utils/DriverUtils.java:
##########
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+public class DriverUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DriverUtils.class);
+
+    /**
+     * 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> discoverDriverClassesStatic(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 = scanJarForDriverClassesStatic(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> scanJarForDriverClassesStatic(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) {
+        final String lowerClassName = className.toLowerCase();
+
+        // Common patterns for JDBC driver class names
+        boolean isCandidate = lowerClassName.contains("driver")
+                || lowerClassName.contains("jdbc")
+                || className.matches(".*Driver$")
+                || className.matches(".*JdbcDriver$")
+                || className.matches(".*SQLServerDriver$");

Review Comment:
   For clarity of maintenance, I recommend creating a single regular expression pattern and declaring it as a static variable.



##########
nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPool.java:
##########
@@ -101,16 +105,30 @@ public List<ConfigVerificationResult> verify(final ConfigurationContext context,
                         .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.discoverDriverClassesStatic(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 found but ensure you apply the controller service configuration before verifying again.");

Review Comment:
   Recommend avoiding `you` in messages, perhaps something like the following
   ```suggestion
                       messageBuilder.append(" Driver Class found but not loaded: Apply configuration before verifying");
   ```



##########
nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-utils/src/main/java/org/apache/nifi/dbcp/utils/DriverUtils.java:
##########
@@ -0,0 +1,215 @@
+/*
+ * 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;
+
+public class DriverUtils {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(DriverUtils.class);
+
+    /**
+     * 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> discoverDriverClassesStatic(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 = scanJarForDriverClassesStatic(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> scanJarForDriverClassesStatic(final File jarFile) {

Review Comment:
   ```suggestion
       private static Set<String> scanJarForDriverClasses(final File jarFile) {
   ```



##########
nifi-extension-bundles/nifi-extension-utils/nifi-dbcp-base/src/main/java/org/apache/nifi/dbcp/AbstractDBCPConnectionPool.java:
##########
@@ -101,16 +105,30 @@ public List<ConfigVerificationResult> verify(final ConfigurationContext context,
                         .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.discoverDriverClassesStatic(driverResources);
+                if (!availableDrivers.isEmpty() && !availableDrivers.contains(driverName)) {
+                    messageBuilder.append(String.format(" Driver class '%s' not found in provided resources. Available driver classes found: %s.",

Review Comment:
   Recommend avoiding the trailing `.` character:
   ```suggestion
                       messageBuilder.append(String.format(" Driver class [%s] not found in provided resources. Available driver classes found: %s",
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

Posted by "pvillard31 (via GitHub)" <gi...@apache.org>.
pvillard31 commented on PR #10039:
URL: https://github.com/apache/nifi/pull/10039#issuecomment-3137032888

   I have pushed some changes to remove the checks based on naming and fairly common database types to only rely on `META-INF/services` at this point - for the verify where class loading may not have happened yet. I have improved the error messages in case the verify is used before or after configuration is applied and driver is loaded. I tested with a few large drivers including the phoenix driver that is about 150MB, all of the operations took below or around one second to complete.
   
   <img width="1065" height="656" alt="Screenshot 2025-07-30 at 18 13 06" src="https://github.com/user-attachments/assets/78e38d08-8cdb-4f17-9cf1-98bb2bac9eca" />
   <img width="1050" height="643" alt="Screenshot 2025-07-30 at 18 13 30" src="https://github.com/user-attachments/assets/9567e87a-8797-4107-87cb-30ee850f1a18" />
   <img width="1040" height="638" alt="Screenshot 2025-07-30 at 18 13 59" src="https://github.com/user-attachments/assets/ee518799-90de-4488-a383-af74ecb30d15" />
   <img width="1042" height="643" alt="Screenshot 2025-07-30 at 18 14 50" src="https://github.com/user-attachments/assets/dc191f2f-81eb-4d0a-8cdd-a73d450d4b5a" />
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

Posted by "pvillard31 (via GitHub)" <gi...@apache.org>.
pvillard31 commented on PR #10039:
URL: https://github.com/apache/nifi/pull/10039#issuecomment-3206757239

   Thanks @exceptionfactory - have made the recommended changes and switched to Derby for the unit test


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

Posted by "pvillard31 (via GitHub)" <gi...@apache.org>.
pvillard31 commented on PR #10039:
URL: https://github.com/apache/nifi/pull/10039#issuecomment-3196158269

   Done, thanks @exceptionfactory 


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

Posted by "exceptionfactory (via GitHub)" <gi...@apache.org>.
exceptionfactory commented on code in PR #10039:
URL: https://github.com/apache/nifi/pull/10039#discussion_r2288223673


##########
nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/test/java/org/apache/nifi/dbcp/DBCPServiceTest.java:
##########
@@ -205,6 +211,41 @@ public void testGetDataSourceProperties() throws SQLException {
         service.getDataSource().close();
     }
 
+    @Test
+    public void testInvalidDirverH2() throws URISyntaxException {
+        final URL driverURL = org.h2.Driver.class
+                .getProtectionDomain()
+                .getCodeSource()
+                .getLocation();

Review Comment:
   For testing purposes, using the Derby JDBC Driver looks like it would avoid this reference to H2, which otherwise appears to be unused.



##########
nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/test/java/org/apache/nifi/dbcp/DBCPServiceTest.java:
##########
@@ -205,6 +211,41 @@ public void testGetDataSourceProperties() throws SQLException {
         service.getDataSource().close();
     }
 
+    @Test
+    public void testInvalidDirverH2() throws URISyntaxException {

Review Comment:
   ```suggestion
       public void testInvalidDriverH2() throws URISyntaxException {
   ```



##########
nifi-extension-bundles/nifi-standard-services/nifi-dbcp-service-bundle/nifi-dbcp-service/src/main/java/org/apache/nifi/dbcp/DBCPConnectionPool.java:
##########
@@ -203,7 +207,33 @@ protected Driver getDriver(final String driverName, final String url) {
         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(String.format("JDBC driver class '%s' not found.", driverName));
+
+            if (!availableDrivers.isEmpty()) {
+                errorMessage.append(String.format(" Available driver classes found in resources: %s.", String.join(", ", availableDrivers)));

Review Comment:
   Minor note, but using `" Available driver classes...".formatted()` instead of `String.format()` would reduce some of the nesting, making this a bit easier to read.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] NIFI-14682 - Help user with JDBC Driver configuration in DBCP controller service [nifi]

Posted by "exceptionfactory (via GitHub)" <gi...@apache.org>.
exceptionfactory merged PR #10039:
URL: https://github.com/apache/nifi/pull/10039


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@nifi.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org