diff --git a/core/build.gradle b/core/build.gradle index 624b2feca8..d77c1186d4 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -218,7 +218,6 @@ dependencies { compile deps['org.bouncycastle:bcpg-jdk15on'] testCompile deps['org.bouncycastle:bcpkix-jdk15on'] compile deps['org.bouncycastle:bcprov-jdk15on'] - compile deps['org.hibernate:hibernate-core'] compile deps['org.joda:joda-money'] compile deps['org.json:json'] testCompile deps['org.mortbay.jetty:jetty'] @@ -254,7 +253,7 @@ dependencies { testCompile deps['org.hamcrest:hamcrest-all'] testCompile deps['org.hamcrest:hamcrest-core'] testCompile deps['org.hamcrest:hamcrest-library'] - compile deps['org.hibernate:hibernate-core'] + compile deps['org.hibernate:hibernate-hikaricp'] testCompile deps['junit:junit'] testCompile deps['org.mockito:mockito-core'] runtime deps['org.postgresql:postgresql'] diff --git a/core/src/main/java/google/registry/config/RegistryConfig.java b/core/src/main/java/google/registry/config/RegistryConfig.java index 4100c7f3d5..3518f94670 100644 --- a/core/src/main/java/google/registry/config/RegistryConfig.java +++ b/core/src/main/java/google/registry/config/RegistryConfig.java @@ -112,7 +112,7 @@ public static String provideLogoFilename(RegistryConfigSettings config) { } /** - * The product name of this specific registry. Used throughout the registrar console. + * The product name of this specific registry. Used throughout the registrar console. * * @see google.registry.ui.server.registrar.ConsoleUiAction */ @@ -123,11 +123,11 @@ public static String provideProductName(RegistryConfigSettings config) { } /** - * Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of + * Returns the roid suffix to be used for the roids of all contacts and hosts. E.g. a value of * "ROID" would end up creating roids that look like "ABC123-ROID". * * @see - * Extensible Provisioning Protocol (EPP) Repository Identifiers + * Extensible Provisioning Protocol (EPP) Repository Identifiers */ @Provides @Config("contactAndHostRoidSuffix") @@ -136,7 +136,7 @@ public static String provideContactAndHostRoidSuffix(RegistryConfigSettings conf } /** - * The e-mail address for questions about integrating with the registry. Used in the + * The e-mail address for questions about integrating with the registry. Used in the * "contact-us" section of the registrar console. * * @see google.registry.ui.server.registrar.ConsoleUiAction @@ -148,7 +148,7 @@ public static String provideIntegrationEmail(RegistryConfigSettings config) { } /** - * The e-mail address for general support. Used in the "contact-us" section of the registrar + * The e-mail address for general support. Used in the "contact-us" section of the registrar * console. * * @see google.registry.ui.server.registrar.ConsoleUiAction @@ -160,7 +160,7 @@ public static String provideSupportEmail(RegistryConfigSettings config) { } /** - * The "From" e-mail address for announcements. Used in the "contact-us" section of the + * The "From" e-mail address for announcements. Used in the "contact-us" section of the * registrar console. * * @see google.registry.ui.server.registrar.ConsoleUiAction @@ -172,7 +172,7 @@ public static String provideAnnouncementsEmail(RegistryConfigSettings config) { } /** - * The contact phone number. Used in the "contact-us" section of the registrar console. + * The contact phone number. Used in the "contact-us" section of the registrar console. * * @see google.registry.ui.server.registrar.ConsoleUiAction */ @@ -1040,8 +1040,8 @@ public static Duration provideMetricsWriteInterval(RegistryConfigSettings config } /** - * The global automatic transfer length for contacts. After this amount of time has - * elapsed, the transfer is automatically approved. + * The global automatic transfer length for contacts. After this amount of time has elapsed, the + * transfer is automatically approved. * * @see google.registry.flows.contact.ContactTransferRequestFlow */ @@ -1196,7 +1196,7 @@ static RegistryConfigSettings provideRegistryConfigSettings() { /** * Provides the OAuth scopes that authentication logic should detect on access tokens. * - *

This list should be a superset of the required OAuth scope set provided below. Note that + *

This list should be a superset of the required OAuth scope set provided below. Note that * ideally, this setting would not be required and all scopes on an access token would be * detected automatically, but that is not the case due to the way {@code OAuthService} works. * @@ -1297,9 +1297,7 @@ public static String provideRdapTosStaticUrl(RegistryConfigSettings config) { } } - /** - * Returns the App Engine project ID, which is based off the environment name. - */ + /** Returns the App Engine project ID, which is based off the environment name. */ public static String getProjectId() { return CONFIG_SETTINGS.get().appEngine.projectId; } @@ -1451,20 +1449,51 @@ public static String getDefaultRegistrarWhoisServer() { return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer; } - /** - * Returns the number of {@code EppResourceIndex} buckets to be used. - */ + /** Returns the number of {@code EppResourceIndex} buckets to be used. */ public static int getEppResourceIndexBucketCount() { return CONFIG_SETTINGS.get().datastore.eppResourceIndexBucketsNum; } - /** - * Returns the base retry duration that gets doubled after each failure within {@code Ofy}. - */ + /** Returns the base retry duration that gets doubled after each failure within {@code Ofy}. */ public static Duration getBaseOfyRetryDuration() { return Duration.millis(CONFIG_SETTINGS.get().datastore.baseOfyRetryMillis); } + /** Returns the default database transaction isolation. */ + public static String getHibernateConnectionIsolation() { + return CONFIG_SETTINGS.get().hibernate.connectionIsolation; + } + + /** Returns true if hibernate.show_sql is enabled. */ + public static String getHibernateLogSqlQueries() { + return CONFIG_SETTINGS.get().hibernate.logSqlQueries; + } + + /** Returns true if schema modification is allowed. */ + public static String getHibernateHbm2ddlAuto() { + return CONFIG_SETTINGS.get().hibernate.hbm2ddlAuto; + } + + /** Returns the connection timeout for HikariCP. */ + public static String getHibernateHikariConnectionTimeout() { + return CONFIG_SETTINGS.get().hibernate.hikariConnectionTimeout; + } + + /** Returns the minimum idle connections for HikariCP. */ + public static String getHibernateHikariMinimumIdle() { + return CONFIG_SETTINGS.get().hibernate.hikariMinimumIdle; + } + + /** Returns the maximum pool size for HikariCP. */ + public static String getHibernateHikariMaximumPoolSize() { + return CONFIG_SETTINGS.get().hibernate.hikariMaximumPoolSize; + } + + /** Returns the idle timeout for HikariCP. */ + public static String getHibernateHikariIdleTimeout() { + return CONFIG_SETTINGS.get().hibernate.hikariIdleTimeout; + } + /** Returns the roid suffix to be used for the roids of all contacts and hosts. */ public static String getContactAndHostRoidSuffix() { return CONFIG_SETTINGS.get().registryPolicy.contactAndHostRoidSuffix; diff --git a/core/src/main/java/google/registry/config/RegistryConfigSettings.java b/core/src/main/java/google/registry/config/RegistryConfigSettings.java index 7392d341d4..3d802cb591 100644 --- a/core/src/main/java/google/registry/config/RegistryConfigSettings.java +++ b/core/src/main/java/google/registry/config/RegistryConfigSettings.java @@ -25,6 +25,7 @@ public class RegistryConfigSettings { public CredentialOAuth credentialOAuth; public RegistryPolicy registryPolicy; public Datastore datastore; + public Hibernate hibernate; public CloudDns cloudDns; public Caching caching; public IcannReporting icannReporting; @@ -105,6 +106,17 @@ public static class Datastore { public int baseOfyRetryMillis; } + /** Configuration for Hibernate. */ + public static class Hibernate { + public String connectionIsolation; + public String logSqlQueries; + public String hbm2ddlAuto; + public String hikariConnectionTimeout; + public String hikariMinimumIdle; + public String hikariMaximumPoolSize; + public String hikariIdleTimeout; + } + /** Configuration for Apache Beam (Cloud Dataflow). */ public static class Beam { public String defaultJobZone; diff --git a/core/src/main/java/google/registry/config/files/default-config.yaml b/core/src/main/java/google/registry/config/files/default-config.yaml index 09f7ec9dd2..cc41f28b4f 100644 --- a/core/src/main/java/google/registry/config/files/default-config.yaml +++ b/core/src/main/java/google/registry/config/files/default-config.yaml @@ -191,6 +191,26 @@ datastore: # doubles after each failure). baseOfyRetryMillis: 100 +hibernate: + # Make 'SERIALIZABLE' the default isolation level to ensure correctness. + # + # Entities that are never involved in multi-table transactions may use optimistic + # locks and a less strict isolation level. We may lower individual transaction's + # isolation level using a framework-dependent method. + # + # Alternatively, if a use case calls for, we may also use a lower isolation level + # but lock tables explicitly, either using framework-dependent API, or execute + # "select table for update" statements directly. + connectionIsolation: TRANSACTION_SERIALIZABLE + # Whether to log all SQL queries to App Engine logs. Overridable at runtime. + logSqlQueries: false + + # Connection pool configurations. + hikariConnectionTimeout: 20000 + hikariMinimumIdle: 0 + hikariMaximumPoolSize: 20 + hikariIdleTimeout: 300000 + cloudDns: # Set both properties to null in Production. # The root url for the Cloud DNS API. Set this to a non-null value to diff --git a/core/src/main/java/google/registry/persistence/EntityManagerFactoryProvider.java b/core/src/main/java/google/registry/persistence/EntityManagerFactoryProvider.java new file mode 100644 index 0000000000..ed61e89ac7 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/EntityManagerFactoryProvider.java @@ -0,0 +1,67 @@ +// Copyright 2019 The Nomulus Authors. All Rights Reserved. +// +// Licensed 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 google.registry.persistence; + +import static google.registry.config.RegistryConfig.getHibernateConnectionIsolation; +import static google.registry.config.RegistryConfig.getHibernateHikariConnectionTimeout; +import static google.registry.config.RegistryConfig.getHibernateHikariIdleTimeout; +import static google.registry.config.RegistryConfig.getHibernateHikariMaximumPoolSize; +import static google.registry.config.RegistryConfig.getHibernateHikariMinimumIdle; +import static google.registry.config.RegistryConfig.getHibernateLogSqlQueries; + +import com.google.common.collect.ImmutableMap; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; +import org.hibernate.cfg.Environment; + +/** Factory class to provide {@link EntityManagerFactory} instance. */ +public class EntityManagerFactoryProvider { + // This name must be the same as the one defined in persistence.xml. + public static final String PERSISTENCE_UNIT_NAME = "nomulus"; + public static final String HIKARI_CONNECTION_TIMEOUT = "hibernate.hikari.connectionTimeout"; + public static final String HIKARI_MINIMUM_IDLE = "hibernate.hikari.minimumIdle"; + public static final String HIKARI_MAXIMUM_POOL_SIZE = "hibernate.hikari.maximumPoolSize"; + public static final String HIKARI_IDLE_TIMEOUT = "hibernate.hikari.idleTimeout"; + + private static ImmutableMap getDefaultProperties() { + ImmutableMap.Builder properties = ImmutableMap.builder(); + + properties.put(Environment.DRIVER, "org.postgresql.Driver"); + properties.put( + Environment.CONNECTION_PROVIDER, + "org.hibernate.hikaricp.internal.HikariCPConnectionProvider"); + // Whether to automatically validate and export schema DDL to the database when the + // SessionFactory is created. Setting it to 'none' to turn off the feature. + properties.put(Environment.HBM2DDL_AUTO, "none"); + + properties.put(Environment.ISOLATION, getHibernateConnectionIsolation()); + properties.put(Environment.SHOW_SQL, getHibernateLogSqlQueries()); + properties.put(HIKARI_CONNECTION_TIMEOUT, getHibernateHikariConnectionTimeout()); + properties.put(HIKARI_MINIMUM_IDLE, getHibernateHikariMinimumIdle()); + properties.put(HIKARI_MAXIMUM_POOL_SIZE, getHibernateHikariMaximumPoolSize()); + properties.put(HIKARI_IDLE_TIMEOUT, getHibernateHikariIdleTimeout()); + return properties.build(); + } + + /** Constructs the {@link EntityManagerFactory} instance. */ + public static EntityManagerFactory create(String jdbcUrl, String username, String password) { + ImmutableMap.Builder properties = ImmutableMap.builder(); + properties.putAll(getDefaultProperties()); + properties.put(Environment.URL, jdbcUrl); + properties.put(Environment.USER, username); + properties.put(Environment.PASS, password); + return Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME, properties.build()); + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml new file mode 100644 index 0000000000..43f31a9ce9 --- /dev/null +++ b/core/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,28 @@ + + + + + Persistence unit for the Nomulus Cloud SQL database. + + org.hibernate.jpa.HibernatePersistenceProvider + + + google.registry.model.domain.DomainBase + google.registry.schema.tmch.ClaimsList + + + NONE + + diff --git a/core/src/test/java/google/registry/persistence/EntityManagerFactoryProviderTest.java b/core/src/test/java/google/registry/persistence/EntityManagerFactoryProviderTest.java new file mode 100644 index 0000000000..e19a496176 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/EntityManagerFactoryProviderTest.java @@ -0,0 +1,55 @@ +// Copyright 2019 The Nomulus Authors. All Rights Reserved. +// +// Licensed 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 google.registry.persistence; + +import static com.google.common.truth.Truth.assertThat; + +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.testcontainers.containers.PostgreSQLContainer; + +/** Unit tests for {@link EntityManagerFactoryProvider}. */ +@RunWith(JUnit4.class) +public class EntityManagerFactoryProviderTest { + @Rule public PostgreSQLContainer database = new PostgreSQLContainer(); + + private EntityManagerFactory emf; + + @Before + public void init() { + emf = + EntityManagerFactoryProvider.create( + database.getJdbcUrl(), database.getUsername(), database.getPassword()); + } + + @After + public void destroy() { + emf.close(); + emf = null; + } + + @Test + public void testConnectToDatabase_success() { + EntityManager em = emf.createEntityManager(); + assertThat(em.isOpen()).isTrue(); + em.close(); + } +} diff --git a/dependencies.gradle b/dependencies.gradle index d98b98fb22..e46608106f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -115,7 +115,7 @@ ext { 'org.hamcrest:hamcrest-all:1.3', 'org.hamcrest:hamcrest-core:1.3', 'org.hamcrest:hamcrest-library:1.3', - 'org.hibernate:hibernate-core:5.4.4.Final', + 'org.hibernate:hibernate-hikaricp:5.4.4.Final', 'org.joda:joda-money:0.10.0', 'org.json:json:20160810', 'org.mockito:mockito-core:2.25.0', @@ -125,8 +125,8 @@ ext { 'org.seleniumhq.selenium:selenium-chrome-driver:3.141.59', 'org.seleniumhq.selenium:selenium-java:3.141.59', 'org.seleniumhq.selenium:selenium-remote-driver:3.141.59', + 'org.testcontainers:postgresql:1.11.3', 'org.testcontainers:selenium:1.10.7', - 'org.testcontainers:postgresql:1.8.3', 'org.yaml:snakeyaml:1.17', 'xerces:xmlParserAPIs:2.6.2', 'xpp3:xpp3:1.1.4c'