diff --git a/core/src/main/java/google/registry/schema/tld/PremiumList.java b/core/src/main/java/google/registry/schema/tld/PremiumList.java new file mode 100644 index 000000000..18da33bfa --- /dev/null +++ b/core/src/main/java/google/registry/schema/tld/PremiumList.java @@ -0,0 +1,102 @@ +// Copyright 2019 The Nomulus Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.schema.tld; + +import static com.google.common.base.Preconditions.checkState; + +import java.math.BigDecimal; +import java.time.ZonedDateTime; +import java.util.Map; +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; +import org.joda.money.CurrencyUnit; + +/** + * A list of premium prices for domain names. + * + *

Note that the primary key of this entity is {@link #revisionId}, which is auto-generated by + * the database. So, if a retry of insertion happens after the previous attempt unexpectedly + * succeeds, we will end up with having two exact same premium lists that differ only by revisionId. + * This is fine though, because we only use the list with the highest revisionId. + */ +@Entity +@Table(name = "PremiumList") +public class PremiumList { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "revision_id") + private Long revisionId; + + @Column(name = "creation_timestamp", nullable = false) + private ZonedDateTime creationTimestamp; + + @Column(name = "currency", nullable = false) + private CurrencyUnit currency; + + @ElementCollection + @CollectionTable( + name = "PremiumEntry", + joinColumns = @JoinColumn(name = "revision_id", referencedColumnName = "revision_id")) + @MapKeyColumn(name = "domain_label") + @Column(name = "price", nullable = false) + private Map labelsToPrices; + + private PremiumList( + ZonedDateTime creationTimestamp, + CurrencyUnit currency, + Map labelsToPrices) { + this.creationTimestamp = creationTimestamp; + this.currency = currency; + this.labelsToPrices = labelsToPrices; + } + + // Hibernate requires this default constructor. + private PremiumList() {} + + // TODO(mcilwain): Change creationTimestamp to Joda DateTime. + /** Constructs a {@link PremiumList} object. */ + public static PremiumList create( + ZonedDateTime creationTimestamp, + CurrencyUnit currency, + Map labelsToPrices) { + return new PremiumList(creationTimestamp, currency, labelsToPrices); + } + + /** Returns the ID of this revision, or throws if null. */ + public Long getRevisionId() { + checkState( + revisionId != null, "revisionId is null because it is not persisted in the database"); + return revisionId; + } + + /** Returns the creation time of this revision of the premium list. */ + public ZonedDateTime getCreationTimestamp() { + return creationTimestamp; + } + + /** Returns a {@link Map} of domain labels to prices. */ + public Map getLabelsToPrices() { + return labelsToPrices; + } +} diff --git a/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java b/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java index 425d2d244..8541ccb7a 100644 --- a/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java +++ b/core/src/main/java/google/registry/tools/GenerateSqlSchemaCommand.java @@ -19,6 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameters; import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableSet; import google.registry.model.domain.DesignatedContact; import google.registry.model.domain.DomainBase; import google.registry.model.domain.GracePeriod; @@ -26,6 +27,8 @@ import google.registry.model.domain.secdns.DelegationSignerData; import google.registry.model.eppcommon.Trid; import google.registry.model.transfer.BaseTransferObject; import google.registry.model.transfer.TransferData; +import google.registry.schema.tld.PremiumList; +import google.registry.schema.tmch.ClaimsList; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; @@ -49,6 +52,21 @@ import org.testcontainers.containers.PostgreSQLContainer; @Parameters(separators = " =", commandDescription = "Generate PostgreSQL schema.") public class GenerateSqlSchemaCommand implements Command { + // TODO(mmuller): These should be read from persistence.xml so we don't need to maintain two + // separate lists of all SQL table classes. + private static final ImmutableSet SQL_TABLE_CLASSES = + ImmutableSet.of( + BaseTransferObject.class, + ClaimsList.class, + DelegationSignerData.class, + DesignatedContact.class, + DomainBase.class, + GracePeriod.class, + Period.class, + PremiumList.class, + TransferData.class, + Trid.class); + @VisibleForTesting public static final String DB_OPTIONS_CLASH = "Database host and port may not be specified along with the option to start a " @@ -109,8 +127,7 @@ public class GenerateSqlSchemaCommand implements Command { + " -d postgres:9.6.12\n\n" + "Copy the container id output from the command, then run:\n\n" + " docker inspect | grep IPAddress\n\n" - + "To obtain the value for --db-host.\n" - ); + + "To obtain the value for --db-host.\n"); // TODO(mmuller): need exit(1), see above. return; } @@ -134,14 +151,7 @@ public class GenerateSqlSchemaCommand implements Command { MetadataSources metadata = new MetadataSources(new StandardServiceRegistryBuilder().applySettings(settings).build()); - metadata.addAnnotatedClass(BaseTransferObject.class); - metadata.addAnnotatedClass(DelegationSignerData.class); - metadata.addAnnotatedClass(DesignatedContact.class); - metadata.addAnnotatedClass(DomainBase.class); - metadata.addAnnotatedClass(GracePeriod.class); - metadata.addAnnotatedClass(Period.class); - metadata.addAnnotatedClass(TransferData.class); - metadata.addAnnotatedClass(Trid.class); + SQL_TABLE_CLASSES.forEach(metadata::addAnnotatedClass); SchemaExport schemaExport = new SchemaExport(); schemaExport.setHaltOnError(true); schemaExport.setFormat(true); diff --git a/core/src/main/resources/META-INF/persistence.xml b/core/src/main/resources/META-INF/persistence.xml index c01e3f24b..fb8a86acb 100644 --- a/core/src/main/resources/META-INF/persistence.xml +++ b/core/src/main/resources/META-INF/persistence.xml @@ -22,6 +22,7 @@ google.registry.model.domain.DomainBase google.registry.schema.tmch.ClaimsList google.registry.model.transfer.BaseTransferObject + google.registry.schema.tld.PremiumList google.registry.model.domain.secdns.DelegationSignerData google.registry.model.domain.DesignatedContact google.registry.model.domain.DomainBase diff --git a/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java b/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java index 63c0ea6ab..f9f0fb909 100644 --- a/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java +++ b/core/src/test/java/google/registry/tools/GenerateSqlSchemaCommandTest.java @@ -27,7 +27,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.testcontainers.containers.PostgreSQLContainer; - /** Unit tests for {@link google.registry.tools.GenerateSqlSchemaCommand}. */ @RunWith(JUnit4.class) public class GenerateSqlSchemaCommandTest extends CommandTestCase { @@ -77,8 +76,7 @@ public class GenerateSqlSchemaCommandTest extends CommandTestCase