diff --git a/core/src/main/java/google/registry/model/EppResource.java b/core/src/main/java/google/registry/model/EppResource.java index c18d4361a..18241ab8c 100644 --- a/core/src/main/java/google/registry/model/EppResource.java +++ b/core/src/main/java/google/registry/model/EppResource.java @@ -53,7 +53,6 @@ import java.util.concurrent.ExecutionException; import javax.persistence.Column; import javax.persistence.MappedSuperclass; import javax.persistence.Transient; -import org.hibernate.annotations.Type; import org.joda.time.DateTime; import org.joda.time.Duration; @@ -117,7 +116,6 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable { DateTime lastEppUpdateTime; /** Status values associated with this resource. */ - @Type(type = "google.registry.model.eppcommon.StatusValue$StatusValueSetType") @Column(name = "statuses") // TODO(mmuller): rename to "statuses" once we're off datastore. Set status; diff --git a/core/src/main/java/google/registry/model/domain/DomainBase.java b/core/src/main/java/google/registry/model/domain/DomainBase.java index 7bc780572..82d09c1b8 100644 --- a/core/src/main/java/google/registry/model/domain/DomainBase.java +++ b/core/src/main/java/google/registry/model/domain/DomainBase.java @@ -185,7 +185,6 @@ public class DomainBase extends EppResource String idnTableName; /** Fully qualified host names of this domain's active subordinate hosts. */ - @org.hibernate.annotations.Type(type = "google.registry.persistence.StringSetUserType") Set subordinateHosts; /** When this domain's registration will expire. */ diff --git a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java index 1a1bbc82b..9433e9482 100644 --- a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java +++ b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java @@ -25,7 +25,6 @@ import google.registry.model.domain.DomainBase; import google.registry.model.host.HostResource; import google.registry.model.translators.EnumToAttributeAdapter.EppEnum; import google.registry.model.translators.StatusValueAdapter; -import google.registry.persistence.EnumSetUserType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; /** @@ -166,6 +165,4 @@ public enum StatusValue implements EppEnum { return StatusValue.valueOf(LOWER_CAMEL.to(UPPER_UNDERSCORE, nullToEmpty(xmlName))); } - /** Hibernate type for sets of {@link StatusValue}. */ - public static class StatusValueSetType extends EnumSetUserType {} } diff --git a/core/src/main/java/google/registry/model/registrar/Registrar.java b/core/src/main/java/google/registry/model/registrar/Registrar.java index 6b4e828fb..d11f944bc 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -262,15 +262,12 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable State state; /** The set of TLDs which this registrar is allowed to access. */ - // TODO(b/147908600): Investigate how to automatically apply user type - @org.hibernate.annotations.Type(type = "google.registry.persistence.StringSetUserType") Set allowedTlds; /** Host name of WHOIS server. */ String whoisServer; /** Base URLs for the registrar's RDAP servers. */ - @org.hibernate.annotations.Type(type = "google.registry.persistence.StringSetUserType") Set rdapBaseUrls; /** @@ -298,7 +295,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable String failoverClientCertificateHash; /** A whitelist of netmasks (in CIDR notation) which the client is allowed to connect from. */ - @org.hibernate.annotations.Type(type = "google.registry.persistence.CidrAddressBlockListUserType") List ipAddressWhitelist; /** A hashed password for EPP access. The hash is a base64 encoded SHA256 string. */ diff --git a/core/src/main/java/google/registry/model/registrar/RegistrarContact.java b/core/src/main/java/google/registry/model/registrar/RegistrarContact.java index 26d1be7af..78a5f853d 100644 --- a/core/src/main/java/google/registry/model/registrar/RegistrarContact.java +++ b/core/src/main/java/google/registry/model/registrar/RegistrarContact.java @@ -42,14 +42,12 @@ import google.registry.model.ImmutableObject; import google.registry.model.JsonMapBuilder; import google.registry.model.Jsonifiable; import google.registry.model.annotations.ReportedOn; -import google.registry.persistence.EnumSetUserType; import java.util.Arrays; import java.util.Map; import java.util.Set; import javax.persistence.Column; import javax.persistence.Table; import javax.persistence.Transient; -import org.hibernate.annotations.Type; /** * A contact for a Registrar. Note, equality, hashCode and comparable have been overridden to only @@ -103,9 +101,6 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable { this.displayName = display; this.required = required; } - - /** Hibernate type for sets of {@link Type}. */ - public static class RegistrarPocType extends EnumSetUserType {} } /** The name of the contact. */ @@ -127,8 +122,6 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable { * Multiple types are used to associate the registrar contact with various mailing groups. This * data is internal to the registry. */ - @org.hibernate.annotations.Type( - type = "google.registry.model.registrar.RegistrarContact$Type$RegistrarPocType") Set types; /** diff --git a/core/src/main/java/google/registry/persistence/CidrAddressBlockListUserType.java b/core/src/main/java/google/registry/persistence/CidrAddressBlockListConverter.java similarity index 58% rename from core/src/main/java/google/registry/persistence/CidrAddressBlockListUserType.java rename to core/src/main/java/google/registry/persistence/CidrAddressBlockListConverter.java index fc26dbeb9..aae37c8b9 100644 --- a/core/src/main/java/google/registry/persistence/CidrAddressBlockListUserType.java +++ b/core/src/main/java/google/registry/persistence/CidrAddressBlockListConverter.java @@ -16,20 +16,24 @@ package google.registry.persistence; import google.registry.util.CidrAddressBlock; import java.util.List; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; /** - * Hibernate {@link org.hibernate.usertype.UserType} for storing/retrieving {@link - * List} objects. + * JPA {@link AttributeConverter} for storing/retrieving {@link List} objects. + * TODO(shicong): Investigate if we can have one converter for any List type */ -public class CidrAddressBlockListUserType extends StringListUserType { +@Converter(autoApply = true) +public class CidrAddressBlockListConverter extends StringListConverterBase { @Override - protected CidrAddressBlock convertToElem(String columnValue) { - return columnValue == null ? null : CidrAddressBlock.create(columnValue); + String toString(CidrAddressBlock element) { + return element.toString(); } @Override - protected String convertToColumn(CidrAddressBlock elementValue) { - return elementValue == null ? null : elementValue.toString(); + CidrAddressBlock fromString(String value) { + return CidrAddressBlock.create(value); } + } diff --git a/core/src/main/java/google/registry/persistence/EnumSetUserType.java b/core/src/main/java/google/registry/persistence/EnumSetUserType.java deleted file mode 100644 index bb3e9ae5d..000000000 --- a/core/src/main/java/google/registry/persistence/EnumSetUserType.java +++ /dev/null @@ -1,51 +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; - -import google.registry.util.TypeUtils.TypeInstantiator; -import java.util.HashSet; -import java.util.Set; - -/** Abstract Hibernate user type for storing/retrieving {@link Set>}. */ -public class EnumSetUserType> - extends GenericCollectionUserType, E, String> { - - @Override - Set getNewCollection() { - return new HashSet<>(); - } - - @Override - ArrayColumnType getColumnType() { - return ArrayColumnType.STRING; - } - - @Override - public Class returnedClass() { - return Set.class; - } - - @Override - protected E convertToElem(String columnValue) { - return columnValue == null - ? null - : Enum.valueOf(new TypeInstantiator(getClass()) {}.getExactType(), columnValue); - } - - @Override - protected String convertToColumn(E elementValue) { - return elementValue == null ? null : elementValue.toString(); - } -} diff --git a/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java b/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java deleted file mode 100644 index 5b35a126e..000000000 --- a/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java +++ /dev/null @@ -1,119 +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; - -import google.registry.util.TypeUtils.TypeInstantiator; -import java.sql.Array; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Types; -import java.util.Collection; -import org.hibernate.HibernateException; -import org.hibernate.engine.spi.SharedSessionContractImplementor; - -/** - * Generic Hibernate user type to store/retrieve Java collection as an array in Cloud SQL. - * - * @param the concrete {@link Collection} type of the entity field - * @param the Java type of the element for the collection of the entity field - * @param the JDBC supported type of the element in the DB column array - */ -public abstract class GenericCollectionUserType, E, C> - extends MutableUserType { - - abstract T getNewCollection(); - - abstract ArrayColumnType getColumnType(); - - enum ArrayColumnType { - STRING(Types.ARRAY, "text"); - - final int typeCode; - final String typeName; - - ArrayColumnType(int typeCode, String typeName) { - this.typeCode = typeCode; - this.typeName = typeName; - } - - int getTypeCode() { - return typeCode; - } - - String getTypeName() { - return typeName; - } - - String getTypeDdlName() { - return typeName + "[]"; - } - } - - @Override - public Class returnedClass() { - return new TypeInstantiator(getClass()) {}.getExactType(); - } - - @Override - public int[] sqlTypes() { - return new int[] {getColumnType().getTypeCode()}; - } - - @Override - public Object nullSafeGet( - ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) - throws HibernateException, SQLException { - if (rs.getArray(names[0]) != null) { - T result = getNewCollection(); - for (C element : (C[]) rs.getArray(names[0]).getArray()) { - result.add(convertToElem(element)); - } - return result; - } - return null; - } - - @Override - public void nullSafeSet( - PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) - throws HibernateException, SQLException { - if (value == null) { - st.setArray(index, null); - return; - } - T collection = (T) value; - Array arr = - st.getConnection() - .createArrayOf( - getColumnType().getTypeName(), - collection.stream().map(this::convertToColumn).toArray()); - st.setArray(index, arr); - } - - /** - * Override this to convert an element value retrieved from the database to a different type. - * - *

This method is useful when encoding a java type to one of the types that can be used as an - * array element. - */ - protected E convertToElem(C columnValue) { - return (E) columnValue; - } - - protected C convertToColumn(E elementValue) { - return (C) elementValue; - } -} diff --git a/core/src/main/java/google/registry/persistence/NomulusPostgreSQLDialect.java b/core/src/main/java/google/registry/persistence/NomulusPostgreSQLDialect.java index 758175adb..786787da9 100644 --- a/core/src/main/java/google/registry/persistence/NomulusPostgreSQLDialect.java +++ b/core/src/main/java/google/registry/persistence/NomulusPostgreSQLDialect.java @@ -13,9 +13,10 @@ // limitations under the License. package google.registry.persistence; -import google.registry.persistence.GenericCollectionUserType.ArrayColumnType; import java.sql.Types; +import org.hibernate.boot.model.TypeContributions; import org.hibernate.dialect.PostgreSQL95Dialect; +import org.hibernate.service.ServiceRegistry; /** Nomulus mapping rules for column types in Postgresql. */ public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect { @@ -25,8 +26,15 @@ public class NomulusPostgreSQLDialect extends PostgreSQL95Dialect { registerColumnType(Types.TIMESTAMP_WITH_TIMEZONE, "timestamptz"); registerColumnType(Types.TIMESTAMP, "timestamptz"); registerColumnType(Types.OTHER, "hstore"); - for (ArrayColumnType arrayType : ArrayColumnType.values()) { - registerColumnType(arrayType.getTypeCode(), arrayType.getTypeDdlName()); - } + registerColumnType( + StringCollectionDescriptor.COLUMN_TYPE, StringCollectionDescriptor.COLUMN_DDL_NAME); + } + + @Override + public void contributeTypes( + TypeContributions typeContributions, ServiceRegistry serviceRegistry) { + super.contributeTypes(typeContributions, serviceRegistry); + typeContributions.contributeJavaTypeDescriptor(new StringCollectionDescriptor()); + typeContributions.contributeSqlTypeDescriptor(new StringCollectionDescriptor()); } } diff --git a/core/src/main/java/google/registry/persistence/RegistrarPocSetConverter.java b/core/src/main/java/google/registry/persistence/RegistrarPocSetConverter.java new file mode 100644 index 000000000..f4e0da400 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/RegistrarPocSetConverter.java @@ -0,0 +1,34 @@ +// 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; + +import google.registry.model.registrar.RegistrarContact.Type; +import java.util.Set; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** JPA {@link AttributeConverter} for storing/retrieving {@link Set}. */ +@Converter(autoApply = true) +public class RegistrarPocSetConverter extends StringSetConverterBase { + @Override + String toString(Type element) { + return element.name(); + } + + @Override + Type fromString(String value) { + return Type.valueOf(value); + } +} diff --git a/core/src/main/java/google/registry/persistence/StatusValueSetConverter.java b/core/src/main/java/google/registry/persistence/StatusValueSetConverter.java new file mode 100644 index 000000000..17485a89b --- /dev/null +++ b/core/src/main/java/google/registry/persistence/StatusValueSetConverter.java @@ -0,0 +1,35 @@ +// 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; + +import google.registry.model.eppcommon.StatusValue; +import java.util.Set; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** JPA {@link AttributeConverter} for storing/retrieving {@link Set}. */ +@Converter(autoApply = true) +public class StatusValueSetConverter extends StringSetConverterBase { + + @Override + String toString(StatusValue element) { + return element.name(); + } + + @Override + StatusValue fromString(String value) { + return StatusValue.valueOf(value); + } +} diff --git a/core/src/main/java/google/registry/persistence/StringCollectionDescriptor.java b/core/src/main/java/google/registry/persistence/StringCollectionDescriptor.java new file mode 100644 index 000000000..098b812f8 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/StringCollectionDescriptor.java @@ -0,0 +1,187 @@ +// 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; + +import static google.registry.persistence.StringCollectionDescriptor.StringCollection; + +import com.google.common.collect.ImmutableList; +import java.sql.Array; +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Types; +import java.util.Collection; +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 StringCollection}. + * + *

