mirror of
https://github.com/google/nomulus.git
synced 2025-05-21 19:59:34 +02:00
Auto-apply JPA converters for map type (#520)
* Add map converter * Delete old map usertype * Refactor bind * Change to use map entry * Use Map.Entry
This commit is contained in:
parent
4a34369ba9
commit
bac1998d6a
12 changed files with 333 additions and 220 deletions
|
@ -392,8 +392,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
|
|||
*/
|
||||
@Nullable
|
||||
@Mapify(CurrencyMapper.class)
|
||||
@org.hibernate.annotations.Type(
|
||||
type = "google.registry.persistence.converter.CurrencyToBillingMapUserType")
|
||||
Map<CurrencyUnit, BillingAccountEntry> billingAccountMap;
|
||||
|
||||
/** A billing account entry for this registrar, consisting of a currency and an account Id. */
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package google.registry.persistence;
|
||||
|
||||
import google.registry.persistence.converter.StringCollectionDescriptor;
|
||||
import google.registry.persistence.converter.StringMapDescriptor;
|
||||
import java.sql.Types;
|
||||
import org.hibernate.boot.model.TypeContributions;
|
||||
import org.hibernate.dialect.PostgreSQL95Dialect;
|
||||
|
@ -26,7 +27,7 @@ public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
|
|||
registerColumnType(Types.VARCHAR, "text");
|
||||
registerColumnType(Types.TIMESTAMP_WITH_TIMEZONE, "timestamptz");
|
||||
registerColumnType(Types.TIMESTAMP, "timestamptz");
|
||||
registerColumnType(Types.OTHER, "hstore");
|
||||
registerColumnType(StringMapDescriptor.COLUMN_TYPE, StringMapDescriptor.COLUMN_NAME);
|
||||
registerColumnType(
|
||||
StringCollectionDescriptor.COLUMN_TYPE, StringCollectionDescriptor.COLUMN_DDL_NAME);
|
||||
}
|
||||
|
@ -37,5 +38,7 @@ public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect {
|
|||
super.contributeTypes(typeContributions, serviceRegistry);
|
||||
typeContributions.contributeJavaTypeDescriptor(StringCollectionDescriptor.getInstance());
|
||||
typeContributions.contributeSqlTypeDescriptor(StringCollectionDescriptor.getInstance());
|
||||
typeContributions.contributeJavaTypeDescriptor(StringMapDescriptor.getInstance());
|
||||
typeContributions.contributeSqlTypeDescriptor(StringMapDescriptor.getInstance());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2020 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.converter;
|
||||
|
||||
import static google.registry.model.registrar.Registrar.BillingAccountEntry;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Converter;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/** JPA converter for storing/retrieving {@link Map <CurrencyUnit, BillingAccountEntry>} objects. */
|
||||
@Converter(autoApply = true)
|
||||
public class CurrencyToBillingConverter
|
||||
extends StringMapConverterBase<CurrencyUnit, BillingAccountEntry> {
|
||||
|
||||
@Override
|
||||
Map.Entry<String, String> convertToDatabaseMapEntry(
|
||||
Map.Entry<CurrencyUnit, BillingAccountEntry> entry) {
|
||||
return Maps.immutableEntry(entry.getKey().getCode(), entry.getValue().getAccountId());
|
||||
}
|
||||
|
||||
@Override
|
||||
Map.Entry<CurrencyUnit, BillingAccountEntry> convertToEntityMapEntry(
|
||||
Map.Entry<String, String> entry) {
|
||||
CurrencyUnit currencyUnit = CurrencyUnit.of(entry.getKey());
|
||||
BillingAccountEntry billingAccountEntry =
|
||||
new BillingAccountEntry(currencyUnit, entry.getValue());
|
||||
return Maps.immutableEntry(currencyUnit, billingAccountEntry);
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// Copyright 2020 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.converter;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
|
||||
import google.registry.model.registrar.Registrar.BillingAccountEntry;
|
||||
import java.util.Map;
|
||||
import org.hibernate.usertype.UserType;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/**
|
||||
* A custom {@link UserType} for storing/retrieving {@link Map<CurrencyUnit, BillingAccountEntry>}
|
||||
* objects.
|
||||
*/
|
||||
public class CurrencyToBillingMapUserType extends MapUserType {
|
||||
|
||||
@Override
|
||||
public Object toEntityTypeMap(Map<String, String> map) {
|
||||
return map == null
|
||||
? null
|
||||
: map.entrySet().stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
entry -> CurrencyUnit.of(entry.getKey()),
|
||||
entry ->
|
||||
new BillingAccountEntry(
|
||||
CurrencyUnit.of(entry.getKey()), entry.getValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> toDbSupportedMap(Object map) {
|
||||
return map == null
|
||||
? null
|
||||
: ((Map<CurrencyUnit, BillingAccountEntry>) map)
|
||||
.entrySet().stream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
entry -> entry.getKey().getCode(),
|
||||
entry -> entry.getValue().getAccountId()));
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2020 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.converter;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Map;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.usertype.UserType;
|
||||
|
||||
/**
|
||||
* A custom {@link UserType} used to convert a Java {@link Map<String, String>} to/from PostgreSQL
|
||||
* hstore type. Per this <a href="https://www.postgresql.org/docs/current/hstore.html">doc</a>, as
|
||||
* hstore keys and values are simply text strings, the type of key and value in the Java map has to
|
||||
* be {@link String} as well.
|
||||
*/
|
||||
public class MapUserType extends MutableUserType {
|
||||
|
||||
@Override
|
||||
public int[] sqlTypes() {
|
||||
return new int[] {Types.OTHER};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class returnedClass() {
|
||||
return Map.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object nullSafeGet(
|
||||
ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
|
||||
throws HibernateException, SQLException {
|
||||
return toEntityTypeMap((Map<String, String>) rs.getObject(names[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void nullSafeSet(
|
||||
PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
|
||||
throws HibernateException, SQLException {
|
||||
st.setObject(index, toDbSupportedMap(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass can override this method to convert the {@link Map<String, String>} to a {@link Map}
|
||||
* of specific type defined in the entity class.
|
||||
*/
|
||||
public Object toEntityTypeMap(Map<String, String> map) {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass can override this method to convert the {@link Map} of specific type to a {@link
|
||||
* Map<String, String>} that can be stored in the hstore type column.
|
||||
*/
|
||||
public Map<String, String> toDbSupportedMap(Object map) {
|
||||
return (Map<String, String>) map;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
// Copyright 2020 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.converter;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.usertype.UserType;
|
||||
|
||||
/**
|
||||
* An abstract class represents a mutable Hibernate user type which implements related methods
|
||||
* defined in {@link UserType}.
|
||||
*/
|
||||
public abstract class MutableUserType implements UserType {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object x, Object y) throws HibernateException {
|
||||
return Objects.equals(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode(Object x) throws HibernateException {
|
||||
return x == null ? 0 : x.hashCode();
|
||||
}
|
||||
|
||||
// TODO(b/147489651): Investigate how to properly implement the below methods.
|
||||
@Override
|
||||
public Object deepCopy(Object value) throws HibernateException {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable disassemble(Object value) throws HibernateException {
|
||||
return (Serializable) value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object assemble(Serializable cached, Object owner) throws HibernateException {
|
||||
return cached;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object replace(Object original, Object target, Object owner) throws HibernateException {
|
||||
return original;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2020 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.converter;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
|
||||
import google.registry.persistence.converter.StringMapDescriptor.StringMap;
|
||||
import java.util.Map;
|
||||
import javax.persistence.AttributeConverter;
|
||||
|
||||
/**
|
||||
* Base JPA converter for {@link Map} objects that are stored in a column with data type of hstore
|
||||
* in the database.
|
||||
*/
|
||||
public abstract class StringMapConverterBase<K, V>
|
||||
implements AttributeConverter<Map<K, V>, StringMap> {
|
||||
|
||||
abstract Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<K, V> entry);
|
||||
|
||||
abstract Map.Entry<K, V> convertToEntityMapEntry(Map.Entry<String, String> entry);
|
||||
|
||||
@Override
|
||||
public StringMap convertToDatabaseColumn(Map<K, V> attribute) {
|
||||
return attribute == null
|
||||
? null
|
||||
: StringMap.create(
|
||||
attribute.entrySet().stream()
|
||||
.map(this::convertToDatabaseMapEntry)
|
||||
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<K, V> convertToEntityAttribute(StringMap dbData) {
|
||||
return dbData == null
|
||||
? null
|
||||
: dbData.getMap().entrySet().stream()
|
||||
.map(this::convertToEntityMapEntry)
|
||||
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
// Copyright 2020 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.converter;
|
||||
|
||||
import static google.registry.persistence.converter.StringMapDescriptor.StringMap;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.Map;
|
||||
import org.hibernate.type.descriptor.ValueBinder;
|
||||
import org.hibernate.type.descriptor.ValueExtractor;
|
||||
import org.hibernate.type.descriptor.WrapperOptions;
|
||||
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
|
||||
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
|
||||
import org.hibernate.type.descriptor.spi.JdbcRecommendedSqlTypeMappingContext;
|
||||
import org.hibernate.type.descriptor.sql.BasicBinder;
|
||||
import org.hibernate.type.descriptor.sql.BasicExtractor;
|
||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
||||
|
||||
/**
|
||||
* The {@link JavaTypeDescriptor} and {@link SqlTypeDescriptor} for {@link StringMap}.
|
||||
*
|
||||
* <p>A {@link StringMap} object is a simple wrapper for a {@link Map <String, String>} which can be
|
||||
* stored in a column with data type of hstore in the database. The {@link JavaTypeDescriptor} and
|
||||
* {@link SqlTypeDescriptor} is used by JPA/Hibernate to map between the map and hstore which is the
|
||||
* actual type that JDBC uses to read from and write to the database.
|
||||
*
|
||||
* @see <a
|
||||
* href="https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#basic-jpa-convert">JPA
|
||||
* 2.1 AttributeConverters</a>
|
||||
* @see <a href="https://www.postgresql.org/docs/current/hstore.html">hstore</a>
|
||||
*/
|
||||
public class StringMapDescriptor extends AbstractTypeDescriptor<StringMap>
|
||||
implements SqlTypeDescriptor {
|
||||
public static final int COLUMN_TYPE = Types.OTHER;
|
||||
public static final String COLUMN_NAME = "hstore";
|
||||
private static final StringMapDescriptor INSTANCE = new StringMapDescriptor();
|
||||
|
||||
protected StringMapDescriptor() {
|
||||
super(StringMap.class);
|
||||
}
|
||||
|
||||
public static StringMapDescriptor getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StringMap fromString(String string) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Constructing StringMapDescriptor from string is not allowed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> X unwrap(StringMap value, Class<X> type, WrapperOptions options) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (Map.class.isAssignableFrom(type)) {
|
||||
return (X) value.getMap();
|
||||
}
|
||||
throw unknownUnwrap(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> StringMap wrap(X value, WrapperOptions options) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
return StringMap.create((Map<String, String>) value);
|
||||
}
|
||||
throw unknownWrap(value.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlTypeDescriptor getJdbcRecommendedSqlType(JdbcRecommendedSqlTypeMappingContext context) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSqlType() {
|
||||
return COLUMN_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeRemapped() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
|
||||
return new BasicBinder<X>(javaTypeDescriptor, this) {
|
||||
@Override
|
||||
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)
|
||||
throws SQLException {
|
||||
st.setObject(index, getStringMap(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBind(CallableStatement st, X value, String name, WrapperOptions options)
|
||||
throws SQLException {
|
||||
st.setObject(name, getStringMap(value));
|
||||
}
|
||||
|
||||
private Map<String, String> getStringMap(X value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof StringMap) {
|
||||
return ((StringMap) value).getMap();
|
||||
} else {
|
||||
throw new UnsupportedOperationException(
|
||||
String.format(
|
||||
"Binding type %s is not supported by StringMapDescriptor",
|
||||
value.getClass().getName()));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
|
||||
return new BasicExtractor<X>(javaTypeDescriptor, this) {
|
||||
@Override
|
||||
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
|
||||
return javaTypeDescriptor.wrap(rs.getObject(name), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, int index, WrapperOptions options)
|
||||
throws SQLException {
|
||||
return javaTypeDescriptor.wrap(statement.getObject(index), options);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, String name, WrapperOptions options)
|
||||
throws SQLException {
|
||||
return javaTypeDescriptor.wrap(statement.getObject(name), options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** A simple wrapper class for {@link Map<String, String>}. */
|
||||
public static class StringMap {
|
||||
private Map<String, String> map;
|
||||
|
||||
private StringMap(Map<String, String> map) {
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
/** Constructs an instance of {@link StringMap} from the given map. */
|
||||
public static StringMap create(Map<String, String> map) {
|
||||
return new StringMap(ImmutableMap.copyOf(map));
|
||||
}
|
||||
|
||||
/** Returns the underlying {@link Map<String, String>} object. */
|
||||
public Map<String, String> getMap() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@
|
|||
<class>google.registry.persistence.converter.BloomFilterConverter</class>
|
||||
<class>google.registry.persistence.converter.CidrAddressBlockListConverter</class>
|
||||
<class>google.registry.persistence.converter.CreateAutoTimestampConverter</class>
|
||||
<class>google.registry.persistence.converter.CurrencyToBillingConverter</class>
|
||||
<class>google.registry.persistence.converter.CurrencyUnitConverter</class>
|
||||
<class>google.registry.persistence.converter.DateTimeConverter</class>
|
||||
<class>google.registry.persistence.converter.DurationConverter</class>
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.junit.runners.JUnit4;
|
|||
|
||||
/** Unit tests for {@link CidrAddressBlockListConverter}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class CidrAddressBlockListUserTypeTest {
|
||||
public class CidrAddressBlockListConverterTest {
|
||||
@Rule
|
||||
public final JpaUnitTestRule jpaRule =
|
||||
new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule();
|
|
@ -25,16 +25,15 @@ import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
|
|||
import java.util.Map;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link CurrencyToBillingMapUserType}. */
|
||||
/** Unit tests for {@link CurrencyToBillingConverter}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class CurrencyToBillingMapUserTypeTest {
|
||||
public class CurrencyToBillingConverterTest {
|
||||
@Rule
|
||||
public final JpaUnitTestRule jpaRule =
|
||||
new JpaTestRules.Builder()
|
||||
|
@ -62,7 +61,6 @@ public class CurrencyToBillingMapUserTypeTest {
|
|||
|
||||
@Id String name = "id";
|
||||
|
||||
@Type(type = "google.registry.persistence.converter.CurrencyToBillingMapUserType")
|
||||
Map<CurrencyUnit, BillingAccountEntry> currencyToBilling;
|
||||
|
||||
private TestEntity() {}
|
|
@ -19,54 +19,56 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
|||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.transaction.JpaTestRules;
|
||||
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Converter;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.NoResultException;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
/** Unit tests for {@link MapUserType}. */
|
||||
/** Unit tests for {@link StringMapConverterBase}. */
|
||||
@RunWith(JUnit4.class)
|
||||
public class MapUserTypeTest {
|
||||
|
||||
// Reusing production script sql/flyway/V14__load_extension_for_hstore.sql, which loads the
|
||||
// hstore extension but nothing else.
|
||||
public class StringMapConverterBaseTest {
|
||||
@Rule
|
||||
public final JpaUnitTestRule jpaRule =
|
||||
public final JpaTestRules.JpaUnitTestRule jpaRule =
|
||||
new JpaTestRules.Builder()
|
||||
.withInitScript("sql/flyway/V14__load_extension_for_hstore.sql")
|
||||
.withEntityClass(TestEntity.class)
|
||||
.withEntityClass(TestStringMapConverter.class, TestEntity.class)
|
||||
.buildUnitTestRule();
|
||||
|
||||
private static final ImmutableMap<Key, Value> MAP =
|
||||
ImmutableMap.of(
|
||||
new Key("key1"), new Value("value1"),
|
||||
new Key("key2"), new Value("value2"),
|
||||
new Key("key3"), new Value("value3"));
|
||||
|
||||
@Test
|
||||
public void roundTripConversion_returnsSameMap() {
|
||||
Map<String, String> map = ImmutableMap.of("key1", "value1", "key2", "value2");
|
||||
TestEntity testEntity = new TestEntity(map);
|
||||
TestEntity testEntity = new TestEntity(MAP);
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
||||
TestEntity persisted =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
|
||||
assertThat(persisted.map).containsExactly("key1", "value1", "key2", "value2");
|
||||
assertThat(persisted.map).containsExactlyEntriesIn(MAP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMerge_succeeds() {
|
||||
Map<String, String> map = ImmutableMap.of("key1", "value1", "key2", "value2");
|
||||
TestEntity testEntity = new TestEntity(map);
|
||||
public void testUpdateColumn_succeeds() {
|
||||
TestEntity testEntity = new TestEntity(MAP);
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
||||
TestEntity persisted =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
|
||||
persisted.map = ImmutableMap.of("key3", "value3");
|
||||
assertThat(persisted.map).containsExactlyEntriesIn(MAP);
|
||||
persisted.map = ImmutableMap.of(new Key("key4"), new Value("value4"));
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().merge(persisted));
|
||||
TestEntity updated =
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id"));
|
||||
assertThat(updated.map).containsExactly("key3", "value3");
|
||||
assertThat(updated.map).containsExactly(new Key("key4"), new Value("value4"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -79,7 +81,7 @@ public class MapUserTypeTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyCollection_writesAndReadsEmptyCollectionSuccessfully() {
|
||||
public void testEmptyMap_writesAndReadsEmptyCollectionSuccessfully() {
|
||||
TestEntity testEntity = new TestEntity(ImmutableMap.of());
|
||||
jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity));
|
||||
TestEntity persisted =
|
||||
|
@ -88,7 +90,7 @@ public class MapUserTypeTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testNativeQuery_succeeds() throws Exception {
|
||||
public void testNativeQuery_succeeds() {
|
||||
executeNativeQuery(
|
||||
"INSERT INTO \"TestEntity\" (name, map) VALUES ('id', 'key1=>value1, key2=>value2')");
|
||||
|
||||
|
@ -126,17 +128,46 @@ public class MapUserTypeTest {
|
|||
.transact(() -> jpaTm().getEntityManager().createNativeQuery(sql).executeUpdate());
|
||||
}
|
||||
|
||||
private static class Key extends ImmutableObject {
|
||||
private String key;
|
||||
|
||||
private Key(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Value extends ImmutableObject {
|
||||
private String value;
|
||||
|
||||
private Value(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
@Converter(autoApply = true)
|
||||
private static class TestStringMapConverter extends StringMapConverterBase<Key, Value> {
|
||||
|
||||
@Override
|
||||
Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<Key, Value> entry) {
|
||||
return Maps.immutableEntry(entry.getKey().key, entry.getValue().value);
|
||||
}
|
||||
|
||||
@Override
|
||||
Map.Entry<Key, Value> convertToEntityMapEntry(Map.Entry<String, String> entry) {
|
||||
return Maps.immutableEntry(new Key(entry.getKey()), new Value(entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "TestEntity") // Override entity name to avoid the nested class reference.
|
||||
private static class TestEntity extends ImmutableObject {
|
||||
|
||||
@Id String name = "id";
|
||||
|
||||
@Type(type = "google.registry.persistence.converter.MapUserType")
|
||||
Map<String, String> map;
|
||||
Map<Key, Value> map;
|
||||
|
||||
private TestEntity() {}
|
||||
|
||||
private TestEntity(Map<String, String> map) {
|
||||
private TestEntity(Map<Key, Value> map) {
|
||||
this.map = map;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue