You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2023/06/14 06:59:31 UTC

[james-project] 09/28: Allow passing openjpa and underlying datasource (DBCP2) properties (#1580)

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

btellier pushed a commit to branch 3.8.x
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 534a7848183c7172d155b52ba0431f5adfc996b5
Author: Wojtek <wo...@users.noreply.github.com>
AuthorDate: Mon Jun 12 04:25:44 2023 -0400

    Allow passing openjpa and underlying datasource (DBCP2) properties (#1580)
---
 .../james/modules/data/JPAConfiguration.java       | 87 ++++++++++++++++++++--
 .../james/modules/data/JPAEntityManagerModule.java | 53 +++++++++----
 src/site/xdoc/server/config-system.xml             |  9 ++-
 3 files changed, 126 insertions(+), 23 deletions(-)

diff --git a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAConfiguration.java b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAConfiguration.java
index bed6c9041a..62419c1bb2 100644
--- a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAConfiguration.java
+++ b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAConfiguration.java
@@ -19,11 +19,17 @@
 package org.apache.james.modules.data;
 
 import static org.apache.james.modules.data.JPAConfiguration.Credential.NO_CREDENTIAL;
+import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.CUSTOM_DATASOURCE_PROPERTIES;
+import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.CUSTOM_OPENJPA_PROPERTIES;
 import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.NO_MAX_CONNECTIONS;
+import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.NO_MULTITHREADED;
 import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.NO_TEST_ON_BORROW;
 import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.NO_VALIDATION_QUERY;
 import static org.apache.james.modules.data.JPAConfiguration.ReadyToBuild.NO_VALIDATION_QUERY_TIMEOUT_SEC;
 
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 
 import org.apache.commons.lang3.StringUtils;
@@ -34,6 +40,23 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
 
 public class JPAConfiguration {
+    static final String JPA_CONNECTION_DRIVER_NAME = "openjpa.ConnectionDriverName";
+    static final String JPA_CONNECTION_USERNAME = "openjpa.ConnectionUserName";
+    static final String JPA_CONNECTION_PASSWORD = "openjpa.ConnectionPassword";
+    static final String JPA_CONNECTION_PROPERTIES = "openjpa.ConnectionProperties";
+    static final String JPA_CONNECTION_URL = "openjpa.ConnectionURL";
+    static final String JPA_MULTITHREADED = "openjpa.Multithreaded";
+    static List<String> DEFAULT_JPA_PROPERTIES = List.of(JPA_CONNECTION_DRIVER_NAME, JPA_CONNECTION_URL, JPA_MULTITHREADED, JPA_CONNECTION_USERNAME, JPA_CONNECTION_PASSWORD);
+
+    static final String DATASOURCE_TEST_ON_BORROW = "datasource.testOnBorrow";
+    static final String DATASOURCE_VALIDATION_QUERY_TIMEOUT_SEC = "datasource.validationQueryTimeoutSec";
+    static final String DATASOURCE_VALIDATION_QUERY = "datasource.validationQuery";
+    static final String DATASOURCE_MAX_TOTAL = "datasource.maxTotal";
+    static List<String> DEFAULT_DATASOURCE_PROPERTIES = List.of(DATASOURCE_TEST_ON_BORROW, DATASOURCE_VALIDATION_QUERY_TIMEOUT_SEC, DATASOURCE_VALIDATION_QUERY, DATASOURCE_MAX_TOTAL);
+
+    static {
+    }
+
     public static class Credential {
         private static final Logger LOGGER = LoggerFactory.getLogger(Credential.class);
         static final Optional<Credential> NO_CREDENTIAL = Optional.empty();
@@ -78,40 +101,50 @@ public class JPAConfiguration {
 
     public static class ReadyToBuild {
         static final Optional<Boolean> NO_TEST_ON_BORROW = Optional.empty();
+        static final Optional<Boolean> NO_MULTITHREADED = Optional.empty();
         static final Optional<Integer> NO_VALIDATION_QUERY_TIMEOUT_SEC = Optional.empty();
         static final Optional<String> NO_VALIDATION_QUERY = Optional.empty();
         static final Optional<Integer> NO_MAX_CONNECTIONS = Optional.empty();
+        static final Map<String,String> CUSTOM_OPENJPA_PROPERTIES = Map.of();
+        static final Map<String,String> CUSTOM_DATASOURCE_PROPERTIES = Map.of();
 
         private final String driverName;
         private final String driverURL;
 
         private Optional<Credential> credential;
         private Optional<Boolean> testOnBorrow;
+        private Optional<Boolean> multithreaded;
         private Optional<Integer> validationQueryTimeoutSec;
         private Optional<String> validationQuery;
         private Optional<Integer> maxConnections;
+        private Map<String,String> customDatasourceProperties;
+        private Map<String,String> customOpenjpaProperties;
 
 
         private ReadyToBuild(String driverName, String driverURL, Optional<Credential> credential,
-                            Optional<Boolean> testOnBorrow, Optional<Integer> validationQueryTimeoutSec,
-                            Optional<String> validationQuery,Optional<Integer> maxConnections
+                            Optional<Boolean> testOnBorrow, Optional<Boolean> multithreaded, Optional<Integer> validationQueryTimeoutSec,
+                            Optional<String> validationQuery,Optional<Integer> maxConnections,
+                            Map<String,String> customDatasourceProperties, Map<String,String> customOpenjpaProperties
         ) {
             this.driverName = driverName;
             this.driverURL = driverURL;
             this.credential = credential;
             this.testOnBorrow = testOnBorrow;
+            this.multithreaded = multithreaded;
             this.validationQueryTimeoutSec = validationQueryTimeoutSec;
             this.validationQuery = validationQuery;
             this.maxConnections = maxConnections;
+            this.customDatasourceProperties = customDatasourceProperties;
+            this.customOpenjpaProperties = customOpenjpaProperties;
         }
 
         public JPAConfiguration build() {
-            return new JPAConfiguration(driverName, driverURL, credential, testOnBorrow, validationQueryTimeoutSec, validationQuery, maxConnections);
+            return new JPAConfiguration(driverName, driverURL, credential, testOnBorrow, multithreaded, validationQueryTimeoutSec, validationQuery, maxConnections, customDatasourceProperties, customOpenjpaProperties);
         }
 
         public RequirePassword username(String username) {
             return password -> new ReadyToBuild(driverName, driverURL, Credential.of(username, password),
-                testOnBorrow, validationQueryTimeoutSec, validationQuery, maxConnections);
+                testOnBorrow, multithreaded, validationQueryTimeoutSec, validationQuery, maxConnections, customDatasourceProperties, customOpenjpaProperties);
         }
 
         public ReadyToBuild testOnBorrow(Boolean testOnBorrow) {
@@ -119,6 +152,11 @@ public class JPAConfiguration {
             return this;
         }
 
+        public ReadyToBuild multithreaded(Boolean multithreaded) {
+            this.multithreaded = Optional.ofNullable(multithreaded);
+            return this;
+        }
+
         public ReadyToBuild validationQueryTimeoutSec(Integer validationQueryTimeoutSec) {
             this.validationQueryTimeoutSec = Optional.ofNullable(validationQueryTimeoutSec);
             return this;
@@ -133,6 +171,19 @@ public class JPAConfiguration {
             this.maxConnections = Optional.ofNullable(maxConnections);
             return this;
         }
+
+        public ReadyToBuild setCustomDatasourceProperties(Map<String, String> customDatasourceProperties) {
+            this.customDatasourceProperties = new HashMap<>(customDatasourceProperties);
+            DEFAULT_DATASOURCE_PROPERTIES.forEach(this.customDatasourceProperties::remove);
+            return this;
+        }
+
+        public ReadyToBuild setCustomOpenjpaProperties(Map<String, String> customOpenjpaProperties) {
+            this.customOpenjpaProperties = customOpenjpaProperties;
+            DEFAULT_JPA_PROPERTIES.forEach(this.customOpenjpaProperties::remove);
+            return this;
+        }
+
     }
 
     @FunctionalInterface
@@ -141,21 +192,25 @@ public class JPAConfiguration {
     }
 
     public static RequireDriverName builder() {
-        return driverName -> driverURL -> new ReadyToBuild(driverName, driverURL, NO_CREDENTIAL, NO_TEST_ON_BORROW,
-            NO_VALIDATION_QUERY_TIMEOUT_SEC, NO_VALIDATION_QUERY, NO_MAX_CONNECTIONS);
+        return driverName -> driverURL -> new ReadyToBuild(driverName, driverURL, NO_CREDENTIAL, NO_TEST_ON_BORROW, NO_MULTITHREADED,
+            NO_VALIDATION_QUERY_TIMEOUT_SEC, NO_VALIDATION_QUERY, NO_MAX_CONNECTIONS, CUSTOM_DATASOURCE_PROPERTIES, CUSTOM_OPENJPA_PROPERTIES);
     }
 
     private final String driverName;
     private final String driverURL;
     private final Optional<Boolean> testOnBorrow;
+    private final Optional<Boolean> multithreaded;
     private final Optional<Integer> validationQueryTimeoutSec;
     private final Optional<Credential> credential;
     private final Optional<String> validationQuery;
     private final Optional<Integer> maxConnections;
+    private Map<String,String> customDatasourceProperties;
+    private Map<String,String> customOpenjpaProperties;
+
 
     @VisibleForTesting
-    JPAConfiguration(String driverName, String driverURL, Optional<Credential> credential, Optional<Boolean> testOnBorrow,
-                     Optional<Integer> validationQueryTimeoutSec, Optional<String> validationQuery, Optional<Integer> maxConnections) {
+    JPAConfiguration(String driverName, String driverURL, Optional<Credential> credential, Optional<Boolean> testOnBorrow, Optional<Boolean> multithreaded,
+                     Optional<Integer> validationQueryTimeoutSec, Optional<String> validationQuery, Optional<Integer> maxConnections, Map<String,String> customDatasourceProperties, Map<String,String> customOpenjpaProperties) {
         Preconditions.checkNotNull(driverName, "driverName cannot be null");
         Preconditions.checkNotNull(driverURL, "driverURL cannot be null");
         validationQueryTimeoutSec.ifPresent(timeoutInSec ->
@@ -167,9 +222,13 @@ public class JPAConfiguration {
         this.driverURL = driverURL;
         this.credential = credential;
         this.testOnBorrow = testOnBorrow;
+        this.multithreaded = multithreaded;
         this.validationQueryTimeoutSec = validationQueryTimeoutSec;
         this.validationQuery = validationQuery;
         this.maxConnections = maxConnections;
+        this.customDatasourceProperties = customDatasourceProperties;
+        this.customOpenjpaProperties = customOpenjpaProperties;
+
     }
 
     public String getDriverName() {
@@ -184,6 +243,10 @@ public class JPAConfiguration {
         return testOnBorrow;
     }
 
+    public Optional<Boolean> isMultithreaded() {
+        return multithreaded;
+    }
+
     public Optional<Integer> getValidationQueryTimeoutSec() {
         return validationQueryTimeoutSec;
     }
@@ -196,6 +259,14 @@ public class JPAConfiguration {
         return credential;
     }
 
+    public Map<String, String> getCustomOpenjpaProperties() {
+        return customOpenjpaProperties;
+    }
+
+    public Map<String, String> getCustomDatasourceProperties() {
+        return customDatasourceProperties;
+    }
+
 
     public Optional<Integer> getMaxConnections() {
         return maxConnections;
diff --git a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAEntityManagerModule.java b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAEntityManagerModule.java
index 8024f4f8b7..efcc300c11 100644
--- a/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAEntityManagerModule.java
+++ b/server/container/guice/jpa-common/src/main/java/org/apache/james/modules/data/JPAEntityManagerModule.java
@@ -21,7 +21,10 @@ package org.apache.james.modules.data;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import javax.inject.Singleton;
 import javax.persistence.EntityManagerFactory;
@@ -41,25 +44,30 @@ public class JPAEntityManagerModule extends AbstractModule {
     public EntityManagerFactory provideEntityManagerFactory(JPAConfiguration jpaConfiguration) {
         HashMap<String, String> properties = new HashMap<>();
 
-        properties.put("openjpa.ConnectionDriverName", jpaConfiguration.getDriverName());
-        properties.put("openjpa.ConnectionURL", jpaConfiguration.getDriverURL());
+        properties.put(JPAConfiguration.JPA_CONNECTION_DRIVER_NAME, jpaConfiguration.getDriverName());
+        properties.put(JPAConfiguration.JPA_CONNECTION_URL, jpaConfiguration.getDriverURL());
         jpaConfiguration.getCredential()
             .ifPresent(credential -> {
-                properties.put("openjpa.ConnectionUserName", credential.getUsername());
-                properties.put("openjpa.ConnectionPassword", credential.getPassword());
+                properties.put(JPAConfiguration.JPA_CONNECTION_USERNAME, credential.getUsername());
+                properties.put(JPAConfiguration.JPA_CONNECTION_PASSWORD, credential.getPassword());
             });
 
-        List<String> connectionFactoryProperties = new ArrayList<>();
-        connectionFactoryProperties.add("TestOnBorrow=" + jpaConfiguration.isTestOnBorrow());
+        List<String> connectionProperties = new ArrayList<>();
+        jpaConfiguration.isTestOnBorrow().ifPresent(testOnBorrow -> connectionProperties.add("TestOnBorrow=" + testOnBorrow));
         jpaConfiguration.getValidationQueryTimeoutSec()
-            .ifPresent(timeoutSecond -> connectionFactoryProperties.add("ValidationTimeout=" + timeoutSecond * 1000));
+            .ifPresent(timeoutSecond -> connectionProperties.add("ValidationTimeout=" + timeoutSecond * 1000));
         jpaConfiguration.getValidationQuery()
-            .ifPresent(validationQuery -> connectionFactoryProperties.add("ValidationSQL='" + validationQuery + "'"));
-        jpaConfiguration.getMaxConnections().ifPresent(maxConnections ->
-                connectionFactoryProperties.add("MaxTotal=" + maxConnections)
-        );
+            .ifPresent(validationQuery -> connectionProperties.add("ValidationSQL='" + validationQuery + "'"));
+        jpaConfiguration.getMaxConnections()
+                .ifPresent(maxConnections -> connectionProperties.add("MaxTotal=" + maxConnections));
 
-        properties.put("openjpa.ConnectionFactoryProperties", Joiner.on(", ").join(connectionFactoryProperties));
+        connectionProperties.addAll(jpaConfiguration.getCustomDatasourceProperties().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.toList()));
+        properties.put(JPAConfiguration.JPA_CONNECTION_PROPERTIES, Joiner.on(",").join(connectionProperties));
+        properties.putAll(jpaConfiguration.getCustomOpenjpaProperties());
+
+        if (jpaConfiguration.isMultithreaded().isPresent()) {
+            properties.put(JPAConfiguration.JPA_MULTITHREADED, jpaConfiguration.isMultithreaded().get().toString());
+        }
 
         return Persistence.createEntityManagerFactory("Global", properties);
     }
@@ -68,15 +76,34 @@ public class JPAEntityManagerModule extends AbstractModule {
     @Singleton
     JPAConfiguration provideConfiguration(PropertiesProvider propertiesProvider) throws FileNotFoundException, ConfigurationException {
         Configuration dataSource = propertiesProvider.getConfiguration("james-database");
+
+        Map<String, String> openjpaProperties = getKeysForPrefix(dataSource, "openjpa", false);
+        Map<String, String> datasourceProperties = getKeysForPrefix(dataSource, "datasource", true);
+
+
         return JPAConfiguration.builder()
                 .driverName(dataSource.getString("database.driverClassName"))
                 .driverURL(dataSource.getString("database.url"))
                 .testOnBorrow(dataSource.getBoolean("datasource.testOnBorrow", false))
                 .validationQueryTimeoutSec(dataSource.getInteger("datasource.validationQueryTimeoutSec", null))
                 .validationQuery(dataSource.getString("datasource.validationQuery", null))
-                .maxConnections(dataSource.getInteger("datasource.maxTotal",null))
+                .maxConnections(dataSource.getInteger("datasource.maxTotal", null))
+                .multithreaded(dataSource.getBoolean(JPAConfiguration.JPA_MULTITHREADED, true))
                 .username(dataSource.getString("database.username"))
                 .password(dataSource.getString("database.password"))
+                .setCustomOpenjpaProperties(openjpaProperties)
+                .setCustomDatasourceProperties(datasourceProperties)
                 .build();
     }
+
+    private static Map<String, String> getKeysForPrefix(Configuration dataSource, String prefix, boolean stripPrefix) {
+        Iterator<String> keys = dataSource.getKeys(prefix);
+        Map<String, String> properties = new HashMap<>();
+        while (keys.hasNext()) {
+            String key = keys.next();
+            String propertyKey = stripPrefix ? key.replace(prefix + ".", "") : key;
+            properties.put(propertyKey, dataSource.getString(key));
+        }
+        return properties;
+    }
 }
diff --git a/src/site/xdoc/server/config-system.xml b/src/site/xdoc/server/config-system.xml
index 9d6602a1b8..55650a198a 100644
--- a/src/site/xdoc/server/config-system.xml
+++ b/src/site/xdoc/server/config-system.xml
@@ -88,14 +88,19 @@
                     <dd>true or false - Use streaming for Blobs. This is only supported on a limited set of databases atm. You
                         should check if its supported by your DB before enable it. See <a href="http://openjpa.apache.org/builds/latest/docs/manual/ref_guide_mapping_jpa.html">http://openjpa.apache.org/builds/latest/docs/manual/ref_guide_mapping_jpa.html</a> (#7.11. LOB Streaming).</dd>
                 </dl>
-                <p>The JPA datasource handling is delegated to commons-dbcp, some of dbcp properties can be configured through james-database.properties. The corresponding definitions and default values can be found in the <a href="https://commons.apache.org/proper/commons-dbcp/configuration.html">dbcp documentation</a></p>
+
+                <p>NOTE: all<code>openjpa.*</code> properties will be passed to OpenJPA library as configuration See <a href="https://openjpa.apache.org/builds/3.2.2/apache-openjpa/docs/ref_guide_conf_openjpa.html">https://openjpa.apache.org/builds/3.2.2/apache-openjpa/docs/ref_guide_conf_openjpa.html</a> for a complete list </p>
+
+                <p>The JPA datasource and connection pooling can be delegated to commons-dbcp2 - the library dependency has to be present in classpath for it to be enabled. dbcp2 properties can be configured through james-database.properties - all properties prefixed with <code>datasource.*</code> will be passed to DBCP2 library via <code>openjpa.ConnectionProperties</code> OpenJPA property. The corresponding definitions and default values can be found in the <a href="https://commons.apa [...]
                 <ul>
                     <li>datasource.testOnBorrow</li>
                     <li>datasource.validationQueryTimeoutSec</li>
                     <li>datasource.validationQuery</li>
+                    <li>datasource.initialSize</li>
+                    <li>datasource.minIdle</li>
                     <li>datasource.maxTotal</li>
-
                 </ul>
+                <p>NOTE: Connection poolling requires that <code>openjpa.Multithreaded</code> property is set to <code>true</code> (default)</p>
                 <p>Note for postgresql databases: Add <code>standard_conforming_strings=off</code> to your postgresql.xml, otherwise you
                     will get <code>""Invalid escape string Hint: Escape string must be empty or one character. {prepstmnt 174928937
                     SELECT t0.mailbox_id, t0.mailbox_highest_modseq, t0.mailbox_last_uid, t0.mailbox_name, t0.mailbox_namespace,


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org