A {@link StringCollection} object is a simple wrapper for a {@link Collection} which + * can be stored as a string array in the database. The {@link JavaTypeDescriptor} and {@link + * SqlTypeDescriptor} is used by JPA/Hibernate to map between the collection and {@link Array} which + * is the actual type that JDBC uses to read from and write to the database. + * + * @see JPA + * 2.1 AttributeConverters + */ +public class StringCollectionDescriptor extends AbstractTypeDescriptor + implements SqlTypeDescriptor { + public static final int COLUMN_TYPE = Types.ARRAY; + public static final String COLUMN_NAME = "text"; + public static final String COLUMN_DDL_NAME = COLUMN_NAME + "[]"; + + protected StringCollectionDescriptor() { + super(StringCollection.class); + } + + @Override + public StringCollection fromString(String string) { + throw new UnsupportedOperationException( + "Constructing StringCollectionDescriptor from string is not allowed"); + } + + @Override + public X unwrap(StringCollection value, Class type, WrapperOptions options) { + if (value == null) { + return null; + } + if (Collection.class.isAssignableFrom(type)) { + return (X) value.getCollection(); + } + throw unknownUnwrap(type); + } + + @Override + public StringCollection wrap(X value, WrapperOptions options) { + if (value == null) { + return null; + } + if (value instanceof Array) { + try { + String[] arr = (String[]) ((Array) value).getArray(); + ImmutableList.Builder builder = new ImmutableList.Builder<>(); + for (String str : arr) { + builder.add(str); + } + return StringCollection.create(builder.build()); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + 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 ValueBinder getBinder(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicBinder(javaTypeDescriptor, this) { + @Override + protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) + throws SQLException { + if (value == null) { + st.setArray(index, null); + return; + } + if (value instanceof StringCollection) { + StringCollection stringCollection = (StringCollection) value; + if (stringCollection.getCollection() == null) { + st.setArray(index, null); + } else { + st.setArray( + index, + st.getConnection() + .createArrayOf(COLUMN_NAME, stringCollection.getCollection().toArray())); + } + } else { + throw new UnsupportedOperationException( + String.format( + "Binding type %s is not supported by StringCollectionDescriptor", + value.getClass().getName())); + } + } + + @Override + protected void doBind(CallableStatement st, X value, String name, WrapperOptions options) + throws SQLException { + // CallableStatement.setArray() doesn't have an overload version for setting array by its + // column name + throw new UnsupportedOperationException( + "Binding array by its column name is not supported"); + } + }; + } + + @Override + public ValueExtractor getExtractor(JavaTypeDescriptor javaTypeDescriptor) { + return new BasicExtractor(javaTypeDescriptor, this) { + @Override + protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException { + return javaTypeDescriptor.wrap(rs.getArray(name), options); + } + + @Override + protected X doExtract(CallableStatement statement, int index, WrapperOptions options) + throws SQLException { + return javaTypeDescriptor.wrap(statement.getArray(index), options); + } + + @Override + protected X doExtract(CallableStatement statement, String name, WrapperOptions options) + throws SQLException { + return javaTypeDescriptor.wrap(statement.getArray(name), options); + } + }; + } + + public static class StringCollection { + private Collection collection; + + private StringCollection(Collection collection) { + this.collection = collection; + } + + public static StringCollection create(Collection collection) { + return new StringCollection(collection); + } + + public Collection getCollection() { + return collection; + } + } +} diff --git a/core/src/main/java/google/registry/persistence/StringListUserType.java b/core/src/main/java/google/registry/persistence/StringListConverter.java similarity index 65% rename from core/src/main/java/google/registry/persistence/StringListUserType.java rename to core/src/main/java/google/registry/persistence/StringListConverter.java index c2ea235b1..331f0b965 100644 --- a/core/src/main/java/google/registry/persistence/StringListUserType.java +++ b/core/src/main/java/google/registry/persistence/StringListConverter.java @@ -14,19 +14,21 @@ package google.registry.persistence; -import com.google.common.collect.Lists; import java.util.List; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; -/** Abstract Hibernate user type for storing/retrieving {@link List}. */ -public class StringListUserType extends GenericCollectionUserType, E, String> { +/** JPA {@link AttributeConverter} for storing/retrieving {@link List}. */ +@Converter(autoApply = true) +public class StringListConverter extends StringListConverterBase { @Override - List getNewCollection() { - return Lists.newArrayList(); + String toString(String element) { + return element; } @Override - ArrayColumnType getColumnType() { - return ArrayColumnType.STRING; + String fromString(String value) { + return value; } } diff --git a/core/src/main/java/google/registry/persistence/StringListConverterBase.java b/core/src/main/java/google/registry/persistence/StringListConverterBase.java new file mode 100644 index 000000000..868cfd04a --- /dev/null +++ b/core/src/main/java/google/registry/persistence/StringListConverterBase.java @@ -0,0 +1,48 @@ +// 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; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +import google.registry.persistence.StringCollectionDescriptor.StringCollection; +import java.util.List; +import javax.persistence.AttributeConverter; + +/** + * Base JPA converter for {@link List} objects that are stored as an array of strings in the + * database. + */ +public abstract class StringListConverterBase + implements AttributeConverter, StringCollection> { + + abstract String toString(T element); + + abstract T fromString(String value); + + @Override + public StringCollection convertToDatabaseColumn(List attribute) { + return attribute == null + ? null + : StringCollection.create( + attribute.stream().map(this::toString).collect(toImmutableList())); + } + + @Override + public List convertToEntityAttribute(StringCollection dbData) { + return dbData == null || dbData.getCollection() == null + ? null + : dbData.getCollection().stream().map(this::fromString).collect(toImmutableList()); + } +} diff --git a/core/src/main/java/google/registry/persistence/StringSetUserType.java b/core/src/main/java/google/registry/persistence/StringSetConverter.java similarity index 65% rename from core/src/main/java/google/registry/persistence/StringSetUserType.java rename to core/src/main/java/google/registry/persistence/StringSetConverter.java index 4453b7369..dc1fe159c 100644 --- a/core/src/main/java/google/registry/persistence/StringSetUserType.java +++ b/core/src/main/java/google/registry/persistence/StringSetConverter.java @@ -14,19 +14,21 @@ package google.registry.persistence; -import com.google.common.collect.Sets; import java.util.Set; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; -/** Abstract Hibernate user type for storing/retrieving {@link Set}. */ -public class StringSetUserType extends GenericCollectionUserType, E, String> { +/** JPA {@link AttributeConverter} for storing/retrieving {@link Set}. */ +@Converter(autoApply = true) +public class StringSetConverter extends StringSetConverterBase { @Override - Set getNewCollection() { - return Sets.newHashSet(); + String toString(String element) { + return element; } @Override - ArrayColumnType getColumnType() { - return ArrayColumnType.STRING; + String fromString(String value) { + return value; } } diff --git a/core/src/main/java/google/registry/persistence/StringSetConverterBase.java b/core/src/main/java/google/registry/persistence/StringSetConverterBase.java new file mode 100644 index 000000000..f74477f6d --- /dev/null +++ b/core/src/main/java/google/registry/persistence/StringSetConverterBase.java @@ -0,0 +1,47 @@ +// 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; + +import static com.google.common.collect.ImmutableSet.toImmutableSet; + +import google.registry.persistence.StringCollectionDescriptor.StringCollection; +import java.util.Set; +import javax.persistence.AttributeConverter; + +/** + * Base JPA converter for {@link Set} objects that are stored as an array of strings in the + * database. + */ +public abstract class StringSetConverterBase + implements AttributeConverter, StringCollection> { + + abstract String toString(T element); + + abstract T fromString(String value); + + @Override + public StringCollection convertToDatabaseColumn(Set attribute) { + return attribute == null + ? null + : StringCollection.create(attribute.stream().map(this::toString).collect(toImmutableSet())); + } + + @Override + public Set convertToEntityAttribute(StringCollection dbData) { + return dbData == null || dbData.getCollection() == null + ? null + : dbData.getCollection().stream().map(this::fromString).collect(toImmutableSet()); + } +} diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index dd0f85838..2c170d823 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -39,9 +39,14 @@ google.registry.persistence.BloomFilterConverter + google.registry.persistence.CidrAddressBlockListConverter google.registry.persistence.CreateAutoTimestampConverter google.registry.persistence.CurrencyUnitConverter google.registry.persistence.DateTimeConverter + google.registry.persistence.RegistrarPocSetConverter + google.registry.persistence.StatusValueSetConverter + google.registry.persistence.StringListConverter + google.registry.persistence.StringSetConverter google.registry.persistence.UpdateAutoTimestampConverter google.registry.persistence.ZonedDateTimeConverter diff --git a/core/src/test/java/google/registry/persistence/CidrAddressBlockListUserTypeTest.java b/core/src/test/java/google/registry/persistence/CidrAddressBlockListUserTypeTest.java index 6f3b95aa9..73930662a 100644 --- a/core/src/test/java/google/registry/persistence/CidrAddressBlockListUserTypeTest.java +++ b/core/src/test/java/google/registry/persistence/CidrAddressBlockListUserTypeTest.java @@ -25,13 +25,12 @@ import google.registry.util.CidrAddressBlock; import java.util.List; import javax.persistence.Entity; import javax.persistence.Id; -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 CidrAddressBlockListUserType}. */ +/** Unit tests for {@link CidrAddressBlockListConverter}. */ @RunWith(JUnit4.class) public class CidrAddressBlockListUserTypeTest { @Rule @@ -59,7 +58,6 @@ public class CidrAddressBlockListUserTypeTest { @Id String name = "id"; - @Type(type = "google.registry.persistence.CidrAddressBlockListUserType") List addresses; private TestEntity() {} diff --git a/core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java b/core/src/test/java/google/registry/persistence/StatusValueSetConverterTest.java similarity index 58% rename from core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java rename to core/src/test/java/google/registry/persistence/StatusValueSetConverterTest.java index 966156b10..a141a59b2 100644 --- a/core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java +++ b/core/src/test/java/google/registry/persistence/StatusValueSetConverterTest.java @@ -18,29 +18,27 @@ import static com.google.common.truth.Truth.assertThat; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; import com.google.common.collect.ImmutableSet; +import google.registry.model.eppcommon.StatusValue; import google.registry.persistence.transaction.JpaTestRules; import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule; import java.util.Set; import javax.persistence.Entity; import javax.persistence.Id; -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 EnumSetUserType}. */ +/** Unit tests for {@link StatusValueSetConverter}. */ @RunWith(JUnit4.class) -public class EnumSetUserTypeTest { +public class StatusValueSetConverterTest { @Rule public final JpaUnitTestRule jpaRule = new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule(); - public EnumSetUserTypeTest() {} - @Test public void testRoundTrip() { - Set enums = ImmutableSet.of(TestEnum.BAR, TestEnum.FOO); + Set enums = ImmutableSet.of(StatusValue.INACTIVE, StatusValue.PENDING_DELETE); TestEntity obj = new TestEntity("foo", enums); jpaTm().transact(() -> jpaTm().getEntityManager().persist(obj)); @@ -49,45 +47,15 @@ public class EnumSetUserTypeTest { assertThat(persisted.data).isEqualTo(enums); } - @Test - public void testNativeQuery_succeeds() { - Set enums = ImmutableSet.of(TestEnum.BAR, TestEnum.FOO); - TestEntity obj = new TestEntity("foo", enums); - - jpaTm().transact(() -> jpaTm().getEntityManager().persist(obj)); - - assertThat( - ImmutableSet.of( - getSingleResultFromNativeQuery( - "SELECT data[1] FROM \"TestEntity\" WHERE name = 'foo'"), - getSingleResultFromNativeQuery( - "SELECT data[2] FROM \"TestEntity\" WHERE name = 'foo'"))) - .containsExactly("BAR", "FOO"); - } - - private static Object getSingleResultFromNativeQuery(String sql) { - return jpaTm() - .transact(() -> jpaTm().getEntityManager().createNativeQuery(sql).getSingleResult()); - } - - enum TestEnum { - FOO, - BAR, - BAZ; - - public static class TestEnumType extends EnumSetUserType {} - } - @Entity(name = "TestEntity") static class TestEntity { @Id String name; - @Type(type = "google.registry.persistence.EnumSetUserTypeTest$TestEnum$TestEnumType") - Set data; + Set data; TestEntity() {} - TestEntity(String name, Set data) { + TestEntity(String name, Set data) { this.name = name; this.data = data; } diff --git a/core/src/test/java/google/registry/persistence/StringListUserTypeTest.java b/core/src/test/java/google/registry/persistence/StringListConverterTest.java similarity index 96% rename from core/src/test/java/google/registry/persistence/StringListUserTypeTest.java rename to core/src/test/java/google/registry/persistence/StringListConverterTest.java index cacd7cb03..5d7ea0a6d 100644 --- a/core/src/test/java/google/registry/persistence/StringListUserTypeTest.java +++ b/core/src/test/java/google/registry/persistence/StringListConverterTest.java @@ -26,15 +26,14 @@ import java.util.List; 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 StringListUserType}. */ +/** Unit tests for {@link StringListConverter}. */ @RunWith(JUnit4.class) -public class StringListUserTypeTest { +public class StringListConverterTest { @Rule public final JpaUnitTestRule jpaRule = new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule(); @@ -123,7 +122,6 @@ public class StringListUserTypeTest { @Id String name = "id"; - @Type(type = "google.registry.persistence.StringListUserType") List tlds; private TestEntity() {} diff --git a/core/src/test/java/google/registry/persistence/StringSetUserTypeTest.java b/core/src/test/java/google/registry/persistence/StringSetConverterTest.java similarity index 93% rename from core/src/test/java/google/registry/persistence/StringSetUserTypeTest.java rename to core/src/test/java/google/registry/persistence/StringSetConverterTest.java index 884d57963..cb5925611 100644 --- a/core/src/test/java/google/registry/persistence/StringSetUserTypeTest.java +++ b/core/src/test/java/google/registry/persistence/StringSetConverterTest.java @@ -24,15 +24,14 @@ import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule; import java.util.Set; import javax.persistence.Entity; import javax.persistence.Id; -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 StringSetUserType}. */ +/** Unit tests for {@link StringSetConverter}. */ @RunWith(JUnit4.class) -public class StringSetUserTypeTest { +public class StringSetConverterTest { @Rule public final JpaUnitTestRule jpaRule = new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule(); @@ -70,7 +69,6 @@ public class StringSetUserTypeTest { @Id String name = "id"; - @Type(type = "google.registry.persistence.StringSetUserType") Set tlds; private TestEntity() {}