mirror of
https://github.com/google/nomulus
synced 2026-06-09 16:33:02 +00:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9da24d114c | |||
| 7dd5876315 | |||
| d1a259f63a | |||
| 8c5d2e9d92 | |||
| cca1306b09 | |||
| 47071b0fbb | |||
| d83565d37e | |||
| a557b3f376 | |||
| f4a49864b5 | |||
| acdecca181 | |||
| 5264ab3fc3 | |||
| a9d59e4d6e | |||
| 1d3738da27 | |||
| 82a3a49268 | |||
| 5bbad483e4 | |||
| f6e9dae58d | |||
| c4c1c72306 | |||
| 775f672f2a | |||
| 372c854268 | |||
| edbca15bf4 | |||
| 5f41adf843 | |||
| e21f64b745 | |||
| 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 | |||
| 632e3831e5 | |||
| 9ff25f9a67 | |||
| eb1a314666 | |||
| 0e182546f9 | |||
| ad06ba2e1e | |||
| c903ed4c13 | |||
| f6d2a7ff91 | |||
| 35530616d6 | |||
| ede919d7dc | |||
| 827b7db227 | |||
| 1aefd9a78d | |||
| 950d12577f | |||
| 5d559085d7 | |||
| 268c1048cc | |||
| 74e22089fe | |||
| 9914d4d04e | |||
| f006605753 | |||
| d8e77e2ab2 |
@@ -26,6 +26,7 @@ project.convention.plugins['war'].webAppDirName =
|
||||
apply plugin: 'com.google.cloud.tools.appengine'
|
||||
|
||||
def coreResourcesDir = "${rootDir}/core/build/resources/main"
|
||||
def coreLibsDir = "${rootDir}/core/build/libs"
|
||||
|
||||
// Get the web.xml file for the service.
|
||||
war {
|
||||
@@ -38,6 +39,10 @@ war {
|
||||
from("${coreResourcesDir}/google/registry/ui/html") {
|
||||
include "*.html"
|
||||
}
|
||||
from("${coreLibsDir}") {
|
||||
include "core.jar"
|
||||
into("WEB-INF/lib")
|
||||
}
|
||||
}
|
||||
|
||||
if (project.path == ":services:default") {
|
||||
@@ -98,6 +103,7 @@ rootProject.deploy.dependsOn appengineDeployAll
|
||||
rootProject.stage.dependsOn appengineStage
|
||||
tasks['war'].dependsOn ':core:compileProdJS'
|
||||
tasks['war'].dependsOn ':core:processResources'
|
||||
tasks['war'].dependsOn ':core:jar'
|
||||
|
||||
// Impose verification for all of the deployment tasks. We haven't found a
|
||||
// better way to do this other than to apply to each of them independently.
|
||||
|
||||
+3
-3
@@ -163,7 +163,7 @@ def verifyDeploymentParams() {
|
||||
System.err.println('-----------------------------------------------------------------')
|
||||
throw new GradleException('Aborting. See prominent error above.')
|
||||
} else if (gcpProject == null) {
|
||||
def error = 'You must specify -P environment={alpha,crash,qa}'
|
||||
def error = 'You must specify -Penvironment={alpha,crash,qa}'
|
||||
System.err.println("\033[33;1m${error}\033[0m")
|
||||
throw GradleException("Aborting: ${error}")
|
||||
}
|
||||
@@ -207,8 +207,8 @@ allprojects {
|
||||
gradle.projectsEvaluated {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.fork = true
|
||||
options.forkOptions.executable =
|
||||
"${project.rootDir}/kythe/extractors/javac-wrapper.sh"
|
||||
options.forkOptions.javaHome =
|
||||
file("${System.env.REAL_JAVA_HOME}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,11 +93,11 @@ org.jacoco:org.jacoco.core:0.8.6=jacocoAnt
|
||||
org.jacoco:org.jacoco.report:0.8.6=jacocoAnt
|
||||
org.javassist:javassist:3.26.0-GA=checkstyle
|
||||
org.json:json:20160212=compileClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-core:4.6.1=testCompileClasspath,testRuntimeClasspath
|
||||
org.objenesis:objenesis:3.2=testRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testRuntimeClasspath
|
||||
|
||||
@@ -50,11 +50,11 @@ org.jacoco:org.jacoco.ant:0.8.6=jacocoAnt
|
||||
org.jacoco:org.jacoco.core:0.8.6=jacocoAnt
|
||||
org.jacoco:org.jacoco.report:0.8.6=jacocoAnt
|
||||
org.javassist:javassist:3.26.0-GA=checkstyle
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit:junit-bom:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.opentest4j:opentest4j:1.2.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.ow2.asm:asm-analysis:8.0.1=jacocoAnt
|
||||
org.ow2.asm:asm-commons:8.0.1=jacocoAnt
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -207,6 +207,9 @@
|
||||
{
|
||||
"moduleLicense": "GNU Library General Public License v2.1 or later"
|
||||
},
|
||||
{
|
||||
"moduleLicense": "GNU Lesser General Public License v3.0"
|
||||
},
|
||||
// This is just 3-clause BSD.
|
||||
{
|
||||
"moduleLicense": "Go License"
|
||||
|
||||
@@ -707,6 +707,10 @@ createToolTask(
|
||||
createToolTask(
|
||||
'jpaDemoPipeline', 'google.registry.beam.common.JpaDemoPipeline')
|
||||
|
||||
createToolTask(
|
||||
'createSyntheticDomainHistories',
|
||||
'google.registry.tools.javascrap.CreateSyntheticDomainHistoriesPipeline')
|
||||
|
||||
project.tasks.create('generateSqlSchema', JavaExec) {
|
||||
classpath = sourceSets.nonprod.runtimeClasspath
|
||||
main = 'google.registry.tools.DevTool'
|
||||
|
||||
+12
-12
@@ -368,7 +368,7 @@ org.codehaus.mojo:animal-sniffer-annotations:1.21=default,deploy_jar,nonprodRunt
|
||||
org.conscrypt:conscrypt-openjdk-uber:2.5.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.easymock:easymock:3.0=css
|
||||
org.eclipse.angus:angus-activation:1.0.0=nonprodRuntime,runtime
|
||||
org.flywaydb:flyway-core:9.0.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.flywaydb:flyway-core:9.0.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-core:4.0.0=nonprodRuntime,runtime
|
||||
org.glassfish.jaxb:jaxb-runtime:2.3.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.glassfish.jaxb:jaxb-runtime:4.0.0=nonprodRuntime,runtime
|
||||
@@ -404,17 +404,17 @@ org.json:json:20160212=soy
|
||||
org.json:json:20200518=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.jsoup:jsoup:1.15.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.junit-pioneer:junit-pioneer:1.7.1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-migrationsupport:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-runner:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-api:1.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-commons:1.9.0-RC1=testRuntimeClasspath
|
||||
org.junit:junit-bom:5.9.0-RC1=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-api:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-engine:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-migrationsupport:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.jupiter:junit-jupiter-params:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-commons:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-engine:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-launcher:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-runner:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-api:1.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.junit.platform:junit-platform-suite-commons:1.9.0=testRuntimeClasspath
|
||||
org.junit:junit-bom:5.9.0=testCompileClasspath,testRuntimeClasspath
|
||||
org.jvnet.staxex:stax-ex:1.8=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
|
||||
org.mockito:mockito-core:1.10.19=css
|
||||
org.mockito:mockito-core:4.6.1=testCompileClasspath,testRuntimeClasspath
|
||||
|
||||
@@ -29,7 +29,7 @@ import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.util.CloudTasksUtils;
|
||||
@@ -140,8 +140,8 @@ public final class AsyncTaskEnqueuer {
|
||||
}
|
||||
|
||||
/** Enqueues a task to asynchronously refresh DNS for a renamed host. */
|
||||
public void enqueueAsyncDnsRefresh(HostResource host, DateTime now) {
|
||||
VKey<HostResource> hostKey = host.createVKey();
|
||||
public void enqueueAsyncDnsRefresh(Host host, DateTime now) {
|
||||
VKey<Host> hostKey = host.createVKey();
|
||||
logger.atInfo().log("Enqueuing async DNS refresh for renamed host %s.", hostKey);
|
||||
addTaskToQueueWithRetry(
|
||||
asyncDnsRefreshPullQueue,
|
||||
|
||||
@@ -32,7 +32,7 @@ import google.registry.flows.EppController;
|
||||
import google.registry.flows.EppRequestSource;
|
||||
import google.registry.flows.PasswordOnlyTransportCredentials;
|
||||
import google.registry.flows.StatelessRequestSessionMetadata;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.ProtocolDefinition;
|
||||
import google.registry.model.eppoutput.EppOutput;
|
||||
import google.registry.persistence.transaction.QueryComposer.Comparator;
|
||||
@@ -128,10 +128,10 @@ public class DeleteExpiredDomainsAction implements Runnable {
|
||||
logger.atInfo().log(
|
||||
"Deleting non-renewing domains with autorenew end times up through %s.", runTime);
|
||||
|
||||
ImmutableList<DomainBase> domainsToDelete =
|
||||
ImmutableList<Domain> domainsToDelete =
|
||||
tm().transact(
|
||||
() ->
|
||||
tm().createQueryComposer(DomainBase.class)
|
||||
tm().createQueryComposer(Domain.class)
|
||||
.where("autorenewEndTime", Comparator.LTE, runTime)
|
||||
.where("deletionTime", Comparator.EQ, END_OF_TIME)
|
||||
.list());
|
||||
@@ -145,10 +145,9 @@ public class DeleteExpiredDomainsAction implements Runnable {
|
||||
"Found %d domains to delete: %s.",
|
||||
domainsToDelete.size(),
|
||||
String.join(
|
||||
", ",
|
||||
domainsToDelete.stream().map(DomainBase::getDomainName).collect(toImmutableList())));
|
||||
", ", domainsToDelete.stream().map(Domain::getDomainName).collect(toImmutableList())));
|
||||
int successes = 0;
|
||||
for (DomainBase domain : domainsToDelete) {
|
||||
for (Domain domain : domainsToDelete) {
|
||||
if (runDomainDeleteFlow(domain)) {
|
||||
successes++;
|
||||
}
|
||||
@@ -163,7 +162,7 @@ public class DeleteExpiredDomainsAction implements Runnable {
|
||||
}
|
||||
|
||||
/** Runs the actual domain delete flow and returns whether the deletion was successful. */
|
||||
private boolean runDomainDeleteFlow(DomainBase domain) {
|
||||
private boolean runDomainDeleteFlow(Domain domain) {
|
||||
logger.atInfo().log("Attempting to delete domain '%s'.", domain.getDomainName());
|
||||
// Create a new transaction that the flow's execution will be enlisted in that loads the domain
|
||||
// transactionally. This way we can ensure that nothing else has modified the domain in question
|
||||
@@ -171,7 +170,7 @@ public class DeleteExpiredDomainsAction implements Runnable {
|
||||
Optional<EppOutput> eppOutput =
|
||||
tm().transact(
|
||||
() -> {
|
||||
DomainBase transDomain = tm().loadByKey(domain.createVKey());
|
||||
Domain transDomain = tm().loadByKey(domain.createVKey());
|
||||
if (!domain.getAutorenewEndTime().isPresent()
|
||||
|| domain.getAutorenewEndTime().get().isAfter(tm().getTransactionTime())) {
|
||||
logger.atSevere().log(
|
||||
|
||||
@@ -29,9 +29,9 @@ 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.domain.DomainBase;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
@@ -43,8 +43,8 @@ import google.registry.util.Clock;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Hard deletes load-test ContactResources, HostResources, 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 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,8 +92,8 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
tm().transact(
|
||||
() -> {
|
||||
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
|
||||
tm().loadAllOfStream(ContactResource.class).forEach(this::deleteContact);
|
||||
tm().loadAllOfStream(HostResource.class).forEach(this::deleteHost);
|
||||
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;
|
||||
}
|
||||
@@ -123,19 +123,19 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
deleteResource(contact);
|
||||
}
|
||||
|
||||
private void deleteHost(HostResource host) {
|
||||
private void deleteHost(Host host) {
|
||||
if (!LOAD_TEST_REGISTRARS.contains(host.getPersistedCurrentSponsorRegistrarId())) {
|
||||
return;
|
||||
}
|
||||
VKey<HostResource> hostVKey = host.createVKey();
|
||||
VKey<Host> hostVKey = host.createVKey();
|
||||
// We can remove hosts from linked domains, so we should do so then delete the hosts
|
||||
ImmutableSet<VKey<DomainBase>> linkedDomains =
|
||||
ImmutableSet<VKey<Domain>> linkedDomains =
|
||||
EppResourceUtils.getLinkedDomainKeys(hostVKey, clock.nowUtc(), null);
|
||||
tm().loadByKeys(linkedDomains)
|
||||
.values()
|
||||
.forEach(
|
||||
domain -> {
|
||||
ImmutableSet<VKey<HostResource>> remainingHosts =
|
||||
ImmutableSet<VKey<Host>> remainingHosts =
|
||||
domain.getNsHosts().stream()
|
||||
.filter(vkey -> !vkey.equals(hostVKey))
|
||||
.collect(toImmutableSet());
|
||||
|
||||
@@ -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;
|
||||
@@ -37,7 +36,7 @@ import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.dns.DnsQueue;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.tld.Registry.TldType;
|
||||
import google.registry.request.Action;
|
||||
@@ -53,8 +52,8 @@ import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* Deletes all prober DomainBases and their subordinate history entries, poll messages, and billing
|
||||
* events, along with their ForeignKeyDomainIndex and EppResourceIndex entities.
|
||||
* Deletes all prober {@link Domain}s and their subordinate history entries, poll messages, and
|
||||
* billing events, along with their ForeignKeyDomainIndex entities.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
@@ -92,7 +91,7 @@ public class DeleteProberDataAction implements Runnable {
|
||||
// Note: creationTime must be compared to a Java object (CreateAutoTimestamp) but deletionTime can
|
||||
// be compared directly to the SQL timestamp (it's a DateTime)
|
||||
private static final String DOMAIN_QUERY_STRING =
|
||||
"FROM Domain d WHERE d.tld IN :tlds AND d.fullyQualifiedDomainName NOT LIKE 'nic.%' AND"
|
||||
"FROM Domain d WHERE d.tld IN :tlds AND d.domainName NOT LIKE 'nic.%' AND"
|
||||
+ " (d.subordinateHosts IS EMPTY OR d.subordinateHosts IS NULL) AND d.creationTime <"
|
||||
+ " :creationTimeCutoff AND ((d.creationTime <= :nowAutoTimestamp AND d.deletionTime >"
|
||||
+ " current_timestamp()) OR d.deletionTime < :nowMinusSoftDeleteDelay) ORDER BY d.repoId";
|
||||
@@ -154,7 +153,7 @@ public class DeleteProberDataAction implements Runnable {
|
||||
// keeping track of which domains to hard-delete (there can be many, so we batch them up)
|
||||
ScrollableResults scrollableResult =
|
||||
jpaTm()
|
||||
.query(DOMAIN_QUERY_STRING, DomainBase.class)
|
||||
.query(DOMAIN_QUERY_STRING, Domain.class)
|
||||
.setParameter("tlds", deletableTlds)
|
||||
.setParameter(
|
||||
"creationTimeCutoff", CreateAutoTimestamp.create(now.minus(DOMAIN_USED_DURATION)))
|
||||
@@ -166,7 +165,7 @@ public class DeleteProberDataAction implements Runnable {
|
||||
ImmutableList.Builder<String> domainRepoIdsToHardDelete = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<String> hostNamesToHardDelete = new ImmutableList.Builder<>();
|
||||
for (int i = 1; scrollableResult.next(); i = (i + 1) % BATCH_SIZE) {
|
||||
DomainBase domain = (DomainBase) scrollableResult.get(0);
|
||||
Domain domain = (Domain) scrollableResult.get(0);
|
||||
processDomain(
|
||||
domain,
|
||||
domainRepoIdsToHardDelete,
|
||||
@@ -187,7 +186,7 @@ public class DeleteProberDataAction implements Runnable {
|
||||
}
|
||||
|
||||
private void processDomain(
|
||||
DomainBase domain,
|
||||
Domain domain,
|
||||
ImmutableList.Builder<String> domainRepoIdsToHardDelete,
|
||||
ImmutableList.Builder<String> hostNamesToHardDelete,
|
||||
AtomicInteger softDeletedDomains,
|
||||
@@ -221,7 +220,7 @@ public class DeleteProberDataAction implements Runnable {
|
||||
private void hardDeleteDomainsAndHosts(
|
||||
ImmutableList<String> domainRepoIds, ImmutableList<String> hostNames) {
|
||||
jpaTm()
|
||||
.query("DELETE FROM Host WHERE fullyQualifiedHostName IN :hostNames")
|
||||
.query("DELETE FROM Host WHERE hostName IN :hostNames")
|
||||
.setParameter("hostNames", hostNames)
|
||||
.executeUpdate();
|
||||
jpaTm()
|
||||
@@ -251,8 +250,8 @@ public class DeleteProberDataAction implements Runnable {
|
||||
}
|
||||
|
||||
// Take a DNS queue + admin registrar id as input so that it can be called from the mapper as well
|
||||
private void softDeleteDomain(DomainBase domain) {
|
||||
DomainBase deletedDomain =
|
||||
private void softDeleteDomain(Domain domain) {
|
||||
Domain deletedDomain =
|
||||
domain.asBuilder().setDeletionTime(tm().getTransactionTime()).setStatusValues(null).build();
|
||||
DomainHistory historyEntry =
|
||||
new DomainHistory.Builder()
|
||||
@@ -266,9 +265,7 @@ public class DeleteProberDataAction implements Runnable {
|
||||
// Note that we don't bother handling grace periods, billing events, pending transfers, poll
|
||||
// messages, or auto-renews because those will all be hard-deleted the next time the job runs
|
||||
// anyway.
|
||||
tm().putAllWithoutBackup(ImmutableList.of(deletedDomain, historyEntry));
|
||||
// updating foreign keys is a no-op in SQL
|
||||
updateForeignKeyIndexDeletionTime(deletedDomain);
|
||||
tm().putAll(ImmutableList.of(deletedDomain, historyEntry));
|
||||
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.OneTime;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.common.Cursor;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
@@ -253,9 +253,7 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
|
||||
final ImmutableSet<DateTime> billingTimes =
|
||||
getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld);
|
||||
|
||||
VKey<DomainBase> domainKey =
|
||||
VKey.create(
|
||||
DomainBase.class, recurring.getDomainRepoId(), recurring.getParentKey().getParent());
|
||||
VKey<Domain> domainKey = VKey.createSql(Domain.class, recurring.getDomainRepoId());
|
||||
Iterable<OneTime> oneTimesForDomain;
|
||||
oneTimesForDomain =
|
||||
tm().createQueryComposer(OneTime.class)
|
||||
@@ -311,11 +309,11 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
|
||||
.getRenewCost())
|
||||
.setEventTime(eventTime)
|
||||
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
|
||||
.setParent(historyEntry)
|
||||
.setDomainHistory(historyEntry)
|
||||
.setPeriodYears(1)
|
||||
.setReason(recurring.getReason())
|
||||
.setSyntheticCreationTime(executeTime)
|
||||
.setCancellationMatchingBillingEvent(recurring.createVKey())
|
||||
.setCancellationMatchingBillingEvent(recurring)
|
||||
.setTargetId(recurring.getTargetId())
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.google.common.net.MediaType;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.RegistryLock;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
@@ -125,7 +125,7 @@ public class RelockDomainAction implements Runnable {
|
||||
|
||||
private void relockDomain() {
|
||||
RegistryLock oldLock = null;
|
||||
DomainBase domain;
|
||||
Domain domain;
|
||||
try {
|
||||
oldLock =
|
||||
RegistryLockDao.getByRevisionId(oldUnlockRevisionId)
|
||||
@@ -134,7 +134,7 @@ public class RelockDomainAction implements Runnable {
|
||||
new IllegalArgumentException(
|
||||
String.format("Unknown revision ID %d", oldUnlockRevisionId)));
|
||||
domain =
|
||||
tm().loadByKey(VKey.create(DomainBase.class, oldLock.getRepoId()))
|
||||
tm().loadByKey(VKey.create(Domain.class, oldLock.getRepoId()))
|
||||
.cloneProjectedAtTime(tm().getTransactionTime());
|
||||
} catch (Throwable t) {
|
||||
handleTransientFailure(Optional.ofNullable(oldLock), t);
|
||||
@@ -180,7 +180,7 @@ public class RelockDomainAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyDomainAndLockState(RegistryLock oldLock, DomainBase domain) {
|
||||
private void verifyDomainAndLockState(RegistryLock oldLock, Domain domain) {
|
||||
// Domain shouldn't be deleted or have a pending transfer/delete
|
||||
String domainName = domain.getDomainName();
|
||||
ImmutableSet<StatusValue> statusValues = domain.getStatusValues();
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -20,6 +20,7 @@ import dagger.Lazy;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.persistence.PersistenceModule;
|
||||
import google.registry.persistence.PersistenceModule.BeamBulkQueryJpaTm;
|
||||
import google.registry.persistence.PersistenceModule.BeamJpaTm;
|
||||
@@ -53,8 +54,7 @@ public interface RegistryPipelineComponent {
|
||||
|
||||
/**
|
||||
* Returns a {@link JpaTransactionManager} optimized for bulk loading multi-level JPA entities
|
||||
* ({@link google.registry.model.domain.DomainBase} and {@link
|
||||
* google.registry.model.domain.DomainHistory}). Please refer to {@link
|
||||
* ({@link Domain} and {@link google.registry.model.domain.DomainHistory}). Please refer to {@link
|
||||
* google.registry.model.bulkquery.BulkQueryEntities} for more information.
|
||||
*/
|
||||
@BeamBulkQueryJpaTm
|
||||
|
||||
@@ -14,47 +14,38 @@
|
||||
|
||||
package google.registry.beam.invoicing;
|
||||
|
||||
import static google.registry.beam.BeamUtils.checkFieldsNotNull;
|
||||
import static google.registry.beam.BeamUtils.extractField;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.reporting.billing.BillingModule;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.avro.generic.GenericRecord;
|
||||
import java.util.regex.Pattern;
|
||||
import org.apache.beam.sdk.coders.AtomicCoder;
|
||||
import org.apache.beam.sdk.coders.Coder;
|
||||
import org.apache.beam.sdk.coders.StringUtf8Coder;
|
||||
import org.apache.beam.sdk.io.gcp.bigquery.SchemaAndRecord;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* A POJO representing a single billable event, parsed from a {@code SchemaAndRecord}.
|
||||
*
|
||||
* <p>This is a trivially serializable class that allows Beam to transform the results of a Bigquery
|
||||
* query into a standard Java representation, giving us the type guarantees and ease of manipulation
|
||||
* Bigquery lacks, while localizing any Bigquery-side failures to the {@link #parseFromRecord}
|
||||
* function.
|
||||
* <p>This is a trivially serializable class that allows Beam to transform the results of a Cloud
|
||||
* SQL query into a standard Java representation, giving us the type guarantees and ease of
|
||||
* manipulation Cloud SQL lacks.
|
||||
*/
|
||||
@AutoValue
|
||||
public abstract class BillingEvent implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -3593088371541450077L;
|
||||
|
||||
private static final DateTimeFormatter DATE_TIME_FORMATTER =
|
||||
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss zzz");
|
||||
|
||||
/** The amount we multiply the price for sunrise creates. This is currently a 15% discount. */
|
||||
private static final double SUNRISE_DISCOUNT_PRICE_MODIFIER = 0.85;
|
||||
private static final Pattern SYNTHETIC_REGEX = Pattern.compile("SYNTHETIC", Pattern.LITERAL);
|
||||
|
||||
private static final ImmutableList<String> FIELD_NAMES =
|
||||
ImmutableList.of(
|
||||
@@ -115,67 +106,7 @@ public abstract class BillingEvent implements Serializable {
|
||||
/** Returns a list of space-delimited flags associated with the event. */
|
||||
abstract String flags();
|
||||
|
||||
/**
|
||||
* Constructs a {@code BillingEvent} from a {@code SchemaAndRecord}.
|
||||
*
|
||||
* @see <a
|
||||
* href=http://avro.apache.org/docs/1.7.7/api/java/org/apache/avro/generic/GenericData.Record.html>
|
||||
* Apache AVRO GenericRecord</a>
|
||||
*/
|
||||
static BillingEvent parseFromRecord(SchemaAndRecord schemaAndRecord) {
|
||||
checkFieldsNotNull(FIELD_NAMES, schemaAndRecord);
|
||||
GenericRecord record = schemaAndRecord.getRecord();
|
||||
String flags = extractField(record, "flags");
|
||||
double amount = getDiscountedAmount(Double.parseDouble(extractField(record, "amount")), flags);
|
||||
return create(
|
||||
// We need to chain parsers off extractField because GenericRecord only returns
|
||||
// Objects, which contain a string representation of their underlying types.
|
||||
Long.parseLong(extractField(record, "id")),
|
||||
// Bigquery provides UNIX timestamps with microsecond precision.
|
||||
new DateTime(Long.parseLong(extractField(record, "billingTime")) / 1000, DateTimeZone.UTC),
|
||||
new DateTime(Long.parseLong(extractField(record, "eventTime")) / 1000, DateTimeZone.UTC),
|
||||
extractField(record, "registrarId"),
|
||||
extractField(record, "billingId"),
|
||||
extractField(record, "poNumber"),
|
||||
extractField(record, "tld"),
|
||||
extractField(record, "action"),
|
||||
extractField(record, "domain"),
|
||||
extractField(record, "repositoryId"),
|
||||
Integer.parseInt(extractField(record, "years")),
|
||||
extractField(record, "currency"),
|
||||
amount,
|
||||
flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a discount to sunrise creates and anchor tenant creates if applicable.
|
||||
*
|
||||
* Currently sunrise creates are discounted 15% and anchor tenant creates are free for 2 years.
|
||||
* All anchor tenant creates are enforced to be 2 years in
|
||||
* {@link google.registry.flows.domain.DomainCreateFlow#verifyAnchorTenantValidPeriod}.
|
||||
*/
|
||||
private static double getDiscountedAmount(double amount, String flags) {
|
||||
// Apply a configurable discount to sunrise creates.
|
||||
if (flags.contains(Flag.SUNRISE.name())) {
|
||||
amount =
|
||||
Double.parseDouble(
|
||||
new DecimalFormat("#.##").format(amount * SUNRISE_DISCOUNT_PRICE_MODIFIER));
|
||||
}
|
||||
// Anchor tenant creates are free for the initial create. This is enforced to be a 2 year period
|
||||
// upon domain create.
|
||||
if (flags.contains(Flag.ANCHOR_TENANT.name())) {
|
||||
amount = 0;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a concrete {@code BillingEvent}.
|
||||
*
|
||||
* <p>This should only be used outside this class for testing- instances of {@code BillingEvent}
|
||||
* should otherwise come from {@link #parseFromRecord}.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
/** Creates a concrete {@link BillingEvent}. */
|
||||
static BillingEvent create(
|
||||
long id,
|
||||
DateTime billingTime,
|
||||
@@ -209,7 +140,7 @@ public abstract class BillingEvent implements Serializable {
|
||||
}
|
||||
|
||||
static String getHeader() {
|
||||
return FIELD_NAMES.stream().collect(Collectors.joining(","));
|
||||
return String.join(",", FIELD_NAMES);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,7 +173,7 @@ public abstract class BillingEvent implements Serializable {
|
||||
currency(),
|
||||
String.format("%.2f", amount()),
|
||||
// Strip out the 'synthetic' flag, which is internal only.
|
||||
flags().replace("SYNTHETIC", "").trim()));
|
||||
SYNTHETIC_REGEX.matcher(flags()).replaceAll("").trim()));
|
||||
}
|
||||
|
||||
/** Returns the grouping key for this {@code BillingEvent}, to generate the overall invoice. */
|
||||
@@ -274,6 +205,8 @@ public abstract class BillingEvent implements Serializable {
|
||||
@AutoValue
|
||||
abstract static class InvoiceGroupingKey implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = -151561764235256205L;
|
||||
|
||||
private static final ImmutableList<String> INVOICE_HEADERS =
|
||||
ImmutableList.of(
|
||||
"StartDate",
|
||||
@@ -345,6 +278,8 @@ public abstract class BillingEvent implements Serializable {
|
||||
/** Coder that provides deterministic (de)serialization for {@code InvoiceGroupingKey}. */
|
||||
static class InvoiceGroupingKeyCoder extends AtomicCoder<InvoiceGroupingKey> {
|
||||
|
||||
private static final long serialVersionUID = 6680701524304107547L;
|
||||
|
||||
@Override
|
||||
public void encode(InvoiceGroupingKey value, OutputStream outStream) throws IOException {
|
||||
Coder<String> stringCoder = StringUtf8Coder.of();
|
||||
|
||||
@@ -24,16 +24,14 @@ import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.beam.invoicing.BillingEvent.InvoiceGroupingKey;
|
||||
import google.registry.beam.invoicing.BillingEvent.InvoiceGroupingKey.InvoiceGroupingKeyCoder;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.OneTime;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.reporting.billing.BillingModule;
|
||||
import google.registry.util.DomainNameUtils;
|
||||
import google.registry.util.SqlTemplate;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.YearMonth;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -66,8 +64,7 @@ import org.joda.money.CurrencyUnit;
|
||||
*/
|
||||
public class InvoicingPipeline implements Serializable {
|
||||
|
||||
private static final DateTimeFormatter TIMESTAMP_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS");
|
||||
private static final long serialVersionUID = 5386330443625580081L;
|
||||
|
||||
private static final Pattern SQL_COMMENT_REGEX =
|
||||
Pattern.compile("^\\s*--.*\\n", Pattern.MULTILINE);
|
||||
@@ -107,8 +104,7 @@ public class InvoicingPipeline implements Serializable {
|
||||
}
|
||||
|
||||
private static Optional<BillingEvent> parseRow(Object[] row) {
|
||||
google.registry.model.billing.BillingEvent.OneTime oneTime =
|
||||
(google.registry.model.billing.BillingEvent.OneTime) row[0];
|
||||
OneTime oneTime = (OneTime) row[0];
|
||||
Registrar registrar = (Registrar) row[1];
|
||||
CurrencyUnit currency = oneTime.getCost().getCurrencyUnit();
|
||||
if (!registrar.getBillingAccountMap().containsKey(currency)) {
|
||||
@@ -140,6 +136,9 @@ public class InvoicingPipeline implements Serializable {
|
||||
/** Transform that converts a {@code BillingEvent} into an invoice CSV row. */
|
||||
private static class GenerateInvoiceRows
|
||||
extends PTransform<PCollection<BillingEvent>, PCollection<String>> {
|
||||
|
||||
private static final long serialVersionUID = -8090619008258393728L;
|
||||
|
||||
@Override
|
||||
public PCollection<String> expand(PCollection<BillingEvent> input) {
|
||||
return input
|
||||
@@ -203,32 +202,13 @@ public class InvoicingPipeline implements Serializable {
|
||||
TextIO.sink().withHeader(BillingEvent.getHeader())));
|
||||
}
|
||||
|
||||
/** Create the Bigquery query for a given project and yearMonth at runtime. */
|
||||
static String makeQuery(String yearMonth, String projectId) {
|
||||
// Get the timestamp endpoints capturing the entire month with microsecond precision
|
||||
YearMonth reportingMonth = YearMonth.parse(yearMonth);
|
||||
LocalDateTime firstMoment = reportingMonth.atDay(1).atTime(LocalTime.MIDNIGHT);
|
||||
LocalDateTime lastMoment = reportingMonth.atEndOfMonth().atTime(LocalTime.MAX);
|
||||
// Construct the month's query by filling in the billing_events.sql template
|
||||
return SqlTemplate.create(getQueryFromFile(InvoicingPipeline.class, "billing_events.sql"))
|
||||
.put("FIRST_TIMESTAMP_OF_MONTH", firstMoment.format(TIMESTAMP_FORMATTER))
|
||||
.put("LAST_TIMESTAMP_OF_MONTH", lastMoment.format(TIMESTAMP_FORMATTER))
|
||||
.put("PROJECT_ID", projectId)
|
||||
.put("DATASTORE_EXPORT_DATA_SET", "latest_datastore_export")
|
||||
.put("ONETIME_TABLE", "OneTime")
|
||||
.put("REGISTRY_TABLE", "Registry")
|
||||
.put("REGISTRAR_TABLE", "Registrar")
|
||||
.put("CANCELLATION_TABLE", "Cancellation")
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Create the Cloud SQL query for a given yearMonth at runtime. */
|
||||
static String makeCloudSqlQuery(String yearMonth) {
|
||||
YearMonth endMonth = YearMonth.parse(yearMonth).plusMonths(1);
|
||||
String queryWithComments =
|
||||
SqlTemplate.create(
|
||||
getQueryFromFile(InvoicingPipeline.class, "cloud_sql_billing_events.sql"))
|
||||
.put("FIRST_TIMESTAMP_OF_MONTH", yearMonth.concat("-01"))
|
||||
.put("FIRST_TIMESTAMP_OF_MONTH", yearMonth + "-01")
|
||||
.put(
|
||||
"LAST_TIMESTAMP_OF_MONTH",
|
||||
String.format("%d-%d-01", endMonth.getYear(), endMonth.getMonthValue()))
|
||||
|
||||
@@ -45,12 +45,12 @@ 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.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.Registrar.Type;
|
||||
@@ -132,7 +132,7 @@ import org.joda.time.DateTime;
|
||||
* that are soft-deleted by watermark. The history is emitted as pairs of (resource repo ID: history
|
||||
* revision ID) from the SQL query.
|
||||
*
|
||||
* <h3>{@link DomainBase}</h3>
|
||||
* <h3>{@link Domain}</h3>
|
||||
*
|
||||
* After the most recent (live) domain resources are loaded from the corresponding history objects,
|
||||
* we marshall them to deposit fragments and emit the (pending deposit: deposit fragment) pairs for
|
||||
@@ -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)
|
||||
@@ -148,15 +148,15 @@ import org.joda.time.DateTime;
|
||||
* then loaded from the remaining referenced contact histories, and marshalled into (pending
|
||||
* deposit: deposit fragment) pairs.
|
||||
*
|
||||
* <h3>{@link HostResource}</h3>
|
||||
* <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.
|
||||
*
|
||||
@@ -195,7 +195,7 @@ public class RdePipeline implements Serializable {
|
||||
private static final ImmutableMap<Class<? extends HistoryEntry>, String> EPP_RESOURCE_FIELD_NAME =
|
||||
ImmutableMap.of(
|
||||
DomainHistory.class,
|
||||
"domainContent",
|
||||
"domainBase",
|
||||
ContactHistory.class,
|
||||
"contactBase",
|
||||
HostHistory.class,
|
||||
@@ -301,7 +301,7 @@ public class RdePipeline implements Serializable {
|
||||
.apply(
|
||||
"Read all production Registrars",
|
||||
RegistryJpaIO.read(
|
||||
"SELECT clientIdentifier FROM Registrar WHERE type NOT IN (:types)",
|
||||
"SELECT registrarId FROM Registrar WHERE type NOT IN (:types)",
|
||||
ImmutableMap.of("types", IGNORED_REGISTRAR_TYPES),
|
||||
String.class,
|
||||
id -> VKey.createSql(Registrar.class, id)))
|
||||
@@ -466,19 +466,18 @@ 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 referencedHostCounter = Metrics.counter("RDE", "ReferencedHostResource");
|
||||
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContact");
|
||||
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
|
||||
return domainHistories.apply(
|
||||
"Map DomainHistory to DepositFragment "
|
||||
+ "and emit referenced ContactResource and HostResource",
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
|
||||
ParDo.of(
|
||||
new DoFn<KV<String, Long>, KV<PendingDeposit, DepositFragment>>() {
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<String, Long> kv, MultiOutputReceiver receiver) {
|
||||
activeDomainCounter.inc();
|
||||
DomainBase domain =
|
||||
(DomainBase)
|
||||
Domain domain =
|
||||
(Domain)
|
||||
loadResourceByHistoryEntryId(
|
||||
DomainHistory.class, kv.getKey(), kv.getValue());
|
||||
pendingDeposits.stream()
|
||||
@@ -534,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(),
|
||||
@@ -565,10 +564,10 @@ public class RdePipeline implements Serializable {
|
||||
private PCollectionTuple processHostHistories(
|
||||
PCollection<KV<String, PendingDeposit>> referencedHosts,
|
||||
PCollection<KV<String, Long>> hostHistories) {
|
||||
Counter subordinateHostCounter = Metrics.counter("RDE", "SubordinateHostResource");
|
||||
Counter externalHostCounter = Metrics.counter("RDE", "ExternalHostResource");
|
||||
Counter subordinateHostCounter = Metrics.counter("RDE", "SubordinateHost");
|
||||
Counter externalHostCounter = Metrics.counter("RDE", "ExternalHost");
|
||||
Counter externalHostFragmentCounter = Metrics.counter("RDE", "ExternalHostFragment");
|
||||
return removeUnreferencedResource(referencedHosts, hostHistories, HostResource.class)
|
||||
return removeUnreferencedResource(referencedHosts, hostHistories, Host.class)
|
||||
.apply(
|
||||
"Map external DomainResource to DepositFragment and process subordinate domains",
|
||||
ParDo.of(
|
||||
@@ -576,8 +575,8 @@ public class RdePipeline implements Serializable {
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<String, CoGbkResult> kv, MultiOutputReceiver receiver) {
|
||||
HostResource host =
|
||||
(HostResource)
|
||||
Host host =
|
||||
(Host)
|
||||
loadResourceByHistoryEntryId(
|
||||
HostHistory.class,
|
||||
kv.getKey(),
|
||||
@@ -631,11 +630,9 @@ public class RdePipeline implements Serializable {
|
||||
Counter referencedSubordinateHostCounter = Metrics.counter("RDE", "ReferencedSubordinateHost");
|
||||
return KeyedPCollectionTuple.of(HOST_TO_PENDING_DEPOSIT, superordinateDomains)
|
||||
.and(REVISION_ID, domainHistories)
|
||||
.apply("Join Host:PendingDeposits with DomainHistory on Domain", CoGroupByKey.create())
|
||||
.apply(
|
||||
"Join HostResource:PendingDeposits with DomainHistory on DomainResource",
|
||||
CoGroupByKey.create())
|
||||
.apply(
|
||||
" Remove unreferenced DomainResource",
|
||||
" Remove unreferenced Domain",
|
||||
Filter.by(
|
||||
kv -> {
|
||||
boolean toInclude =
|
||||
@@ -647,15 +644,15 @@ public class RdePipeline implements Serializable {
|
||||
return toInclude;
|
||||
}))
|
||||
.apply(
|
||||
"Map subordinate HostResource to DepositFragment",
|
||||
"Map subordinate Host to DepositFragment",
|
||||
FlatMapElements.into(
|
||||
kvs(
|
||||
TypeDescriptor.of(PendingDeposit.class),
|
||||
TypeDescriptor.of(DepositFragment.class)))
|
||||
.via(
|
||||
(KV<String, CoGbkResult> kv) -> {
|
||||
DomainBase superordinateDomain =
|
||||
(DomainBase)
|
||||
Domain superordinateDomain =
|
||||
(Domain)
|
||||
loadResourceByHistoryEntryId(
|
||||
DomainHistory.class,
|
||||
kv.getKey(),
|
||||
@@ -664,8 +661,8 @@ public class RdePipeline implements Serializable {
|
||||
new ImmutableSet.Builder<>();
|
||||
for (KV<String, CoGbkResult> hostToPendingDeposits :
|
||||
kv.getValue().getAll(HOST_TO_PENDING_DEPOSIT)) {
|
||||
HostResource host =
|
||||
(HostResource)
|
||||
Host host =
|
||||
(Host)
|
||||
loadResourceByHistoryEntryId(
|
||||
HostHistory.class,
|
||||
hostToPendingDeposits.getKey(),
|
||||
|
||||
@@ -14,24 +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.HostResource;
|
||||
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;
|
||||
@@ -51,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, DomainBase.class, HostResource.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
|
||||
@@ -67,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 ="
|
||||
@@ -86,7 +91,6 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
}
|
||||
|
||||
void setupPipeline(Pipeline pipeline) {
|
||||
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED);
|
||||
if (options.getFast()) {
|
||||
fastResaveContacts(pipeline);
|
||||
fastResaveDomains(pipeline);
|
||||
@@ -97,13 +101,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,64 +115,85 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
* transfers, grace periods).
|
||||
*
|
||||
* <p>The logic of what might have changed is paraphrased from {@link
|
||||
* google.registry.model.domain.DomainContent#cloneProjectedAtTime(DateTime)}.
|
||||
* DomainBase#cloneProjectedAtTime(DateTime)}.
|
||||
*/
|
||||
private void fastResaveDomains(Pipeline pipeline) {
|
||||
Read<DomainBase, DomainBase> read =
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
DOMAINS_TO_PROJECT_QUERY,
|
||||
ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME),
|
||||
DomainBase.class,
|
||||
d -> d);
|
||||
projectAndResaveResources(pipeline, DomainBase.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);
|
||||
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ);
|
||||
new ResaveAllEppResourcesPipeline(options).run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ public class SafeBrowsingTransforms {
|
||||
private final String apiKey;
|
||||
|
||||
/**
|
||||
* Maps a domain name's {@code fullyQualifiedDomainName} to its corresponding {@link
|
||||
* DomainNameInfo} to facilitate batching SafeBrowsing API requests.
|
||||
* Maps a domain name's {@code domainName} to its corresponding {@link DomainNameInfo} to
|
||||
* facilitate batching SafeBrowsing API requests.
|
||||
*/
|
||||
private final Map<String, DomainNameInfo> domainNameInfoBuffer =
|
||||
new LinkedHashMap<>(BATCH_SIZE);
|
||||
@@ -186,8 +186,8 @@ public class SafeBrowsingTransforms {
|
||||
private JSONObject createRequestBody() throws JSONException {
|
||||
// Accumulate all domain names to evaluate.
|
||||
JSONArray threatArray = new JSONArray();
|
||||
for (String fullyQualifiedDomainName : domainNameInfoBuffer.keySet()) {
|
||||
threatArray.put(new JSONObject().put("url", fullyQualifiedDomainName));
|
||||
for (String domainName : domainNameInfoBuffer.keySet()) {
|
||||
threatArray.put(new JSONObject().put("url", domainName));
|
||||
}
|
||||
// Construct the JSON request body
|
||||
return new JSONObject()
|
||||
|
||||
@@ -26,7 +26,7 @@ import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.beam.spec11.SafeBrowsingTransforms.EvaluateSafeBrowsingFn;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.reporting.Spec11ThreatMatch;
|
||||
import google.registry.model.reporting.Spec11ThreatMatch.ThreatType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
@@ -113,7 +113,7 @@ public class Spec11Pipeline implements Serializable {
|
||||
Read<Object[], KV<String, String>> read =
|
||||
RegistryJpaIO.read(
|
||||
"select d.repoId, r.emailAddress from Domain d join Registrar r on"
|
||||
+ " d.currentSponsorClientId = r.clientIdentifier where r.type = 'REAL' and"
|
||||
+ " d.currentSponsorClientId = r.registrarId where r.type = 'REAL' and"
|
||||
+ " d.deletionTime > now()",
|
||||
false,
|
||||
Spec11Pipeline::parseRow);
|
||||
@@ -127,22 +127,21 @@ public class Spec11Pipeline implements Serializable {
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<String, String> input, OutputReceiver<DomainNameInfo> output) {
|
||||
DomainBase domainBase =
|
||||
Domain domain =
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.loadByKey(
|
||||
VKey.createSql(DomainBase.class, input.getKey())));
|
||||
.loadByKey(VKey.createSql(Domain.class, input.getKey())));
|
||||
String emailAddress = input.getValue();
|
||||
if (emailAddress == null) {
|
||||
emailAddress = "";
|
||||
}
|
||||
DomainNameInfo domainNameInfo =
|
||||
DomainNameInfo.create(
|
||||
domainBase.getDomainName(),
|
||||
domainBase.getRepoId(),
|
||||
domainBase.getCurrentSponsorRegistrarId(),
|
||||
domain.getDomainName(),
|
||||
domain.getRepoId(),
|
||||
domain.getCurrentSponsorRegistrarId(),
|
||||
emailAddress);
|
||||
output.output(domainNameInfo);
|
||||
}
|
||||
|
||||
@@ -25,23 +25,23 @@ import org.json.JSONObject;
|
||||
public abstract class ThreatMatch implements Serializable {
|
||||
|
||||
private static final String THREAT_TYPE_FIELD = "threatType";
|
||||
private static final String DOMAIN_NAME_FIELD = "fullyQualifiedDomainName";
|
||||
private static final String DOMAIN_NAME_FIELD = "domainName";
|
||||
|
||||
/** Returns what kind of threat it is (malware, phishing etc.) */
|
||||
public abstract String threatType();
|
||||
/** Returns the fully qualified domain name [SLD].[TLD] of the matched threat. */
|
||||
public abstract String fullyQualifiedDomainName();
|
||||
public abstract String domainName();
|
||||
|
||||
@VisibleForTesting
|
||||
static ThreatMatch create(String threatType, String fullyQualifiedDomainName) {
|
||||
return new AutoValue_ThreatMatch(threatType, fullyQualifiedDomainName);
|
||||
static ThreatMatch create(String threatType, String domainName) {
|
||||
return new AutoValue_ThreatMatch(threatType, domainName);
|
||||
}
|
||||
|
||||
/** Returns a {@link JSONObject} representing a subset of this object's data. */
|
||||
JSONObject toJSON() throws JSONException {
|
||||
return new JSONObject()
|
||||
.put(THREAT_TYPE_FIELD, threatType())
|
||||
.put(DOMAIN_NAME_FIELD, fullyQualifiedDomainName());
|
||||
.put(DOMAIN_NAME_FIELD, domainName());
|
||||
}
|
||||
|
||||
/** Parses a {@link JSONObject} and returns an equivalent {@link ThreatMatch}. */
|
||||
|
||||
@@ -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) {
|
||||
@@ -1427,6 +1457,11 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().caching.eppResourceMaxCachedEntries;
|
||||
}
|
||||
|
||||
/** Returns the amount of time that a particular claims list should be cached. */
|
||||
public static java.time.Duration getClaimsListCacheDuration() {
|
||||
return java.time.Duration.ofSeconds(CONFIG_SETTINGS.get().caching.claimsListCachingSeconds);
|
||||
}
|
||||
|
||||
/** Returns the email address that outgoing emails from the app are sent from. */
|
||||
public static InternetAddress getGSuiteOutgoingEmailAddress() {
|
||||
return parseEmailAddress(CONFIG_SETTINGS.get().gSuite.outgoingEmailAddress);
|
||||
@@ -1447,11 +1482,6 @@ public final class RegistryConfig {
|
||||
return CONFIG_SETTINGS.get().registryPolicy.defaultRegistrarWhoisServer;
|
||||
}
|
||||
|
||||
/** Returns the number of {@code EppResourceIndex} buckets to be used. */
|
||||
public static int getEppResourceIndexBucketCount() {
|
||||
return CONFIG_SETTINGS.get().datastore.eppResourceIndexBucketsNum;
|
||||
}
|
||||
|
||||
/** Returns the base retry duration that gets doubled after each failure within {@code Ofy}. */
|
||||
public static Duration getBaseOfyRetryDuration() {
|
||||
return Duration.millis(CONFIG_SETTINGS.get().datastore.baseOfyRetryMillis);
|
||||
|
||||
@@ -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 {
|
||||
@@ -107,7 +108,6 @@ public class RegistryConfigSettings {
|
||||
|
||||
/** Configuration for Cloud Datastore. */
|
||||
public static class Datastore {
|
||||
public int eppResourceIndexBucketsNum;
|
||||
public int baseOfyRetryMillis;
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@ public class RegistryConfigSettings {
|
||||
public boolean eppResourceCachingEnabled;
|
||||
public int eppResourceCachingSeconds;
|
||||
public int eppResourceMaxCachedEntries;
|
||||
public int claimsListCachingSeconds;
|
||||
}
|
||||
|
||||
/** Configuration for ICANN monthly reporting. */
|
||||
@@ -245,4 +246,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,10 +183,6 @@ registryPolicy:
|
||||
requireSslCertificates: true
|
||||
|
||||
datastore:
|
||||
# Number of EPP resource index buckets in Datastore. Don’t change after
|
||||
# initial install.
|
||||
eppResourceIndexBucketsNum: 997
|
||||
|
||||
# Milliseconds that Objectify waits to retry a Datastore transaction (this
|
||||
# doubles after each failure).
|
||||
baseOfyRetryMillis: 100
|
||||
@@ -294,6 +290,10 @@ caching:
|
||||
# have to be very large to achieve the vast majority of possible gains.
|
||||
eppResourceMaxCachedEntries: 500
|
||||
|
||||
# Length of time that a claims list will be cached after retrieval. A fairly
|
||||
# long duration is acceptable because claims lists don't change frequently.
|
||||
claimsListCachingSeconds: 21600 # six hours
|
||||
|
||||
oAuth:
|
||||
# OAuth scopes to detect on access tokens. Superset of requiredOauthScopes.
|
||||
availableOauthScopes:
|
||||
@@ -473,6 +473,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 {
|
||||
|
||||
@@ -20,8 +20,8 @@ import google.registry.dns.DnsConstants.TargetType;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.HttpException.BadRequestException;
|
||||
import google.registry.request.HttpException.NotFoundException;
|
||||
@@ -62,11 +62,11 @@ public final class RefreshDnsAction implements Runnable {
|
||||
}
|
||||
switch (type) {
|
||||
case DOMAIN:
|
||||
loadAndVerifyExistence(DomainBase.class, domainOrHostName);
|
||||
loadAndVerifyExistence(Domain.class, domainOrHostName);
|
||||
dnsQueue.addDomainRefreshTask(domainOrHostName);
|
||||
break;
|
||||
case HOST:
|
||||
verifyHostIsSubordinate(loadAndVerifyExistence(HostResource.class, domainOrHostName));
|
||||
verifyHostIsSubordinate(loadAndVerifyExistence(Host.class, domainOrHostName));
|
||||
dnsQueue.addHostRefreshTask(domainOrHostName);
|
||||
break;
|
||||
default:
|
||||
@@ -86,7 +86,7 @@ public final class RefreshDnsAction implements Runnable {
|
||||
domainOrHostName)));
|
||||
}
|
||||
|
||||
private static void verifyHostIsSubordinate(HostResource host) {
|
||||
private static void verifyHostIsSubordinate(Host host) {
|
||||
if (!host.isSubordinate()) {
|
||||
throw new BadRequestException(
|
||||
String.format("%s isn't a subordinate hostname", host.getHostName()));
|
||||
|
||||
@@ -36,9 +36,9 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.writer.BaseDnsWriter;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Registries;
|
||||
import google.registry.util.Clock;
|
||||
import google.registry.util.Concurrent;
|
||||
@@ -121,13 +121,12 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
String absoluteDomainName = getAbsoluteHostName(domainName);
|
||||
|
||||
// Load the target domain. Note that it can be absent if this domain was just deleted.
|
||||
Optional<DomainBase> domainBase =
|
||||
loadByForeignKey(DomainBase.class, domainName, clock.nowUtc());
|
||||
Optional<Domain> domain = loadByForeignKey(Domain.class, domainName, clock.nowUtc());
|
||||
|
||||
// Return early if no DNS records should be published.
|
||||
// desiredRecordsBuilder is populated with an empty set to indicate that all existing records
|
||||
// should be deleted.
|
||||
if (!domainBase.isPresent() || !domainBase.get().shouldPublishToDns()) {
|
||||
if (!domain.isPresent() || !domain.get().shouldPublishToDns()) {
|
||||
desiredRecords.put(absoluteDomainName, ImmutableSet.of());
|
||||
return;
|
||||
}
|
||||
@@ -135,10 +134,10 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
ImmutableSet.Builder<ResourceRecordSet> domainRecords = new ImmutableSet.Builder<>();
|
||||
|
||||
// Construct DS records (if any).
|
||||
Set<DelegationSignerData> dsData = domainBase.get().getDsData();
|
||||
Set<DomainDsData> dsData = domain.get().getDsData();
|
||||
if (!dsData.isEmpty()) {
|
||||
HashSet<String> dsRrData = new HashSet<>();
|
||||
for (DelegationSignerData ds : dsData) {
|
||||
for (DomainDsData ds : dsData) {
|
||||
dsRrData.add(ds.toRrData());
|
||||
}
|
||||
|
||||
@@ -154,8 +153,8 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
}
|
||||
|
||||
// Construct NS records (if any).
|
||||
Set<String> nameserverData = domainBase.get().loadNameserverHostNames();
|
||||
Set<String> subordinateHosts = domainBase.get().getSubordinateHosts();
|
||||
Set<String> nameserverData = domain.get().loadNameserverHostNames();
|
||||
Set<String> subordinateHosts = domain.get().getSubordinateHosts();
|
||||
if (!nameserverData.isEmpty()) {
|
||||
HashSet<String> nsRrData = new HashSet<>();
|
||||
for (String hostName : nameserverData) {
|
||||
@@ -191,7 +190,7 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
// Load the target host. Note that it can be absent if this host was just deleted.
|
||||
// desiredRecords is populated with an empty set to indicate that all existing records
|
||||
// should be deleted.
|
||||
Optional<HostResource> host = loadByForeignKey(HostResource.class, hostName, clock.nowUtc());
|
||||
Optional<Host> host = loadByForeignKey(Host.class, hostName, clock.nowUtc());
|
||||
|
||||
// Return early if the host is deleted.
|
||||
if (!host.isPresent()) {
|
||||
|
||||
@@ -27,9 +27,9 @@ import com.google.common.net.InternetDomainName;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.writer.BaseDnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Registries;
|
||||
import google.registry.util.Clock;
|
||||
import java.io.IOException;
|
||||
@@ -127,12 +127,11 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
* this domain refresh request
|
||||
*/
|
||||
private void publishDomain(String domainName, String requestingHostName) {
|
||||
Optional<DomainBase> domainOptional =
|
||||
loadByForeignKey(DomainBase.class, domainName, clock.nowUtc());
|
||||
Optional<Domain> domainOptional = loadByForeignKey(Domain.class, domainName, clock.nowUtc());
|
||||
update.delete(toAbsoluteName(domainName), Type.ANY);
|
||||
// If the domain is now deleted, then don't update DNS for it.
|
||||
if (domainOptional.isPresent()) {
|
||||
DomainBase domain = domainOptional.get();
|
||||
Domain domain = domainOptional.get();
|
||||
// As long as the domain exists, orphan glues should be cleaned.
|
||||
deleteSubordinateHostAddressSet(domain, requestingHostName, update);
|
||||
if (domain.shouldPublishToDns()) {
|
||||
@@ -184,9 +183,9 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private RRset makeDelegationSignerSet(DomainBase domain) {
|
||||
private RRset makeDelegationSignerSet(Domain domain) {
|
||||
RRset signerSet = new RRset();
|
||||
for (DelegationSignerData signerData : domain.getDsData()) {
|
||||
for (DomainDsData signerData : domain.getDsData()) {
|
||||
DSRecord dsRecord =
|
||||
new DSRecord(
|
||||
toAbsoluteName(domain.getDomainName()),
|
||||
@@ -202,7 +201,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
}
|
||||
|
||||
private void deleteSubordinateHostAddressSet(
|
||||
DomainBase domain, String additionalHost, Update update) {
|
||||
Domain domain, String additionalHost, Update update) {
|
||||
for (String hostName :
|
||||
union(
|
||||
domain.getSubordinateHosts(),
|
||||
@@ -213,17 +212,17 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private void addInBailiwickNameServerSet(DomainBase domain, Update update) {
|
||||
private void addInBailiwickNameServerSet(Domain domain, Update update) {
|
||||
for (String hostName :
|
||||
intersection(domain.loadNameserverHostNames(), domain.getSubordinateHosts())) {
|
||||
Optional<HostResource> host = loadByForeignKey(HostResource.class, hostName, clock.nowUtc());
|
||||
Optional<Host> host = loadByForeignKey(Host.class, hostName, clock.nowUtc());
|
||||
checkState(host.isPresent(), "Host %s cannot be loaded", hostName);
|
||||
update.add(makeAddressSet(host.get()));
|
||||
update.add(makeV6AddressSet(host.get()));
|
||||
}
|
||||
}
|
||||
|
||||
private RRset makeNameServerSet(DomainBase domain) {
|
||||
private RRset makeNameServerSet(Domain domain) {
|
||||
RRset nameServerSet = new RRset();
|
||||
for (String hostName : domain.loadNameserverHostNames()) {
|
||||
NSRecord record =
|
||||
@@ -237,7 +236,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
return nameServerSet;
|
||||
}
|
||||
|
||||
private RRset makeAddressSet(HostResource host) {
|
||||
private RRset makeAddressSet(Host host) {
|
||||
RRset addressSet = new RRset();
|
||||
for (InetAddress address : host.getInetAddresses()) {
|
||||
if (address instanceof Inet4Address) {
|
||||
@@ -253,7 +252,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
return addressSet;
|
||||
}
|
||||
|
||||
private RRset makeV6AddressSet(HostResource host) {
|
||||
private RRset makeV6AddressSet(Host host) {
|
||||
RRset addressSet = new RRset();
|
||||
for (InetAddress address : host.getInetAddresses()) {
|
||||
if (address instanceof Inet6Address) {
|
||||
|
||||
+16
-16
@@ -1,58 +1,58 @@
|
||||
<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"/>
|
||||
</datastore-index>
|
||||
<!-- For finding domain resources by registrar. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For finding domain resources by TLD. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="tld" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For finding domain resources by registrar. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For finding the most recently created domain resources. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="tld" direction="asc"/>
|
||||
<property name="creationTime" direction="desc"/>
|
||||
</datastore-index>
|
||||
<!-- For finding host resources by registrar. -->
|
||||
<datastore-index kind="HostResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Host" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="fullyQualifiedHostName" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For determining the active domains linked to a given contact. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="allContacts.contact" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For determining the active domains linked to a given host. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="nsHosts" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For deleting expired not-previously-deleted domains. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="autorenewEndTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For RDAP searches by linked nameserver. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="nsHosts" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For WHOIS IP address lookup -->
|
||||
<datastore-index kind="HostResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Host" ancestor="false" source="manual">
|
||||
<property name="inetAddresses" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
@@ -74,24 +74,24 @@
|
||||
<property name="modificationTime" direction="asc"/>
|
||||
</datastore-index>
|
||||
<!-- For RDAP. -->
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="fullyQualifiedDomainName" direction="asc"/>
|
||||
</datastore-index>
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="tld" direction="asc"/>
|
||||
<property name="fullyQualifiedDomainName" direction="asc"/>
|
||||
</datastore-index>
|
||||
<datastore-index kind="DomainBase" ancestor="false" source="manual">
|
||||
<datastore-index kind="Domain" ancestor="false" source="manual">
|
||||
<property name="tld" direction="asc"/>
|
||||
<property name="fullyQualifiedDomainName" direction="asc"/>
|
||||
</datastore-index>
|
||||
<datastore-index kind="HostResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Host" ancestor="false" source="manual">
|
||||
<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>
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<description>
|
||||
@@ -110,6 +111,7 @@
|
||||
<schedule>1st monday of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/updateRegistrarRdapBaseUrls]]></url>
|
||||
@@ -267,7 +269,7 @@
|
||||
about 2 hours to complete, so we give 11 hours to be safe. Normally, we give 24+ hours (see
|
||||
icannReportingStaging), but the invoicing team prefers receiving the e-mail on the first of
|
||||
each month. -->
|
||||
<schedule>1 of month 17:00</schedule>
|
||||
<schedule>1 of month 19:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
|
||||
<!-- TODO(b/249863289): disable until it is safe to run this pipeline
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/task/resaveAllEppResourcesPipeline?fast=true]]></url>
|
||||
<description>
|
||||
@@ -94,6 +95,7 @@
|
||||
<schedule>1st monday of month 09:00</schedule>
|
||||
<target>backend</target>
|
||||
</cron>
|
||||
-->
|
||||
|
||||
<cron>
|
||||
<url><![CDATA[/_dr/cron/fanout?queue=retryable-cron-tasks&endpoint=/_dr/task/exportDomainLists&runInEmpty]]></url>
|
||||
|
||||
@@ -91,10 +91,10 @@ public class ExportDomainListsAction implements Runnable {
|
||||
// field that compares with the substituted value.
|
||||
jpaTm()
|
||||
.query(
|
||||
"SELECT fullyQualifiedDomainName FROM Domain "
|
||||
"SELECT domainName FROM Domain "
|
||||
+ "WHERE tld = :tld "
|
||||
+ "AND deletionTime > :now "
|
||||
+ "ORDER by fullyQualifiedDomainName ASC",
|
||||
+ "ORDER by domainName ASC",
|
||||
String.class)
|
||||
.setParameter("tld", tld)
|
||||
.setParameter("now", clock.nowUtc())
|
||||
|
||||
@@ -114,7 +114,7 @@ class SyncRegistrarsSheet {
|
||||
// and you'll need to remove deleted columns probably like a week after
|
||||
// deployment.
|
||||
//
|
||||
builder.put("clientIdentifier", convert(registrar.getRegistrarId()));
|
||||
builder.put("registrarId", convert(registrar.getRegistrarId()));
|
||||
builder.put("registrarName", convert(registrar.getRegistrarName()));
|
||||
builder.put("state", convert(registrar.getState()));
|
||||
builder.put("ianaIdentifier", convert(registrar.getIanaIdentifier()));
|
||||
|
||||
@@ -44,8 +44,8 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException;
|
||||
import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.tld.label.ReservationType;
|
||||
import google.registry.monitoring.whitebox.CheckApiMetric;
|
||||
@@ -156,8 +156,7 @@ public class CheckApiAction implements Runnable {
|
||||
}
|
||||
|
||||
private boolean checkExists(String domainString, DateTime now) {
|
||||
return !ForeignKeyIndex.loadCached(DomainBase.class, ImmutableList.of(domainString), now)
|
||||
.isEmpty();
|
||||
return !ForeignKeyUtils.loadCached(Domain.class, ImmutableList.of(domainString), now).isEmpty();
|
||||
}
|
||||
|
||||
private Optional<String> checkReserved(InternetDomainName domainName) {
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.flows;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static google.registry.model.EppResourceUtils.isLinked;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
@@ -38,14 +37,14 @@ 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.ForeignKeyUtils;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.model.domain.Period;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.List;
|
||||
@@ -70,22 +69,17 @@ public final class ResourceFlowUtils {
|
||||
/**
|
||||
* Check whether if there are domains linked to the resource to be deleted. Throws an exception if
|
||||
* so.
|
||||
*
|
||||
* <p>Note that in datastore this is a smoke test as the query for linked domains is eventually
|
||||
* consistent, so we only check a few domains to fail fast.
|
||||
*/
|
||||
public static <R extends EppResource> void checkLinkedDomains(
|
||||
final String targetId, final DateTime now, final Class<R> resourceClass) throws EppException {
|
||||
EppException failfastException =
|
||||
tm().transact(
|
||||
() -> {
|
||||
final ForeignKeyIndex<R> fki = ForeignKeyIndex.load(resourceClass, targetId, now);
|
||||
if (fki == null) {
|
||||
VKey<R> key = ForeignKeyUtils.load(resourceClass, targetId, now);
|
||||
if (key == null) {
|
||||
return new ResourceDoesNotExistException(resourceClass, targetId);
|
||||
}
|
||||
return isLinked(fki.getResourceKey(), now)
|
||||
? new ResourceToDeleteIsReferencedException()
|
||||
: null;
|
||||
return isLinked(key, now) ? new ResourceToDeleteIsReferencedException() : null;
|
||||
});
|
||||
if (failfastException != null) {
|
||||
throw failfastException;
|
||||
@@ -118,7 +112,7 @@ public final class ResourceFlowUtils {
|
||||
|
||||
public static <R extends EppResource> void verifyResourceDoesNotExist(
|
||||
Class<R> clazz, String targetId, DateTime now, String registrarId) throws EppException {
|
||||
VKey<R> key = loadAndGetKey(clazz, targetId, now);
|
||||
VKey<R> key = ForeignKeyUtils.load(clazz, targetId, now);
|
||||
if (key != null) {
|
||||
R resource = tm().loadByKey(key);
|
||||
// These are similar exceptions, but we can track them internally as log-based metrics.
|
||||
@@ -139,7 +133,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);
|
||||
@@ -147,7 +141,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, DomainBase domain)
|
||||
public static void verifyOptionalAuthInfo(Optional<AuthInfo> authInfo, Domain domain)
|
||||
throws EppException {
|
||||
if (authInfo.isPresent()) {
|
||||
verifyAuthInfo(authInfo.get(), domain);
|
||||
@@ -155,7 +149,7 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
|
||||
/** Check that the given {@link AuthInfo} is valid for the given domain. */
|
||||
public static void verifyAuthInfo(AuthInfo authInfo, DomainBase domain) throws EppException {
|
||||
public static void verifyAuthInfo(AuthInfo authInfo, Domain domain) throws EppException {
|
||||
final String authRepoId = authInfo.getPw().getRepoId();
|
||||
String authPassword = authInfo.getPw().getValue();
|
||||
if (authRepoId == null) {
|
||||
@@ -167,7 +161,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 +173,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();
|
||||
@@ -236,7 +229,7 @@ public final class ResourceFlowUtils {
|
||||
* @param domain is the domain already projected at approvalTime
|
||||
*/
|
||||
public static DateTime computeExDateForApprovalTime(
|
||||
DomainContent domain, DateTime approvalTime, Period period) {
|
||||
DomainBase domain, DateTime approvalTime, Period period) {
|
||||
boolean inAutoRenew = domain.getGracePeriodStatuses().contains(GracePeriodStatus.AUTO_RENEW);
|
||||
// inAutoRenew is set to false if the period is zero because a zero-period transfer should not
|
||||
// subsume an autorenew.
|
||||
@@ -246,7 +239,7 @@ public final class ResourceFlowUtils {
|
||||
if (period.getValue() == 0) {
|
||||
inAutoRenew = false;
|
||||
}
|
||||
return DomainBase.extendRegistrationWithCap(
|
||||
return Domain.extendRegistrationWithCap(
|
||||
approvalTime,
|
||||
domain.getRegistrationExpirationTime(),
|
||||
period.getValue() - (inAutoRenew ? 1 : 0));
|
||||
|
||||
@@ -114,7 +114,7 @@ public class TlsCredentials implements TransportCredentials {
|
||||
"Authentication error: IP address %s is not allow-listed for registrar %s; allow list is:"
|
||||
+ " %s",
|
||||
clientInetAddr, registrar.getRegistrarId(), ipAddressAllowList);
|
||||
throw new BadRegistrarIpAddressException();
|
||||
throw new BadRegistrarIpAddressException(clientInetAddr);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -216,8 +216,13 @@ public class TlsCredentials implements TransportCredentials {
|
||||
|
||||
/** Registrar IP address is not in stored allow list. */
|
||||
public static class BadRegistrarIpAddressException extends AuthenticationErrorException {
|
||||
BadRegistrarIpAddressException() {
|
||||
super("Registrar IP address is not in stored allow list");
|
||||
BadRegistrarIpAddressException(Optional<InetAddress> clientInetAddr) {
|
||||
super(
|
||||
clientInetAddr.isPresent()
|
||||
? String.format(
|
||||
"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);
|
||||
|
||||
@@ -23,7 +23,6 @@ import static google.registry.model.IdService.allocateId;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
@@ -33,15 +32,13 @@ 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 +72,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)
|
||||
@@ -96,12 +93,7 @@ public final class ContactCreateFlow implements TransactionalFlow {
|
||||
.setType(HistoryEntry.Type.CONTACT_CREATE)
|
||||
.setXmlBytes(null) // We don't want to store contact details in the history entry.
|
||||
.setContact(newContact);
|
||||
tm().insertAll(
|
||||
ImmutableSet.of(
|
||||
newContact,
|
||||
historyBuilder.build(),
|
||||
ForeignKeyIndex.create(newContact, newContact.getDeletionTime()),
|
||||
EppResourceIndex.create(Key.create(newContact))));
|
||||
tm().insertAll(ImmutableSet.of(newContact, historyBuilder.build()));
|
||||
return responseBuilder
|
||||
.setResData(ContactCreateData.create(newContact.getContactId(), now))
|
||||
.build();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -22,7 +22,7 @@ import google.registry.flows.FlowMetadata;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.domain.DomainCreateFlow;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseData;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
@@ -125,10 +125,9 @@ public class DomainCreateFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
public abstract static class BeforeSaveParameters extends ImmutableObject {
|
||||
|
||||
/**
|
||||
* The new {@link DomainBase} entity that is going to be persisted at the end of the
|
||||
* transaction.
|
||||
* The new {@link Domain} entity that is going to be persisted at the end of the transaction.
|
||||
*/
|
||||
public abstract DomainBase newDomain();
|
||||
public abstract Domain newDomain();
|
||||
|
||||
/**
|
||||
* The new {@link HistoryEntry} entity for the domain's creation that is going to be persisted
|
||||
@@ -162,7 +161,7 @@ public class DomainCreateFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setNewDomain(DomainBase newDomain);
|
||||
public abstract Builder setNewDomain(Domain newDomain);
|
||||
|
||||
public abstract Builder setHistoryEntry(HistoryEntry historyEntry);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import google.registry.flows.FlowMetadata;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.domain.DomainDeleteFlow;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.model.eppoutput.Result;
|
||||
@@ -83,7 +83,7 @@ public class DomainDeleteFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class AfterValidationParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase existingDomain();
|
||||
public abstract Domain existingDomain();
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new AutoValue_DomainDeleteFlowCustomLogic_AfterValidationParameters.Builder();
|
||||
@@ -93,7 +93,7 @@ public class DomainDeleteFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setExistingDomain(DomainBase existingDomain);
|
||||
public abstract Builder setExistingDomain(Domain existingDomain);
|
||||
|
||||
public abstract AfterValidationParameters build();
|
||||
}
|
||||
@@ -109,9 +109,9 @@ public class DomainDeleteFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class BeforeSaveParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase existingDomain();
|
||||
public abstract Domain existingDomain();
|
||||
|
||||
public abstract DomainBase newDomain();
|
||||
public abstract Domain newDomain();
|
||||
|
||||
public abstract HistoryEntry historyEntry();
|
||||
|
||||
@@ -125,9 +125,9 @@ public class DomainDeleteFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setExistingDomain(DomainBase existingDomain);
|
||||
public abstract Builder setExistingDomain(Domain existingDomain);
|
||||
|
||||
public abstract Builder setNewDomain(DomainBase newDomain);
|
||||
public abstract Builder setNewDomain(Domain newDomain);
|
||||
|
||||
public abstract Builder setHistoryEntry(HistoryEntry historyEntry);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import google.registry.flows.FlowMetadata;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.domain.DomainInfoFlow;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainInfoData;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
@@ -53,8 +53,8 @@ public class DomainInfoFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
/**
|
||||
* A hook that runs before the response is returned.
|
||||
*
|
||||
* <p>This takes the {@link DomainBase} and {@link ResponseExtension}s as input and returns
|
||||
* them, potentially with modifications.
|
||||
* <p>This takes the {@link Domain} and {@link ResponseExtension}s as input and returns them,
|
||||
* potentially with modifications.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public BeforeResponseReturnData beforeResponse(BeforeResponseParameters parameters)
|
||||
@@ -69,7 +69,7 @@ public class DomainInfoFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class AfterValidationParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase domain();
|
||||
public abstract Domain domain();
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new AutoValue_DomainInfoFlowCustomLogic_AfterValidationParameters.Builder();
|
||||
@@ -79,7 +79,7 @@ public class DomainInfoFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setDomain(DomainBase domain);
|
||||
public abstract Builder setDomain(Domain domain);
|
||||
|
||||
public abstract AfterValidationParameters build();
|
||||
}
|
||||
@@ -89,7 +89,7 @@ public class DomainInfoFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class BeforeResponseParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase domain();
|
||||
public abstract Domain domain();
|
||||
|
||||
public abstract DomainInfoData resData();
|
||||
|
||||
@@ -103,7 +103,7 @@ public class DomainInfoFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setDomain(DomainBase domain);
|
||||
public abstract Builder setDomain(Domain domain);
|
||||
|
||||
public abstract Builder setResData(DomainInfoData resData);
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import google.registry.flows.FlowMetadata;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.domain.DomainRenewFlow;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseData;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
@@ -68,8 +68,8 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
/**
|
||||
* A hook that runs before the response is returned.
|
||||
*
|
||||
* <p>This takes the {@link DomainBase} and {@link ResponseExtension}s as input and returns
|
||||
* them, potentially with modifications.
|
||||
* <p>This takes the {@link Domain} and {@link ResponseExtension}s as input and returns them,
|
||||
* potentially with modifications.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public BeforeResponseReturnData beforeResponse(BeforeResponseParameters parameters)
|
||||
@@ -84,7 +84,7 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class AfterValidationParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase existingDomain();
|
||||
public abstract Domain existingDomain();
|
||||
|
||||
public abstract int years();
|
||||
|
||||
@@ -98,7 +98,7 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setExistingDomain(DomainBase existingDomain);
|
||||
public abstract Builder setExistingDomain(Domain existingDomain);
|
||||
|
||||
public abstract Builder setYears(int years);
|
||||
|
||||
@@ -118,9 +118,9 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class BeforeSaveParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase existingDomain();
|
||||
public abstract Domain existingDomain();
|
||||
|
||||
public abstract DomainBase newDomain();
|
||||
public abstract Domain newDomain();
|
||||
|
||||
public abstract HistoryEntry historyEntry();
|
||||
|
||||
@@ -138,9 +138,9 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setExistingDomain(DomainBase existingDomain);
|
||||
public abstract Builder setExistingDomain(Domain existingDomain);
|
||||
|
||||
public abstract Builder setNewDomain(DomainBase newDomain);
|
||||
public abstract Builder setNewDomain(Domain newDomain);
|
||||
|
||||
public abstract Builder setHistoryEntry(HistoryEntry historyEntry);
|
||||
|
||||
@@ -158,7 +158,7 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class BeforeResponseParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase domain();
|
||||
public abstract Domain domain();
|
||||
|
||||
public abstract ResponseData resData();
|
||||
|
||||
@@ -172,7 +172,7 @@ public class DomainRenewFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract BeforeResponseParameters.Builder setDomain(DomainBase domain);
|
||||
public abstract BeforeResponseParameters.Builder setDomain(Domain domain);
|
||||
|
||||
public abstract BeforeResponseParameters.Builder setResData(ResponseData resData);
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import google.registry.flows.FlowMetadata;
|
||||
import google.registry.flows.SessionMetadata;
|
||||
import google.registry.flows.domain.DomainUpdateFlow;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppinput.EppInput;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
|
||||
@@ -65,7 +65,7 @@ public class DomainUpdateFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class AfterValidationParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase existingDomain();
|
||||
public abstract Domain existingDomain();
|
||||
|
||||
public static Builder newBuilder() {
|
||||
return new AutoValue_DomainUpdateFlowCustomLogic_AfterValidationParameters.Builder();
|
||||
@@ -75,7 +75,7 @@ public class DomainUpdateFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setExistingDomain(DomainBase existingDomain);
|
||||
public abstract Builder setExistingDomain(Domain existingDomain);
|
||||
|
||||
public abstract AfterValidationParameters build();
|
||||
}
|
||||
@@ -91,9 +91,9 @@ public class DomainUpdateFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue
|
||||
public abstract static class BeforeSaveParameters extends ImmutableObject {
|
||||
|
||||
public abstract DomainBase existingDomain();
|
||||
public abstract Domain existingDomain();
|
||||
|
||||
public abstract DomainBase newDomain();
|
||||
public abstract Domain newDomain();
|
||||
|
||||
public abstract HistoryEntry historyEntry();
|
||||
|
||||
@@ -107,9 +107,9 @@ public class DomainUpdateFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
|
||||
public abstract Builder setExistingDomain(DomainBase existingDomain);
|
||||
public abstract Builder setExistingDomain(Domain existingDomain);
|
||||
|
||||
public abstract Builder setNewDomain(DomainBase newDomain);
|
||||
public abstract Builder setNewDomain(Domain newDomain);
|
||||
|
||||
public abstract Builder setHistoryEntry(HistoryEntry historyEntry);
|
||||
|
||||
|
||||
@@ -53,8 +53,9 @@ import google.registry.flows.custom.DomainCheckFlowCustomLogic.BeforeResponseRet
|
||||
import google.registry.flows.domain.token.AllocationTokenDomainCheckResults;
|
||||
import google.registry.flows.domain.token.AllocationTokenFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Check;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtension;
|
||||
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
|
||||
@@ -70,7 +71,6 @@ import google.registry.model.eppoutput.CheckData.DomainCheck;
|
||||
import google.registry.model.eppoutput.CheckData.DomainCheckData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.eppoutput.EppResponse.ResponseExtension;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.tld.Registry.TldState;
|
||||
@@ -169,8 +169,8 @@ public final class DomainCheckFlow implements Flow {
|
||||
// TODO: Use as of date from fee extension v0.12 instead of now, if specified.
|
||||
.setAsOfDate(now)
|
||||
.build());
|
||||
ImmutableMap<String, ForeignKeyIndex<DomainBase>> existingDomains =
|
||||
ForeignKeyIndex.load(DomainBase.class, domainNames, now);
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains =
|
||||
ForeignKeyUtils.load(Domain.class, domainNames, now);
|
||||
Optional<AllocationTokenExtension> allocationTokenExtension =
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class);
|
||||
Optional<AllocationTokenDomainCheckResults> tokenDomainCheckResults =
|
||||
@@ -227,7 +227,7 @@ public final class DomainCheckFlow implements Flow {
|
||||
|
||||
private Optional<String> getMessageForCheck(
|
||||
InternetDomainName domainName,
|
||||
ImmutableMap<String, ForeignKeyIndex<DomainBase>> existingDomains,
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains,
|
||||
ImmutableMap<InternetDomainName, String> tokenCheckResults,
|
||||
ImmutableMap<String, TldState> tldStates,
|
||||
Optional<AllocationToken> allocationToken) {
|
||||
@@ -251,7 +251,7 @@ public final class DomainCheckFlow implements Flow {
|
||||
/** Handle the fee check extension. */
|
||||
private ImmutableList<? extends ResponseExtension> getResponseExtensions(
|
||||
ImmutableMap<String, InternetDomainName> domainNames,
|
||||
ImmutableMap<String, ForeignKeyIndex<DomainBase>> existingDomains,
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains,
|
||||
ImmutableSet<String> availableDomains,
|
||||
DateTime now,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
@@ -264,7 +264,7 @@ public final class DomainCheckFlow implements Flow {
|
||||
FeeCheckCommandExtension<?, ?> feeCheck = feeCheckOpt.get();
|
||||
ImmutableList.Builder<FeeCheckResponseExtensionItem> responseItems =
|
||||
new ImmutableList.Builder<>();
|
||||
ImmutableMap<String, DomainBase> domainObjs =
|
||||
ImmutableMap<String, Domain> domainObjs =
|
||||
loadDomainsForRestoreChecks(feeCheck, domainNames, existingDomains);
|
||||
ImmutableMap<String, BillingEvent.Recurring> recurrences =
|
||||
loadRecurrencesForDomains(domainObjs);
|
||||
@@ -272,12 +272,12 @@ public final class DomainCheckFlow implements Flow {
|
||||
for (FeeCheckCommandExtensionItem feeCheckItem : feeCheck.getItems()) {
|
||||
for (String domainName : getDomainNamesToCheckForFee(feeCheckItem, domainNames.keySet())) {
|
||||
FeeCheckResponseExtensionItem.Builder<?> builder = feeCheckItem.createResponseBuilder();
|
||||
Optional<DomainBase> domainBase = Optional.ofNullable(domainObjs.get(domainName));
|
||||
Optional<Domain> domain = Optional.ofNullable(domainObjs.get(domainName));
|
||||
handleFeeRequest(
|
||||
feeCheckItem,
|
||||
builder,
|
||||
domainNames.get(domainName),
|
||||
domainBase,
|
||||
domain,
|
||||
feeCheck.getCurrency(),
|
||||
now,
|
||||
pricingLogic,
|
||||
@@ -297,14 +297,14 @@ public final class DomainCheckFlow implements Flow {
|
||||
* renewal is part of the cost of a restore.
|
||||
*
|
||||
* <p>This may be resource-intensive for large checks of many restore fees, but those are
|
||||
* comparatively rare, and we are at least using an in-memory cache. Also this will get a lot
|
||||
* comparatively rare, and we are at least using an in-memory cache. Also, this will get a lot
|
||||
* nicer in Cloud SQL when we can SELECT just the fields we want rather than having to load the
|
||||
* entire entity.
|
||||
*/
|
||||
private ImmutableMap<String, DomainBase> loadDomainsForRestoreChecks(
|
||||
private ImmutableMap<String, Domain> loadDomainsForRestoreChecks(
|
||||
FeeCheckCommandExtension<?, ?> feeCheck,
|
||||
ImmutableMap<String, InternetDomainName> domainNames,
|
||||
ImmutableMap<String, ForeignKeyIndex<DomainBase>> existingDomains) {
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains) {
|
||||
ImmutableList<String> restoreCheckDomains;
|
||||
if (feeCheck instanceof FeeCheckCommandExtensionV06) {
|
||||
// The V06 fee extension supports specifying the command fees to check on a per-domain basis.
|
||||
@@ -326,25 +326,25 @@ public final class DomainCheckFlow implements Flow {
|
||||
}
|
||||
|
||||
// Filter down to just domains we know exist and then use the EppResource cache to load them.
|
||||
ImmutableMap<String, VKey<DomainBase>> existingDomainsToLoad =
|
||||
ImmutableMap<String, VKey<Domain>> existingDomainsToLoad =
|
||||
restoreCheckDomains.stream()
|
||||
.filter(existingDomains::containsKey)
|
||||
.collect(toImmutableMap(d -> d, d -> existingDomains.get(d).getResourceKey()));
|
||||
.collect(toImmutableMap(d -> d, existingDomains::get));
|
||||
ImmutableMap<VKey<? extends EppResource>, EppResource> loadedDomains =
|
||||
EppResource.loadCached(ImmutableList.copyOf(existingDomainsToLoad.values()));
|
||||
return ImmutableMap.copyOf(
|
||||
Maps.transformEntries(existingDomainsToLoad, (k, v) -> (DomainBase) loadedDomains.get(v)));
|
||||
Maps.transformEntries(existingDomainsToLoad, (k, v) -> (Domain) loadedDomains.get(v)));
|
||||
}
|
||||
|
||||
private ImmutableMap<String, BillingEvent.Recurring> loadRecurrencesForDomains(
|
||||
ImmutableMap<String, DomainBase> domainObjs) {
|
||||
ImmutableMap<String, Domain> domainObjs) {
|
||||
return tm().transact(
|
||||
() -> {
|
||||
ImmutableMap<VKey<? extends BillingEvent.Recurring>, BillingEvent.Recurring>
|
||||
recurrences =
|
||||
tm().loadByKeys(
|
||||
domainObjs.values().stream()
|
||||
.map(DomainBase::getAutorenewBillingEvent)
|
||||
.map(Domain::getAutorenewBillingEvent)
|
||||
.collect(toImmutableSet()));
|
||||
return ImmutableMap.copyOf(
|
||||
Maps.transformValues(
|
||||
|
||||
@@ -59,7 +59,6 @@ import com.google.auto.value.AutoValue;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.dns.DnsQueue;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.CommandUseErrorException;
|
||||
@@ -83,7 +82,7 @@ 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.billing.BillingEvent.RenewalPriceBehavior;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.DomainCommand.Create;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
@@ -105,8 +104,6 @@ import google.registry.model.eppinput.EppInput;
|
||||
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 +150,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}
|
||||
@@ -247,9 +246,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
verifyUnitIsYears(period);
|
||||
int years = period.getValue();
|
||||
validateRegistrationPeriod(years);
|
||||
verifyResourceDoesNotExist(DomainBase.class, targetId, now, registrarId);
|
||||
verifyResourceDoesNotExist(Domain.class, targetId, now, registrarId);
|
||||
// Validate that this is actually a legal domain name on a TLD that the registrar has access to.
|
||||
InternetDomainName domainName = validateDomainName(command.getFullyQualifiedDomainName());
|
||||
InternetDomainName domainName = validateDomainName(command.getDomainName());
|
||||
String domainLabel = domainName.parts().get(0);
|
||||
Registry registry = Registry.get(domainName.parent().toString());
|
||||
validateCreateCommandContactsAndNameservers(command, registry, domainName);
|
||||
@@ -333,9 +332,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
validateSecDnsExtension(eppInput.getSingleExtension(SecDnsCreateExtension.class));
|
||||
DateTime registrationExpirationTime = leapSafeAddYears(now, years);
|
||||
String repoId = createDomainRepoId(allocateId(), registry.getTldStr());
|
||||
Key<DomainHistory> domainHistoryKey =
|
||||
Key.create(Key.create(DomainBase.class, repoId), DomainHistory.class, allocateId());
|
||||
historyBuilder.setId(domainHistoryKey.getId());
|
||||
long historyRevisionId = allocateId();
|
||||
DomainHistoryId domainHistoryId = new DomainHistoryId(repoId, historyRevisionId);
|
||||
historyBuilder.setId(historyRevisionId);
|
||||
// Bill for the create.
|
||||
BillingEvent.OneTime createBillingEvent =
|
||||
createOneTimeBillingEvent(
|
||||
@@ -345,17 +344,17 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
isReserved(domainName, isSunriseCreate),
|
||||
years,
|
||||
feesAndCredits,
|
||||
domainHistoryKey,
|
||||
domainHistoryId,
|
||||
allocationToken,
|
||||
now);
|
||||
// Create a new autorenew billing event and poll message starting at the expiration time.
|
||||
BillingEvent.Recurring autorenewBillingEvent =
|
||||
createAutorenewBillingEvent(
|
||||
domainHistoryKey,
|
||||
domainHistoryId,
|
||||
registrationExpirationTime,
|
||||
getRenewalPriceInfo(isAnchorTenant, allocationToken, feesAndCredits));
|
||||
PollMessage.Autorenew autorenewPollMessage =
|
||||
createAutorenewPollMessage(domainHistoryKey, registrationExpirationTime);
|
||||
createAutorenewPollMessage(domainHistoryId, registrationExpirationTime);
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
entitiesToSave.add(createBillingEvent, autorenewBillingEvent, autorenewPollMessage);
|
||||
// Bill for EAP cost, if any.
|
||||
@@ -368,16 +367,15 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
reservationTypes.contains(NAME_COLLISION)
|
||||
? ImmutableSet.of(SERVER_HOLD)
|
||||
: ImmutableSet.of();
|
||||
DomainBase domain =
|
||||
new DomainBase.Builder()
|
||||
Domain domain =
|
||||
new Domain.Builder()
|
||||
.setCreationRegistrarId(registrarId)
|
||||
.setPersistedCurrentSponsorRegistrarId(registrarId)
|
||||
.setRepoId(repoId)
|
||||
.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,17 +388,21 @@ 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)) {
|
||||
entitiesToSave.add(
|
||||
createNameCollisionOneTimePollMessage(targetId, domainHistory, registrarId, now));
|
||||
}
|
||||
entitiesToSave.add(
|
||||
domain,
|
||||
domainHistory,
|
||||
ForeignKeyIndex.create(domain, domain.getDeletionTime()),
|
||||
EppResourceIndex.create(Key.create(domain)));
|
||||
entitiesToSave.add(domain, domainHistory);
|
||||
if (allocationToken.isPresent()
|
||||
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
|
||||
entitiesToSave.add(
|
||||
@@ -481,7 +483,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 +499,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) {
|
||||
@@ -530,7 +540,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(
|
||||
DomainBase domain, Registry registry, DateTime now, Period period, Duration addGracePeriod) {
|
||||
Domain domain, Registry registry, DateTime now, Period period, Duration addGracePeriod) {
|
||||
// We ignore prober transactions
|
||||
if (registry.getTldType() == TldType.REAL) {
|
||||
historyBuilder
|
||||
@@ -552,7 +562,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
boolean isReserved,
|
||||
int years,
|
||||
FeesAndCredits feesAndCredits,
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
DomainHistoryId domainHistoryId,
|
||||
Optional<AllocationToken> allocationToken,
|
||||
DateTime now) {
|
||||
ImmutableSet.Builder<Flag> flagsBuilder = new ImmutableSet.Builder<>();
|
||||
@@ -581,12 +591,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
? registry.getAnchorTenantAddGracePeriodLength()
|
||||
: registry.getAddGracePeriodLength()))
|
||||
.setFlags(flagsBuilder.build())
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(domainHistoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
private Recurring createAutorenewBillingEvent(
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
DomainHistoryId domainHistoryId,
|
||||
DateTime registrationExpirationTime,
|
||||
RenewalPriceInfo renewalpriceInfo) {
|
||||
return new BillingEvent.Recurring.Builder()
|
||||
@@ -596,21 +606,20 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setRegistrarId(registrarId)
|
||||
.setEventTime(registrationExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(domainHistoryId)
|
||||
.setRenewalPriceBehavior(renewalpriceInfo.renewalPriceBehavior())
|
||||
.setRenewalPrice(renewalpriceInfo.renewalPrice())
|
||||
.build();
|
||||
}
|
||||
|
||||
private Autorenew createAutorenewPollMessage(
|
||||
Key<DomainHistory> domainHistoryKey, DateTime registrationExpirationTime) {
|
||||
DomainHistoryId domainHistoryId, DateTime registrationExpirationTime) {
|
||||
return new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(targetId)
|
||||
.setRegistrarId(registrarId)
|
||||
.setEventTime(registrationExpirationTime)
|
||||
.setMsg("Domain was auto-renewed.")
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.setDomainHistoryId(domainHistoryId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -625,15 +634,12 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setEventTime(createBillingEvent.getEventTime())
|
||||
.setBillingTime(createBillingEvent.getBillingTime())
|
||||
.setFlags(createBillingEvent.getFlags())
|
||||
.setParent(createBillingEvent.getParentKey())
|
||||
.setDomainHistoryId(createBillingEvent.getDomainHistoryId())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static PollMessage.OneTime createNameCollisionOneTimePollMessage(
|
||||
String fullyQualifiedDomainName,
|
||||
HistoryEntry historyEntry,
|
||||
String registrarId,
|
||||
DateTime now) {
|
||||
String domainName, HistoryEntry historyEntry, String registrarId, DateTime now) {
|
||||
return new PollMessage.OneTime.Builder()
|
||||
.setRegistrarId(registrarId)
|
||||
.setEventTime(now)
|
||||
@@ -641,18 +647,17 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
DomainPendingActionNotificationResponse.create(
|
||||
fullyQualifiedDomainName, true, historyEntry.getTrid(), now)))
|
||||
domainName, true, historyEntry.getTrid(), now)))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build();
|
||||
}
|
||||
|
||||
private void enqueueTasks(
|
||||
DomainBase newDomain, boolean hasSignedMarks, boolean hasClaimsNotice) {
|
||||
private void enqueueTasks(Domain newDomain, boolean hasSignedMarks, boolean hasClaimsNotice) {
|
||||
if (newDomain.shouldPublishToDns()) {
|
||||
dnsQueue.addDomainRefreshTask(newDomain.getDomainName());
|
||||
}
|
||||
if (hasClaimsNotice || hasSignedMarks) {
|
||||
LordnTaskUtils.enqueueDomainBaseTask(newDomain);
|
||||
LordnTaskUtils.enqueueDomainTask(newDomain);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,6 +731,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) {
|
||||
@@ -735,4 +751,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;
|
||||
@@ -64,7 +63,8 @@ import google.registry.flows.custom.DomainDeleteFlowCustomLogic.BeforeSaveParame
|
||||
import google.registry.flows.custom.EntityChanges;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
@@ -146,13 +146,13 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
// Loads the target resource if it exists
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
Registry registry = Registry.get(existingDomain.getTld());
|
||||
verifyDeleteAllowed(existingDomain, registry, now);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build());
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
DomainBase.Builder builder;
|
||||
Domain.Builder builder;
|
||||
if (existingDomain.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
|
||||
builder =
|
||||
denyPendingTransfer(existingDomain, TransferStatus.SERVER_CANCELLED, now, registrarId)
|
||||
@@ -233,7 +233,12 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
// No cancellation is written if the grace period was not for a billable event.
|
||||
if (gracePeriod.hasBillingEvent()) {
|
||||
entitiesToSave.add(
|
||||
BillingEvent.Cancellation.forGracePeriod(gracePeriod, now, domainHistoryKey, targetId));
|
||||
BillingEvent.Cancellation.forGracePeriod(
|
||||
gracePeriod,
|
||||
now,
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()),
|
||||
targetId));
|
||||
if (gracePeriod.getOneTimeBillingEvent() != null) {
|
||||
// Take the amount of amount of registration time being refunded off the expiration time.
|
||||
// This can be either add grace periods or renew grace periods.
|
||||
@@ -248,15 +253,16 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
}
|
||||
builder.setRegistrationExpirationTime(newExpirationTime);
|
||||
|
||||
DomainBase newDomain = builder.build();
|
||||
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, 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.
|
||||
@@ -289,7 +295,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private void verifyDeleteAllowed(DomainBase existingDomain, Registry registry, DateTime now)
|
||||
private void verifyDeleteAllowed(Domain existingDomain, Registry registry, DateTime now)
|
||||
throws EppException {
|
||||
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
@@ -304,7 +310,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(
|
||||
DomainBase domain,
|
||||
Domain domain,
|
||||
Registry registry,
|
||||
DateTime now,
|
||||
Duration durationUntilDelete,
|
||||
@@ -337,7 +343,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private PollMessage.OneTime createDeletePollMessage(
|
||||
DomainBase existingDomain, Key<DomainHistory> domainHistoryKey, DateTime deletionTime) {
|
||||
Domain existingDomain, Key<DomainHistory> domainHistoryKey, DateTime deletionTime) {
|
||||
Optional<MetadataExtension> metadataExtension =
|
||||
eppInput.getSingleExtension(MetadataExtension.class);
|
||||
boolean hasMetadataMessage =
|
||||
@@ -362,7 +368,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private PollMessage.OneTime createImmediateDeletePollMessage(
|
||||
DomainBase existingDomain,
|
||||
Domain existingDomain,
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
DateTime now,
|
||||
DateTime deletionTime) {
|
||||
@@ -384,7 +390,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
|
||||
@Nullable
|
||||
private ImmutableList<FeeTransformResponseExtension> getResponseExtensions(
|
||||
BillingEvent.Recurring recurringBillingEvent, DomainBase existingDomain, DateTime now) {
|
||||
BillingEvent.Recurring recurringBillingEvent, Domain existingDomain, DateTime now) {
|
||||
FeeTransformResponseExtension.Builder feeResponseBuilder = getDeleteResponseBuilder();
|
||||
if (feeResponseBuilder == null) {
|
||||
return ImmutableList.of();
|
||||
|
||||
@@ -25,7 +25,7 @@ import static com.google.common.collect.Iterables.any;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static google.registry.model.domain.DomainBase.MAX_REGISTRATION_YEARS;
|
||||
import static google.registry.model.domain.Domain.MAX_REGISTRATION_YEARS;
|
||||
import static google.registry.model.tld.Registries.findTldForName;
|
||||
import static google.registry.model.tld.Registries.getTlds;
|
||||
import static google.registry.model.tld.Registry.TldState.GENERAL_AVAILABILITY;
|
||||
@@ -79,10 +79,10 @@ 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.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Create;
|
||||
import google.registry.model.domain.DomainCommand.CreateOrUpdate;
|
||||
import google.registry.model.domain.DomainCommand.InvalidReferencesException;
|
||||
@@ -105,16 +105,17 @@ import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.domain.launch.LaunchNotice.InvalidChecksumException;
|
||||
import google.registry.model.domain.launch.LaunchPhase;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import google.registry.model.domain.secdns.SecDnsInfoExtension;
|
||||
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.HostResource;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.Registrar.State;
|
||||
@@ -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();
|
||||
}
|
||||
@@ -309,14 +316,14 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
/** Check that the DS data that will be set on a domain is valid. */
|
||||
static void validateDsData(Set<DelegationSignerData> dsData) throws EppException {
|
||||
static void validateDsData(Set<DomainDsData> dsData) throws EppException {
|
||||
if (dsData != null) {
|
||||
if (dsData.size() > MAX_DS_RECORDS_PER_DOMAIN) {
|
||||
throw new TooManyDsRecordsException(
|
||||
String.format(
|
||||
"A maximum of %s DS records are allowed per domain.", MAX_DS_RECORDS_PER_DOMAIN));
|
||||
}
|
||||
ImmutableList<DelegationSignerData> invalidAlgorithms =
|
||||
ImmutableList<DomainDsData> invalidAlgorithms =
|
||||
dsData.stream()
|
||||
.filter(ds -> !validateAlgorithm(ds.getAlgorithm()))
|
||||
.collect(toImmutableList());
|
||||
@@ -326,7 +333,7 @@ public class DomainFlowUtils {
|
||||
"Domain contains DS record(s) with an invalid algorithm wire value: %s",
|
||||
invalidAlgorithms));
|
||||
}
|
||||
ImmutableList<DelegationSignerData> invalidDigestTypes =
|
||||
ImmutableList<DomainDsData> invalidDigestTypes =
|
||||
dsData.stream()
|
||||
.filter(ds -> !DigestType.fromWireValue(ds.getDigestType()).isPresent())
|
||||
.collect(toImmutableList());
|
||||
@@ -336,7 +343,7 @@ public class DomainFlowUtils {
|
||||
"Domain contains DS record(s) with an invalid digest type: %s",
|
||||
invalidDigestTypes));
|
||||
}
|
||||
ImmutableList<DelegationSignerData> digestsWithInvalidDigestLength =
|
||||
ImmutableList<DomainDsData> digestsWithInvalidDigestLength =
|
||||
dsData.stream()
|
||||
.filter(
|
||||
ds ->
|
||||
@@ -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<HostResource>> 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();
|
||||
@@ -553,7 +556,7 @@ public class DomainFlowUtils {
|
||||
* Fills in a builder with the data needed for an autorenew billing event for this domain. This
|
||||
* does not copy over the id of the current autorenew billing event.
|
||||
*/
|
||||
public static BillingEvent.Recurring.Builder newAutorenewBillingEvent(DomainBase domain) {
|
||||
public static BillingEvent.Recurring.Builder newAutorenewBillingEvent(Domain domain) {
|
||||
return new BillingEvent.Recurring.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
@@ -566,7 +569,7 @@ public class DomainFlowUtils {
|
||||
* Fills in a builder with the data needed for an autorenew poll message for this domain. This
|
||||
* does not copy over the id of the current autorenew poll message.
|
||||
*/
|
||||
public static PollMessage.Autorenew.Builder newAutorenewPollMessage(DomainBase domain) {
|
||||
public static PollMessage.Autorenew.Builder newAutorenewPollMessage(Domain domain) {
|
||||
return new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(domain.getDomainName())
|
||||
.setRegistrarId(domain.getCurrentSponsorRegistrarId())
|
||||
@@ -583,7 +586,11 @@ public class DomainFlowUtils {
|
||||
*
|
||||
* <p>Returns the new autorenew recurring billing event.
|
||||
*/
|
||||
public static Recurring updateAutorenewRecurrenceEndTime(DomainBase domain, DateTime newEndTime) {
|
||||
public static Recurring updateAutorenewRecurrenceEndTime(
|
||||
Domain domain,
|
||||
Recurring existingRecurring,
|
||||
DateTime newEndTime,
|
||||
@Nullable DomainHistoryId historyId) {
|
||||
Optional<PollMessage.Autorenew> autorenewPollMessage =
|
||||
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
|
||||
|
||||
@@ -591,33 +598,34 @@ 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 {
|
||||
tm().put(updatedAutorenewPollMessage);
|
||||
}
|
||||
|
||||
Recurring recurring =
|
||||
tm().loadByKey(domain.getAutorenewBillingEvent())
|
||||
.asBuilder()
|
||||
.setRecurrenceEndTime(newEndTime)
|
||||
.build();
|
||||
tm().put(recurring);
|
||||
return recurring;
|
||||
Recurring newRecurring = existingRecurring.asBuilder().setRecurrenceEndTime(newEndTime).build();
|
||||
tm().put(newRecurring);
|
||||
return newRecurring;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -628,7 +636,7 @@ public class DomainFlowUtils {
|
||||
FeeQueryCommandExtensionItem feeRequest,
|
||||
FeeQueryResponseExtensionItem.Builder<?, ?> builder,
|
||||
InternetDomainName domainName,
|
||||
Optional<DomainBase> domain,
|
||||
Optional<Domain> domain,
|
||||
@Nullable CurrencyUnit topLevelCurrency,
|
||||
DateTime currentDate,
|
||||
DomainPricingLogic pricingLogic,
|
||||
@@ -708,7 +716,10 @@ public class DomainFlowUtils {
|
||||
throw new TransfersAreAlwaysForOneYearException();
|
||||
}
|
||||
builder.setAvailIfSupported(true);
|
||||
fees = pricingLogic.getTransferPrice(registry, domainNameString, now).getFees();
|
||||
fees =
|
||||
pricingLogic
|
||||
.getTransferPrice(registry, domainNameString, now, recurringBillingEvent)
|
||||
.getFees();
|
||||
break;
|
||||
case UPDATE:
|
||||
builder.setAvailIfSupported(true);
|
||||
@@ -767,7 +778,7 @@ public class DomainFlowUtils {
|
||||
final Optional<? extends FeeTransformCommandExtension> feeCommand,
|
||||
FeesAndCredits feesAndCredits)
|
||||
throws EppException {
|
||||
if (isDomainPremium(domainName, priceTime) && !feeCommand.isPresent()) {
|
||||
if (feesAndCredits.hasAnyPremiumFees() && !feeCommand.isPresent()) {
|
||||
throw new FeesRequiredForPremiumNameException();
|
||||
}
|
||||
validateFeesAckedIfPresent(feeCommand, feesAndCredits);
|
||||
@@ -878,7 +889,7 @@ public class DomainFlowUtils {
|
||||
|
||||
/**
|
||||
* Check whether a new expiration time (via a renew) does not extend beyond a maximum number of
|
||||
* years (e.g. {@link DomainBase#MAX_REGISTRATION_YEARS}) from "now".
|
||||
* years (e.g. {@link Domain#MAX_REGISTRATION_YEARS}) from "now".
|
||||
*
|
||||
* @throws ExceedsMaxRegistrationYearsException if the new registration period is too long
|
||||
*/
|
||||
@@ -891,7 +902,7 @@ public class DomainFlowUtils {
|
||||
|
||||
/**
|
||||
* Check that a new registration period (via a create) does not extend beyond a maximum number of
|
||||
* years (e.g. {@link DomainBase#MAX_REGISTRATION_YEARS}).
|
||||
* years (e.g. {@link Domain#MAX_REGISTRATION_YEARS}).
|
||||
*
|
||||
* @throws ExceedsMaxRegistrationYearsException if the new registration period is too long
|
||||
*/
|
||||
@@ -909,16 +920,15 @@ public class DomainFlowUtils {
|
||||
* and we are going to ignore it; clients who don't care about secDNS can just ignore it.
|
||||
*/
|
||||
static void addSecDnsExtensionIfPresent(
|
||||
ImmutableList.Builder<ResponseExtension> extensions,
|
||||
ImmutableSet<DelegationSignerData> dsData) {
|
||||
ImmutableList.Builder<ResponseExtension> extensions, ImmutableSet<DomainDsData> dsData) {
|
||||
if (!dsData.isEmpty()) {
|
||||
extensions.add(SecDnsInfoExtension.create(dsData));
|
||||
}
|
||||
}
|
||||
|
||||
/** Update {@link DelegationSignerData} based on an update extension command. */
|
||||
static ImmutableSet<DelegationSignerData> updateDsData(
|
||||
ImmutableSet<DelegationSignerData> oldDsData, SecDnsUpdateExtension secDnsUpdate)
|
||||
/** Update {@link DomainDsData} based on an update extension command. */
|
||||
static ImmutableSet<DomainDsData> updateDsData(
|
||||
ImmutableSet<DomainDsData> oldDsData, SecDnsUpdateExtension secDnsUpdate)
|
||||
throws EppException {
|
||||
// We don't support 'urgent' because we do everything as fast as we can anyways.
|
||||
if (Boolean.TRUE.equals(secDnsUpdate.getUrgent())) { // We allow both false and null.
|
||||
@@ -937,8 +947,8 @@ public class DomainFlowUtils {
|
||||
if (remove != null && Boolean.FALSE.equals(remove.getAll())) {
|
||||
throw new SecDnsAllUsageException(); // Explicit all=false is meaningless.
|
||||
}
|
||||
Set<DelegationSignerData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
|
||||
Set<DelegationSignerData> toRemove =
|
||||
Set<DomainDsData> toAdd = (add == null) ? ImmutableSet.of() : add.getDsData();
|
||||
Set<DomainDsData> toRemove =
|
||||
(remove == null)
|
||||
? ImmutableSet.of()
|
||||
: (remove.getAll() == null) ? remove.getDsData() : oldDsData;
|
||||
@@ -947,7 +957,7 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
/** If a domain "clientUpdateProhibited" set, updates must clear it or fail. */
|
||||
static void verifyClientUpdateNotProhibited(Update command, DomainBase existingResource)
|
||||
static void verifyClientUpdateNotProhibited(Update command, Domain existingResource)
|
||||
throws ResourceHasClientUpdateProhibitedException {
|
||||
if (existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
&& !command
|
||||
@@ -990,9 +1000,9 @@ public class DomainFlowUtils {
|
||||
validateRegistrantAllowedOnTld(tld, command.getRegistrantContactId());
|
||||
validateNoDuplicateContacts(command.getContacts());
|
||||
validateRequiredContactsPresent(command.getRegistrant(), command.getContacts());
|
||||
ImmutableSet<String> fullyQualifiedHostNames = command.getNameserverFullyQualifiedHostNames();
|
||||
validateNameserversCountForTld(tld, domainName, fullyQualifiedHostNames.size());
|
||||
validateNameserversAllowedOnTld(tld, fullyQualifiedHostNames);
|
||||
ImmutableSet<String> hostNames = command.getNameserverHostNames();
|
||||
validateNameserversCountForTld(tld, domainName, hostNames.size());
|
||||
validateNameserversAllowedOnTld(tld, hostNames);
|
||||
}
|
||||
|
||||
/** Validate the secDNS extension, if present. */
|
||||
@@ -1120,13 +1130,13 @@ public class DomainFlowUtils {
|
||||
* the most recent HistoryEntry that fits the above criteria, with negated reportAmounts.
|
||||
*/
|
||||
static ImmutableSet<DomainTransactionRecord> createCancelingRecords(
|
||||
DomainBase domainBase,
|
||||
Domain domain,
|
||||
final DateTime now,
|
||||
Duration maxSearchPeriod,
|
||||
final ImmutableSet<TransactionReportField> cancelableFields) {
|
||||
|
||||
List<? extends HistoryEntry> recentHistoryEntries =
|
||||
findRecentHistoryEntries(domainBase, now, maxSearchPeriod);
|
||||
findRecentHistoryEntries(domain, now, maxSearchPeriod);
|
||||
Optional<? extends HistoryEntry> entryToCancel =
|
||||
Streams.findLast(
|
||||
recentHistoryEntries.stream()
|
||||
@@ -1165,14 +1175,14 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
private static List<? extends HistoryEntry> findRecentHistoryEntries(
|
||||
DomainBase domainBase, DateTime now, Duration maxSearchPeriod) {
|
||||
Domain domain, DateTime now, Duration maxSearchPeriod) {
|
||||
return jpaTm()
|
||||
.query(
|
||||
"FROM DomainHistory WHERE modificationTime >= :beginning AND domainRepoId = "
|
||||
+ ":repoId ORDER BY modificationTime ASC",
|
||||
DomainHistory.class)
|
||||
.setParameter("beginning", now.minus(maxSearchPeriod))
|
||||
.setParameter("repoId", domainBase.getRepoId())
|
||||
.setParameter("repoId", domain.getRepoId())
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
@@ -1531,11 +1541,11 @@ public class DomainFlowUtils {
|
||||
/** Nameservers are not allow-listed for this TLD. */
|
||||
public static class NameserversNotAllowedForTldException
|
||||
extends StatusProhibitsOperationException {
|
||||
public NameserversNotAllowedForTldException(Set<String> fullyQualifiedHostNames) {
|
||||
public NameserversNotAllowedForTldException(Set<String> hostNames) {
|
||||
super(
|
||||
String.format(
|
||||
"Nameservers '%s' are not allow-listed for this TLD",
|
||||
Joiner.on(',').join(fullyQualifiedHostNames)));
|
||||
Joiner.on(',').join(hostNames)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,18 +30,21 @@ 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;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.AfterValidationParameters;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.BeforeResponseParameters;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic.BeforeResponseReturnData;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Info;
|
||||
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,19 +88,20 @@ 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();
|
||||
DateTime now = clock.nowUtc();
|
||||
DomainBase domain = verifyExistence(
|
||||
DomainBase.class, targetId, loadByForeignKey(DomainBase.class, targetId, now));
|
||||
Domain domain =
|
||||
verifyExistence(Domain.class, targetId, loadByForeignKey(Domain.class, targetId, now));
|
||||
verifyOptionalAuthInfo(authInfo, domain);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setDomain(domain).build());
|
||||
@@ -105,7 +109,7 @@ public final class DomainInfoFlow implements Flow {
|
||||
// This is a policy decision that is left up to us by the rfcs.
|
||||
DomainInfoData.Builder infoBuilder =
|
||||
DomainInfoData.newBuilder()
|
||||
.setFullyQualifiedDomainName(domain.getDomainName())
|
||||
.setDomainName(domain.getDomainName())
|
||||
.setRepoId(domain.getRepoId())
|
||||
.setCurrentSponsorClientId(domain.getCurrentSponsorRegistrarId())
|
||||
.setRegistrant(
|
||||
@@ -142,14 +146,23 @@ public final class DomainInfoFlow implements Flow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private ImmutableList<ResponseExtension> getDomainResponseExtensions(
|
||||
DomainBase domain, DateTime now) throws EppException {
|
||||
private ImmutableList<ResponseExtension> getDomainResponseExtensions(Domain domain, DateTime now)
|
||||
throws EppException {
|
||||
ImmutableList.Builder<ResponseExtension> extensions = new ImmutableList.Builder<>();
|
||||
addSecDnsExtensionIfPresent(extensions, domain.getDsData());
|
||||
ImmutableSet<GracePeriodStatus> gracePeriodStatuses = domain.getGracePeriodStatuses();
|
||||
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.
|
||||
|
||||
@@ -195,9 +195,14 @@ public final class DomainPricingLogic {
|
||||
}
|
||||
|
||||
/** Returns a new transfer price for the pricer. */
|
||||
FeesAndCredits getTransferPrice(Registry registry, String domainName, DateTime dateTime)
|
||||
FeesAndCredits getTransferPrice(
|
||||
Registry registry,
|
||||
String domainName,
|
||||
DateTime dateTime,
|
||||
@Nullable Recurring recurringBillingEvent)
|
||||
throws EppException {
|
||||
DomainPrices domainPrices = getPricesForDomainName(domainName, dateTime);
|
||||
FeesAndCredits renewPrice =
|
||||
getRenewPrice(registry, domainName, dateTime, 1, recurringBillingEvent);
|
||||
return customLogic.customizeTransferPrice(
|
||||
TransferPriceParameters.newBuilder()
|
||||
.setFeesAndCredits(
|
||||
@@ -205,9 +210,9 @@ public final class DomainPricingLogic {
|
||||
.setCurrency(registry.getCurrency())
|
||||
.addFeeOrCredit(
|
||||
Fee.create(
|
||||
domainPrices.getRenewCost().getAmount(),
|
||||
renewPrice.getRenewCost().getAmount(),
|
||||
FeeType.RENEW,
|
||||
domainPrices.isPremium()))
|
||||
renewPrice.hasAnyPremiumFees()))
|
||||
.build())
|
||||
.setRegistry(registry)
|
||||
.setDomainName(InternetDomainName.from(domainName))
|
||||
|
||||
@@ -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;
|
||||
@@ -57,7 +59,7 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.OneTime;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Renew;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
@@ -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}
|
||||
@@ -166,7 +172,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Renew command = (Renew) resourceCommand;
|
||||
// Loads the target resource if it exists
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
Optional<AllocationToken> allocationToken =
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
@@ -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
|
||||
@@ -210,7 +220,9 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRenewalPrice(existingRecurringBillingEvent.getRenewalPrice().orElse(null))
|
||||
.setRenewalPriceBehavior(existingRecurringBillingEvent.getRenewalPriceBehavior())
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
PollMessage.Autorenew newAutorenewPollMessage =
|
||||
newAutorenewPollMessage(existingDomain)
|
||||
@@ -220,17 +232,20 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
// End the old autorenew billing event and poll message now. This may delete the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, now);
|
||||
DomainBase newDomain =
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
now,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
Domain newDomain =
|
||||
existingDomain
|
||||
.asBuilder()
|
||||
.setLastEppUpdateTime(now)
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
newAutorenewPollMessage.createVKey(),
|
||||
newAutorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
|
||||
.addGracePeriod(
|
||||
GracePeriod.forBillingEvent(
|
||||
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
|
||||
@@ -273,7 +288,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(
|
||||
DomainBase newDomain, DateTime now, Period period, Duration renewGracePeriod) {
|
||||
Domain newDomain, DateTime now, Period period, Duration renewGracePeriod) {
|
||||
Optional<MetadataExtension> metadataExtensionOpt =
|
||||
eppInput.getSingleExtension(MetadataExtension.class);
|
||||
if (metadataExtensionOpt.isPresent()) {
|
||||
@@ -299,8 +314,10 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
|
||||
private void verifyRenewAllowed(
|
||||
Optional<AuthInfo> authInfo,
|
||||
DomainBase existingDomain,
|
||||
Renew command) throws EppException {
|
||||
Domain existingDomain,
|
||||
Renew command,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
throws EppException {
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyNoDisallowedStatuses(existingDomain, RENEW_DISALLOWED_STATUSES);
|
||||
if (!isSuperuser) {
|
||||
@@ -309,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())) {
|
||||
@@ -330,9 +349,14 @@ 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()))
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -50,7 +49,7 @@ import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.OneTime;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Update;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
@@ -140,7 +139,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
Update command = (Update) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
boolean isExpired = existingDomain.getRegistrationExpirationTime().isBefore(now);
|
||||
FeesAndCredits feesAndCredits =
|
||||
pricingLogic.getRestorePrice(
|
||||
@@ -150,6 +149,8 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
verifyRestoreAllowed(command, existingDomain, feeUpdate, feesAndCredits, now);
|
||||
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
|
||||
historyBuilder.setId(domainHistoryKey.getId());
|
||||
DomainHistoryId domainHistoryId =
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId());
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
|
||||
DateTime newExpirationTime =
|
||||
@@ -158,17 +159,17 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
// a year and bill for it immediately, with no grace period.
|
||||
if (isExpired) {
|
||||
entitiesToSave.add(
|
||||
createRenewBillingEvent(domainHistoryKey, feesAndCredits.getRenewCost(), now));
|
||||
createRenewBillingEvent(domainHistoryId, feesAndCredits.getRenewCost(), now));
|
||||
}
|
||||
// Always bill for the restore itself.
|
||||
entitiesToSave.add(
|
||||
createRestoreBillingEvent(domainHistoryKey, feesAndCredits.getRestoreCost(), now));
|
||||
createRestoreBillingEvent(domainHistoryId, feesAndCredits.getRestoreCost(), now));
|
||||
|
||||
BillingEvent.Recurring autorenewEvent =
|
||||
newAutorenewBillingEvent(existingDomain)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(domainHistoryId)
|
||||
.build();
|
||||
PollMessage.Autorenew autorenewPollMessage =
|
||||
newAutorenewPollMessage(existingDomain)
|
||||
@@ -178,7 +179,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
DomainBase newDomain =
|
||||
Domain newDomain =
|
||||
performRestore(
|
||||
existingDomain,
|
||||
newExpirationTime,
|
||||
@@ -186,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());
|
||||
@@ -197,7 +197,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(DomainBase newDomain, DateTime now) {
|
||||
private DomainHistory buildDomainHistory(Domain newDomain, DateTime now) {
|
||||
return historyBuilder
|
||||
.setType(DOMAIN_RESTORE)
|
||||
.setDomain(newDomain)
|
||||
@@ -210,10 +210,11 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
|
||||
private void verifyRestoreAllowed(
|
||||
Update command,
|
||||
DomainBase existingDomain,
|
||||
Domain existingDomain,
|
||||
Optional<FeeUpdateCommandExtension> feeUpdate,
|
||||
FeesAndCredits feesAndCredits,
|
||||
DateTime now) throws EppException {
|
||||
DateTime now)
|
||||
throws EppException {
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
if (!isSuperuser) {
|
||||
verifyResourceOwnership(registrarId, existingDomain);
|
||||
@@ -233,8 +234,8 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
validateFeeChallenge(targetId, now, feeUpdate, feesAndCredits);
|
||||
}
|
||||
|
||||
private static DomainBase performRestore(
|
||||
DomainBase existingDomain,
|
||||
private static Domain performRestore(
|
||||
Domain existingDomain,
|
||||
DateTime newExpirationTime,
|
||||
BillingEvent.Recurring autorenewEvent,
|
||||
PollMessage.Autorenew autorenewPollMessage,
|
||||
@@ -248,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())
|
||||
@@ -259,19 +259,17 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private OneTime createRenewBillingEvent(
|
||||
Key<DomainHistory> domainHistoryKey, Money renewCost, DateTime now) {
|
||||
return prepareBillingEvent(domainHistoryKey, renewCost, now).setReason(Reason.RENEW).build();
|
||||
DomainHistoryId domainHistoryId, Money renewCost, DateTime now) {
|
||||
return prepareBillingEvent(domainHistoryId, renewCost, now).setReason(Reason.RENEW).build();
|
||||
}
|
||||
|
||||
private BillingEvent.OneTime createRestoreBillingEvent(
|
||||
Key<DomainHistory> domainHistoryKey, Money restoreCost, DateTime now) {
|
||||
return prepareBillingEvent(domainHistoryKey, restoreCost, now)
|
||||
.setReason(Reason.RESTORE)
|
||||
.build();
|
||||
DomainHistoryId domainHistoryId, Money restoreCost, DateTime now) {
|
||||
return prepareBillingEvent(domainHistoryId, restoreCost, now).setReason(Reason.RESTORE).build();
|
||||
}
|
||||
|
||||
private OneTime.Builder prepareBillingEvent(
|
||||
Key<DomainHistory> domainHistoryKey, Money cost, DateTime now) {
|
||||
DomainHistoryId domainHistoryId, Money cost, DateTime now) {
|
||||
return new BillingEvent.OneTime.Builder()
|
||||
.setTargetId(targetId)
|
||||
.setRegistrarId(registrarId)
|
||||
@@ -279,7 +277,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
.setBillingTime(now)
|
||||
.setPeriodYears(1)
|
||||
.setCost(cost)
|
||||
.setParent(domainHistoryKey);
|
||||
.setDomainHistoryId(domainHistoryId);
|
||||
}
|
||||
|
||||
private static ImmutableList<FeeTransformResponseExtension> createResponseExtensions(
|
||||
|
||||
@@ -31,7 +31,6 @@ import static google.registry.model.ResourceTransferUtils.approvePendingTransfer
|
||||
import static google.registry.model.reporting.DomainTransactionRecord.TransactionReportField.TRANSFER_SUCCESSFUL;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.pricing.PricingEngineProxy.getDomainRenewCost;
|
||||
import static google.registry.util.CollectionUtils.union;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
@@ -45,17 +44,21 @@ 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;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
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 {
|
||||
@@ -96,19 +111,29 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
@Inject DomainHistory.Builder historyBuilder;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
|
||||
@Inject EppInput eppInput;
|
||||
|
||||
@Inject DomainTransferApproveFlow() {}
|
||||
|
||||
/**
|
||||
* The logic in this flow, which handles client approvals, very closely parallels the logic in
|
||||
* {@link DomainBase#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
* {@link Domain#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
*/
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
extensionManager.register(MetadataExtension.class, AllocationTokenExtension.class);
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
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);
|
||||
@@ -120,10 +145,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
String gainingRegistrarId = transferData.getGainingRegistrarId();
|
||||
// Create a transfer billing event for 1 year, unless the superuser extension was used to set
|
||||
// the transfer period to zero. There is not a transfer cost if the transfer period is zero.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
|
||||
historyBuilder.setId(domainHistoryKey.getId());
|
||||
Optional<BillingEvent.OneTime> billingEvent =
|
||||
(transferData.getTransferPeriod().getValue() == 0)
|
||||
transferData.getTransferPeriod().getValue() == 0
|
||||
? Optional.empty()
|
||||
: Optional.of(
|
||||
new BillingEvent.OneTime.Builder()
|
||||
@@ -131,10 +157,19 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
.setTargetId(targetId)
|
||||
.setRegistrarId(gainingRegistrarId)
|
||||
.setPeriodYears(1)
|
||||
.setCost(getDomainRenewCost(targetId, transferData.getTransferRequestTime(), 1))
|
||||
.setCost(
|
||||
pricingLogic
|
||||
.getTransferPrice(
|
||||
Registry.get(tld),
|
||||
targetId,
|
||||
transferData.getTransferRequestTime(),
|
||||
existingRecurring)
|
||||
.getRenewCost())
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build());
|
||||
ImmutableList.Builder<ImmutableObject> entitiesToSave = new ImmutableList.Builder<>();
|
||||
// If we are within an autorenew grace period, cancel the autorenew billing event and don't
|
||||
@@ -143,19 +178,27 @@ 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(
|
||||
BillingEvent.Cancellation.forGracePeriod(
|
||||
autorenewGrace, now, domainHistoryKey, targetId));
|
||||
autorenewGrace,
|
||||
now,
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()),
|
||||
targetId));
|
||||
}
|
||||
}
|
||||
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
|
||||
// up deleting the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, 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.
|
||||
@@ -166,8 +209,12 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
.setTargetId(targetId)
|
||||
.setRegistrarId(gainingRegistrarId)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior())
|
||||
.setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null))
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
// Create a new autorenew poll message.
|
||||
PollMessage.Autorenew gainingClientAutorenewPollMessage =
|
||||
@@ -182,9 +229,9 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
// Construct the post-transfer domain.
|
||||
DomainBase partiallyApprovedDomain =
|
||||
Domain partiallyApprovedDomain =
|
||||
approvePendingTransfer(existingDomain, TransferStatus.CLIENT_APPROVED, now);
|
||||
DomainBase newDomain =
|
||||
Domain newDomain =
|
||||
partiallyApprovedDomain
|
||||
.asBuilder()
|
||||
// Update the transferredRegistrationExpirationTime here since approvePendingTransfer()
|
||||
@@ -197,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
|
||||
@@ -238,7 +283,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(
|
||||
DomainBase newDomain, Registry registry, DateTime now, String gainingRegistrarId) {
|
||||
Domain newDomain, Registry registry, DateTime now, String gainingRegistrarId) {
|
||||
ImmutableSet<DomainTransactionRecord> cancelingRecords =
|
||||
createCancelingRecords(
|
||||
newDomain,
|
||||
|
||||
@@ -40,7 +40,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.domain.DomainBase;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
@@ -90,7 +91,7 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyHasPendingTransfer(existingDomain);
|
||||
verifyTransferInitiator(registrarId, existingDomain);
|
||||
@@ -104,7 +105,7 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
.setId(domainHistoryKey.getId())
|
||||
.setOtherRegistrarId(existingDomain.getTransferData().getLosingRegistrarId());
|
||||
|
||||
DomainBase newDomain =
|
||||
Domain newDomain =
|
||||
denyPendingTransfer(existingDomain, TransferStatus.CLIENT_CANCELLED, now, registrarId);
|
||||
DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now);
|
||||
tm().putAll(
|
||||
@@ -114,7 +115,9 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
targetId, newDomain.getTransferData(), null, domainHistoryKey));
|
||||
// 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.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
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());
|
||||
@@ -123,7 +126,7 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(DomainBase newDomain, Registry registry, DateTime now) {
|
||||
private DomainHistory buildDomainHistory(Domain newDomain, Registry registry, DateTime now) {
|
||||
ImmutableSet<DomainTransactionRecord> cancelingRecords =
|
||||
createCancelingRecords(
|
||||
newDomain,
|
||||
|
||||
@@ -28,7 +28,7 @@ import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.NoTransferHistoryToQueryException;
|
||||
import google.registry.flows.exceptions.NotAuthorizedToViewTransferException;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
@@ -71,7 +71,7 @@ public final class DomainTransferQueryFlow implements Flow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
DateTime now = clock.nowUtc();
|
||||
DomainBase domain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain domain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
verifyOptionalAuthInfo(authInfo, domain);
|
||||
// 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).
|
||||
|
||||
@@ -42,7 +42,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.domain.DomainBase;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
@@ -92,7 +93,7 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
Registry registry = Registry.get(existingDomain.getTld());
|
||||
Key<DomainHistory> domainHistoryKey = createHistoryKey(existingDomain, DomainHistory.class);
|
||||
historyBuilder
|
||||
@@ -105,7 +106,7 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
if (!isSuperuser) {
|
||||
checkAllowedAccessToTld(registrarId, existingDomain.getTld());
|
||||
}
|
||||
DomainBase newDomain =
|
||||
Domain newDomain =
|
||||
denyPendingTransfer(existingDomain, TransferStatus.CLIENT_REJECTED, now, registrarId);
|
||||
DomainHistory domainHistory = buildDomainHistory(newDomain, registry, now);
|
||||
tm().putAll(
|
||||
@@ -115,7 +116,9 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
targetId, newDomain.getTransferData(), null, now, domainHistoryKey));
|
||||
// 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.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, END_OF_TIME);
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
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());
|
||||
@@ -124,7 +127,7 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(DomainBase newDomain, Registry registry, DateTime now) {
|
||||
private DomainHistory buildDomainHistory(Domain newDomain, Registry registry, DateTime now) {
|
||||
ImmutableSet<DomainTransactionRecord> cancelingRecords =
|
||||
createCancelingRecords(
|
||||
newDomain,
|
||||
|
||||
@@ -32,6 +32,8 @@ import static google.registry.flows.domain.DomainTransferUtils.createLosingTrans
|
||||
import static google.registry.flows.domain.DomainTransferUtils.createPendingTransferData;
|
||||
import static google.registry.flows.domain.DomainTransferUtils.createTransferResponse;
|
||||
import static google.registry.flows.domain.DomainTransferUtils.createTransferServerApproveEntities;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
@@ -47,19 +49,24 @@ 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;
|
||||
import google.registry.flows.exceptions.TransferPeriodMustBeOneYearException;
|
||||
import google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
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.AllocationToken;
|
||||
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;
|
||||
@@ -116,6 +123,20 @@ 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}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException}
|
||||
*/
|
||||
@ReportingSpec(ActivityReportField.DOMAIN_TRANSFER_REQUEST)
|
||||
public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
@@ -137,6 +158,8 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
@Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject AllocationTokenFlowUtils allocationTokenFlowUtils;
|
||||
|
||||
@Inject DomainTransferRequestFlow() {}
|
||||
|
||||
@Override
|
||||
@@ -144,19 +167,31 @@ 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();
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
Optional<AllocationToken> allocationToken =
|
||||
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
|
||||
existingDomain,
|
||||
Registry.get(existingDomain.getTld()),
|
||||
gainingClientId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
|
||||
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
|
||||
Period period =
|
||||
superuserExtension.isPresent()
|
||||
? superuserExtension.get().getRenewalPeriod()
|
||||
: ((Transfer) resourceCommand).getPeriod();
|
||||
verifyTransferAllowed(existingDomain, period, now, superuserExtension);
|
||||
verifyTransferAllowed(existingDomain, period, now, superuserExtension, allocationToken);
|
||||
|
||||
// If client passed an applicable static token this updates the domain
|
||||
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
|
||||
|
||||
String tld = existingDomain.getTld();
|
||||
Registry registry = Registry.get(tld);
|
||||
// An optional extension from the client specifying what they think the transfer should cost.
|
||||
@@ -168,10 +203,12 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
throw new TransferPeriodZeroAndFeeTransferExtensionException();
|
||||
}
|
||||
// If the period is zero, then there is no fee for the transfer.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
Optional<FeesAndCredits> feesAndCredits =
|
||||
(period.getValue() == 0)
|
||||
? Optional.empty()
|
||||
: Optional.of(pricingLogic.getTransferPrice(registry, targetId, now));
|
||||
: Optional.of(
|
||||
pricingLogic.getTransferPrice(registry, targetId, now, existingRecurring));
|
||||
if (feesAndCredits.isPresent()) {
|
||||
validateFeeChallenge(targetId, now, feeTransfer, feesAndCredits.get());
|
||||
}
|
||||
@@ -180,9 +217,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
|
||||
@@ -190,7 +230,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
//
|
||||
// See b/19430703#comment17 and https://www.icann.org/news/advisory-2002-06-06-en for the
|
||||
// policy documentation for transfers subsuming autorenews within the autorenew grace period.
|
||||
DomainBase domainAtTransferTime = existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||
Domain domainAtTransferTime = existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||
// The new expiration time if there is a server approval.
|
||||
DateTime serverApproveNewExpirationTime =
|
||||
computeExDateForApprovalTime(domainAtTransferTime, automaticTransferTime, period);
|
||||
@@ -201,6 +241,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
serverApproveNewExpirationTime,
|
||||
domainHistoryKey,
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
trid,
|
||||
gainingClientId,
|
||||
feesAndCredits.map(FeesAndCredits::getTotalCost),
|
||||
@@ -230,8 +271,12 @@ 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, automaticTransferTime);
|
||||
DomainBase newDomain =
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
automaticTransferTime,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
Domain newDomain =
|
||||
existingDomain
|
||||
.asBuilder()
|
||||
.setTransferData(pendingTransferData)
|
||||
@@ -255,12 +300,14 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private void verifyTransferAllowed(
|
||||
DomainBase existingDomain,
|
||||
Domain existingDomain,
|
||||
Period period,
|
||||
DateTime now,
|
||||
Optional<DomainTransferRequestSuperuserExtension> superuserExtension)
|
||||
Optional<DomainTransferRequestSuperuserExtension> superuserExtension,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
throws EppException {
|
||||
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
|
||||
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
|
||||
if (!isSuperuser) {
|
||||
verifyAuthInfoPresentForResourceTransfer(authInfo);
|
||||
verifyAuthInfo(authInfo.get(), existingDomain);
|
||||
@@ -294,7 +341,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.
|
||||
@@ -320,7 +367,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private DomainHistory buildDomainHistory(
|
||||
DomainBase newDomain, Registry registry, DateTime now, Period period) {
|
||||
Domain newDomain, Registry registry, DateTime now, Period period) {
|
||||
return historyBuilder
|
||||
.setType(DOMAIN_TRANSFER_REQUEST)
|
||||
.setPeriod(period)
|
||||
@@ -337,9 +384,9 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
private DomainTransferResponse createResponse(
|
||||
Period period, DomainBase existingDomain, DomainBase newDomain, DateTime now) {
|
||||
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);
|
||||
|
||||
@@ -24,7 +24,8 @@ import com.googlecode.objectify.Key;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
@@ -109,7 +110,8 @@ public final class DomainTransferUtils {
|
||||
DateTime automaticTransferTime,
|
||||
DateTime serverApproveNewExpirationTime,
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
DomainBase existingDomain,
|
||||
Domain existingDomain,
|
||||
Recurring existingRecurring,
|
||||
Trid trid,
|
||||
String gainingRegistrarId,
|
||||
Optional<Money> transferCost,
|
||||
@@ -144,7 +146,11 @@ public final class DomainTransferUtils {
|
||||
return builder
|
||||
.add(
|
||||
createGainingClientAutorenewEvent(
|
||||
serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingRegistrarId))
|
||||
existingRecurring,
|
||||
serverApproveNewExpirationTime,
|
||||
domainHistoryKey,
|
||||
targetId,
|
||||
gainingRegistrarId))
|
||||
.add(
|
||||
createGainingClientAutorenewPollMessage(
|
||||
serverApproveNewExpirationTime, domainHistoryKey, targetId, gainingRegistrarId))
|
||||
@@ -212,7 +218,7 @@ public final class DomainTransferUtils {
|
||||
TransferData transferData,
|
||||
@Nullable DateTime extendedRegistrationExpirationTime) {
|
||||
return new DomainTransferResponse.Builder()
|
||||
.setFullyQualifiedDomainName(targetId)
|
||||
.setDomainName(targetId)
|
||||
.setGainingRegistrarId(transferData.getGainingRegistrarId())
|
||||
.setLosingRegistrarId(transferData.getLosingRegistrarId())
|
||||
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
|
||||
@@ -239,6 +245,7 @@ public final class DomainTransferUtils {
|
||||
}
|
||||
|
||||
private static BillingEvent.Recurring createGainingClientAutorenewEvent(
|
||||
Recurring existingRecurring,
|
||||
DateTime serverApproveNewExpirationTime,
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
String targetId,
|
||||
@@ -250,7 +257,10 @@ public final class DomainTransferUtils {
|
||||
.setRegistrarId(gainingRegistrarId)
|
||||
.setEventTime(serverApproveNewExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(domainHistoryKey)
|
||||
.setRenewalPriceBehavior(existingRecurring.getRenewalPriceBehavior())
|
||||
.setRenewalPrice(existingRecurring.getRenewalPrice().orElse(null))
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -264,7 +274,7 @@ public final class DomainTransferUtils {
|
||||
* renewal, we must issue a cancellation for the autorenew, so that the losing registrar will not
|
||||
* be charged (essentially, the gaining registrar takes on the cost of the year of registration
|
||||
* that the autorenew just added). 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
|
||||
* without an additional year 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.
|
||||
*
|
||||
* <p>For details on the policy justification, see b/19430703#comment17 and <a
|
||||
@@ -275,17 +285,20 @@ public final class DomainTransferUtils {
|
||||
DateTime now,
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
String targetId,
|
||||
DomainBase existingDomain,
|
||||
Domain existingDomain,
|
||||
Optional<Money> transferCost) {
|
||||
DomainBase domainAtTransferTime =
|
||||
existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||
Domain domainAtTransferTime = existingDomain.cloneProjectedAtTime(automaticTransferTime);
|
||||
GracePeriod autorenewGracePeriod =
|
||||
getOnlyElement(
|
||||
domainAtTransferTime.getGracePeriodsOfType(GracePeriodStatus.AUTO_RENEW), null);
|
||||
if (autorenewGracePeriod != null && transferCost.isPresent()) {
|
||||
return Optional.of(
|
||||
BillingEvent.Cancellation.forGracePeriod(
|
||||
autorenewGracePeriod, now, domainHistoryKey, targetId)
|
||||
autorenewGracePeriod,
|
||||
now,
|
||||
new DomainHistoryId(
|
||||
domainHistoryKey.getParent().getName(), domainHistoryKey.getId()),
|
||||
targetId)
|
||||
.asBuilder()
|
||||
.setEventTime(automaticTransferTime)
|
||||
.build());
|
||||
@@ -308,7 +321,8 @@ public final class DomainTransferUtils {
|
||||
.setPeriodYears(1)
|
||||
.setEventTime(automaticTransferTime)
|
||||
.setBillingTime(automaticTransferTime.plus(registry.getTransferGracePeriodLength()))
|
||||
.setParent(domainHistoryKey)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -67,14 +67,14 @@ import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Update;
|
||||
import google.registry.model.domain.DomainCommand.Update.AddRemove;
|
||||
import google.registry.model.domain.DomainCommand.Update.Change;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
@@ -87,6 +87,7 @@ import google.registry.model.poll.PendingActionNotificationResponse.DomainPendin
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.model.tld.Registry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -173,15 +174,18 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
Update command = cloneAndLinkReferences((Update) resourceCommand, now);
|
||||
DomainBase existingDomain = loadAndVerifyExistence(DomainBase.class, targetId, now);
|
||||
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
|
||||
verifyUpdateAllowed(command, existingDomain, now);
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setExistingDomain(existingDomain).build());
|
||||
DomainBase newDomain = performUpdate(command, existingDomain, now);
|
||||
Domain newDomain = performUpdate(command, existingDomain, now);
|
||||
DomainHistory domainHistory =
|
||||
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
|
||||
validateNewState(newDomain);
|
||||
dnsQueue.addDomainRefreshTask(targetId);
|
||||
if (!Objects.equals(newDomain.getDsData(), existingDomain.getDsData())
|
||||
|| !Objects.equals(newDomain.getNsHosts(), existingDomain.getNsHosts())) {
|
||||
dnsQueue.addDomainRefreshTask(targetId);
|
||||
}
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
entitiesToSave.add(newDomain, domainHistory);
|
||||
Optional<BillingEvent.OneTime> statusUpdateBillingEvent =
|
||||
@@ -204,7 +208,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
/** Fail if the object doesn't exist or was deleted. */
|
||||
private void verifyUpdateAllowed(Update command, DomainBase existingDomain, DateTime now)
|
||||
private void verifyUpdateAllowed(Update command, Domain existingDomain, DateTime now)
|
||||
throws EppException {
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
AddRemove add = command.getInnerAdd();
|
||||
@@ -229,12 +233,10 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
validateContactsHaveTypes(add.getContacts());
|
||||
validateContactsHaveTypes(remove.getContacts());
|
||||
validateRegistrantAllowedOnTld(tld, command.getInnerChange().getRegistrantContactId());
|
||||
validateNameserversAllowedOnTld(
|
||||
tld, add.getNameserverFullyQualifiedHostNames());
|
||||
validateNameserversAllowedOnTld(tld, add.getNameserverHostNames());
|
||||
}
|
||||
|
||||
private DomainBase performUpdate(Update command, DomainBase domain, DateTime now)
|
||||
throws EppException {
|
||||
private Domain performUpdate(Update command, Domain domain, DateTime now) throws EppException {
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
checkSameValuesNotAddedAndRemoved(add.getNameservers(), remove.getNameservers());
|
||||
@@ -251,7 +253,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
Sets.union(Sets.difference(domain.getContacts(), remove.getContacts()), add.getContacts());
|
||||
validateNoDuplicateContacts(newContacts);
|
||||
|
||||
DomainBase.Builder domainBuilder =
|
||||
Domain.Builder domainBuilder =
|
||||
domain
|
||||
.asBuilder()
|
||||
// Handle the secDNS extension. As dsData in secDnsUpdate is read from EPP input and
|
||||
@@ -261,7 +263,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
secDnsUpdate.isPresent()
|
||||
? updateDsData(
|
||||
domain.getDsData().stream()
|
||||
.map(DelegationSignerData::cloneWithoutDomainRepoId)
|
||||
.map(DomainDsData::cloneWithoutDomainRepoId)
|
||||
.collect(toImmutableSet()),
|
||||
secDnsUpdate.get())
|
||||
: domain.getDsData())
|
||||
@@ -269,12 +271,18 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.addStatusValues(add.getStatusValues())
|
||||
.removeStatusValues(remove.getStatusValues())
|
||||
.addNameservers(add.getNameservers().stream().collect(toImmutableSet()))
|
||||
.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()))
|
||||
.removeContacts(remove.getContacts())
|
||||
.addContacts(add.getContacts())
|
||||
.setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant()))
|
||||
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
|
||||
|
||||
if (!add.getNameservers().isEmpty()) {
|
||||
domainBuilder.addNameservers(add.getNameservers().stream().collect(toImmutableSet()));
|
||||
}
|
||||
if (!remove.getNameservers().isEmpty()) {
|
||||
domainBuilder.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()));
|
||||
}
|
||||
|
||||
Optional<DomainUpdateSuperuserExtension> superuserExt =
|
||||
eppInput.getSingleExtension(DomainUpdateSuperuserExtension.class);
|
||||
if (superuserExt.isPresent()) {
|
||||
@@ -293,7 +301,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateNewState(DomainBase newDomain) throws EppException {
|
||||
private void validateNewState(Domain newDomain) throws EppException {
|
||||
validateRequiredContactsPresent(newDomain.getRegistrant(), newDomain.getContacts());
|
||||
validateDsData(newDomain.getDsData());
|
||||
validateNameserversCountForTld(
|
||||
@@ -304,7 +312,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
|
||||
/** Some status updates cost money. Bill only once no matter how many of them are changed. */
|
||||
private Optional<BillingEvent.OneTime> createBillingEventForStatusUpdates(
|
||||
DomainBase existingDomain, DomainBase newDomain, DomainHistory historyEntry, DateTime now) {
|
||||
Domain existingDomain, Domain newDomain, DomainHistory historyEntry, DateTime now) {
|
||||
Optional<MetadataExtension> metadataExtension =
|
||||
eppInput.getSingleExtension(MetadataExtension.class);
|
||||
if (metadataExtension.isPresent() && metadataExtension.get().getRequestedByRegistrar()) {
|
||||
@@ -320,7 +328,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
.setCost(Registry.get(existingDomain.getTld()).getServerStatusChangeCost())
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now)
|
||||
.setParent(historyEntry)
|
||||
.setDomainHistory(historyEntry)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
@@ -330,7 +338,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
|
||||
/** Enqueues a poll message iff a superuser is adding/removing server statuses. */
|
||||
private Optional<PollMessage.OneTime> createPollMessageForServerStatusUpdates(
|
||||
DomainBase existingDomain, DomainBase newDomain, DomainHistory historyEntry, DateTime now) {
|
||||
Domain existingDomain, Domain newDomain, DomainHistory historyEntry, DateTime now) {
|
||||
if (registrarId.equals(existingDomain.getPersistedCurrentSponsorRegistrarId())) {
|
||||
// Don't send a poll message when a superuser registrar is updating its own domain.
|
||||
return Optional.empty();
|
||||
|
||||
+2
-2
@@ -19,7 +19,7 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import google.registry.flows.EppException;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.tld.Registry;
|
||||
@@ -46,7 +46,7 @@ public class AllocationTokenCustomLogic {
|
||||
|
||||
/** Performs additional custom logic for validating a token on an existing domain. */
|
||||
public AllocationToken validateToken(
|
||||
DomainBase domain, AllocationToken token, Registry registry, String registrarId, DateTime now)
|
||||
Domain domain, AllocationToken token, Registry registry, String registrarId, DateTime now)
|
||||
throws EppException {
|
||||
// Do nothing.
|
||||
return token;
|
||||
|
||||
+96
-26
@@ -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.domain.DomainBase;
|
||||
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();
|
||||
}
|
||||
@@ -160,18 +175,14 @@ public class AllocationTokenFlowUtils {
|
||||
return Optional.empty();
|
||||
}
|
||||
AllocationToken tokenEntity = loadToken(extension.get().getAllocationToken());
|
||||
validateToken(
|
||||
InternetDomainName.from(command.getFullyQualifiedDomainName()),
|
||||
tokenEntity,
|
||||
registrarId,
|
||||
now);
|
||||
validateToken(InternetDomainName.from(command.getDomainName()), tokenEntity, registrarId, now);
|
||||
return Optional.of(
|
||||
tokenCustomLogic.validateToken(command, tokenEntity, registry, registrarId, now));
|
||||
}
|
||||
|
||||
/** Verifies and returns the allocation token if one is specified, otherwise does nothing. */
|
||||
public Optional<AllocationToken> verifyAllocationTokenIfPresent(
|
||||
DomainBase existingDomain,
|
||||
Domain existingDomain,
|
||||
Registry registry,
|
||||
String registrarId,
|
||||
DateTime now,
|
||||
@@ -187,6 +198,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 +288,20 @@ public class AllocationTokenFlowUtils {
|
||||
super("The allocation token is invalid");
|
||||
}
|
||||
}
|
||||
|
||||
/** The __REMOVEPACKAGE__ token is missing on a package domain command */
|
||||
public static class MissingRemovePackageTokenOnPackageDomainException
|
||||
extends AssociationProhibitsOperationException {
|
||||
MissingRemovePackageTokenOnPackageDomainException() {
|
||||
super("Domains that are inside packages cannot be explicitly renewed or transferred");
|
||||
}
|
||||
}
|
||||
|
||||
/** 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheck;
|
||||
import google.registry.model.eppoutput.CheckData.HostCheckData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostCommand.Check;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import javax.inject.Inject;
|
||||
@@ -61,8 +61,7 @@ public final class HostCheckFlow implements Flow {
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
ImmutableList<String> hostnames = ((Check) resourceCommand).getTargetIds();
|
||||
verifyTargetIdCount(hostnames, maxChecks);
|
||||
ImmutableSet<String> existingIds =
|
||||
checkResourcesExist(HostResource.class, hostnames, clock.nowUtc());
|
||||
ImmutableSet<String> existingIds = checkResourcesExist(Host.class, hostnames, clock.nowUtc());
|
||||
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String hostname : hostnames) {
|
||||
boolean unused = !existingIds.contains(hostname);
|
||||
|
||||
@@ -27,7 +27,6 @@ import static google.registry.persistence.transaction.TransactionManagerFactory.
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.DnsQueue;
|
||||
import google.registry.flows.EppException;
|
||||
@@ -41,16 +40,14 @@ import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
|
||||
import google.registry.flows.exceptions.ResourceCreateContentionException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.CreateData.HostCreateData;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostCommand.Create;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
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;
|
||||
@@ -105,11 +102,11 @@ public final class HostCreateFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
Create command = (Create) resourceCommand;
|
||||
DateTime now = tm().getTransactionTime();
|
||||
verifyResourceDoesNotExist(HostResource.class, targetId, now, registrarId);
|
||||
verifyResourceDoesNotExist(Host.class, targetId, now, registrarId);
|
||||
// The superordinate domain of the host object if creating an in-bailiwick host, or null if
|
||||
// creating an external host. This is looked up before we actually create the Host object so
|
||||
// creating an external host. This is looked up before we actually create the Host object, so
|
||||
// we can detect error conditions earlier.
|
||||
Optional<DomainBase> superordinateDomain =
|
||||
Optional<Domain> superordinateDomain =
|
||||
lookupSuperordinateDomain(validateHostName(targetId), now);
|
||||
verifySuperordinateDomainNotInPendingDelete(superordinateDomain.orElse(null));
|
||||
verifySuperordinateDomainOwnership(registrarId, superordinateDomain.orElse(null));
|
||||
@@ -121,28 +118,23 @@ public final class HostCreateFlow implements TransactionalFlow {
|
||||
? new SubordinateHostMustHaveIpException()
|
||||
: new UnexpectedExternalHostIpException();
|
||||
}
|
||||
HostResource newHost =
|
||||
new HostResource.Builder()
|
||||
Host newHost =
|
||||
new Host.Builder()
|
||||
.setCreationRegistrarId(registrarId)
|
||||
.setPersistedCurrentSponsorRegistrarId(registrarId)
|
||||
.setHostName(targetId)
|
||||
.setInetAddresses(command.getInetAddresses())
|
||||
.setRepoId(createRepoId(allocateId(), roidSuffix))
|
||||
.setSuperordinateDomain(superordinateDomain.map(DomainBase::createVKey).orElse(null))
|
||||
.setSuperordinateDomain(superordinateDomain.map(Domain::createVKey).orElse(null))
|
||||
.build();
|
||||
historyBuilder.setType(HOST_CREATE).setHost(newHost);
|
||||
ImmutableSet<ImmutableObject> entitiesToSave =
|
||||
ImmutableSet.of(
|
||||
newHost,
|
||||
historyBuilder.build(),
|
||||
ForeignKeyIndex.create(newHost, newHost.getDeletionTime()),
|
||||
EppResourceIndex.create(Key.create(newHost)));
|
||||
ImmutableSet<ImmutableObject> entitiesToSave = ImmutableSet.of(newHost, historyBuilder.build());
|
||||
if (superordinateDomain.isPresent()) {
|
||||
tm().update(
|
||||
superordinateDomain
|
||||
.get()
|
||||
.asBuilder()
|
||||
.addSubordinateHost(command.getFullyQualifiedHostName())
|
||||
.addSubordinateHost(command.getHostName())
|
||||
.build());
|
||||
// Only update DNS if this is a subordinate host. External hosts have no glue to write, so
|
||||
// they are only written as NS records from the referencing domain.
|
||||
@@ -154,14 +146,14 @@ public final class HostCreateFlow implements TransactionalFlow {
|
||||
|
||||
/** Subordinate hosts must have an ip address. */
|
||||
static class SubordinateHostMustHaveIpException extends RequiredParameterMissingException {
|
||||
public SubordinateHostMustHaveIpException() {
|
||||
SubordinateHostMustHaveIpException() {
|
||||
super("Subordinate hosts must have an ip address");
|
||||
}
|
||||
}
|
||||
|
||||
/** External hosts must not have ip addresses. */
|
||||
static class UnexpectedExternalHostIpException extends ParameterValueRangeErrorException {
|
||||
public UnexpectedExternalHostIpException() {
|
||||
UnexpectedExternalHostIpException() {
|
||||
super("External hosts must not have ip addresses");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,8 @@ import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppcommon.Trid;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import javax.inject.Inject;
|
||||
@@ -92,8 +92,8 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
validateHostName(targetId);
|
||||
checkLinkedDomains(targetId, now, HostResource.class);
|
||||
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
|
||||
checkLinkedDomains(targetId, now, Host.class);
|
||||
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
||||
if (!isSuperuser) {
|
||||
// Hosts transfer with their superordinate domains, so for hosts with a superordinate domain,
|
||||
@@ -104,8 +104,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||
: existingHost;
|
||||
verifyResourceOwnership(registrarId, owningResource);
|
||||
}
|
||||
HostResource newHost =
|
||||
existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build();
|
||||
Host newHost = existingHost.asBuilder().setStatusValues(null).setDeletionTime(now).build();
|
||||
if (existingHost.isSubordinate()) {
|
||||
dnsQueue.addHostRefreshTask(existingHost.getHostName());
|
||||
tm().update(
|
||||
|
||||
@@ -29,7 +29,7 @@ import google.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import google.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.util.Idn;
|
||||
import java.util.Optional;
|
||||
@@ -77,8 +77,8 @@ public class HostFlowUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the {@link DomainBase} this host is subordinate to, or null for external hosts. */
|
||||
public static Optional<DomainBase> lookupSuperordinateDomain(
|
||||
/** Return the {@link Domain} this host is subordinate to, or null for external hosts. */
|
||||
public static Optional<Domain> lookupSuperordinateDomain(
|
||||
InternetDomainName hostName, DateTime now) throws EppException {
|
||||
Optional<InternetDomainName> tld = findTldForName(hostName);
|
||||
if (!tld.isPresent()) {
|
||||
@@ -90,8 +90,7 @@ public class HostFlowUtils {
|
||||
hostName.parts().stream()
|
||||
.skip(hostName.parts().size() - (tld.get().parts().size() + 1))
|
||||
.collect(joining("."));
|
||||
Optional<DomainBase> superordinateDomain =
|
||||
loadByForeignKey(DomainBase.class, domainName, now);
|
||||
Optional<Domain> superordinateDomain = loadByForeignKey(Domain.class, domainName, now);
|
||||
if (!superordinateDomain.isPresent() || !isActive(superordinateDomain.get(), now)) {
|
||||
throw new SuperordinateDomainDoesNotExistException(domainName);
|
||||
}
|
||||
@@ -101,12 +100,12 @@ public class HostFlowUtils {
|
||||
/** Superordinate domain for this hostname does not exist. */
|
||||
static class SuperordinateDomainDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public SuperordinateDomainDoesNotExistException(String domainName) {
|
||||
super(DomainBase.class, domainName);
|
||||
super(Domain.class, domainName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure that the superordinate domain is sponsored by the provided registrar ID. */
|
||||
static void verifySuperordinateDomainOwnership(String registrarId, DomainBase superordinateDomain)
|
||||
static void verifySuperordinateDomainOwnership(String registrarId, Domain superordinateDomain)
|
||||
throws EppException {
|
||||
if (superordinateDomain != null
|
||||
&& !registrarId.equals(superordinateDomain.getCurrentSponsorRegistrarId())) {
|
||||
@@ -122,7 +121,7 @@ public class HostFlowUtils {
|
||||
}
|
||||
|
||||
/** Ensure that the superordinate domain is not in pending delete. */
|
||||
static void verifySuperordinateDomainNotInPendingDelete(DomainBase superordinateDomain)
|
||||
static void verifySuperordinateDomainNotInPendingDelete(Domain superordinateDomain)
|
||||
throws EppException {
|
||||
if ((superordinateDomain != null)
|
||||
&& superordinateDomain.getStatusValues().contains(StatusValue.PENDING_DELETE)) {
|
||||
|
||||
@@ -27,11 +27,11 @@ import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostInfoData;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.util.Clock;
|
||||
import javax.inject.Inject;
|
||||
@@ -65,7 +65,7 @@ public final class HostInfoFlow implements Flow {
|
||||
extensionManager.validate(); // There are no legal extensions for this flow.
|
||||
validateHostName(targetId);
|
||||
DateTime now = clock.nowUtc();
|
||||
HostResource host = loadAndVerifyExistence(HostResource.class, targetId, now);
|
||||
Host host = loadAndVerifyExistence(Host.class, targetId, now);
|
||||
ImmutableSet.Builder<StatusValue> statusValues = new ImmutableSet.Builder<>();
|
||||
statusValues.addAll(host.getStatusValues());
|
||||
if (isLinked(host.createVKey(), now)) {
|
||||
@@ -76,7 +76,7 @@ public final class HostInfoFlow implements Flow {
|
||||
// the client id, last transfer time, and pending transfer status need to be read off of it. If
|
||||
// there is no superordinate domain, the host's own values for these fields will be correct.
|
||||
if (host.isSubordinate()) {
|
||||
DomainBase superordinateDomain =
|
||||
Domain superordinateDomain =
|
||||
tm().transact(
|
||||
() -> tm().loadByKey(host.getSuperordinateDomain()).cloneProjectedAtTime(now));
|
||||
hostInfoDataBuilder
|
||||
@@ -93,7 +93,7 @@ public final class HostInfoFlow implements Flow {
|
||||
return responseBuilder
|
||||
.setResData(
|
||||
hostInfoDataBuilder
|
||||
.setFullyQualifiedHostName(host.getHostName())
|
||||
.setHostName(host.getHostName())
|
||||
.setRepoId(host.getRepoId())
|
||||
.setStatusValues(statusValues.build())
|
||||
.setInetAddresses(host.getInetAddresses())
|
||||
|
||||
@@ -26,7 +26,6 @@ import static google.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain
|
||||
import static google.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainNotInPendingDelete;
|
||||
import static google.registry.flows.host.HostFlowUtils.verifySuperordinateDomainOwnership;
|
||||
import static google.registry.model.index.ForeignKeyIndex.loadAndGetKey;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.HOST_UPDATE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
@@ -46,18 +45,18 @@ import google.registry.flows.TransactionalFlow;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.exceptions.ResourceHasClientUpdateProhibitedException;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.eppinput.ResourceCommand;
|
||||
import google.registry.model.eppoutput.EppResponse;
|
||||
import google.registry.model.host.Host;
|
||||
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.host.HostResource;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Objects;
|
||||
@@ -130,33 +129,33 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
extensionManager.validate();
|
||||
Update command = (Update) resourceCommand;
|
||||
Change change = command.getInnerChange();
|
||||
String suppliedNewHostName = change.getFullyQualifiedHostName();
|
||||
String suppliedNewHostName = change.getHostName();
|
||||
DateTime now = tm().getTransactionTime();
|
||||
validateHostName(targetId);
|
||||
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
|
||||
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
||||
boolean isHostRename = suppliedNewHostName != null;
|
||||
String oldHostName = targetId;
|
||||
String newHostName = firstNonNull(suppliedNewHostName, oldHostName);
|
||||
DomainBase oldSuperordinateDomain =
|
||||
Domain oldSuperordinateDomain =
|
||||
existingHost.isSubordinate()
|
||||
? tm().loadByKey(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
|
||||
: null;
|
||||
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
|
||||
Optional<DomainBase> newSuperordinateDomain =
|
||||
Optional<Domain> newSuperordinateDomain =
|
||||
lookupSuperordinateDomain(validateHostName(newHostName), now);
|
||||
verifySuperordinateDomainNotInPendingDelete(newSuperordinateDomain.orElse(null));
|
||||
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
||||
verifyUpdateAllowed(
|
||||
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
||||
if (isHostRename && loadAndGetKey(HostResource.class, newHostName, now) != null) {
|
||||
if (isHostRename && ForeignKeyUtils.load(Host.class, newHostName, now) != null) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
AddRemove remove = command.getInnerRemove();
|
||||
checkSameValuesNotAddedAndRemoved(add.getStatusValues(), remove.getStatusValues());
|
||||
checkSameValuesNotAddedAndRemoved(add.getInetAddresses(), remove.getInetAddresses());
|
||||
VKey<DomainBase> newSuperordinateDomainKey =
|
||||
newSuperordinateDomain.map(DomainBase::createVKey).orElse(null);
|
||||
VKey<Domain> newSuperordinateDomainKey =
|
||||
newSuperordinateDomain.map(Domain::createVKey).orElse(null);
|
||||
// If the superordinateDomain field is changing, set the lastSuperordinateChange to now.
|
||||
DateTime lastSuperordinateChange =
|
||||
Objects.equals(newSuperordinateDomainKey, existingHost.getSuperordinateDomain())
|
||||
@@ -175,7 +174,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
newSuperordinateDomain.isPresent()
|
||||
? newSuperordinateDomain.get().getCurrentSponsorRegistrarId()
|
||||
: owningResource.getPersistedCurrentSponsorRegistrarId();
|
||||
HostResource newHost =
|
||||
Host newHost =
|
||||
existingHost
|
||||
.asBuilder()
|
||||
.setHostName(newHostName)
|
||||
@@ -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);
|
||||
@@ -210,11 +205,11 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
|
||||
private void verifyUpdateAllowed(
|
||||
Update command,
|
||||
HostResource existingHost,
|
||||
DomainBase newSuperordinateDomain,
|
||||
Host existingHost,
|
||||
Domain newSuperordinateDomain,
|
||||
EppResource owningResource,
|
||||
boolean isHostRename)
|
||||
throws EppException {
|
||||
throws EppException {
|
||||
if (!isSuperuser) {
|
||||
// Verify that the host belongs to this registrar, either directly or because it is currently
|
||||
// subordinate to a domain owned by this registrar.
|
||||
@@ -237,8 +232,8 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
||||
}
|
||||
|
||||
private void verifyHasIpsIffIsExternal(
|
||||
Update command, HostResource existingHost, HostResource newHost) throws EppException {
|
||||
private void verifyHasIpsIffIsExternal(Update command, Host existingHost, Host newHost)
|
||||
throws EppException {
|
||||
boolean wasSubordinate = existingHost.isSubordinate();
|
||||
boolean willBeSubordinate = newHost.isSubordinate();
|
||||
boolean willBeExternal = !willBeSubordinate;
|
||||
@@ -258,14 +253,14 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private void enqueueTasks(HostResource existingHost, HostResource newHost) {
|
||||
private void enqueueTasks(Host existingHost, Host newHost) {
|
||||
// Only update DNS for subordinate hosts. External hosts have no glue to write, so they
|
||||
// are only written as NS records from the referencing domain.
|
||||
if (existingHost.isSubordinate()) {
|
||||
dnsQueue.addHostRefreshTask(existingHost.getHostName());
|
||||
}
|
||||
// In case of a rename, there are many updates we need to queue up.
|
||||
if (((Update) resourceCommand).getInnerChange().getFullyQualifiedHostName() != null) {
|
||||
if (((Update) resourceCommand).getInnerChange().getHostName() != null) {
|
||||
// If the renamed host is also subordinate, then we must enqueue an update to write the new
|
||||
// glue.
|
||||
if (newHost.isSubordinate()) {
|
||||
@@ -277,7 +272,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateSuperordinateDomains(HostResource existingHost, HostResource newHost) {
|
||||
private static void updateSuperordinateDomains(Host existingHost, Host newHost) {
|
||||
if (existingHost.isSubordinate()
|
||||
&& newHost.isSubordinate()
|
||||
&& Objects.equals(
|
||||
|
||||
@@ -41,7 +41,7 @@ public abstract class BackupGroupRoot extends ImmutableObject implements UnsafeS
|
||||
* that this is updated on every save, rather than only in response to an {@code <update>} command
|
||||
*/
|
||||
@XmlTransient
|
||||
// Prevents subclasses from unexpectedly accessing as property (e.g., HostResource), which would
|
||||
// Prevents subclasses from unexpectedly accessing as property (e.g., Host), which would
|
||||
// require an unnecessary non-private setter method.
|
||||
@Access(AccessType.FIELD)
|
||||
@AttributeOverride(name = "lastUpdateTime", column = @Column(name = "updateTimestamp"))
|
||||
|
||||
@@ -20,6 +20,7 @@ import static google.registry.model.IdService.allocateId;
|
||||
import static google.registry.model.ModelUtils.getAllFields;
|
||||
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import google.registry.model.annotations.OfyIdAllocation;
|
||||
import google.registry.util.TypeUtils.TypeInstantiator;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
@@ -59,7 +60,10 @@ public interface Buildable {
|
||||
// any entity it has one and only one @Id field in its class hierarchy.
|
||||
Field idField =
|
||||
getAllFields(instance.getClass()).values().stream()
|
||||
.filter(field -> field.isAnnotationPresent(Id.class))
|
||||
.filter(
|
||||
field ->
|
||||
field.isAnnotationPresent(Id.class)
|
||||
|| field.isAnnotationPresent(OfyIdAllocation.class))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (idField != null
|
||||
|
||||
@@ -16,25 +16,14 @@ package google.registry.model;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
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.DomainBase;
|
||||
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.host.HostResource;
|
||||
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
|
||||
@@ -43,32 +32,14 @@ public final class EntityClasses {
|
||||
/** Set of entity classes. */
|
||||
public static final ImmutableSet<Class<? extends ImmutableObject>> ALL_CLASSES =
|
||||
ImmutableSet.of(
|
||||
AllocationToken.class,
|
||||
BillingEvent.Cancellation.class,
|
||||
BillingEvent.Modification.class,
|
||||
BillingEvent.OneTime.class,
|
||||
BillingEvent.Recurring.class,
|
||||
Contact.class,
|
||||
ContactHistory.class,
|
||||
ContactResource.class,
|
||||
DomainBase.class,
|
||||
Domain.class,
|
||||
DomainHistory.class,
|
||||
EntityGroupRoot.class,
|
||||
EppResourceIndex.class,
|
||||
EppResourceIndexBucket.class,
|
||||
ForeignKeyIndex.ForeignKeyContactIndex.class,
|
||||
ForeignKeyIndex.ForeignKeyDomainIndex.class,
|
||||
ForeignKeyIndex.ForeignKeyHostIndex.class,
|
||||
GaeUserIdConverter.class,
|
||||
HistoryEntry.class,
|
||||
HostHistory.class,
|
||||
HostResource.class,
|
||||
Lock.class,
|
||||
PollMessage.class,
|
||||
PollMessage.Autorenew.class,
|
||||
PollMessage.OneTime.class,
|
||||
RdeRevision.class,
|
||||
Registrar.class,
|
||||
ServerSecret.class);
|
||||
Host.class,
|
||||
HostHistory.class);
|
||||
|
||||
private EntityClasses() {}
|
||||
}
|
||||
|
||||
@@ -140,9 +140,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
DateTime lastEppUpdateTime;
|
||||
|
||||
/** Status values associated with this resource. */
|
||||
@Column(name = "statuses")
|
||||
// TODO(b/177567432): rename to "statuses" once we're off datastore.
|
||||
Set<StatusValue> status;
|
||||
@Ignore Set<StatusValue> statuses;
|
||||
|
||||
public String getRepoId() {
|
||||
return repoId;
|
||||
@@ -188,7 +186,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
}
|
||||
|
||||
public final ImmutableSet<StatusValue> getStatusValues() {
|
||||
return nullToEmptyImmutableCopy(status);
|
||||
return nullToEmptyImmutableCopy(statuses);
|
||||
}
|
||||
|
||||
public DateTime getDeletionTime() {
|
||||
@@ -306,7 +304,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
statusValue,
|
||||
resourceClass.getSimpleName());
|
||||
}
|
||||
getInstance().status = statusValues;
|
||||
getInstance().statuses = statusValues;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,11 +32,10 @@ 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.domain.DomainBase;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.model.tld.Registry;
|
||||
@@ -117,20 +116,19 @@ public final class EppResourceUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the last created version of an {@link EppResource} from Datastore by foreign key, using a
|
||||
* cache.
|
||||
* Loads the last created version of an {@link EppResource} from the database by foreign key,
|
||||
* using a cache.
|
||||
*
|
||||
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*
|
||||
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
|
||||
* it may have various expirable conditions and status values that might implicitly change its
|
||||
* state as time progresses even if it has not been updated in Datastore. Rather, the resource
|
||||
* state as time progresses even if it has not been updated in the database. Rather, the resource
|
||||
* must be combined with a timestamp to view its current state. We use a global last updated
|
||||
* timestamp on the resource's entity group (which is essentially free since all writes to the
|
||||
* entity group must be serialized anyways) to guarantee monotonically increasing write times, and
|
||||
* forward our projected time to the greater of this timestamp or "now". This guarantees that
|
||||
* we're not projecting into the past.
|
||||
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
|
||||
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
|
||||
* past.
|
||||
*
|
||||
* <p>Do not call this cached version for anything that needs transactional consistency. It should
|
||||
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
|
||||
@@ -150,19 +148,18 @@ public final class EppResourceUtils {
|
||||
checkArgument(
|
||||
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
|
||||
"loadByForeignKey may only be called for foreign keyed EPP resources");
|
||||
ForeignKeyIndex<T> fki =
|
||||
VKey<T> key =
|
||||
useCache
|
||||
? ForeignKeyIndex.loadCached(clazz, ImmutableList.of(foreignKey), now)
|
||||
.getOrDefault(foreignKey, null)
|
||||
: ForeignKeyIndex.load(clazz, foreignKey, now);
|
||||
// The value of fki.getResourceKey() might be null for hard-deleted prober data.
|
||||
if (fki == null || isAtOrAfter(now, fki.getDeletionTime()) || fki.getResourceKey() == null) {
|
||||
? ForeignKeyUtils.loadCached(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
|
||||
: ForeignKeyUtils.load(clazz, foreignKey, now);
|
||||
// The returned key is null if the resource is hard deleted or soft deleted by the given time.
|
||||
if (key == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
T resource =
|
||||
useCache
|
||||
? EppResource.loadCached(fki.getResourceKey())
|
||||
: tm().transact(() -> tm().loadByKeyIfPresent(fki.getResourceKey()).orElse(null));
|
||||
? EppResource.loadCached(key)
|
||||
: tm().transact(() -> tm().loadByKeyIfPresent(key).orElse(null));
|
||||
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@@ -178,7 +175,7 @@ public final class EppResourceUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks multiple {@link EppResource} objects from Datastore by unique ids.
|
||||
* Checks multiple {@link EppResource} objects from the database by unique ids.
|
||||
*
|
||||
* <p>There are currently no resources that support checks and do not use foreign keys. If we need
|
||||
* to support that case in the future, we can loosen the type to allow any {@link EppResource} and
|
||||
@@ -190,7 +187,7 @@ public final class EppResourceUtils {
|
||||
*/
|
||||
public static <T extends EppResource> ImmutableSet<String> checkResourcesExist(
|
||||
Class<T> clazz, List<String> uniqueIds, final DateTime now) {
|
||||
return ForeignKeyIndex.load(clazz, uniqueIds, now).keySet();
|
||||
return ForeignKeyUtils.load(clazz, uniqueIds, now).keySet();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -342,24 +339,24 @@ public final class EppResourceUtils {
|
||||
* @param now the logical time of the check
|
||||
* @param limit the maximum number of returned keys, unlimited if null
|
||||
*/
|
||||
public static ImmutableSet<VKey<DomainBase>> getLinkedDomainKeys(
|
||||
public static ImmutableSet<VKey<Domain>> getLinkedDomainKeys(
|
||||
VKey<? extends EppResource> key, DateTime now, @Nullable Integer limit) {
|
||||
checkArgument(
|
||||
key.getKind().equals(ContactResource.class) || key.getKind().equals(HostResource.class),
|
||||
"key must be either VKey<ContactResource> or VKey<HostResource>, 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<DomainBase> query =
|
||||
com.googlecode.objectify.cmd.Query<Domain> query =
|
||||
auditedOfy()
|
||||
.load()
|
||||
.type(DomainBase.class)
|
||||
.type(Domain.class)
|
||||
.filter(isContactKey ? "allContacts.contact" : "nsHosts", key.getOfyKey())
|
||||
.filter("deletionTime >", now);
|
||||
if (limit != null) {
|
||||
query.limit(limit);
|
||||
}
|
||||
return query.keys().list().stream().map(DomainBase::createVKey).collect(toImmutableSet());
|
||||
return query.keys().list().stream().map(Domain::createVKey).collect(toImmutableSet());
|
||||
} else {
|
||||
return tm().transact(
|
||||
() -> {
|
||||
@@ -382,16 +379,15 @@ public final class EppResourceUtils {
|
||||
query.setMaxResults(limit);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<VKey<DomainBase>> domainBaseKeySet =
|
||||
(ImmutableSet<VKey<DomainBase>>)
|
||||
ImmutableSet<VKey<Domain>> domainKeySet =
|
||||
(ImmutableSet<VKey<Domain>>)
|
||||
query
|
||||
.getResultStream()
|
||||
.map(
|
||||
repoId ->
|
||||
DomainBase.createVKey(
|
||||
Key.create(DomainBase.class, (String) repoId)))
|
||||
Domain.createVKey(Key.create(Domain.class, (String) repoId)))
|
||||
.collect(toImmutableSet());
|
||||
return domainBaseKeySet;
|
||||
return domainKeySet;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
// 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;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
|
||||
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.config.RegistryConfig;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Util class to map a foreign key to the {@link VKey} to the active instance of {@link EppResource}
|
||||
* whose unique repoId matches the foreign key string at a given time. The instance is never
|
||||
* deleted, but it is updated if a newer entity becomes the active entity.
|
||||
*/
|
||||
public final class ForeignKeyUtils {
|
||||
|
||||
private ForeignKeyUtils() {}
|
||||
|
||||
private static final ImmutableMap<Class<? extends EppResource>, String>
|
||||
RESOURCE_TYPE_TO_FK_PROPERTY =
|
||||
ImmutableMap.of(
|
||||
Contact.class, "contactId",
|
||||
Domain.class, "domainName",
|
||||
Host.class, "hostName");
|
||||
|
||||
/**
|
||||
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
|
||||
*
|
||||
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
|
||||
* created resource was deleted before time "now".
|
||||
*
|
||||
* @param clazz the resource type to load
|
||||
* @param foreignKey foreign key to match
|
||||
* @param now the current logical time to use when checking for soft deletion of the foreign key
|
||||
* index
|
||||
*/
|
||||
@Nullable
|
||||
public static <E extends EppResource> VKey<E> load(
|
||||
Class<E> clazz, String foreignKey, DateTime now) {
|
||||
return load(clazz, ImmutableList.of(foreignKey), now).get(foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a map of {@link String} foreign keys to {@link VKey}s to {@link EppResource} that are
|
||||
* active at or after the specified moment in time.
|
||||
*
|
||||
* <p>The returned map will omit any foreign keys for which the {@link EppResource} doesn't exist
|
||||
* or has been soft deleted.
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> load(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
return load(clazz, foreignKeys, false).entrySet().stream()
|
||||
.filter(e -> now.isBefore(e.getValue().deletionTime()))
|
||||
.collect(toImmutableMap(Entry::getKey, e -> VKey.createSql(clazz, e.getValue().repoId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to load {@link VKey}s to all the most recent {@link EppResource}s for the given
|
||||
* foreign 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 production, the {@code deletionTime} for entities with the same foreign key
|
||||
* should monotonically increase as one cannot create a domain/host/contact with the same foreign
|
||||
* key without soft deleting the existing resource first. However, in test, there's no such
|
||||
* guarantee and one must make sure that no two resources with the same foreign key exist with the
|
||||
* same max {@code deleteTime}, usually {@code END_OF_TIME}, lest this method throws an error due
|
||||
* to duplicate keys.
|
||||
*/
|
||||
private static <E extends EppResource> ImmutableMap<String, MostRecentResource> load(
|
||||
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaJpaTm) {
|
||||
String fkProperty = RESOURCE_TYPE_TO_FK_PROPERTY.get(clazz);
|
||||
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
|
||||
return jpaTmToUse.transact(
|
||||
() ->
|
||||
jpaTmToUse
|
||||
.query(
|
||||
("SELECT %fkProperty%, repoId, deletionTime FROM %entity% WHERE (%fkProperty%,"
|
||||
+ " deletionTime) IN (SELECT %fkProperty%, MAX(deletionTime) FROM"
|
||||
+ " %entity% WHERE %fkProperty% IN (:fks) GROUP BY %fkProperty%)")
|
||||
.replace("%fkProperty%", fkProperty)
|
||||
.replace("%entity%", clazz.getSimpleName()),
|
||||
Object[].class)
|
||||
.setParameter("fks", foreignKeys)
|
||||
.getResultStream()
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
row -> (String) row[0],
|
||||
row -> MostRecentResource.create((String) row[1], (DateTime) row[2]))));
|
||||
}
|
||||
|
||||
private static final CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
CACHE_LOADER =
|
||||
new CacheLoader<VKey<? extends EppResource>, Optional<MostRecentResource>>() {
|
||||
|
||||
@Override
|
||||
public Optional<MostRecentResource> load(VKey<? extends EppResource> key) {
|
||||
return loadAll(ImmutableList.of(key)).get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, Optional<MostRecentResource>> loadAll(
|
||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||
if (!keys.iterator().hasNext()) {
|
||||
return ImmutableMap.of();
|
||||
}
|
||||
// It is safe to use the resource type of first element because when this function is
|
||||
// called, it is always passed with a list of VKeys with the same type.
|
||||
Class<? extends EppResource> clazz = keys.iterator().next().getKind();
|
||||
ImmutableList<String> foreignKeys =
|
||||
Streams.stream(keys)
|
||||
.map(key -> (String) key.getSqlKey())
|
||||
.collect(toImmutableList());
|
||||
ImmutableMap<String, MostRecentResource> existingKeys =
|
||||
ForeignKeyUtils.load(clazz, foreignKeys, true);
|
||||
// The above map only contains keys that exist in the database, so we re-add the
|
||||
// missing ones with Optional.empty() values for caching.
|
||||
return Maps.asMap(
|
||||
ImmutableSet.copyOf(keys),
|
||||
key -> Optional.ofNullable(existingKeys.get((String) key.getSqlKey())));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A limited size, limited time cache for foreign-keyed entities.
|
||||
*
|
||||
* <p>This is only used to cache foreign-keyed entities for the purposes of checking whether they
|
||||
* exist (and if so, what entity they point to) during a few domain flows. Any other operations on
|
||||
* foreign keys should not use this cache.
|
||||
*
|
||||
* <p>Note that here the key of the {@link LoadingCache} is of type {@code VKey<? extends
|
||||
* EppResource>}, but they are not legal {VKey}s to {@link EppResource}s, whose keys are the SQL
|
||||
* primary keys, i.e. the {@code repoId}s. Instead, their keys are the foreign keys used to query
|
||||
* the database. We use {@link VKey} here because it is a convenient composite class that contains
|
||||
* both the resource type and the foreign key, which are needed to for the query and caching.
|
||||
*
|
||||
* <p>Also note that the value type of this cache is {@link Optional} because the foreign keys in
|
||||
* question are coming from external commands, and thus don't necessarily represent entities in
|
||||
* our system that actually exist. So we cache the fact that they *don't* exist by using
|
||||
* Optional.empty(), then several layers up the EPP command will fail with an error message like
|
||||
* "The contact with given IDs (blah) don't exist."
|
||||
*/
|
||||
@NonFinalForTesting
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
foreignKeyCache = createForeignKeyMapCache(getEppResourceCachingDuration());
|
||||
|
||||
private static LoadingCache<VKey<? extends EppResource>, Optional<MostRecentResource>>
|
||||
createForeignKeyMapCache(Duration expiry) {
|
||||
return CacheUtils.newCacheBuilder(expiry)
|
||||
.maximumSize(getEppResourceMaxCachedEntries())
|
||||
.build(CACHE_LOADER);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setCacheForTest(Optional<Duration> expiry) {
|
||||
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
|
||||
foreignKeyCache = createForeignKeyMapCache(effectiveExpiry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of {@link VKey} to {@link EppResource} instances by class and foreign key strings
|
||||
* that are active at or after the specified moment in time, using the cache if enabled.
|
||||
*
|
||||
* <p>The returned map will omit any keys for which the {@link EppResource} doesn't exist or has
|
||||
* been soft deleted.
|
||||
*
|
||||
* <p>Don't use the cached version of this method unless you really need it for performance
|
||||
* reasons, and are OK with the trade-offs in loss of transactional consistency.
|
||||
*/
|
||||
public static <E extends EppResource> ImmutableMap<String, VKey<E>> loadCached(
|
||||
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
|
||||
if (!RegistryConfig.isEppResourceCachingEnabled()) {
|
||||
return load(clazz, foreignKeys, now);
|
||||
}
|
||||
return foreignKeyCache
|
||||
.getAll(
|
||||
foreignKeys.stream().map(fk -> VKey.createSql(clazz, fk)).collect(toImmutableList()))
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(e -> e.getValue().isPresent() && now.isBefore(e.getValue().get().deletionTime()))
|
||||
.collect(
|
||||
toImmutableMap(
|
||||
e -> (String) e.getKey().getSqlKey(),
|
||||
e -> VKey.createSql(clazz, e.getValue().get().repoId())));
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class MostRecentResource {
|
||||
|
||||
abstract String repoId();
|
||||
|
||||
abstract DateTime deletionTime();
|
||||
|
||||
static MostRecentResource create(String repoId, DateTime deletionTime) {
|
||||
return new AutoValue_ForeignKeyUtils_MostRecentResource(repoId, deletionTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public class OteStats {
|
||||
((DomainCommand.Create)
|
||||
((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
|
||||
.getResourceCommand())
|
||||
.getFullyQualifiedDomainName()
|
||||
.getDomainName()
|
||||
.startsWith(ACE_PREFIX);
|
||||
|
||||
private static final Predicate<EppInput> IS_SUBORDINATE =
|
||||
|
||||
@@ -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.domain.DomainBase;
|
||||
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,13 +58,13 @@ 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;
|
||||
builder =
|
||||
new DomainTransferResponse.Builder()
|
||||
.setFullyQualifiedDomainName(eppResource.getForeignKey())
|
||||
.setDomainName(eppResource.getForeignKey())
|
||||
.setExtendedRegistrationExpirationTime(
|
||||
ADD_EXDATE_STATUSES.contains(domainTransferData.getTransferStatus())
|
||||
? domainTransferData.getTransferredRegistrationExpirationTime()
|
||||
@@ -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 DomainBase);
|
||||
}
|
||||
|
||||
/** 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. */
|
||||
|
||||
+11
-10
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
|
||||
// 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.
|
||||
@@ -14,22 +14,23 @@
|
||||
|
||||
package google.registry.model.annotations;
|
||||
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.IdService;
|
||||
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.
|
||||
* Annotation to indicate an ID field that needs to be allocated by Ofy.
|
||||
*
|
||||
* <p>This means that the entity's <code>@Parent</code> field has to have the value of {@link
|
||||
* EntityGroupRoot#getCrossTldKey}.
|
||||
* <p>This annotation is only used for the field of a {@link google.registry.model.Buildable} class
|
||||
* that was previously annotated by both Ofy's and JPA's {@code @Id} annotations, of which the Ofy
|
||||
* annotation has been removed. The field still needs to be allocated automatically by the builder,
|
||||
* via the {@link IdService#allocateId()}.
|
||||
*
|
||||
* <p>It should be removed after we switch to using SQL to directly allocate IDs.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Inherited
|
||||
public @interface InCrossTld {}
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface OfyIdAllocation {}
|
||||
@@ -18,7 +18,6 @@ import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
@@ -26,31 +25,21 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
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.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.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.annotations.OfyIdAllocation;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.common.TimeOfYear;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||
import google.registry.persistence.BillingVKey.BillingEventVKey;
|
||||
import google.registry.persistence.BillingVKey.BillingRecurrenceVKey;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithLongVKey;
|
||||
import google.registry.persistence.WithVKey;
|
||||
import google.registry.persistence.converter.JodaMoneyType;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -58,11 +47,13 @@ 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.MappedSuperclass;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.Table;
|
||||
import org.hibernate.annotations.Columns;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.joda.money.Money;
|
||||
@@ -76,7 +67,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
/** The reason for the bill, which maps 1:1 to skus in go/registry-billing-skus. */
|
||||
public enum Reason {
|
||||
CREATE(true),
|
||||
@Deprecated // TODO(b/31676071): remove this legacy value once old data is cleaned up.
|
||||
@Deprecated // DO NOT USE THIS REASON. IT REMAINS BECAUSE OF HISTORICAL DATA. SEE b/31676071.
|
||||
ERROR(false),
|
||||
FEE_EARLY_ACCESS(true),
|
||||
RENEW(true),
|
||||
@@ -105,9 +96,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
ALLOCATION,
|
||||
ANCHOR_TENANT,
|
||||
AUTO_RENEW,
|
||||
/**
|
||||
* Landrush billing events are historical only and are no longer created.
|
||||
*/
|
||||
/** Landrush billing events are historical only and are no longer created. */
|
||||
LANDRUSH,
|
||||
/**
|
||||
* This flag is used on create {@link OneTime} billing events for domains that were reserved.
|
||||
@@ -122,7 +111,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* This flag will be added to any {@link OneTime} events that are created via, e.g., an
|
||||
* automated process to expand {@link Recurring} events.
|
||||
*/
|
||||
SYNTHETIC;
|
||||
SYNTHETIC
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,37 +138,30 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
*/
|
||||
NONPREMIUM,
|
||||
/**
|
||||
* This indicates that the renewalPrice in {@link BillingEvent.Recurring} will be used for
|
||||
* domain renewal.
|
||||
* This indicates that the renewalPrice in {@link Recurring} will be used for domain renewal.
|
||||
*
|
||||
* <p>The renewalPrice has a non-null value iff the price behavior is set to "SPECIFIED". This
|
||||
* behavior is used with internal registrations.
|
||||
*/
|
||||
SPECIFIED;
|
||||
SPECIFIED
|
||||
}
|
||||
|
||||
/** Entity id. */
|
||||
@Id @javax.persistence.Id Long id;
|
||||
|
||||
@Parent @DoNotHydrate @Transient Key<DomainHistory> parent;
|
||||
@OfyIdAllocation @Id Long id;
|
||||
|
||||
/** The registrar to bill. */
|
||||
@Index
|
||||
@Column(name = "registrarId", nullable = false)
|
||||
String clientId;
|
||||
|
||||
/** Revision id of the entry in DomainHistory table that ths bill belongs to. */
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
Long domainHistoryRevisionId;
|
||||
|
||||
/** ID of the EPP resource that the bill is for. */
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
String domainRepoId;
|
||||
|
||||
/** When this event was created. For recurring events, this is also the recurrence start time. */
|
||||
@Index
|
||||
@Column(nullable = false)
|
||||
DateTime eventTime;
|
||||
|
||||
@@ -192,17 +174,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@Column(name = "domain_name", nullable = false)
|
||||
String targetId;
|
||||
|
||||
@Nullable
|
||||
Set<Flag> flags;
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
parent =
|
||||
Key.create(
|
||||
Key.create(DomainBase.class, domainRepoId),
|
||||
DomainHistory.class,
|
||||
domainHistoryRevisionId);
|
||||
}
|
||||
@Nullable Set<Flag> flags;
|
||||
|
||||
public String getRegistrarId() {
|
||||
return clientId;
|
||||
@@ -232,14 +204,17 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return targetId;
|
||||
}
|
||||
|
||||
public Key<DomainHistory> getParentKey() {
|
||||
return parent;
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
return new DomainHistoryId(domainRepoId, domainHistoryRevisionId);
|
||||
}
|
||||
|
||||
public ImmutableSet<Flag> getFlags() {
|
||||
return nullToEmptyImmutableCopy(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract VKey<? extends BillingEvent> createVKey();
|
||||
|
||||
/** Override Buildable.asBuilder() to give this method stronger typing. */
|
||||
@Override
|
||||
public abstract Builder<?, ?> asBuilder();
|
||||
@@ -269,16 +244,6 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDomainHistoryRevisionId(long domainHistoryRevisionId) {
|
||||
getInstance().domainHistoryRevisionId = domainHistoryRevisionId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDomainRepoId(String domainRepoId) {
|
||||
getInstance().domainRepoId = domainRepoId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setEventTime(DateTime eventTime) {
|
||||
getInstance().eventTime = eventTime;
|
||||
return thisCastToDerived();
|
||||
@@ -294,14 +259,14 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setParent(DomainHistory parent) {
|
||||
getInstance().parent = Key.create(parent);
|
||||
public B setDomainHistoryId(DomainHistoryId domainHistoryId) {
|
||||
getInstance().domainHistoryRevisionId = domainHistoryId.getId();
|
||||
getInstance().domainRepoId = domainHistoryId.getDomainRepoId();
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setParent(Key<DomainHistory> parentKey) {
|
||||
getInstance().parent = parentKey;
|
||||
return thisCastToDerived();
|
||||
public B setDomainHistory(DomainHistory domainHistory) {
|
||||
return setDomainHistoryId(domainHistory.getDomainHistoryId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -311,32 +276,26 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
checkNotNull(instance.clientId, "Registrar ID must be set");
|
||||
checkNotNull(instance.eventTime, "Event time must be set");
|
||||
checkNotNull(instance.targetId, "Target ID must be set");
|
||||
checkNotNull(instance.parent, "Parent must be set");
|
||||
checkNotNull(instance.parent.getParent(), "parent.getParent() must be set");
|
||||
checkNotNull(
|
||||
instance.parent.getParent().getName(), "parent.getParent().getName() must be set");
|
||||
instance.domainHistoryRevisionId = instance.parent.getId();
|
||||
instance.domainRepoId = instance.parent.getParent().getName();
|
||||
checkNotNull(instance.domainHistoryRevisionId, "Domain History Revision ID must be set");
|
||||
checkNotNull(instance.domainRepoId, "Domain Repo ID must be set");
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
|
||||
/** A one-time billable event. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "BillingEvent")
|
||||
@javax.persistence.Table(
|
||||
@Entity(name = "BillingEvent")
|
||||
@Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "registrarId"),
|
||||
@javax.persistence.Index(columnList = "eventTime"),
|
||||
@javax.persistence.Index(columnList = "billingTime"),
|
||||
@javax.persistence.Index(columnList = "syntheticCreationTime"),
|
||||
@javax.persistence.Index(columnList = "domainRepoId"),
|
||||
@javax.persistence.Index(columnList = "allocationToken"),
|
||||
@javax.persistence.Index(columnList = "cancellation_matching_billing_recurrence_id")
|
||||
@Index(columnList = "registrarId"),
|
||||
@Index(columnList = "eventTime"),
|
||||
@Index(columnList = "billingTime"),
|
||||
@Index(columnList = "syntheticCreationTime"),
|
||||
@Index(columnList = "domainRepoId"),
|
||||
@Index(columnList = "allocationToken"),
|
||||
@Index(columnList = "cancellation_matching_billing_recurrence_id")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_event_id"))
|
||||
@WithLongVKey(compositeKey = true)
|
||||
@WithVKey(Long.class)
|
||||
public static class OneTime extends BillingEvent {
|
||||
|
||||
/** The billable value. */
|
||||
@@ -345,15 +304,13 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
Money cost;
|
||||
|
||||
/** When the cost should be billed. */
|
||||
@Index
|
||||
DateTime billingTime;
|
||||
|
||||
/**
|
||||
* The period in years of the action being billed for, if applicable, otherwise null. Used for
|
||||
* financial reporting.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
Integer periodYears = null;
|
||||
Integer periodYears;
|
||||
|
||||
/**
|
||||
* For {@link Flag#SYNTHETIC} events, when this event was persisted to Datastore (i.e. the
|
||||
@@ -361,11 +318,10 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* needs to be undone, a query on this field will return the complete set of potentially bad
|
||||
* events.
|
||||
*/
|
||||
@Index
|
||||
DateTime syntheticCreationTime;
|
||||
|
||||
/**
|
||||
* For {@link Flag#SYNTHETIC} events, a {@link Key} to the {@link Recurring} from which this
|
||||
* For {@link Flag#SYNTHETIC} events, a {@link VKey} to the {@link Recurring} from which this
|
||||
* {@link OneTime} was created. This is needed in order to properly match billing events against
|
||||
* {@link Cancellation}s.
|
||||
*/
|
||||
@@ -373,20 +329,17 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
VKey<Recurring> cancellationMatchingBillingEvent;
|
||||
|
||||
/**
|
||||
* For {@link Flag#SYNTHETIC} events, the {@link DomainHistory} revision ID of the the {@link
|
||||
* For {@link Flag#SYNTHETIC} events, the {@link DomainHistory} revision ID of the {@link
|
||||
* Recurring} from which this {@link OneTime} was created. This is needed in order to recreate
|
||||
* the {@link VKey} when reading from SQL.
|
||||
*/
|
||||
@Ignore
|
||||
@Column(name = "recurrence_history_revision_id")
|
||||
Long recurringEventHistoryRevisionId;
|
||||
|
||||
/**
|
||||
* The {@link AllocationToken} used in the creation of this event, or null if one was not used.
|
||||
*/
|
||||
@Index
|
||||
@Nullable
|
||||
VKey<AllocationToken> allocationToken;
|
||||
@Nullable VKey<AllocationToken> allocationToken;
|
||||
|
||||
public Money getCost() {
|
||||
return cost;
|
||||
@@ -418,11 +371,11 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
|
||||
@Override
|
||||
public VKey<OneTime> createVKey() {
|
||||
return VKey.create(OneTime.class, getId(), Key.create(this));
|
||||
return createVKey(getId());
|
||||
}
|
||||
|
||||
public static VKey<OneTime> createVKey(Key<OneTime> key) {
|
||||
return VKey.create(OneTime.class, key.getId(), key);
|
||||
public static VKey<OneTime> createVKey(long id) {
|
||||
return VKey.createSql(OneTime.class, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -430,19 +383,6 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
void postLoad() {
|
||||
super.postLoad();
|
||||
if (cancellationMatchingBillingEvent != null) {
|
||||
cancellationMatchingBillingEvent =
|
||||
cancellationMatchingBillingEvent.restoreOfy(
|
||||
DomainBase.class,
|
||||
domainRepoId,
|
||||
DomainHistory.class,
|
||||
recurringEventHistoryRevisionId);
|
||||
}
|
||||
}
|
||||
|
||||
/** A builder for {@link OneTime} since it is immutable. */
|
||||
public static class Builder extends BillingEvent.Builder<OneTime, Builder> {
|
||||
|
||||
@@ -475,11 +415,11 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
}
|
||||
|
||||
public Builder setCancellationMatchingBillingEvent(
|
||||
VKey<Recurring> cancellationMatchingBillingEvent) {
|
||||
getInstance().cancellationMatchingBillingEvent = cancellationMatchingBillingEvent;
|
||||
// getOfyKey() here is safe, recurring billing event VKeys have a valid ofy key.
|
||||
Recurring cancellationMatchingBillingEvent) {
|
||||
getInstance().cancellationMatchingBillingEvent =
|
||||
cancellationMatchingBillingEvent.createVKey();
|
||||
getInstance().recurringEventHistoryRevisionId =
|
||||
cancellationMatchingBillingEvent.getOfyKey().getParent().getId();
|
||||
cancellationMatchingBillingEvent.getDomainHistoryRevisionId();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -511,12 +451,6 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
== (instance.cancellationMatchingBillingEvent != null),
|
||||
"Cancellation matching billing event must be set if and only if the SYNTHETIC flag "
|
||||
+ "is set.");
|
||||
// getOfyKey() here is safe, billing event VKeys have a valid ofy key.
|
||||
checkState(
|
||||
!instance.getFlags().contains(Flag.SYNTHETIC)
|
||||
|| (instance.cancellationMatchingBillingEvent.getOfyKey().getParent().getId()
|
||||
== instance.recurringEventHistoryRevisionId),
|
||||
"Cancellation matching billing event and its history revision ID does not match.");
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
@@ -526,29 +460,26 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* A recurring billable event.
|
||||
*
|
||||
* <p>Unlike {@link OneTime} events, these do not store an explicit cost, since the cost of the
|
||||
* recurring event might change and each time we bill for it we need to bill at the current cost,
|
||||
* recurring event might change and each time we bill for it, we need to bill at the current cost,
|
||||
* not the value that was in use at the time the recurrence was created.
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "BillingRecurrence")
|
||||
@javax.persistence.Table(
|
||||
@Entity(name = "BillingRecurrence")
|
||||
@Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "registrarId"),
|
||||
@javax.persistence.Index(columnList = "eventTime"),
|
||||
@javax.persistence.Index(columnList = "domainRepoId"),
|
||||
@javax.persistence.Index(columnList = "recurrenceEndTime"),
|
||||
@javax.persistence.Index(columnList = "recurrence_time_of_year")
|
||||
@Index(columnList = "registrarId"),
|
||||
@Index(columnList = "eventTime"),
|
||||
@Index(columnList = "domainRepoId"),
|
||||
@Index(columnList = "recurrenceEndTime"),
|
||||
@Index(columnList = "recurrence_time_of_year")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id"))
|
||||
@WithLongVKey(compositeKey = true)
|
||||
@WithVKey(Long.class)
|
||||
public static class Recurring extends BillingEvent {
|
||||
|
||||
/**
|
||||
* The billing event recurs every year between {@link #eventTime} and this time on the
|
||||
* [month, day, time] specified in {@link #recurrenceTimeOfYear}.
|
||||
* The billing event recurs every year between {@link #eventTime} and this time on the [month,
|
||||
* day, time] specified in {@link #recurrenceTimeOfYear}.
|
||||
*/
|
||||
@Index
|
||||
DateTime recurrenceEndTime;
|
||||
|
||||
/**
|
||||
@@ -564,11 +495,9 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* (same day of year, which can be 365 or 366 days later) which is what {@link TimeOfYear} can
|
||||
* model, whereas the billing time is a fixed {@link org.joda.time.Duration} later.
|
||||
*/
|
||||
@Index
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "timeString", column = @Column(name = "recurrence_time_of_year"))
|
||||
})
|
||||
@AttributeOverrides(
|
||||
@AttributeOverride(name = "timeString", column = @Column(name = "recurrence_time_of_year")))
|
||||
TimeOfYear recurrenceTimeOfYear;
|
||||
|
||||
/**
|
||||
@@ -605,11 +534,11 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
|
||||
@Override
|
||||
public VKey<Recurring> createVKey() {
|
||||
return VKey.create(Recurring.class, getId(), Key.create(this));
|
||||
return createVKey(getId());
|
||||
}
|
||||
|
||||
public static VKey<Recurring> createVKey(Key<Recurring> key) {
|
||||
return VKey.create(Recurring.class, key.getId(), key);
|
||||
public static VKey<Recurring> createVKey(Long id) {
|
||||
return VKey.createSql(Recurring.class, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -647,8 +576,8 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
checkNotNull(instance.eventTime);
|
||||
checkNotNull(instance.reason);
|
||||
checkArgument(
|
||||
(instance.renewalPriceBehavior == RenewalPriceBehavior.SPECIFIED)
|
||||
^ (instance.renewalPrice == null),
|
||||
instance.renewalPriceBehavior == RenewalPriceBehavior.SPECIFIED
|
||||
^ instance.renewalPrice == null,
|
||||
"Renewal price can have a value if and only if the renewal price behavior is"
|
||||
+ " SPECIFIED");
|
||||
instance.recurrenceTimeOfYear = TimeOfYear.fromDateTime(instance.eventTime);
|
||||
@@ -666,47 +595,45 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* preserve the immutability of billing events.
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "BillingCancellation")
|
||||
@javax.persistence.Table(
|
||||
@Entity(name = "BillingCancellation")
|
||||
@Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "registrarId"),
|
||||
@javax.persistence.Index(columnList = "eventTime"),
|
||||
@javax.persistence.Index(columnList = "domainRepoId"),
|
||||
@javax.persistence.Index(columnList = "billingTime"),
|
||||
@javax.persistence.Index(columnList = "billing_event_id"),
|
||||
@javax.persistence.Index(columnList = "billing_recurrence_id")
|
||||
@Index(columnList = "registrarId"),
|
||||
@Index(columnList = "eventTime"),
|
||||
@Index(columnList = "domainRepoId"),
|
||||
@Index(columnList = "billingTime"),
|
||||
@Index(columnList = "billing_event_id"),
|
||||
@Index(columnList = "billing_recurrence_id")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id"))
|
||||
@WithLongVKey(compositeKey = true)
|
||||
@WithVKey(Long.class)
|
||||
public static class Cancellation extends BillingEvent {
|
||||
|
||||
/** The billing time of the charge that is being cancelled. */
|
||||
@Index
|
||||
DateTime billingTime;
|
||||
|
||||
/**
|
||||
* The one-time billing event to cancel, or null for autorenew cancellations.
|
||||
*
|
||||
* <p>Although the type is {@link Key} the name "ref" is preserved for historical reasons.
|
||||
* <p>Although the type is {@link VKey} the name "ref" is preserved for historical reasons.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
BillingEventVKey refOneTime = null;
|
||||
@Column(name = "billing_event_id")
|
||||
VKey<OneTime> refOneTime;
|
||||
|
||||
/**
|
||||
* The recurring billing event to cancel, or null for non-autorenew cancellations.
|
||||
*
|
||||
* <p>Although the type is {@link Key} the name "ref" is preserved for historical reasons.
|
||||
* <p>Although the type is {@link VKey} the name "ref" is preserved for historical reasons.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
BillingRecurrenceVKey refRecurring = null;
|
||||
@Column(name = "billing_recurrence_id")
|
||||
VKey<Recurring> refRecurring;
|
||||
|
||||
public DateTime getBillingTime() {
|
||||
return billingTime;
|
||||
}
|
||||
|
||||
public VKey<? extends BillingEvent> getEventKey() {
|
||||
return firstNonNull(refOneTime, refRecurring).createVKey();
|
||||
return firstNonNull(refOneTime, refRecurring);
|
||||
}
|
||||
|
||||
/** The mapping from billable grace period types to originating billing event reasons. */
|
||||
@@ -723,22 +650,23 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* using the supplied targetId and deriving other metadata (clientId, billing time, and the
|
||||
* cancellation reason) from the grace period.
|
||||
*/
|
||||
public static BillingEvent.Cancellation forGracePeriod(
|
||||
public static Cancellation forGracePeriod(
|
||||
GracePeriod gracePeriod,
|
||||
DateTime eventTime,
|
||||
Key<DomainHistory> domainHistoryKey,
|
||||
DomainHistoryId domainHistoryId,
|
||||
String targetId) {
|
||||
checkArgument(gracePeriod.hasBillingEvent(),
|
||||
checkArgument(
|
||||
gracePeriod.hasBillingEvent(),
|
||||
"Cannot create cancellation for grace period without billing event");
|
||||
BillingEvent.Cancellation.Builder builder =
|
||||
new BillingEvent.Cancellation.Builder()
|
||||
Builder builder =
|
||||
new Builder()
|
||||
.setReason(checkNotNull(GRACE_PERIOD_TO_REASON.get(gracePeriod.getType())))
|
||||
.setTargetId(targetId)
|
||||
.setRegistrarId(gracePeriod.getRegistrarId())
|
||||
.setEventTime(eventTime)
|
||||
// The charge being cancelled will take place at the grace period's expiration time.
|
||||
.setBillingTime(gracePeriod.getExpirationTime())
|
||||
.setParent(domainHistoryKey);
|
||||
.setDomainHistoryId(domainHistoryId);
|
||||
// Set the grace period's billing event using the appropriate Cancellation builder method.
|
||||
if (gracePeriod.getOneTimeBillingEvent() != null) {
|
||||
builder.setOneTimeEventKey(gracePeriod.getOneTimeBillingEvent());
|
||||
@@ -750,11 +678,11 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
|
||||
@Override
|
||||
public VKey<Cancellation> createVKey() {
|
||||
return VKey.create(Cancellation.class, getId(), Key.create(this));
|
||||
return createVKey(getId());
|
||||
}
|
||||
|
||||
public static VKey<Cancellation> createVKey(Key<Cancellation> key) {
|
||||
return VKey.create(Cancellation.class, key.getId(), key);
|
||||
public static VKey<Cancellation> createVKey(long id) {
|
||||
return VKey.createSql(Cancellation.class, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -776,13 +704,13 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOneTimeEventKey(VKey<BillingEvent.OneTime> eventKey) {
|
||||
getInstance().refOneTime = BillingEventVKey.create(eventKey);
|
||||
public Builder setOneTimeEventKey(VKey<OneTime> eventKey) {
|
||||
getInstance().refOneTime = eventKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRecurringEventKey(VKey<BillingEvent.Recurring> eventKey) {
|
||||
getInstance().refRecurring = BillingRecurrenceVKey.create(eventKey);
|
||||
public Builder setRecurringEventKey(VKey<Recurring> eventKey) {
|
||||
getInstance().refRecurring = eventKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -791,115 +719,11 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
Cancellation instance = getInstance();
|
||||
checkNotNull(instance.billingTime, "Must set billing time");
|
||||
checkNotNull(instance.reason, "Must set reason");
|
||||
checkState((instance.refOneTime == null) != (instance.refRecurring == null),
|
||||
checkState(
|
||||
(instance.refOneTime == null) != (instance.refRecurring == null),
|
||||
"Cancellations must have exactly one billing event key set");
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** An event representing a modification of an existing one-time billing event. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@WithLongVKey(compositeKey = true)
|
||||
public static class Modification extends BillingEvent {
|
||||
|
||||
/** The change in cost that should be applied to the original billing event. */
|
||||
Money cost;
|
||||
|
||||
/** The one-time billing event to modify. */
|
||||
Key<BillingEvent.OneTime> eventRef;
|
||||
|
||||
/**
|
||||
* Description of the modification (and presumably why it was issued). This text may appear as a
|
||||
* line item on an invoice or report about such modifications.
|
||||
*/
|
||||
String description;
|
||||
|
||||
public Money getCost() {
|
||||
return cost;
|
||||
}
|
||||
|
||||
public Key<BillingEvent.OneTime> getEventKey() {
|
||||
return eventRef;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public VKey<Modification> createVKey() {
|
||||
return VKey.create(Modification.class, getId(), Key.create(this));
|
||||
}
|
||||
|
||||
public static VKey<Modification> createVKey(Key<Modification> key) {
|
||||
return VKey.create(Modification.class, key.getId(), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Modification billing event which is a refund of the given OneTime billing event
|
||||
* and that is parented off the given HistoryEntry.
|
||||
*
|
||||
* <p>Note that this method may appear to be unused most of the time, but it is kept around
|
||||
* because it is needed by one-off scrap tools that need to make billing adjustments.
|
||||
*/
|
||||
public static Modification createRefundFor(
|
||||
OneTime billingEvent, DomainHistory historyEntry, String description) {
|
||||
return new Builder()
|
||||
.setRegistrarId(billingEvent.getRegistrarId())
|
||||
.setFlags(billingEvent.getFlags())
|
||||
.setReason(billingEvent.getReason())
|
||||
.setTargetId(billingEvent.getTargetId())
|
||||
.setEventKey(Key.create(billingEvent))
|
||||
.setEventTime(historyEntry.getModificationTime())
|
||||
.setDescription(description)
|
||||
.setCost(billingEvent.getCost().negated())
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** A builder for {@link Modification} since it is immutable. */
|
||||
public static class Builder extends BillingEvent.Builder<Modification, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
private Builder(Modification instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
public Builder setCost(Money cost) {
|
||||
getInstance().cost = cost;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setEventKey(Key<BillingEvent.OneTime> eventKey) {
|
||||
getInstance().eventRef = eventKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDescription(String description) {
|
||||
getInstance().description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modification build() {
|
||||
Modification instance = getInstance();
|
||||
checkNotNull(instance.reason);
|
||||
checkNotNull(instance.eventRef);
|
||||
BillingEvent.OneTime billingEvent =
|
||||
tm().transact(() -> tm().loadByKey(VKey.from(instance.eventRef)));
|
||||
checkArgument(
|
||||
Objects.equals(instance.cost.getCurrencyUnit(), billingEvent.cost.getCurrencyUnit()),
|
||||
"Referenced billing event is in a different currency");
|
||||
return super.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,28 +19,28 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.GracePeriod;
|
||||
import google.registry.model.domain.GracePeriod.GracePeriodHistory;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.secdns.DomainDsDataHistory;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
|
||||
/**
|
||||
* Utilities for managing an alternative JPA entity model optimized for bulk loading multi-level
|
||||
* entities such as {@link DomainBase} and {@link DomainHistory}.
|
||||
* entities such as {@link Domain} and {@link DomainHistory}.
|
||||
*
|
||||
* <p>In a bulk query for a multi-level JPA entity type, the JPA framework only generates a bulk
|
||||
* query (SELECT * FROM table) for the base table. Then, for each row in the base table, additional
|
||||
* queries are issued to load associated rows in child tables. This can be very slow when an entity
|
||||
* type has multiple child tables.
|
||||
*
|
||||
* <p>We have defined an alternative entity model for {@code DomainBase} and {@code DomainHistory},
|
||||
* <p>We have defined an alternative entity model for {@link Domain} and {@link DomainHistory},
|
||||
* where the base table as well as the child tables are mapped to single-level entity types. The
|
||||
* idea is to load each of these types using a bulk query, and assemble them into the target type in
|
||||
* memory in a pipeline. The main use case is Datastore-Cloud SQL validation during the Registry
|
||||
@@ -53,8 +53,8 @@ public class BulkQueryEntities {
|
||||
*/
|
||||
public static final ImmutableMap<String, String> JPA_ENTITIES_REPLACEMENTS =
|
||||
ImmutableMap.of(
|
||||
DomainBase.class.getCanonicalName(),
|
||||
DomainBaseLite.class.getCanonicalName(),
|
||||
Domain.class.getCanonicalName(),
|
||||
DomainLite.class.getCanonicalName(),
|
||||
DomainHistory.class.getCanonicalName(),
|
||||
DomainHistoryLite.class.getCanonicalName());
|
||||
|
||||
@@ -64,34 +64,34 @@ public class BulkQueryEntities {
|
||||
ImmutableList.of(
|
||||
DomainHost.class.getCanonicalName(), DomainHistoryHost.class.getCanonicalName());
|
||||
|
||||
public static DomainBase assembleDomainBase(
|
||||
DomainBaseLite domainBaseLite,
|
||||
public static Domain assembleDomain(
|
||||
DomainLite domainLite,
|
||||
ImmutableSet<GracePeriod> gracePeriods,
|
||||
ImmutableSet<DelegationSignerData> delegationSignerData,
|
||||
ImmutableSet<VKey<HostResource>> nsHosts) {
|
||||
DomainBase.Builder builder = new DomainBase.Builder();
|
||||
builder.copyFrom(domainBaseLite);
|
||||
ImmutableSet<DomainDsData> domainDsData,
|
||||
ImmutableSet<VKey<Host>> nsHosts) {
|
||||
Domain.Builder builder = new Domain.Builder();
|
||||
builder.copyFrom(domainLite);
|
||||
builder.setGracePeriods(gracePeriods);
|
||||
builder.setDsData(delegationSignerData);
|
||||
builder.setDsData(domainDsData);
|
||||
builder.setNameservers(nsHosts);
|
||||
// Restore the original update timestamp (this gets cleared when we set nameservers or DS data).
|
||||
builder.setUpdateTimestamp(domainBaseLite.getUpdateTimestamp());
|
||||
builder.setUpdateTimestamp(domainLite.getUpdateTimestamp());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static DomainHistory assembleDomainHistory(
|
||||
DomainHistoryLite domainHistoryLite,
|
||||
ImmutableSet<DomainDsDataHistory> dsDataHistories,
|
||||
ImmutableSet<VKey<HostResource>> domainHistoryHosts,
|
||||
ImmutableSet<VKey<Host>> domainHistoryHosts,
|
||||
ImmutableSet<GracePeriodHistory> gracePeriodHistories,
|
||||
ImmutableSet<DomainTransactionRecord> transactionRecords) {
|
||||
DomainHistory.Builder builder = new DomainHistory.Builder();
|
||||
builder.copyFrom(domainHistoryLite);
|
||||
DomainContent rawDomainContent = domainHistoryLite.domainContent;
|
||||
if (rawDomainContent != null) {
|
||||
DomainContent newDomainContent =
|
||||
DomainBase rawDomainBase = domainHistoryLite.domainBase;
|
||||
if (rawDomainBase != null) {
|
||||
DomainBase newDomainBase =
|
||||
domainHistoryLite
|
||||
.domainContent
|
||||
.domainBase
|
||||
.asBuilder()
|
||||
.setNameservers(domainHistoryHosts)
|
||||
.setGracePeriods(
|
||||
@@ -99,14 +99,12 @@ public class BulkQueryEntities {
|
||||
.map(GracePeriod::createFromHistory)
|
||||
.collect(toImmutableSet()))
|
||||
.setDsData(
|
||||
dsDataHistories.stream()
|
||||
.map(DelegationSignerData::create)
|
||||
.collect(toImmutableSet()))
|
||||
dsDataHistories.stream().map(DomainDsData::create).collect(toImmutableSet()))
|
||||
// Restore the original update timestamp (this gets cleared when we set nameservers or
|
||||
// DS data).
|
||||
.setUpdateTimestamp(domainHistoryLite.domainContent.getUpdateTimestamp())
|
||||
.setUpdateTimestamp(domainHistoryLite.domainBase.getUpdateTimestamp())
|
||||
.build();
|
||||
builder.setDomain(newDomainContent);
|
||||
builder.setDomain(newDomainBase);
|
||||
}
|
||||
return builder.buildAndAssemble(
|
||||
dsDataHistories, domainHistoryHosts, gracePeriodHistories, transactionRecords);
|
||||
|
||||
@@ -16,7 +16,7 @@ package google.registry.model.bulkquery;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Access;
|
||||
@@ -44,8 +44,8 @@ public class DomainHistoryHost implements Serializable {
|
||||
return new DomainHistoryId(domainHistoryDomainRepoId, domainHistoryHistoryRevisionId);
|
||||
}
|
||||
|
||||
public VKey<HostResource> getHostVKey() {
|
||||
return VKey.create(HostResource.class, hostRepoId);
|
||||
public VKey<Host> getHostVKey() {
|
||||
return VKey.create(Host.class, hostRepoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
package google.registry.model.bulkquery;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
import google.registry.model.domain.Period;
|
||||
@@ -49,9 +49,9 @@ import javax.persistence.PostLoad;
|
||||
@IdClass(DomainHistoryId.class)
|
||||
public class DomainHistoryLite extends HistoryEntry {
|
||||
|
||||
// Store DomainContent instead of DomainBase so we don't pick up its @Id
|
||||
// Store DomainBase instead of Domain so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
@Nullable DomainContent domainContent;
|
||||
@Nullable DomainBase domainBase;
|
||||
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
@@ -64,7 +64,7 @@ public class DomainHistoryLite extends HistoryEntry {
|
||||
/** This method is private because it is only used by Hibernate. */
|
||||
@SuppressWarnings("unused")
|
||||
private void setDomainRepoId(String domainRepoId) {
|
||||
parent = Key.create(DomainBase.class, domainRepoId);
|
||||
parent = Key.create(Domain.class, domainRepoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -101,9 +101,9 @@ public class DomainHistoryLite extends HistoryEntry {
|
||||
return super.getId();
|
||||
}
|
||||
|
||||
/** The key to the {@link DomainBase} this is based off of. */
|
||||
public VKey<DomainBase> getParentVKey() {
|
||||
return VKey.create(DomainBase.class, getDomainRepoId());
|
||||
/** The key to the {@link Domain} this is based off of. */
|
||||
public VKey<Domain> getParentVKey() {
|
||||
return VKey.create(Domain.class, getDomainRepoId());
|
||||
}
|
||||
|
||||
public DomainHistoryId getDomainHistoryId() {
|
||||
@@ -112,14 +112,14 @@ public class DomainHistoryLite extends HistoryEntry {
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
if (domainContent == null) {
|
||||
if (domainBase == null) {
|
||||
return;
|
||||
}
|
||||
// See inline comments in DomainHistory.postLoad for reasons for the following lines.
|
||||
if (domainContent.getDomainName() == null) {
|
||||
domainContent = null;
|
||||
} else if (domainContent.getRepoId() == null) {
|
||||
domainContent.setRepoId(parent.getName());
|
||||
if (domainBase.getDomainName() == null) {
|
||||
domainBase = null;
|
||||
} else if (domainBase.getRepoId() == null) {
|
||||
domainBase.setRepoId(parent.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
package google.registry.model.bulkquery;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.Serializable;
|
||||
import javax.persistence.Access;
|
||||
@@ -40,8 +40,8 @@ public class DomainHost implements Serializable {
|
||||
return domainRepoId;
|
||||
}
|
||||
|
||||
public VKey<HostResource> getHostVKey() {
|
||||
return VKey.create(HostResource.class, hostRepoId);
|
||||
public VKey<Host> getHostVKey() {
|
||||
return VKey.create(Host.class, hostRepoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+9
-9
@@ -14,26 +14,26 @@
|
||||
|
||||
package google.registry.model.bulkquery;
|
||||
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainContent;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import google.registry.persistence.WithVKey;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.Entity;
|
||||
|
||||
/**
|
||||
* A 'light' version of {@link DomainBase} with only base table ("Domain") attributes, which allows
|
||||
* fast bulk loading. They are used in in-memory assembly of {@code DomainBase} instances along with
|
||||
* A 'light' version of {@link Domain} with only base table ("Domain") attributes, which allows fast
|
||||
* bulk loading. They are used in in-memory assembly of {@code Domain} instances along with
|
||||
* bulk-loaded child entities ({@code GracePeriod} etc). The in-memory assembly achieves much higher
|
||||
* performance than loading {@code DomainBase} directly.
|
||||
* performance than loading {@code Domain} directly.
|
||||
*
|
||||
* <p>Please refer to {@link BulkQueryEntities} for more information.
|
||||
*/
|
||||
@Entity(name = "Domain")
|
||||
@WithStringVKey
|
||||
@WithVKey(String.class)
|
||||
@Access(AccessType.FIELD)
|
||||
public class DomainBaseLite extends DomainContent {
|
||||
public class DomainLite extends DomainBase {
|
||||
|
||||
@Override
|
||||
@javax.persistence.Id
|
||||
@@ -42,7 +42,7 @@ public class DomainBaseLite extends DomainContent {
|
||||
return super.getRepoId();
|
||||
}
|
||||
|
||||
public static VKey<DomainBaseLite> createVKey(String repoId) {
|
||||
return VKey.createSql(DomainBaseLite.class, repoId);
|
||||
public static VKey<DomainLite> createVKey(String repoId) {
|
||||
return VKey.createSql(DomainLite.class, repoId);
|
||||
}
|
||||
}
|
||||
@@ -53,12 +53,16 @@ public class ClassPathManager {
|
||||
}
|
||||
|
||||
public static <T> Class<T> getClass(String className) {
|
||||
checkArgument(CLASS_REGISTRY.containsKey(className), "Class not found in class registry");
|
||||
checkArgument(
|
||||
CLASS_REGISTRY.containsKey(className), "Class %s not found in class registry", className);
|
||||
return (Class<T>) CLASS_REGISTRY.get(className);
|
||||
}
|
||||
|
||||
public static <T> String getClassName(Class<T> clazz) {
|
||||
checkArgument(CLASS_NAME_REGISTRY.containsKey(clazz), "Class not found in class name registry");
|
||||
checkArgument(
|
||||
CLASS_NAME_REGISTRY.containsKey(clazz),
|
||||
"Class %s not found in class name registry",
|
||||
clazz.getSimpleName());
|
||||
return CLASS_NAME_REGISTRY.get(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ public class DatabaseMigrationStateSchedule extends CrossTldSingleton {
|
||||
MigrationState.DATASTORE_ONLY,
|
||||
"migrationTransitionMap must start with DATASTORE_ONLY");
|
||||
validateTransitionAtCurrentTime(transitions);
|
||||
jpaTm().putWithoutBackup(new DatabaseMigrationStateSchedule(transitions));
|
||||
jpaTm().put(new DatabaseMigrationStateSchedule(transitions));
|
||||
CACHE.invalidateAll();
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// 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;
|
||||
|
||||
/** Permissions that users may have in the UI, either per-registrar or globally. */
|
||||
public enum ConsolePermission {
|
||||
/** Add, update, or remove other console users. */
|
||||
MANAGE_USERS,
|
||||
/** Add, update, or remove registrars. */
|
||||
MANAGE_REGISTRARS,
|
||||
/** Manage related registrars, e.g. when one registrar owns another. */
|
||||
MANAGE_ACCREDITATION,
|
||||
/** Set up the EPP connection (e.g. certs). */
|
||||
CONFIGURE_EPP_CONNECTION,
|
||||
/** Retrieve the unredacted registrant email from a domain. */
|
||||
GET_REGISTRANT_EMAIL,
|
||||
/** Suspend a domain for compliance (non-URS) reasons. */
|
||||
SUSPEND_DOMAIN,
|
||||
/** Suspend a domain for the Uniform Rapid Suspension process. */
|
||||
SUSPEND_DOMAIN_URS,
|
||||
/** Download a list of domains under management. */
|
||||
DOWNLOAD_DOMAINS,
|
||||
/** Change the password for a registrar. */
|
||||
CHANGE_NOMULUS_PASSWORD,
|
||||
/** View all possible TLDs. */
|
||||
VIEW_TLD_PORTFOLIO,
|
||||
/** Onboarding the registrar(s) to extra programs like registry locking. */
|
||||
ONBOARD_ADDITIONAL_PROGRAMS,
|
||||
/** Execute arbitrary EPP commands through the UI. */
|
||||
EXECUTE_EPP_COMMANDS,
|
||||
/** Send messages to the registry support team. */
|
||||
CONTACT_SUPPORT,
|
||||
/** Access billing and payment details for a registrar. */
|
||||
ACCESS_BILLING_DETAILS,
|
||||
/** Access any available documentation about the registry. */
|
||||
ACCESS_DOCUMENTATION,
|
||||
/** Change the documentation available in the UI about the registry. */
|
||||
MANAGE_DOCUMENTATION,
|
||||
/** Viewing the onboarding status of a registrar on a TLD. */
|
||||
CHECK_ONBOARDING_STATUS,
|
||||
/** View premium and/or reserved lists for a TLD. */
|
||||
VIEW_PREMIUM_RESERVED_LISTS,
|
||||
/** Sign and/or change legal documents like RRAs. */
|
||||
UPLOAD_CONTRACTS,
|
||||
/** Viewing legal documents like RRAs. */
|
||||
ACCESS_CONTRACTS,
|
||||
/** View analytics on operations and billing data (possibly TBD). */
|
||||
VIEW_OPERATIONAL_DATA,
|
||||
/** Create announcements that can be displayed in the UI. */
|
||||
SEND_ANNOUNCEMENTS,
|
||||
/** View announcements in the UI. */
|
||||
VIEW_ANNOUNCEMENTS,
|
||||
/** Viewing a record of actions performed in the UI for a particular registrar. */
|
||||
VIEW_ACTIVITY_LOG,
|
||||
/** View and perform registry locks. */
|
||||
REGISTRY_LOCK
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user