diff --git a/core/src/main/java/google/registry/model/eppcommon/Address.java b/core/src/main/java/google/registry/model/eppcommon/Address.java index 03380f789..d3cd34d5d 100644 --- a/core/src/main/java/google/registry/model/eppcommon/Address.java +++ b/core/src/main/java/google/registry/model/eppcommon/Address.java @@ -15,16 +15,22 @@ package google.registry.model.eppcommon; import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.nullToEmpty; import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.annotation.Ignore; +import com.googlecode.objectify.annotation.OnLoad; import google.registry.model.Buildable; import google.registry.model.ImmutableObject; import google.registry.model.JsonMapBuilder; import google.registry.model.Jsonifiable; import java.util.List; import java.util.Map; +import javax.persistence.Embeddable; +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.CollapsedStringAdapter; @@ -42,12 +48,21 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; * @see google.registry.model.registrar.RegistrarAddress */ @XmlTransient +@Embeddable +@MappedSuperclass public class Address extends ImmutableObject implements Jsonifiable { /** The schema validation will enforce that this has 3 lines at most. */ @XmlJavaTypeAdapter(NormalizedStringAdapter.class) + @Transient List street; + @Ignore String streetLine1; + + @Ignore String streetLine2; + + @Ignore String streetLine3; + @XmlJavaTypeAdapter(NormalizedStringAdapter.class) String city; @@ -64,7 +79,23 @@ public class Address extends ImmutableObject implements Jsonifiable { String countryCode; public ImmutableList getStreet() { - return nullToEmptyImmutableCopy(street); + if (street == null && streetLine1 != null) { + return ImmutableList.of(streetLine1, nullToEmpty(streetLine2), nullToEmpty(streetLine3)); + } else { + return nullToEmptyImmutableCopy(street); + } + } + + public String getStreetLine1() { + return streetLine1; + } + + public String getStreetLine2() { + return streetLine2; + } + + public String getStreetLine13() { + return streetLine3; } public String getCity() { @@ -139,4 +170,14 @@ public class Address extends ImmutableObject implements Jsonifiable { return this; } } + + @OnLoad + void setStreetForCloudSql() { + if (street == null || street.size() == 0) { + return; + } + streetLine1 = street.get(0); + streetLine2 = street.size() >= 2 ? street.get(1) : null; + streetLine3 = street.size() >= 3 ? street.get(2) : null; + } } 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 d3c7acf1c..c9761eb81 100644 --- a/core/src/main/java/google/registry/model/eppcommon/StatusValue.java +++ b/core/src/main/java/google/registry/model/eppcommon/StatusValue.java @@ -169,8 +169,8 @@ public enum StatusValue implements EppEnum { /** Hibernate type for sets of {@link StatusValue}. */ public static class StatusValueSetType extends EnumSetUserType { @Override - protected Object convertToElem(Object value) { - return StatusValue.valueOf((String) value); + protected StatusValue convertToElem(String value) { + return StatusValue.valueOf(value); } } } 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 d05864602..6825e80c3 100644 --- a/core/src/main/java/google/registry/model/registrar/Registrar.java +++ b/core/src/main/java/google/registry/model/registrar/Registrar.java @@ -85,12 +85,27 @@ import java.util.function.Predicate; import javax.annotation.Nullable; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.Column; +import javax.persistence.Embedded; +import javax.persistence.Table; +import javax.persistence.Transient; +import org.hibernate.annotations.Type; import org.joda.money.CurrencyUnit; import org.joda.time.DateTime; /** Information about a registrar. */ @ReportedOn @Entity +@javax.persistence.Entity +@Table( + indexes = { + @javax.persistence.Index(columnList = "registrarName", name = "registrar_name_idx"), + @javax.persistence.Index( + columnList = "ianaIdentifier", + name = "registrar_iana_identifier_idx"), + }) public class Registrar extends ImmutableObject implements Buildable, Jsonifiable { /** Represents the type of a registrar entity. */ @@ -208,14 +223,17 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable .doTransactionless( () -> Maps.uniqueIndex(loadAll(), Registrar::getClientId))); - @Parent - Key parent = getCrossTldKey(); + @Parent @Transient Key parent = getCrossTldKey(); /** * Unique registrar client id. Must conform to "clIDType" as defined in RFC5730. + * * @see Shared Structure Schema + *

TODO(shicong): Rename this field to clientId */ @Id + @javax.persistence.Id + @Column(name = "client_id", nullable = false) String clientIdentifier; /** @@ -229,21 +247,27 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable * @see ICANN-Accredited * Registrars */ - @Index String registrarName; + @Index + @Column(nullable = false) + String registrarName; /** The type of this registrar. */ + @Column(nullable = false) Type type; /** The state of this registrar. */ 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; /** @@ -271,12 +295,14 @@ 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. */ String passwordHash; /** Randomly generated hash salt. */ + @Column(name = "password_salt") String salt; // The following fields may appear redundant to the above, but are @@ -287,6 +313,24 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable * unrestricted UTF-8. */ @IgnoreSave(IfNull.class) + @Embedded + @AttributeOverrides({ + @AttributeOverride( + name = "streetLine1", + column = @Column(name = "localized_address_street_line1")), + @AttributeOverride( + name = "streetLine2", + column = @Column(name = "localized_address_street_line2")), + @AttributeOverride( + name = "streetLine3", + column = @Column(name = "localized_address_street_line3")), + @AttributeOverride(name = "city", column = @Column(name = "localized_address_city")), + @AttributeOverride(name = "state", column = @Column(name = "localized_address_state")), + @AttributeOverride(name = "zip", column = @Column(name = "localized_address_zip")), + @AttributeOverride( + name = "countryCode", + column = @Column(name = "localized_address_country_code")) + }) RegistrarAddress localizedAddress; /** @@ -294,6 +338,16 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable * representable in the 7-bit US-ASCII character set. */ @IgnoreSave(IfNull.class) + @Embedded + @AttributeOverrides({ + @AttributeOverride(name = "streetLine1", column = @Column(name = "i18n_address_street_line1")), + @AttributeOverride(name = "streetLine2", column = @Column(name = "i18n_address_street_line2")), + @AttributeOverride(name = "streetLine3", column = @Column(name = "i18n_address_street_line3")), + @AttributeOverride(name = "city", column = @Column(name = "i18n_address_city")), + @AttributeOverride(name = "state", column = @Column(name = "i18n_address_state")), + @AttributeOverride(name = "zip", column = @Column(name = "i18n_address_zip")), + @AttributeOverride(name = "countryCode", column = @Column(name = "i18n_address_country_code")) + }) RegistrarAddress internationalizedAddress; /** Voice number. */ @@ -309,16 +363,17 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable /** * Registrar identifier used for reporting to ICANN. + * *

    *
  • 8 is used for Testing Registrar. *
  • 9997 is used by ICAAN for SLA monitoring. *
  • 9999 is used for cases when the registry operator acts as registrar. *
- * @see Registrar IDs + * + * @see Registrar + * IDs */ - @Index - @Nullable - Long ianaIdentifier; + @Index @Nullable Long ianaIdentifier; /** Identifier of registrar used in external billing system (e.g. Oracle). */ @Nullable @@ -338,18 +393,19 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable */ @Nullable @Mapify(CurrencyMapper.class) + @org.hibernate.annotations.Type(type = "google.registry.persistence.CurrencyToBillingMapUserType") Map billingAccountMap; /** A billing account entry for this registrar, consisting of a currency and an account Id. */ @Embed - static class BillingAccountEntry extends ImmutableObject { + public static class BillingAccountEntry extends ImmutableObject { CurrencyUnit currency; String accountId; BillingAccountEntry() {} - BillingAccountEntry(CurrencyUnit currency, String accountId) { + public BillingAccountEntry(CurrencyUnit currency, String accountId) { this.accountId = accountId; this.currency = currency; } @@ -366,6 +422,11 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable return billingAccountEntry.currency; } } + + /** Returns the account id of this entry. */ + public String getAccountId() { + return accountId; + } } /** URL of registrar's website. */ @@ -390,14 +451,10 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable /** An automatically managed last-saved timestamp. */ UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null); - /** - * The time that the certificate was last updated. - */ + /** The time that the certificate was last updated. */ DateTime lastCertificateUpdateTime; - /** - * Telephone support passcode (5-digit numeric) - */ + /** Telephone support passcode (5-digit numeric) */ String phonePasscode; /** diff --git a/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java b/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java index 239a23a83..45f634522 100644 --- a/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java +++ b/core/src/main/java/google/registry/model/registrar/RegistrarAddress.java @@ -20,6 +20,7 @@ import static google.registry.util.CollectionUtils.forceEmptyToNull; import com.google.common.annotations.VisibleForTesting; import com.googlecode.objectify.annotation.Embed; import google.registry.model.eppcommon.Address; +import javax.persistence.Embeddable; /** * Registrar Address @@ -29,6 +30,7 @@ import google.registry.model.eppcommon.Address; * classes. */ @Embed +@Embeddable public class RegistrarAddress extends Address { @Override diff --git a/core/src/main/java/google/registry/persistence/CidrAddressBlockListUserType.java b/core/src/main/java/google/registry/persistence/CidrAddressBlockListUserType.java new file mode 100644 index 000000000..fc26dbeb9 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/CidrAddressBlockListUserType.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.util.CidrAddressBlock; +import java.util.List; + +/** + * Hibernate {@link org.hibernate.usertype.UserType} for storing/retrieving {@link + * List} objects. + */ +public class CidrAddressBlockListUserType extends StringListUserType { + + @Override + protected CidrAddressBlock convertToElem(String columnValue) { + return columnValue == null ? null : CidrAddressBlock.create(columnValue); + } + + @Override + protected String convertToColumn(CidrAddressBlock elementValue) { + return elementValue == null ? null : elementValue.toString(); + } +} diff --git a/core/src/main/java/google/registry/persistence/CurrencyToBillingMapUserType.java b/core/src/main/java/google/registry/persistence/CurrencyToBillingMapUserType.java new file mode 100644 index 000000000..072ec63d3 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/CurrencyToBillingMapUserType.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.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} + * objects. + */ +public class CurrencyToBillingMapUserType extends MapUserType { + + @Override + public Object toEntityTypeMap(Map map) { + return map.entrySet().stream() + .collect( + toImmutableMap( + entry -> CurrencyUnit.of(entry.getKey()), + entry -> + new BillingAccountEntry(CurrencyUnit.of(entry.getKey()), entry.getValue()))); + } + + @Override + public Map toDbSupportedMap(Object map) { + return ((Map) map) + .entrySet().stream() + .collect( + toImmutableMap( + entry -> entry.getKey().getCode(), entry -> entry.getValue().getAccountId())); + } +} diff --git a/core/src/main/java/google/registry/persistence/EnumSetUserType.java b/core/src/main/java/google/registry/persistence/EnumSetUserType.java index b8e24455d..d811bfa69 100644 --- a/core/src/main/java/google/registry/persistence/EnumSetUserType.java +++ b/core/src/main/java/google/registry/persistence/EnumSetUserType.java @@ -18,11 +18,12 @@ import java.util.HashSet; import java.util.Set; /** Abstract Hibernate user type for storing/retrieving {@link Set>}. */ -public class EnumSetUserType> extends GenericCollectionUserType>> { +public class EnumSetUserType> + extends GenericCollectionUserType, E, String> { @Override - Set> getNewCollection() { - return new HashSet>(); + Set getNewCollection() { + return new HashSet<>(); } @Override diff --git a/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java b/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java index 70ee13f98..5b35a126e 100644 --- a/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java +++ b/core/src/main/java/google/registry/persistence/GenericCollectionUserType.java @@ -14,6 +14,7 @@ package google.registry.persistence; +import google.registry.util.TypeUtils.TypeInstantiator; import java.sql.Array; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -23,8 +24,15 @@ 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. */ -public abstract class GenericCollectionUserType extends MutableUserType { +/** + * 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(); @@ -54,6 +62,11 @@ public abstract class GenericCollectionUserType extends Mu } } + @Override + public Class returnedClass() { + return new TypeInstantiator(getClass()) {}.getExactType(); + } + @Override public int[] sqlTypes() { return new int[] {getColumnType().getTypeCode()}; @@ -65,7 +78,7 @@ public abstract class GenericCollectionUserType extends Mu throws HibernateException, SQLException { if (rs.getArray(names[0]) != null) { T result = getNewCollection(); - for (Object element : (Object[]) rs.getArray(names[0]).getArray()) { + for (C element : (C[]) rs.getArray(names[0]).getArray()) { result.add(convertToElem(element)); } return result; @@ -81,8 +94,12 @@ public abstract class GenericCollectionUserType extends Mu st.setArray(index, null); return; } - T list = (T) value; - Array arr = st.getConnection().createArrayOf(getColumnType().getTypeName(), list.toArray()); + T collection = (T) value; + Array arr = + st.getConnection() + .createArrayOf( + getColumnType().getTypeName(), + collection.stream().map(this::convertToColumn).toArray()); st.setArray(index, arr); } @@ -92,7 +109,11 @@ public abstract class GenericCollectionUserType extends Mu *

This method is useful when encoding a java type to one of the types that can be used as an * array element. */ - protected Object convertToElem(Object columnValue) { - return columnValue; + 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/GenericEnumConverter.java b/core/src/main/java/google/registry/persistence/GenericEnumConverter.java new file mode 100644 index 000000000..3fd9ee2c2 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/GenericEnumConverter.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.util.TypeUtils.TypeInstantiator; +import javax.persistence.AttributeConverter; + +/** Generic converter for storing/retrieving {@link Enum} objects. */ +public class GenericEnumConverter> implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(T attribute) { + return attribute == null ? null : attribute.toString(); + } + + @Override + public T convertToEntityAttribute(String dbData) { + return dbData == null + ? null + : Enum.valueOf(new TypeInstantiator(getClass()) {}.getExactType(), dbData); + } +} diff --git a/core/src/main/java/google/registry/persistence/MapUserType.java b/core/src/main/java/google/registry/persistence/MapUserType.java index 6ec284d5e..6a2759888 100644 --- a/core/src/main/java/google/registry/persistence/MapUserType.java +++ b/core/src/main/java/google/registry/persistence/MapUserType.java @@ -45,13 +45,29 @@ public class MapUserType extends MutableUserType { public Object nullSafeGet( ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException { - return rs.getObject(names[0]); + return toEntityTypeMap((Map) rs.getObject(names[0])); } @Override public void nullSafeSet( PreparedStatement st, Object value, int index, SharedSessionContractImplementor session) throws HibernateException, SQLException { - st.setObject(index, value); + st.setObject(index, toDbSupportedMap(value)); + } + + /** + * Subclass can override this method to convert the {@link Map} to a {@link Map} + * of specific type defined in the entity class. + */ + public Object toEntityTypeMap(Map map) { + return map; + } + + /** + * Subclass can override this method to convert the {@link Map} of specific type to a {@link + * Map} that can be stored in the hstore type column. + */ + public Map toDbSupportedMap(Object map) { + return (Map) map; } } diff --git a/core/src/main/java/google/registry/persistence/RegistrarStateConverter.java b/core/src/main/java/google/registry/persistence/RegistrarStateConverter.java new file mode 100644 index 000000000..24646dc28 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/RegistrarStateConverter.java @@ -0,0 +1,22 @@ +// 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.Registrar; +import javax.persistence.Converter; + +/** JPA converter for storing/retrieving {@link Registrar.State} objects. */ +@Converter(autoApply = true) +public class RegistrarStateConverter extends GenericEnumConverter {} diff --git a/core/src/main/java/google/registry/persistence/RegistrarTypeConverter.java b/core/src/main/java/google/registry/persistence/RegistrarTypeConverter.java new file mode 100644 index 000000000..a9a1f9dd7 --- /dev/null +++ b/core/src/main/java/google/registry/persistence/RegistrarTypeConverter.java @@ -0,0 +1,22 @@ +// 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.Registrar; +import javax.persistence.Converter; + +/** JPA converter for storing/retrieving {@link Registrar.Type} objects. */ +@Converter(autoApply = true) +public class RegistrarTypeConverter extends GenericEnumConverter {} diff --git a/core/src/main/java/google/registry/persistence/StringListUserType.java b/core/src/main/java/google/registry/persistence/StringListUserType.java index 08af4e32a..c2ea235b1 100644 --- a/core/src/main/java/google/registry/persistence/StringListUserType.java +++ b/core/src/main/java/google/registry/persistence/StringListUserType.java @@ -18,10 +18,10 @@ import com.google.common.collect.Lists; import java.util.List; /** Abstract Hibernate user type for storing/retrieving {@link List}. */ -public class StringListUserType extends GenericCollectionUserType> { +public class StringListUserType extends GenericCollectionUserType, E, String> { @Override - List getNewCollection() { + List getNewCollection() { return Lists.newArrayList(); } @@ -29,9 +29,4 @@ public class StringListUserType extends GenericCollectionUserType> ArrayColumnType getColumnType() { return ArrayColumnType.STRING; } - - @Override - public Class returnedClass() { - return List.class; - } } diff --git a/core/src/main/java/google/registry/persistence/StringSetUserType.java b/core/src/main/java/google/registry/persistence/StringSetUserType.java index 5df1d37e8..4453b7369 100644 --- a/core/src/main/java/google/registry/persistence/StringSetUserType.java +++ b/core/src/main/java/google/registry/persistence/StringSetUserType.java @@ -18,10 +18,10 @@ import com.google.common.collect.Sets; import java.util.Set; /** Abstract Hibernate user type for storing/retrieving {@link Set}. */ -public class StringSetUserType extends GenericCollectionUserType> { +public class StringSetUserType extends GenericCollectionUserType, E, String> { @Override - Set getNewCollection() { + Set getNewCollection() { return Sets.newHashSet(); } @@ -29,9 +29,4 @@ public class StringSetUserType extends GenericCollectionUserType> { ArrayColumnType getColumnType() { return ArrayColumnType.STRING; } - - @Override - public Class returnedClass() { - return Set.class; - } } diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index 279f4c776..403c310ab 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -20,6 +20,7 @@ * Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant) --> google.registry.model.domain.DomainBase + google.registry.model.registrar.Registrar google.registry.schema.domain.RegistryLock google.registry.schema.tmch.ClaimsList google.registry.schema.cursor.Cursor @@ -40,6 +41,8 @@ google.registry.persistence.CreateAutoTimestampConverter google.registry.persistence.CurrencyUnitConverter google.registry.persistence.DateTimeConverter + google.registry.persistence.RegistrarStateConverter + google.registry.persistence.RegistrarTypeConverter 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 new file mode 100644 index 000000000..6f3b95aa9 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/CidrAddressBlockListUserTypeTest.java @@ -0,0 +1,71 @@ +// 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.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; + +import com.google.common.collect.ImmutableList; +import google.registry.model.ImmutableObject; +import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule; +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}. */ +@RunWith(JUnit4.class) +public class CidrAddressBlockListUserTypeTest { + @Rule + public final JpaUnitTestRule jpaRule = + new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule(); + + @Test + public void roundTripConversion_returnsSameCidrAddressBlock() { + List addresses = + ImmutableList.of( + CidrAddressBlock.create("0.0.0.0/32"), + CidrAddressBlock.create("255.255.255.254/31"), + CidrAddressBlock.create("::"), + CidrAddressBlock.create("8000::/1"), + CidrAddressBlock.create("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128")); + TestEntity testEntity = new TestEntity(addresses); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + TestEntity persisted = + jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id")); + assertThat(persisted.addresses).isEqualTo(addresses); + } + + @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.CidrAddressBlockListUserType") + List addresses; + + private TestEntity() {} + + private TestEntity(List addresses) { + this.addresses = addresses; + } + } +} diff --git a/core/src/test/java/google/registry/persistence/CurrencyToBillingMapUserTypeTest.java b/core/src/test/java/google/registry/persistence/CurrencyToBillingMapUserTypeTest.java new file mode 100644 index 000000000..b0577cecb --- /dev/null +++ b/core/src/test/java/google/registry/persistence/CurrencyToBillingMapUserTypeTest.java @@ -0,0 +1,74 @@ +// 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.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; + +import com.google.common.collect.ImmutableMap; +import google.registry.model.ImmutableObject; +import google.registry.model.registrar.Registrar.BillingAccountEntry; +import google.registry.persistence.transaction.JpaTestRules; +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}. */ +@RunWith(JUnit4.class) +public class CurrencyToBillingMapUserTypeTest { + @Rule + public final JpaUnitTestRule jpaRule = + new JpaTestRules.Builder() + .withInitScript("sql/flyway/V14__load_extension_for_hstore.sql") + .withEntityClass(TestEntity.class) + .buildUnitTestRule(); + + @Test + public void roundTripConversion_returnsSameCurrencyToBillingMap() { + ImmutableMap currencyToBilling = + ImmutableMap.of( + CurrencyUnit.of("USD"), + new BillingAccountEntry(CurrencyUnit.of("USD"), "accountId1"), + CurrencyUnit.of("CNY"), + new BillingAccountEntry(CurrencyUnit.of("CNY"), "accountId2")); + TestEntity testEntity = new TestEntity(currencyToBilling); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + TestEntity persisted = + jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id")); + assertThat(persisted.currencyToBilling).containsExactlyEntriesIn(currencyToBilling); + } + + @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.CurrencyToBillingMapUserType") + Map currencyToBilling; + + private TestEntity() {} + + private TestEntity(Map currencyToBilling) { + this.currencyToBilling = currencyToBilling; + } + } +} diff --git a/core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java b/core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java index e22655688..888808c69 100644 --- a/core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java +++ b/core/src/test/java/google/registry/persistence/EnumSetUserTypeTest.java @@ -56,8 +56,8 @@ public class EnumSetUserTypeTest { public static class TestEnumType extends EnumSetUserType { @Override - protected Object convertToElem(Object value) { - return TestEnum.valueOf((String) value); + protected TestEnum convertToElem(String value) { + return TestEnum.valueOf(value); } } } diff --git a/core/src/test/java/google/registry/persistence/RegistrarStateConverterTest.java b/core/src/test/java/google/registry/persistence/RegistrarStateConverterTest.java new file mode 100644 index 000000000..2a2ff8eaa --- /dev/null +++ b/core/src/test/java/google/registry/persistence/RegistrarStateConverterTest.java @@ -0,0 +1,77 @@ +// 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.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; + +import google.registry.model.ImmutableObject; +import google.registry.model.registrar.Registrar.State; +import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link RegistrarStateConverter}. */ +@RunWith(JUnit4.class) +public class RegistrarStateConverterTest { + + @Rule + public final JpaUnitTestRule jpaRule = + new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule(); + + @Test + public void roundTripConversion_returnsSameEnum() { + TestEntity testEntity = new TestEntity(State.ACTIVE); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + TestEntity persisted = + jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id")); + assertThat(persisted.state).isEqualTo(State.ACTIVE); + } + + @Test + public void testNativeQuery_succeeds() { + TestEntity testEntity = new TestEntity(State.DISABLED); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + + assertThat( + jpaTm() + .transact( + () -> + jpaTm() + .getEntityManager() + .createNativeQuery("SELECT state FROM \"TestEntity\" WHERE name = 'id'") + .getSingleResult())) + .isEqualTo("DISABLED"); + } + + @Entity(name = "TestEntity") // Override entity name to avoid the nested class reference. + private static class TestEntity extends ImmutableObject { + + @Id String name = "id"; + + State state; + + private TestEntity() {} + + private TestEntity(State state) { + this.state = state; + } + } +} diff --git a/core/src/test/java/google/registry/persistence/RegistrarTypeConverterTest.java b/core/src/test/java/google/registry/persistence/RegistrarTypeConverterTest.java new file mode 100644 index 000000000..c309c74ff --- /dev/null +++ b/core/src/test/java/google/registry/persistence/RegistrarTypeConverterTest.java @@ -0,0 +1,76 @@ +// 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.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; + +import google.registry.model.ImmutableObject; +import google.registry.model.registrar.Registrar.Type; +import google.registry.persistence.transaction.JpaTestRules; +import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link RegistrarTypeConverter}. */ +@RunWith(JUnit4.class) +public class RegistrarTypeConverterTest { + @Rule + public final JpaUnitTestRule jpaRule = + new JpaTestRules.Builder().withEntityClass(TestEntity.class).buildUnitTestRule(); + + @Test + public void roundTripConversion_returnsSameEnum() { + TestEntity testEntity = new TestEntity(Type.MONITORING); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + TestEntity persisted = + jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, "id")); + assertThat(persisted.type).isEqualTo(Type.MONITORING); + } + + @Test + public void testNativeQuery_succeeds() { + TestEntity testEntity = new TestEntity(Type.MONITORING); + jpaTm().transact(() -> jpaTm().getEntityManager().persist(testEntity)); + + assertThat( + jpaTm() + .transact( + () -> + jpaTm() + .getEntityManager() + .createNativeQuery("SELECT type FROM \"TestEntity\" WHERE name = 'id'") + .getSingleResult())) + .isEqualTo("MONITORING"); + } + + @Entity(name = "TestEntity") // Override entity name to avoid the nested class reference. + private static class TestEntity extends ImmutableObject { + + @Id String name = "id"; + + Type type; + + private TestEntity() {} + + private TestEntity(Type type) { + this.type = type; + } + } +} diff --git a/db/src/main/resources/sql/flyway/V16__create_registrar.sql b/db/src/main/resources/sql/flyway/V16__create_registrar.sql new file mode 100644 index 000000000..58a3adeb9 --- /dev/null +++ b/db/src/main/resources/sql/flyway/V16__create_registrar.sql @@ -0,0 +1,65 @@ +-- 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. + +create table "Registrar" ( + client_id text not null, + allowed_tlds text[], + billing_account_map hstore, + billing_identifier int8, + block_premium_names boolean not null, + client_certificate text, + client_certificate_hash text, + contacts_require_syncing boolean not null, + creation_time timestamptz, + drive_folder_id text, + email_address text, + failover_client_certificate text, + failover_client_certificate_hash text, + fax_number text, + iana_identifier int8, + icann_referral_email text, + i18n_address_city text, + i18n_address_country_code text, + i18n_address_state text, + i18n_address_street_line1 text, + i18n_address_street_line2 text, + i18n_address_street_line3 text, + i18n_address_zip text, + ip_address_whitelist text[], + last_certificate_update_time timestamptz, + last_update_time timestamptz, + localized_address_city text, + localized_address_country_code text, + localized_address_state text, + localized_address_street_line1 text, + localized_address_street_line2 text, + localized_address_street_line3 text, + localized_address_zip text, + password_hash text, + phone_number text, + phone_passcode text, + po_number text, + rdap_base_urls text[], + registrar_name text not null, + registry_lock_allowed boolean not null, + password_salt text, + state text, + type text not null, + url text, + whois_server text, + primary key (client_id) + ); + +create index registrar_name_idx on "Registrar" (registrar_name); +create index registrar_iana_identifier_idx on "Registrar" (iana_identifier); diff --git a/db/src/main/resources/sql/schema/db-schema.sql.generated b/db/src/main/resources/sql/schema/db-schema.sql.generated index 2152772be..1dfa65b65 100644 --- a/db/src/main/resources/sql/schema/db-schema.sql.generated +++ b/db/src/main/resources/sql/schema/db-schema.sql.generated @@ -93,6 +93,55 @@ primary key (revision_id) ); + create table "Registrar" ( + client_id text not null, + allowed_tlds text[], + billing_account_map hstore, + billing_identifier int8, + block_premium_names boolean not null, + client_certificate text, + client_certificate_hash text, + contacts_require_syncing boolean not null, + creation_time timestamptz, + drive_folder_id text, + email_address text, + failover_client_certificate text, + failover_client_certificate_hash text, + fax_number text, + iana_identifier int8, + icann_referral_email text, + i18n_address_city text, + i18n_address_country_code text, + i18n_address_state text, + i18n_address_street_line1 text, + i18n_address_street_line2 text, + i18n_address_street_line3 text, + i18n_address_zip text, + ip_address_whitelist text[], + last_certificate_update_time timestamptz, + last_update_time timestamptz, + localized_address_city text, + localized_address_country_code text, + localized_address_state text, + localized_address_street_line1 text, + localized_address_street_line2 text, + localized_address_street_line3 text, + localized_address_zip text, + password_hash text, + phone_number text, + phone_passcode text, + po_number text, + rdap_base_urls text[], + registrar_name text not null, + registry_lock_allowed boolean not null, + password_salt text, + state text, + type text not null, + url text, + whois_server text, + primary key (client_id) + ); + create table "RegistryLock" ( revision_id bigserial not null, domain_name text not null, @@ -130,6 +179,8 @@ create index IDX5mnf0wn20tno4b9do88j61klr on "Domain" (deletion_time); create index IDX1rcgkdd777bpvj0r94sltwd5y on "Domain" (fully_qualified_domain_name); create index IDXrwl38wwkli1j7gkvtywi9jokq on "Domain" (tld); create index premiumlist_name_idx on "PremiumList" (name); +create index registrar_name_idx on "Registrar" (registrar_name); +create index registrar_iana_identifier_idx on "Registrar" (iana_identifier); create index idx_registry_lock_verification_code on "RegistryLock" (verification_code); create index idx_registry_lock_registrar_id on "RegistryLock" (registrar_id); diff --git a/db/src/main/resources/sql/schema/nomulus.golden.sql b/db/src/main/resources/sql/schema/nomulus.golden.sql index f633ce0b6..b75c768d5 100644 --- a/db/src/main/resources/sql/schema/nomulus.golden.sql +++ b/db/src/main/resources/sql/schema/nomulus.golden.sql @@ -159,6 +159,59 @@ CREATE SEQUENCE public."PremiumList_revision_id_seq" ALTER SEQUENCE public."PremiumList_revision_id_seq" OWNED BY public."PremiumList".revision_id; +-- +-- Name: Registrar; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public."Registrar" ( + client_id text NOT NULL, + allowed_tlds text[], + billing_account_map public.hstore, + billing_identifier bigint, + block_premium_names boolean NOT NULL, + client_certificate text, + client_certificate_hash text, + contacts_require_syncing boolean NOT NULL, + creation_time timestamp with time zone, + drive_folder_id text, + email_address text, + failover_client_certificate text, + failover_client_certificate_hash text, + fax_number text, + iana_identifier bigint, + icann_referral_email text, + i18n_address_city text, + i18n_address_country_code text, + i18n_address_state text, + i18n_address_street_line1 text, + i18n_address_street_line2 text, + i18n_address_street_line3 text, + i18n_address_zip text, + ip_address_whitelist text[], + last_certificate_update_time timestamp with time zone, + last_update_time timestamp with time zone, + localized_address_city text, + localized_address_country_code text, + localized_address_state text, + localized_address_street_line1 text, + localized_address_street_line2 text, + localized_address_street_line3 text, + localized_address_zip text, + password_hash text, + phone_number text, + phone_passcode text, + po_number text, + rdap_base_urls text[], + registrar_name text NOT NULL, + registry_lock_allowed boolean NOT NULL, + password_salt text, + state text, + type text NOT NULL, + url text, + whois_server text +); + + -- -- Name: RegistryLock; Type: TABLE; Schema: public; Owner: - -- @@ -317,6 +370,14 @@ ALTER TABLE ONLY public."PremiumList" ADD CONSTRAINT "PremiumList_pkey" PRIMARY KEY (revision_id); +-- +-- Name: Registrar Registrar_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public."Registrar" + ADD CONSTRAINT "Registrar_pkey" PRIMARY KEY (client_id); + + -- -- Name: RegistryLock RegistryLock_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -405,6 +466,20 @@ CREATE INDEX idxrwl38wwkli1j7gkvtywi9jokq ON public."Domain" USING btree (tld); CREATE INDEX premiumlist_name_idx ON public."PremiumList" USING btree (name); +-- +-- Name: registrar_iana_identifier_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX registrar_iana_identifier_idx ON public."Registrar" USING btree (iana_identifier); + + +-- +-- Name: registrar_name_idx; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX registrar_name_idx ON public."Registrar" USING btree (registrar_name); + + -- -- Name: reservedlist_name_idx; Type: INDEX; Schema: public; Owner: - --