mirror of
https://github.com/google/nomulus
synced 2026-05-21 07:11:48 +00:00
Compare commits
48 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0dee97934a | ||
|
|
1070173264 | ||
|
|
b9a3c0cd96 | ||
|
|
120456d138 | ||
|
|
66736d52f0 | ||
|
|
b159541278 | ||
|
|
335b229ce8 | ||
|
|
8ee0a85531 | ||
|
|
5cbc307cd1 | ||
|
|
bd37541b49 | ||
|
|
312bc143d5 | ||
|
|
49ade014ab | ||
|
|
b8d901effe | ||
|
|
23520048dc | ||
|
|
37ed6c925c | ||
|
|
17a21f3326 | ||
|
|
f623da9948 | ||
|
|
ddc4a615db | ||
|
|
06a1fc0022 | ||
|
|
eec272b6ba | ||
|
|
3d5b52b853 | ||
|
|
bd4af052a6 | ||
|
|
78249e1329 | ||
|
|
7aec579d96 | ||
|
|
b9f8faa165 | ||
|
|
b0e4e86586 | ||
|
|
3412f4417f | ||
|
|
db6329a070 | ||
|
|
02af277148 | ||
|
|
8b02f76ae9 | ||
|
|
6dd96c247a | ||
|
|
919c744d8c | ||
|
|
5bccd65bd7 | ||
|
|
5268e35155 | ||
|
|
6e201450f0 | ||
|
|
dda9a3ef7e | ||
|
|
8c1fb6bf00 | ||
|
|
4e21152f04 | ||
|
|
22193474d5 | ||
|
|
efd5244ebd | ||
|
|
87e5d19fe5 | ||
|
|
bbb6174c9f | ||
|
|
2b826651e6 | ||
|
|
e4132db8ed | ||
|
|
45d90e7c68 | ||
|
|
028005906a | ||
|
|
78d78e21cb | ||
|
|
2f3ac2e43b |
@@ -61,12 +61,6 @@ by Joshua Bloch in his book Effective Java -->
|
||||
<property name="message" value="Use assertThrows and expectThrows from JUnitBackports instead of the deprecated methods on ExpectedException."/>
|
||||
</module>
|
||||
|
||||
<!-- Checks that the deprecated MockitoJUnitRunner is not used. -->
|
||||
<module name="RegexpSingleline">
|
||||
<property name="format" value="MockitoJUnitRunner"/>
|
||||
<property name="message" value="MockitoJUnitRunner is deprecated. Use @RunWith(JUnit4.class) and MockitoRule instead."/>
|
||||
</module>
|
||||
|
||||
<module name="LineLength">
|
||||
<!-- Checks if a line is too long. -->
|
||||
<property name="max" value="${com.puppycrawl.tools.checkstyle.checks.sizes.LineLength.max}" default="100"/>
|
||||
|
||||
@@ -29,7 +29,7 @@ import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.flows.poll.PollFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -43,8 +43,8 @@ import google.registry.util.Clock;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Hard deletes load-test ContactResources, Hosts, their subordinate history entries, and the
|
||||
* associated ForeignKey and EppResourceIndex entities.
|
||||
* Hard deletes load-test Contacts, Hosts, their subordinate history entries, and the associated
|
||||
* ForeignKey and EppResourceIndex entities.
|
||||
*
|
||||
* <p>This only deletes contacts and hosts, NOT domains. To delete domains, use {@link
|
||||
* DeleteProberDataAction} and pass it the TLD(s) that the load test domains were created on. Note
|
||||
@@ -92,7 +92,7 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
tm().transact(
|
||||
() -> {
|
||||
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
|
||||
tm().loadAllOfStream(ContactResource.class).forEach(this::deleteContact);
|
||||
tm().loadAllOfStream(Contact.class).forEach(this::deleteContact);
|
||||
tm().loadAllOfStream(Host.class).forEach(this::deleteHost);
|
||||
});
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteContact(ContactResource contact) {
|
||||
private void deleteContact(Contact contact) {
|
||||
if (!LOAD_TEST_REGISTRARS.contains(contact.getPersistedCurrentSponsorRegistrarId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.batch.BatchModule.PARAM_DRY_RUN;
|
||||
import static google.registry.config.RegistryEnvironment.PRODUCTION;
|
||||
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
|
||||
import static google.registry.model.tld.Registries.getTldsOfType;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
@@ -267,8 +266,6 @@ public class DeleteProberDataAction implements Runnable {
|
||||
// messages, or auto-renews because those will all be hard-deleted the next time the job runs
|
||||
// anyway.
|
||||
tm().putAll(ImmutableList.of(deletedDomain, historyEntry));
|
||||
// updating foreign keys is a no-op in SQL
|
||||
updateForeignKeyIndexDeletionTime(deletedDomain);
|
||||
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package google.registry.beam.common;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import java.io.Serializable;
|
||||
@@ -46,8 +46,7 @@ public class JpaDemoPipeline implements Serializable {
|
||||
.apply(
|
||||
"Read contacts",
|
||||
RegistryJpaIO.read(
|
||||
() -> CriteriaQueryBuilder.create(ContactResource.class).build(),
|
||||
ContactResource::getRepoId))
|
||||
() -> CriteriaQueryBuilder.create(Contact.class).build(), Contact::getRepoId))
|
||||
.apply(
|
||||
"Count Contacts",
|
||||
ParDo.of(
|
||||
|
||||
@@ -45,8 +45,8 @@ import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -140,7 +140,7 @@ import org.joda.time.DateTime;
|
||||
* pairs of (contact/host repo ID: pending deposit) for all RDE pending deposits for further
|
||||
* processing.
|
||||
*
|
||||
* <h3>{@link ContactResource}</h3>
|
||||
* <h3>{@link Contact}</h3>
|
||||
*
|
||||
* We first join most recent contact histories, represented by (contact repo ID: contact history
|
||||
* revision ID) pairs, with referenced contacts, represented by (contact repo ID: pending deposit)
|
||||
@@ -150,13 +150,13 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* <h3>{@link Host}</h3>
|
||||
*
|
||||
* Similar to {@link ContactResource}, we join the most recent host history with referenced hosts to
|
||||
* find most recent referenced hosts. For external hosts we do the same treatment as we did on
|
||||
* contacts and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need
|
||||
* to find the superordinate domain in order to properly handle pending transfer in the deposit as
|
||||
* well. So we first find the superordinate domain repo ID from the host and join the (superordinate
|
||||
* domain repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain
|
||||
* repo ID: revision ID) pair obtained from the domain history query in order to map the host at
|
||||
* Similar to {@link Contact}, we join the most recent host history with referenced hosts to find
|
||||
* most recent referenced hosts. For external hosts we do the same treatment as we did on contacts
|
||||
* and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need to find
|
||||
* the superordinate domain in order to properly handle pending transfer in the deposit as well. So
|
||||
* we first find the superordinate domain repo ID from the host and join the (superordinate domain
|
||||
* repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain repo
|
||||
* ID: revision ID) pair obtained from the domain history query in order to map the host at
|
||||
* watermark to the domain at watermark. We then proceed to create the (pending deposit: deposit
|
||||
* fragment) pair for subordinate hosts using the added domain information.
|
||||
*
|
||||
@@ -466,10 +466,10 @@ public class RdePipeline implements Serializable {
|
||||
private PCollectionTuple processDomainHistories(PCollection<KV<String, Long>> domainHistories) {
|
||||
Counter activeDomainCounter = Metrics.counter("RDE", "ActiveDomainBase");
|
||||
Counter domainFragmentCounter = Metrics.counter("RDE", "DomainFragment");
|
||||
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContactResource");
|
||||
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContact");
|
||||
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
|
||||
return domainHistories.apply(
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced ContactResource and Host",
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
|
||||
ParDo.of(
|
||||
new DoFn<KV<String, Long>, KV<PendingDeposit, DepositFragment>>() {
|
||||
@ProcessElement
|
||||
@@ -533,17 +533,17 @@ public class RdePipeline implements Serializable {
|
||||
PCollection<KV<String, PendingDeposit>> referencedContacts,
|
||||
PCollection<KV<String, Long>> contactHistories) {
|
||||
Counter contactFragmentCounter = Metrics.counter("RDE", "ContactFragment");
|
||||
return removeUnreferencedResource(referencedContacts, contactHistories, ContactResource.class)
|
||||
return removeUnreferencedResource(referencedContacts, contactHistories, Contact.class)
|
||||
.apply(
|
||||
"Map ContactResource to DepositFragment",
|
||||
"Map Contact to DepositFragment",
|
||||
FlatMapElements.into(
|
||||
kvs(
|
||||
TypeDescriptor.of(PendingDeposit.class),
|
||||
TypeDescriptor.of(DepositFragment.class)))
|
||||
.via(
|
||||
(KV<String, CoGbkResult> kv) -> {
|
||||
ContactResource contact =
|
||||
(ContactResource)
|
||||
Contact contact =
|
||||
(Contact)
|
||||
loadResourceByHistoryEntryId(
|
||||
ContactHistory.class,
|
||||
kv.getKey(),
|
||||
|
||||
@@ -14,25 +14,29 @@
|
||||
|
||||
package google.registry.beam.resave;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static org.apache.beam.sdk.values.TypeDescriptors.integers;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.PipelineResult;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.apache.beam.sdk.transforms.DoFn;
|
||||
import org.apache.beam.sdk.transforms.GroupIntoBatches;
|
||||
import org.apache.beam.sdk.transforms.ParDo;
|
||||
@@ -52,7 +56,7 @@ import org.joda.time.DateTime;
|
||||
public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
|
||||
private static final ImmutableSet<Class<? extends EppResource>> EPP_RESOURCE_CLASSES =
|
||||
ImmutableSet.of(ContactResource.class, Domain.class, Host.class);
|
||||
ImmutableSet.of(Contact.class, Domain.class, Host.class);
|
||||
|
||||
/**
|
||||
* There exist three possible situations where we know we'll want to project domains to the
|
||||
@@ -68,7 +72,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
* multiple times, and to avoid projecting and resaving the same domain multiple times.
|
||||
*/
|
||||
private static final String DOMAINS_TO_PROJECT_QUERY =
|
||||
"FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
|
||||
"SELECT repoId FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
|
||||
+ " d.transferData.pendingTransferExpirationTime < current_timestamp()) OR"
|
||||
+ " (d.registrationExpirationTime < current_timestamp() AND d.deletionTime ="
|
||||
+ " (:END_OF_TIME)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId ="
|
||||
@@ -98,13 +102,13 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
|
||||
/** Projects to the current time and saves any contacts with expired transfers. */
|
||||
private void fastResaveContacts(Pipeline pipeline) {
|
||||
Read<ContactResource, ContactResource> read =
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
"FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
|
||||
"SELECT repoId FROM Contact WHERE transferData.transferStatus = 'PENDING' AND"
|
||||
+ " transferData.pendingTransferExpirationTime < current_timestamp()",
|
||||
ContactResource.class,
|
||||
c -> c);
|
||||
projectAndResaveResources(pipeline, ContactResource.class, read);
|
||||
String.class,
|
||||
r -> r);
|
||||
projectAndResaveResources(pipeline, Contact.class, repoIdRead);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,61 +119,81 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
* DomainBase#cloneProjectedAtTime(DateTime)}.
|
||||
*/
|
||||
private void fastResaveDomains(Pipeline pipeline) {
|
||||
Read<Domain, Domain> read =
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
DOMAINS_TO_PROJECT_QUERY,
|
||||
ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME),
|
||||
Domain.class,
|
||||
d -> d);
|
||||
projectAndResaveResources(pipeline, Domain.class, read);
|
||||
String.class,
|
||||
r -> r);
|
||||
projectAndResaveResources(pipeline, Domain.class, repoIdRead);
|
||||
}
|
||||
|
||||
/** Projects all resources to the current time and saves them. */
|
||||
private <T extends EppResource> void forceResaveAllResources(Pipeline pipeline, Class<T> clazz) {
|
||||
Read<T, T> read = RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(clazz).build());
|
||||
projectAndResaveResources(pipeline, clazz, read);
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
// Note: cannot use SQL parameters for the table name
|
||||
String.format("SELECT repoId FROM %s", clazz.getSimpleName()), String.class, r -> r);
|
||||
projectAndResaveResources(pipeline, clazz, repoIdRead);
|
||||
}
|
||||
|
||||
/** Projects and re-saves the result of the provided {@link Read}. */
|
||||
/** Projects and re-saves all resources with repo IDs provided by the {@link Read}. */
|
||||
private <T extends EppResource> void projectAndResaveResources(
|
||||
Pipeline pipeline, Class<T> clazz, Read<?, T> read) {
|
||||
Pipeline pipeline, Class<T> clazz, Read<?, String> repoIdRead) {
|
||||
int numShards = options.getSqlWriteShards();
|
||||
int batchSize = options.getSqlWriteBatchSize();
|
||||
String className = clazz.getSimpleName();
|
||||
pipeline
|
||||
.apply("Read " + className, read)
|
||||
.apply("Read " + className, repoIdRead)
|
||||
.apply(
|
||||
"Shard data for class" + className,
|
||||
WithKeys.<Integer, T>of(e -> ThreadLocalRandom.current().nextInt(numShards))
|
||||
WithKeys.<Integer, String>of(e -> ThreadLocalRandom.current().nextInt(numShards))
|
||||
.withKeyType(integers()))
|
||||
.apply(
|
||||
"Group into batches for class" + className,
|
||||
GroupIntoBatches.<Integer, T>ofSize(batchSize).withShardedKey())
|
||||
.apply("Map " + className + " to now", ParDo.of(new BatchedProjectionFunction<>()))
|
||||
GroupIntoBatches.<Integer, String>ofSize(batchSize).withShardedKey())
|
||||
.apply(
|
||||
"Write transformed " + className,
|
||||
RegistryJpaIO.<EppResource>write()
|
||||
.withName("Write transformed " + className)
|
||||
.withBatchSize(batchSize)
|
||||
.withShards(numShards));
|
||||
"Load, map, and save " + className,
|
||||
ParDo.of(new BatchedLoadProjectAndSaveFunction(clazz)));
|
||||
}
|
||||
|
||||
private static class BatchedProjectionFunction<T extends EppResource>
|
||||
extends DoFn<KV<ShardedKey<Integer>, Iterable<T>>, EppResource> {
|
||||
/** Function that loads, projects, and saves resources all in the same transaction. */
|
||||
private static class BatchedLoadProjectAndSaveFunction
|
||||
extends DoFn<KV<ShardedKey<Integer>, Iterable<String>>, Void> {
|
||||
|
||||
private final Class<? extends EppResource> clazz;
|
||||
|
||||
private BatchedLoadProjectAndSaveFunction(Class<? extends EppResource> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<ShardedKey<Integer>, Iterable<T>> element,
|
||||
OutputReceiver<EppResource> outputReceiver) {
|
||||
@Element KV<ShardedKey<Integer>, Iterable<String>> element,
|
||||
OutputReceiver<Void> outputReceiver) {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
element
|
||||
.getValue()
|
||||
.forEach(
|
||||
resource ->
|
||||
outputReceiver.output(
|
||||
resource.cloneProjectedAtTime(jpaTm().getTransactionTime()))));
|
||||
() -> {
|
||||
DateTime now = jpaTm().getTransactionTime();
|
||||
ImmutableList<VKey<? extends EppResource>> keys =
|
||||
Streams.stream(element.getValue())
|
||||
.map(repoId -> VKey.create(clazz, repoId))
|
||||
.collect(toImmutableList());
|
||||
ImmutableList<EppResource> mappedResources =
|
||||
jpaTm().loadByKeys(keys).values().stream()
|
||||
.map(r -> r.cloneProjectedAtTime(now))
|
||||
.collect(toImmutableList());
|
||||
jpaTm().putAll(mappedResources);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
PipelineOptionsFactory.register(ResaveAllEppResourcesPipelineOptions.class);
|
||||
ResaveAllEppResourcesPipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(args)
|
||||
.withValidation()
|
||||
.as(ResaveAllEppResourcesPipelineOptions.class);
|
||||
new ResaveAllEppResourcesPipeline(options).run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1300,6 +1300,36 @@ public final class RegistryConfig {
|
||||
return config.sslCertificateValidation.expirationWarningEmailSubjectText;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("dnsUpdateFailEmailSubjectText")
|
||||
public static String provideDnsUpdateFailEmailSubjectText(RegistryConfigSettings config) {
|
||||
return config.dnsUpdate.dnsUpdateFailEmailSubjectText;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("dnsUpdateFailEmailBodyText")
|
||||
public static String provideDnsUpdateFailEmailBodyText(RegistryConfigSettings config) {
|
||||
return config.dnsUpdate.dnsUpdateFailEmailBodyText;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("dnsUpdateFailRegistryName")
|
||||
public static String provideDnsUpdateFailRegistryName(RegistryConfigSettings config) {
|
||||
return config.dnsUpdate.dnsUpdateFailRegistryName;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("registrySupportEmail")
|
||||
public static InternetAddress provideRegistrySupportEmail(RegistryConfigSettings config) {
|
||||
return parseEmailAddress(config.dnsUpdate.registrySupportEmail);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("registryCcEmail")
|
||||
public static InternetAddress provideRegistryCcEmail(RegistryConfigSettings config) {
|
||||
return parseEmailAddress(config.dnsUpdate.registryCcEmail);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Config("allowedEcdsaCurves")
|
||||
public static ImmutableSet<String> provideAllowedEcdsaCurves(RegistryConfigSettings config) {
|
||||
|
||||
@@ -42,6 +42,7 @@ public class RegistryConfigSettings {
|
||||
public RegistryTool registryTool;
|
||||
public SslCertificateValidation sslCertificateValidation;
|
||||
public ContactHistory contactHistory;
|
||||
public DnsUpdate dnsUpdate;
|
||||
|
||||
/** Configuration options that apply to the entire App Engine project. */
|
||||
public static class AppEngine {
|
||||
@@ -246,4 +247,13 @@ public class RegistryConfigSettings {
|
||||
public int minMonthsBeforeWipeOut;
|
||||
public int wipeOutQueryBatchSize;
|
||||
}
|
||||
|
||||
/** Configuration for dns update. */
|
||||
public static class DnsUpdate {
|
||||
public String dnsUpdateFailEmailSubjectText;
|
||||
public String dnsUpdateFailEmailBodyText;
|
||||
public String dnsUpdateFailRegistryName;
|
||||
public String registrySupportEmail;
|
||||
public String registryCcEmail;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -477,6 +477,29 @@ contactHistory:
|
||||
# The batch size for querying ContactHistory table in the database.
|
||||
wipeOutQueryBatchSize: 500
|
||||
|
||||
# Configuration options relevant to the DNS update functionality.
|
||||
dnsUpdate:
|
||||
dnsUpdateFailRegistryName: Example name
|
||||
registrySupportEmail: email@example.com
|
||||
registryCcEmail: email@example.com
|
||||
# Email subject text template to notify partners after repeatedly failing DNS update
|
||||
dnsUpdateFailEmailSubjectText: "[ACTION REQUIRED]: Incomplete DNS Update"
|
||||
# Email body text template for failing DNS update that accepts 5 parameters:
|
||||
# registrar name, domain or host address, 'domain' or 'host' as a string that failed,
|
||||
# registry support email (see dnsUpdateFailRegistrySupportEmail) and registry display name
|
||||
dnsUpdateFailEmailBodyText: >
|
||||
Dear %1$s,
|
||||
|
||||
We are contacting you regarding the changes you recently made to one of your %2$ss.
|
||||
The DNS update for the %3$s %2$s failed to process. Please review your %2$s's DNS records
|
||||
and ensure that it is valid before trying another update.
|
||||
|
||||
If you have any questions or require additional support, please contact us
|
||||
at %4$s.
|
||||
|
||||
Regards,
|
||||
%5$s
|
||||
|
||||
# Configuration options for checking SSL certificates.
|
||||
sslCertificateValidation:
|
||||
# A map specifying the maximum amount of days the certificate can be valid.
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.dns;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.dns.DnsConstants.DNS_PUBLISH_PUSH_QUEUE_NAME;
|
||||
import static google.registry.dns.DnsModule.PARAM_DNS_WRITER;
|
||||
import static google.registry.dns.DnsModule.PARAM_DOMAINS;
|
||||
@@ -22,6 +23,7 @@ import static google.registry.dns.DnsModule.PARAM_LOCK_INDEX;
|
||||
import static google.registry.dns.DnsModule.PARAM_NUM_PUBLISH_LOCKS;
|
||||
import static google.registry.dns.DnsModule.PARAM_PUBLISH_TASK_ENQUEUED;
|
||||
import static google.registry.dns.DnsModule.PARAM_REFRESH_REQUEST_CREATED;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.request.Action.Method.POST;
|
||||
import static google.registry.request.RequestParameters.PARAM_TLD;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
@@ -32,11 +34,16 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import dagger.Lazy;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.DnsMetrics.ActionStatus;
|
||||
import google.registry.dns.DnsMetrics.CommitStatus;
|
||||
import google.registry.dns.DnsMetrics.PublishStatus;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarPoc;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
@@ -49,10 +56,13 @@ import google.registry.request.lock.LockHandler;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import google.registry.util.EmailMessage;
|
||||
import google.registry.util.SendEmailService;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.inject.Inject;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
@@ -71,7 +81,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
// tasks.
|
||||
public static final String APP_ENGINE_RETRY_HEADER = "X-AppEngine-TaskRetryCount";
|
||||
public static final String CLOUD_TASKS_RETRY_HEADER = "X-CloudTasks-TaskRetryCount";
|
||||
public static final int RETRIES_BEFORE_PERMANENT_FAILURE = 10;
|
||||
public static final int RETRIES_BEFORE_PERMANENT_FAILURE = 20;
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@@ -102,6 +112,13 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
private final Clock clock;
|
||||
private final CloudTasksUtils cloudTasksUtils;
|
||||
private final Response response;
|
||||
private final SendEmailService sendEmailService;
|
||||
private final String dnsUpdateFailEmailSubjectText;
|
||||
private final String dnsUpdateFailEmailBodyText;
|
||||
private final String dnsUpdateFailRegistryName;
|
||||
private final Lazy<InternetAddress> registrySupportEmail;
|
||||
private final Lazy<InternetAddress> registryCcEmail;
|
||||
private final InternetAddress gSuiteOutgoingEmailAddress;
|
||||
|
||||
@Inject
|
||||
public PublishDnsUpdatesAction(
|
||||
@@ -114,6 +131,12 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
@Parameter(PARAM_HOSTS) Set<String> hosts,
|
||||
@Parameter(PARAM_TLD) String tld,
|
||||
@Config("publishDnsUpdatesLockDuration") Duration timeout,
|
||||
@Config("dnsUpdateFailEmailSubjectText") String dnsUpdateFailEmailSubjectText,
|
||||
@Config("dnsUpdateFailEmailBodyText") String dnsUpdateFailEmailBodyText,
|
||||
@Config("dnsUpdateFailRegistryName") String dnsUpdateFailRegistryName,
|
||||
@Config("registrySupportEmail") Lazy<InternetAddress> registrySupportEmail,
|
||||
@Config("registryCcEmail") Lazy<InternetAddress> registryCcEmail,
|
||||
@Config("gSuiteOutgoingEmailAddress") InternetAddress gSuiteOutgoingEmailAddress,
|
||||
@Header(APP_ENGINE_RETRY_HEADER) Optional<Integer> appEngineRetryCount,
|
||||
@Header(CLOUD_TASKS_RETRY_HEADER) Optional<Integer> cloudTasksRetryCount,
|
||||
DnsQueue dnsQueue,
|
||||
@@ -122,11 +145,13 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
LockHandler lockHandler,
|
||||
Clock clock,
|
||||
CloudTasksUtils cloudTasksUtils,
|
||||
SendEmailService sendEmailService,
|
||||
Response response) {
|
||||
this.dnsQueue = dnsQueue;
|
||||
this.dnsWriterProxy = dnsWriterProxy;
|
||||
this.dnsMetrics = dnsMetrics;
|
||||
this.timeout = timeout;
|
||||
this.sendEmailService = sendEmailService;
|
||||
this.retryCount =
|
||||
cloudTasksRetryCount.orElse(
|
||||
appEngineRetryCount.orElseThrow(
|
||||
@@ -143,6 +168,12 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
this.clock = clock;
|
||||
this.cloudTasksUtils = cloudTasksUtils;
|
||||
this.response = response;
|
||||
this.dnsUpdateFailEmailBodyText = dnsUpdateFailEmailBodyText;
|
||||
this.dnsUpdateFailEmailSubjectText = dnsUpdateFailEmailSubjectText;
|
||||
this.dnsUpdateFailRegistryName = dnsUpdateFailRegistryName;
|
||||
this.registrySupportEmail = registrySupportEmail;
|
||||
this.registryCcEmail = registryCcEmail;
|
||||
this.gSuiteOutgoingEmailAddress = gSuiteOutgoingEmailAddress;
|
||||
}
|
||||
|
||||
private void recordActionResult(ActionStatus status) {
|
||||
@@ -209,9 +240,35 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
} else if (retryCount < RETRIES_BEFORE_PERMANENT_FAILURE) {
|
||||
// If the batch only contains 1 name, allow it more retries
|
||||
throw e;
|
||||
} else {
|
||||
// By the time we get here there's either single domain or a single host
|
||||
domains.stream()
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
dn -> {
|
||||
Optional<Domain> domain = loadByForeignKey(Domain.class, dn, clock.nowUtc());
|
||||
if (domain.isPresent()) {
|
||||
notifyWithEmailAboutDnsUpdateFailure(
|
||||
domain.get().getCurrentSponsorRegistrarId(), dn, false);
|
||||
} else {
|
||||
logger.atSevere().log(String.format("Domain entity for %s not found", dn));
|
||||
}
|
||||
});
|
||||
|
||||
hosts.stream()
|
||||
.findFirst()
|
||||
.ifPresent(
|
||||
hn -> {
|
||||
Optional<Host> host = loadByForeignKey(Host.class, hn, clock.nowUtc());
|
||||
if (host.isPresent()) {
|
||||
notifyWithEmailAboutDnsUpdateFailure(
|
||||
host.get().getPersistedCurrentSponsorRegistrarId(), hn, true);
|
||||
} else {
|
||||
logger.atSevere().log(String.format("Host entity for %s not found", hn));
|
||||
}
|
||||
});
|
||||
}
|
||||
// If we get here, we should terminate this task as it is likely a perpetually failing task.
|
||||
// TODO(b/237302821): Send an email notifying partner the dns update failed
|
||||
|
||||
recordActionResult(ActionStatus.MAX_RETRIES_EXCEEDED);
|
||||
response.setStatus(SC_ACCEPTED);
|
||||
logger.atSevere().withCause(e).log("Terminated task after too many retries");
|
||||
@@ -219,6 +276,53 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
return null;
|
||||
}
|
||||
|
||||
private InternetAddress emailToInternetAddress(String email) {
|
||||
try {
|
||||
return new InternetAddress(email, true);
|
||||
} catch (Exception e) {
|
||||
logger.atWarning().withCause(e).log(
|
||||
String.format(
|
||||
"Could not parse email contact %s to send DNS failure notification", email));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends an email to partners regarding a failure during DNS update */
|
||||
private void notifyWithEmailAboutDnsUpdateFailure(
|
||||
String registrarId, String hostOrDomainName, Boolean isHost) {
|
||||
Optional<Registrar> registrar = Registrar.loadByRegistrarIdCached(registrarId);
|
||||
|
||||
if (registrar.isPresent()) {
|
||||
String body =
|
||||
String.format(
|
||||
dnsUpdateFailEmailBodyText,
|
||||
registrar.get().getRegistrarName(),
|
||||
hostOrDomainName,
|
||||
isHost ? "host" : "domain",
|
||||
registrySupportEmail.get().getAddress(),
|
||||
dnsUpdateFailRegistryName);
|
||||
|
||||
ImmutableList<InternetAddress> recipients =
|
||||
registrar.get().getContacts().stream()
|
||||
.filter(c -> c.getTypes().contains(RegistrarPoc.Type.ADMIN))
|
||||
.map(RegistrarPoc::getEmailAddress)
|
||||
.map(this::emailToInternetAddress)
|
||||
.collect(toImmutableList());
|
||||
|
||||
sendEmailService.sendEmail(
|
||||
EmailMessage.newBuilder()
|
||||
.setBody(body)
|
||||
.setSubject(dnsUpdateFailEmailSubjectText)
|
||||
.setRecipients(recipients)
|
||||
.addBcc(registryCcEmail.get())
|
||||
.setFrom(gSuiteOutgoingEmailAddress)
|
||||
.build());
|
||||
|
||||
} else {
|
||||
logger.atSevere().log(String.format("Could not find registrar %s", registrarId));
|
||||
}
|
||||
}
|
||||
|
||||
/** Splits the domains and hosts in a batch into smaller batches and adds them to the queue. */
|
||||
private void splitBatch() {
|
||||
ImmutableList<String> domainList = ImmutableList.copyOf(domains);
|
||||
@@ -294,8 +398,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
int domainsPublished = 0;
|
||||
int domainsRejected = 0;
|
||||
for (String domain : nullToEmpty(domains)) {
|
||||
if (!DomainNameUtils.isUnder(
|
||||
InternetDomainName.from(domain), InternetDomainName.from(tld))) {
|
||||
if (!DomainNameUtils.isUnder(InternetDomainName.from(domain), InternetDomainName.from(tld))) {
|
||||
logger.atSevere().log("%s: skipping domain %s not under TLD.", tld, domain);
|
||||
domainsRejected += 1;
|
||||
} else {
|
||||
@@ -310,8 +413,7 @@ public final class PublishDnsUpdatesAction implements Runnable, Callable<Void> {
|
||||
int hostsPublished = 0;
|
||||
int hostsRejected = 0;
|
||||
for (String host : nullToEmpty(hosts)) {
|
||||
if (!DomainNameUtils.isUnder(
|
||||
InternetDomainName.from(host), InternetDomainName.from(tld))) {
|
||||
if (!DomainNameUtils.isUnder(InternetDomainName.from(host), InternetDomainName.from(tld))) {
|
||||
logger.atSevere().log("%s: skipping host %s not under TLD.", tld, host);
|
||||
hostsRejected += 1;
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<datastore-indexes autoGenerate="false">
|
||||
<!-- For finding contact resources by registrar. -->
|
||||
<datastore-index kind="ContactResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Contact" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="searchName" direction="asc"/>
|
||||
@@ -91,7 +91,7 @@
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="fullyQualifiedHostName" direction="asc"/>
|
||||
</datastore-index>
|
||||
<datastore-index kind="ContactResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Contact" ancestor="false" source="manual">
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="searchName" direction="asc"/>
|
||||
</datastore-index>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<name>dns-publish</name>
|
||||
<rate>100/s</rate>
|
||||
<bucket-size>100</bucket-size>
|
||||
<!-- 30 sec backoff increasing linearly up to 10 minutes. -->
|
||||
<!-- 30 sec backoff increasing linearly up to 30 minutes. -->
|
||||
<retry-parameters>
|
||||
<min-backoff-seconds>30</min-backoff-seconds>
|
||||
<max-backoff-seconds>600</max-backoff-seconds>
|
||||
<max-backoff-seconds>1800</max-backoff-seconds>
|
||||
<max-doublings>0</max-doublings>
|
||||
</retry-parameters>
|
||||
</queue>
|
||||
|
||||
@@ -38,7 +38,7 @@ import google.registry.flows.exceptions.TooManyResourceChecksException;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Period;
|
||||
@@ -139,7 +139,7 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
|
||||
/** Check that the given AuthInfo is either missing or else is valid for the given resource. */
|
||||
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, ContactResource contact)
|
||||
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, Contact contact)
|
||||
throws EppException {
|
||||
if (authInfo.isPresent()) {
|
||||
verifyAuthInfo(authInfo.get(), contact);
|
||||
@@ -167,7 +167,7 @@ public final class ResourceFlowUtils {
|
||||
return;
|
||||
}
|
||||
// The roid should match one of the contacts.
|
||||
Optional<VKey<ContactResource>> foundContact =
|
||||
Optional<VKey<Contact>> foundContact =
|
||||
domain.getReferencedContacts().stream()
|
||||
.filter(key -> key.getSqlKey().equals(authRepoId))
|
||||
.findFirst();
|
||||
@@ -179,8 +179,7 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
|
||||
/** Check that the given {@link AuthInfo} is valid for the given contact. */
|
||||
public static void verifyAuthInfo(AuthInfo authInfo, ContactResource contact)
|
||||
throws EppException {
|
||||
public static void verifyAuthInfo(AuthInfo authInfo, Contact contact) throws EppException {
|
||||
String authRepoId = authInfo.getPw().getRepoId();
|
||||
String authPassword = authInfo.getPw().getValue();
|
||||
String contactPassword = contact.getAuthInfo().getPw().getValue();
|
||||
|
||||
@@ -220,7 +220,8 @@ public class TlsCredentials implements TransportCredentials {
|
||||
super(
|
||||
clientInetAddr.isPresent()
|
||||
? String.format(
|
||||
"Registrar IP address %s is not in stored allow list", clientInetAddr.get())
|
||||
"Registrar IP address %s is not in stored allow list",
|
||||
clientInetAddr.get().getHostAddress())
|
||||
: "Registrar IP address is not in stored allow list");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Check;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.ContactCheck;
|
||||
import google.registry.model.eppoutput.CheckData.ContactCheckData;
|
||||
@@ -62,7 +62,7 @@ public final class ContactCheckFlow implements Flow {
|
||||
ImmutableList<String> targetIds = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(targetIds, maxChecks);
|
||||
ImmutableSet<String> existingIds =
|
||||
checkResourcesExist(ContactResource.class, targetIds, clock.nowUtc());
|
||||
checkResourcesExist(Contact.class, targetIds, clock.nowUtc());
|
||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
|
||||
@@ -33,15 +33,14 @@ import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Create;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CreateData.ContactCreateData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import javax.inject.Inject;
|
||||
@@ -75,9 +74,9 @@ public final class ContactCreateFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
Create command = (Create) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
verifyResourceDoesNotExist(ContactResource.class, targetId, now, registrarId);
|
||||
ContactResource newContact =
|
||||
new ContactResource.Builder()
|
||||
verifyResourceDoesNotExist(Contact.class, targetId, now, registrarId);
|
||||
Contact newContact =
|
||||
new Contact.Builder()
|
||||
.setContactId(targetId)
|
||||
.setAuthInfo(command.getAuthInfo())
|
||||
.setCreationRegistrarId(registrarId)
|
||||
@@ -100,7 +99,6 @@ public final class ContactCreateFlow implements TransactionalFlow {
|
||||
ImmutableSet.of(
|
||||
newContact,
|
||||
historyBuilder.build(),
|
||||
ForeignKeyIndex.create(newContact, newContact.getDeletionTime()),
|
||||
EppResourceIndex.create(Key.create(newContact))));
|
||||
return responseBuilder
|
||||
.setResData(ContactCreateData.create(newContact.getContactId(), now))
|
||||
|
||||
@@ -35,8 +35,8 @@ import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -91,15 +91,15 @@ public final class ContactDeleteFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
checkLinkedDomains(targetId, now, ContactResource.class);
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
checkLinkedDomains(targetId, now, Contact.class);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
if (!isSuperuser) {
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
}
|
||||
// Handle pending transfers on contact deletion.
|
||||
ContactResource newContact =
|
||||
Contact newContact =
|
||||
existingContact.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
||||
? denyPendingTransfer(existingContact, SERVER_CANCELLED, now, registrarId)
|
||||
: existingContact;
|
||||
|
||||
@@ -24,10 +24,10 @@ import com.googlecode.objectify.Key;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactAddress;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactHistory.ContactHistoryId;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -61,7 +61,7 @@ public class ContactFlowUtils {
|
||||
}
|
||||
|
||||
/** Check contact's state against server policy. */
|
||||
static void validateContactAgainstPolicy(ContactResource contact) throws EppException {
|
||||
static void validateContactAgainstPolicy(Contact contact) throws EppException {
|
||||
if (contact.getDisclose() != null && !contact.getDisclose().getFlag()) {
|
||||
throw new DeclineContactDisclosureFieldDisallowedPolicyException();
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactInfoData;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
@@ -69,7 +69,7 @@ public final class ContactInfoFlow implements Flow {
|
||||
DateTime now = clock.nowUtc();
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ContactResource contact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
Contact contact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
if (!isSuperuser) {
|
||||
verifyResourceOwnership(registrarId, contact);
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
@@ -74,7 +74,7 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
|
||||
|
||||
/**
|
||||
* The logic in this flow, which handles client approvals, very closely parallels the logic in
|
||||
* {@link ContactResource#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
* {@link Contact#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
*/
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
@@ -82,11 +82,11 @@ public final class ContactTransferApproveFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
ContactResource newContact =
|
||||
Contact newContact =
|
||||
approvePendingTransfer(existingContact, TransferStatus.CLIENT_APPROVED, now);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_APPROVE).setContact(newContact).build();
|
||||
|
||||
@@ -33,8 +33,8 @@ import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
@@ -78,11 +78,11 @@ public final class ContactTransferCancelFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyTransferInitiator(registrarId, existingContact);
|
||||
ContactResource newContact =
|
||||
Contact newContact =
|
||||
denyPendingTransfer(existingContact, TransferStatus.CLIENT_CANCELLED, now, registrarId);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_CANCEL).setContact(newContact).build();
|
||||
|
||||
@@ -27,7 +27,7 @@ import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
@@ -66,8 +66,7 @@ public final class ContactTransferQueryFlow implements Flow {
|
||||
public EppResponse run() throws EppException {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ContactResource contact =
|
||||
loadAndVerifyExistence(ContactResource.class, targetId, clock.nowUtc());
|
||||
Contact contact = loadAndVerifyExistence(Contact.class, targetId, clock.nowUtc());
|
||||
verifyOptionalAuthInfo(authInfo, contact);
|
||||
// Most of the fields on the transfer response are required, so there's no way to return valid
|
||||
// XML if the object has never been transferred (and hence the fields aren't populated).
|
||||
|
||||
@@ -33,8 +33,8 @@ import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
@@ -76,11 +76,11 @@ public final class ContactTransferRejectFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
verifyHasPendingTransfer(existingContact);
|
||||
verifyResourceOwnership(registrarId, existingContact);
|
||||
ContactResource newContact =
|
||||
Contact newContact =
|
||||
denyPendingTransfer(existingContact, TransferStatus.CLIENT_REJECTED, now, registrarId);
|
||||
ContactHistory contactHistory =
|
||||
historyBuilder.setType(CONTACT_TRANSFER_REJECT).setContact(newContact).build();
|
||||
|
||||
@@ -38,8 +38,8 @@ import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
@@ -96,7 +96,7 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(gainingClientId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyAuthInfoPresentForResourceTransfer(authInfo);
|
||||
verifyAuthInfo(authInfo.get(), existingContact);
|
||||
// Verify that the resource does not already have a pending transfer.
|
||||
@@ -146,10 +146,12 @@ public final class ContactTransferRequestFlow implements TransactionalFlow {
|
||||
.asBuilder()
|
||||
.setEventTime(now) // Unlike the serverApprove messages, this applies immediately.
|
||||
.build();
|
||||
ContactResource newContact = existingContact.asBuilder()
|
||||
.setTransferData(pendingTransferData)
|
||||
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.build();
|
||||
Contact newContact =
|
||||
existingContact
|
||||
.asBuilder()
|
||||
.setTransferData(pendingTransferData)
|
||||
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.build();
|
||||
tm().update(newContact);
|
||||
tm().insertAll(
|
||||
ImmutableSet.of(
|
||||
|
||||
@@ -36,10 +36,10 @@ import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactCommand.Update;
|
||||
import google.registry.model.contact.ContactCommand.Update.Change;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
@@ -94,7 +94,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
Update command = (Update) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
ContactResource existingContact = loadAndVerifyExistence(ContactResource.class, targetId, now);
|
||||
Contact existingContact = loadAndVerifyExistence(Contact.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingContact);
|
||||
ImmutableSet<StatusValue> statusToRemove = command.getInnerRemove().getStatusValues();
|
||||
ImmutableSet<StatusValue> statusesToAdd = command.getInnerAdd().getStatusValues();
|
||||
@@ -104,7 +104,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
|
||||
}
|
||||
verifyNoDisallowedStatuses(existingContact, DISALLOWED_STATUSES);
|
||||
checkSameValuesNotAddedAndRemoved(statusesToAdd, statusToRemove);
|
||||
ContactResource.Builder builder = existingContact.asBuilder();
|
||||
Contact.Builder builder = existingContact.asBuilder();
|
||||
Change change = command.getInnerChange();
|
||||
// The spec requires the following behaviors:
|
||||
// * If you update part of a postal info, the fields that you didn't update are unchanged.
|
||||
@@ -126,7 +126,7 @@ public final class ContactUpdateFlow implements TransactionalFlow {
|
||||
builder.setInternationalizedPostalInfo(null);
|
||||
}
|
||||
}
|
||||
ContactResource newContact =
|
||||
Contact newContact =
|
||||
builder
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
|
||||
@@ -106,7 +106,6 @@ import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CreateData.DomainCreateData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.poll.PollMessage.Autorenew;
|
||||
@@ -153,6 +152,8 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainCreateFlow.AnchorTenantCreatePeriodException}
|
||||
* @error {@link DomainCreateFlow.MustHaveSignedMarksInCurrentPhaseException}
|
||||
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
|
||||
* @error {@link DomainCreateFlow.NoTrademarkedRegistrationsBeforeSunriseException}
|
||||
* @error {@link DomainCreateFlow.PackageDomainRegisteredForTooManyYearsException}
|
||||
* @error {@link DomainCreateFlow.SignedMarksOnlyDuringSunriseException}
|
||||
* @error {@link DomainFlowTmchUtils.NoMarksFoundMatchingDomainException}
|
||||
* @error {@link DomainFlowTmchUtils.FoundMarkNotYetValidException}
|
||||
@@ -376,8 +377,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setIdnTableName(validateDomainNameWithIdnTables(domainName))
|
||||
.setRegistrationExpirationTime(registrationExpirationTime)
|
||||
.setAutorenewBillingEvent(autorenewBillingEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
|
||||
.setSmdId(signedMarkId)
|
||||
.setDsData(secDnsCreate.map(SecDnsCreateExtension::getDsData).orElse(null))
|
||||
@@ -390,6 +390,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.addGracePeriod(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.ADD, repoId, createBillingEvent))
|
||||
.build();
|
||||
if (allocationToken.isPresent()
|
||||
&& allocationToken.get().getTokenType().equals(TokenType.PACKAGE)) {
|
||||
if (years > 1) {
|
||||
throw new PackageDomainRegisteredForTooManyYearsException(allocationToken.get().getToken());
|
||||
}
|
||||
domain =
|
||||
domain.asBuilder().setCurrentPackageToken(allocationToken.get().createVKey()).build();
|
||||
}
|
||||
DomainHistory domainHistory =
|
||||
buildDomainHistory(domain, registry, now, period, registry.getAddGracePeriodLength());
|
||||
if (reservationTypes.contains(NAME_COLLISION)) {
|
||||
@@ -399,7 +407,6 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
entitiesToSave.add(
|
||||
domain,
|
||||
domainHistory,
|
||||
ForeignKeyIndex.create(domain, domain.getDeletionTime()),
|
||||
EppResourceIndex.create(Key.create(domain)));
|
||||
if (allocationToken.isPresent()
|
||||
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
|
||||
@@ -481,7 +488,8 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
boolean isValidReservedCreate,
|
||||
boolean hasSignedMarks)
|
||||
throws NoGeneralRegistrationsInCurrentPhaseException,
|
||||
MustHaveSignedMarksInCurrentPhaseException {
|
||||
MustHaveSignedMarksInCurrentPhaseException,
|
||||
NoTrademarkedRegistrationsBeforeSunriseException {
|
||||
// We allow general registration during GA.
|
||||
TldState currentState = registry.getTldState(now);
|
||||
if (currentState.equals(GENERAL_AVAILABILITY)) {
|
||||
@@ -496,16 +504,23 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
// Bypass most TLD state checks if that behavior is specified by the token
|
||||
if (behavior.equals(RegistrationBehavior.BYPASS_TLD_STATE)
|
||||
|| behavior.equals(RegistrationBehavior.ANCHOR_TENANT)) {
|
||||
// If bypassing TLD state checks, a post-sunrise state is always fine
|
||||
if (!currentState.equals(START_DATE_SUNRISE)
|
||||
&& registry.getTldStateTransitions().headMap(now).containsValue(START_DATE_SUNRISE)) {
|
||||
return;
|
||||
}
|
||||
// Non-trademarked names with the state check bypassed are always available
|
||||
if (!claimsList.getClaimKey(domainLabel).isPresent()) {
|
||||
return;
|
||||
}
|
||||
if (!currentState.equals(START_DATE_SUNRISE)) {
|
||||
// Trademarked domains cannot be registered until after the sunrise period has ended, unless
|
||||
// a valid signed mark is provided. Signed marks can only be provided during sunrise.
|
||||
// Thus, when bypassing TLD state checks, a post-sunrise state is always fine.
|
||||
if (registry.getTldStateTransitions().headMap(now).containsValue(START_DATE_SUNRISE)) {
|
||||
return;
|
||||
} else {
|
||||
// If sunrise hasn't happened yet, trademarked domains are unavailable
|
||||
throw new NoTrademarkedRegistrationsBeforeSunriseException(domainLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, signed marks are necessary and sufficient in the sunrise period
|
||||
if (currentState.equals(START_DATE_SUNRISE)) {
|
||||
if (!hasSignedMarks) {
|
||||
@@ -724,6 +739,17 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
}
|
||||
}
|
||||
|
||||
/** Trademarked domains cannot be registered before the sunrise period. */
|
||||
static class NoTrademarkedRegistrationsBeforeSunriseException
|
||||
extends ParameterValuePolicyErrorException {
|
||||
public NoTrademarkedRegistrationsBeforeSunriseException(String domainLabel) {
|
||||
super(
|
||||
String.format(
|
||||
"The trademarked label %s cannot be registered before the sunrise period.",
|
||||
domainLabel));
|
||||
}
|
||||
}
|
||||
|
||||
/** Anchor tenant domain create is for the wrong number of years. */
|
||||
static class AnchorTenantCreatePeriodException extends ParameterValuePolicyErrorException {
|
||||
public AnchorTenantCreatePeriodException(int invalidYears) {
|
||||
@@ -733,4 +759,14 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
ANCHOR_TENANT_CREATE_VALID_YEARS, invalidYears));
|
||||
}
|
||||
}
|
||||
|
||||
/** Package domain registered for too many years. */
|
||||
static class PackageDomainRegisteredForTooManyYearsException extends CommandUseErrorException {
|
||||
public PackageDomainRegisteredForTooManyYearsException(String token) {
|
||||
super(
|
||||
String.format(
|
||||
"The package token %s cannot be used to register names for longer than 1 year.",
|
||||
token));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ import static google.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurr
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotInPredelegation;
|
||||
import static google.registry.model.ResourceTransferUtils.denyPendingTransfer;
|
||||
import static google.registry.model.ResourceTransferUtils.handlePendingTransferOnDelete;
|
||||
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.ADD_FIELDS;
|
||||
@@ -257,13 +256,13 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
Domain newDomain = builder.build();
|
||||
DomainHistory domainHistory =
|
||||
buildDomainHistory(newDomain, registry, now, durationUntilDelete, inAddGracePeriod);
|
||||
updateForeignKeyIndexDeletionTime(newDomain);
|
||||
handlePendingTransferOnDelete(existingDomain, newDomain, now, domainHistory);
|
||||
// Close the autorenew billing event and poll message. This may delete the poll message. Store
|
||||
// the updated recurring billing event, we'll need it later and can't reload it.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
BillingEvent.Recurring recurringBillingEvent =
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain, existingRecurring, now, domainHistory.getDomainHistoryId());
|
||||
// If there's a pending transfer, the gaining client's autorenew billing
|
||||
// event and poll message will already have been deleted in
|
||||
// ResourceDeleteFlow since it's listed in serverApproveEntities.
|
||||
|
||||
@@ -79,7 +79,7 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -112,6 +112,7 @@ import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.RegistrationBehavior;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -270,7 +271,13 @@ public class DomainFlowUtils {
|
||||
&& token.get().getDomainName().get().equals(domainName.toString())) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise check whether the metadata extension is being used by a superuser to specify that
|
||||
// Otherwise, check to see if we're using the specialized anchor tenant registration behavior on
|
||||
// the allocation token
|
||||
if (token.isPresent()
|
||||
&& token.get().getRegistrationBehavior().equals(RegistrationBehavior.ANCHOR_TENANT)) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise, check whether the metadata extension is being used by a superuser to specify that
|
||||
// it's an anchor tenant creation.
|
||||
return metadataExtension.isPresent() && metadataExtension.get().getIsAnchorTenant();
|
||||
}
|
||||
@@ -373,9 +380,7 @@ public class DomainFlowUtils {
|
||||
|
||||
/** Verify that no linked resources have disallowed statuses. */
|
||||
static void verifyNotInPendingDelete(
|
||||
Set<DesignatedContact> contacts,
|
||||
VKey<ContactResource> registrant,
|
||||
Set<VKey<Host>> nameservers)
|
||||
Set<DesignatedContact> contacts, VKey<Contact> registrant, Set<VKey<Host>> nameservers)
|
||||
throws EppException {
|
||||
ImmutableList.Builder<VKey<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
|
||||
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
|
||||
@@ -420,7 +425,7 @@ public class DomainFlowUtils {
|
||||
|
||||
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
|
||||
throws ParameterValuePolicyErrorException {
|
||||
ImmutableMultimap<Type, VKey<ContactResource>> contactsByType =
|
||||
ImmutableMultimap<Type, VKey<Contact>> contactsByType =
|
||||
contacts.stream()
|
||||
.collect(
|
||||
toImmutableSetMultimap(
|
||||
@@ -429,15 +434,13 @@ public class DomainFlowUtils {
|
||||
// If any contact type has multiple contacts:
|
||||
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
|
||||
// Find the duplicates.
|
||||
Map<Type, Collection<VKey<ContactResource>>> dupeKeysMap =
|
||||
Map<Type, Collection<VKey<Contact>>> dupeKeysMap =
|
||||
Maps.filterEntries(contactsByType.asMap(), e -> e.getValue().size() > 1);
|
||||
ImmutableList<VKey<ContactResource>> dupeKeys =
|
||||
ImmutableList<VKey<Contact>> dupeKeys =
|
||||
dupeKeysMap.values().stream().flatMap(Collection::stream).collect(toImmutableList());
|
||||
// Load the duplicates in one batch.
|
||||
Map<VKey<? extends ContactResource>, ContactResource> dupeContacts =
|
||||
tm().loadByKeys(dupeKeys);
|
||||
ImmutableMultimap.Builder<Type, VKey<ContactResource>> typesMap =
|
||||
new ImmutableMultimap.Builder<>();
|
||||
Map<VKey<? extends Contact>, Contact> dupeContacts = tm().loadByKeys(dupeKeys);
|
||||
ImmutableMultimap.Builder<Type, VKey<Contact>> typesMap = new ImmutableMultimap.Builder<>();
|
||||
dupeKeysMap.forEach(typesMap::putAll);
|
||||
// Create an error message showing the type and contact IDs of the duplicates.
|
||||
throw new DuplicateContactForRoleException(
|
||||
@@ -446,7 +449,7 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
static void validateRequiredContactsPresent(
|
||||
@Nullable VKey<ContactResource> registrant, Set<DesignatedContact> contacts)
|
||||
@Nullable VKey<Contact> registrant, Set<DesignatedContact> contacts)
|
||||
throws RequiredParameterMissingException {
|
||||
if (registrant == null) {
|
||||
throw new MissingRegistrantException();
|
||||
@@ -584,7 +587,10 @@ public class DomainFlowUtils {
|
||||
* <p>Returns the new autorenew recurring billing event.
|
||||
*/
|
||||
public static Recurring updateAutorenewRecurrenceEndTime(
|
||||
Domain domain, Recurring existingRecurring, DateTime newEndTime) {
|
||||
Domain domain,
|
||||
Recurring existingRecurring,
|
||||
DateTime newEndTime,
|
||||
@Nullable DomainHistoryId historyId) {
|
||||
Optional<PollMessage.Autorenew> autorenewPollMessage =
|
||||
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
|
||||
|
||||
@@ -592,20 +598,25 @@ public class DomainFlowUtils {
|
||||
// create a new one at the same id. This can happen if a transfer was requested on a domain
|
||||
// where all autorenew poll messages had already been delivered (this would cause the poll
|
||||
// message to be deleted), and then subsequently the transfer was canceled, rejected, or deleted
|
||||
// (which would cause the poll message to be recreated here).
|
||||
PollMessage.Autorenew updatedAutorenewPollMessage =
|
||||
autorenewPollMessage.isPresent()
|
||||
? autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build()
|
||||
: newAutorenewPollMessage(domain)
|
||||
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
|
||||
.setAutorenewEndTime(newEndTime)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(
|
||||
domain.getRepoId(), domain.getAutorenewPollMessageHistoryId()))
|
||||
.build();
|
||||
// (which would cause the poll message to be recreated here). In the latter case, the history id
|
||||
// of the event that created the new poll message will also be used.
|
||||
PollMessage.Autorenew updatedAutorenewPollMessage;
|
||||
if (autorenewPollMessage.isPresent()) {
|
||||
updatedAutorenewPollMessage =
|
||||
autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build();
|
||||
} else {
|
||||
checkNotNull(
|
||||
historyId, "Cannot create a new autorenew poll message without a domain history id");
|
||||
updatedAutorenewPollMessage =
|
||||
newAutorenewPollMessage(domain)
|
||||
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
|
||||
.setAutorenewEndTime(newEndTime)
|
||||
.setDomainHistoryId(historyId)
|
||||
.build();
|
||||
}
|
||||
|
||||
// If the resultant autorenew poll message would have no poll messages to deliver, then just
|
||||
// delete it. Otherwise save it with the new end time.
|
||||
// delete it. Otherwise, save it with the new end time.
|
||||
if (isAtOrAfter(updatedAutorenewPollMessage.getEventTime(), newEndTime)) {
|
||||
autorenewPollMessage.ifPresent(autorenew -> tm().delete(autorenew));
|
||||
} else {
|
||||
|
||||
@@ -30,6 +30,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic;
|
||||
@@ -42,6 +43,8 @@ import google.registry.model.domain.DomainCommand.Info.HostsRequest;
|
||||
import google.registry.model.domain.DomainInfoData;
|
||||
import google.registry.model.domain.fee06.FeeInfoCommandExtensionV06;
|
||||
import google.registry.model.domain.fee06.FeeInfoResponseExtensionV06;
|
||||
import google.registry.model.domain.packagetoken.PackageTokenExtension;
|
||||
import google.registry.model.domain.packagetoken.PackageTokenResponseExtension;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.rgp.RgpInfoExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
@@ -85,13 +88,14 @@ public final class DomainInfoFlow implements Flow {
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject DomainInfoFlowCustomLogic flowCustomLogic;
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
|
||||
@Inject
|
||||
DomainInfoFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(FeeInfoCommandExtensionV06.class);
|
||||
extensionManager.register(FeeInfoCommandExtensionV06.class, PackageTokenExtension.class);
|
||||
flowCustomLogic.beforeValidation();
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
@@ -150,6 +154,15 @@ public final class DomainInfoFlow implements Flow {
|
||||
if (!gracePeriodStatuses.isEmpty()) {
|
||||
extensions.add(RgpInfoExtension.create(gracePeriodStatuses));
|
||||
}
|
||||
Optional<PackageTokenExtension> packageInfo =
|
||||
eppInput.getSingleExtension(PackageTokenExtension.class);
|
||||
if (packageInfo.isPresent()) {
|
||||
// Package info was requested.
|
||||
if (isSuperuser || registrarId.equals(domain.getCurrentSponsorRegistrarId())) {
|
||||
// Only show package info to owning registrar or superusers
|
||||
extensions.add(PackageTokenResponseExtension.create(domain.getCurrentPackageToken()));
|
||||
}
|
||||
}
|
||||
Optional<FeeInfoCommandExtensionV06> feeInfo =
|
||||
eppInput.getSingleExtension(FeeInfoCommandExtensionV06.class);
|
||||
if (feeInfo.isPresent()) { // Fee check was requested.
|
||||
|
||||
@@ -30,6 +30,8 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
|
||||
@@ -119,6 +121,10 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.RemovePackageTokenOnNonPackageDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
|
||||
@@ -174,7 +180,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
verifyRenewAllowed(authInfo, existingDomain, command);
|
||||
verifyRenewAllowed(authInfo, existingDomain, command, allocationToken);
|
||||
|
||||
// If client passed an applicable static token this updates the domain
|
||||
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
|
||||
|
||||
int years = command.getPeriod().getValue();
|
||||
DateTime newExpirationTime =
|
||||
leapSafeAddYears(existingDomain.getRegistrationExpirationTime(), years); // Uncapped
|
||||
@@ -223,7 +233,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.build();
|
||||
// End the old autorenew billing event and poll message now. This may delete the poll message.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
now,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
Domain newDomain =
|
||||
existingDomain
|
||||
.asBuilder()
|
||||
@@ -231,9 +245,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
newAutorenewPollMessage.createVKey(),
|
||||
newAutorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
|
||||
.addGracePeriod(
|
||||
GracePeriod.forBillingEvent(
|
||||
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
|
||||
@@ -300,7 +312,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private void verifyRenewAllowed(Optional<AuthInfo> authInfo, Domain existingDomain, Renew command)
|
||||
private void verifyRenewAllowed(
|
||||
Optional<AuthInfo> authInfo,
|
||||
Domain existingDomain,
|
||||
Renew command,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
throws EppException {
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyNoDisallowedStatuses(existingDomain, RENEW_DISALLOWED_STATUSES);
|
||||
@@ -310,6 +326,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
checkHasBillingAccount(registrarId, existingDomain.getTld());
|
||||
}
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
// We only allow __REMOVE_PACKAGE__ token on promo package domains for now
|
||||
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
|
||||
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
|
||||
if (!command.getCurrentExpirationDate().equals(
|
||||
existingDomain.getRegistrationExpirationTime().toLocalDate())) {
|
||||
@@ -331,7 +349,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.setPeriodYears(years)
|
||||
.setCost(renewCost)
|
||||
.setEventTime(now)
|
||||
.setAllocationToken(allocationToken.map(AllocationToken::createVKey).orElse(null))
|
||||
.setAllocationToken(
|
||||
allocationToken
|
||||
.filter(t -> AllocationToken.TokenBehavior.DEFAULT.equals(t.getTokenBehavior()))
|
||||
.map(AllocationToken::createVKey)
|
||||
.orElse(null))
|
||||
.setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength()))
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
|
||||
@@ -27,7 +27,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
|
||||
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RESTORE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
@@ -188,7 +187,6 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
autorenewPollMessage,
|
||||
now,
|
||||
registrarId);
|
||||
updateForeignKeyIndexDeletionTime(newDomain);
|
||||
DomainHistory domainHistory = buildDomainHistory(newDomain, now);
|
||||
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
|
||||
tm().putAll(entitiesToSave.build());
|
||||
@@ -251,8 +249,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
.setGracePeriods(null)
|
||||
.setDeletePollMessage(null)
|
||||
.setAutorenewBillingEvent(autorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
// Clear the autorenew end time so if it had expired but is now explicitly being restored,
|
||||
// it won't immediately be deleted again.
|
||||
.setAutorenewEndTime(Optional.empty())
|
||||
|
||||
@@ -44,6 +44,7 @@ import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
@@ -55,7 +56,9 @@ import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
@@ -85,6 +88,18 @@ import org.joda.time.DateTime;
|
||||
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link google.registry.flows.exceptions.NotPendingTransferException}
|
||||
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_APPROVE)
|
||||
public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
@@ -97,6 +112,8 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
@Inject DomainHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
|
||||
@Inject EppInput eppInput;
|
||||
|
||||
@Inject DomainTransferApproveFlow() {}
|
||||
|
||||
@@ -106,11 +123,17 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
*/
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
extensionManager.register(MetadataExtension.class, AllocationTokenExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyHasPendingTransfer(existingDomain);
|
||||
verifyResourceOwnership(registrarId, existingDomain);
|
||||
@@ -155,9 +178,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
getOnlyElement(existingDomain.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null);
|
||||
if (autorenewGrace != null) {
|
||||
// During a normal transfer, if the domain is in the auto-renew grace period, the auto-renew
|
||||
// billing event is cancelled and the gaining registrar is charged for the one year renewal.
|
||||
// billing event is cancelled and the gaining registrar is charged for the one-year renewal.
|
||||
// But, if the superuser extension is used to request a transfer without an additional year
|
||||
// then the gaining registrar is not charged for the one year renewal and the losing registrar
|
||||
// then the gaining registrar is not charged for the one-year renewal and the losing registrar
|
||||
// still needs to be charged for the auto-renew.
|
||||
if (billingEvent.isPresent()) {
|
||||
entitiesToSave.add(
|
||||
@@ -171,7 +194,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
}
|
||||
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
|
||||
// up deleting the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
now,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
DateTime newExpirationTime =
|
||||
computeExDateForApprovalTime(existingDomain, now, transferData.getTransferPeriod());
|
||||
// Create a new autorenew event starting at the expiration time.
|
||||
@@ -217,9 +244,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
.build())
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(autorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
gainingClientAutorenewPollMessage.createVKey(),
|
||||
gainingClientAutorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(gainingClientAutorenewPollMessage.createVKey())
|
||||
// Remove all the old grace periods and add a new one for the transfer.
|
||||
.setGracePeriods(
|
||||
billingEvent
|
||||
|
||||
@@ -116,7 +116,8 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
|
||||
// may recreate the autorenew poll message if it was deleted when the transfer request was made.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, END_OF_TIME);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -117,7 +117,8 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
|
||||
// may end up recreating the poll message if it was deleted upon the transfer request.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, END_OF_TIME);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -47,6 +47,7 @@ import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.flows.exceptions.AlreadyPendingTransferException;
|
||||
import google.registry.flows.exceptions.InvalidTransferPeriodValueException;
|
||||
import google.registry.flows.exceptions.ObjectAlreadySponsoredException;
|
||||
@@ -56,11 +57,13 @@ import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Transfer;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.fee.FeeTransferCommandExtension;
|
||||
import google.registry.model.domain.fee.FeeTransformResponseExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
@@ -117,6 +120,18 @@ import org.joda.time.DateTime;
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
* @error {@link DomainFlowUtils.RegistrarMustBeActiveForThisOperationException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotInPromotionException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForRegistrarException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForTldException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
|
||||
public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
@@ -138,6 +153,8 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
|
||||
|
||||
@Inject DomainTransferRequestFlow() {}
|
||||
|
||||
@Override
|
||||
@@ -145,12 +162,19 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
extensionManager.register(
|
||||
DomainTransferRequestSuperuserExtension.class,
|
||||
FeeTransferCommandExtension.class,
|
||||
MetadataExtension.class);
|
||||
MetadataExtension.class,
|
||||
AllocationTokenExtension.class);
|
||||
validateRegistrarIsLoggedIn(gainingClientId);
|
||||
verifyRegistrarIsActive(gainingClientId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
gainingClientId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
|
||||
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
|
||||
Period period =
|
||||
@@ -183,9 +207,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
.setId(domainHistoryKey.getId())
|
||||
.setOtherRegistrarId(existingDomain.getCurrentSponsorRegistrarId());
|
||||
DateTime automaticTransferTime =
|
||||
superuserExtension.isPresent()
|
||||
? now.plusDays(superuserExtension.get().getAutomaticTransferLength())
|
||||
: now.plus(registry.getAutomaticTransferLength());
|
||||
superuserExtension
|
||||
.map(
|
||||
domainTransferRequestSuperuserExtension ->
|
||||
now.plusDays(
|
||||
domainTransferRequestSuperuserExtension.getAutomaticTransferLength()))
|
||||
.orElseGet(() -> now.plus(registry.getAutomaticTransferLength()));
|
||||
// If the domain will be in the auto-renew grace period at the moment of transfer, the transfer
|
||||
// will subsume the autorenew, so we don't add the normal extra year from the transfer.
|
||||
// The gaining registrar is still billed for the extra year; the losing registrar will get a
|
||||
@@ -234,7 +261,11 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
// the poll message if it has no events left. Note that if the automatic transfer succeeds, then
|
||||
// cloneProjectedAtTime() will replace these old autorenew entities with the server approve ones
|
||||
// that we've created in this flow and stored in pendingTransferData.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, automaticTransferTime);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
automaticTransferTime,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
Domain newDomain =
|
||||
existingDomain
|
||||
.asBuilder()
|
||||
@@ -298,7 +329,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
*
|
||||
* <p>Even if not required, this policy is desirable because it dramatically simplifies the logic
|
||||
* in transfer flows. Registrars appear to never request 2+ year transfers in practice, and they
|
||||
* can always decompose an multi-year transfer into a 1-year transfer followed by a manual renewal
|
||||
* can always decompose a multi-year transfer into a 1-year transfer followed by a manual renewal
|
||||
* afterwards. The <a href="https://tools.ietf.org/html/rfc5731#section-3.2.4">EPP Domain RFC,
|
||||
* section 3.2.4</a> says about EPP transfer periods that "the number of units available MAY be
|
||||
* subject to limits imposed by the server" so we're just limiting the units to one.
|
||||
@@ -343,7 +374,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
private DomainTransferResponse createResponse(
|
||||
Period period, Domain existingDomain, Domain newDomain, DateTime now) {
|
||||
// If the registration were approved this instant, this is what the new expiration would be,
|
||||
// because we cap at 10 years from the moment of approval. This is different than the server
|
||||
// because we cap at 10 years from the moment of approval. This is different from the server
|
||||
// approval new expiration time, which is capped at 10 years from the server approve time.
|
||||
DateTime approveNowExtendedRegistrationTime =
|
||||
computeExDateForApprovalTime(existingDomain, now, period);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.flows.domain.token;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
@@ -26,9 +27,12 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenBehavior;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
@@ -109,23 +113,27 @@ public class AllocationTokenFlowUtils {
|
||||
private void validateToken(
|
||||
InternetDomainName domainName, AllocationToken token, String registrarId, DateTime now)
|
||||
throws EppException {
|
||||
if (!token.getAllowedRegistrarIds().isEmpty()
|
||||
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
|
||||
throw new AllocationTokenNotValidForRegistrarException();
|
||||
}
|
||||
if (!token.getAllowedTlds().isEmpty()
|
||||
&& !token.getAllowedTlds().contains(domainName.parent().toString())) {
|
||||
throw new AllocationTokenNotValidForTldException();
|
||||
}
|
||||
if (token.getDomainName().isPresent()
|
||||
&& !token.getDomainName().get().equals(domainName.toString())) {
|
||||
throw new AllocationTokenNotValidForDomainException();
|
||||
}
|
||||
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
|
||||
// check the status transitions map if it's non-trivial.
|
||||
if (token.getTokenStatusTransitions().size() > 1
|
||||
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
|
||||
throw new AllocationTokenNotInPromotionException();
|
||||
|
||||
// Only tokens with default behavior require validation
|
||||
if (TokenBehavior.DEFAULT.equals(token.getTokenBehavior())) {
|
||||
if (!token.getAllowedRegistrarIds().isEmpty()
|
||||
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
|
||||
throw new AllocationTokenNotValidForRegistrarException();
|
||||
}
|
||||
if (!token.getAllowedTlds().isEmpty()
|
||||
&& !token.getAllowedTlds().contains(domainName.parent().toString())) {
|
||||
throw new AllocationTokenNotValidForTldException();
|
||||
}
|
||||
if (token.getDomainName().isPresent()
|
||||
&& !token.getDomainName().get().equals(domainName.toString())) {
|
||||
throw new AllocationTokenNotValidForDomainException();
|
||||
}
|
||||
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
|
||||
// check the status transitions map if it's non-trivial.
|
||||
if (token.getTokenStatusTransitions().size() > 1
|
||||
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
|
||||
throw new AllocationTokenNotInPromotionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +145,15 @@ public class AllocationTokenFlowUtils {
|
||||
// See https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04#section-2.1
|
||||
throw new InvalidAllocationTokenException();
|
||||
}
|
||||
Optional<AllocationToken> maybeTokenEntity =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
|
||||
|
||||
Optional<AllocationToken> maybeTokenEntity = AllocationToken.maybeGetStaticTokenInstance(token);
|
||||
if (maybeTokenEntity.isPresent()) {
|
||||
return maybeTokenEntity.get();
|
||||
}
|
||||
|
||||
maybeTokenEntity =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(AllocationToken.class, token)));
|
||||
|
||||
if (!maybeTokenEntity.isPresent()) {
|
||||
throw new InvalidAllocationTokenException();
|
||||
}
|
||||
@@ -187,6 +202,49 @@ public class AllocationTokenFlowUtils {
|
||||
tokenCustomLogic.validateToken(existingDomain, tokenEntity, registry, registrarId, now));
|
||||
}
|
||||
|
||||
public static void verifyTokenAllowedOnDomain(
|
||||
Domain domain, Optional<AllocationToken> allocationToken) throws EppException {
|
||||
|
||||
boolean domainHasPackageToken = domain.getCurrentPackageToken().isPresent();
|
||||
boolean hasRemovePackageToken =
|
||||
allocationToken.isPresent()
|
||||
&& TokenBehavior.REMOVE_PACKAGE.equals(allocationToken.get().getTokenBehavior());
|
||||
|
||||
if (hasRemovePackageToken && !domainHasPackageToken) {
|
||||
throw new RemovePackageTokenOnNonPackageDomainException();
|
||||
} else if (!hasRemovePackageToken && domainHasPackageToken) {
|
||||
throw new MissingRemovePackageTokenOnPackageDomainException();
|
||||
}
|
||||
}
|
||||
|
||||
public static Domain maybeApplyPackageRemovalToken(
|
||||
Domain domain, Optional<AllocationToken> allocationToken) {
|
||||
if (!allocationToken.isPresent()
|
||||
|| !TokenBehavior.REMOVE_PACKAGE.equals(allocationToken.get().getTokenBehavior())) {
|
||||
return domain;
|
||||
}
|
||||
|
||||
Recurring newRecurringBillingEvent =
|
||||
tm().loadByKey(domain.getAutorenewBillingEvent())
|
||||
.asBuilder()
|
||||
.setRenewalPriceBehavior(RenewalPriceBehavior.DEFAULT)
|
||||
.setRenewalPrice(null)
|
||||
.build();
|
||||
|
||||
// the Recurring billing event is reloaded later in the renew flow, so we synchronize changed
|
||||
// RecurringBillingEvent with storage manually
|
||||
tm().put(newRecurringBillingEvent);
|
||||
jpaTm().getEntityManager().flush();
|
||||
jpaTm().getEntityManager().clear();
|
||||
|
||||
// Remove current package token
|
||||
return domain
|
||||
.asBuilder()
|
||||
.setCurrentPackageToken(null)
|
||||
.setAutorenewBillingEvent(newRecurringBillingEvent.createVKey())
|
||||
.build();
|
||||
}
|
||||
|
||||
// Note: exception messages should be <= 32 characters long for domain check results
|
||||
|
||||
/** The allocation token is not currently valid. */
|
||||
@@ -234,4 +292,20 @@ public class AllocationTokenFlowUtils {
|
||||
super("The allocation token is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEPACKAGE__ token is missing on a renew package domain command */
|
||||
public static class MissingRemovePackageTokenOnPackageDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
MissingRemovePackageTokenOnPackageDomainException() {
|
||||
super("Domains that are inside packages cannot be explicitly renewed");
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEPACKAGE__ token is not allowed on non package domains */
|
||||
public static class RemovePackageTokenOnNonPackageDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
RemovePackageTokenOnNonPackageDomainException() {
|
||||
super("__REMOVEPACKAGE__ token is not allowed on non package domains");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostCommand.Create;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
@@ -135,7 +134,6 @@ public final class HostCreateFlow implements TransactionalFlow {
|
||||
ImmutableSet.of(
|
||||
newHost,
|
||||
historyBuilder.build(),
|
||||
ForeignKeyIndex.create(newHost, newHost.getDeletionTime()),
|
||||
EppResourceIndex.create(Key.create(newHost)));
|
||||
if (superordinateDomain.isPresent()) {
|
||||
tm().update(
|
||||
|
||||
@@ -57,7 +57,6 @@ import google.registry.model.host.HostCommand.Update;
|
||||
import google.registry.model.host.HostCommand.Update.AddRemove;
|
||||
import google.registry.model.host.HostCommand.Update.Change;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Objects;
|
||||
@@ -194,11 +193,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToInsert = new ImmutableSet.Builder<>();
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToUpdate = new ImmutableSet.Builder<>();
|
||||
entitiesToUpdate.add(newHost);
|
||||
// Keep the {@link ForeignKeyIndex} for this host up to date.
|
||||
if (isHostRename) {
|
||||
// Update the foreign key for the old host name and save one for the new host name.
|
||||
entitiesToUpdate.add(ForeignKeyIndex.create(existingHost, now));
|
||||
entitiesToUpdate.add(ForeignKeyIndex.create(newHost, newHost.getDeletionTime()));
|
||||
updateSuperordinateDomains(existingHost, newHost);
|
||||
}
|
||||
enqueueTasks(existingHost, newHost);
|
||||
|
||||
@@ -16,24 +16,16 @@ package google.registry.model;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.common.GaeUserIdConverter;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.EppResourceIndexBucket;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.server.Lock;
|
||||
import google.registry.model.server.ServerSecret;
|
||||
|
||||
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
|
||||
@DeleteAfterMigration
|
||||
@@ -42,28 +34,16 @@ public final class EntityClasses {
|
||||
/** Set of entity classes. */
|
||||
public static final ImmutableSet<Class<? extends ImmutableObject>> ALL_CLASSES =
|
||||
ImmutableSet.of(
|
||||
AllocationToken.class,
|
||||
Contact.class,
|
||||
ContactHistory.class,
|
||||
ContactResource.class,
|
||||
Domain.class,
|
||||
DomainHistory.class,
|
||||
EntityGroupRoot.class,
|
||||
EppResourceIndex.class,
|
||||
EppResourceIndexBucket.class,
|
||||
ForeignKeyIndex.ForeignKeyContactIndex.class,
|
||||
ForeignKeyIndex.ForeignKeyDomainIndex.class,
|
||||
ForeignKeyIndex.ForeignKeyHostIndex.class,
|
||||
GaeUserIdConverter.class,
|
||||
HistoryEntry.class,
|
||||
Host.class,
|
||||
HostHistory.class,
|
||||
Lock.class,
|
||||
PollMessage.class,
|
||||
PollMessage.Autorenew.class,
|
||||
PollMessage.OneTime.class,
|
||||
RdeRevision.class,
|
||||
Registrar.class,
|
||||
ServerSecret.class);
|
||||
HostHistory.class);
|
||||
|
||||
private EntityClasses() {}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
DateTime lastEppUpdateTime;
|
||||
|
||||
/** Status values associated with this resource. */
|
||||
@Ignore
|
||||
@Column(name = "statuses")
|
||||
// TODO(b/177567432): rename to "statuses" once we're off datastore.
|
||||
Set<StatusValue> status;
|
||||
|
||||
@@ -32,7 +32,7 @@ import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.EppResource.BuilderWithTransferData;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -345,10 +345,10 @@ public final class EppResourceUtils {
|
||||
public static ImmutableSet<VKey<Domain>> getLinkedDomainKeys(
|
||||
VKey<? extends EppResource> key, DateTime now, @Nullable Integer limit) {
|
||||
checkArgument(
|
||||
key.getKind().equals(ContactResource.class) || key.getKind().equals(Host.class),
|
||||
"key must be either VKey<ContactResource> or VKey<Host>, but it is %s",
|
||||
key.getKind().equals(Contact.class) || key.getKind().equals(Host.class),
|
||||
"key must be either VKey<Contact> or VKey<Host>, but it is %s",
|
||||
key);
|
||||
boolean isContactKey = key.getKind().equals(ContactResource.class);
|
||||
boolean isContactKey = key.getKind().equals(Contact.class);
|
||||
if (tm().isOfy()) {
|
||||
com.googlecode.objectify.cmd.Query<Domain> query =
|
||||
auditedOfy()
|
||||
|
||||
@@ -257,7 +257,7 @@ public abstract class ImmutableObject implements Cloneable {
|
||||
return (Map<String, Object>) toMapRecursive(this);
|
||||
}
|
||||
|
||||
public VKey createVKey() {
|
||||
public VKey<? extends ImmutableObject> createVKey() {
|
||||
throw new UnsupportedOperationException("VKey creation is not supported for this entity");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,13 +23,11 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import google.registry.model.EppResource.BuilderWithTransferData;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import google.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
@@ -60,7 +58,7 @@ public final class ResourceTransferUtils {
|
||||
EppResource eppResource, TransferData transferData) {
|
||||
assertIsContactOrDomain(eppResource);
|
||||
TransferResponse.Builder<? extends TransferResponse, ?> builder;
|
||||
if (eppResource instanceof ContactResource) {
|
||||
if (eppResource instanceof Contact) {
|
||||
builder = new ContactTransferResponse.Builder().setContactId(eppResource.getForeignKey());
|
||||
} else {
|
||||
DomainTransferData domainTransferData = (DomainTransferData) transferData;
|
||||
@@ -93,7 +91,7 @@ public final class ResourceTransferUtils {
|
||||
boolean actionResult,
|
||||
DateTime processedDate) {
|
||||
assertIsContactOrDomain(eppResource);
|
||||
return eppResource instanceof ContactResource
|
||||
return eppResource instanceof Contact
|
||||
? ContactPendingActionNotificationResponse.create(
|
||||
eppResource.getForeignKey(), actionResult, transferRequestTrid, processedDate)
|
||||
: DomainPendingActionNotificationResponse.create(
|
||||
@@ -101,14 +99,7 @@ public final class ResourceTransferUtils {
|
||||
}
|
||||
|
||||
private static void assertIsContactOrDomain(EppResource eppResource) {
|
||||
checkState(eppResource instanceof ContactResource || eppResource instanceof Domain);
|
||||
}
|
||||
|
||||
/** Update the relevant {@link ForeignKeyIndex} to cache the new deletion time. */
|
||||
public static <R extends EppResource> void updateForeignKeyIndexDeletionTime(R resource) {
|
||||
if (resource instanceof ForeignKeyedEppResource) {
|
||||
tm().insert(ForeignKeyIndex.create(resource, resource.getDeletionTime()));
|
||||
}
|
||||
checkState(eppResource instanceof Contact || eppResource instanceof Domain);
|
||||
}
|
||||
|
||||
/** If there is a transfer out, delete the server-approve entities and enqueue a poll message. */
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2021 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.model.annotations;
|
||||
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation for an Objectify {@link Entity} to indicate that it is in the cross-TLD entity group.
|
||||
*
|
||||
* <p>This means that the entity's <code>@Parent</code> field has to have the value of {@link
|
||||
* EntityGroupRoot#getCrossTldKey}.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Inherited
|
||||
public @interface InCrossTld {}
|
||||
@@ -212,6 +212,9 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return nullToEmptyImmutableCopy(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract VKey<? extends BillingEvent> createVKey();
|
||||
|
||||
/** Override Buildable.asBuilder() to give this method stronger typing. */
|
||||
@Override
|
||||
public abstract Builder<?, ?> asBuilder();
|
||||
|
||||
@@ -14,26 +14,22 @@
|
||||
|
||||
package google.registry.model.common;
|
||||
|
||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
/** A singleton entity in Datastore. */
|
||||
/**
|
||||
* A singleton entity in the database.
|
||||
*
|
||||
* <p>This class should not be deleted after the migration, because there is still a concept of
|
||||
* singleton in SQL. We should remove the ofy @Id annotation after all of its subclass are Ofy-free.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
@MappedSuperclass
|
||||
@InCrossTld
|
||||
public abstract class CrossTldSingleton extends ImmutableObject {
|
||||
|
||||
public static final long SINGLETON_ID = 1; // There is always exactly one of these.
|
||||
|
||||
@Id @javax.persistence.Id long id = SINGLETON_ID;
|
||||
|
||||
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2017 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.model.common;
|
||||
|
||||
import com.google.apphosting.api.ApiProxy;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The root key for the entity group which is known as the cross-tld entity group for historical
|
||||
* reasons.
|
||||
*
|
||||
* <p>This exists as a storage place for common configuration options and global settings that
|
||||
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
|
||||
* reason this common entity group exists is because it enables strongly consistent queries and
|
||||
* updates across this seldomly updated data. This shared entity group also helps cut down on a
|
||||
* potential ballooning in the number of entity groups enlisted in transactions.
|
||||
*
|
||||
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in a
|
||||
* single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was the
|
||||
* entity group for the single namespace where global data applicable for all TLDs lived.
|
||||
*/
|
||||
@Entity
|
||||
@DeleteAfterMigration
|
||||
public class EntityGroupRoot extends BackupGroupRoot {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
/** The root key for cross-tld resources such as registrars. */
|
||||
public static @Nullable Key<EntityGroupRoot> getCrossTldKey() {
|
||||
// If we cannot get a current environment, calling Key.create() will fail. Instead we return a
|
||||
// null in cases where this key is not actually needed (for example when loading an entity from
|
||||
// SQL) to initialize an object, to avoid having to register a DatastoreEntityExtension in
|
||||
// tests.
|
||||
return ApiProxy.getCurrentEnvironment() == null
|
||||
? null
|
||||
: Key.create(EntityGroupRoot.class, "cross-tld");
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import static org.joda.time.DateTimeZone.UTC;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ContiguousSet;
|
||||
import com.google.common.collect.Range;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
@@ -44,7 +43,6 @@ import org.joda.time.DateTime;
|
||||
* allows it to be embeddable with no translation needed and also delays parsing of the string on
|
||||
* load until it's actually needed.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class TimeOfYear extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
|
||||
@@ -63,5 +63,7 @@ public enum ConsolePermission {
|
||||
/** View announcements in the UI. */
|
||||
VIEW_ANNOUNCEMENTS,
|
||||
/** Viewing a record of actions performed in the UI for a particular registrar. */
|
||||
VIEW_ACTIVITY_LOG
|
||||
VIEW_ACTIVITY_LOG,
|
||||
/** View and perform registry locks. */
|
||||
REGISTRY_LOCK
|
||||
}
|
||||
|
||||
@@ -44,7 +44,8 @@ public class ConsoleRoleDefinitions {
|
||||
ConsolePermission.VIEW_OPERATIONAL_DATA,
|
||||
ConsolePermission.SEND_ANNOUNCEMENTS,
|
||||
ConsolePermission.VIEW_ANNOUNCEMENTS,
|
||||
ConsolePermission.VIEW_ACTIVITY_LOG);
|
||||
ConsolePermission.VIEW_ACTIVITY_LOG,
|
||||
ConsolePermission.REGISTRY_LOCK);
|
||||
|
||||
/** Permissions for a registry support lead. */
|
||||
static final ImmutableSet<ConsolePermission> SUPPORT_LEAD_PERMISSIONS =
|
||||
@@ -76,10 +77,17 @@ public class ConsoleRoleDefinitions {
|
||||
ConsolePermission.VIEW_OPERATIONAL_DATA,
|
||||
ConsolePermission.VIEW_ANNOUNCEMENTS);
|
||||
|
||||
/** Permissions for a registrar partner account manager that can perform registry locks. */
|
||||
static final ImmutableSet<ConsolePermission> ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS =
|
||||
new ImmutableSet.Builder<ConsolePermission>()
|
||||
.addAll(ACCOUNT_MANAGER_PERMISSIONS)
|
||||
.add(ConsolePermission.REGISTRY_LOCK)
|
||||
.build();
|
||||
|
||||
/** Permissions for the tech contact of a registrar. */
|
||||
static final ImmutableSet<ConsolePermission> TECH_CONTACT_PERMISSIONS =
|
||||
new ImmutableSet.Builder<ConsolePermission>()
|
||||
.addAll(ACCOUNT_MANAGER_PERMISSIONS)
|
||||
.addAll(ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS)
|
||||
.add(
|
||||
ConsolePermission.MANAGE_ACCREDITATION,
|
||||
ConsolePermission.CONFIGURE_EPP_CONNECTION,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.model.console;
|
||||
|
||||
import static google.registry.model.console.ConsoleRoleDefinitions.ACCOUNT_MANAGER_PERMISSIONS;
|
||||
import static google.registry.model.console.ConsoleRoleDefinitions.ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS;
|
||||
import static google.registry.model.console.ConsoleRoleDefinitions.PRIMARY_CONTACT_PERMISSIONS;
|
||||
import static google.registry.model.console.ConsoleRoleDefinitions.TECH_CONTACT_PERMISSIONS;
|
||||
|
||||
@@ -25,6 +26,8 @@ public enum RegistrarRole {
|
||||
|
||||
/** The user is a standard account manager at a registrar. */
|
||||
ACCOUNT_MANAGER(ACCOUNT_MANAGER_PERMISSIONS),
|
||||
/** The user is an account manager that can perform registry locks. */
|
||||
ACCOUNT_MANAGER_WITH_REGISTRY_LOCK(ACCOUNT_MANAGER_WITH_REGISTRY_LOCK_PERMISSIONS),
|
||||
/** The user is a technical contact of a registrar. */
|
||||
TECH_CONTACT(TECH_CONTACT_PERMISSIONS),
|
||||
/** The user is the primary contact at a registrar. */
|
||||
|
||||
@@ -22,30 +22,42 @@ import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/** A console user, either a registry employee or a registrar partner. */
|
||||
public class User extends ImmutableObject implements Buildable {
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "gaiaId", name = "user_gaia_id_idx"),
|
||||
@Index(columnList = "emailAddress", name = "user_email_address_idx")
|
||||
})
|
||||
public class User extends BackupGroupRoot implements Buildable {
|
||||
|
||||
/** Autogenerated unique ID of this user. */
|
||||
private long id;
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/** GAIA ID associated with the user in question. */
|
||||
@Column(nullable = false)
|
||||
private String gaiaId;
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@Column(nullable = false)
|
||||
private String emailAddress;
|
||||
|
||||
/** Roles (which grant permissions) associated with this user. */
|
||||
@Column(nullable = false)
|
||||
private UserRoles userRoles;
|
||||
|
||||
/**
|
||||
* Whether the contact is allowed to set their registry lock password through the registrar
|
||||
* console. This will be set to false on contact creation and when the user sets a password.
|
||||
*/
|
||||
boolean allowedToSetRegistryLockPassword = false;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
@@ -55,6 +67,10 @@ public class User extends ImmutableObject implements Buildable {
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getGaiaId() {
|
||||
return gaiaId;
|
||||
}
|
||||
@@ -67,11 +83,7 @@ public class User extends ImmutableObject implements Buildable {
|
||||
return userRoles;
|
||||
}
|
||||
|
||||
public boolean isAllowedToSetRegistryLockPassword() {
|
||||
return allowedToSetRegistryLockPassword;
|
||||
}
|
||||
|
||||
public boolean isRegistryLockAllowed() {
|
||||
public boolean hasRegistryLockPassword() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
@@ -85,6 +97,22 @@ public class User extends ImmutableObject implements Buildable {
|
||||
.equals(registryLockPasswordHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the user has the registry lock permission on any registrar or globally.
|
||||
*
|
||||
* <p>If so, they should be allowed to (re)set their registry lock password.
|
||||
*/
|
||||
public boolean hasAnyRegistryLockPermission() {
|
||||
if (userRoles == null) {
|
||||
return false;
|
||||
}
|
||||
if (userRoles.isAdmin() || userRoles.hasGlobalPermission(ConsolePermission.REGISTRY_LOCK)) {
|
||||
return true;
|
||||
}
|
||||
return userRoles.getRegistrarRoles().values().stream()
|
||||
.anyMatch(role -> role.hasPermission(ConsolePermission.REGISTRY_LOCK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
@@ -99,6 +127,7 @@ public class User extends ImmutableObject implements Buildable {
|
||||
super(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User build() {
|
||||
checkArgumentNotNull(getInstance().gaiaId, "Gaia ID cannot be null");
|
||||
checkArgumentNotNull(getInstance().emailAddress, "Email address cannot be null");
|
||||
@@ -123,25 +152,22 @@ public class User extends ImmutableObject implements Buildable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
|
||||
if (allowedToSetRegistryLockPassword) {
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
}
|
||||
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
|
||||
public Builder removeRegistryLockPassword() {
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().allowedToSetRegistryLockPassword,
|
||||
"Not allowed to set registry lock password for this user");
|
||||
getInstance().hasAnyRegistryLockPermission(), "User has no registry lock permission");
|
||||
checkArgument(
|
||||
!getInstance().hasRegistryLockPassword(), "User already has a password, remove it first");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
getInstance().registryLockPasswordSalt = base64().encode(SALT_SUPPLIER.get());
|
||||
getInstance().registryLockPasswordHash =
|
||||
hashPassword(registryLockPassword, getInstance().registryLockPasswordSalt);
|
||||
getInstance().allowedToSetRegistryLockPassword = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2022 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.model.console;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** Data access object for {@link User} objects to simplify saving and retrieval. */
|
||||
public class UserDao {
|
||||
|
||||
/** Retrieves the one user with this email address if it exists. */
|
||||
public static Optional<User> loadUser(String emailAddress) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query("FROM User WHERE emailAddress = :emailAddress", User.class)
|
||||
.setParameter("emailAddress", emailAddress)
|
||||
.getResultStream()
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
/** Saves the given user, checking that no existing user already exists with this email. */
|
||||
public static void saveUser(User user) {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
// Check for an existing user (the unique constraint protects us, but this gives a
|
||||
// nicer exception)
|
||||
Optional<User> maybeSavedUser = loadUser(user.getEmailAddress());
|
||||
if (maybeSavedUser.isPresent()) {
|
||||
User savedUser = maybeSavedUser.get();
|
||||
checkArgument(
|
||||
savedUser.getId().equals(user.getId()),
|
||||
String.format(
|
||||
"Attempted save of User with email address %s and ID %s, user with that"
|
||||
+ " email already exists with ID %s",
|
||||
user.getEmailAddress(), user.getId(), savedUser.getId()));
|
||||
}
|
||||
jpaTm().put(user);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@ import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
|
||||
/**
|
||||
* Contains the global and per-registrar roles for a given user.
|
||||
@@ -28,15 +32,19 @@ import java.util.Map;
|
||||
* <p>See <a href="https://go/nomulus-console-authz">go/nomulus-console-authz</a> for more
|
||||
* information.
|
||||
*/
|
||||
@Embeddable
|
||||
public class UserRoles extends ImmutableObject implements Buildable {
|
||||
|
||||
/** Whether the user is a global admin, who has access to everything. */
|
||||
@Column(nullable = false)
|
||||
private boolean isAdmin = false;
|
||||
|
||||
/**
|
||||
* The global role (e.g. {@link GlobalRole#SUPPORT_AGENT}) that the user has across all
|
||||
* registrars.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private GlobalRole globalRole = GlobalRole.NONE;
|
||||
|
||||
/** Any per-registrar roles that this user may have. */
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
@@ -23,6 +22,10 @@ import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -32,35 +35,35 @@ import org.joda.time.DateTime;
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "Contact")
|
||||
@javax.persistence.Table(
|
||||
@com.googlecode.objectify.annotation.Entity
|
||||
@Table(
|
||||
name = "Contact",
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "creationTime"),
|
||||
@javax.persistence.Index(columnList = "currentSponsorRegistrarId"),
|
||||
@javax.persistence.Index(columnList = "deletionTime"),
|
||||
@javax.persistence.Index(columnList = "contactId"),
|
||||
@javax.persistence.Index(columnList = "searchName")
|
||||
@Index(columnList = "creationTime"),
|
||||
@Index(columnList = "currentSponsorRegistrarId"),
|
||||
@Index(columnList = "deletionTime"),
|
||||
@Index(columnList = "contactId"),
|
||||
@Index(columnList = "searchName")
|
||||
})
|
||||
@ExternalMessagingName("contact")
|
||||
@WithStringVKey
|
||||
@Access(AccessType.FIELD)
|
||||
public class ContactResource extends ContactBase implements ForeignKeyedEppResource {
|
||||
public class Contact extends ContactBase implements ForeignKeyedEppResource {
|
||||
|
||||
@Override
|
||||
public VKey<ContactResource> createVKey() {
|
||||
return VKey.create(ContactResource.class, getRepoId(), Key.create(this));
|
||||
public VKey<Contact> createVKey() {
|
||||
return VKey.create(Contact.class, getRepoId(), Key.create(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@javax.persistence.Id
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
public String getRepoId() {
|
||||
return super.getRepoId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactResource cloneProjectedAtTime(DateTime now) {
|
||||
public Contact cloneProjectedAtTime(DateTime now) {
|
||||
return ContactBase.cloneContactProjectedAtTime(this, now);
|
||||
}
|
||||
|
||||
@@ -69,12 +72,12 @@ public class ContactResource extends ContactBase implements ForeignKeyedEppResou
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link ContactResource}, since it is immutable. */
|
||||
public static class Builder extends ContactBase.Builder<ContactResource, Builder> {
|
||||
/** A builder for constructing {@link Contact}, since it is immutable. */
|
||||
public static class Builder extends ContactBase.Builder<Contact, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
private Builder(ContactResource instance) {
|
||||
private Builder(Contact instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.Address;
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
@@ -30,7 +29,6 @@ import javax.persistence.Embeddable;
|
||||
*
|
||||
* @see PostalInfo
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class ContactAddress extends Address {
|
||||
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/** A version of authInfo specifically for contacts. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
|
||||
public class ContactAuthInfo extends AuthInfo {
|
||||
public static ContactAuthInfo create(PasswordAuth pw) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
@@ -66,9 +67,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
|
||||
/**
|
||||
* Localized postal info for the contact. All contained values must be representable in the 7-bit
|
||||
* US-ASCII character set. Personal info; cleared by {@link ContactResource.Builder#wipeOut}.
|
||||
* US-ASCII character set. Personal info; cleared by {@link Contact.Builder#wipeOut}.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "name", column = @Column(name = "addr_local_name")),
|
||||
@@ -94,9 +95,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
|
||||
/**
|
||||
* Internationalized postal info for the contact. Personal info; cleared by {@link
|
||||
* ContactResource.Builder#wipeOut}.
|
||||
* Contact.Builder#wipeOut}.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")),
|
||||
@@ -123,12 +124,12 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
/**
|
||||
* Contact name used for name searches. This is set automatically to be the internationalized
|
||||
* postal name, or if null, the localized postal name, or if that is null as well, null. Personal
|
||||
* info; cleared by {@link ContactResource.Builder#wipeOut}.
|
||||
* info; cleared by {@link Contact.Builder#wipeOut}.
|
||||
*/
|
||||
@Index String searchName;
|
||||
|
||||
/** Contact’s voice number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
/** Contact’s voice number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")),
|
||||
@@ -136,8 +137,8 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
})
|
||||
ContactPhoneNumber voice;
|
||||
|
||||
/** Contact’s fax number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
/** Contact’s fax number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")),
|
||||
@@ -145,11 +146,12 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
})
|
||||
ContactPhoneNumber fax;
|
||||
|
||||
/** Contact’s email address. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
/** Contact’s email address. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
String email;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the contact. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
|
||||
@@ -158,7 +160,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
ContactAuthInfo authInfo;
|
||||
|
||||
/** Data about any pending or past transfers on this contact. */
|
||||
ContactTransferData transferData;
|
||||
@Ignore ContactTransferData transferData;
|
||||
|
||||
/**
|
||||
* The time that this resource was last transferred.
|
||||
@@ -171,6 +173,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
// the wipeOut() function, so that data is not kept around for deleted contacts.
|
||||
|
||||
/** Disclosure policy. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "name", column = @Column(name = "disclose_types_name")),
|
||||
@@ -187,7 +190,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
public VKey<? extends ContactBase> createVKey() {
|
||||
throw new UnsupportedOperationException(
|
||||
"ContactBase is not an actual persisted entity you can create a key to;"
|
||||
+ " use ContactResource instead");
|
||||
+ " use Contact instead");
|
||||
}
|
||||
|
||||
@OnLoad
|
||||
@@ -291,7 +294,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link ContactResource}, since it is immutable. */
|
||||
/** A builder for constructing {@link Contact}, since it is immutable. */
|
||||
public static class Builder<T extends ContactBase, B extends Builder<T, B>>
|
||||
extends EppResource.Builder<T, B> implements BuilderWithTransferData<ContactTransferData, B> {
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ import javax.xml.bind.annotation.XmlType;
|
||||
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
/** A collection of {@link ContactResource} commands. */
|
||||
/** A collection of {@link Contact} commands. */
|
||||
public class ContactCommand {
|
||||
|
||||
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@XmlTransient
|
||||
public static class ContactCreateOrChange extends ImmutableObject
|
||||
implements ResourceCreateOrChange<ContactResource.Builder> {
|
||||
implements ResourceCreateOrChange<Contact.Builder> {
|
||||
|
||||
/** Postal info for the contact. */
|
||||
List<PostalInfo> postalInfo;
|
||||
@@ -111,13 +111,13 @@ public class ContactCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* A create command for a {@link ContactResource}, mapping "createType" from <a
|
||||
* A create command for a {@link Contact}, mapping "createType" from <a
|
||||
* href="http://tools.ietf.org/html/rfc5733">RFC5733</a>}.
|
||||
*/
|
||||
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
|
||||
@XmlRootElement
|
||||
public static class Create extends ContactCreateOrChange
|
||||
implements SingleResourceCommand, ResourceCreateOrChange<ContactResource.Builder> {
|
||||
implements SingleResourceCommand, ResourceCreateOrChange<Contact.Builder> {
|
||||
/**
|
||||
* Unique identifier for this contact.
|
||||
*
|
||||
@@ -139,29 +139,29 @@ public class ContactCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/** A delete command for a {@link ContactResource}. */
|
||||
/** A delete command for a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
public static class Delete extends AbstractSingleResourceCommand {}
|
||||
|
||||
/** An info request for a {@link ContactResource}. */
|
||||
/** An info request for a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {"targetId", "authInfo"})
|
||||
public static class Info extends AbstractContactAuthCommand {}
|
||||
|
||||
/** A check request for {@link ContactResource}. */
|
||||
/** A check request for {@link Contact}. */
|
||||
@XmlRootElement
|
||||
public static class Check extends ResourceCheck {}
|
||||
|
||||
/** A transfer operation for a {@link ContactResource}. */
|
||||
/** A transfer operation for a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {"targetId", "authInfo"})
|
||||
public static class Transfer extends AbstractContactAuthCommand {}
|
||||
|
||||
/** An update to a {@link ContactResource}. */
|
||||
/** An update to a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
|
||||
public static class Update
|
||||
extends ResourceUpdate<Update.AddRemove, ContactResource.Builder, Update.Change> {
|
||||
extends ResourceUpdate<Update.AddRemove, Contact.Builder, Update.Change> {
|
||||
|
||||
@XmlElement(name = "chg")
|
||||
protected Change innerChange;
|
||||
|
||||
@@ -58,7 +58,7 @@ import javax.persistence.PostLoad;
|
||||
@IdClass(ContactHistoryId.class)
|
||||
public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
|
||||
// Store ContactBase instead of ContactResource so we don't pick up its @Id
|
||||
// Store ContactBase instead of Contact so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
@DoNotCompare @Nullable ContactBase contactBase;
|
||||
|
||||
@@ -73,7 +73,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
/** This method is private because it is only used by Hibernate. */
|
||||
@SuppressWarnings("unused")
|
||||
private void setContactRepoId(String contactRepoId) {
|
||||
parent = Key.create(ContactResource.class, contactRepoId);
|
||||
parent = Key.create(Contact.class, contactRepoId);
|
||||
}
|
||||
|
||||
@Id
|
||||
@@ -98,9 +98,9 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
return Optional.ofNullable(contactBase);
|
||||
}
|
||||
|
||||
/** The key to the {@link ContactResource} this is based off of. */
|
||||
public VKey<ContactResource> getParentVKey() {
|
||||
return VKey.create(ContactResource.class, getContactRepoId());
|
||||
/** The key to the {@link Contact} this is based off of. */
|
||||
public VKey<Contact> getParentVKey() {
|
||||
return VKey.create(Contact.class, getContactRepoId());
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance for this entity. */
|
||||
@@ -112,8 +112,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
|
||||
@Override
|
||||
public Optional<? extends EppResource> getResourceAtPointInTime() {
|
||||
return getContactBase()
|
||||
.map(contactBase -> new ContactResource.Builder().copyFrom(contactBase).build());
|
||||
return getContactBase().map(contactBase -> new Contact.Builder().copyFrom(contactBase).build());
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
@@ -210,7 +209,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
}
|
||||
|
||||
public Builder setContactRepoId(String contactRepoId) {
|
||||
getInstance().parent = Key.create(ContactResource.class, contactRepoId);
|
||||
getInstance().parent = Key.create(Contact.class, contactRepoId);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,20 +14,18 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.PhoneNumber;
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
/**
|
||||
* EPP Contact Phone Number
|
||||
*
|
||||
* <p>This class is embedded inside a {@link ContactResource} hold the phone number of an EPP
|
||||
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
|
||||
* still necessary to pick up the contact namespace.
|
||||
* <p>This class is embedded inside a {@link Contact} hold the phone number of an EPP contact. The
|
||||
* fields are all defined in the parent class {@link PhoneNumber}, but the subclass is still
|
||||
* necessary to pick up the contact namespace.
|
||||
*
|
||||
* @see ContactResource
|
||||
* @see Contact
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class ContactPhoneNumber extends PhoneNumber {
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.model.contact;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
@@ -30,7 +29,6 @@ import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/** The "discloseType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
|
||||
public class Disclose extends ImmutableObject implements UnsafeSerializable {
|
||||
@@ -79,7 +77,6 @@ public class Disclose extends ImmutableObject implements UnsafeSerializable {
|
||||
}
|
||||
|
||||
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
public static class PostalInfoChoice extends ImmutableObject implements Serializable {
|
||||
@XmlAttribute
|
||||
PostalInfo.Type type;
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.model.contact;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.Buildable.Overlayable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
@@ -36,7 +35,6 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
* Implementation of both "postalInfoType" and "chgPostalInfoType" from <a href=
|
||||
* "http://tools.ietf.org/html/rfc5733">RFC5733</a>.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "address", "type"})
|
||||
public class PostalInfo extends ImmutableObject
|
||||
|
||||
@@ -17,12 +17,11 @@ package google.registry.model.domain;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.xml.bind.annotation.XmlEnumValue;
|
||||
@@ -45,7 +44,6 @@ import javax.xml.bind.annotation.XmlEnumValue;
|
||||
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
|
||||
* - Contact and Client Identifiers</a>
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class DesignatedContact extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@@ -64,7 +62,7 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
|
||||
REGISTRANT
|
||||
}
|
||||
|
||||
public static DesignatedContact create(Type type, VKey<ContactResource> contact) {
|
||||
public static DesignatedContact create(Type type, VKey<Contact> contact) {
|
||||
DesignatedContact instance = new DesignatedContact();
|
||||
instance.type = type;
|
||||
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
|
||||
@@ -74,14 +72,14 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
|
||||
|
||||
Type type;
|
||||
|
||||
@Index Key<ContactResource> contact;
|
||||
@Ignore VKey<ContactResource> contactVKey;
|
||||
@Index Key<Contact> contact;
|
||||
@Ignore VKey<Contact> contactVKey;
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getContactKey() {
|
||||
public VKey<Contact> getContactKey() {
|
||||
return contactVKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,13 +90,13 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Column(name = "host_repo_id")
|
||||
public Set<VKey<Host>> getNsHosts() {
|
||||
return super.nsHosts;
|
||||
return nsHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of {@link GracePeriod} associated with the domain.
|
||||
*
|
||||
* <p>This is the getter method specific for Hibernate to access the field so it is set to
|
||||
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
|
||||
* private. The caller can use the public {@link #getGracePeriods()} to get the grace periods.
|
||||
*
|
||||
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
|
||||
@@ -104,10 +104,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
* deleting the whole entry from the table when the {@link GracePeriod} is removed from the set.
|
||||
*/
|
||||
@Access(AccessType.PROPERTY)
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
orphanRemoval = true)
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(
|
||||
name = "domainRepoId",
|
||||
referencedColumnName = "repoId",
|
||||
@@ -121,7 +118,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
/**
|
||||
* Returns the set of {@link DelegationSignerData} associated with the domain.
|
||||
*
|
||||
* <p>This is the getter method specific for Hibernate to access the field so it is set to
|
||||
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
|
||||
* private. The caller can use the public {@link #getDsData()} to get the DS data.
|
||||
*
|
||||
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
|
||||
@@ -130,10 +127,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
* the set.
|
||||
*/
|
||||
@Access(AccessType.PROPERTY)
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
orphanRemoval = true)
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(
|
||||
name = "domainRepoId",
|
||||
referencedColumnName = "repoId",
|
||||
@@ -182,10 +176,9 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
}
|
||||
|
||||
public Builder copyFrom(DomainBase domainBase) {
|
||||
this.getInstance().copyUpdateTimestamp(domainBase);
|
||||
return this.setAuthInfo(domainBase.getAuthInfo())
|
||||
.setAutorenewPollMessage(
|
||||
domainBase.getAutorenewPollMessage(), domainBase.getAutorenewPollMessageHistoryId())
|
||||
getInstance().copyUpdateTimestamp(domainBase);
|
||||
return setAuthInfo(domainBase.getAuthInfo())
|
||||
.setAutorenewPollMessage(domainBase.getAutorenewPollMessage())
|
||||
.setAutorenewBillingEvent(domainBase.getAutorenewBillingEvent())
|
||||
.setAutorenewEndTime(domainBase.getAutorenewEndTime())
|
||||
.setContacts(domainBase.getContacts())
|
||||
@@ -210,7 +203,8 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
.setSubordinateHosts(domainBase.getSubordinateHosts())
|
||||
.setStatusValues(domainBase.getStatusValues())
|
||||
.setTransferData(domainBase.getTransferData())
|
||||
.setDnsRefreshRequestTime(domainBase.getDnsRefreshRequestTime());
|
||||
.setDnsRefreshRequestTime(domainBase.getDnsRefreshRequestTime())
|
||||
.setCurrentPackageToken(domainBase.getCurrentPackageToken().orElse(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
/** A version of authInfo specifically for domains. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class DomainAuthInfo extends AuthInfo {
|
||||
public static DomainAuthInfo create(PasswordAuth pw) {
|
||||
DomainAuthInfo instance = new DomainAuthInfo();
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
@@ -51,15 +50,14 @@ import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
@@ -131,13 +129,14 @@ public class DomainBase extends EppResource
|
||||
@EmptySetToNull @Index @Transient Set<VKey<Host>> nsHosts;
|
||||
|
||||
/** Contacts. */
|
||||
VKey<ContactResource> adminContact;
|
||||
VKey<Contact> adminContact;
|
||||
|
||||
VKey<ContactResource> billingContact;
|
||||
VKey<ContactResource> techContact;
|
||||
VKey<ContactResource> registrantContact;
|
||||
VKey<Contact> billingContact;
|
||||
VKey<Contact> techContact;
|
||||
VKey<Contact> registrantContact;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the domain. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
|
||||
@@ -146,13 +145,14 @@ public class DomainBase extends EppResource
|
||||
DomainAuthInfo authInfo;
|
||||
|
||||
/** Data used to construct DS records for this domain. */
|
||||
@Transient Set<DelegationSignerData> dsData;
|
||||
@Ignore @Transient Set<DelegationSignerData> dsData;
|
||||
|
||||
/**
|
||||
* The claims notice supplied when this domain was created, if there was one.
|
||||
*
|
||||
* <p>It's {@literal @}XmlTransient because it's not returned in an info response.
|
||||
*/
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "noticeId.tcnId", column = @Column(name = "launch_notice_tcn_id")),
|
||||
@@ -215,15 +215,8 @@ public class DomainBase extends EppResource
|
||||
@Column(name = "autorenew_poll_message_id")
|
||||
VKey<PollMessage.Autorenew> autorenewPollMessage;
|
||||
|
||||
/**
|
||||
* History record for the autorenew poll message.
|
||||
*
|
||||
* <p>Here so we can restore the original ofy key from sql.
|
||||
*/
|
||||
@Ignore Long autorenewPollMessageHistoryId;
|
||||
|
||||
/** The unexpired grace periods for this domain (some of which may not be active yet). */
|
||||
@Transient Set<GracePeriod> gracePeriods;
|
||||
@Ignore @Transient Set<GracePeriod> gracePeriods;
|
||||
|
||||
/**
|
||||
* The id of the signed mark that was used to create this domain in sunrise.
|
||||
@@ -234,7 +227,7 @@ public class DomainBase extends EppResource
|
||||
String smdId;
|
||||
|
||||
/** Data about any pending or past transfers on this domain. */
|
||||
DomainTransferData transferData;
|
||||
@Ignore DomainTransferData transferData;
|
||||
|
||||
/**
|
||||
* The time that this resource was last transferred.
|
||||
@@ -282,6 +275,9 @@ public class DomainBase extends EppResource
|
||||
// TODO(mcilwain): Start using this field once we are further along in the DB migration.
|
||||
@Ignore DateTime dnsRefreshRequestTime;
|
||||
|
||||
/** The {@link AllocationToken} for the package this domain is currently a part of. */
|
||||
@Ignore @Nullable VKey<AllocationToken> currentPackageToken;
|
||||
|
||||
/**
|
||||
* Returns the DNS refresh request time iff this domain's DNS needs refreshing, otherwise absent.
|
||||
*/
|
||||
@@ -289,15 +285,6 @@ public class DomainBase extends EppResource
|
||||
return Optional.ofNullable(dnsRefreshRequestTime);
|
||||
}
|
||||
|
||||
public static <T> VKey<T> restoreOfyFrom(Key<Domain> domainKey, VKey<T> key, Long historyId) {
|
||||
if (historyId == null) {
|
||||
// This is a legacy key (or a null key, in which case this works too)
|
||||
return VKey.restoreOfyFrom(key, EntityGroupRoot.class, "per-tld");
|
||||
} else {
|
||||
return VKey.restoreOfyFrom(key, domainKey, HistoryEntry.class, historyId);
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getSubordinateHosts() {
|
||||
return nullToEmptyImmutableCopy(subordinateHosts);
|
||||
}
|
||||
@@ -318,10 +305,6 @@ public class DomainBase extends EppResource
|
||||
return autorenewPollMessage;
|
||||
}
|
||||
|
||||
public Long getAutorenewPollMessageHistoryId() {
|
||||
return autorenewPollMessageHistoryId;
|
||||
}
|
||||
|
||||
public ImmutableSet<GracePeriod> getGracePeriods() {
|
||||
return nullToEmptyImmutableCopy(gracePeriods);
|
||||
}
|
||||
@@ -330,6 +313,10 @@ public class DomainBase extends EppResource
|
||||
return smdId;
|
||||
}
|
||||
|
||||
public Optional<VKey<AllocationToken>> getCurrentPackageToken() {
|
||||
return Optional.ofNullable(currentPackageToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the autorenew end time if there is one, otherwise empty.
|
||||
*
|
||||
@@ -501,9 +488,7 @@ public class DomainBase extends EppResource
|
||||
// Set the speculatively-written new autorenew events as the domain's autorenew
|
||||
// events.
|
||||
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
|
||||
.setAutorenewPollMessage(
|
||||
transferData.getServerApproveAutorenewPollMessage(),
|
||||
transferData.getServerApproveAutorenewPollMessageHistoryId());
|
||||
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage());
|
||||
if (transferData.getTransferPeriod().getValue() == 1) {
|
||||
// Set the grace period using a key to the prescheduled transfer billing event. Not using
|
||||
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
|
||||
@@ -607,19 +592,19 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
/** A key to the registrant who registered this domain. */
|
||||
public VKey<ContactResource> getRegistrant() {
|
||||
public VKey<Contact> getRegistrant() {
|
||||
return registrantContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getAdminContact() {
|
||||
public VKey<Contact> getAdminContact() {
|
||||
return adminContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getBillingContact() {
|
||||
public VKey<Contact> getBillingContact() {
|
||||
return billingContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getTechContact() {
|
||||
public VKey<Contact> getTechContact() {
|
||||
return techContact;
|
||||
}
|
||||
|
||||
@@ -633,7 +618,7 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
/** Returns all referenced contacts from this domain. */
|
||||
public ImmutableSet<VKey<ContactResource>> getReferencedContacts() {
|
||||
public ImmutableSet<VKey<Contact>> getReferencedContacts() {
|
||||
return nullToEmptyImmutableCopy(getAllContacts(true)).stream()
|
||||
.map(DesignatedContact::getContactKey)
|
||||
.filter(Objects::nonNull)
|
||||
@@ -776,7 +761,7 @@ public class DomainBase extends EppResource
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistrant(VKey<ContactResource> registrant) {
|
||||
public B setRegistrant(VKey<Contact> registrant) {
|
||||
// Set the registrant field specifically.
|
||||
getInstance().registrantContact = registrant;
|
||||
return thisCastToDerived();
|
||||
@@ -878,11 +863,8 @@ public class DomainBase extends EppResource
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setAutorenewPollMessage(
|
||||
@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage,
|
||||
@Nullable Long autorenewPollMessageHistoryId) {
|
||||
public B setAutorenewPollMessage(@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage) {
|
||||
getInstance().autorenewPollMessage = autorenewPollMessage;
|
||||
getInstance().autorenewPollMessageHistoryId = autorenewPollMessageHistoryId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -938,5 +920,10 @@ public class DomainBase extends EppResource
|
||||
getInstance().lastTransferTime = lastTransferTime;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setCurrentPackageToken(@Nullable VKey<AllocationToken> currentPackageToken) {
|
||||
getInstance().currentPackageToken = currentPackageToken;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
|
||||
@@ -80,7 +80,7 @@ public class DomainCommand {
|
||||
String registrantContactId;
|
||||
|
||||
/** A resolved key to the registrant who registered this domain. */
|
||||
@XmlTransient VKey<ContactResource> registrant;
|
||||
@XmlTransient VKey<Contact> registrant;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the domain. */
|
||||
DomainAuthInfo authInfo;
|
||||
@@ -90,7 +90,7 @@ public class DomainCommand {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VKey<ContactResource> getRegistrant() {
|
||||
public VKey<Contact> getRegistrant() {
|
||||
return registrant;
|
||||
}
|
||||
|
||||
@@ -391,7 +391,7 @@ public class DomainCommand {
|
||||
? null
|
||||
: getOnlyElement(
|
||||
loadByForeignKeysCached(
|
||||
ImmutableSet.of(clone.registrantContactId), ContactResource.class, now)
|
||||
ImmutableSet.of(clone.registrantContactId), Contact.class, now)
|
||||
.values());
|
||||
return clone;
|
||||
}
|
||||
@@ -431,8 +431,8 @@ public class DomainCommand {
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
foreignKeys.add(contact.contactId);
|
||||
}
|
||||
ImmutableMap<String, VKey<ContactResource>> loadedContacts =
|
||||
loadByForeignKeysCached(foreignKeys.build(), ContactResource.class, now);
|
||||
ImmutableMap<String, VKey<Contact>> loadedContacts =
|
||||
loadByForeignKeysCached(foreignKeys.build(), Contact.class, now);
|
||||
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
linkedContacts.add(
|
||||
|
||||
@@ -20,6 +20,7 @@ import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
@@ -133,6 +134,7 @@ public class DomainHistory extends HistoryEntry {
|
||||
updatable = false)
|
||||
})
|
||||
// HashSet rather than ImmutableSet so that Hibernate can fill them out lazily on request
|
||||
@Ignore
|
||||
Set<DomainDsDataHistory> dsDataHistories = new HashSet<>();
|
||||
|
||||
@DoNotCompare
|
||||
@@ -153,6 +155,7 @@ public class DomainHistory extends HistoryEntry {
|
||||
updatable = false)
|
||||
})
|
||||
// HashSet rather than ImmutableSet so that Hibernate can fill them out lazily on request
|
||||
@Ignore
|
||||
Set<GracePeriodHistory> gracePeriodHistories = new HashSet<>();
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,7 +19,6 @@ import static google.registry.model.IdService.allocateId;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
@@ -40,7 +39,6 @@ import org.joda.time.DateTime;
|
||||
* <p>When a grace period expires, it is lazily removed from the {@link Domain} the next time the
|
||||
* resource is loaded from Datastore.
|
||||
*/
|
||||
@Embed
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
@@ -31,7 +29,6 @@ import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Base class containing common fields and methods for {@link GracePeriod}. */
|
||||
@Embed
|
||||
@MappedSuperclass
|
||||
@Access(AccessType.FIELD)
|
||||
public class GracePeriodBase extends ImmutableObject implements UnsafeSerializable {
|
||||
@@ -40,7 +37,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
|
||||
@Transient long gracePeriodId;
|
||||
|
||||
/** Repository id for the domain which this grace period belongs to. */
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
String domainRepoId;
|
||||
|
||||
@@ -65,7 +61,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
|
||||
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
|
||||
@Access(AccessType.FIELD)
|
||||
@Column(name = "billing_event_id")
|
||||
@Ignore
|
||||
VKey<BillingEvent.OneTime> billingEventOneTime = null;
|
||||
|
||||
/**
|
||||
@@ -75,7 +70,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
|
||||
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
|
||||
@Access(AccessType.FIELD)
|
||||
@Column(name = "billing_recurrence_id")
|
||||
@Ignore
|
||||
VKey<BillingEvent.Recurring> billingEventRecurring = null;
|
||||
|
||||
public long getGracePeriodId() {
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -24,8 +24,7 @@ import javax.xml.bind.annotation.XmlEnumValue;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
/** The "periodType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class Period extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
|
||||
@@ -23,12 +23,10 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
@@ -37,17 +35,15 @@ import javax.xml.bind.annotation.XmlValue;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The claims notice id from the claims phase. */
|
||||
@Embed
|
||||
@XmlType(propOrder = {"noticeId", "expirationTime", "acceptedTime"})
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class LaunchNotice extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/** An empty instance to use in place of null. */
|
||||
private static final NoticeIdType EMPTY_NOTICE_ID = new NoticeIdType();
|
||||
|
||||
/** An id with a validator-id attribute. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public static class NoticeIdType extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
@@ -57,7 +53,6 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable
|
||||
@XmlValue String tcnId;
|
||||
|
||||
/** The identifier of the TMDB provider to use, defaulting to the TMCH. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
@XmlAttribute(name = "validatorID")
|
||||
String validatorId;
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.model.domain.launch;
|
||||
|
||||
import static java.util.Objects.hash;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.util.Objects;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -47,7 +46,6 @@ import javax.xml.bind.annotation.XmlValue;
|
||||
* sets it is the one that needs to make sure the domain isn't a trademark and that the fields are
|
||||
* correct.
|
||||
*/
|
||||
@Embed
|
||||
public class LaunchPhase extends ImmutableObject {
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2022 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.model.domain.packagetoken;
|
||||
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/** A package token extension that may be present on EPP domain commands. */
|
||||
@XmlRootElement(name = "info")
|
||||
public class PackageTokenExtension extends ImmutableObject implements CommandExtension {}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2022 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.model.domain.packagetoken;
|
||||
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.xml.TrimWhitespaceAdapter;
|
||||
import java.util.Optional;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
/**
|
||||
* An XML data object that represents a package token extension that may be present on the response
|
||||
* to EPP domain info commands.
|
||||
*/
|
||||
@XmlRootElement(name = "packageData")
|
||||
public class PackageTokenResponseExtension extends ImmutableObject implements ResponseExtension {
|
||||
|
||||
/** Token string of the PACKAGE token the name belongs to. */
|
||||
@XmlJavaTypeAdapter(TrimWhitespaceAdapter.class)
|
||||
String token;
|
||||
|
||||
public static PackageTokenResponseExtension create(Optional<VKey<AllocationToken>> tokenKey) {
|
||||
PackageTokenResponseExtension instance = new PackageTokenResponseExtension();
|
||||
instance.token = "";
|
||||
if (tokenKey.isPresent()) {
|
||||
instance.token = tokenKey.get().getSqlKey().toString();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2022 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.
|
||||
|
||||
@XmlSchema(
|
||||
namespace = "urn:google:params:xml:ns:packageToken-1.0",
|
||||
xmlns =
|
||||
@XmlNs(prefix = "packageToken", namespaceURI = "urn:google:params:xml:ns:packageToken-1.0"),
|
||||
elementFormDefault = XmlNsForm.QUALIFIED)
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
package google.registry.model.domain.packagetoken;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlNs;
|
||||
import javax.xml.bind.annotation.XmlNsForm;
|
||||
import javax.xml.bind.annotation.XmlSchema;
|
||||
@@ -16,7 +16,6 @@ package google.registry.model.domain.secdns;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData.DomainDsDataId;
|
||||
import java.io.Serializable;
|
||||
@@ -37,7 +36,6 @@ import javax.xml.bind.annotation.XmlType;
|
||||
* @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a>
|
||||
* <p>TODO(b/177567432): Rename this class to DomainDsData.
|
||||
*/
|
||||
@Embed
|
||||
@XmlType(name = "dsData")
|
||||
@Entity
|
||||
@IdClass(DomainDsDataId.class)
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
package google.registry.model.domain.secdns;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Access;
|
||||
@@ -29,12 +27,11 @@ import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
/** Base class for {@link DelegationSignerData} and {@link DomainDsDataHistory}. */
|
||||
@Embed
|
||||
@MappedSuperclass
|
||||
@Access(AccessType.FIELD)
|
||||
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Ignore @XmlTransient @Transient String domainRepoId;
|
||||
@XmlTransient @Transient String domainRepoId;
|
||||
|
||||
/** The identifier for this particular key in the domain. */
|
||||
@Transient int keyTag;
|
||||
|
||||
@@ -15,14 +15,12 @@
|
||||
package google.registry.model.domain.secdns;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
|
||||
/** The EPP secDNS extension to be returned with domain info commands. */
|
||||
@XmlRootElement(name = "infData")
|
||||
@Embed
|
||||
public class SecDnsInfoExtension extends ImmutableObject implements ResponseExtension {
|
||||
|
||||
/** Signatures for this domain. */
|
||||
|
||||
@@ -26,22 +26,16 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import com.google.common.collect.Range;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.domain.DomainFlowUtils;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
@@ -54,31 +48,31 @@ import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** An entity representing an allocation token. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@WithStringVKey
|
||||
@javax.persistence.Entity
|
||||
@WithStringVKey(compositeKey = true)
|
||||
@Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(
|
||||
columnList = "token",
|
||||
name = "allocation_token_token_idx",
|
||||
unique = true),
|
||||
@javax.persistence.Index(
|
||||
columnList = "domainName",
|
||||
name = "allocation_token_domain_name_idx"),
|
||||
@javax.persistence.Index(columnList = "tokenType"),
|
||||
@javax.persistence.Index(columnList = "redemption_domain_repo_id")
|
||||
@Index(columnList = "token", name = "allocation_token_token_idx", unique = true),
|
||||
@Index(columnList = "domainName", name = "allocation_token_domain_name_idx"),
|
||||
@Index(columnList = "tokenType"),
|
||||
@Index(columnList = "redemption_domain_repo_id")
|
||||
})
|
||||
public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
|
||||
private static final long serialVersionUID = -3954475393220876903L;
|
||||
private static final String REMOVE_PACKAGE = "__REMOVEPACKAGE__";
|
||||
|
||||
private static final ImmutableMap<String, TokenBehavior> STATIC_TOKEN_BEHAVIORS =
|
||||
ImmutableMap.of(REMOVE_PACKAGE, TokenBehavior.REMOVE_PACKAGE);
|
||||
|
||||
// Promotions should only move forward, and ENDED / CANCELLED are terminal states.
|
||||
private static final ImmutableMultimap<TokenStatus, TokenStatus> VALID_TOKEN_STATUS_TRANSITIONS =
|
||||
@@ -87,6 +81,18 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
.putAll(VALID, ENDED, CANCELLED)
|
||||
.build();
|
||||
|
||||
private static final ImmutableMap<String, AllocationToken> BEHAVIORAL_TOKENS =
|
||||
ImmutableMap.of(
|
||||
REMOVE_PACKAGE,
|
||||
new AllocationToken.Builder()
|
||||
.setTokenType(TokenType.UNLIMITED_USE)
|
||||
.setToken(REMOVE_PACKAGE)
|
||||
.build());
|
||||
|
||||
public static Optional<AllocationToken> maybeGetStaticTokenInstance(String name) {
|
||||
return Optional.ofNullable(BEHAVIORAL_TOKENS.get(name));
|
||||
}
|
||||
|
||||
/** Any special behavior that should be used when registering domains using this token. */
|
||||
public enum RegistrationBehavior {
|
||||
/** No special behavior */
|
||||
@@ -103,10 +109,28 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
ANCHOR_TENANT
|
||||
}
|
||||
|
||||
/** Single-use tokens are invalid after use. Infinite-use tokens, predictably, are not. */
|
||||
/**
|
||||
* Single-use tokens are invalid after use. Infinite-use tokens, predictably, are not. Package
|
||||
* tokens are used in package promotions.
|
||||
*/
|
||||
public enum TokenType {
|
||||
PACKAGE,
|
||||
SINGLE_USE,
|
||||
UNLIMITED_USE
|
||||
UNLIMITED_USE,
|
||||
}
|
||||
|
||||
/**
|
||||
* System behaves differently based on a token it gets inside a command. This enumerates different
|
||||
* types of behaviors we support.
|
||||
*/
|
||||
public enum TokenBehavior {
|
||||
/** No special behavior */
|
||||
DEFAULT,
|
||||
/**
|
||||
* REMOVE_PACKAGE triggers domain removal from promotional package, bypasses DEFAULT token
|
||||
* validations.
|
||||
*/
|
||||
REMOVE_PACKAGE
|
||||
}
|
||||
|
||||
/** The status of this token with regard to any potential promotion. */
|
||||
@@ -122,11 +146,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
}
|
||||
|
||||
/** The allocation token string. */
|
||||
@javax.persistence.Id @Id String token;
|
||||
@Id String token;
|
||||
|
||||
/** The key of the history entry for which the token was used. Null if not yet used. */
|
||||
@Nullable
|
||||
@Index
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "repoId", column = @Column(name = "redemption_domain_repo_id")),
|
||||
@AttributeOverride(
|
||||
@@ -136,10 +159,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
DomainHistoryVKey redemptionHistoryEntry;
|
||||
|
||||
/** The fully-qualified domain name that this token is limited to, if any. */
|
||||
@Nullable @Index String domainName;
|
||||
@Nullable String domainName;
|
||||
|
||||
/** When this token was created. */
|
||||
@Ignore CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/** Allowed registrar client IDs for this token, or null if all registrars are allowed. */
|
||||
@Column(name = "allowedRegistrarIds")
|
||||
@@ -168,21 +191,12 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "renewalPriceBehavior", nullable = false)
|
||||
@Ignore
|
||||
RenewalPriceBehavior renewalPriceBehavior = RenewalPriceBehavior.DEFAULT;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
RegistrationBehavior registrationBehavior = RegistrationBehavior.DEFAULT;
|
||||
|
||||
// TODO: Remove onLoad once all allocation tokens are migrated to have a discountYears of 1.
|
||||
@OnLoad
|
||||
void onLoad() {
|
||||
if (discountYears == 0) {
|
||||
discountYears = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Promotional token validity periods.
|
||||
*
|
||||
@@ -251,9 +265,17 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
return registrationBehavior;
|
||||
}
|
||||
|
||||
public TokenBehavior getTokenBehavior() {
|
||||
return STATIC_TOKEN_BEHAVIORS.getOrDefault(token, TokenBehavior.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public VKey<AllocationToken> createVKey() {
|
||||
return VKey.create(AllocationToken.class, getToken(), Key.create(this));
|
||||
if (!AllocationToken.TokenBehavior.DEFAULT.equals(getTokenBehavior())) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("%s tokens are not stored in the database", getTokenBehavior()));
|
||||
}
|
||||
return VKey.createSql(AllocationToken.class, getToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -274,6 +296,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
public AllocationToken build() {
|
||||
checkArgumentNotNull(getInstance().tokenType, "Token type must be specified");
|
||||
checkArgument(!Strings.isNullOrEmpty(getInstance().token), "Token must not be null or empty");
|
||||
checkArgument(
|
||||
!getInstance().tokenType.equals(TokenType.PACKAGE)
|
||||
|| getInstance().renewalPriceBehavior.equals(RenewalPriceBehavior.SPECIFIED),
|
||||
"Package tokens must have renewalPriceBehavior set to SPECIFIED");
|
||||
checkArgument(
|
||||
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
|
||||
"Domain name can only be specified for SINGLE_USE tokens");
|
||||
@@ -281,6 +307,11 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
getInstance().redemptionHistoryEntry == null
|
||||
|| TokenType.SINGLE_USE.equals(getInstance().tokenType),
|
||||
"Redemption history entry can only be specified for SINGLE_USE tokens");
|
||||
checkArgument(
|
||||
getInstance().tokenType != TokenType.PACKAGE
|
||||
|| (getInstance().allowedClientIds != null
|
||||
&& getInstance().allowedClientIds.size() == 1),
|
||||
"PACKAGE tokens must have exactly one allowed client registrar");
|
||||
checkArgument(
|
||||
getInstance().discountFraction > 0 || !getInstance().discountPremiums,
|
||||
"Discount premiums can only be specified along with a discount fraction");
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright 2022 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.model.domain.token;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.converter.JodaMoneyType;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import org.hibernate.annotations.Columns;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** An entity representing a package promotion. */
|
||||
@Entity
|
||||
@javax.persistence.Table(indexes = {@javax.persistence.Index(columnList = "token")})
|
||||
public class PackagePromotion extends ImmutableObject implements Buildable {
|
||||
|
||||
/** An autogenerated identifier for the package promotion. */
|
||||
@Id long packagePromotionId;
|
||||
|
||||
/** The allocation token string for the package. */
|
||||
@Column(nullable = false)
|
||||
VKey<AllocationToken> token;
|
||||
|
||||
/** The maximum number of active domains the package allows at any given time. */
|
||||
@Column(nullable = false)
|
||||
int maxDomains;
|
||||
|
||||
/** The maximum number of domains that can be created in the package each year. */
|
||||
@Column(nullable = false)
|
||||
int maxCreates;
|
||||
|
||||
/** The annual price of the package. */
|
||||
@Type(type = JodaMoneyType.TYPE_NAME)
|
||||
@Columns(
|
||||
columns = {
|
||||
@Column(name = "package_price_amount", nullable = false),
|
||||
@Column(name = "package_price_currency", nullable = false)
|
||||
})
|
||||
Money packagePrice;
|
||||
|
||||
/** The next billing date of the package. */
|
||||
@Column(nullable = false)
|
||||
DateTime nextBillingDate = END_OF_TIME;
|
||||
|
||||
/** Date the last warning email was sent that the package has exceeded the maxDomains limit. */
|
||||
@Nullable DateTime lastNotificationSent;
|
||||
|
||||
public VKey<AllocationToken> getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
public int getMaxDomains() {
|
||||
return maxDomains;
|
||||
}
|
||||
|
||||
public int getMaxCreates() {
|
||||
return maxCreates;
|
||||
}
|
||||
|
||||
public Money getPackagePrice() {
|
||||
return packagePrice;
|
||||
}
|
||||
|
||||
public DateTime getNextBillingDate() {
|
||||
return nextBillingDate;
|
||||
}
|
||||
|
||||
public Optional<DateTime> getLastNotificationSent() {
|
||||
return Optional.ofNullable(lastNotificationSent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link PackagePromotion} objects, since they are immutable. */
|
||||
public static class Builder extends Buildable.Builder<PackagePromotion> {
|
||||
public Builder() {}
|
||||
|
||||
private Builder(PackagePromotion instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PackagePromotion build() {
|
||||
checkArgumentNotNull(getInstance().token, "Allocation token must be specified");
|
||||
AllocationToken allocationToken = tm().transact(() -> tm().loadByKey(getInstance().token));
|
||||
checkArgument(
|
||||
allocationToken.tokenType == TokenType.PACKAGE,
|
||||
"Allocation token must be a PACKAGE type");
|
||||
return super.build();
|
||||
}
|
||||
|
||||
public Builder setToken(AllocationToken token) {
|
||||
checkArgumentNotNull(token, "Allocation token must not be null");
|
||||
checkArgument(
|
||||
token.tokenType == TokenType.PACKAGE, "Allocation token must be a PACKAGE type");
|
||||
getInstance().token = token.createVKey();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxDomains(int maxDomains) {
|
||||
checkArgumentNotNull(maxDomains, "maxDomains must not be null");
|
||||
getInstance().maxDomains = maxDomains;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setMaxCreates(int maxCreates) {
|
||||
checkArgumentNotNull(maxCreates, "maxCreates must not be null");
|
||||
getInstance().maxCreates = maxCreates;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPackagePrice(Money packagePrice) {
|
||||
checkArgumentNotNull(packagePrice, "Package price must not be null");
|
||||
getInstance().packagePrice = packagePrice;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNextBillingDate(@Nullable DateTime nextBillingDate) {
|
||||
checkArgumentNotNull(nextBillingDate, "Next billing date must not be null");
|
||||
getInstance().nextBillingDate = nextBillingDate;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLastNotificationSent(@Nullable DateTime lastNotificationSent) {
|
||||
getInstance().lastNotificationSent = lastNotificationSent;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,11 @@
|
||||
package google.registry.model.eppcommon;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.annotation.AlsoLoad;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.JsonMapBuilder;
|
||||
@@ -59,40 +56,62 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
@MappedSuperclass
|
||||
public class Address extends ImmutableObject implements Jsonifiable, UnsafeSerializable {
|
||||
|
||||
/** The schema validation will enforce that this has 3 lines at most. */
|
||||
// TODO(b/177569726): Remove this field after migration. We need to figure out how to generate
|
||||
// same XML from streetLine[1,2,3].
|
||||
/**
|
||||
* At most three lines of addresses parsed from XML elements.
|
||||
*
|
||||
* <p>This field is used to marshal to/unmarshal from XML elements. When persisting to/from SQL,
|
||||
* the next three separate fields are used. Those lines are <em>only</em> used for persistence
|
||||
* purpose and should not be directly used in Java.
|
||||
*
|
||||
* <p>We need to keep the list and the three fields in sync in all the following scenarios when an
|
||||
* entity containing a {@link Address} is created:
|
||||
*
|
||||
* <ul>
|
||||
* <li>When creating an {@link Address} directly in java, using the {@link Builder}: The {@link
|
||||
* Builder#setStreet(ImmutableList)} sets both the list and the fields.
|
||||
* <li>When unmarshalling from XML: The list will be set based on the content of the XML.
|
||||
* Afterwards, {@link #afterUnmarshal(Unmarshaller, Object)}} will be called to set the
|
||||
* fields.
|
||||
* <li>When loading from the database: The fields will be set by the values in SQL. Afterwards,
|
||||
* {@link #postLoad()} will be called to set the list.
|
||||
* </ul>
|
||||
*
|
||||
* The syncing is especially important because when merging a detached entity into a session, JPA
|
||||
* provides no guarantee that transient fields will be preserved. In fact, Hibernate chooses to
|
||||
* discard them when returning a newly merged entity. This means that it is not enough to provide
|
||||
* callbacks to populate the fields based on the list before persistence, as merging does not
|
||||
* invoke the callbacks, and we would lose the address fields if only the list is set. Instead,
|
||||
* the fields must be populated when the list is.
|
||||
*
|
||||
* <p>Schema validation will enforce the 3-line limit.
|
||||
*/
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
@Transient
|
||||
List<String> street;
|
||||
protected List<String> street;
|
||||
|
||||
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine1;
|
||||
@XmlTransient @IgnoredInDiffableMap protected String streetLine1;
|
||||
|
||||
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine2;
|
||||
@XmlTransient @IgnoredInDiffableMap protected String streetLine2;
|
||||
|
||||
@Ignore @XmlTransient @IgnoredInDiffableMap String streetLine3;
|
||||
@XmlTransient @IgnoredInDiffableMap protected String streetLine3;
|
||||
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
String city;
|
||||
protected String city;
|
||||
|
||||
@XmlElement(name = "sp")
|
||||
@XmlJavaTypeAdapter(NormalizedStringAdapter.class)
|
||||
String state;
|
||||
protected String state;
|
||||
|
||||
@XmlElement(name = "pc")
|
||||
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
|
||||
String zip;
|
||||
protected String zip;
|
||||
|
||||
@XmlElement(name = "cc")
|
||||
@XmlJavaTypeAdapter(CollapsedStringAdapter.class)
|
||||
String countryCode;
|
||||
protected String countryCode;
|
||||
|
||||
public ImmutableList<String> getStreet() {
|
||||
if (street == null && streetLine1 != null) {
|
||||
return ImmutableList.of(streetLine1, nullToEmpty(streetLine2), nullToEmpty(streetLine3));
|
||||
} else {
|
||||
return nullToEmptyImmutableCopy(street);
|
||||
}
|
||||
return nullToEmptyImmutableCopy(street);
|
||||
}
|
||||
|
||||
public String getCity() {
|
||||
@@ -139,7 +158,13 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
public Builder<T> setStreet(ImmutableList<String> street) {
|
||||
checkArgument(
|
||||
street == null || (!street.isEmpty() && street.size() <= 3),
|
||||
"Street address must have [1-3] lines: %s", street);
|
||||
"Street address must have [1-3] lines: %s",
|
||||
street);
|
||||
//noinspection ConstantConditions
|
||||
checkArgument(
|
||||
street.stream().noneMatch(String::isEmpty),
|
||||
"Street address cannot contain empty string: %s",
|
||||
street);
|
||||
getInstance().street = street;
|
||||
getInstance().streetLine1 = street.get(0);
|
||||
getInstance().streetLine2 = street.size() >= 2 ? street.get(1) : null;
|
||||
@@ -171,24 +196,13 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #streetLine1}, {@link #streetLine2} and {@link #streetLine3} after loading the
|
||||
* entity from Datastore.
|
||||
*
|
||||
* <p>This callback method is used by Objectify to set streetLine[1,2,3] fields as they are not
|
||||
* persisted in the Datastore.
|
||||
*/
|
||||
void onLoad(@AlsoLoad("street") List<String> street) {
|
||||
mapStreetListToIndividualFields(street);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link #street} after loading the entity from Cloud SQL.
|
||||
*
|
||||
* <p>This callback method is used by Hibernate to set {@link #street} field as it is not
|
||||
* persisted in Cloud SQL. We are doing this because the street list field is exposed by Address
|
||||
* class and is used everywhere in our code base. Also, setting/reading a list of strings is more
|
||||
* convenient.
|
||||
* <p>This callback method is used by Hibernate to set the {@link #street} field as it is not
|
||||
* persisted in Cloud SQL. We are doing this because this field is exposed and used everywhere in
|
||||
* our code base, whereas the individual {@code streetLine} fields are only used by Hibernate for
|
||||
* persistence. Also, setting/reading a list of strings is more convenient.
|
||||
*/
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
@@ -206,12 +220,9 @@ public class Address extends ImmutableObject implements Jsonifiable, UnsafeSeria
|
||||
*
|
||||
* <p>This is a callback function that JAXB invokes after unmarshalling the XML message.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
void afterUnmarshal(Unmarshaller unmarshaller, Object parent) {
|
||||
mapStreetListToIndividualFields(street);
|
||||
}
|
||||
|
||||
private void mapStreetListToIndividualFields(List<String> street) {
|
||||
if (street == null || street.size() == 0) {
|
||||
if (street == null || street.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
streetLine1 = street.get(0);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.model.eppcommon;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Embeddable;
|
||||
@@ -45,7 +44,6 @@ public abstract class AuthInfo extends ImmutableObject implements UnsafeSerializ
|
||||
}
|
||||
|
||||
/** The "pwAuthInfoType" complex type. */
|
||||
@Embed
|
||||
@XmlType(namespace = "urn:ietf:params:xml:ns:eppcom-1.0")
|
||||
@Embeddable
|
||||
public static class PasswordAuth extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@@ -31,25 +31,27 @@ import java.io.ByteArrayOutputStream;
|
||||
public class EppXmlTransformer {
|
||||
|
||||
// Hardcoded XML schemas, ordered with respect to dependency.
|
||||
private static final ImmutableList<String> SCHEMAS = ImmutableList.of(
|
||||
"eppcom.xsd",
|
||||
"epp.xsd",
|
||||
"contact.xsd",
|
||||
"host.xsd",
|
||||
"domain.xsd",
|
||||
"rgp.xsd",
|
||||
"secdns.xsd",
|
||||
"fee06.xsd",
|
||||
"fee11.xsd",
|
||||
"fee12.xsd",
|
||||
"metadata.xsd",
|
||||
"mark.xsd",
|
||||
"dsig.xsd",
|
||||
"smd.xsd",
|
||||
"launch.xsd",
|
||||
"allocate.xsd",
|
||||
"superuser.xsd",
|
||||
"allocationToken-1.0.xsd");
|
||||
private static final ImmutableList<String> SCHEMAS =
|
||||
ImmutableList.of(
|
||||
"eppcom.xsd",
|
||||
"epp.xsd",
|
||||
"contact.xsd",
|
||||
"host.xsd",
|
||||
"domain.xsd",
|
||||
"rgp.xsd",
|
||||
"secdns.xsd",
|
||||
"fee06.xsd",
|
||||
"fee11.xsd",
|
||||
"fee12.xsd",
|
||||
"metadata.xsd",
|
||||
"mark.xsd",
|
||||
"dsig.xsd",
|
||||
"smd.xsd",
|
||||
"launch.xsd",
|
||||
"allocate.xsd",
|
||||
"superuser.xsd",
|
||||
"allocationToken-1.0.xsd",
|
||||
"packageToken.xsd");
|
||||
|
||||
private static final XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppInput.class);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.model.eppcommon;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Embeddable;
|
||||
@@ -26,7 +25,6 @@ import javax.xml.bind.annotation.XmlTransient;
|
||||
* <p>When placed in a field "foo", this will correctly unmarshal from both {@code <foo/>} and
|
||||
* {@code <foo></foo>}, and will unmarshal always to {@code <foo/>}.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class PresenceMarker extends ImmutableObject implements Serializable {
|
||||
@XmlTransient
|
||||
|
||||
@@ -20,8 +20,8 @@ import static com.google.common.base.Strings.nullToEmpty;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -130,12 +130,12 @@ public enum StatusValue implements EppEnum {
|
||||
/** Enum to help clearly list which resource types a status value is allowed to be present on. */
|
||||
private enum AllowedOn {
|
||||
ALL(
|
||||
Contact.class,
|
||||
ContactBase.class,
|
||||
ContactResource.class,
|
||||
DomainBase.class,
|
||||
Domain.class,
|
||||
HostBase.class,
|
||||
Host.class),
|
||||
DomainBase.class,
|
||||
Host.class,
|
||||
HostBase.class),
|
||||
NONE,
|
||||
DOMAINS(DomainBase.class, Domain.class);
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ package google.registry.model.eppcommon;
|
||||
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
@@ -30,9 +30,8 @@ import javax.xml.bind.annotation.XmlType;
|
||||
* is formed using the {@code clTRID} associated with the command if supplied by the client and a
|
||||
* {@code svTRID} (server transaction identifier) that is assigned by and unique to the server."
|
||||
*/
|
||||
@Embed
|
||||
@XmlType(propOrder = {"clientTransactionId", "serverTransactionId"})
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class Trid extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/** The server transaction id. */
|
||||
|
||||
@@ -45,6 +45,7 @@ import google.registry.model.domain.launch.LaunchDeleteExtension;
|
||||
import google.registry.model.domain.launch.LaunchInfoExtension;
|
||||
import google.registry.model.domain.launch.LaunchUpdateExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.packagetoken.PackageTokenExtension;
|
||||
import google.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
@@ -362,6 +363,7 @@ public class EppInput extends ImmutableObject {
|
||||
// Other extensions
|
||||
@XmlElementRef(type = AllocationTokenExtension.class),
|
||||
@XmlElementRef(type = MetadataExtension.class),
|
||||
@XmlElementRef(type = PackageTokenExtension.class),
|
||||
@XmlElementRef(type = RgpUpdateExtension.class),
|
||||
@XmlElementRef(type = SecDnsCreateExtension.class),
|
||||
@XmlElementRef(type = SecDnsUpdateExtension.class)
|
||||
|
||||
@@ -43,6 +43,7 @@ import google.registry.model.domain.fee12.FeeRenewResponseExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeTransferResponseExtensionV12;
|
||||
import google.registry.model.domain.fee12.FeeUpdateResponseExtensionV12;
|
||||
import google.registry.model.domain.launch.LaunchCheckResponseExtension;
|
||||
import google.registry.model.domain.packagetoken.PackageTokenResponseExtension;
|
||||
import google.registry.model.domain.rgp.RgpInfoExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsInfoExtension;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
@@ -121,28 +122,30 @@ public class EppResponse extends ImmutableObject implements ResponseOrGreeting {
|
||||
|
||||
/** Zero or more response extensions. */
|
||||
@XmlElementRefs({
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeInfoResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV12.class),
|
||||
@XmlElementRef(type = LaunchCheckResponseExtension.class),
|
||||
@XmlElementRef(type = RgpInfoExtension.class),
|
||||
@XmlElementRef(type = SecDnsInfoExtension.class) })
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeInfoResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV06.class),
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV11.class),
|
||||
@XmlElementRef(type = FeeCheckResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeCreateResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeDeleteResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeRenewResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeTransferResponseExtensionV12.class),
|
||||
@XmlElementRef(type = FeeUpdateResponseExtensionV12.class),
|
||||
@XmlElementRef(type = LaunchCheckResponseExtension.class),
|
||||
@XmlElementRef(type = PackageTokenResponseExtension.class),
|
||||
@XmlElementRef(type = RgpInfoExtension.class),
|
||||
@XmlElementRef(type = SecDnsInfoExtension.class)
|
||||
})
|
||||
@XmlElementWrapper(name = "extension")
|
||||
ImmutableList<? extends ResponseExtension> extensions;
|
||||
|
||||
|
||||
@@ -19,10 +19,8 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.entriesToImmutableMap;
|
||||
import static google.registry.util.TypeUtils.instantiate;
|
||||
|
||||
@@ -36,18 +34,11 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.CacheUtils;
|
||||
import google.registry.model.CacheUtils.AppEngineEnvironmentCacheLoader;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -67,56 +58,44 @@ import org.joda.time.DateTime;
|
||||
* the foreign key string. The instance is never deleted, but it is updated if a newer entity
|
||||
* becomes the active entity.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroupRoot {
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link ContactResource} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<ContactResource> {}
|
||||
/** The {@link ForeignKeyIndex} type for {@link Contact} entities. */
|
||||
public static class ForeignKeyContactIndex extends ForeignKeyIndex<Contact> {}
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link Domain} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyDomainIndex extends ForeignKeyIndex<Domain> {}
|
||||
|
||||
/** The {@link ForeignKeyIndex} type for {@link Host} entities. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class ForeignKeyHostIndex extends ForeignKeyIndex<Host> {}
|
||||
|
||||
private static final ImmutableBiMap<
|
||||
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
|
||||
RESOURCE_CLASS_TO_FKI_CLASS =
|
||||
ImmutableBiMap.of(
|
||||
ContactResource.class, ForeignKeyContactIndex.class,
|
||||
Contact.class, ForeignKeyContactIndex.class,
|
||||
Domain.class, ForeignKeyDomainIndex.class,
|
||||
Host.class, ForeignKeyHostIndex.class);
|
||||
|
||||
private static final ImmutableMap<Class<? extends EppResource>, String>
|
||||
RESOURCE_CLASS_TO_FKI_PROPERTY =
|
||||
ImmutableMap.of(
|
||||
ContactResource.class, "contactId",
|
||||
Contact.class, "contactId",
|
||||
Domain.class, "fullyQualifiedDomainName",
|
||||
Host.class, "fullyQualifiedHostName");
|
||||
|
||||
@Id String foreignKey;
|
||||
String foreignKey;
|
||||
|
||||
/**
|
||||
* The deletion time of this {@link ForeignKeyIndex}.
|
||||
*
|
||||
* <p>This will generally be equal to the deletion time of {@link #topReference}. However, in the
|
||||
* <p>This will generally be equal to the deletion time of {@link #reference}. However, in the
|
||||
* case of a {@link Host} that was renamed, this field will hold the time of the rename.
|
||||
*/
|
||||
@Index DateTime deletionTime;
|
||||
DateTime deletionTime;
|
||||
|
||||
/**
|
||||
* The referenced resource.
|
||||
*
|
||||
* <p>This field holds a key to the only referenced resource. It is named "topReference" for
|
||||
* historical reasons.
|
||||
*/
|
||||
VKey<E> topReference;
|
||||
/** The referenced resource. */
|
||||
VKey<E> reference;
|
||||
|
||||
public String getForeignKey() {
|
||||
return foreignKey;
|
||||
@@ -127,7 +106,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
}
|
||||
|
||||
public VKey<E> getResourceKey() {
|
||||
return topReference;
|
||||
return reference;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -137,26 +116,19 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
}
|
||||
|
||||
/** Create a {@link ForeignKeyIndex} instance for a resource, expiring at a specified time. */
|
||||
public static <E extends EppResource> ForeignKeyIndex<E> create(
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends EppResource> ForeignKeyIndex<E> create(
|
||||
E resource, DateTime deletionTime) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> resourceClass = (Class<E>) resource.getClass();
|
||||
ForeignKeyIndex<E> instance = instantiate(mapToFkiClass(resourceClass));
|
||||
instance.topReference = (VKey<E>) resource.createVKey();
|
||||
instance.reference = (VKey<E>) resource.createVKey();
|
||||
instance.foreignKey = resource.getForeignKey();
|
||||
instance.deletionTime = deletionTime;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/** Create a {@link ForeignKeyIndex} key for a resource. */
|
||||
public static <E extends EppResource> Key<ForeignKeyIndex<E>> createKey(E resource) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> resourceClass = (Class<E>) resource.getClass();
|
||||
return Key.create(mapToFkiClass(resourceClass), resource.getForeignKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a {@link Key} to an {@link EppResource} from Datastore by foreign key.
|
||||
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
|
||||
*
|
||||
* <p>Returns null if no foreign key index with this foreign key was ever created, or if the most
|
||||
* recently created foreign key index was deleted before time "now". This method does not actually
|
||||
@@ -172,7 +144,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
public static <E extends EppResource> VKey<E> loadAndGetKey(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
ForeignKeyIndex<E> index = load(clazz, foreignKey, now);
|
||||
return (index == null) ? null : index.getResourceKey();
|
||||
return index == null ? null : index.getResourceKey();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +168,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
return loadIndexesFromStore(clazz, foreignKeys, true, false).entrySet().stream()
|
||||
return loadIndexesFromStore(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
|
||||
.collect(entriesToImmutableMap());
|
||||
}
|
||||
@@ -206,55 +178,37 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
* keys, regardless of whether or not they have been soft-deleted.
|
||||
*
|
||||
* <p>Used by both the cached (w/o deletion check) and the non-cached (with deletion check) calls.
|
||||
*
|
||||
* <p>Note that in the cached case, we wish to run this outside of any transaction because we may
|
||||
* be loading many entities, going over the Datastore limit on the number of enrolled entity
|
||||
* groups per transaction (25). If we require consistency, however, we must use a transaction.
|
||||
*
|
||||
* @param inTransaction whether or not to use an Objectify transaction
|
||||
*/
|
||||
private static <E extends EppResource>
|
||||
ImmutableMap<String, ForeignKeyIndex<E>> loadIndexesFromStore(
|
||||
Class<E> clazz,
|
||||
Collection<String> foreignKeys,
|
||||
boolean inTransaction,
|
||||
boolean useReplicaJpaTm) {
|
||||
if (tm().isOfy()) {
|
||||
Class<ForeignKeyIndex<E>> fkiClass = mapToFkiClass(clazz);
|
||||
return ImmutableMap.copyOf(
|
||||
inTransaction
|
||||
? auditedOfy().load().type(fkiClass).ids(foreignKeys)
|
||||
: tm().doTransactionless(() -> auditedOfy().load().type(fkiClass).ids(foreignKeys)));
|
||||
} else {
|
||||
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
|
||||
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
|
||||
ImmutableList<ForeignKeyIndex<E>> indexes =
|
||||
jpaTmToUse.transact(
|
||||
() ->
|
||||
jpaTmToUse
|
||||
.criteriaQuery(
|
||||
CriteriaQueryBuilder.create(clazz)
|
||||
.whereFieldIsIn(property, foreignKeys)
|
||||
.build())
|
||||
.getResultStream()
|
||||
.map(e -> ForeignKeyIndex.create(e, e.getDeletionTime()))
|
||||
.collect(toImmutableList()));
|
||||
// We need to find and return the entities with the maximum deletionTime for each foreign key.
|
||||
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
|
||||
.map(
|
||||
entry ->
|
||||
Maps.immutableEntry(
|
||||
entry.getKey(),
|
||||
entry.getValue().stream()
|
||||
.max(Comparator.comparing(ForeignKeyIndex::getDeletionTime))
|
||||
.get()))
|
||||
.collect(entriesToImmutableMap());
|
||||
}
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaJpaTm) {
|
||||
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
|
||||
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
|
||||
ImmutableList<ForeignKeyIndex<E>> indexes =
|
||||
jpaTmToUse.transact(
|
||||
() ->
|
||||
jpaTmToUse
|
||||
.criteriaQuery(
|
||||
CriteriaQueryBuilder.create(clazz)
|
||||
.whereFieldIsIn(property, foreignKeys)
|
||||
.build())
|
||||
.getResultStream()
|
||||
.map(e -> create(e, e.getDeletionTime()))
|
||||
.collect(toImmutableList()));
|
||||
// We need to find and return the entities with the maximum deletionTime for each foreign key.
|
||||
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
|
||||
.map(
|
||||
entry ->
|
||||
Maps.immutableEntry(
|
||||
entry.getKey(),
|
||||
entry.getValue().stream()
|
||||
.max(Comparator.comparing(ForeignKeyIndex::getDeletionTime))
|
||||
.get()))
|
||||
.collect(entriesToImmutableMap());
|
||||
}
|
||||
|
||||
static final CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> CACHE_LOADER =
|
||||
new AppEngineEnvironmentCacheLoader<
|
||||
VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>() {
|
||||
new CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>() {
|
||||
|
||||
@Override
|
||||
public Optional<ForeignKeyIndex<?>> load(VKey<ForeignKeyIndex<?>> key) {
|
||||
@@ -263,7 +217,6 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
loadIndexesFromStore(
|
||||
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(key.getKind()),
|
||||
ImmutableSet.of(foreignKey),
|
||||
false,
|
||||
true)
|
||||
.get(foreignKey));
|
||||
}
|
||||
@@ -280,7 +233,7 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
Streams.stream(keys).map(v -> v.getSqlKey().toString()).collect(toImmutableSet());
|
||||
ImmutableSet<VKey<ForeignKeyIndex<?>>> typedKeys = ImmutableSet.copyOf(keys);
|
||||
ImmutableMap<String, ? extends ForeignKeyIndex<? extends EppResource>> existingFkis =
|
||||
loadIndexesFromStore(resourceClass, foreignKeys, false, true);
|
||||
loadIndexesFromStore(resourceClass, foreignKeys, true);
|
||||
// ofy omits keys that don't have values in Datastore, so re-add them in
|
||||
// here with Optional.empty() values.
|
||||
return Maps.asMap(
|
||||
@@ -333,14 +286,14 @@ public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroup
|
||||
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> loadCached(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return tm().doTransactionless(() -> load(clazz, foreignKeys, now));
|
||||
return load(clazz, foreignKeys, now);
|
||||
}
|
||||
Class<? extends ForeignKeyIndex<?>> fkiClass = mapToFkiClass(clazz);
|
||||
// Safe to cast VKey<FKI<E>> to VKey<FKI<?>>
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableList<VKey<ForeignKeyIndex<?>>> fkiVKeys =
|
||||
foreignKeys.stream()
|
||||
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.create(fkiClass, fk))
|
||||
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.createSql(fkiClass, fk))
|
||||
.collect(toImmutableList());
|
||||
// This cast is safe because when we loaded ForeignKeyIndexes above we used type clazz, which
|
||||
// is scoped to E.
|
||||
|
||||
@@ -34,7 +34,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
*
|
||||
* <p>All first class entities are represented as a resource class - {@link
|
||||
* google.registry.model.domain.Domain}, {@link google.registry.model.host.Host}, {@link
|
||||
* google.registry.model.contact.ContactResource}, and {@link
|
||||
* google.registry.model.contact.Contact}, and {@link
|
||||
* google.registry.model.registrar.Registrar}. Resource objects are written in a single shared
|
||||
* entity group per TLD. All commands that operate on those entities are grouped in a "Command"
|
||||
* class- {@link google.registry.model.domain.DomainCommand}, {@link
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
package google.registry.model.poll;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
@@ -36,7 +35,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
|
||||
implements ResponseData, UnsafeSerializable {
|
||||
|
||||
/** The inner name type that contains a name and the result boolean. */
|
||||
@Embed
|
||||
@Embeddable
|
||||
static class NameOrId extends ImmutableObject implements UnsafeSerializable {
|
||||
@XmlValue
|
||||
@@ -80,7 +78,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
|
||||
}
|
||||
|
||||
/** An adapter to output the XML in response to resolving a pending command on a domain. */
|
||||
@Embed
|
||||
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
|
||||
@XmlType(
|
||||
propOrder = {"name", "trid", "processedDate"},
|
||||
@@ -105,7 +102,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
|
||||
}
|
||||
|
||||
/** An adapter to output the XML in response to resolving a pending command on a contact. */
|
||||
@Embed
|
||||
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:contact-1.0")
|
||||
@XmlType(
|
||||
propOrder = {"id", "trid", "processedDate"},
|
||||
@@ -130,7 +126,6 @@ public class PendingActionNotificationResponse extends ImmutableObject
|
||||
}
|
||||
|
||||
/** An adapter to output the XML in response to resolving a pending command on a host. */
|
||||
@Embed
|
||||
@XmlRootElement(name = "panData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
|
||||
@XmlType(
|
||||
propOrder = {"name", "trid", "processedDate"},
|
||||
|
||||
@@ -20,21 +20,15 @@ import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.annotations.OfyIdAllocation;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactHistory.ContactHistoryId;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
@@ -55,16 +49,19 @@ import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithLongVKey;
|
||||
import google.registry.util.NullIgnoringCollectionBuilder;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.DiscriminatorColumn;
|
||||
import javax.persistence.DiscriminatorValue;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Inheritance;
|
||||
import javax.persistence.InheritanceType;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Table;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -76,10 +73,10 @@ import org.joda.time.DateTime;
|
||||
* retained for historical purposes, poll messages are truly deleted once they have been delivered
|
||||
* and ACKed.
|
||||
*
|
||||
* <p>Poll messages are parented off of the {@link HistoryEntry} that resulted in their creation.
|
||||
* This means that poll messages are contained in the Datastore entity group of the parent {@link
|
||||
* EppResource} (which can be a domain, contact, or host). It is thus possible to perform a strongly
|
||||
* consistent query to find all poll messages associated with a given EPP resource.
|
||||
* <p>Poll messages have a reference to the revision id of the {@link HistoryEntry} that resulted in
|
||||
* their creation or re-creation, in case of transfer cancellation/rejection. The revision ids are
|
||||
* not used directly by the Java code, but their presence in the database makes it easier to
|
||||
* diagnose potential issues related to poll messages.
|
||||
*
|
||||
* <p>Poll messages are identified externally by registrars using the format defined in {@link
|
||||
* PollMessageExternalKeyConverter}.
|
||||
@@ -88,29 +85,23 @@ import org.joda.time.DateTime;
|
||||
* Command</a>
|
||||
*/
|
||||
@Entity
|
||||
@ReportedOn
|
||||
@ExternalMessagingName("message")
|
||||
@javax.persistence.Entity
|
||||
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
|
||||
@DiscriminatorColumn(name = "type")
|
||||
@javax.persistence.Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "registrar_id"),
|
||||
@javax.persistence.Index(columnList = "eventTime")
|
||||
})
|
||||
@Table(indexes = {@Index(columnList = "registrar_id"), @Index(columnList = "eventTime")})
|
||||
public abstract class PollMessage extends ImmutableObject
|
||||
implements Buildable, TransferServerApproveEntity, UnsafeSerializable {
|
||||
|
||||
/** Indicates the type of entity the poll message is for. */
|
||||
public enum Type {
|
||||
DOMAIN(1L, Domain.class),
|
||||
CONTACT(2L, ContactResource.class),
|
||||
CONTACT(2L, Contact.class),
|
||||
HOST(3L, Host.class);
|
||||
|
||||
private final long id;
|
||||
private final Class<?> clazz;
|
||||
private final Class<? extends EppResource> clazz;
|
||||
|
||||
Type(long id, Class<?> clazz) {
|
||||
Type(long id, Class<? extends EppResource> clazz) {
|
||||
this.id = id;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
@@ -124,58 +115,40 @@ public abstract class PollMessage extends ImmutableObject
|
||||
}
|
||||
|
||||
/** Returns the class of the underlying resource for the poll message type. */
|
||||
public Class<?> getResourceClass() {
|
||||
public Class<? extends EppResource> getResourceClass() {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type specified by the identifer, {@code Optional.empty()} if the id is out of
|
||||
* range.
|
||||
*/
|
||||
public static Optional<Type> fromId(long id) {
|
||||
for (Type val : values()) {
|
||||
if (val.id == id) {
|
||||
return Optional.of(val);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/** Entity id. */
|
||||
@Id
|
||||
@javax.persistence.Id
|
||||
@OfyIdAllocation
|
||||
@Column(name = "poll_message_id")
|
||||
Long id;
|
||||
|
||||
/** The registrar that this poll message will be delivered to. */
|
||||
@Index
|
||||
@Column(name = "registrar_id", nullable = false)
|
||||
String clientId;
|
||||
|
||||
/** The time when the poll message should be delivered. May be in the future. */
|
||||
@Index
|
||||
@Column(nullable = false)
|
||||
DateTime eventTime;
|
||||
|
||||
/** Human readable message that will be returned with this poll message. */
|
||||
/** Human-readable message that will be returned with this poll message. */
|
||||
@Column(name = "message")
|
||||
String msg;
|
||||
|
||||
// TODO(b/456803336): Replace these fields with {Domain,Contact,Host}HistoryId objects.
|
||||
String domainRepoId;
|
||||
|
||||
@Ignore String domainRepoId;
|
||||
String contactRepoId;
|
||||
|
||||
@Ignore String contactRepoId;
|
||||
String hostRepoId;
|
||||
|
||||
@Ignore String hostRepoId;
|
||||
Long domainHistoryRevisionId;
|
||||
|
||||
@Ignore Long domainHistoryRevisionId;
|
||||
Long contactHistoryRevisionId;
|
||||
|
||||
@Ignore Long contactHistoryRevisionId;
|
||||
|
||||
@Ignore Long hostHistoryRevisionId;
|
||||
Long hostHistoryRevisionId;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
@@ -206,7 +179,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||
/**
|
||||
* Returns the contact repo id.
|
||||
*
|
||||
* <p>This may only be used on a ContactResource poll event.
|
||||
* <p>This may only be used on a {@link Contact} poll event.
|
||||
*/
|
||||
public String getContactRepoId() {
|
||||
checkArgument(getType() == Type.CONTACT);
|
||||
@@ -228,31 +201,24 @@ public abstract class PollMessage extends ImmutableObject
|
||||
* the resource.
|
||||
*/
|
||||
public String getResourceName() {
|
||||
return domainRepoId != null
|
||||
? domainRepoId
|
||||
: (contactRepoId != null ? contactRepoId : hostRepoId);
|
||||
return domainRepoId != null ? domainRepoId : contactRepoId != null ? contactRepoId : hostRepoId;
|
||||
}
|
||||
|
||||
/** Gets the underlying history revision id, regardless of the type of the resource. */
|
||||
public Long getHistoryRevisionId() {
|
||||
return domainHistoryRevisionId != null
|
||||
? domainHistoryRevisionId
|
||||
: (contactHistoryRevisionId != null ? contactHistoryRevisionId : hostHistoryRevisionId);
|
||||
: contactHistoryRevisionId != null ? contactHistoryRevisionId : hostHistoryRevisionId;
|
||||
}
|
||||
|
||||
public Type getType() {
|
||||
return domainRepoId != null ? Type.DOMAIN : (contactRepoId != null ? Type.CONTACT : Type.HOST);
|
||||
return domainRepoId != null ? Type.DOMAIN : contactRepoId != null ? Type.CONTACT : Type.HOST;
|
||||
}
|
||||
|
||||
public abstract ImmutableList<ResponseData> getResponseData();
|
||||
|
||||
@Override
|
||||
public abstract VKey<? extends PollMessage> createVKey();
|
||||
|
||||
/** Static VKey factory method for use by VKeyTranslatorFactory. */
|
||||
public static VKey<PollMessage> createVKey(Key<PollMessage> key) {
|
||||
return VKey.create(PollMessage.class, key.getId(), key);
|
||||
}
|
||||
public abstract ImmutableList<ResponseData> getResponseData();
|
||||
|
||||
/** Override Buildable.asBuilder() to give this method stronger typing. */
|
||||
@Override
|
||||
@@ -318,9 +284,11 @@ public abstract class PollMessage extends ImmutableObject
|
||||
// Set the appropriate field based on the history entry type.
|
||||
if (history instanceof DomainHistory) {
|
||||
return setDomainHistoryId(((DomainHistory) history).getDomainHistoryId());
|
||||
} else if (history instanceof ContactHistory) {
|
||||
}
|
||||
if (history instanceof ContactHistory) {
|
||||
return setContactHistoryId(((ContactHistory) history).getContactHistoryId());
|
||||
} else if (history instanceof HostHistory) {
|
||||
}
|
||||
if (history instanceof HostHistory) {
|
||||
return setHostHistoryId(((HostHistory) history).getHostHistoryId());
|
||||
}
|
||||
return thisCastToDerived();
|
||||
@@ -330,7 +298,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||
* Given an array containing pairs of objects, verifies that both members of exactly one of the
|
||||
* pairs is non-null.
|
||||
*/
|
||||
private boolean exactlyOnePairNonNull(Object... pairs) {
|
||||
private static boolean exactlyOnePairNonNull(Object... pairs) {
|
||||
int count = 0;
|
||||
checkArgument(pairs.length % 2 == 0, "Odd number of arguments provided.");
|
||||
for (int i = 0; i < pairs.length; i += 2) {
|
||||
@@ -360,9 +328,9 @@ public abstract class PollMessage extends ImmutableObject
|
||||
instance.contactHistoryRevisionId,
|
||||
instance.hostRepoId,
|
||||
instance.hostHistoryRevisionId),
|
||||
"the repo id and history revision id must be defined for exactly one of domain, "
|
||||
+ "contact or host: "
|
||||
+ instance);
|
||||
"The repo id and history revision id must be defined for exactly one of domain, "
|
||||
+ "contact or host: %s",
|
||||
instance);
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
@@ -372,13 +340,11 @@ public abstract class PollMessage extends ImmutableObject
|
||||
*
|
||||
* <p>One-time poll messages are deleted from Datastore once they have been delivered and ACKed.
|
||||
*/
|
||||
@EntitySubclass(index = false)
|
||||
@javax.persistence.Entity
|
||||
@Entity
|
||||
@DiscriminatorValue("ONE_TIME")
|
||||
@WithLongVKey(compositeKey = true)
|
||||
public static class OneTime extends PollMessage {
|
||||
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
@@ -399,7 +365,6 @@ public abstract class PollMessage extends ImmutableObject
|
||||
})
|
||||
PendingActionNotificationResponse pendingActionNotificationResponse;
|
||||
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
@@ -420,35 +385,21 @@ public abstract class PollMessage extends ImmutableObject
|
||||
})
|
||||
TransferResponse transferResponse;
|
||||
|
||||
@Ignore
|
||||
@Column(name = "transfer_response_domain_name")
|
||||
String fullyQualifiedDomainName;
|
||||
|
||||
@Ignore
|
||||
@Column(name = "transfer_response_domain_expiration_time")
|
||||
DateTime extendedRegistrationExpirationTime;
|
||||
|
||||
@Ignore
|
||||
@Column(name = "transfer_response_contact_id")
|
||||
String contactId;
|
||||
|
||||
@Ignore
|
||||
@Column(name = "transfer_response_host_id")
|
||||
String hostId;
|
||||
|
||||
@Override
|
||||
public VKey<OneTime> createVKey() {
|
||||
return VKey.create(OneTime.class, getId(), Key.create(this));
|
||||
}
|
||||
|
||||
/** Converts an unspecialized VKey<PollMessage> to a VKey of the derived class. */
|
||||
public static @Nullable VKey<OneTime> convertVKey(@Nullable VKey<? extends PollMessage> key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
Key<OneTime> ofyKey =
|
||||
Key.create(key.getOfyKey().getParent(), OneTime.class, key.getOfyKey().getId());
|
||||
return VKey.create(OneTime.class, key.getSqlKey(), ofyKey);
|
||||
return VKey.createSql(OneTime.class, getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -525,8 +476,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||
/** A builder for {@link OneTime} since it is immutable. */
|
||||
public static class Builder extends PollMessage.Builder<OneTime, Builder> {
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
public Builder() {}
|
||||
|
||||
private Builder(OneTime instance) {
|
||||
super(instance);
|
||||
@@ -587,14 +537,13 @@ public abstract class PollMessage extends ImmutableObject
|
||||
}
|
||||
|
||||
/**
|
||||
* An auto-renew poll message which recurs annually.
|
||||
* An autorenew poll message which recurs annually.
|
||||
*
|
||||
* <p>Auto-renew poll messages are not deleted until the registration of their parent domain has
|
||||
* been canceled, because there will always be a speculative renewal for next year until that
|
||||
* happens.
|
||||
*/
|
||||
@EntitySubclass(index = false)
|
||||
@javax.persistence.Entity
|
||||
@Entity
|
||||
@DiscriminatorValue("AUTORENEW")
|
||||
@WithLongVKey(compositeKey = true)
|
||||
public static class Autorenew extends PollMessage {
|
||||
@@ -604,7 +553,6 @@ public abstract class PollMessage extends ImmutableObject
|
||||
String targetId;
|
||||
|
||||
/** The autorenew recurs annually between {@link #eventTime} and this time. */
|
||||
@Index
|
||||
DateTime autorenewEndTime;
|
||||
|
||||
public String getTargetId() {
|
||||
@@ -617,25 +565,14 @@ public abstract class PollMessage extends ImmutableObject
|
||||
|
||||
@Override
|
||||
public VKey<Autorenew> createVKey() {
|
||||
return VKey.create(Autorenew.class, getId(), Key.create(this));
|
||||
}
|
||||
|
||||
/** Converts an unspecialized VKey<PollMessage> to a VKey of the derived class. */
|
||||
public static @Nullable VKey<Autorenew> convertVKey(VKey<? extends PollMessage> key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
Key<Autorenew> ofyKey =
|
||||
Key.create(key.getOfyKey().getParent(), Autorenew.class, key.getOfyKey().getId());
|
||||
return VKey.create(Autorenew.class, key.getSqlKey(), ofyKey);
|
||||
return VKey.createSql(Autorenew.class, getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImmutableList<ResponseData> getResponseData() {
|
||||
// Note that the event time is when the auto-renew occured, so the expiration time in the
|
||||
// Note that the event time is when the autorenew occurred, so the expiration time in the
|
||||
// response should be 1 year past that, since it denotes the new expiration time.
|
||||
return ImmutableList.of(
|
||||
DomainRenewData.create(getTargetId(), getEventTime().plusYears(1)));
|
||||
return ImmutableList.of(DomainRenewData.create(getTargetId(), getEventTime().plusYears(1)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,48 +14,38 @@
|
||||
|
||||
package google.registry.model.poll;
|
||||
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A converter between external key strings for {@link PollMessage}s (i.e. what registrars use to
|
||||
* identify and ACK them) and Datastore keys to the resource.
|
||||
* identify and ACK them) and {@link VKey}s to the resource.
|
||||
*
|
||||
* <p>The format of the key string is A-B-C-D-E-F as follows:
|
||||
* <p>The format of the key string is A-B as follows:
|
||||
*
|
||||
* <pre>
|
||||
* A = EppResource.typeId (decimal)
|
||||
* B = EppResource.repoId prefix (STRING)
|
||||
* C = EppResource.repoId suffix (STRING)
|
||||
* D = HistoryEntry.id (decimal)
|
||||
* E = PollMessage.id (decimal)
|
||||
* F = PollMessage.eventTime (decimal, year only)
|
||||
* A = PollMessage.id (decimal)
|
||||
* B = PollMessage.eventTime (decimal, year only)
|
||||
* </pre>
|
||||
*
|
||||
* <p>A typical poll message ID might thus look like: 1-FE0F22-TLD-10071463070-10072612074-2018
|
||||
* <p>A typical poll message ID might thus look like: 10072612074-2018
|
||||
*/
|
||||
public class PollMessageExternalKeyConverter {
|
||||
public final class PollMessageExternalKeyConverter {
|
||||
|
||||
/** An exception thrown when an external key cannot be parsed. */
|
||||
public static class PollMessageExternalKeyParseException extends RuntimeException {}
|
||||
public static class PollMessageExternalKeyParseException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 9026397649048831777L;
|
||||
}
|
||||
|
||||
/** Returns an external poll message ID for the given poll message. */
|
||||
public static String makePollMessageExternalId(PollMessage pollMessage) {
|
||||
return String.format(
|
||||
"%d-%s-%d-%d-%d",
|
||||
pollMessage.getType().getId(),
|
||||
pollMessage.getResourceName(),
|
||||
pollMessage.getHistoryRevisionId(),
|
||||
pollMessage.getId(),
|
||||
pollMessage.getEventTime().getYear());
|
||||
return String.format("%d-%d", pollMessage.getId(), pollMessage.getEventTime().getYear());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Objectify Key to a PollMessage corresponding with the external ID.
|
||||
* Returns a {@link VKey} to a {@link PollMessage} corresponding with the external ID.
|
||||
*
|
||||
* <p>Note that the year field that is included at the end of the poll message isn't actually used
|
||||
* for anything; it exists solely to create unique externally visible IDs for autorenews. We thus
|
||||
@@ -66,26 +56,13 @@ public class PollMessageExternalKeyConverter {
|
||||
*/
|
||||
public static VKey<PollMessage> parsePollMessageExternalId(String externalKey) {
|
||||
List<String> idComponents = Splitter.on('-').splitToList(externalKey);
|
||||
if (idComponents.size() != 6) {
|
||||
if (idComponents.size() != 2) {
|
||||
throw new PollMessageExternalKeyParseException();
|
||||
}
|
||||
try {
|
||||
Class<?> resourceClazz =
|
||||
PollMessage.Type.fromId(Long.parseLong(idComponents.get(0)))
|
||||
.orElseThrow(() -> new PollMessageExternalKeyParseException())
|
||||
.getResourceClass();
|
||||
return VKey.from(
|
||||
Key.create(
|
||||
Key.create(
|
||||
Key.create(
|
||||
null,
|
||||
resourceClazz,
|
||||
String.format("%s-%s", idComponents.get(1), idComponents.get(2))),
|
||||
HistoryEntry.class,
|
||||
Long.parseLong(idComponents.get(3))),
|
||||
PollMessage.class,
|
||||
Long.parseLong(idComponents.get(4))));
|
||||
// Note that idComponents.get(5) is entirely ignored; we never use the year field internally.
|
||||
Long id = Long.parseLong(idComponents.get(0));
|
||||
return VKey.createSql(PollMessage.class, id);
|
||||
// Note that idComponents.get(1) is entirely ignored; we never use the year field internally.
|
||||
} catch (NumberFormatException e) {
|
||||
throw new PollMessageExternalKeyParseException();
|
||||
}
|
||||
|
||||
@@ -19,10 +19,6 @@ import static google.registry.model.rde.RdeNamingUtils.makePartialName;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.base.VerifyException;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.rde.RdeRevision.RdeRevisionId;
|
||||
@@ -32,10 +28,11 @@ import java.io.Serializable;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Convert;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.IdClass;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.LocalDate;
|
||||
|
||||
@@ -47,18 +44,14 @@ import org.joda.time.LocalDate;
|
||||
* flag is included in the generated XML.
|
||||
*/
|
||||
@Entity
|
||||
@javax.persistence.Entity
|
||||
@IdClass(RdeRevisionId.class)
|
||||
public final class RdeRevision extends BackupGroupRoot {
|
||||
|
||||
/** String triplet of tld, date, and mode, e.g. {@code soy_2015-09-01_full}. */
|
||||
@Id @Transient String id;
|
||||
@Id String tld;
|
||||
|
||||
@javax.persistence.Id @Ignore String tld;
|
||||
@Id LocalDate date;
|
||||
|
||||
@javax.persistence.Id @Ignore LocalDate date;
|
||||
|
||||
@javax.persistence.Id @Ignore RdeMode mode;
|
||||
@Id RdeMode mode;
|
||||
|
||||
/**
|
||||
* Number of last revision successfully staged to GCS.
|
||||
@@ -71,10 +64,8 @@ public final class RdeRevision extends BackupGroupRoot {
|
||||
/** Hibernate requires an empty constructor. */
|
||||
private RdeRevision() {}
|
||||
|
||||
public static RdeRevision create(
|
||||
String id, String tld, LocalDate date, RdeMode mode, int revision) {
|
||||
public static RdeRevision create(String tld, LocalDate date, RdeMode mode, int revision) {
|
||||
RdeRevision instance = new RdeRevision();
|
||||
instance.id = id;
|
||||
instance.tld = tld;
|
||||
instance.date = date;
|
||||
instance.mode = mode;
|
||||
@@ -92,12 +83,9 @@ public final class RdeRevision extends BackupGroupRoot {
|
||||
* @return {@code 0} for first deposit generation and {@code >0} for resends
|
||||
*/
|
||||
public static int getNextRevision(String tld, DateTime date, RdeMode mode) {
|
||||
String id = makePartialName(tld, date, mode);
|
||||
RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
|
||||
Key<RdeRevision> ofyKey = Key.create(RdeRevision.class, id);
|
||||
Optional<RdeRevision> revisionOptional =
|
||||
tm().transact(
|
||||
() -> tm().loadByKeyIfPresent(VKey.create(RdeRevision.class, sqlKey, ofyKey)));
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(RdeRevision.class, sqlKey)));
|
||||
return revisionOptional.map(rdeRevision -> rdeRevision.revision + 1).orElse(0);
|
||||
}
|
||||
|
||||
@@ -121,12 +109,10 @@ public final class RdeRevision extends BackupGroupRoot {
|
||||
*/
|
||||
public static void saveRevision(String tld, DateTime date, RdeMode mode, int revision) {
|
||||
checkArgument(revision >= 0, "Negative revision: %s", revision);
|
||||
String triplet = makePartialName(tld, date, mode);
|
||||
tm().assertInTransaction();
|
||||
RdeRevisionId sqlKey = RdeRevisionId.create(tld, date.toLocalDate(), mode);
|
||||
Key<RdeRevision> ofyKey = Key.create(RdeRevision.class, triplet);
|
||||
Optional<RdeRevision> revisionOptional =
|
||||
tm().loadByKeyIfPresent(VKey.create(RdeRevision.class, sqlKey, ofyKey));
|
||||
tm().loadByKeyIfPresent(VKey.createSql(RdeRevision.class, sqlKey));
|
||||
if (revision == 0) {
|
||||
revisionOptional.ifPresent(
|
||||
rdeRevision -> {
|
||||
@@ -139,7 +125,7 @@ public final class RdeRevision extends BackupGroupRoot {
|
||||
checkArgument(
|
||||
revisionOptional.isPresent(),
|
||||
"Couldn't find existing RDE revision %s when trying to save new revision %s",
|
||||
triplet,
|
||||
makePartialName(tld, date, mode),
|
||||
revision);
|
||||
checkArgument(
|
||||
revisionOptional.get().revision == revision - 1,
|
||||
@@ -147,7 +133,7 @@ public final class RdeRevision extends BackupGroupRoot {
|
||||
revision - 1,
|
||||
revisionOptional.get());
|
||||
}
|
||||
RdeRevision object = RdeRevision.create(triplet, tld, date.toLocalDate(), mode, revision);
|
||||
RdeRevision object = create(tld, date.toLocalDate(), mode, revision);
|
||||
tm().put(object);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,6 @@ import static com.google.common.collect.Sets.immutableEnumSet;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.config.RegistryConfig.getDefaultRegistrarWhoisServer;
|
||||
import static google.registry.model.CacheUtils.memoizeWithShortExpiration;
|
||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.model.tld.Registries.assertTldsExist;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -44,7 +42,6 @@ import static java.util.function.Predicate.isEqual;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Supplier;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -55,14 +52,6 @@ import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.re2j.Pattern;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
@@ -70,9 +59,6 @@ import google.registry.model.JsonMapBuilder;
|
||||
import google.registry.model.Jsonifiable;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.tld.Registry.TldType;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -85,6 +71,7 @@ import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.mail.internet.AddressException;
|
||||
import javax.mail.internet.InternetAddress;
|
||||
@@ -92,25 +79,22 @@ import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
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"),
|
||||
@Index(columnList = "registrarName", name = "registrar_name_idx"),
|
||||
@Index(columnList = "ianaIdentifier", name = "registrar_iana_identifier_idx"),
|
||||
})
|
||||
@InCrossTld
|
||||
public class Registrar extends ImmutableObject
|
||||
implements Buildable, Jsonifiable, UnsafeSerializable {
|
||||
|
||||
@@ -199,7 +183,7 @@ public class Registrar extends ImmutableObject
|
||||
|
||||
/** The states in which a {@link Registrar} is considered {@link #isLive live}. */
|
||||
private static final ImmutableSet<State> LIVE_STATES =
|
||||
Sets.immutableEnumSet(State.ACTIVE, State.SUSPENDED);
|
||||
immutableEnumSet(State.ACTIVE, State.SUSPENDED);
|
||||
|
||||
/**
|
||||
* The types for which a {@link Registrar} should be included in WHOIS and RDAP output. We exclude
|
||||
@@ -215,18 +199,9 @@ public class Registrar extends ImmutableObject
|
||||
private static final Comparator<RegistrarPoc> CONTACT_EMAIL_COMPARATOR =
|
||||
comparing(RegistrarPoc::getEmailAddress, String::compareTo);
|
||||
|
||||
/**
|
||||
* A caching {@link Supplier} of a registrarId to {@link Registrar} map.
|
||||
*
|
||||
* <p>The supplier's get() method enters a transactionless context briefly to avoid enrolling the
|
||||
* query inside an unrelated client-affecting transaction.
|
||||
*/
|
||||
/** A caching {@link Supplier} of a registrarId to {@link Registrar} map. */
|
||||
private static final Supplier<ImmutableMap<String, Registrar>> CACHE_BY_REGISTRAR_ID =
|
||||
memoizeWithShortExpiration(
|
||||
() ->
|
||||
tm().doTransactionless(() -> Maps.uniqueIndex(loadAll(), Registrar::getRegistrarId)));
|
||||
|
||||
@Parent @Transient Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
memoizeWithShortExpiration(() -> Maps.uniqueIndex(loadAll(), Registrar::getRegistrarId));
|
||||
|
||||
/**
|
||||
* Unique registrar client id. Must conform to "clIDType" as defined in RFC5730.
|
||||
@@ -235,7 +210,6 @@ public class Registrar extends ImmutableObject
|
||||
* <p>TODO(b/177567432): Rename this field to registrarId.
|
||||
*/
|
||||
@Id
|
||||
@javax.persistence.Id
|
||||
@Column(name = "registrarId", nullable = false)
|
||||
String clientIdentifier;
|
||||
|
||||
@@ -250,7 +224,6 @@ public class Registrar extends ImmutableObject
|
||||
* @see <a href="http://www.icann.org/registrar-reports/accredited-list.html">ICANN-Accredited
|
||||
* Registrars</a>
|
||||
*/
|
||||
@Index
|
||||
@Column(nullable = false)
|
||||
String registrarName;
|
||||
|
||||
@@ -315,7 +288,6 @@ public class Registrar extends ImmutableObject
|
||||
* Localized {@link RegistrarAddress} for this registrar. Contents can be represented in
|
||||
* unrestricted UTF-8.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
@@ -340,7 +312,6 @@ public class Registrar extends ImmutableObject
|
||||
* Internationalized {@link RegistrarAddress} for this registrar. All contained values must be
|
||||
* representable in the 7-bit US-ASCII character set.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "streetLine1", column = @Column(name = "i18n_address_street_line1")),
|
||||
@@ -369,14 +340,14 @@ public class Registrar extends ImmutableObject
|
||||
*
|
||||
* <ul>
|
||||
* <li>8 is used for Testing Registrar.
|
||||
* <li>9997 is used by ICAAN for SLA monitoring.
|
||||
* <li>9997 is used by ICANN for SLA monitoring.
|
||||
* <li>9999 is used for cases when the registry operator acts as registrar.
|
||||
* </ul>
|
||||
*
|
||||
* @see <a href="http://www.iana.org/assignments/registrar-ids/registrar-ids.txt">Registrar
|
||||
* IDs</a>
|
||||
*/
|
||||
@Index @Nullable Long ianaIdentifier;
|
||||
@Nullable Long ianaIdentifier;
|
||||
|
||||
/** Purchase Order number used for invoices in external billing system, if applicable. */
|
||||
@Nullable String poNumber;
|
||||
@@ -397,7 +368,7 @@ public class Registrar extends ImmutableObject
|
||||
/**
|
||||
* ICANN referral email address.
|
||||
*
|
||||
* <p>This value is specified in the initial registrar contact. It can't be edited in the web GUI
|
||||
* <p>This value is specified in the initial registrar contact. It can't be edited in the web GUI,
|
||||
* and it must be specified when the registrar account is created.
|
||||
*/
|
||||
String icannReferralEmail;
|
||||
@@ -408,10 +379,10 @@ public class Registrar extends ImmutableObject
|
||||
// Metadata.
|
||||
|
||||
/** The time when this registrar was created. */
|
||||
@Ignore CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
CreateAutoTimestamp creationTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/** An automatically managed last-saved timestamp. */
|
||||
@Ignore UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
|
||||
UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
|
||||
|
||||
/** The time that the certificate was last updated. */
|
||||
DateTime lastCertificateUpdateTime;
|
||||
@@ -612,18 +583,13 @@ public class Registrar extends ImmutableObject
|
||||
}
|
||||
|
||||
private Iterable<RegistrarPoc> getContactsIterable() {
|
||||
if (tm().isOfy()) {
|
||||
return auditedOfy().load().type(RegistrarPoc.class).ancestor(Registrar.this);
|
||||
} else {
|
||||
return tm().transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query(
|
||||
"FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
|
||||
.setParameter("registrarId", clientIdentifier)
|
||||
.getResultStream()
|
||||
.collect(toImmutableList()));
|
||||
}
|
||||
return tm().transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
|
||||
.setParameter("registrarId", clientIdentifier)
|
||||
.getResultStream()
|
||||
.collect(toImmutableList()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -689,18 +655,13 @@ public class Registrar extends ImmutableObject
|
||||
/** Creates a {@link VKey} for this instance. */
|
||||
@Override
|
||||
public VKey<Registrar> createVKey() {
|
||||
return createVKey(Key.create(this));
|
||||
return createVKey(clientIdentifier);
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} for the given {@code registrarId}. */
|
||||
public static VKey<Registrar> createVKey(String registrarId) {
|
||||
checkArgumentNotNull(registrarId, "registrarId must be specified");
|
||||
return createVKey(Key.create(getCrossTldKey(), Registrar.class, registrarId));
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance from a {@link Key} instance. */
|
||||
public static VKey<Registrar> createVKey(Key<Registrar> key) {
|
||||
return VKey.create(Registrar.class, key.getName(), key);
|
||||
return VKey.createSql(Registrar.class, registrarId);
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link Registrar}, since it is immutable. */
|
||||
@@ -776,7 +737,7 @@ public class Registrar extends ImmutableObject
|
||||
Set<VKey<Registry>> missingTldKeys =
|
||||
Sets.difference(
|
||||
newTldKeys, tm().transact(() -> tm().loadByKeysIfPresent(newTldKeys)).keySet());
|
||||
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexisting TLDs: %s", missingTldKeys);
|
||||
checkArgument(missingTldKeys.isEmpty(), "Trying to set nonexistent TLDs: %s", missingTldKeys);
|
||||
getInstance().allowedTlds = ImmutableSortedSet.copyOf(allowedTlds);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||
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;
|
||||
|
||||
@@ -29,7 +28,6 @@ import javax.persistence.Embeddable;
|
||||
* all defined in parent class {@link Address} so that it can share it with other similar address
|
||||
* classes.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class RegistrarAddress extends Address {
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
@@ -43,15 +41,13 @@ import org.joda.time.DateTime;
|
||||
* only exception is for reportField = TRANSFER_LOSING_SUCCESSFUL or TRANSFER_LOSING_NACKED, which
|
||||
* uses HistoryEntry.otherClientId because the losing party in a transfer is always the otherClient.
|
||||
*/
|
||||
@Embed
|
||||
@Entity
|
||||
public class DomainTransactionRecord extends ImmutableObject
|
||||
implements Buildable, UnsafeSerializable {
|
||||
|
||||
@Id
|
||||
@Ignore
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@ImmutableObject.Insignificant
|
||||
@Insignificant
|
||||
Long id;
|
||||
|
||||
/** The TLD this record operates on. */
|
||||
@@ -62,9 +58,9 @@ public class DomainTransactionRecord extends ImmutableObject
|
||||
// Datastore-SQL validation. They are excluded from equality check since they are not set in
|
||||
// Datastore.
|
||||
// TODO(b/203609782): post migration, decide whether to keep these two fields.
|
||||
@Ignore @ImmutableObject.Insignificant String domainRepoId;
|
||||
@Insignificant String domainRepoId;
|
||||
|
||||
@Ignore @ImmutableObject.Insignificant Long historyRevisionId;
|
||||
@Insignificant Long historyRevisionId;
|
||||
|
||||
/**
|
||||
* The time this Transaction takes effect (counting grace periods and other nuances).
|
||||
|
||||
@@ -25,19 +25,18 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactBase;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactHistory.ContactHistoryId;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
@@ -155,8 +154,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
|
||||
* The length of time that a create, allocate, renewal, or transfer request was issued for. Will
|
||||
* be null for all other types.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Transient // domain-specific
|
||||
@Ignore @Transient // domain-specific
|
||||
Period period;
|
||||
|
||||
/**
|
||||
@@ -189,6 +187,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
|
||||
|
||||
/** Transaction id that made this change, or null if the entry was not created by a flow. */
|
||||
@Nullable
|
||||
@Ignore
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(
|
||||
name = "clientTransactionId",
|
||||
@@ -218,6 +217,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
|
||||
* also be empty if the HistoryEntry refers to an EPP mutation that does not affect domain
|
||||
* transaction counts (such as contact or host mutations).
|
||||
*/
|
||||
@Ignore
|
||||
@Transient // domain-specific
|
||||
@ImmutableObject.EmptySetToNull
|
||||
protected Set<DomainTransactionRecord> domainTransactionRecords;
|
||||
@@ -392,7 +392,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
|
||||
} else if (parentKind.equals(getKind(Host.class))) {
|
||||
resultEntity =
|
||||
new HostHistory.Builder().copyFrom(this).setHostRepoId(parent.getName()).build();
|
||||
} else if (parentKind.equals(getKind(ContactResource.class))) {
|
||||
} else if (parentKind.equals(getKind(Contact.class))) {
|
||||
resultEntity =
|
||||
new ContactHistory.Builder().copyFrom(this).setContactRepoId(parent.getName()).build();
|
||||
} else {
|
||||
@@ -418,7 +418,7 @@ public class HistoryEntry extends ImmutableObject implements Buildable, UnsafeSe
|
||||
HostHistory.class,
|
||||
new HostHistoryId(repoId, id),
|
||||
Key.create(parent, HostHistory.class, id));
|
||||
} else if (parentKind.equals(getKind(ContactResource.class))) {
|
||||
} else if (parentKind.equals(getKind(Contact.class))) {
|
||||
return VKey.create(
|
||||
ContactHistory.class,
|
||||
new ContactHistoryId(repoId, id),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user