mirror of
https://github.com/google/nomulus
synced 2026-02-02 19:12:27 +00:00
Compare commits
78 Commits
nomulus-20
...
nomulus-20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82092b3516 | ||
|
|
0746d28e0c | ||
|
|
aaa311ec40 | ||
|
|
addef17904 | ||
|
|
8fe3c08069 | ||
|
|
5dc796b1f7 | ||
|
|
8bddf35d0d | ||
|
|
7b9c16ca3e | ||
|
|
1ab077d267 | ||
|
|
ca65fbcc79 | ||
|
|
0cfa7f8081 | ||
|
|
9e31047c3a | ||
|
|
b7c2e8fba5 | ||
|
|
a299df3005 | ||
|
|
a9b35c163d | ||
|
|
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 |
@@ -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'
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// 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.batch;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.token.PackagePromotion;
|
||||
import google.registry.request.Action;
|
||||
import google.registry.request.Action.Service;
|
||||
import google.registry.request.auth.Auth;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An action that checks all {@link PackagePromotion} objects for compliance with their max create
|
||||
* limit.
|
||||
*/
|
||||
@Action(
|
||||
service = Service.BACKEND,
|
||||
path = CheckPackagesComplianceAction.PATH,
|
||||
auth = Auth.AUTH_INTERNAL_OR_ADMIN)
|
||||
public class CheckPackagesComplianceAction implements Runnable {
|
||||
|
||||
public static final String PATH = "/_dr/task/checkPackagesCompliance";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
tm().transact(
|
||||
() -> {
|
||||
ImmutableList<PackagePromotion> packages = tm().loadAllOf(PackagePromotion.class);
|
||||
ImmutableList.Builder<PackagePromotion> packagesOverCreateLimit =
|
||||
new ImmutableList.Builder<>();
|
||||
for (PackagePromotion packagePromo : packages) {
|
||||
List<DomainHistory> creates =
|
||||
jpaTm()
|
||||
.query(
|
||||
"FROM DomainHistory WHERE current_package_token = :token AND"
|
||||
+ " modificationTime >= :lastBilling AND type = 'DOMAIN_CREATE'",
|
||||
DomainHistory.class)
|
||||
.setParameter("token", packagePromo.getToken().getSqlKey().toString())
|
||||
.setParameter(
|
||||
"lastBilling", packagePromo.getNextBillingDate().minusYears(1))
|
||||
.getResultList();
|
||||
|
||||
if (creates.size() > packagePromo.getMaxCreates()) {
|
||||
int overage = creates.size() - packagePromo.getMaxCreates();
|
||||
logger.atInfo().log(
|
||||
"Package with package token %s has exceeded their max domain creation limit"
|
||||
+ " by %d name(s).",
|
||||
packagePromo.getToken().getSqlKey(), overage);
|
||||
packagesOverCreateLimit.add(packagePromo);
|
||||
}
|
||||
}
|
||||
if (packagesOverCreateLimit.build().isEmpty()) {
|
||||
logger.atInfo().log("Found no packages over their create limit.");
|
||||
} else {
|
||||
logger.atInfo().log(
|
||||
"Found %d packages over their create limit.",
|
||||
packagesOverCreateLimit.build().size());
|
||||
// TODO(sarahbot@) Send email to registrar and registry informing of creation
|
||||
// overage once email template is finalized.
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.flows.poll.PollFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResourceUtils;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
@@ -43,8 +43,8 @@ import google.registry.util.Clock;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Hard deletes load-test ContactResources, Hosts, their subordinate history entries, and the
|
||||
* associated ForeignKey and EppResourceIndex entities.
|
||||
* Hard deletes load-test Contacts, Hosts, their subordinate history entries, and the associated
|
||||
* ForeignKey entities.
|
||||
*
|
||||
* <p>This only deletes contacts and hosts, NOT domains. To delete domains, use {@link
|
||||
* DeleteProberDataAction} and pass it the TLD(s) that the load test domains were created on. Note
|
||||
@@ -92,7 +92,7 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
tm().transact(
|
||||
() -> {
|
||||
LOAD_TEST_REGISTRARS.forEach(this::deletePollMessages);
|
||||
tm().loadAllOfStream(ContactResource.class).forEach(this::deleteContact);
|
||||
tm().loadAllOfStream(Contact.class).forEach(this::deleteContact);
|
||||
tm().loadAllOfStream(Host.class).forEach(this::deleteHost);
|
||||
});
|
||||
}
|
||||
@@ -108,7 +108,7 @@ public class DeleteLoadTestDataAction implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteContact(ContactResource contact) {
|
||||
private void deleteContact(Contact contact) {
|
||||
if (!LOAD_TEST_REGISTRARS.contains(contact.getPersistedCurrentSponsorRegistrarId())) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.batch.BatchModule.PARAM_DRY_RUN;
|
||||
import static google.registry.config.RegistryEnvironment.PRODUCTION;
|
||||
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_DELETE;
|
||||
import static google.registry.model.tld.Registries.getTldsOfType;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
@@ -54,7 +53,7 @@ import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* Deletes all prober {@link Domain}s and their subordinate history entries, poll messages, and
|
||||
* billing events, along with their ForeignKeyDomainIndex and EppResourceIndex entities.
|
||||
* 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";
|
||||
@@ -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()
|
||||
@@ -267,8 +266,6 @@ public class DeleteProberDataAction implements Runnable {
|
||||
// messages, or auto-renews because those will all be hard-deleted the next time the job runs
|
||||
// anyway.
|
||||
tm().putAll(ImmutableList.of(deletedDomain, historyEntry));
|
||||
// updating foreign keys is a no-op in SQL
|
||||
updateForeignKeyIndexDeletionTime(deletedDomain);
|
||||
dnsQueue.addDomainRefreshTask(deletedDomain.getDomainName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ package google.registry.beam.common;
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import java.io.Serializable;
|
||||
@@ -46,8 +46,7 @@ public class JpaDemoPipeline implements Serializable {
|
||||
.apply(
|
||||
"Read contacts",
|
||||
RegistryJpaIO.read(
|
||||
() -> CriteriaQueryBuilder.create(ContactResource.class).build(),
|
||||
ContactResource::getRepoId))
|
||||
() -> CriteriaQueryBuilder.create(Contact.class).build(), Contact::getRepoId))
|
||||
.apply(
|
||||
"Count Contacts",
|
||||
ParDo.of(
|
||||
|
||||
@@ -16,6 +16,7 @@ package google.registry.beam.common;
|
||||
|
||||
import google.registry.beam.common.RegistryJpaIO.Write;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.persistence.PersistenceModule.JpaTransactionManagerType;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import java.util.Objects;
|
||||
@@ -65,6 +66,17 @@ public interface RegistryPipelineOptions extends GcpOptions {
|
||||
|
||||
void setSqlWriteShards(int maxConcurrentSqlWriters);
|
||||
|
||||
@DeleteAfterMigration
|
||||
@Description(
|
||||
"Whether to use self allocated primary IDs when building entities. This should only be used"
|
||||
+ " when the IDs are not significant and the resulting entities are not persisted back to"
|
||||
+ " the database. Use with caution as self allocated IDs are not unique across workers,"
|
||||
+ " and persisting entities with these IDs can be dangerous.")
|
||||
@Default.Boolean(false)
|
||||
boolean getUseSelfAllocatedId();
|
||||
|
||||
void setUseSelfAllocatedId(boolean useSelfAllocatedId);
|
||||
|
||||
static RegistryPipelineComponent toRegistryPipelineComponent(RegistryPipelineOptions options) {
|
||||
return DaggerRegistryPipelineComponent.builder()
|
||||
.isolationOverride(options.getIsolationOverride())
|
||||
|
||||
@@ -22,6 +22,8 @@ import dagger.Lazy;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.config.SystemPropertySetter;
|
||||
import google.registry.model.AppEngineEnvironment;
|
||||
import google.registry.model.IdService;
|
||||
import google.registry.model.IdService.SelfAllocatedIdSupplier;
|
||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||
import org.apache.beam.sdk.harness.JvmInitializer;
|
||||
@@ -65,12 +67,20 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
|
||||
transactionManagerLazy = registryPipelineComponent.getJpaTransactionManager();
|
||||
}
|
||||
TransactionManagerFactory.setJpaTmOnBeamWorker(transactionManagerLazy::get);
|
||||
// Masquerade all threads as App Engine threads so we can create Ofy keys in the pipeline. Also
|
||||
// Masquerade all threads as App Engine threads, so we can create Ofy keys in the pipeline. Also
|
||||
// loads all ofy entities.
|
||||
new AppEngineEnvironment("s~" + registryPipelineComponent.getProjectId())
|
||||
.setEnvironmentForAllThreads();
|
||||
// Set the system property so that we can call IdService.allocateId() without access to
|
||||
// datastore.
|
||||
SystemPropertySetter.PRODUCTION_IMPL.setProperty(PROPERTY, "true");
|
||||
// Use self-allocated IDs if requested. Note that this inevitably results in duplicate IDs from
|
||||
// multiple workers, which can also collide with existing IDs in the database. So they cannot be
|
||||
// dependent upon for comparison or anything significant. The resulting entities can never be
|
||||
// persisted back into the database. This is a stop-gap measure that should only be used when
|
||||
// you need to create Buildables in Beam, but do not have control over how the IDs are
|
||||
// allocated, and you don't care about the generated IDs as long
|
||||
// as you can build the entities.
|
||||
if (registryOptions.getUseSelfAllocatedId()) {
|
||||
IdService.setIdSupplier(SelfAllocatedIdSupplier.getInstance());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ import google.registry.config.CredentialModule;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.gcs.GcsUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.host.Host;
|
||||
@@ -128,7 +128,7 @@ import org.joda.time.DateTime;
|
||||
* <h2>{@link EppResource}</h2>
|
||||
*
|
||||
* All EPP resources are loaded from the corresponding {@link HistoryEntry}, which has the resource
|
||||
* embedded. In general we find most recent history entry before watermark and filter out the ones
|
||||
* embedded. In general, we find most recent history entry before watermark and filter out the ones
|
||||
* that are soft-deleted by watermark. The history is emitted as pairs of (resource repo ID: history
|
||||
* revision ID) from the SQL query.
|
||||
*
|
||||
@@ -140,7 +140,7 @@ import org.joda.time.DateTime;
|
||||
* pairs of (contact/host repo ID: pending deposit) for all RDE pending deposits for further
|
||||
* processing.
|
||||
*
|
||||
* <h3>{@link ContactResource}</h3>
|
||||
* <h3>{@link Contact}</h3>
|
||||
*
|
||||
* We first join most recent contact histories, represented by (contact repo ID: contact history
|
||||
* revision ID) pairs, with referenced contacts, represented by (contact repo ID: pending deposit)
|
||||
@@ -150,13 +150,13 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* <h3>{@link Host}</h3>
|
||||
*
|
||||
* Similar to {@link ContactResource}, we join the most recent host history with referenced hosts to
|
||||
* find most recent referenced hosts. For external hosts we do the same treatment as we did on
|
||||
* contacts and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need
|
||||
* to find the superordinate domain in order to properly handle pending transfer in the deposit as
|
||||
* well. So we first find the superordinate domain repo ID from the host and join the (superordinate
|
||||
* domain repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain
|
||||
* repo ID: revision ID) pair obtained from the domain history query in order to map the host at
|
||||
* Similar to {@link Contact}, we join the most recent host history with referenced hosts to find
|
||||
* most recent referenced hosts. For external hosts we do the same treatment as we did on contacts
|
||||
* and obtain the (pending deposit: deposit fragment) pairs. For subordinate hosts, we need to find
|
||||
* the superordinate domain in order to properly handle pending transfer in the deposit as well. So
|
||||
* we first find the superordinate domain repo ID from the host and join the (superordinate domain
|
||||
* repo ID: (subordinate host repo ID: (pending deposit: revision ID))) pair with the (domain repo
|
||||
* ID: revision ID) pair obtained from the domain history query in order to map the host at
|
||||
* watermark to the domain at watermark. We then proceed to create the (pending deposit: deposit
|
||||
* fragment) pair for subordinate hosts using the added domain information.
|
||||
*
|
||||
@@ -164,7 +164,7 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* The (pending deposit: deposit fragment) pairs from different resources are combined and grouped
|
||||
* by pending deposit. For each pending deposit, all the relevant deposit fragments are written into
|
||||
* a encrypted file stored on GCS. The filename is uniquely determined by the Beam job ID so there
|
||||
* an encrypted file stored on GCS. The filename is uniquely determined by the Beam job ID so there
|
||||
* is no need to lock the GCS write operation to prevent stomping. The cursor for staging the
|
||||
* pending deposit is then rolled forward, and the next action is enqueued. The latter two
|
||||
* operations are performed in a transaction so the cursor is rolled back if enqueueing failed.
|
||||
@@ -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,10 +466,10 @@ public class RdePipeline implements Serializable {
|
||||
private PCollectionTuple processDomainHistories(PCollection<KV<String, Long>> domainHistories) {
|
||||
Counter activeDomainCounter = Metrics.counter("RDE", "ActiveDomainBase");
|
||||
Counter domainFragmentCounter = Metrics.counter("RDE", "DomainFragment");
|
||||
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContactResource");
|
||||
Counter referencedContactCounter = Metrics.counter("RDE", "ReferencedContact");
|
||||
Counter referencedHostCounter = Metrics.counter("RDE", "ReferencedHost");
|
||||
return domainHistories.apply(
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced ContactResource and Host",
|
||||
"Map DomainHistory to DepositFragment " + "and emit referenced Contact and Host",
|
||||
ParDo.of(
|
||||
new DoFn<KV<String, Long>, KV<PendingDeposit, DepositFragment>>() {
|
||||
@ProcessElement
|
||||
@@ -533,17 +533,17 @@ public class RdePipeline implements Serializable {
|
||||
PCollection<KV<String, PendingDeposit>> referencedContacts,
|
||||
PCollection<KV<String, Long>> contactHistories) {
|
||||
Counter contactFragmentCounter = Metrics.counter("RDE", "ContactFragment");
|
||||
return removeUnreferencedResource(referencedContacts, contactHistories, ContactResource.class)
|
||||
return removeUnreferencedResource(referencedContacts, contactHistories, Contact.class)
|
||||
.apply(
|
||||
"Map ContactResource to DepositFragment",
|
||||
"Map Contact to DepositFragment",
|
||||
FlatMapElements.into(
|
||||
kvs(
|
||||
TypeDescriptor.of(PendingDeposit.class),
|
||||
TypeDescriptor.of(DepositFragment.class)))
|
||||
.via(
|
||||
(KV<String, CoGbkResult> kv) -> {
|
||||
ContactResource contact =
|
||||
(ContactResource)
|
||||
Contact contact =
|
||||
(Contact)
|
||||
loadResourceByHistoryEntryId(
|
||||
ContactHistory.class,
|
||||
kv.getKey(),
|
||||
@@ -698,8 +698,8 @@ public class RdePipeline implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the pending deposit set in an URL safe string that is sent to the pipeline worker by
|
||||
* the pipeline launcher as a pipeline option.
|
||||
* Encodes the pending deposit set in a URL safe string that is sent to the pipeline worker by the
|
||||
* pipeline launcher as a pipeline option.
|
||||
*/
|
||||
public static String encodePendingDeposits(ImmutableSet<PendingDeposit> pendingDeposits)
|
||||
throws IOException {
|
||||
@@ -715,6 +715,12 @@ public class RdePipeline implements Serializable {
|
||||
PipelineOptionsFactory.register(RdePipelineOptions.class);
|
||||
RdePipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(args).withValidation().as(RdePipelineOptions.class);
|
||||
// We need to self allocate the IDs because the pipeline creates EPP resources from history
|
||||
// entries and projects them to watermark. These buildable entities would otherwise request an
|
||||
// ID from datastore, which Beam does not have access to. The IDs are not included in the
|
||||
// deposits or are these entities persisted back to the database, so it is OK to use a self
|
||||
// allocated ID to get around the limitations of beam.
|
||||
options.setUseSelfAllocatedId(true);
|
||||
RegistryPipelineOptions.validateRegistryPipelineOptions(options);
|
||||
options.setIsolationOverride(TransactionIsolationLevel.TRANSACTION_READ_COMMITTED);
|
||||
DaggerRdePipeline_RdePipelineComponent.builder().options(options).build().rdePipeline().run();
|
||||
|
||||
@@ -14,25 +14,29 @@
|
||||
|
||||
package google.registry.beam.resave;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static org.apache.beam.sdk.values.TypeDescriptors.integers;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Streams;
|
||||
import google.registry.beam.common.RegistryJpaIO;
|
||||
import google.registry.beam.common.RegistryJpaIO.Read;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.PersistenceModule.TransactionIsolationLevel;
|
||||
import google.registry.persistence.transaction.CriteriaQueryBuilder;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import org.apache.beam.sdk.Pipeline;
|
||||
import org.apache.beam.sdk.PipelineResult;
|
||||
import org.apache.beam.sdk.options.PipelineOptionsFactory;
|
||||
import org.apache.beam.sdk.transforms.DoFn;
|
||||
import org.apache.beam.sdk.transforms.GroupIntoBatches;
|
||||
import org.apache.beam.sdk.transforms.ParDo;
|
||||
@@ -52,7 +56,7 @@ import org.joda.time.DateTime;
|
||||
public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
|
||||
private static final ImmutableSet<Class<? extends EppResource>> EPP_RESOURCE_CLASSES =
|
||||
ImmutableSet.of(ContactResource.class, Domain.class, Host.class);
|
||||
ImmutableSet.of(Contact.class, Domain.class, Host.class);
|
||||
|
||||
/**
|
||||
* There exist three possible situations where we know we'll want to project domains to the
|
||||
@@ -68,7 +72,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
* multiple times, and to avoid projecting and resaving the same domain multiple times.
|
||||
*/
|
||||
private static final String DOMAINS_TO_PROJECT_QUERY =
|
||||
"FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
|
||||
"SELECT repoId FROM Domain d WHERE (d.transferData.transferStatus = 'PENDING' AND"
|
||||
+ " d.transferData.pendingTransferExpirationTime < current_timestamp()) OR"
|
||||
+ " (d.registrationExpirationTime < current_timestamp() AND d.deletionTime ="
|
||||
+ " (:END_OF_TIME)) OR (EXISTS (SELECT 1 FROM GracePeriod gp WHERE gp.domainRepoId ="
|
||||
@@ -87,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);
|
||||
@@ -98,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,61 +118,82 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
|
||||
* DomainBase#cloneProjectedAtTime(DateTime)}.
|
||||
*/
|
||||
private void fastResaveDomains(Pipeline pipeline) {
|
||||
Read<Domain, Domain> read =
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
DOMAINS_TO_PROJECT_QUERY,
|
||||
ImmutableMap.of("END_OF_TIME", DateTimeUtils.END_OF_TIME),
|
||||
Domain.class,
|
||||
d -> d);
|
||||
projectAndResaveResources(pipeline, Domain.class, read);
|
||||
String.class,
|
||||
r -> r);
|
||||
projectAndResaveResources(pipeline, Domain.class, repoIdRead);
|
||||
}
|
||||
|
||||
/** Projects all resources to the current time and saves them. */
|
||||
private <T extends EppResource> void forceResaveAllResources(Pipeline pipeline, Class<T> clazz) {
|
||||
Read<T, T> read = RegistryJpaIO.read(() -> CriteriaQueryBuilder.create(clazz).build());
|
||||
projectAndResaveResources(pipeline, clazz, read);
|
||||
Read<String, String> repoIdRead =
|
||||
RegistryJpaIO.read(
|
||||
// Note: cannot use SQL parameters for the table name
|
||||
String.format("SELECT repoId FROM %s", clazz.getSimpleName()), String.class, r -> r);
|
||||
projectAndResaveResources(pipeline, clazz, repoIdRead);
|
||||
}
|
||||
|
||||
/** Projects and re-saves the result of the provided {@link Read}. */
|
||||
/** Projects and re-saves all resources with repo IDs provided by the {@link Read}. */
|
||||
private <T extends EppResource> void projectAndResaveResources(
|
||||
Pipeline pipeline, Class<T> clazz, Read<?, T> read) {
|
||||
Pipeline pipeline, Class<T> clazz, Read<?, String> repoIdRead) {
|
||||
int numShards = options.getSqlWriteShards();
|
||||
int batchSize = options.getSqlWriteBatchSize();
|
||||
String className = clazz.getSimpleName();
|
||||
pipeline
|
||||
.apply("Read " + className, read)
|
||||
.apply("Read " + className, repoIdRead)
|
||||
.apply(
|
||||
"Shard data for class" + className,
|
||||
WithKeys.<Integer, T>of(e -> ThreadLocalRandom.current().nextInt(numShards))
|
||||
WithKeys.<Integer, String>of(e -> ThreadLocalRandom.current().nextInt(numShards))
|
||||
.withKeyType(integers()))
|
||||
.apply(
|
||||
"Group into batches for class" + className,
|
||||
GroupIntoBatches.<Integer, T>ofSize(batchSize).withShardedKey())
|
||||
.apply("Map " + className + " to now", ParDo.of(new BatchedProjectionFunction<>()))
|
||||
GroupIntoBatches.<Integer, String>ofSize(batchSize).withShardedKey())
|
||||
.apply(
|
||||
"Write transformed " + className,
|
||||
RegistryJpaIO.<EppResource>write()
|
||||
.withName("Write transformed " + className)
|
||||
.withBatchSize(batchSize)
|
||||
.withShards(numShards));
|
||||
"Load, map, and save " + className,
|
||||
ParDo.of(new BatchedLoadProjectAndSaveFunction(clazz)));
|
||||
}
|
||||
|
||||
private static class BatchedProjectionFunction<T extends EppResource>
|
||||
extends DoFn<KV<ShardedKey<Integer>, Iterable<T>>, EppResource> {
|
||||
/** Function that loads, projects, and saves resources all in the same transaction. */
|
||||
private static class BatchedLoadProjectAndSaveFunction
|
||||
extends DoFn<KV<ShardedKey<Integer>, Iterable<String>>, Void> {
|
||||
|
||||
private final Class<? extends EppResource> clazz;
|
||||
|
||||
private BatchedLoadProjectAndSaveFunction(Class<? extends EppResource> clazz) {
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
@ProcessElement
|
||||
public void processElement(
|
||||
@Element KV<ShardedKey<Integer>, Iterable<T>> element,
|
||||
OutputReceiver<EppResource> outputReceiver) {
|
||||
@Element KV<ShardedKey<Integer>, Iterable<String>> element,
|
||||
OutputReceiver<Void> outputReceiver) {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
element
|
||||
.getValue()
|
||||
.forEach(
|
||||
resource ->
|
||||
outputReceiver.output(
|
||||
resource.cloneProjectedAtTime(jpaTm().getTransactionTime()))));
|
||||
() -> {
|
||||
DateTime now = jpaTm().getTransactionTime();
|
||||
ImmutableList<VKey<? extends EppResource>> keys =
|
||||
Streams.stream(element.getValue())
|
||||
.map(repoId -> VKey.create(clazz, repoId))
|
||||
.collect(toImmutableList());
|
||||
ImmutableList<EppResource> mappedResources =
|
||||
jpaTm().loadByKeys(keys).values().stream()
|
||||
.map(r -> r.cloneProjectedAtTime(now))
|
||||
.collect(toImmutableList());
|
||||
jpaTm().putAll(mappedResources);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
PipelineOptionsFactory.register(ResaveAllEppResourcesPipelineOptions.class);
|
||||
ResaveAllEppResourcesPipelineOptions options =
|
||||
PipelineOptionsFactory.fromArgs(args)
|
||||
.withValidation()
|
||||
.as(ResaveAllEppResourcesPipelineOptions.class);
|
||||
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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -25,28 +25,34 @@ 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";
|
||||
private static final String OUTDATED_NAME_FIELD = "fullyQualifiedDomainName";
|
||||
|
||||
/** 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}. */
|
||||
public static ThreatMatch fromJSON(JSONObject threatMatch) throws JSONException {
|
||||
// TODO: delete OUTDATED_NAME_FIELD once we no longer process reports saved with
|
||||
// fullyQualifiedDomainName in them, likely 2023
|
||||
return new AutoValue_ThreatMatch(
|
||||
threatMatch.getString(THREAT_TYPE_FIELD), threatMatch.getString(DOMAIN_NAME_FIELD));
|
||||
threatMatch.getString(THREAT_TYPE_FIELD),
|
||||
threatMatch.has(OUTDATED_NAME_FIELD)
|
||||
? threatMatch.getString(OUTDATED_NAME_FIELD)
|
||||
: threatMatch.getString(DOMAIN_NAME_FIELD));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.config;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import dagger.Module;
|
||||
@@ -37,6 +36,36 @@ import javax.inject.Singleton;
|
||||
@Module
|
||||
public abstract class CredentialModule {
|
||||
|
||||
/**
|
||||
* Provides a {@link GoogleCredentialsBundle} backed by the application default credential from
|
||||
* the Google Cloud Runtime. This credential may be used to access GCP APIs that are NOT part of
|
||||
* the Google Workspace.
|
||||
*
|
||||
* <p>The credential returned by the Cloud Runtime depends on the runtime environment:
|
||||
*
|
||||
* <ul>
|
||||
* <li>On App Engine, returns a scope-less {@code ComputeEngineCredentials} for
|
||||
* PROJECT_ID@appspot.gserviceaccount.com
|
||||
* <li>On Compute Engine, returns a scope-less {@code ComputeEngineCredentials} for
|
||||
* PROJECT_NUMBER-compute@developer.gserviceaccount.com
|
||||
* <li>On end user host, this returns the credential downloaded by gcloud. Please refer to <a
|
||||
* href="https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login">Cloud
|
||||
* SDK documentation</a> for details.
|
||||
* </ul>
|
||||
*/
|
||||
@ApplicationDefaultCredential
|
||||
@Provides
|
||||
@Singleton
|
||||
public static GoogleCredentialsBundle provideApplicationDefaultCredential() {
|
||||
GoogleCredentials credential;
|
||||
try {
|
||||
credential = GoogleCredentials.getApplicationDefault();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return GoogleCredentialsBundle.create(credential);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the default {@link GoogleCredentialsBundle} from the Google Cloud runtime.
|
||||
*
|
||||
@@ -70,26 +99,19 @@ public abstract class CredentialModule {
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the default {@link GoogleCredential} from the Google Cloud runtime for G Suite
|
||||
* Drive API.
|
||||
* TODO(b/138195359): Deprecate this credential once we figure out how to use
|
||||
* {@link GoogleCredentials} for G Suite Drive API.
|
||||
* Provides a {@link GoogleCredentialsBundle} for accessing Google Workspace APIs, such as Drive
|
||||
* and Sheets.
|
||||
*/
|
||||
@GSuiteDriveCredential
|
||||
@GoogleWorkspaceCredential
|
||||
@Provides
|
||||
@Singleton
|
||||
public static GoogleCredential provideGSuiteDriveCredential(
|
||||
public static GoogleCredentialsBundle provideGSuiteDriveCredential(
|
||||
@ApplicationDefaultCredential GoogleCredentialsBundle applicationDefaultCredential,
|
||||
@Config("defaultCredentialOauthScopes") ImmutableList<String> requiredScopes) {
|
||||
GoogleCredential credential;
|
||||
try {
|
||||
credential = GoogleCredential.getApplicationDefault();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (credential.createScopedRequired()) {
|
||||
credential = credential.createScoped(requiredScopes);
|
||||
}
|
||||
return credential;
|
||||
GoogleCredentials credential = applicationDefaultCredential.getGoogleCredentials();
|
||||
// Although credential is scope-less, its `createScopedRequired` method still returns false.
|
||||
credential = credential.createScoped(requiredScopes);
|
||||
return GoogleCredentialsBundle.create(credential);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,18 +158,24 @@ public abstract class CredentialModule {
|
||||
.createScoped(requiredScopes));
|
||||
}
|
||||
|
||||
/** Dagger qualifier for the scope-less Application Default Credential. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ApplicationDefaultCredential {}
|
||||
|
||||
/** Dagger qualifier for the Application Default Credential. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Deprecated // Switching to @ApplicationDefaultCredential
|
||||
public @interface DefaultCredential {}
|
||||
|
||||
|
||||
/** Dagger qualifier for the credential for G Suite Drive API. */
|
||||
/** Dagger qualifier for the credential for Google Workspace APIs. */
|
||||
@Qualifier
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface GSuiteDriveCredential {}
|
||||
public @interface GoogleWorkspaceCredential {}
|
||||
|
||||
/**
|
||||
* Dagger qualifier for a credential from a service account's JSON key, to be used in non-request
|
||||
|
||||
@@ -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) {
|
||||
@@ -1452,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;
|
||||
}
|
||||
|
||||
@@ -246,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
|
||||
@@ -477,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 {
|
||||
|
||||
@@ -37,7 +37,7 @@ import google.registry.dns.writer.BaseDnsWriter;
|
||||
import google.registry.dns.writer.DnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Registries;
|
||||
import google.registry.util.Clock;
|
||||
@@ -134,10 +134,10 @@ public class CloudDnsWriter extends BaseDnsWriter {
|
||||
ImmutableSet.Builder<ResourceRecordSet> domainRecords = new ImmutableSet.Builder<>();
|
||||
|
||||
// Construct DS records (if any).
|
||||
Set<DelegationSignerData> dsData = domain.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());
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.dns.writer.BaseDnsWriter;
|
||||
import google.registry.dns.writer.DnsWriterZone;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.tld.Registries;
|
||||
import google.registry.util.Clock;
|
||||
@@ -185,7 +185,7 @@ public class DnsUpdateWriter extends BaseDnsWriter {
|
||||
|
||||
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()),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<datastore-indexes autoGenerate="false">
|
||||
<!-- For finding contact resources by registrar. -->
|
||||
<datastore-index kind="ContactResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Contact" ancestor="false" source="manual">
|
||||
<property name="currentSponsorClientId" direction="asc"/>
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="searchName" direction="asc"/>
|
||||
@@ -91,7 +91,7 @@
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="fullyQualifiedHostName" direction="asc"/>
|
||||
</datastore-index>
|
||||
<datastore-index kind="ContactResource" ancestor="false" source="manual">
|
||||
<datastore-index kind="Contact" ancestor="false" source="manual">
|
||||
<property name="deletionTime" direction="asc"/>
|
||||
<property name="searchName" direction="asc"/>
|
||||
</datastore-index>
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<name>dns-publish</name>
|
||||
<rate>100/s</rate>
|
||||
<bucket-size>100</bucket-size>
|
||||
<!-- 30 sec backoff increasing linearly up to 10 minutes. -->
|
||||
<!-- 30 sec backoff increasing linearly up to 30 minutes. -->
|
||||
<retry-parameters>
|
||||
<min-backoff-seconds>30</min-backoff-seconds>
|
||||
<max-backoff-seconds>600</max-backoff-seconds>
|
||||
<max-backoff-seconds>1800</max-backoff-seconds>
|
||||
<max-doublings>0</max-doublings>
|
||||
</retry-parameters>
|
||||
</queue>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -14,16 +14,16 @@
|
||||
|
||||
package google.registry.export;
|
||||
|
||||
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
|
||||
import com.google.api.services.drive.Drive;
|
||||
import dagger.Component;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.CredentialModule;
|
||||
import google.registry.config.CredentialModule.GSuiteDriveCredential;
|
||||
import google.registry.config.CredentialModule.GoogleWorkspaceCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.config.RegistryConfig.ConfigModule;
|
||||
import google.registry.storage.drive.DriveConnection;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
/** Dagger module for Google {@link Drive} service connection objects. */
|
||||
@@ -32,13 +32,13 @@ public final class DriveModule {
|
||||
|
||||
@Provides
|
||||
static Drive provideDrive(
|
||||
@GSuiteDriveCredential GoogleCredential googleCredential,
|
||||
@GoogleWorkspaceCredential GoogleCredentialsBundle googleCredential,
|
||||
@Config("projectId") String projectId) {
|
||||
|
||||
return new Drive.Builder(
|
||||
googleCredential.getTransport(),
|
||||
googleCredential.getHttpTransport(),
|
||||
googleCredential.getJsonFactory(),
|
||||
googleCredential)
|
||||
googleCredential.getHttpRequestInitializer())
|
||||
.setApplicationName(projectId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -163,7 +163,7 @@ public final class SyncGroupMembersAction implements Runnable {
|
||||
registrarsToSave.add(result.getKey().asBuilder().setContactsRequireSyncing(false).build());
|
||||
}
|
||||
}
|
||||
tm().transactNew(() -> tm().updateAll(registrarsToSave.build()));
|
||||
tm().transact(() -> tm().updateAll(registrarsToSave.build()));
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ package google.registry.export.sheet;
|
||||
import com.google.api.services.sheets.v4.Sheets;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import google.registry.config.CredentialModule.JsonCredential;
|
||||
import google.registry.config.CredentialModule.GoogleWorkspaceCredential;
|
||||
import google.registry.config.RegistryConfig.Config;
|
||||
import google.registry.util.GoogleCredentialsBundle;
|
||||
|
||||
@@ -27,7 +27,7 @@ public final class SheetsServiceModule {
|
||||
|
||||
@Provides
|
||||
static Sheets provideSheets(
|
||||
@JsonCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@GoogleWorkspaceCredential GoogleCredentialsBundle credentialsBundle,
|
||||
@Config("projectId") String projectId) {
|
||||
return new Sheets.Builder(
|
||||
credentialsBundle.getHttpTransport(),
|
||||
|
||||
@@ -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.ForeignKeyUtils;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.tld.label.ReservationType;
|
||||
import google.registry.monitoring.whitebox.CheckApiMetric;
|
||||
@@ -156,7 +156,7 @@ public class CheckApiAction implements Runnable {
|
||||
}
|
||||
|
||||
private boolean checkExists(String domainString, DateTime now) {
|
||||
return !ForeignKeyIndex.loadCached(Domain.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.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);
|
||||
@@ -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();
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -53,6 +53,7 @@ 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.Domain;
|
||||
import google.registry.model.domain.DomainCommand.Check;
|
||||
@@ -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<Domain>> existingDomains =
|
||||
ForeignKeyIndex.load(Domain.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<Domain>> 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<Domain>> existingDomains,
|
||||
ImmutableMap<String, VKey<Domain>> existingDomains,
|
||||
ImmutableSet<String> availableDomains,
|
||||
DateTime now,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
@@ -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, Domain> loadDomainsForRestoreChecks(
|
||||
FeeCheckCommandExtension<?, ?> feeCheck,
|
||||
ImmutableMap<String, InternetDomainName> domainNames,
|
||||
ImmutableMap<String, ForeignKeyIndex<Domain>> 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.
|
||||
@@ -329,7 +329,7 @@ public final class DomainCheckFlow implements Flow {
|
||||
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(
|
||||
|
||||
@@ -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;
|
||||
@@ -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}
|
||||
@@ -249,7 +248,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
validateRegistrationPeriod(years);
|
||||
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);
|
||||
@@ -376,8 +375,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setIdnTableName(validateDomainNameWithIdnTables(domainName))
|
||||
.setRegistrationExpirationTime(registrationExpirationTime)
|
||||
.setAutorenewBillingEvent(autorenewBillingEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
|
||||
.setSmdId(signedMarkId)
|
||||
.setDsData(secDnsCreate.map(SecDnsCreateExtension::getDsData).orElse(null))
|
||||
@@ -390,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) {
|
||||
@@ -629,10 +639,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -640,7 +647,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
|
||||
.setResponseData(
|
||||
ImmutableList.of(
|
||||
DomainPendingActionNotificationResponse.create(
|
||||
fullyQualifiedDomainName, true, historyEntry.getTrid(), now)))
|
||||
domainName, true, historyEntry.getTrid(), now)))
|
||||
.setHistoryEntry(historyEntry)
|
||||
.build();
|
||||
}
|
||||
@@ -724,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) {
|
||||
@@ -733,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;
|
||||
@@ -257,13 +256,13 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
Domain newDomain = builder.build();
|
||||
DomainHistory domainHistory =
|
||||
buildDomainHistory(newDomain, registry, now, durationUntilDelete, inAddGracePeriod);
|
||||
updateForeignKeyIndexDeletionTime(newDomain);
|
||||
handlePendingTransferOnDelete(existingDomain, newDomain, now, domainHistory);
|
||||
// Close the autorenew billing event and poll message. This may delete the poll message. Store
|
||||
// the updated recurring billing event, we'll need it later and can't reload it.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
BillingEvent.Recurring recurringBillingEvent =
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain, existingRecurring, now, domainHistory.getDomainHistoryId());
|
||||
// If there's a pending transfer, the gaining client's autorenew billing
|
||||
// event and poll message will already have been deleted in
|
||||
// ResourceDeleteFlow since it's listed in serverApproveEntities.
|
||||
|
||||
@@ -79,7 +79,7 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DesignatedContact.Type;
|
||||
import google.registry.model.domain.Domain;
|
||||
@@ -105,13 +105,14 @@ 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.Host;
|
||||
@@ -270,7 +271,13 @@ public class DomainFlowUtils {
|
||||
&& token.get().getDomainName().get().equals(domainName.toString())) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise check whether the metadata extension is being used by a superuser to specify that
|
||||
// Otherwise, check to see if we're using the specialized anchor tenant registration behavior on
|
||||
// the allocation token
|
||||
if (token.isPresent()
|
||||
&& token.get().getRegistrationBehavior().equals(RegistrationBehavior.ANCHOR_TENANT)) {
|
||||
return true;
|
||||
}
|
||||
// Otherwise, check whether the metadata extension is being used by a superuser to specify that
|
||||
// it's an anchor tenant creation.
|
||||
return metadataExtension.isPresent() && metadataExtension.get().getIsAnchorTenant();
|
||||
}
|
||||
@@ -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<Host>> nameservers)
|
||||
Set<DesignatedContact> contacts, VKey<Contact> registrant, Set<VKey<Host>> nameservers)
|
||||
throws EppException {
|
||||
ImmutableList.Builder<VKey<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
|
||||
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
|
||||
@@ -420,7 +425,7 @@ public class DomainFlowUtils {
|
||||
|
||||
static void validateNoDuplicateContacts(Set<DesignatedContact> contacts)
|
||||
throws ParameterValuePolicyErrorException {
|
||||
ImmutableMultimap<Type, VKey<ContactResource>> contactsByType =
|
||||
ImmutableMultimap<Type, VKey<Contact>> contactsByType =
|
||||
contacts.stream()
|
||||
.collect(
|
||||
toImmutableSetMultimap(
|
||||
@@ -429,15 +434,13 @@ public class DomainFlowUtils {
|
||||
// If any contact type has multiple contacts:
|
||||
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
|
||||
// Find the duplicates.
|
||||
Map<Type, Collection<VKey<ContactResource>>> dupeKeysMap =
|
||||
Map<Type, Collection<VKey<Contact>>> dupeKeysMap =
|
||||
Maps.filterEntries(contactsByType.asMap(), e -> e.getValue().size() > 1);
|
||||
ImmutableList<VKey<ContactResource>> dupeKeys =
|
||||
ImmutableList<VKey<Contact>> dupeKeys =
|
||||
dupeKeysMap.values().stream().flatMap(Collection::stream).collect(toImmutableList());
|
||||
// Load the duplicates in one batch.
|
||||
Map<VKey<? extends ContactResource>, ContactResource> dupeContacts =
|
||||
tm().loadByKeys(dupeKeys);
|
||||
ImmutableMultimap.Builder<Type, VKey<ContactResource>> typesMap =
|
||||
new ImmutableMultimap.Builder<>();
|
||||
Map<VKey<? extends Contact>, Contact> dupeContacts = tm().loadByKeys(dupeKeys);
|
||||
ImmutableMultimap.Builder<Type, VKey<Contact>> typesMap = new ImmutableMultimap.Builder<>();
|
||||
dupeKeysMap.forEach(typesMap::putAll);
|
||||
// Create an error message showing the type and contact IDs of the duplicates.
|
||||
throw new DuplicateContactForRoleException(
|
||||
@@ -446,7 +449,7 @@ public class DomainFlowUtils {
|
||||
}
|
||||
|
||||
static void validateRequiredContactsPresent(
|
||||
@Nullable VKey<ContactResource> registrant, Set<DesignatedContact> contacts)
|
||||
@Nullable VKey<Contact> registrant, Set<DesignatedContact> contacts)
|
||||
throws RequiredParameterMissingException {
|
||||
if (registrant == null) {
|
||||
throw new MissingRegistrantException();
|
||||
@@ -584,7 +587,10 @@ public class DomainFlowUtils {
|
||||
* <p>Returns the new autorenew recurring billing event.
|
||||
*/
|
||||
public static Recurring updateAutorenewRecurrenceEndTime(
|
||||
Domain domain, Recurring existingRecurring, DateTime newEndTime) {
|
||||
Domain domain,
|
||||
Recurring existingRecurring,
|
||||
DateTime newEndTime,
|
||||
@Nullable DomainHistoryId historyId) {
|
||||
Optional<PollMessage.Autorenew> autorenewPollMessage =
|
||||
tm().loadByKeyIfPresent(domain.getAutorenewPollMessage());
|
||||
|
||||
@@ -592,20 +598,25 @@ public class DomainFlowUtils {
|
||||
// create a new one at the same id. This can happen if a transfer was requested on a domain
|
||||
// where all autorenew poll messages had already been delivered (this would cause the poll
|
||||
// message to be deleted), and then subsequently the transfer was canceled, rejected, or deleted
|
||||
// (which would cause the poll message to be recreated here).
|
||||
PollMessage.Autorenew updatedAutorenewPollMessage =
|
||||
autorenewPollMessage.isPresent()
|
||||
? autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build()
|
||||
: newAutorenewPollMessage(domain)
|
||||
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
|
||||
.setAutorenewEndTime(newEndTime)
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(
|
||||
domain.getRepoId(), domain.getAutorenewPollMessageHistoryId()))
|
||||
.build();
|
||||
// (which would cause the poll message to be recreated here). In the latter case, the history id
|
||||
// of the event that created the new poll message will also be used.
|
||||
PollMessage.Autorenew updatedAutorenewPollMessage;
|
||||
if (autorenewPollMessage.isPresent()) {
|
||||
updatedAutorenewPollMessage =
|
||||
autorenewPollMessage.get().asBuilder().setAutorenewEndTime(newEndTime).build();
|
||||
} else {
|
||||
checkNotNull(
|
||||
historyId, "Cannot create a new autorenew poll message without a domain history id");
|
||||
updatedAutorenewPollMessage =
|
||||
newAutorenewPollMessage(domain)
|
||||
.setId((Long) domain.getAutorenewPollMessage().getSqlKey())
|
||||
.setAutorenewEndTime(newEndTime)
|
||||
.setDomainHistoryId(historyId)
|
||||
.build();
|
||||
}
|
||||
|
||||
// If the resultant autorenew poll message would have no poll messages to deliver, then just
|
||||
// delete it. Otherwise save it with the new end time.
|
||||
// delete it. Otherwise, save it with the new end time.
|
||||
if (isAtOrAfter(updatedAutorenewPollMessage.getEventTime(), newEndTime)) {
|
||||
autorenewPollMessage.ifPresent(autorenew -> tm().delete(autorenew));
|
||||
} else {
|
||||
@@ -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;
|
||||
@@ -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. */
|
||||
@@ -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,6 +30,7 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.ExtensionManager;
|
||||
import google.registry.flows.Flow;
|
||||
import google.registry.flows.FlowModule.RegistrarId;
|
||||
import google.registry.flows.FlowModule.Superuser;
|
||||
import google.registry.flows.FlowModule.TargetId;
|
||||
import google.registry.flows.annotations.ReportingSpec;
|
||||
import google.registry.flows.custom.DomainInfoFlowCustomLogic;
|
||||
@@ -42,6 +43,8 @@ import google.registry.model.domain.DomainCommand.Info.HostsRequest;
|
||||
import google.registry.model.domain.DomainInfoData;
|
||||
import google.registry.model.domain.fee06.FeeInfoCommandExtensionV06;
|
||||
import google.registry.model.domain.fee06.FeeInfoResponseExtensionV06;
|
||||
import google.registry.model.domain.packagetoken.PackageTokenExtension;
|
||||
import google.registry.model.domain.packagetoken.PackageTokenResponseExtension;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.rgp.RgpInfoExtension;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
@@ -85,13 +88,14 @@ public final class DomainInfoFlow implements Flow {
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject DomainInfoFlowCustomLogic flowCustomLogic;
|
||||
@Inject DomainPricingLogic pricingLogic;
|
||||
@Inject @Superuser boolean isSuperuser;
|
||||
|
||||
@Inject
|
||||
DomainInfoFlow() {}
|
||||
|
||||
@Override
|
||||
public EppResponse run() throws EppException {
|
||||
extensionManager.register(FeeInfoCommandExtensionV06.class);
|
||||
extensionManager.register(FeeInfoCommandExtensionV06.class, PackageTokenExtension.class);
|
||||
flowCustomLogic.beforeValidation();
|
||||
validateRegistrarIsLoggedIn(registrarId);
|
||||
extensionManager.validate();
|
||||
@@ -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(
|
||||
@@ -150,6 +154,15 @@ public final class DomainInfoFlow implements Flow {
|
||||
if (!gracePeriodStatuses.isEmpty()) {
|
||||
extensions.add(RgpInfoExtension.create(gracePeriodStatuses));
|
||||
}
|
||||
Optional<PackageTokenExtension> packageInfo =
|
||||
eppInput.getSingleExtension(PackageTokenExtension.class);
|
||||
if (packageInfo.isPresent()) {
|
||||
// Package info was requested.
|
||||
if (isSuperuser || registrarId.equals(domain.getCurrentSponsorRegistrarId())) {
|
||||
// Only show package info to owning registrar or superusers
|
||||
extensions.add(PackageTokenResponseExtension.create(domain.getCurrentPackageToken()));
|
||||
}
|
||||
}
|
||||
Optional<FeeInfoCommandExtensionV06> feeInfo =
|
||||
eppInput.getSingleExtension(FeeInfoCommandExtensionV06.class);
|
||||
if (feeInfo.isPresent()) { // Fee check was requested.
|
||||
|
||||
@@ -30,6 +30,8 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.validateRegistrationPeriod;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeApplyPackageRemovalToken;
|
||||
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyTokenAllowedOnDomain;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.leapSafeAddYears;
|
||||
@@ -119,6 +121,10 @@ import org.joda.time.Duration;
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.MissingRemovePackageTokenOnPackageDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.RemovePackageTokenOnNonPackageDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.AllocationTokenNotValidForDomainException}
|
||||
* @error {@link
|
||||
* google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException}
|
||||
@@ -174,7 +180,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
registrarId,
|
||||
now,
|
||||
eppInput.getSingleExtension(AllocationTokenExtension.class));
|
||||
verifyRenewAllowed(authInfo, existingDomain, command);
|
||||
verifyRenewAllowed(authInfo, existingDomain, command, allocationToken);
|
||||
|
||||
// If client passed an applicable static token this updates the domain
|
||||
existingDomain = maybeApplyPackageRemovalToken(existingDomain, allocationToken);
|
||||
|
||||
int years = command.getPeriod().getValue();
|
||||
DateTime newExpirationTime =
|
||||
leapSafeAddYears(existingDomain.getRegistrationExpirationTime(), years); // Uncapped
|
||||
@@ -223,7 +233,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.build();
|
||||
// End the old autorenew billing event and poll message now. This may delete the poll message.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
now,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
Domain newDomain =
|
||||
existingDomain
|
||||
.asBuilder()
|
||||
@@ -231,9 +245,7 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.setLastEppUpdateRegistrarId(registrarId)
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(newAutorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
newAutorenewPollMessage.createVKey(),
|
||||
newAutorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(newAutorenewPollMessage.createVKey())
|
||||
.addGracePeriod(
|
||||
GracePeriod.forBillingEvent(
|
||||
GracePeriodStatus.RENEW, existingDomain.getRepoId(), explicitRenewEvent))
|
||||
@@ -300,7 +312,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.build();
|
||||
}
|
||||
|
||||
private void verifyRenewAllowed(Optional<AuthInfo> authInfo, Domain existingDomain, Renew command)
|
||||
private void verifyRenewAllowed(
|
||||
Optional<AuthInfo> authInfo,
|
||||
Domain existingDomain,
|
||||
Renew command,
|
||||
Optional<AllocationToken> allocationToken)
|
||||
throws EppException {
|
||||
verifyOptionalAuthInfo(authInfo, existingDomain);
|
||||
verifyNoDisallowedStatuses(existingDomain, RENEW_DISALLOWED_STATUSES);
|
||||
@@ -310,6 +326,8 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
checkHasBillingAccount(registrarId, existingDomain.getTld());
|
||||
}
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
// We only allow __REMOVE_PACKAGE__ token on promo package domains for now
|
||||
verifyTokenAllowedOnDomain(existingDomain, allocationToken);
|
||||
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
|
||||
if (!command.getCurrentExpirationDate().equals(
|
||||
existingDomain.getRegistrationExpirationTime().toLocalDate())) {
|
||||
@@ -331,7 +349,11 @@ public final class DomainRenewFlow implements TransactionalFlow {
|
||||
.setPeriodYears(years)
|
||||
.setCost(renewCost)
|
||||
.setEventTime(now)
|
||||
.setAllocationToken(allocationToken.map(AllocationToken::createVKey).orElse(null))
|
||||
.setAllocationToken(
|
||||
allocationToken
|
||||
.filter(t -> AllocationToken.TokenBehavior.DEFAULT.equals(t.getTokenBehavior()))
|
||||
.map(AllocationToken::createVKey)
|
||||
.orElse(null))
|
||||
.setBillingTime(now.plus(Registry.get(tld).getRenewGracePeriodLength()))
|
||||
.setDomainHistoryId(
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()))
|
||||
|
||||
@@ -27,7 +27,6 @@ import static google.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.verifyRegistrarIsActive;
|
||||
import static google.registry.model.ResourceTransferUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RESTORE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
@@ -188,7 +187,6 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
autorenewPollMessage,
|
||||
now,
|
||||
registrarId);
|
||||
updateForeignKeyIndexDeletionTime(newDomain);
|
||||
DomainHistory domainHistory = buildDomainHistory(newDomain, now);
|
||||
entitiesToSave.add(newDomain, domainHistory, autorenewEvent, autorenewPollMessage);
|
||||
tm().putAll(entitiesToSave.build());
|
||||
@@ -251,8 +249,7 @@ public final class DomainRestoreRequestFlow implements TransactionalFlow {
|
||||
.setGracePeriods(null)
|
||||
.setDeletePollMessage(null)
|
||||
.setAutorenewBillingEvent(autorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
autorenewPollMessage.createVKey(), autorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(autorenewPollMessage.createVKey())
|
||||
// Clear the autorenew end time so if it had expired but is now explicitly being restored,
|
||||
// it won't immediately be deleted again.
|
||||
.setAutorenewEndTime(Optional.empty())
|
||||
|
||||
@@ -194,7 +194,11 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
}
|
||||
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
|
||||
// up deleting the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, now);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
now,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
DateTime newExpirationTime =
|
||||
computeExDateForApprovalTime(existingDomain, now, transferData.getTransferPeriod());
|
||||
// Create a new autorenew event starting at the expiration time.
|
||||
@@ -240,9 +244,7 @@ public final class DomainTransferApproveFlow implements TransactionalFlow {
|
||||
.build())
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(autorenewEvent.createVKey())
|
||||
.setAutorenewPollMessage(
|
||||
gainingClientAutorenewPollMessage.createVKey(),
|
||||
gainingClientAutorenewPollMessage.getHistoryRevisionId())
|
||||
.setAutorenewPollMessage(gainingClientAutorenewPollMessage.createVKey())
|
||||
// Remove all the old grace periods and add a new one for the transfer.
|
||||
.setGracePeriods(
|
||||
billingEvent
|
||||
|
||||
@@ -116,7 +116,8 @@ public final class DomainTransferCancelFlow implements TransactionalFlow {
|
||||
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
|
||||
// may recreate the autorenew poll message if it was deleted when the transfer request was made.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, END_OF_TIME);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -117,7 +117,8 @@ public final class DomainTransferRejectFlow implements TransactionalFlow {
|
||||
// Reopen the autorenew event and poll message that we closed for the implicit transfer. This
|
||||
// may end up recreating the poll message if it was deleted upon the transfer request.
|
||||
Recurring existingRecurring = tm().loadByKey(existingDomain.getAutorenewBillingEvent());
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, END_OF_TIME);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain, existingRecurring, END_OF_TIME, domainHistory.getDomainHistoryId());
|
||||
// Delete the billing event and poll messages that were written in case the transfer would have
|
||||
// been implicitly server approved.
|
||||
tm().delete(existingDomain.getTransferData().getServerApproveEntities());
|
||||
|
||||
@@ -57,6 +57,7 @@ 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;
|
||||
@@ -181,6 +182,7 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
? superuserExtension.get().getRenewalPeriod()
|
||||
: ((Transfer) resourceCommand).getPeriod();
|
||||
verifyTransferAllowed(existingDomain, period, now, superuserExtension);
|
||||
|
||||
String tld = existingDomain.getTld();
|
||||
Registry registry = Registry.get(tld);
|
||||
// An optional extension from the client specifying what they think the transfer should cost.
|
||||
@@ -260,7 +262,11 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
|
||||
// the poll message if it has no events left. Note that if the automatic transfer succeeds, then
|
||||
// cloneProjectedAtTime() will replace these old autorenew entities with the server approve ones
|
||||
// that we've created in this flow and stored in pendingTransferData.
|
||||
updateAutorenewRecurrenceEndTime(existingDomain, existingRecurring, automaticTransferTime);
|
||||
updateAutorenewRecurrenceEndTime(
|
||||
existingDomain,
|
||||
existingRecurring,
|
||||
automaticTransferTime,
|
||||
new DomainHistoryId(domainHistoryKey.getParent().getName(), domainHistoryKey.getId()));
|
||||
Domain newDomain =
|
||||
existingDomain
|
||||
.asBuilder()
|
||||
|
||||
@@ -218,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())
|
||||
@@ -274,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
|
||||
|
||||
@@ -74,7 +74,7 @@ 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;
|
||||
@@ -181,7 +182,9 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
DomainHistory domainHistory =
|
||||
historyBuilder.setType(DOMAIN_UPDATE).setDomain(newDomain).build();
|
||||
validateNewState(newDomain);
|
||||
dnsQueue.addDomainRefreshTask(targetId);
|
||||
if (requiresDnsUpdate(existingDomain, newDomain)) {
|
||||
dnsQueue.addDomainRefreshTask(targetId);
|
||||
}
|
||||
ImmutableSet.Builder<ImmutableObject> entitiesToSave = new ImmutableSet.Builder<>();
|
||||
entitiesToSave.add(newDomain, domainHistory);
|
||||
Optional<BillingEvent.OneTime> statusUpdateBillingEvent =
|
||||
@@ -203,6 +206,16 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
return responseBuilder.build();
|
||||
}
|
||||
|
||||
/** Determines if any of the changes to new domain should trigger DNS update. */
|
||||
private boolean requiresDnsUpdate(Domain existingDomain, Domain newDomain) {
|
||||
if (existingDomain.shouldPublishToDns() != newDomain.shouldPublishToDns()
|
||||
|| !Objects.equals(newDomain.getDsData(), existingDomain.getDsData())
|
||||
|| !Objects.equals(newDomain.getNsHosts(), existingDomain.getNsHosts())) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Fail if the object doesn't exist or was deleted. */
|
||||
private void verifyUpdateAllowed(Update command, Domain existingDomain, DateTime now)
|
||||
throws EppException {
|
||||
@@ -229,8 +242,7 @@ 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 Domain performUpdate(Update command, Domain domain, DateTime now) throws EppException {
|
||||
@@ -260,7 +272,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())
|
||||
@@ -268,12 +280,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()) {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.flows.domain.token;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
@@ -26,9 +27,12 @@ import google.registry.flows.EppException;
|
||||
import google.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||
import google.registry.flows.EppException.AuthorizationErrorException;
|
||||
import google.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.billing.BillingEvent.RenewalPriceBehavior;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenBehavior;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenStatus;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.model.domain.token.AllocationTokenExtension;
|
||||
@@ -109,23 +113,27 @@ public class AllocationTokenFlowUtils {
|
||||
private void validateToken(
|
||||
InternetDomainName domainName, AllocationToken token, String registrarId, DateTime now)
|
||||
throws EppException {
|
||||
if (!token.getAllowedRegistrarIds().isEmpty()
|
||||
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
|
||||
throw new AllocationTokenNotValidForRegistrarException();
|
||||
}
|
||||
if (!token.getAllowedTlds().isEmpty()
|
||||
&& !token.getAllowedTlds().contains(domainName.parent().toString())) {
|
||||
throw new AllocationTokenNotValidForTldException();
|
||||
}
|
||||
if (token.getDomainName().isPresent()
|
||||
&& !token.getDomainName().get().equals(domainName.toString())) {
|
||||
throw new AllocationTokenNotValidForDomainException();
|
||||
}
|
||||
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
|
||||
// check the status transitions map if it's non-trivial.
|
||||
if (token.getTokenStatusTransitions().size() > 1
|
||||
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
|
||||
throw new AllocationTokenNotInPromotionException();
|
||||
|
||||
// Only tokens with default behavior require validation
|
||||
if (TokenBehavior.DEFAULT.equals(token.getTokenBehavior())) {
|
||||
if (!token.getAllowedRegistrarIds().isEmpty()
|
||||
&& !token.getAllowedRegistrarIds().contains(registrarId)) {
|
||||
throw new AllocationTokenNotValidForRegistrarException();
|
||||
}
|
||||
if (!token.getAllowedTlds().isEmpty()
|
||||
&& !token.getAllowedTlds().contains(domainName.parent().toString())) {
|
||||
throw new AllocationTokenNotValidForTldException();
|
||||
}
|
||||
if (token.getDomainName().isPresent()
|
||||
&& !token.getDomainName().get().equals(domainName.toString())) {
|
||||
throw new AllocationTokenNotValidForDomainException();
|
||||
}
|
||||
// Tokens without status transitions will just have a single-entry NOT_STARTED map, so only
|
||||
// check the status transitions map if it's non-trivial.
|
||||
if (token.getTokenStatusTransitions().size() > 1
|
||||
&& !TokenStatus.VALID.equals(token.getTokenStatusTransitions().getValueAtTime(now))) {
|
||||
throw new AllocationTokenNotInPromotionException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +145,15 @@ public class AllocationTokenFlowUtils {
|
||||
// See https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04#section-2.1
|
||||
throw new InvalidAllocationTokenException();
|
||||
}
|
||||
Optional<AllocationToken> maybeTokenEntity =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.create(AllocationToken.class, token)));
|
||||
|
||||
Optional<AllocationToken> maybeTokenEntity = AllocationToken.maybeGetStaticTokenInstance(token);
|
||||
if (maybeTokenEntity.isPresent()) {
|
||||
return maybeTokenEntity.get();
|
||||
}
|
||||
|
||||
maybeTokenEntity =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(VKey.createSql(AllocationToken.class, token)));
|
||||
|
||||
if (!maybeTokenEntity.isPresent()) {
|
||||
throw new InvalidAllocationTokenException();
|
||||
}
|
||||
@@ -160,11 +175,7 @@ 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));
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -49,8 +48,6 @@ 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.index.EppResourceIndex;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import java.util.Optional;
|
||||
import javax.inject.Inject;
|
||||
@@ -107,7 +104,7 @@ public final class HostCreateFlow implements TransactionalFlow {
|
||||
DateTime now = tm().getTransactionTime();
|
||||
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<Domain> superordinateDomain =
|
||||
lookupSuperordinateDomain(validateHostName(targetId), now);
|
||||
@@ -131,18 +128,13 @@ public final class HostCreateFlow implements TransactionalFlow {
|
||||
.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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,6 +45,7 @@ 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.Domain;
|
||||
import google.registry.model.domain.metadata.MetadataExtension;
|
||||
@@ -57,7 +57,6 @@ import google.registry.model.host.HostCommand.Update;
|
||||
import google.registry.model.host.HostCommand.Update.AddRemove;
|
||||
import google.registry.model.host.HostCommand.Update.Change;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Objects;
|
||||
@@ -130,7 +129,7 @@ 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);
|
||||
Host existingHost = loadAndVerifyExistence(Host.class, targetId, now);
|
||||
@@ -148,7 +147,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
EppResource owningResource = firstNonNull(oldSuperordinateDomain, existingHost);
|
||||
verifyUpdateAllowed(
|
||||
command, existingHost, newSuperordinateDomain.orElse(null), owningResource, isHostRename);
|
||||
if (isHostRename && loadAndGetKey(Host.class, newHostName, now) != null) {
|
||||
if (isHostRename && ForeignKeyUtils.load(Host.class, newHostName, now) != null) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
AddRemove add = command.getInnerAdd();
|
||||
@@ -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);
|
||||
@@ -265,7 +260,7 @@ public final class HostUpdateFlow implements TransactionalFlow {
|
||||
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()) {
|
||||
|
||||
@@ -108,7 +108,7 @@ public final class PollAckFlow implements TransactionalFlow {
|
||||
// acked, then we return a special status code indicating that. Note that the query will
|
||||
// include the message being acked.
|
||||
|
||||
int messageCount = tm().doTransactionless(() -> getPollMessageCount(registrarId, now));
|
||||
int messageCount = tm().transact(() -> getPollMessageCount(registrarId, now));
|
||||
if (messageCount <= 0) {
|
||||
return responseBuilder.setResultFromCode(SUCCESS_WITH_NO_MESSAGES).build();
|
||||
}
|
||||
|
||||
@@ -16,24 +16,14 @@ package google.registry.model;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.common.GaeUserIdConverter;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.contact.ContactHistory;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainHistory;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.host.HostHistory;
|
||||
import google.registry.model.index.EppResourceIndex;
|
||||
import google.registry.model.index.EppResourceIndexBucket;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.rde.RdeRevision;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.server.Lock;
|
||||
import google.registry.model.server.ServerSecret;
|
||||
|
||||
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
|
||||
@DeleteAfterMigration
|
||||
@@ -42,28 +32,14 @@ public final class EntityClasses {
|
||||
/** Set of entity classes. */
|
||||
public static final ImmutableSet<Class<? extends ImmutableObject>> ALL_CLASSES =
|
||||
ImmutableSet.of(
|
||||
AllocationToken.class,
|
||||
Contact.class,
|
||||
ContactHistory.class,
|
||||
ContactResource.class,
|
||||
Domain.class,
|
||||
DomainHistory.class,
|
||||
EntityGroupRoot.class,
|
||||
EppResourceIndex.class,
|
||||
EppResourceIndexBucket.class,
|
||||
ForeignKeyIndex.ForeignKeyContactIndex.class,
|
||||
ForeignKeyIndex.ForeignKeyDomainIndex.class,
|
||||
ForeignKeyIndex.ForeignKeyHostIndex.class,
|
||||
GaeUserIdConverter.class,
|
||||
HistoryEntry.class,
|
||||
Host.class,
|
||||
HostHistory.class,
|
||||
Lock.class,
|
||||
PollMessage.class,
|
||||
PollMessage.Autorenew.class,
|
||||
PollMessage.OneTime.class,
|
||||
RdeRevision.class,
|
||||
Registrar.class,
|
||||
ServerSecret.class);
|
||||
HostHistory.class);
|
||||
|
||||
private EntityClasses() {}
|
||||
}
|
||||
|
||||
@@ -140,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();
|
||||
}
|
||||
|
||||
@@ -367,13 +365,13 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
|
||||
|
||||
@Override
|
||||
public EppResource load(VKey<? extends EppResource> key) {
|
||||
return replicaTm().doTransactionless(() -> replicaTm().loadByKey(key));
|
||||
return replicaTm().transact(() -> replicaTm().loadByKey(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<VKey<? extends EppResource>, EppResource> loadAll(
|
||||
Iterable<? extends VKey<? extends EppResource>> keys) {
|
||||
return replicaTm().doTransactionless(() -> replicaTm().loadByKeys(keys));
|
||||
return replicaTm().transact(() -> replicaTm().loadByKeys(keys));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.model;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
@@ -32,11 +31,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.contact.Contact;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntryDao;
|
||||
import google.registry.model.tld.Registry;
|
||||
@@ -68,7 +66,7 @@ public final class EppResourceUtils {
|
||||
+ "AND deletionTime > :now";
|
||||
|
||||
// We have to use the native SQL query here because DomainHost table doesn't have its entity
|
||||
// class so we cannot reference its property like domainHost.hostRepoId in a JPQL query.
|
||||
// class, so we cannot reference its property like domainHost.hostRepoId in a JPQL query.
|
||||
private static final String HOST_LINKED_DOMAIN_QUERY =
|
||||
"SELECT d.repo_id FROM \"Domain\" d "
|
||||
+ "JOIN \"DomainHost\" dh ON dh.domain_repo_id = d.repo_id "
|
||||
@@ -117,20 +115,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 +147,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 +174,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 +186,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,7 +259,7 @@ public final class EppResourceUtils {
|
||||
/**
|
||||
* Rewinds an {@link EppResource} object to a given point in time.
|
||||
*
|
||||
* <p>This method costs nothing if {@code resource} is already current. Otherwise it needs to
|
||||
* <p>This method costs nothing if {@code resource} is already current. Otherwise, it needs to
|
||||
* perform a single fetch operation.
|
||||
*
|
||||
* <p><b>Warning:</b> A resource can only be rolled backwards in time, not forwards; therefore
|
||||
@@ -295,7 +291,7 @@ public final class EppResourceUtils {
|
||||
/**
|
||||
* Rewinds an {@link EppResource} object to a given point in time.
|
||||
*
|
||||
* <p>This method costs nothing if {@code resource} is already current. Otherwise it returns an
|
||||
* <p>This method costs nothing if {@code resource} is already current. Otherwise, it returns an
|
||||
* async operation that performs a single fetch operation.
|
||||
*
|
||||
* @return an asynchronous operation returning resource at {@code timestamp} or {@code null} if
|
||||
@@ -345,54 +341,41 @@ public final class EppResourceUtils {
|
||||
public static ImmutableSet<VKey<Domain>> getLinkedDomainKeys(
|
||||
VKey<? extends EppResource> key, DateTime now, @Nullable Integer limit) {
|
||||
checkArgument(
|
||||
key.getKind().equals(ContactResource.class) || key.getKind().equals(Host.class),
|
||||
"key must be either VKey<ContactResource> or VKey<Host>, but it is %s",
|
||||
key.getKind().equals(Contact.class) || key.getKind().equals(Host.class),
|
||||
"key must be either VKey<Contact> or VKey<Host>, but it is %s",
|
||||
key);
|
||||
boolean isContactKey = key.getKind().equals(ContactResource.class);
|
||||
if (tm().isOfy()) {
|
||||
com.googlecode.objectify.cmd.Query<Domain> query =
|
||||
auditedOfy()
|
||||
.load()
|
||||
.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(Domain::createVKey).collect(toImmutableSet());
|
||||
} else {
|
||||
return tm().transact(
|
||||
() -> {
|
||||
Query query;
|
||||
if (isContactKey) {
|
||||
query =
|
||||
jpaTm()
|
||||
.query(CONTACT_LINKED_DOMAIN_QUERY, String.class)
|
||||
.setParameter("fkRepoId", key)
|
||||
.setParameter("now", now);
|
||||
} else {
|
||||
query =
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(HOST_LINKED_DOMAIN_QUERY)
|
||||
.setParameter("fkRepoId", key.getSqlKey())
|
||||
.setParameter("now", now.toDate());
|
||||
}
|
||||
if (limit != null) {
|
||||
query.setMaxResults(limit);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<VKey<Domain>> domainKeySet =
|
||||
(ImmutableSet<VKey<Domain>>)
|
||||
query
|
||||
.getResultStream()
|
||||
.map(
|
||||
repoId ->
|
||||
Domain.createVKey(Key.create(Domain.class, (String) repoId)))
|
||||
.collect(toImmutableSet());
|
||||
return domainKeySet;
|
||||
});
|
||||
}
|
||||
boolean isContactKey = key.getKind().equals(Contact.class);
|
||||
return tm().transact(
|
||||
() -> {
|
||||
Query query;
|
||||
if (isContactKey) {
|
||||
query =
|
||||
jpaTm()
|
||||
.query(CONTACT_LINKED_DOMAIN_QUERY, String.class)
|
||||
.setParameter("fkRepoId", key)
|
||||
.setParameter("now", now);
|
||||
} else {
|
||||
query =
|
||||
jpaTm()
|
||||
.getEntityManager()
|
||||
.createNativeQuery(HOST_LINKED_DOMAIN_QUERY)
|
||||
.setParameter("fkRepoId", key.getSqlKey())
|
||||
.setParameter("now", now.toDate());
|
||||
}
|
||||
if (limit != null) {
|
||||
query.setMaxResults(limit);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
ImmutableSet<VKey<Domain>> domainKeySet =
|
||||
(ImmutableSet<VKey<Domain>>)
|
||||
query
|
||||
.getResultStream()
|
||||
.map(
|
||||
repoId ->
|
||||
Domain.createVKey(Key.create(Domain.class, (String) repoId)))
|
||||
.collect(toImmutableSet());
|
||||
return domainKeySet;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
236
core/src/main/java/google/registry/model/ForeignKeyUtils.java
Normal file
236
core/src/main/java/google/registry/model/ForeignKeyUtils.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,58 +17,114 @@ package google.registry.model;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.appengine.api.datastore.DatastoreServiceFactory;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import google.registry.beam.common.RegistryPipelineWorkerInitializer;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Allocates a globally unique {@link Long} number to use as an Ofy {@code @Id}.
|
||||
* Allocates a {@link long} to use as a {@code @Id}, (part) of the primary SQL key for an entity.
|
||||
*
|
||||
* <p>In non-test, non-beam environments the Id is generated by Datastore, otherwise it's from an
|
||||
* atomic long number that's incremented every time this method is called.
|
||||
* <p>Normally, the ID is globally unique and allocated by Datastore. It is possible to override
|
||||
* this behavior by providing an ID supplier, such as in unit tests, where a self-allocated ID based
|
||||
* on a monotonically increasing atomic {@link long} is used. Such an ID supplier can also be used
|
||||
* in other scenarios, such as in a Beam pipeline to get around the limitation of Beam's inability
|
||||
* to use GAE SDK to access Datastore. The override should be used with great care lest it results
|
||||
* in irreversible data corruption.
|
||||
*
|
||||
* @see #setIdSupplier(Supplier)
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
public final class IdService {
|
||||
|
||||
/**
|
||||
* A placeholder String passed into DatastoreService.allocateIds that ensures that all ids are
|
||||
* initialized from the same id pool.
|
||||
*/
|
||||
private static final String APP_WIDE_ALLOCATION_KIND = "common";
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private IdService() {}
|
||||
|
||||
private static Supplier<Long> idSupplier =
|
||||
RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get())
|
||||
? SelfAllocatedIdSupplier.getInstance()
|
||||
: DatastoreIdSupplier.getInstance();
|
||||
|
||||
/**
|
||||
* Counts of used ids for use in unit tests or Beam.
|
||||
* Provides a {@link Supplier} of ID that overrides the default.
|
||||
*
|
||||
* <p>Note that one should only use self-allocate Ids in Beam for entities whose Ids are not
|
||||
* important and are not persisted back to the database, i. e. nowhere the uniqueness of the ID is
|
||||
* required.
|
||||
* <p>Currently, the only use case for an override is in the Beam pipeline, where access to
|
||||
* Datastore is not possible through the App Engine API. As such, the setter explicitly checks if
|
||||
* the runtime is Beam.
|
||||
*
|
||||
* <p>Because the provided supplier is not guaranteed to be globally unique and compatible with
|
||||
* existing IDs in the database, one should proceed with great care. It is safe to use an
|
||||
* arbitrary supplier when the resulting IDs are not significant and not persisted back to the
|
||||
* database, i.e. the IDs are only required by the {@link Buildable} contract but are not used in
|
||||
* any meaningful way. One example is the RDE pipeline where we project EPP resource entities from
|
||||
* history entries to watermark time, which are then marshalled into XML elements in the RDE
|
||||
* deposits, where the IDs are omitted.
|
||||
*/
|
||||
private static final AtomicLong nextSelfAllocatedId = new AtomicLong(1); // ids cannot be zero
|
||||
|
||||
private static final boolean isSelfAllocated() {
|
||||
return RegistryEnvironment.UNITTEST.equals(RegistryEnvironment.get())
|
||||
|| "true".equals(System.getProperty(RegistryPipelineWorkerInitializer.PROPERTY, "false"));
|
||||
public static void setIdSupplier(Supplier<Long> idSupplier) {
|
||||
checkState(
|
||||
"true".equals(System.getProperty(RegistryPipelineWorkerInitializer.PROPERTY, "false")),
|
||||
"Can only set ID supplier in a Beam pipeline");
|
||||
logger.atWarning().log("Using ID supplier override!");
|
||||
IdService.idSupplier = idSupplier;
|
||||
}
|
||||
|
||||
/** Allocates an id. */
|
||||
// TODO(b/201547855): Find a way to allocate a unique ID without datastore.
|
||||
public static long allocateId() {
|
||||
return isSelfAllocated()
|
||||
? nextSelfAllocatedId.getAndIncrement()
|
||||
: DatastoreServiceFactory.getDatastoreService()
|
||||
.allocateIds(APP_WIDE_ALLOCATION_KIND, 1)
|
||||
.iterator()
|
||||
.next()
|
||||
.getId();
|
||||
return idSupplier.get();
|
||||
}
|
||||
|
||||
/** Resets the global self-allocated id counter (i.e. sets the next id to 1). */
|
||||
@VisibleForTesting
|
||||
public static void resetSelfAllocatedId() {
|
||||
checkState(
|
||||
isSelfAllocated(), "Can only call resetSelfAllocatedId() in unit tests or Beam pipelines");
|
||||
nextSelfAllocatedId.set(1); // ids cannot be zero
|
||||
// TODO(b/201547855): Find a way to allocate a unique ID without datastore.
|
||||
private static class DatastoreIdSupplier implements Supplier<Long> {
|
||||
|
||||
private static final DatastoreIdSupplier INSTANCE = new DatastoreIdSupplier();
|
||||
|
||||
/**
|
||||
* A placeholder String passed into {@code DatastoreService.allocateIds} that ensures that all
|
||||
* IDs are initialized from the same ID pool.
|
||||
*/
|
||||
private static final String APP_WIDE_ALLOCATION_KIND = "common";
|
||||
|
||||
public static DatastoreIdSupplier getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long get() {
|
||||
return DatastoreServiceFactory.getDatastoreService()
|
||||
.allocateIds(APP_WIDE_ALLOCATION_KIND, 1)
|
||||
.iterator()
|
||||
.next()
|
||||
.getId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An ID supplier that allocates an ID from a monotonically increasing atomic {@link long}.
|
||||
*
|
||||
* <p>The generated IDs are only unique within the same JVM. It is not suitable for production use
|
||||
* unless in cases the IDs are not significant.
|
||||
*/
|
||||
public static class SelfAllocatedIdSupplier implements Supplier<Long> {
|
||||
|
||||
private static final SelfAllocatedIdSupplier INSTANCE = new SelfAllocatedIdSupplier();
|
||||
|
||||
/** Counts of used ids for self allocating IDs. */
|
||||
private static final AtomicLong nextSelfAllocatedId = new AtomicLong(1); // ids cannot be zero
|
||||
|
||||
public static SelfAllocatedIdSupplier getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long get() {
|
||||
return nextSelfAllocatedId.getAndIncrement();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
nextSelfAllocatedId.set(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,17 +14,13 @@
|
||||
|
||||
package google.registry.model;
|
||||
|
||||
import static com.google.common.collect.Iterables.transform;
|
||||
import static com.google.common.collect.Maps.transformValues;
|
||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.persistence.VKey;
|
||||
@@ -56,15 +52,6 @@ public abstract class ImmutableObject implements Cloneable {
|
||||
@Target(FIELD)
|
||||
public @interface DoNotHydrate {}
|
||||
|
||||
/**
|
||||
* Indicates that the field should be ignored when comparing an object in the datastore to the
|
||||
* corresponding object in Cloud SQL.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RUNTIME)
|
||||
@Target(FIELD)
|
||||
public @interface DoNotCompare {}
|
||||
|
||||
/**
|
||||
* Indicates that the field stores a null value to indicate an empty set. This is also used in
|
||||
* object comparison.
|
||||
@@ -105,7 +92,7 @@ public abstract class ImmutableObject implements Cloneable {
|
||||
*/
|
||||
protected Map<Field, Object> getSignificantFields() {
|
||||
// Can't use streams or ImmutableMap because we can have null values.
|
||||
Map<Field, Object> result = new LinkedHashMap();
|
||||
Map<Field, Object> result = new LinkedHashMap<>();
|
||||
for (Map.Entry<Field, Object> entry : ModelUtils.getFieldValues(this).entrySet()) {
|
||||
if (!entry.getKey().isAnnotationPresent(Insignificant.class)) {
|
||||
result.put(entry.getKey(), entry.getValue());
|
||||
@@ -190,15 +177,15 @@ public abstract class ImmutableObject implements Cloneable {
|
||||
/** Helper function to recursively hydrate an ImmutableObject. */
|
||||
private static Object hydrate(Object value) {
|
||||
if (value instanceof Key) {
|
||||
if (tm().isOfy()) {
|
||||
return hydrate(auditedOfy().load().key((Key<?>) value).now());
|
||||
}
|
||||
return value;
|
||||
} else if (value instanceof Map) {
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
return transformValues((Map<?, ?>) value, ImmutableObject::hydrate);
|
||||
} else if (value instanceof Collection) {
|
||||
return transform((Collection<?>) value, ImmutableObject::hydrate);
|
||||
} else if (value instanceof ImmutableObject) {
|
||||
}
|
||||
if (value instanceof Collection) {
|
||||
return ((Collection<?>) value).stream().map(ImmutableObject::hydrate);
|
||||
}
|
||||
if (value instanceof ImmutableObject) {
|
||||
return ((ImmutableObject) value).toHydratedString();
|
||||
}
|
||||
return value;
|
||||
@@ -220,7 +207,7 @@ public abstract class ImmutableObject implements Cloneable {
|
||||
}
|
||||
return result;
|
||||
} else if (o instanceof Map) {
|
||||
return Maps.transformValues((Map<?, ?>) o, ImmutableObject::toMapRecursive);
|
||||
return transformValues((Map<?, ?>) o, ImmutableObject::toMapRecursive);
|
||||
} else if (o instanceof Set) {
|
||||
return ((Set<?>) o)
|
||||
.stream()
|
||||
@@ -257,7 +244,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.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 Domain);
|
||||
}
|
||||
|
||||
/** Update the relevant {@link ForeignKeyIndex} to cache the new deletion time. */
|
||||
public static <R extends EppResource> void updateForeignKeyIndexDeletionTime(R resource) {
|
||||
if (resource instanceof ForeignKeyedEppResource) {
|
||||
tm().insert(ForeignKeyIndex.create(resource, resource.getDeletionTime()));
|
||||
}
|
||||
checkState(eppResource instanceof Contact || eppResource instanceof Domain);
|
||||
}
|
||||
|
||||
/** If there is a transfer out, delete the server-approve entities and enqueue a poll message. */
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.annotations;
|
||||
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Annotation for an Objectify {@link Entity} to indicate that it is in the cross-TLD entity group.
|
||||
*
|
||||
* <p>This means that the entity's <code>@Parent</code> field has to have the value of {@link
|
||||
* EntityGroupRoot#getCrossTldKey}.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE})
|
||||
@Inherited
|
||||
public @interface InCrossTld {}
|
||||
@@ -38,7 +38,7 @@ 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.VKey;
|
||||
import google.registry.persistence.WithLongVKey;
|
||||
import google.registry.persistence.WithVKey;
|
||||
import google.registry.persistence.converter.JodaMoneyType;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -212,6 +212,9 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return nullToEmptyImmutableCopy(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public abstract VKey<? extends BillingEvent> createVKey();
|
||||
|
||||
/** Override Buildable.asBuilder() to give this method stronger typing. */
|
||||
@Override
|
||||
public abstract Builder<?, ?> asBuilder();
|
||||
@@ -292,7 +295,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@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. */
|
||||
@@ -470,7 +473,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@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 {
|
||||
|
||||
/**
|
||||
@@ -603,7 +606,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
@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. */
|
||||
|
||||
@@ -24,7 +24,7 @@ import google.registry.model.domain.DomainBase;
|
||||
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.Host;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
@@ -67,12 +67,12 @@ public class BulkQueryEntities {
|
||||
public static Domain assembleDomain(
|
||||
DomainLite domainLite,
|
||||
ImmutableSet<GracePeriod> gracePeriods,
|
||||
ImmutableSet<DelegationSignerData> delegationSignerData,
|
||||
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(domainLite.getUpdateTimestamp());
|
||||
@@ -99,9 +99,7 @@ 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.domainBase.getUpdateTimestamp())
|
||||
|
||||
@@ -17,7 +17,7 @@ package google.registry.model.bulkquery;
|
||||
import google.registry.model.domain.Domain;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
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;
|
||||
@@ -31,7 +31,7 @@ import javax.persistence.Entity;
|
||||
* <p>Please refer to {@link BulkQueryEntities} for more information.
|
||||
*/
|
||||
@Entity(name = "Domain")
|
||||
@WithStringVKey
|
||||
@WithVKey(String.class)
|
||||
@Access(AccessType.FIELD)
|
||||
public class DomainLite extends DomainBase {
|
||||
|
||||
|
||||
@@ -14,26 +14,22 @@
|
||||
|
||||
package google.registry.model.common;
|
||||
|
||||
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import google.registry.model.annotations.InCrossTld;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Transient;
|
||||
|
||||
/** A singleton entity in Datastore. */
|
||||
/**
|
||||
* A singleton entity in the database.
|
||||
*
|
||||
* <p>This class should not be deleted after the migration, because there is still a concept of
|
||||
* singleton in SQL. We should remove the ofy @Id annotation after all of its subclass are Ofy-free.
|
||||
*/
|
||||
@DeleteAfterMigration
|
||||
@MappedSuperclass
|
||||
@InCrossTld
|
||||
public abstract class CrossTldSingleton extends ImmutableObject {
|
||||
|
||||
public static final long SINGLETON_ID = 1; // There is always exactly one of these.
|
||||
|
||||
@Id @javax.persistence.Id long id = SINGLETON_ID;
|
||||
|
||||
@Transient @Parent Key<EntityGroupRoot> parent = getCrossTldKey();
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2017 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.common;
|
||||
|
||||
import com.google.apphosting.api.ApiProxy;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.annotations.DeleteAfterMigration;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The root key for the entity group which is known as the cross-tld entity group for historical
|
||||
* reasons.
|
||||
*
|
||||
* <p>This exists as a storage place for common configuration options and global settings that
|
||||
* aren't updated too frequently. Entities in this entity group are usually cached upon load. The
|
||||
* reason this common entity group exists is because it enables strongly consistent queries and
|
||||
* updates across this seldomly updated data. This shared entity group also helps cut down on a
|
||||
* potential ballooning in the number of entity groups enlisted in transactions.
|
||||
*
|
||||
* <p>Historically, each TLD used to have a separate namespace, and all entities for a TLD were in a
|
||||
* single EntityGroupRoot for that TLD. Hence why there was a "cross-tld" entity group -- it was the
|
||||
* entity group for the single namespace where global data applicable for all TLDs lived.
|
||||
*/
|
||||
@Entity
|
||||
@DeleteAfterMigration
|
||||
public class EntityGroupRoot extends BackupGroupRoot {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
/** The root key for cross-tld resources such as registrars. */
|
||||
public static @Nullable Key<EntityGroupRoot> getCrossTldKey() {
|
||||
// If we cannot get a current environment, calling Key.create() will fail. Instead we return a
|
||||
// null in cases where this key is not actually needed (for example when loading an entity from
|
||||
// SQL) to initialize an object, to avoid having to register a DatastoreEntityExtension in
|
||||
// tests.
|
||||
return ApiProxy.getCurrentEnvironment() == null
|
||||
? null
|
||||
: Key.create(EntityGroupRoot.class, "cross-tld");
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import static org.joda.time.DateTimeZone.UTC;
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.ContiguousSet;
|
||||
import com.google.common.collect.Range;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
@@ -44,7 +43,6 @@ import org.joda.time.DateTime;
|
||||
* allows it to be embeddable with no translation needed and also delays parsing of the string on
|
||||
* load until it's actually needed.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class TimeOfYear extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
|
||||
@@ -22,22 +22,40 @@ import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.BackupGroupRoot;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/** A console user, either a registry employee or a registrar partner. */
|
||||
public class User extends ImmutableObject implements Buildable {
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
@Index(columnList = "gaiaId", name = "user_gaia_id_idx"),
|
||||
@Index(columnList = "emailAddress", name = "user_email_address_idx")
|
||||
})
|
||||
public class User extends BackupGroupRoot implements Buildable {
|
||||
|
||||
/** Autogenerated unique ID of this user. */
|
||||
private long id;
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/** GAIA ID associated with the user in question. */
|
||||
@Column(nullable = false)
|
||||
private String gaiaId;
|
||||
|
||||
/** Email address of the user in question. */
|
||||
@Column(nullable = false)
|
||||
private String emailAddress;
|
||||
|
||||
/** Roles (which grant permissions) associated with this user. */
|
||||
@Column(nullable = false)
|
||||
private UserRoles userRoles;
|
||||
|
||||
/**
|
||||
@@ -49,7 +67,7 @@ public class User extends ImmutableObject implements Buildable {
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
public long getId() {
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package google.registry.model.console;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/** Data access object for {@link User} objects to simplify saving and retrieval. */
|
||||
public class UserDao {
|
||||
|
||||
/** Retrieves the one user with this email address if it exists. */
|
||||
public static Optional<User> loadUser(String emailAddress) {
|
||||
return jpaTm()
|
||||
.transact(
|
||||
() ->
|
||||
jpaTm()
|
||||
.query("FROM User WHERE emailAddress = :emailAddress", User.class)
|
||||
.setParameter("emailAddress", emailAddress)
|
||||
.getResultStream()
|
||||
.findFirst());
|
||||
}
|
||||
|
||||
/** Saves the given user, checking that no existing user already exists with this email. */
|
||||
public static void saveUser(User user) {
|
||||
jpaTm()
|
||||
.transact(
|
||||
() -> {
|
||||
// Check for an existing user (the unique constraint protects us, but this gives a
|
||||
// nicer exception)
|
||||
Optional<User> maybeSavedUser = loadUser(user.getEmailAddress());
|
||||
if (maybeSavedUser.isPresent()) {
|
||||
User savedUser = maybeSavedUser.get();
|
||||
checkArgument(
|
||||
savedUser.getId().equals(user.getId()),
|
||||
String.format(
|
||||
"Attempted save of User with email address %s and ID %s, user with that"
|
||||
+ " email already exists with ID %s",
|
||||
user.getEmailAddress(), user.getId(), savedUser.getId()));
|
||||
}
|
||||
jpaTm().put(user);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@ import com.google.common.collect.ImmutableMap;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
|
||||
/**
|
||||
* Contains the global and per-registrar roles for a given user.
|
||||
@@ -28,15 +32,19 @@ import java.util.Map;
|
||||
* <p>See <a href="https://go/nomulus-console-authz">go/nomulus-console-authz</a> for more
|
||||
* information.
|
||||
*/
|
||||
@Embeddable
|
||||
public class UserRoles extends ImmutableObject implements Buildable {
|
||||
|
||||
/** Whether the user is a global admin, who has access to everything. */
|
||||
@Column(nullable = false)
|
||||
private boolean isAdmin = false;
|
||||
|
||||
/**
|
||||
* The global role (e.g. {@link GlobalRole#SUPPORT_AGENT}) that the user has across all
|
||||
* registrars.
|
||||
*/
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
private GlobalRole globalRole = GlobalRole.NONE;
|
||||
|
||||
/** Any per-registrar roles that this user may have. */
|
||||
|
||||
@@ -14,15 +14,17 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
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;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Index;
|
||||
import javax.persistence.Table;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -32,35 +34,35 @@ import org.joda.time.DateTime;
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "Contact")
|
||||
@javax.persistence.Table(
|
||||
@com.googlecode.objectify.annotation.Entity
|
||||
@Table(
|
||||
name = "Contact",
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "creationTime"),
|
||||
@javax.persistence.Index(columnList = "currentSponsorRegistrarId"),
|
||||
@javax.persistence.Index(columnList = "deletionTime"),
|
||||
@javax.persistence.Index(columnList = "contactId"),
|
||||
@javax.persistence.Index(columnList = "searchName")
|
||||
@Index(columnList = "creationTime"),
|
||||
@Index(columnList = "currentSponsorRegistrarId"),
|
||||
@Index(columnList = "deletionTime"),
|
||||
@Index(columnList = "contactId"),
|
||||
@Index(columnList = "searchName")
|
||||
})
|
||||
@ExternalMessagingName("contact")
|
||||
@WithStringVKey
|
||||
@WithVKey(String.class)
|
||||
@Access(AccessType.FIELD)
|
||||
public class ContactResource extends ContactBase implements ForeignKeyedEppResource {
|
||||
public class Contact extends ContactBase implements ForeignKeyedEppResource {
|
||||
|
||||
@Override
|
||||
public VKey<ContactResource> createVKey() {
|
||||
return VKey.create(ContactResource.class, getRepoId(), Key.create(this));
|
||||
public VKey<Contact> createVKey() {
|
||||
return VKey.createSql(Contact.class, getRepoId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@javax.persistence.Id
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
public String getRepoId() {
|
||||
return super.getRepoId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContactResource cloneProjectedAtTime(DateTime now) {
|
||||
public Contact cloneProjectedAtTime(DateTime now) {
|
||||
return ContactBase.cloneContactProjectedAtTime(this, now);
|
||||
}
|
||||
|
||||
@@ -69,12 +71,12 @@ public class ContactResource extends ContactBase implements ForeignKeyedEppResou
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link ContactResource}, since it is immutable. */
|
||||
public static class Builder extends ContactBase.Builder<ContactResource, Builder> {
|
||||
/** A builder for constructing {@link Contact}, since it is immutable. */
|
||||
public static class Builder extends ContactBase.Builder<Contact, Builder> {
|
||||
|
||||
public Builder() {}
|
||||
|
||||
private Builder(ContactResource instance) {
|
||||
private Builder(Contact instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.Address;
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
@@ -30,7 +29,6 @@ import javax.persistence.Embeddable;
|
||||
*
|
||||
* @see PostalInfo
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class ContactAddress extends Address {
|
||||
|
||||
|
||||
@@ -14,13 +14,12 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/** A version of authInfo specifically for contacts. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
@XmlType(namespace = "urn:ietf:params:xml:ns:contact-1.0")
|
||||
public class ContactAuthInfo extends AuthInfo {
|
||||
public static ContactAuthInfo create(PasswordAuth pw) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
@@ -66,9 +67,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
|
||||
/**
|
||||
* Localized postal info for the contact. All contained values must be representable in the 7-bit
|
||||
* US-ASCII character set. Personal info; cleared by {@link ContactResource.Builder#wipeOut}.
|
||||
* US-ASCII character set. Personal info; cleared by {@link Contact.Builder#wipeOut}.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "name", column = @Column(name = "addr_local_name")),
|
||||
@@ -94,9 +95,9 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
|
||||
/**
|
||||
* Internationalized postal info for the contact. Personal info; cleared by {@link
|
||||
* ContactResource.Builder#wipeOut}.
|
||||
* Contact.Builder#wipeOut}.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "name", column = @Column(name = "addr_i18n_name")),
|
||||
@@ -123,12 +124,12 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
/**
|
||||
* Contact name used for name searches. This is set automatically to be the internationalized
|
||||
* postal name, or if null, the localized postal name, or if that is null as well, null. Personal
|
||||
* info; cleared by {@link ContactResource.Builder#wipeOut}.
|
||||
* info; cleared by {@link Contact.Builder#wipeOut}.
|
||||
*/
|
||||
@Index String searchName;
|
||||
|
||||
/** Contact’s voice number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
/** Contact’s voice number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "phoneNumber", column = @Column(name = "voice_phone_number")),
|
||||
@@ -136,8 +137,8 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
})
|
||||
ContactPhoneNumber voice;
|
||||
|
||||
/** Contact’s fax number. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
/** Contact’s fax number. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "phoneNumber", column = @Column(name = "fax_phone_number")),
|
||||
@@ -145,11 +146,12 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
})
|
||||
ContactPhoneNumber fax;
|
||||
|
||||
/** Contact’s email address. Personal info; cleared by {@link ContactResource.Builder#wipeOut}. */
|
||||
/** Contact’s email address. Personal info; cleared by {@link Contact.Builder#wipeOut}. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
String email;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the contact. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
|
||||
@@ -158,7 +160,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
ContactAuthInfo authInfo;
|
||||
|
||||
/** Data about any pending or past transfers on this contact. */
|
||||
ContactTransferData transferData;
|
||||
@Ignore ContactTransferData transferData;
|
||||
|
||||
/**
|
||||
* The time that this resource was last transferred.
|
||||
@@ -171,6 +173,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
// the wipeOut() function, so that data is not kept around for deleted contacts.
|
||||
|
||||
/** Disclosure policy. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "name", column = @Column(name = "disclose_types_name")),
|
||||
@@ -187,7 +190,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
public VKey<? extends ContactBase> createVKey() {
|
||||
throw new UnsupportedOperationException(
|
||||
"ContactBase is not an actual persisted entity you can create a key to;"
|
||||
+ " use ContactResource instead");
|
||||
+ " use Contact instead");
|
||||
}
|
||||
|
||||
@OnLoad
|
||||
@@ -291,7 +294,7 @@ public class ContactBase extends EppResource implements ResourceWithTransferData
|
||||
return new Builder<>(clone(this));
|
||||
}
|
||||
|
||||
/** A builder for constructing {@link ContactResource}, since it is immutable. */
|
||||
/** A builder for constructing {@link Contact}, since it is immutable. */
|
||||
public static class Builder<T extends ContactBase, B extends Builder<T, B>>
|
||||
extends EppResource.Builder<T, B> implements BuilderWithTransferData<ContactTransferData, B> {
|
||||
|
||||
|
||||
@@ -34,13 +34,13 @@ import javax.xml.bind.annotation.XmlType;
|
||||
import javax.xml.bind.annotation.adapters.CollapsedStringAdapter;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
/** A collection of {@link ContactResource} commands. */
|
||||
/** A collection of {@link Contact} commands. */
|
||||
public class ContactCommand {
|
||||
|
||||
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@XmlTransient
|
||||
public static class ContactCreateOrChange extends ImmutableObject
|
||||
implements ResourceCreateOrChange<ContactResource.Builder> {
|
||||
implements ResourceCreateOrChange<Contact.Builder> {
|
||||
|
||||
/** Postal info for the contact. */
|
||||
List<PostalInfo> postalInfo;
|
||||
@@ -111,13 +111,13 @@ public class ContactCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* A create command for a {@link ContactResource}, mapping "createType" from <a
|
||||
* A create command for a {@link Contact}, mapping "createType" from <a
|
||||
* href="http://tools.ietf.org/html/rfc5733">RFC5733</a>}.
|
||||
*/
|
||||
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
|
||||
@XmlRootElement
|
||||
public static class Create extends ContactCreateOrChange
|
||||
implements SingleResourceCommand, ResourceCreateOrChange<ContactResource.Builder> {
|
||||
implements SingleResourceCommand, ResourceCreateOrChange<Contact.Builder> {
|
||||
/**
|
||||
* Unique identifier for this contact.
|
||||
*
|
||||
@@ -139,29 +139,29 @@ public class ContactCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/** A delete command for a {@link ContactResource}. */
|
||||
/** A delete command for a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
public static class Delete extends AbstractSingleResourceCommand {}
|
||||
|
||||
/** An info request for a {@link ContactResource}. */
|
||||
/** An info request for a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {"targetId", "authInfo"})
|
||||
public static class Info extends AbstractContactAuthCommand {}
|
||||
|
||||
/** A check request for {@link ContactResource}. */
|
||||
/** A check request for {@link Contact}. */
|
||||
@XmlRootElement
|
||||
public static class Check extends ResourceCheck {}
|
||||
|
||||
/** A transfer operation for a {@link ContactResource}. */
|
||||
/** A transfer operation for a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {"targetId", "authInfo"})
|
||||
public static class Transfer extends AbstractContactAuthCommand {}
|
||||
|
||||
/** An update to a {@link ContactResource}. */
|
||||
/** An update to a {@link Contact}. */
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
|
||||
public static class Update
|
||||
extends ResourceUpdate<Update.AddRemove, ContactResource.Builder, Update.Change> {
|
||||
extends ResourceUpdate<Update.AddRemove, Contact.Builder, Update.Change> {
|
||||
|
||||
@XmlElement(name = "chg")
|
||||
protected Change innerChange;
|
||||
|
||||
@@ -58,9 +58,9 @@ import javax.persistence.PostLoad;
|
||||
@IdClass(ContactHistoryId.class)
|
||||
public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
|
||||
// Store ContactBase instead of ContactResource so we don't pick up its @Id
|
||||
// Store ContactBase instead of Contact so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
@DoNotCompare @Nullable ContactBase contactBase;
|
||||
@Nullable ContactBase contactBase;
|
||||
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
@@ -73,7 +73,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
/** This method is private because it is only used by Hibernate. */
|
||||
@SuppressWarnings("unused")
|
||||
private void setContactRepoId(String contactRepoId) {
|
||||
parent = Key.create(ContactResource.class, contactRepoId);
|
||||
parent = Key.create(Contact.class, contactRepoId);
|
||||
}
|
||||
|
||||
@Id
|
||||
@@ -98,9 +98,9 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
return Optional.ofNullable(contactBase);
|
||||
}
|
||||
|
||||
/** The key to the {@link ContactResource} this is based off of. */
|
||||
public VKey<ContactResource> getParentVKey() {
|
||||
return VKey.create(ContactResource.class, getContactRepoId());
|
||||
/** The key to the {@link Contact} this is based off of. */
|
||||
public VKey<Contact> getParentVKey() {
|
||||
return VKey.create(Contact.class, getContactRepoId());
|
||||
}
|
||||
|
||||
/** Creates a {@link VKey} instance for this entity. */
|
||||
@@ -112,8 +112,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
|
||||
@Override
|
||||
public Optional<? extends EppResource> getResourceAtPointInTime() {
|
||||
return getContactBase()
|
||||
.map(contactBase -> new ContactResource.Builder().copyFrom(contactBase).build());
|
||||
return getContactBase().map(contactBase -> new Contact.Builder().copyFrom(contactBase).build());
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
@@ -210,7 +209,7 @@ public class ContactHistory extends HistoryEntry implements UnsafeSerializable {
|
||||
}
|
||||
|
||||
public Builder setContactRepoId(String contactRepoId) {
|
||||
getInstance().parent = Key.create(ContactResource.class, contactRepoId);
|
||||
getInstance().parent = Key.create(Contact.class, contactRepoId);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,20 +14,18 @@
|
||||
|
||||
package google.registry.model.contact;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.PhoneNumber;
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
/**
|
||||
* EPP Contact Phone Number
|
||||
*
|
||||
* <p>This class is embedded inside a {@link ContactResource} hold the phone number of an EPP
|
||||
* contact. The fields are all defined in the parent class {@link PhoneNumber}, but the subclass is
|
||||
* still necessary to pick up the contact namespace.
|
||||
* <p>This class is embedded inside a {@link Contact} hold the phone number of an EPP contact. The
|
||||
* fields are all defined in the parent class {@link PhoneNumber}, but the subclass is still
|
||||
* necessary to pick up the contact namespace.
|
||||
*
|
||||
* @see ContactResource
|
||||
* @see Contact
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class ContactPhoneNumber extends PhoneNumber {
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.model.contact;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
@@ -30,7 +29,6 @@ import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/** The "discloseType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
|
||||
public class Disclose extends ImmutableObject implements UnsafeSerializable {
|
||||
@@ -79,7 +77,6 @@ public class Disclose extends ImmutableObject implements UnsafeSerializable {
|
||||
}
|
||||
|
||||
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
public static class PostalInfoChoice extends ImmutableObject implements Serializable {
|
||||
@XmlAttribute
|
||||
PostalInfo.Type type;
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.model.contact;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.Buildable.Overlayable;
|
||||
import google.registry.model.ImmutableObject;
|
||||
@@ -36,7 +35,6 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
* Implementation of both "postalInfoType" and "chgPostalInfoType" from <a href=
|
||||
* "http://tools.ietf.org/html/rfc5733">RFC5733</a>.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "address", "type"})
|
||||
public class PostalInfo extends ImmutableObject
|
||||
|
||||
@@ -17,12 +17,11 @@ package google.registry.model.domain;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.xml.bind.annotation.XmlEnumValue;
|
||||
@@ -45,7 +44,6 @@ import javax.xml.bind.annotation.XmlEnumValue;
|
||||
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
|
||||
* - Contact and Client Identifiers</a>
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class DesignatedContact extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@@ -64,7 +62,7 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
|
||||
REGISTRANT
|
||||
}
|
||||
|
||||
public static DesignatedContact create(Type type, VKey<ContactResource> contact) {
|
||||
public static DesignatedContact create(Type type, VKey<Contact> contact) {
|
||||
DesignatedContact instance = new DesignatedContact();
|
||||
instance.type = type;
|
||||
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
|
||||
@@ -74,14 +72,14 @@ public class DesignatedContact extends ImmutableObject implements UnsafeSerializ
|
||||
|
||||
Type type;
|
||||
|
||||
@Index Key<ContactResource> contact;
|
||||
@Ignore VKey<ContactResource> contactVKey;
|
||||
@Index Key<Contact> contact;
|
||||
@Ignore VKey<Contact> contactVKey;
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getContactKey() {
|
||||
public VKey<Contact> getContactKey() {
|
||||
return contactVKey;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,10 @@ import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import google.registry.persistence.WithVKey;
|
||||
import java.util.Set;
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
@@ -66,7 +66,7 @@ import org.joda.time.DateTime;
|
||||
@Index(columnList = "transfer_billing_event_id"),
|
||||
@Index(columnList = "transfer_billing_recurrence_id")
|
||||
})
|
||||
@WithStringVKey
|
||||
@WithVKey(String.class)
|
||||
@ExternalMessagingName("domain")
|
||||
@Access(AccessType.FIELD)
|
||||
public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
@@ -90,13 +90,13 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
@Access(AccessType.PROPERTY)
|
||||
@Column(name = "host_repo_id")
|
||||
public Set<VKey<Host>> getNsHosts() {
|
||||
return super.nsHosts;
|
||||
return nsHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of {@link GracePeriod} associated with the domain.
|
||||
*
|
||||
* <p>This is the getter method specific for Hibernate to access the field so it is set to
|
||||
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
|
||||
* private. The caller can use the public {@link #getGracePeriods()} to get the grace periods.
|
||||
*
|
||||
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
|
||||
@@ -104,10 +104,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
* deleting the whole entry from the table when the {@link GracePeriod} is removed from the set.
|
||||
*/
|
||||
@Access(AccessType.PROPERTY)
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
orphanRemoval = true)
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(
|
||||
name = "domainRepoId",
|
||||
referencedColumnName = "repoId",
|
||||
@@ -119,28 +116,24 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of {@link DelegationSignerData} associated with the domain.
|
||||
* Returns the set of {@link DomainDsData} associated with the domain.
|
||||
*
|
||||
* <p>This is the getter method specific for Hibernate to access the field so it is set to
|
||||
* <p>This is the getter method specific for Hibernate to access the field, so it is set to
|
||||
* private. The caller can use the public {@link #getDsData()} to get the DS data.
|
||||
*
|
||||
* <p>Note that we need to set `insertable = false, updatable = false` for @JoinColumn, otherwise
|
||||
* Hibernate would try to set the foreign key to null(through an UPDATE TABLE sql) instead of
|
||||
* deleting the whole entry from the table when the {@link DelegationSignerData} is removed from
|
||||
* the set.
|
||||
* deleting the whole entry from the table when the {@link DomainDsData} is removed from the set.
|
||||
*/
|
||||
@Access(AccessType.PROPERTY)
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
orphanRemoval = true)
|
||||
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
|
||||
@JoinColumn(
|
||||
name = "domainRepoId",
|
||||
referencedColumnName = "repoId",
|
||||
insertable = false,
|
||||
updatable = false)
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private Set<DelegationSignerData> getInternalDelegationSignerData() {
|
||||
private Set<DomainDsData> getInternalDelegationSignerData() {
|
||||
return dsData;
|
||||
}
|
||||
|
||||
@@ -154,7 +147,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
|
||||
@Override
|
||||
public VKey<Domain> createVKey() {
|
||||
return VKey.create(Domain.class, getRepoId(), Key.create(this));
|
||||
return VKey.createSql(Domain.class, getRepoId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -182,10 +175,9 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||
}
|
||||
|
||||
public Builder copyFrom(DomainBase domainBase) {
|
||||
this.getInstance().copyUpdateTimestamp(domainBase);
|
||||
return this.setAuthInfo(domainBase.getAuthInfo())
|
||||
.setAutorenewPollMessage(
|
||||
domainBase.getAutorenewPollMessage(), domainBase.getAutorenewPollMessageHistoryId())
|
||||
getInstance().copyUpdateTimestamp(domainBase);
|
||||
return setAuthInfo(domainBase.getAuthInfo())
|
||||
.setAutorenewPollMessage(domainBase.getAutorenewPollMessage())
|
||||
.setAutorenewBillingEvent(domainBase.getAutorenewBillingEvent())
|
||||
.setAutorenewEndTime(domainBase.getAutorenewEndTime())
|
||||
.setContacts(domainBase.getContacts())
|
||||
|
||||
@@ -14,12 +14,11 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.eppcommon.AuthInfo;
|
||||
import javax.persistence.Embeddable;
|
||||
|
||||
/** A version of authInfo specifically for domains. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class DomainAuthInfo extends AuthInfo {
|
||||
public static DomainAuthInfo create(PasswordAuth pw) {
|
||||
DomainAuthInfo instance = new DomainAuthInfo();
|
||||
|
||||
@@ -41,7 +41,6 @@ import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
@@ -51,16 +50,15 @@ import google.registry.flows.ResourceFlowUtils;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.EppResource.ResourceWithTransferData;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.domain.launch.LaunchNotice;
|
||||
import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.domain.secdns.DomainDsData;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.domain.token.AllocationToken.TokenType;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.poll.PollMessage;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tld.Registry;
|
||||
import google.registry.model.transfer.DomainTransferData;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
@@ -115,30 +113,28 @@ public class DomainBase extends EppResource
|
||||
* Fully qualified domain name (puny-coded), which serves as the foreign key for this domain.
|
||||
*
|
||||
* <p>This is only unique in the sense that for any given lifetime specified as the time range
|
||||
* from (creationTime, deletionTime) there can only be one domain in Datastore with this name.
|
||||
* from (creationTime, deletionTime) there can only be one domain in the database with this name.
|
||||
* However, there can be many domains with the same name and non-overlapping lifetimes.
|
||||
*
|
||||
* @invariant fullyQualifiedDomainName == fullyQualifiedDomainName.toLowerCase(Locale.ENGLISH)
|
||||
* @invariant domainName == domainName.toLowerCase(Locale.ENGLISH)
|
||||
*/
|
||||
// TODO(b/177567432): Rename this to domainName when we are off Datastore
|
||||
@Column(name = "domainName")
|
||||
@Index
|
||||
String fullyQualifiedDomainName;
|
||||
@Index String domainName;
|
||||
|
||||
/** The top level domain this is under, dernormalized from {@link #fullyQualifiedDomainName}. */
|
||||
/** The top level domain this is under, dernormalized from {@link #domainName}. */
|
||||
@Index String tld;
|
||||
|
||||
/** References to hosts that are the nameservers for the domain. */
|
||||
@EmptySetToNull @Index @Transient Set<VKey<Host>> nsHosts;
|
||||
|
||||
/** Contacts. */
|
||||
VKey<ContactResource> adminContact;
|
||||
VKey<Contact> adminContact;
|
||||
|
||||
VKey<ContactResource> billingContact;
|
||||
VKey<ContactResource> techContact;
|
||||
VKey<ContactResource> registrantContact;
|
||||
VKey<Contact> billingContact;
|
||||
VKey<Contact> techContact;
|
||||
VKey<Contact> registrantContact;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the domain. */
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "pw.value", column = @Column(name = "auth_info_value")),
|
||||
@@ -147,13 +143,14 @@ public class DomainBase extends EppResource
|
||||
DomainAuthInfo authInfo;
|
||||
|
||||
/** Data used to construct DS records for this domain. */
|
||||
@Transient Set<DelegationSignerData> dsData;
|
||||
@Ignore @Transient Set<DomainDsData> dsData;
|
||||
|
||||
/**
|
||||
* The claims notice supplied when this domain was created, if there was one.
|
||||
*
|
||||
* <p>It's {@literal @}XmlTransient because it's not returned in an info response.
|
||||
*/
|
||||
@Ignore
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "noticeId.tcnId", column = @Column(name = "launch_notice_tcn_id")),
|
||||
@@ -216,15 +213,8 @@ public class DomainBase extends EppResource
|
||||
@Column(name = "autorenew_poll_message_id")
|
||||
VKey<PollMessage.Autorenew> autorenewPollMessage;
|
||||
|
||||
/**
|
||||
* History record for the autorenew poll message.
|
||||
*
|
||||
* <p>Here so we can restore the original ofy key from sql.
|
||||
*/
|
||||
@Ignore Long autorenewPollMessageHistoryId;
|
||||
|
||||
/** The unexpired grace periods for this domain (some of which may not be active yet). */
|
||||
@Transient Set<GracePeriod> gracePeriods;
|
||||
@Ignore @Transient Set<GracePeriod> gracePeriods;
|
||||
|
||||
/**
|
||||
* The id of the signed mark that was used to create this domain in sunrise.
|
||||
@@ -235,7 +225,7 @@ public class DomainBase extends EppResource
|
||||
String smdId;
|
||||
|
||||
/** Data about any pending or past transfers on this domain. */
|
||||
DomainTransferData transferData;
|
||||
@Ignore DomainTransferData transferData;
|
||||
|
||||
/**
|
||||
* The time that this resource was last transferred.
|
||||
@@ -284,7 +274,7 @@ public class DomainBase extends EppResource
|
||||
@Ignore DateTime dnsRefreshRequestTime;
|
||||
|
||||
/** The {@link AllocationToken} for the package this domain is currently a part of. */
|
||||
@Nullable VKey<AllocationToken> currentPackageToken;
|
||||
@Ignore @Nullable VKey<AllocationToken> currentPackageToken;
|
||||
|
||||
/**
|
||||
* Returns the DNS refresh request time iff this domain's DNS needs refreshing, otherwise absent.
|
||||
@@ -293,15 +283,6 @@ public class DomainBase extends EppResource
|
||||
return Optional.ofNullable(dnsRefreshRequestTime);
|
||||
}
|
||||
|
||||
public static <T> VKey<T> restoreOfyFrom(Key<Domain> domainKey, VKey<T> key, Long historyId) {
|
||||
if (historyId == null) {
|
||||
// This is a legacy key (or a null key, in which case this works too)
|
||||
return VKey.restoreOfyFrom(key, EntityGroupRoot.class, "per-tld");
|
||||
} else {
|
||||
return VKey.restoreOfyFrom(key, domainKey, HistoryEntry.class, historyId);
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getSubordinateHosts() {
|
||||
return nullToEmptyImmutableCopy(subordinateHosts);
|
||||
}
|
||||
@@ -322,10 +303,6 @@ public class DomainBase extends EppResource
|
||||
return autorenewPollMessage;
|
||||
}
|
||||
|
||||
public Long getAutorenewPollMessageHistoryId() {
|
||||
return autorenewPollMessageHistoryId;
|
||||
}
|
||||
|
||||
public ImmutableSet<GracePeriod> getGracePeriods() {
|
||||
return nullToEmptyImmutableCopy(gracePeriods);
|
||||
}
|
||||
@@ -361,14 +338,14 @@ public class DomainBase extends EppResource
|
||||
|
||||
@Override
|
||||
public String getForeignKey() {
|
||||
return fullyQualifiedDomainName;
|
||||
return domainName;
|
||||
}
|
||||
|
||||
public String getDomainName() {
|
||||
return fullyQualifiedDomainName;
|
||||
return domainName;
|
||||
}
|
||||
|
||||
public ImmutableSet<DelegationSignerData> getDsData() {
|
||||
public ImmutableSet<DomainDsData> getDsData() {
|
||||
return nullToEmptyImmutableCopy(dsData);
|
||||
}
|
||||
|
||||
@@ -413,9 +390,9 @@ public class DomainBase extends EppResource
|
||||
|
||||
// Hibernate needs this in order to populate dsData but no one else should ever use it
|
||||
@SuppressWarnings("UnusedMethod")
|
||||
private void setInternalDelegationSignerData(Set<DelegationSignerData> dsData) {
|
||||
private void setInternalDelegationSignerData(Set<DomainDsData> dsData) {
|
||||
if (this.dsData instanceof PersistentSet) {
|
||||
Set<DelegationSignerData> nonNullDsData = nullToEmpty(dsData);
|
||||
Set<DomainDsData> nonNullDsData = nullToEmpty(dsData);
|
||||
this.dsData.retainAll(nonNullDsData);
|
||||
this.dsData.addAll(nonNullDsData);
|
||||
} else {
|
||||
@@ -509,9 +486,7 @@ public class DomainBase extends EppResource
|
||||
// Set the speculatively-written new autorenew events as the domain's autorenew
|
||||
// events.
|
||||
.setAutorenewBillingEvent(transferData.getServerApproveAutorenewEvent())
|
||||
.setAutorenewPollMessage(
|
||||
transferData.getServerApproveAutorenewPollMessage(),
|
||||
transferData.getServerApproveAutorenewPollMessageHistoryId());
|
||||
.setAutorenewPollMessage(transferData.getServerApproveAutorenewPollMessage());
|
||||
if (transferData.getTransferPeriod().getValue() == 1) {
|
||||
// Set the grace period using a key to the prescheduled transfer billing event. Not using
|
||||
// GracePeriod.forBillingEvent() here in order to avoid the actual Datastore fetch.
|
||||
@@ -615,19 +590,19 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
/** A key to the registrant who registered this domain. */
|
||||
public VKey<ContactResource> getRegistrant() {
|
||||
public VKey<Contact> getRegistrant() {
|
||||
return registrantContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getAdminContact() {
|
||||
public VKey<Contact> getAdminContact() {
|
||||
return adminContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getBillingContact() {
|
||||
public VKey<Contact> getBillingContact() {
|
||||
return billingContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getTechContact() {
|
||||
public VKey<Contact> getTechContact() {
|
||||
return techContact;
|
||||
}
|
||||
|
||||
@@ -641,7 +616,7 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
/** Returns all referenced contacts from this domain. */
|
||||
public ImmutableSet<VKey<ContactResource>> getReferencedContacts() {
|
||||
public ImmutableSet<VKey<Contact>> getReferencedContacts() {
|
||||
return nullToEmptyImmutableCopy(getAllContacts(true)).stream()
|
||||
.map(DesignatedContact::getContactKey)
|
||||
.filter(Objects::nonNull)
|
||||
@@ -751,9 +726,9 @@ public class DomainBase extends EppResource
|
||||
// If there is no autorenew end time, set it to END_OF_TIME.
|
||||
instance.autorenewEndTime = firstNonNull(getInstance().autorenewEndTime, END_OF_TIME);
|
||||
|
||||
checkArgumentNotNull(emptyToNull(instance.fullyQualifiedDomainName), "Missing domainName");
|
||||
checkArgumentNotNull(emptyToNull(instance.domainName), "Missing domainName");
|
||||
checkArgumentNotNull(instance.getRegistrant(), "Missing registrant");
|
||||
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
|
||||
instance.tld = getTldFromDomainName(instance.domainName);
|
||||
|
||||
T newDomain = super.build();
|
||||
// Hibernate throws exception if gracePeriods or dsData is null because we enabled all
|
||||
@@ -774,17 +749,17 @@ public class DomainBase extends EppResource
|
||||
domainName.equals(canonicalizeHostname(domainName)),
|
||||
"Domain name %s not in puny-coded, lower-case form",
|
||||
domainName);
|
||||
getInstance().fullyQualifiedDomainName = domainName;
|
||||
getInstance().domainName = domainName;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDsData(ImmutableSet<DelegationSignerData> dsData) {
|
||||
public B setDsData(ImmutableSet<DomainDsData> dsData) {
|
||||
getInstance().dsData = dsData;
|
||||
getInstance().resetUpdateTimestamp();
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setRegistrant(VKey<ContactResource> registrant) {
|
||||
public B setRegistrant(VKey<Contact> registrant) {
|
||||
// Set the registrant field specifically.
|
||||
getInstance().registrantContact = registrant;
|
||||
return thisCastToDerived();
|
||||
@@ -886,11 +861,8 @@ public class DomainBase extends EppResource
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setAutorenewPollMessage(
|
||||
@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage,
|
||||
@Nullable Long autorenewPollMessageHistoryId) {
|
||||
public B setAutorenewPollMessage(@Nullable VKey<PollMessage.Autorenew> autorenewPollMessage) {
|
||||
getInstance().autorenewPollMessage = autorenewPollMessage;
|
||||
getInstance().autorenewPollMessageHistoryId = autorenewPollMessageHistoryId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -948,6 +920,21 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
public B setCurrentPackageToken(@Nullable VKey<AllocationToken> currentPackageToken) {
|
||||
if (currentPackageToken == null) {
|
||||
getInstance().currentPackageToken = currentPackageToken;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
AllocationToken token =
|
||||
tm().transact(() -> tm().loadByKeyIfPresent(currentPackageToken))
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
String.format(
|
||||
"The package token %s does not exist",
|
||||
currentPackageToken.getSqlKey())));
|
||||
checkArgument(
|
||||
token.getTokenType().equals(TokenType.PACKAGE),
|
||||
"The currentPackageToken must have a PACKAGE TokenType");
|
||||
getInstance().currentPackageToken = currentPackageToken;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package google.registry.model.domain;
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static com.google.common.collect.Maps.transformValues;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static google.registry.util.CollectionUtils.difference;
|
||||
import static google.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
@@ -31,15 +30,15 @@ import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ForeignKeyUtils;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.contact.Contact;
|
||||
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
|
||||
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
|
||||
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import google.registry.model.host.Host;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -80,7 +79,7 @@ public class DomainCommand {
|
||||
String registrantContactId;
|
||||
|
||||
/** A resolved key to the registrant who registered this domain. */
|
||||
@XmlTransient VKey<ContactResource> registrant;
|
||||
@XmlTransient VKey<Contact> registrant;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the domain. */
|
||||
DomainAuthInfo authInfo;
|
||||
@@ -90,7 +89,7 @@ public class DomainCommand {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public VKey<ContactResource> getRegistrant() {
|
||||
public VKey<Contact> getRegistrant() {
|
||||
return registrant;
|
||||
}
|
||||
|
||||
@@ -106,9 +105,9 @@ public class DomainCommand {
|
||||
@XmlRootElement
|
||||
@XmlType(
|
||||
propOrder = {
|
||||
"fullyQualifiedDomainName",
|
||||
"domainName",
|
||||
"period",
|
||||
"nameserverFullyQualifiedHostNames",
|
||||
"nameserverHostNames",
|
||||
"registrantContactId",
|
||||
"foreignKeyedDesignatedContacts",
|
||||
"authInfo"
|
||||
@@ -118,12 +117,12 @@ public class DomainCommand {
|
||||
|
||||
/** Fully qualified domain name, which serves as a unique identifier for this domain. */
|
||||
@XmlElement(name = "name")
|
||||
String fullyQualifiedDomainName;
|
||||
String domainName;
|
||||
|
||||
/** Fully qualified host names of the hosts that are the nameservers for the domain. */
|
||||
@XmlElementWrapper(name = "ns")
|
||||
@XmlElement(name = "hostObj")
|
||||
Set<String> nameserverFullyQualifiedHostNames;
|
||||
Set<String> nameserverHostNames;
|
||||
|
||||
/** Resolved keys to hosts that are the nameservers for the domain. */
|
||||
@XmlTransient Set<VKey<Host>> nameservers;
|
||||
@@ -145,15 +144,15 @@ public class DomainCommand {
|
||||
|
||||
@Override
|
||||
public String getTargetId() {
|
||||
return fullyQualifiedDomainName;
|
||||
return domainName;
|
||||
}
|
||||
|
||||
public String getFullyQualifiedDomainName() {
|
||||
return fullyQualifiedDomainName;
|
||||
public String getDomainName() {
|
||||
return domainName;
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getNameserverFullyQualifiedHostNames() {
|
||||
return nullToEmptyImmutableCopy(nameserverFullyQualifiedHostNames);
|
||||
public ImmutableSet<String> getNameserverHostNames() {
|
||||
return nullToEmptyImmutableCopy(nameserverHostNames);
|
||||
}
|
||||
|
||||
public ImmutableSet<VKey<Host>> getNameservers() {
|
||||
@@ -173,7 +172,7 @@ public class DomainCommand {
|
||||
@Override
|
||||
public Create cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
|
||||
Create clone = clone(this);
|
||||
clone.nameservers = linkHosts(clone.nameserverFullyQualifiedHostNames, now);
|
||||
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
|
||||
if (registrantContactId == null) {
|
||||
clone.contacts = linkContacts(clone.foreignKeyedDesignatedContacts, now);
|
||||
} else {
|
||||
@@ -206,7 +205,7 @@ public class DomainCommand {
|
||||
|
||||
/** The name of the domain to look up, and an attribute specifying the host lookup type. */
|
||||
@XmlElement(name = "name")
|
||||
NameWithHosts fullyQualifiedDomainName;
|
||||
NameWithHosts domainName;
|
||||
|
||||
DomainAuthInfo authInfo;
|
||||
|
||||
@@ -245,12 +244,12 @@ public class DomainCommand {
|
||||
/** Get the enum that specifies the requested hosts (applies only to info flows). */
|
||||
public HostsRequest getHostsRequest() {
|
||||
// Null "hosts" is implicitly ALL.
|
||||
return MoreObjects.firstNonNull(fullyQualifiedDomainName.hosts, HostsRequest.ALL);
|
||||
return MoreObjects.firstNonNull(domainName.hosts, HostsRequest.ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTargetId() {
|
||||
return fullyQualifiedDomainName.name;
|
||||
return domainName.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -338,15 +337,12 @@ public class DomainCommand {
|
||||
}
|
||||
|
||||
/** The inner change type on a domain update command. */
|
||||
@XmlType(propOrder = {
|
||||
"nameserverFullyQualifiedHostNames",
|
||||
"foreignKeyedDesignatedContacts",
|
||||
"statusValues"})
|
||||
@XmlType(propOrder = {"nameserverHostNames", "foreignKeyedDesignatedContacts", "statusValues"})
|
||||
public static class AddRemove extends ResourceUpdate.AddRemove {
|
||||
/** Fully qualified host names of the hosts that are the nameservers for the domain. */
|
||||
@XmlElementWrapper(name = "ns")
|
||||
@XmlElement(name = "hostObj")
|
||||
Set<String> nameserverFullyQualifiedHostNames;
|
||||
Set<String> nameserverHostNames;
|
||||
|
||||
/** Resolved keys to hosts that are the nameservers for the domain. */
|
||||
@XmlTransient Set<VKey<Host>> nameservers;
|
||||
@@ -359,8 +355,8 @@ public class DomainCommand {
|
||||
@XmlTransient
|
||||
Set<DesignatedContact> contacts;
|
||||
|
||||
public ImmutableSet<String> getNameserverFullyQualifiedHostNames() {
|
||||
return nullSafeImmutableCopy(nameserverFullyQualifiedHostNames);
|
||||
public ImmutableSet<String> getNameserverHostNames() {
|
||||
return nullSafeImmutableCopy(nameserverHostNames);
|
||||
}
|
||||
|
||||
public ImmutableSet<VKey<Host>> getNameservers() {
|
||||
@@ -374,7 +370,7 @@ public class DomainCommand {
|
||||
/** Creates a copy of this {@link AddRemove} with hard links to hosts and contacts. */
|
||||
private AddRemove cloneAndLinkReferences(DateTime now) throws InvalidReferencesException {
|
||||
AddRemove clone = clone(this);
|
||||
clone.nameservers = linkHosts(clone.nameserverFullyQualifiedHostNames, now);
|
||||
clone.nameservers = linkHosts(clone.nameserverHostNames, now);
|
||||
clone.contacts = linkContacts(clone.foreignKeyedDesignatedContacts, now);
|
||||
return clone;
|
||||
}
|
||||
@@ -391,7 +387,7 @@ public class DomainCommand {
|
||||
? null
|
||||
: getOnlyElement(
|
||||
loadByForeignKeysCached(
|
||||
ImmutableSet.of(clone.registrantContactId), ContactResource.class, now)
|
||||
ImmutableSet.of(clone.registrantContactId), Contact.class, now)
|
||||
.values());
|
||||
return clone;
|
||||
}
|
||||
@@ -413,13 +409,12 @@ public class DomainCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<VKey<Host>> linkHosts(Set<String> fullyQualifiedHostNames, DateTime now)
|
||||
private static Set<VKey<Host>> linkHosts(Set<String> hostNames, DateTime now)
|
||||
throws InvalidReferencesException {
|
||||
if (fullyQualifiedHostNames == null) {
|
||||
if (hostNames == null) {
|
||||
return null;
|
||||
}
|
||||
return ImmutableSet.copyOf(
|
||||
loadByForeignKeysCached(fullyQualifiedHostNames, Host.class, now).values());
|
||||
return ImmutableSet.copyOf(loadByForeignKeysCached(hostNames, Host.class, now).values());
|
||||
}
|
||||
|
||||
private static Set<DesignatedContact> linkContacts(
|
||||
@@ -431,8 +426,8 @@ public class DomainCommand {
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
foreignKeys.add(contact.contactId);
|
||||
}
|
||||
ImmutableMap<String, VKey<ContactResource>> loadedContacts =
|
||||
loadByForeignKeysCached(foreignKeys.build(), ContactResource.class, now);
|
||||
ImmutableMap<String, VKey<Contact>> loadedContacts =
|
||||
loadByForeignKeysCached(foreignKeys.build(), Contact.class, now);
|
||||
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
linkedContacts.add(
|
||||
@@ -445,13 +440,12 @@ public class DomainCommand {
|
||||
private static <T extends EppResource> ImmutableMap<String, VKey<T>> loadByForeignKeysCached(
|
||||
final Set<String> foreignKeys, final Class<T> clazz, final DateTime now)
|
||||
throws InvalidReferencesException {
|
||||
ImmutableMap<String, ForeignKeyIndex<T>> fkis =
|
||||
ForeignKeyIndex.loadCached(clazz, foreignKeys, now);
|
||||
if (!fkis.keySet().equals(foreignKeys)) {
|
||||
ImmutableMap<String, VKey<T>> fks = ForeignKeyUtils.loadCached(clazz, foreignKeys, now);
|
||||
if (!fks.keySet().equals(foreignKeys)) {
|
||||
throw new InvalidReferencesException(
|
||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fkis.keySet())));
|
||||
clazz, ImmutableSet.copyOf(difference(foreignKeys, fks.keySet())));
|
||||
}
|
||||
return ImmutableMap.copyOf(transformValues(fkis, ForeignKeyIndex::getResourceKey));
|
||||
return fks;
|
||||
}
|
||||
|
||||
/** Exception to throw when referenced objects don't exist. */
|
||||
|
||||
@@ -20,11 +20,12 @@ import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
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.Host;
|
||||
import google.registry.model.reporting.DomainTransactionRecord;
|
||||
@@ -82,7 +83,7 @@ public class DomainHistory extends HistoryEntry {
|
||||
|
||||
// Store DomainBase instead of Domain so we don't pick up its @Id
|
||||
// Nullable for the sake of pre-Registry-3.0 history objects
|
||||
@DoNotCompare @Nullable DomainBase domainBase;
|
||||
@Nullable DomainBase domainBase;
|
||||
|
||||
@Id
|
||||
@Access(AccessType.PROPERTY)
|
||||
@@ -101,7 +102,6 @@ public class DomainHistory extends HistoryEntry {
|
||||
// We could have reused domainBase.nsHosts here, but Hibernate throws a weird exception after
|
||||
// we change to use a composite primary key.
|
||||
// TODO(b/166776754): Investigate if we can reuse domainBase.nsHosts for storing host keys.
|
||||
@DoNotCompare
|
||||
@ElementCollection
|
||||
@JoinTable(
|
||||
name = "DomainHistoryHost",
|
||||
@@ -115,7 +115,6 @@ public class DomainHistory extends HistoryEntry {
|
||||
@Column(name = "host_repo_id")
|
||||
Set<VKey<Host>> nsHosts;
|
||||
|
||||
@DoNotCompare
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
@@ -133,9 +132,9 @@ public class DomainHistory extends HistoryEntry {
|
||||
updatable = false)
|
||||
})
|
||||
// HashSet rather than ImmutableSet so that Hibernate can fill them out lazily on request
|
||||
@Ignore
|
||||
Set<DomainDsDataHistory> dsDataHistories = new HashSet<>();
|
||||
|
||||
@DoNotCompare
|
||||
@OneToMany(
|
||||
cascade = {CascadeType.ALL},
|
||||
fetch = FetchType.EAGER,
|
||||
@@ -153,6 +152,7 @@ public class DomainHistory extends HistoryEntry {
|
||||
updatable = false)
|
||||
})
|
||||
// HashSet rather than ImmutableSet so that Hibernate can fill them out lazily on request
|
||||
@Ignore
|
||||
Set<GracePeriodHistory> gracePeriodHistories = new HashSet<>();
|
||||
|
||||
@Override
|
||||
@@ -277,7 +277,7 @@ public class DomainHistory extends HistoryEntry {
|
||||
.map(GracePeriod::createFromHistory)
|
||||
.collect(toImmutableSet());
|
||||
domainBase.dsData =
|
||||
dsDataHistories.stream().map(DelegationSignerData::create).collect(toImmutableSet());
|
||||
dsDataHistories.stream().map(DomainDsData::create).collect(toImmutableSet());
|
||||
// Normally Hibernate would see that the domain fields are all null and would fill
|
||||
// domainBase with a null object. Unfortunately, the updateTimestamp is never null in SQL.
|
||||
if (domainBase.getDomainName() == null) {
|
||||
|
||||
@@ -30,28 +30,30 @@ import org.joda.time.DateTime;
|
||||
|
||||
/** The {@link ResponseData} returned for an EPP info flow on a domain. */
|
||||
@XmlRootElement(name = "infData")
|
||||
@XmlType(propOrder = {
|
||||
"fullyQualifiedDomainName",
|
||||
"repoId",
|
||||
"statusValues",
|
||||
"registrant",
|
||||
"contacts",
|
||||
"nameservers",
|
||||
"subordinateHosts",
|
||||
"currentSponsorClientId",
|
||||
"creationClientId",
|
||||
"creationTime",
|
||||
"lastEppUpdateClientId",
|
||||
"lastEppUpdateTime",
|
||||
"registrationExpirationTime",
|
||||
"lastTransferTime",
|
||||
"authInfo"})
|
||||
@XmlType(
|
||||
propOrder = {
|
||||
"domainName",
|
||||
"repoId",
|
||||
"statusValues",
|
||||
"registrant",
|
||||
"contacts",
|
||||
"nameservers",
|
||||
"subordinateHosts",
|
||||
"currentSponsorClientId",
|
||||
"creationClientId",
|
||||
"creationTime",
|
||||
"lastEppUpdateClientId",
|
||||
"lastEppUpdateTime",
|
||||
"registrationExpirationTime",
|
||||
"lastTransferTime",
|
||||
"authInfo"
|
||||
})
|
||||
@AutoValue
|
||||
@CopyAnnotations
|
||||
public abstract class DomainInfoData implements ResponseData {
|
||||
|
||||
@XmlElement(name = "name")
|
||||
abstract String getFullyQualifiedDomainName();
|
||||
abstract String getDomainName();
|
||||
|
||||
@XmlElement(name = "roid")
|
||||
abstract String getRepoId();
|
||||
@@ -110,7 +112,8 @@ public abstract class DomainInfoData implements ResponseData {
|
||||
/** Builder for {@link DomainInfoData}. */
|
||||
@AutoValue.Builder
|
||||
public abstract static class Builder {
|
||||
public abstract Builder setFullyQualifiedDomainName(String fullyQualifiedDomainName);
|
||||
public abstract Builder setDomainName(String domainName);
|
||||
|
||||
public abstract Builder setRepoId(String repoId);
|
||||
public abstract Builder setStatusValues(@Nullable ImmutableSet<StatusValue> statusValues);
|
||||
public abstract Builder setRegistrant(String registrant);
|
||||
|
||||
@@ -19,7 +19,6 @@ import static google.registry.model.IdService.allocateId;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.domain.DomainHistory.DomainHistoryId;
|
||||
@@ -40,7 +39,6 @@ import org.joda.time.DateTime;
|
||||
* <p>When a grace period expires, it is lazily removed from the {@link Domain} the next time the
|
||||
* resource is loaded from Datastore.
|
||||
*/
|
||||
@Embed
|
||||
@Entity
|
||||
@Table(
|
||||
indexes = {
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
@@ -31,7 +29,6 @@ import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Base class containing common fields and methods for {@link GracePeriod}. */
|
||||
@Embed
|
||||
@MappedSuperclass
|
||||
@Access(AccessType.FIELD)
|
||||
public class GracePeriodBase extends ImmutableObject implements UnsafeSerializable {
|
||||
@@ -40,7 +37,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
|
||||
@Transient long gracePeriodId;
|
||||
|
||||
/** Repository id for the domain which this grace period belongs to. */
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
String domainRepoId;
|
||||
|
||||
@@ -65,7 +61,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
|
||||
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
|
||||
@Access(AccessType.FIELD)
|
||||
@Column(name = "billing_event_id")
|
||||
@Ignore
|
||||
VKey<BillingEvent.OneTime> billingEventOneTime = null;
|
||||
|
||||
/**
|
||||
@@ -75,7 +70,6 @@ public class GracePeriodBase extends ImmutableObject implements UnsafeSerializab
|
||||
// NB: Would @IgnoreSave(IfNull.class), but not allowed for @Embed collections.
|
||||
@Access(AccessType.FIELD)
|
||||
@Column(name = "billing_recurrence_id")
|
||||
@Ignore
|
||||
VKey<BillingEvent.Recurring> billingEventRecurring = null;
|
||||
|
||||
public long getGracePeriodId() {
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
|
||||
package google.registry.model.domain;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -24,8 +24,7 @@ import javax.xml.bind.annotation.XmlEnumValue;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
/** The "periodType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class Period extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
|
||||
@@ -23,12 +23,10 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.condition.IfNull;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.UnsafeSerializable;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
@@ -37,17 +35,15 @@ import javax.xml.bind.annotation.XmlValue;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** The claims notice id from the claims phase. */
|
||||
@Embed
|
||||
@XmlType(propOrder = {"noticeId", "expirationTime", "acceptedTime"})
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public class LaunchNotice extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/** An empty instance to use in place of null. */
|
||||
private static final NoticeIdType EMPTY_NOTICE_ID = new NoticeIdType();
|
||||
|
||||
/** An id with a validator-id attribute. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
@Embeddable
|
||||
public static class NoticeIdType extends ImmutableObject implements UnsafeSerializable {
|
||||
|
||||
/**
|
||||
@@ -57,7 +53,6 @@ public class LaunchNotice extends ImmutableObject implements UnsafeSerializable
|
||||
@XmlValue String tcnId;
|
||||
|
||||
/** The identifier of the TMDB provider to use, defaulting to the TMCH. */
|
||||
@IgnoreSave(IfNull.class)
|
||||
@XmlAttribute(name = "validatorID")
|
||||
String validatorId;
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ package google.registry.model.domain.launch;
|
||||
|
||||
import static java.util.Objects.hash;
|
||||
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.util.Objects;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
@@ -47,7 +46,6 @@ import javax.xml.bind.annotation.XmlValue;
|
||||
* sets it is the one that needs to make sure the domain isn't a trademark and that the fields are
|
||||
* correct.
|
||||
*/
|
||||
@Embed
|
||||
public class LaunchPhase extends ImmutableObject {
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user