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

ashishvijaywargiya pushed a commit to branch secret-manager
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git

commit 9e5308193ba2a72d91fce42baf11afbcad6e28dc
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Sun Jun 7 17:46:13 2026 +0530

    Adding support of Secret Manager framework support in Apache OFBiz. For 
various providers support we can add plugins components.
---
 build.gradle                                       |  7 +++
 dependencies.gradle                                |  1 +
 .../ofbiz/base/secret/FileBasedSecretProvider.java | 43 +++++++++++++++
 .../apache/ofbiz/base/secret/SecretProvider.java   | 49 +++++++++++++++++
 .../ofbiz/base/secret/SecretProviderFactory.java   | 63 ++++++++++++++++++++++
 .../ofbiz/entity/config/model/EntityConfig.java    | 13 ++---
 .../entity/connection/DBCPConnectionFactory.java   |  5 ++
 7 files changed, 175 insertions(+), 6 deletions(-)

diff --git a/build.gradle b/build.gradle
index 09c4349187..fbc08af7d0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -168,6 +168,13 @@ tasks.withType(JavaCompile) {
 // Enables Zip larger than 4 GB and more than 65535 entries
 tasks.withType(Zip) { zip64 = true }
 
+// Multiple SecretProvider plugins each contribute the same META-INF/services/ 
filename.
+// Only one provider should be enabled at a time (via ofbiz-component.xml 
enabled="true/false").
+// FIRST keeps the alphabetically first file during transitional states where 
two are temporarily enabled.
+processResources {
+    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+}
+
 // Only used for release branches
 def getCurrentGitBranch() {
     return "git branch --show-current".execute().text.trim()
diff --git a/dependencies.gradle b/dependencies.gradle
index 102c3f1251..adf88d33ca 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -98,6 +98,7 @@ dependencies {
     runtimeOnly 'org.apache.axis2:axis2-transport-http:1.8.2'
     runtimeOnly 'org.apache.axis2:axis2-transport-local:1.8.2'
     runtimeOnly 'com.h2database:h2:2.4.240'
+    runtimeOnly 'com.mysql:mysql-connector-j:8.4.0'
     runtimeOnly 'org.apache.geronimo.specs:geronimo-jaxrpc_1.1_spec:2.1'
     runtimeOnly 'org.apache.logging.log4j:log4j-1.2-api:2.25.4' // for 
external jars using the old log4j1.2: routes logging to log4j 2
     runtimeOnly 'org.apache.logging.log4j:log4j-jul:2.25.4' // for external 
jars using the java.util.logging: routes logging to log4j 2
diff --git 
a/framework/base/src/main/java/org/apache/ofbiz/base/secret/FileBasedSecretProvider.java
 
b/framework/base/src/main/java/org/apache/ofbiz/base/secret/FileBasedSecretProvider.java
new file mode 100644
index 0000000000..2879830d5e
--- /dev/null
+++ 
b/framework/base/src/main/java/org/apache/ofbiz/base/secret/FileBasedSecretProvider.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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.ofbiz.base.secret;
+
+import org.apache.ofbiz.base.util.GeneralException;
+import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.UtilValidate;
+
+/**
+ * Default {@link SecretProvider} implementation that resolves secrets from
+ * {@code framework/base/config/passwords.properties}.
+ *
+ * <p>This preserves full backward compatibility with the existing
+ * {@code jdbc-password-lookup} mechanism in {@code entityengine.xml}.
+ * No configuration is required to use this implementation.</p>
+ */
+public final class FileBasedSecretProvider implements SecretProvider {
+
+    @Override
+    public String getSecret(String key) throws GeneralException {
+        String value = UtilProperties.getPropertyValue("passwords", key);
+        if (UtilValidate.isEmpty(value)) {
+            throw new GeneralException("Secret key '" + key + "' not found in 
passwords.properties");
+        }
+        return value;
+    }
+}
diff --git 
a/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProvider.java 
b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProvider.java
new file mode 100644
index 0000000000..11b1f86851
--- /dev/null
+++ 
b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProvider.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * 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.ofbiz.base.secret;
+
+import org.apache.ofbiz.base.util.GeneralException;
+
+/**
+ * SPI for resolving secrets and credentials (e.g. database passwords, API 
keys).
+ *
+ * <p>Implementations must be thread-safe. Register a custom implementation via
+ * Java's {@link java.util.ServiceLoader} by providing a file named
+ * {@code META-INF/services/org.apache.ofbiz.base.secret.SecretProvider} in
+ * your plugin JAR containing the fully-qualified class name of your
+ * implementation. If no custom implementation is registered,
+ * {@link FileBasedSecretProvider} is used as the default, which resolves
+ * secrets from {@code framework/base/config/passwords.properties}.</p>
+ *
+ * <p>Example entry in a vault plugin's service descriptor:</p>
+ * <pre>
+ *   org.example.ofbiz.vault.AwsSecretsManagerProvider
+ * </pre>
+ */
+public interface SecretProvider {
+
+    /**
+     * Returns the secret value for the given key.
+     *
+     * @param key the identifier for the secret (e.g. {@code 
"jdbc-password.mydb"})
+     * @return the resolved secret value, never {@code null} or empty
+     * @throws GeneralException if the secret cannot be found or an error 
occurs during resolution
+     */
+    String getSecret(String key) throws GeneralException;
+}
diff --git 
a/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProviderFactory.java
 
b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProviderFactory.java
new file mode 100644
index 0000000000..9d8ec088ac
--- /dev/null
+++ 
b/framework/base/src/main/java/org/apache/ofbiz/base/secret/SecretProviderFactory.java
@@ -0,0 +1,63 @@
+/*******************************************************************************
+ * 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.ofbiz.base.secret;
+
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import org.apache.ofbiz.base.lang.ThreadSafe;
+import org.apache.ofbiz.base.util.Debug;
+
+/**
+ * Factory that provides the active {@link SecretProvider} instance.
+ *
+ * <p>On first access the factory uses Java's {@link ServiceLoader} to discover
+ * a custom {@link SecretProvider} registered in any plugin's
+ * {@code META-INF/services/org.apache.ofbiz.base.secret.SecretProvider} file.
+ * If none is found, {@link FileBasedSecretProvider} is used automatically,
+ * preserving backward compatibility with {@code passwords.properties}.</p>
+ *
+ * <p>This mirrors the pattern already used by
+ * {@link org.apache.ofbiz.security.SecurityFactory}.</p>
+ */
+@ThreadSafe
+public final class SecretProviderFactory {
+
+    private static final String MODULE = SecretProviderFactory.class.getName();
+
+    private static final SecretProvider INSTANCE = loadProvider();
+
+    private static SecretProvider loadProvider() {
+        Iterator<SecretProvider> it = 
ServiceLoader.load(SecretProvider.class).iterator();
+        if (it.hasNext()) {
+            SecretProvider provider = it.next();
+            Debug.logInfo("SecretProvider: using custom implementation " + 
provider.getClass().getName(), MODULE);
+            return provider;
+        }
+        Debug.logInfo("SecretProvider: no custom implementation found, using 
FileBasedSecretProvider", MODULE);
+        return new FileBasedSecretProvider();
+    }
+
+    /** Returns the active {@link SecretProvider} instance. */
+    public static SecretProvider getInstance() {
+        return INSTANCE;
+    }
+
+    private SecretProviderFactory() { }
+}
diff --git 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java
 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java
index 1861e176cf..ff65892e7a 100644
--- 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java
+++ 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/config/model/EntityConfig.java
@@ -26,8 +26,9 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.ofbiz.base.lang.ThreadSafe;
+import org.apache.ofbiz.base.secret.SecretProviderFactory;
 import org.apache.ofbiz.base.util.Debug;
-import org.apache.ofbiz.base.util.UtilProperties;
+import org.apache.ofbiz.base.util.GeneralException;
 import org.apache.ofbiz.base.util.UtilURL;
 import org.apache.ofbiz.base.util.UtilXml;
 import org.apache.ofbiz.entity.GenericEntityConfException;
@@ -353,12 +354,12 @@ public final class EntityConfig {
                     + inlineJdbcElement.getLineNumber());
         }
         String key = "jdbc-password.".concat(jdbcPasswordLookup);
-        jdbcPassword = UtilProperties.getPropertyValue("passwords", key);
-        if (jdbcPassword.isEmpty()) {
-            throw new GenericEntityConfException("'" + key + "' property not 
found in passwords.properties file for inline-jdbc element, line: "
-                    + inlineJdbcElement.getLineNumber());
+        try {
+            return SecretProviderFactory.getInstance().getSecret(key);
+        } catch (GeneralException e) {
+            throw new GenericEntityConfException("Secret not found for key '" 
+ key + "' for inline-jdbc element, line: "
+                    + inlineJdbcElement.getLineNumber() + " - " + 
e.getMessage());
         }
-        return jdbcPassword;
     }
 
     /** Returns the <code>&lt;datasource&gt;</code> child elements as a 
<code>Map</code>. */
diff --git 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
index 45ffe2ef11..98e2759d5b 100644
--- 
a/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
+++ 
b/framework/entity/src/main/java/org/apache/ofbiz/entity/connection/DBCPConnectionFactory.java
@@ -152,6 +152,11 @@ public class DBCPConnectionFactory implements 
ConnectionFactory {
 
         GenericObjectPool<PoolableConnection> pool = new 
GenericObjectPool<>(factory, poolConfig);
         factory.setPool(pool);
+        try {
+            pool.preparePool();
+        } catch (Exception e) {
+            Debug.logWarning("Could not pre-warm connection pool: " + 
e.getMessage(), MODULE);
+        }
 
         mds = new DebugManagedDataSource<>(pool, 
xacf.getTransactionRegistry());
         mds.setAccessToUnderlyingConnectionAllowed(true);

Reply via email to