1
0
mirror of https://github.com/google/nomulus synced 2026-07-06 00:04:50 +00:00

Compare commits

...

33 Commits

Author SHA1 Message Date
Pavlo Tkach 7b9c16ca3e Update conditions when domain update flow triggers dns publish task (#1811)
Addressing b/246375161
2022-10-12 10:25:33 -04:00
Ben McIlwain 1ab077d267 Upgrade App Engine Standard to Java 17 w/ bundled APIs (#1816) 2022-10-11 20:06:37 -04:00
gbrodman ca65fbcc79 Refactor createSynthetic to be a command instead of a pipeline (#1813) 2022-10-11 12:23:31 -04:00
sarahcaseybot 0cfa7f8081 Remove allocation token check for transfering package domains (#1814) 2022-10-11 11:37:52 -04:00
Lai Jiang 9e31047c3a Fix nomulus command (#1812)
go/r3pr/1805 introduced an injectable clock in a few commands, but we
forgot to add the corresponding injector in the component. This PR fixes
it.
2022-10-09 16:45:42 -04:00
Pavlo Tkach b7c2e8fba5 Limit environments allowed to send emails out (#1807) 2022-10-07 12:12:57 -04:00
Pavlo Tkach a299df3005 Add fallback for Spec11 ThreatMatch parser (#1806) 2022-10-06 13:54:43 +00:00
Pavlo Tkach a9b35c163d Revert "Do not enqueue DNS updates when flow doesn't affect nameservers (#1785)" (#1808)
This reverts commit 775f672f2a.
2022-10-05 14:13:52 -04:00
gbrodman 9da24d114c Use injected times in URSC and CommandTestCase (#1805)
We started getting failures because some of the tests used October. In
general we should freeze the clock for testing as much as possible.

Same thing with the Get*Commands
2022-10-04 15:36:41 -04:00
Lai Jiang 7dd5876315 Refactor VKeyConverter (#1794)
Remove the redundant composite key boolean and simply the annotation
structure a bit.
2022-10-03 15:49:18 -04:00
gbrodman d1a259f63a Modify the CreateSynthetic pipeline to run over all non-deleted domains (#1803) 2022-10-03 15:15:41 -04:00
sarahcaseybot 8c5d2e9d92 Don't allow package tokens to discount premium names (#1804) 2022-10-03 14:27:10 -04:00
gbrodman cca1306b09 Change some READ_COMMITTED levels to REPEATABLE_READ (#1802)
Basically, any time we're loading a bunch of linked objects that might
change, we want to have REPEATABLE_READ so that another transaction
doesn't come along and smush whatever we think we're loading.

The following instances of READ_COMMITTED haven't changed:
- RdePipeline (it only loads immutable objects like histories)
- Invoicing pipeline (only immutable objects like BillingEvents)
- Spec11 (doesn't use any linked info from Domain)

This also changes the PersistenceModule to use REPEATABLE_READ by
default on the replica JPA TM, for the standard reasoning.
2022-09-30 14:44:50 -04:00
Weimin Yu 47071b0fbb Restore log4j exclusion in gradle build (#1801) 2022-09-30 14:04:00 -04:00
Weimin Yu d83565d37e Add a new allowed license string (#1800)
There are sporadic errors when building on desktop using maven central.
2022-09-30 14:03:17 -04:00
Weimin Yu a557b3f376 Disable the cron job for ResaveAllEppResourcesPipelineAction (#1799)
See b/249863289 for more information.
2022-09-30 12:05:29 -04:00
sarahcaseybot f4a49864b5 Add a get_package_promotion Command (#1793)
* Add a get_package_promotion Command

* add changes to loadByTokenString

* Fix test
2022-09-29 15:02:16 -04:00
gbrodman acdecca181 Don't create unnecessary synthetic History objects (#1796) 2022-09-26 13:41:57 -04:00
gbrodman 5264ab3fc3 Create pipeline to save synthetic DomainHistory objects (#1795)
This runs over all domains that weren't deleted as of September 5. This
will fix most of b/248112997, which is itself caused by b/245940594 --
creating synthetic history objects means that the RDE pipeline will look
at those instead of the potentially-no-longer-valid data in the old
history objects.
2022-09-22 14:58:50 -04:00
sarahcaseybot a9d59e4d6e Fix id generation in PackagePromotion (#1788)
* Fix id generation in PackagePromotion

* Fix update command tests
2022-09-21 15:19:49 -04:00
sarahcaseybot 1d3738da27 Add mutating commands for PackagePromotion (#1769)
* Add mutating commands for PackagePromotion

* Add checkAllocationToken methods

* Remove abstract methods

* Add better comments

* Small fixes

* Remove unneccesary init method

* Only assert in transaction in helper method
2022-09-21 12:38:09 -04:00
Lai Jiang 82a3a49268 Rename various fields and classes after migration (#1784)
Also fixed a bug introduced in #1785 where identity checked were performed instead of equality. This resulted in two sets containing the same elements not being regarded as equal and subsequent DNS updated being unnecessarily enqueued.
2022-09-21 11:49:22 -04:00
Pavlo Tkach 5bbad483e4 Fail genenerate invoices job when billing events not finished expanding (#1791) 2022-09-21 09:20:05 -04:00
Pavlo Tkach f6e9dae58d Add REMOVEPACKAGE token functionality to domain transfer flow (#1792) 2022-09-19 15:11:36 -04:00
Lai Jiang c4c1c72306 Refactor ForeignKeyIndex into ForeignKeyUtils (#1783)
The old class is modeled after datastore with some logic jammed in for it to work with SQL as well. As of #1777, the ofy related logic is deleted, however the general structure of the class remained datastore oriented.

This PR refactors the existing class into a ForeignKeyUtils helper class that does away wit the index subclasses and provides static helper methods to do the same, in a SQL-idiomatic fashion.

Some minor changes are made to the EPP resource classes to make it possible to create them in a SQL only environment in tests.
2022-09-19 14:41:19 -04:00
Pavlo Tkach 775f672f2a Do not enqueue DNS updates when flow doesn't affect nameservers (#1785) 2022-09-16 16:59:04 -04:00
gbrodman 372c854268 Create a scrap command to cancel OneTime billing events by ID (#1790)
This allows us to correct situations where we have erroneously charged
registrars for an action, without explicitly issuing a refund.
2022-09-16 16:17:31 -04:00
Lai Jiang edbca15bf4 Remove generics from TransferData (#1787)
`TransferData` is currently a generic class with a complicated type parameter that designate the `Builder` class of its concrete subclass, on order to facilitate returning the said `Builder` from an instance loosely typed to the superclass (`TransferData`) itself.

While this works, in most all places that a `TransferData` is used, the raw, un-generic type is declared, resulting a lot of warnings, not to mention the fact that type safety not actually checked when raw type is used.

In this PR, we make it so that the concrete `Builder` is returned through a protected abstract method that is implemented by the subclasses. The type information therefore no longer needs to be embedded in the superclass type signature, and reflection is not necessary to create the `Builder` either. Overall, it makes `TransferData` a much cleaner class without the messiness of generics.
2022-09-15 14:07:38 -04:00
sarahcaseybot 5f41adf843 Flyway file for autogenerated PackagePromotion id fix (#1789)
* Flyway file for autogenerated PackagePromotion id fix

* Actually include the flyway file
2022-09-15 13:28:46 -04:00
Lai Jiang e21f64b745 Delete EppResourceIndex and EppResourceIndexBucket (#1774) 2022-09-15 10:50:22 -04:00
sarahcaseybot 0dee97934a Prevent creation of package domains for more than 1 year (#1786)
* Prevent creation of package domains for more than 1 year

* Fix docs test
2022-09-14 14:49:56 -04:00
gbrodman 1070173264 Load, project, and save in one txn in ResaveAERP (#1780) 2022-09-13 15:59:49 -04:00
Pavlo Tkach b9a3c0cd96 Add dry run test for remove package token (#1782) 2022-09-13 11:20:53 -04:00
234 changed files with 4309 additions and 3162 deletions
@@ -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"
+4
View File
@@ -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'
+11 -8
View File
@@ -31,10 +31,10 @@ com.github.jnr:jnr-unixsocket:0.38.17=compileClasspath,default,deploy_jar,nonpro
com.github.jnr:jnr-x86asm:1.0.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,errorprone,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.android:annotations:4.1.1.4=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-appengine:1.35.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-appengine:1.32.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-jackson2:1.32.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-java6:1.35.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:1.35.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:1.32.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api-client:google-api-client:1.35.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:2.12.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.136.2=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -90,12 +90,15 @@ com.google.apis:google-api-services-pubsub:v1-rev20211130-1.32.1=compileClasspat
com.google.apis:google-api-services-sheets:v4-rev20220620-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20220623-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20220705-1.32.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine.tools:appengine-gcs-client:0.8.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine.tools:appengine-gcs-client:0.8.3=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine.tools:appengine-pipeline:0.2.13=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-api-1.0-sdk:2.0.5=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-api-stubs:2.0.5=testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-remote-api:2.0.5=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-testing:1.9.86=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-api-1.0-sdk:2.0.5=compileClasspath,nonprodCompileClasspath
com.google.appengine:appengine-api-1.0-sdk:2.0.9=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-api-stubs:2.0.9=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-remote-api:2.0.5=compileClasspath,nonprodCompileClasspath
com.google.appengine:appengine-remote-api:2.0.9=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-testing:2.0.9=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.appengine:appengine-tools-sdk:2.0.9=default,deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-credentials:1.8.0=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.8.0=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -299,7 +302,7 @@ javax.validation:validation-api:1.0.0.GA=compileClasspath,default,deploy_jar,non
javax.xml.bind:jaxb-api:2.3.1=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.xml.bind:jaxb-api:2.4.0-b180830.0359=jaxb
jline:jline:1.0=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
joda-time:joda-time:2.10.10=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
joda-time:joda-time:2.10.13=compileClasspath,default,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
junit:junit:4.13.2=default,nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.arnx:nashorn-promise:0.1.1=nonprodRuntime,runtime,testRuntimeClasspath
net.bytebuddy:byte-buddy-agent:1.12.10=testCompileClasspath,testRuntimeClasspath
@@ -44,7 +44,7 @@ import javax.inject.Inject;
/**
* Hard deletes load-test Contacts, Hosts, their subordinate history entries, and the associated
* ForeignKey and EppResourceIndex entities.
* 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
@@ -53,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,
@@ -91,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";
@@ -220,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()
@@ -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)))
@@ -14,11 +14,14 @@
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;
@@ -27,7 +30,7 @@ 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;
@@ -69,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 ="
@@ -88,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);
@@ -99,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<Contact, Contact> 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()",
Contact.class,
c -> c);
projectAndResaveResources(pipeline, Contact.class, read);
String.class,
r -> r);
projectAndResaveResources(pipeline, Contact.class, repoIdRead);
}
/**
@@ -116,61 +118,72 @@ 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);
});
}
}
@@ -180,6 +193,7 @@ public class ResaveAllEppResourcesPipeline implements Serializable {
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));
}
}
@@ -1482,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);
@@ -108,7 +108,6 @@ public class RegistryConfigSettings {
/** Configuration for Cloud Datastore. */
public static class Datastore {
public int eppResourceIndexBucketsNum;
public int baseOfyRetryMillis;
}
@@ -183,10 +183,6 @@ registryPolicy:
requireSslCertificates: true
datastore:
# Number of EPP resource index buckets in Datastore. Dont change after
# initial install.
eppResourceIndexBucketsNum: 997
# Milliseconds that Objectify waits to retry a Datastore transaction (this
# doubles after each failure).
baseOfyRetryMillis: 100
@@ -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,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>backend</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>default</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>pubapi</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>tools</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>backend</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>default</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>pubapi</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>tools</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>backend</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>default</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>pubapi</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>tools</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>backend</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>default</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
@@ -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>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>pubapi</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>tools</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>backend</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>default</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>F4_1G</instance-class>
<automatic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>pubapi</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>tools</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>backend</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>default</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
@@ -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>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>pubapi</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4_1G</instance-class>
<manual-scaling>
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<runtime>java17</runtime>
<app-engine-apis>true</app-engine-apis>
<service>tools</service>
<threadsafe>true</threadsafe>
<sessions-enabled>true</sessions-enabled>
<instance-class>B4</instance-class>
<basic-scaling>
@@ -91,10 +91,10 @@ public class ExportDomainListsAction implements Runnable {
// field that compares with the substituted value.
jpaTm()
.query(
"SELECT fullyQualifiedDomainName FROM Domain "
"SELECT domainName FROM Domain "
+ "WHERE tld = :tld "
+ "AND deletionTime > :now "
+ "ORDER by fullyQualifiedDomainName ASC",
+ "ORDER by domainName ASC",
String.class)
.setParameter("tld", tld)
.setParameter("now", clock.nowUtc())
@@ -114,7 +114,7 @@ class SyncRegistrarsSheet {
// and you'll need to remove deleted columns probably like a week after
// deployment.
//
builder.put("clientIdentifier", convert(registrar.getRegistrarId()));
builder.put("registrarId", convert(registrar.getRegistrarId()));
builder.put("registrarName", convert(registrar.getRegistrarName()));
builder.put("state", convert(registrar.getState()));
builder.put("ianaIdentifier", convert(registrar.getIanaIdentifier()));
@@ -44,8 +44,8 @@ import dagger.Module;
import dagger.Provides;
import google.registry.flows.domain.DomainFlowUtils.BadCommandForRegistryPhaseException;
import google.registry.flows.domain.DomainFlowUtils.InvalidIdnDomainLabelException;
import google.registry.model.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,6 +37,7 @@ import google.registry.flows.exceptions.TooManyResourceChecksException;
import google.registry.model.EppResource;
import google.registry.model.EppResource.ForeignKeyedEppResource;
import google.registry.model.EppResource.ResourceWithTransferData;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.contact.Contact;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainBase;
@@ -45,7 +45,6 @@ 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.
@@ -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;
@@ -40,7 +39,6 @@ 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.reporting.HistoryEntry;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import javax.inject.Inject;
@@ -95,11 +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(),
EppResourceIndex.create(Key.create(newContact))));
tm().insertAll(ImmutableSet.of(newContact, historyBuilder.build()));
return responseBuilder
.setResData(ContactCreateData.create(newContact.getContactId(), now))
.build();
@@ -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,7 +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.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
import google.registry.model.poll.PollMessage;
import google.registry.model.poll.PollMessage.Autorenew;
@@ -153,6 +151,7 @@ import org.joda.time.Duration;
* @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);
@@ -391,6 +390,9 @@ public final class DomainCreateFlow implements TransactionalFlow {
.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();
}
@@ -400,10 +402,7 @@ public final class DomainCreateFlow implements TransactionalFlow {
entitiesToSave.add(
createNameCollisionOneTimePollMessage(targetId, domainHistory, registrarId, now));
}
entitiesToSave.add(
domain,
domainHistory,
EppResourceIndex.create(Key.create(domain)));
entitiesToSave.add(domain, domainHistory);
if (allocationToken.isPresent()
&& TokenType.SINGLE_USE.equals(allocationToken.get().getTokenType())) {
entitiesToSave.add(
@@ -640,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)
@@ -651,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();
}
@@ -755,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));
}
}
}
@@ -105,7 +105,7 @@ 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;
@@ -316,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());
@@ -333,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());
@@ -343,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 ->
@@ -920,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.
@@ -948,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;
@@ -1001,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. */
@@ -1542,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)));
}
}
@@ -109,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(
@@ -63,6 +63,7 @@ import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.superuser.DomainTransferRequestSuperuserExtension;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
@@ -169,19 +170,21 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
extensionManager.validate();
DateTime now = tm().getTransactionTime();
Domain existingDomain = loadAndVerifyExistence(Domain.class, targetId, now);
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
gainingClientId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<AllocationToken> allocationToken =
allocationTokenFlowUtils.verifyAllocationTokenIfPresent(
existingDomain,
Registry.get(existingDomain.getTld()),
gainingClientId,
now,
eppInput.getSingleExtension(AllocationTokenExtension.class));
Optional<DomainTransferRequestSuperuserExtension> superuserExtension =
eppInput.getSingleExtension(DomainTransferRequestSuperuserExtension.class);
Period period =
superuserExtension.isPresent()
? superuserExtension.get().getRenewalPeriod()
: ((Transfer) resourceCommand).getPeriod();
verifyTransferAllowed(existingDomain, period, now, superuserExtension);
verifyTransferAllowed(existingDomain, period, now, superuserExtension, allocationToken);
String tld = existingDomain.getTld();
Registry registry = Registry.get(tld);
// An optional extension from the client specifying what they think the transfer should cost.
@@ -293,7 +296,8 @@ public final class DomainTransferRequestFlow implements TransactionalFlow {
Domain existingDomain,
Period period,
DateTime now,
Optional<DomainTransferRequestSuperuserExtension> superuserExtension)
Optional<DomainTransferRequestSuperuserExtension> superuserExtension,
Optional<AllocationToken> allocationToken)
throws EppException {
verifyNoDisallowedStatuses(existingDomain, DISALLOWED_STATUSES);
if (!isSuperuser) {
@@ -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()) {
@@ -175,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));
}
@@ -293,11 +289,11 @@ public class AllocationTokenFlowUtils {
}
}
/** The __REMOVEPACKAGE__ token is missing on a renew package domain command */
/** 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");
super("Domains that are inside packages cannot be explicitly renewed or transferred");
}
}
@@ -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,7 +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.reporting.IcannReportingTypes.ActivityReportField;
import java.util.Optional;
import javax.inject.Inject;
@@ -106,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);
@@ -130,17 +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(),
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.
@@ -152,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;
@@ -129,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);
@@ -147,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();
@@ -260,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()) {
@@ -23,8 +23,6 @@ import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainHistory;
import google.registry.model.host.Host;
import google.registry.model.host.HostHistory;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.reporting.HistoryEntry;
/** Sets of classes of the Objectify-registered entities in use throughout the model. */
@@ -38,8 +36,6 @@ public final class EntityClasses {
ContactHistory.class,
Domain.class,
DomainHistory.class,
EppResourceIndex.class,
EppResourceIndexBucket.class,
GaeUserIdConverter.class,
HistoryEntry.class,
Host.class,
@@ -140,10 +140,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
DateTime lastEppUpdateTime;
/** Status values associated with this resource. */
@Ignore
@Column(name = "statuses")
// TODO(b/177567432): rename to "statuses" once we're off datastore.
Set<StatusValue> status;
@Ignore Set<StatusValue> statuses;
public String getRepoId() {
return repoId;
@@ -189,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() {
@@ -307,7 +304,7 @@ public abstract class EppResource extends BackupGroupRoot implements Buildable {
statusValue,
resourceClass.getSimpleName());
}
getInstance().status = statusValues;
getInstance().statuses = statusValues;
return thisCastToDerived();
}
@@ -36,7 +36,6 @@ 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;
@@ -117,20 +116,19 @@ public final class EppResourceUtils {
}
/**
* Loads the last created version of an {@link EppResource} from Datastore by foreign key, using a
* cache.
* Loads the last created version of an {@link EppResource} from the database by foreign key,
* using a cache.
*
* <p>Returns null if no resource with this foreign key was ever created, or if the most recently
* created resource was deleted before time "now".
*
* <p>Loading an {@link EppResource} by itself is not sufficient to know its current state since
* it may have various expirable conditions and status values that might implicitly change its
* state as time progresses even if it has not been updated in Datastore. Rather, the resource
* state as time progresses even if it has not been updated in the database. Rather, the resource
* must be combined with a timestamp to view its current state. We use a global last updated
* timestamp on the resource's entity group (which is essentially free since all writes to the
* entity group must be serialized anyways) to guarantee monotonically increasing write times, and
* forward our projected time to the greater of this timestamp or "now". This guarantees that
* we're not projecting into the past.
* timestamp to guarantee monotonically increasing write times, and forward our projected time to
* the greater of this timestamp or "now". This guarantees that we're not projecting into the
* past.
*
* <p>Do not call this cached version for anything that needs transactional consistency. It should
* only be used when it's OK if the data is potentially being out of date, e.g. WHOIS.
@@ -150,19 +148,18 @@ public final class EppResourceUtils {
checkArgument(
ForeignKeyedEppResource.class.isAssignableFrom(clazz),
"loadByForeignKey may only be called for foreign keyed EPP resources");
ForeignKeyIndex<T> fki =
VKey<T> key =
useCache
? ForeignKeyIndex.loadCached(clazz, ImmutableList.of(foreignKey), now)
.getOrDefault(foreignKey, null)
: ForeignKeyIndex.load(clazz, foreignKey, now);
// The value of fki.getResourceKey() might be null for hard-deleted prober data.
if (fki == null || isAtOrAfter(now, fki.getDeletionTime()) || fki.getResourceKey() == null) {
? ForeignKeyUtils.loadCached(clazz, ImmutableList.of(foreignKey), now).get(foreignKey)
: ForeignKeyUtils.load(clazz, foreignKey, now);
// The returned key is null if the resource is hard deleted or soft deleted by the given time.
if (key == null) {
return Optional.empty();
}
T resource =
useCache
? EppResource.loadCached(fki.getResourceKey())
: tm().transact(() -> tm().loadByKeyIfPresent(fki.getResourceKey()).orElse(null));
? EppResource.loadCached(key)
: tm().transact(() -> tm().loadByKeyIfPresent(key).orElse(null));
if (resource == null || isAtOrAfter(now, resource.getDeletionTime())) {
return Optional.empty();
}
@@ -178,7 +175,7 @@ public final class EppResourceUtils {
}
/**
* Checks multiple {@link EppResource} objects from Datastore by unique ids.
* Checks multiple {@link EppResource} objects from the database by unique ids.
*
* <p>There are currently no resources that support checks and do not use foreign keys. If we need
* to support that case in the future, we can loosen the type to allow any {@link EppResource} and
@@ -190,7 +187,7 @@ public final class EppResourceUtils {
*/
public static <T extends EppResource> ImmutableSet<String> checkResourcesExist(
Class<T> clazz, List<String> uniqueIds, final DateTime now) {
return ForeignKeyIndex.load(clazz, uniqueIds, now).keySet();
return ForeignKeyUtils.load(clazz, uniqueIds, now).keySet();
}
/**
@@ -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);
}
}
}
@@ -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 =
@@ -64,7 +64,7 @@ public final class ResourceTransferUtils {
DomainTransferData domainTransferData = (DomainTransferData) transferData;
builder =
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(eppResource.getForeignKey())
.setDomainName(eppResource.getForeignKey())
.setExtendedRegistrationExpirationTime(
ADD_EXDATE_STATUSES.contains(domainTransferData.getTransferStatus())
? domainTransferData.getTransferredRegistrationExpirationTime()
@@ -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;
@@ -295,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. */
@@ -473,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 {
/**
@@ -606,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,12 +14,11 @@
package google.registry.model.contact;
import com.googlecode.objectify.Key;
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;
@@ -46,13 +45,13 @@ import org.joda.time.DateTime;
@Index(columnList = "searchName")
})
@ExternalMessagingName("contact")
@WithStringVKey
@WithVKey(String.class)
@Access(AccessType.FIELD)
public class Contact extends ContactBase implements ForeignKeyedEppResource {
@Override
public VKey<Contact> createVKey() {
return VKey.create(Contact.class, getRepoId(), Key.create(this));
return VKey.createSql(Contact.class, getRepoId());
}
@Override
@@ -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 {
@@ -116,15 +116,14 @@ 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
* 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)
@@ -134,7 +133,7 @@ public class Domain extends DomainBase implements ForeignKeyedEppResource {
insertable = false,
updatable = false)
@SuppressWarnings("UnusedMethod")
private Set<DelegationSignerData> getInternalDelegationSignerData() {
private Set<DomainDsData> getInternalDelegationSignerData() {
return dsData;
}
@@ -148,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
@@ -53,7 +53,7 @@ import google.registry.model.billing.BillingEvent;
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.eppcommon.StatusValue;
import google.registry.model.host.Host;
@@ -112,17 +112,14 @@ 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. */
@@ -145,7 +142,7 @@ public class DomainBase extends EppResource
DomainAuthInfo authInfo;
/** Data used to construct DS records for this domain. */
@Ignore @Transient Set<DelegationSignerData> dsData;
@Ignore @Transient Set<DomainDsData> dsData;
/**
* The claims notice supplied when this domain was created, if there was one.
@@ -340,14 +337,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);
}
@@ -392,9 +389,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 {
@@ -728,9 +725,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
@@ -751,11 +748,11 @@ 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();
@@ -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,6 +30,7 @@ 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.Contact;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
@@ -39,7 +39,6 @@ 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;
@@ -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;
}
@@ -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(
@@ -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. */
@@ -25,7 +25,7 @@ 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;
@@ -280,7 +280,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);
@@ -17,7 +17,7 @@ package google.registry.model.domain.secdns;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.secdns.DelegationSignerData.DomainDsDataId;
import google.registry.model.domain.secdns.DomainDsData.DomainDsDataId;
import java.io.Serializable;
import javax.persistence.Access;
import javax.persistence.AccessType;
@@ -34,15 +34,14 @@ import javax.xml.bind.annotation.XmlType;
*
* @see <a href="http://tools.ietf.org/html/rfc5910">RFC 5910</a>
* @see <a href="http://tools.ietf.org/html/rfc4034">RFC 4034</a>
* <p>TODO(b/177567432): Rename this class to DomainDsData.
*/
@XmlType(name = "dsData")
@Entity
@IdClass(DomainDsDataId.class)
@Table(indexes = @Index(columnList = "domainRepoId"))
public class DelegationSignerData extends DomainDsDataBase {
@Table(name = "DelegationSignerData", indexes = @Index(columnList = "domainRepoId"))
public class DomainDsData extends DomainDsDataBase {
private DelegationSignerData() {}
private DomainDsData() {}
@Override
@Id
@@ -79,21 +78,21 @@ public class DelegationSignerData extends DomainDsDataBase {
return super.getDigest();
}
public DelegationSignerData cloneWithDomainRepoId(String domainRepoId) {
DelegationSignerData clone = clone(this);
public DomainDsData cloneWithDomainRepoId(String domainRepoId) {
DomainDsData clone = clone(this);
clone.domainRepoId = checkArgumentNotNull(domainRepoId);
return clone;
}
public DelegationSignerData cloneWithoutDomainRepoId() {
DelegationSignerData clone = clone(this);
public DomainDsData cloneWithoutDomainRepoId() {
DomainDsData clone = clone(this);
clone.domainRepoId = null;
return clone;
}
public static DelegationSignerData create(
public static DomainDsData create(
int keyTag, int algorithm, int digestType, byte[] digest, String domainRepoId) {
DelegationSignerData instance = new DelegationSignerData();
DomainDsData instance = new DomainDsData();
instance.keyTag = keyTag;
instance.algorithm = algorithm;
instance.digestType = digestType;
@@ -102,17 +101,15 @@ public class DelegationSignerData extends DomainDsDataBase {
return instance;
}
public static DelegationSignerData create(
int keyTag, int algorithm, int digestType, byte[] digest) {
public static DomainDsData create(int keyTag, int algorithm, int digestType, byte[] digest) {
return create(keyTag, algorithm, digestType, digest, null);
}
public static DelegationSignerData create(
int keyTag, int algorithm, int digestType, String digestAsHex) {
public static DomainDsData create(int keyTag, int algorithm, int digestType, String digestAsHex) {
return create(keyTag, algorithm, digestType, DatatypeConverter.parseHexBinary(digestAsHex));
}
public static DelegationSignerData create(DomainDsDataHistory history) {
public static DomainDsData create(DomainDsDataHistory history) {
return create(
history.keyTag,
history.algorithm,
@@ -121,7 +118,7 @@ public class DelegationSignerData extends DomainDsDataBase {
history.domainRepoId);
}
/** Class to represent the composite primary key of {@link DelegationSignerData} entity. */
/** Class to represent the composite primary key of {@link DomainDsData} entity. */
static class DomainDsDataId extends ImmutableObject implements Serializable {
String domainRepoId;
@@ -26,7 +26,7 @@ import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** Base class for {@link DelegationSignerData} and {@link DomainDsDataHistory}. */
/** Base class for {@link DomainDsData} and {@link DomainDsDataHistory}. */
@MappedSuperclass
@Access(AccessType.FIELD)
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
@@ -25,7 +25,7 @@ import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/** Entity class to represent a historic {@link DelegationSignerData}. */
/** Entity class to represent a historic {@link DomainDsData}. */
@Entity
public class DomainDsDataHistory extends DomainDsDataBase implements UnsafeSerializable {
@@ -39,10 +39,9 @@ public class DomainDsDataHistory extends DomainDsDataBase implements UnsafeSeria
/**
* Creates a {@link DomainDsDataHistory} instance from given {@link #domainHistoryRevisionId} and
* {@link DelegationSignerData} instance.
* {@link DomainDsData} instance.
*/
public static DomainDsDataHistory createFrom(
long domainHistoryRevisionId, DelegationSignerData dsData) {
public static DomainDsDataHistory createFrom(long domainHistoryRevisionId, DomainDsData dsData) {
DomainDsDataHistory instance = new DomainDsDataHistory();
instance.domainHistoryRevisionId = domainHistoryRevisionId;
instance.domainRepoId = dsData.domainRepoId;
@@ -36,13 +36,13 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
Long maxSigLife;
/** Signatures for this domain. */
Set<DelegationSignerData> dsData;
Set<DomainDsData> dsData;
public Long getMaxSigLife() {
return maxSigLife;
}
public ImmutableSet<DelegationSignerData> getDsData() {
public ImmutableSet<DomainDsData> getDsData() {
return nullSafeImmutableCopy(dsData);
}
}
@@ -24,9 +24,9 @@ import javax.xml.bind.annotation.XmlRootElement;
public class SecDnsInfoExtension extends ImmutableObject implements ResponseExtension {
/** Signatures for this domain. */
ImmutableSet<DelegationSignerData> dsData;
ImmutableSet<DomainDsData> dsData;
public static SecDnsInfoExtension create(ImmutableSet<DelegationSignerData> dsData) {
public static SecDnsInfoExtension create(ImmutableSet<DomainDsData> dsData) {
SecDnsInfoExtension instance = new SecDnsInfoExtension();
instance.dsData = dsData;
return instance;
@@ -70,9 +70,9 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
@XmlTransient
abstract static class AddRemoveBase extends ImmutableObject {
/** Delegations to add or remove. */
Set<DelegationSignerData> dsData;
Set<DomainDsData> dsData;
public ImmutableSet<DelegationSignerData> getDsData() {
public ImmutableSet<DomainDsData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
}
@@ -41,7 +41,7 @@ import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.DomainHistoryVKey;
import google.registry.persistence.VKey;
import google.registry.persistence.WithStringVKey;
import google.registry.persistence.WithVKey;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
@@ -58,7 +58,7 @@ import org.joda.time.DateTime;
/** An entity representing an allocation token. */
@Entity
@WithStringVKey(compositeKey = true)
@WithVKey(String.class)
@Table(
indexes = {
@Index(columnList = "token", name = "allocation_token_token_idx", unique = true),
@@ -300,6 +300,9 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
!getInstance().tokenType.equals(TokenType.PACKAGE)
|| getInstance().renewalPriceBehavior.equals(RenewalPriceBehavior.SPECIFIED),
"Package tokens must have renewalPriceBehavior set to SPECIFIED");
checkArgument(
!getInstance().tokenType.equals(TokenType.PACKAGE) || !getInstance().discountPremiums,
"Package tokens cannot discount premium names");
checkArgument(
getInstance().domainName == null || TokenType.SINGLE_USE.equals(getInstance().tokenType),
"Domain name can only be specified for SINGLE_USE tokens");
@@ -15,6 +15,7 @@
package google.registry.model.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 static google.registry.util.DateTimeUtils.END_OF_TIME;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
@@ -28,6 +29,8 @@ import java.util.Optional;
import javax.annotation.Nullable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
@@ -40,7 +43,9 @@ import org.joda.time.DateTime;
public class PackagePromotion extends ImmutableObject implements Buildable {
/** An autogenerated identifier for the package promotion. */
@Id long packagePromotionId;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long packagePromotionId;
/** The allocation token string for the package. */
@Column(nullable = false)
@@ -94,6 +99,21 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return Optional.ofNullable(lastNotificationSent);
}
/** Loads and returns a PackagePromotion entity by its token string directly from Cloud SQL. */
public static Optional<PackagePromotion> loadByTokenString(String tokenString) {
jpaTm().assertInTransaction();
return jpaTm()
.query("FROM PackagePromotion WHERE token = :token", PackagePromotion.class)
.setParameter("token", VKey.createSql(AllocationToken.class, tokenString))
.getResultStream()
.findFirst();
}
@Override
public VKey<PackagePromotion> createVKey() {
return VKey.createSql(PackagePromotion.class, packagePromotionId);
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -143,7 +163,7 @@ public class PackagePromotion extends ImmutableObject implements Buildable {
return this;
}
public Builder setNextBillingDate(@Nullable DateTime nextBillingDate) {
public Builder setNextBillingDate(DateTime nextBillingDate) {
checkArgumentNotNull(nextBillingDate, "Next billing date must not be null");
getInstance().nextBillingDate = nextBillingDate;
return this;
@@ -14,13 +14,12 @@
package google.registry.model.host;
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;
@@ -52,7 +51,7 @@ import javax.persistence.AccessType;
@javax.persistence.Index(columnList = "currentSponsorRegistrarId")
})
@ExternalMessagingName("host")
@WithStringVKey
@WithVKey(String.class)
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
public class Host extends HostBase implements ForeignKeyedEppResource {
@@ -65,7 +64,7 @@ public class Host extends HostBase implements ForeignKeyedEppResource {
@Override
public VKey<Host> createVKey() {
return VKey.create(Host.class, getRepoId(), Key.create(this));
return VKey.createSql(Host.class, getRepoId());
}
@Override
@@ -35,7 +35,6 @@ import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.MappedSuperclass;
import org.joda.time.DateTime;
@@ -64,10 +63,7 @@ public class HostBase extends EppResource {
* from (creationTime, deletionTime) there can only be one host in Datastore with this name.
* However, there can be many hosts with the same name and non-overlapping lifetimes.
*/
// TODO(b/177567432): Rename this to hostName when we are off Datastore
@Index
@Column(name = "hostName")
String fullyQualifiedHostName;
@Index String hostName;
/** IP Addresses for this host. Can be null if this is an external host. */
@Index Set<InetAddress> inetAddresses;
@@ -95,7 +91,7 @@ public class HostBase extends EppResource {
DateTime lastSuperordinateChange;
public String getHostName() {
return fullyQualifiedHostName;
return hostName;
}
public VKey<Domain> getSuperordinateDomain() {
@@ -120,7 +116,7 @@ public class HostBase extends EppResource {
@Override
public String getForeignKey() {
return fullyQualifiedHostName;
return hostName;
}
@Override
@@ -197,7 +193,7 @@ public class HostBase extends EppResource {
hostName.equals(canonicalizeHostname(hostName)),
"Host name %s not in puny-coded, lower-case form",
hostName);
getInstance().fullyQualifiedHostName = hostName;
getInstance().hostName = hostName;
return thisCastToDerived();
}
@@ -36,7 +36,7 @@ public class HostCommand {
@XmlTransient
abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
implements ResourceCreateOrChange<Host.Builder> {
public String getFullyQualifiedHostName() {
public String getHostName() {
return getTargetId();
}
}
@@ -28,23 +28,25 @@ import org.joda.time.DateTime;
/** The {@link ResponseData} returned for an EPP info flow on a host. */
@XmlRootElement(name = "infData")
@XmlType(propOrder = {
"fullyQualifiedHostName",
"repoId",
"statusValues",
"inetAddresses",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"lastTransferTime" })
@XmlType(
propOrder = {
"hostName",
"repoId",
"statusValues",
"inetAddresses",
"currentSponsorClientId",
"creationClientId",
"creationTime",
"lastEppUpdateClientId",
"lastEppUpdateTime",
"lastTransferTime"
})
@AutoValue
@CopyAnnotations
public abstract class HostInfoData implements ResponseData {
@XmlElement(name = "name")
abstract String getFullyQualifiedHostName();
abstract String getHostName();
@XmlElement(name = "roid")
abstract String getRepoId();
@@ -79,7 +81,8 @@ public abstract class HostInfoData implements ResponseData {
/** Builder for {@link HostInfoData}. */
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder setFullyQualifiedHostName(String fullyQualifiedHostName);
public abstract Builder setHostName(String hostName);
public abstract Builder setRepoId(String repoId);
public abstract Builder setStatusValues(ImmutableSet<StatusValue> statusValues);
public abstract Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses);
@@ -1,79 +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.index;
import static google.registry.util.TypeUtils.instantiate;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Parent;
import google.registry.model.BackupGroupRoot;
import google.registry.model.EppResource;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.ReportedOn;
import google.registry.persistence.VKey;
/** An index that allows for quick enumeration of all EppResource entities (e.g. via map reduce). */
@ReportedOn
@Entity
@DeleteAfterMigration
public class EppResourceIndex extends BackupGroupRoot {
@Id String id;
@Parent Key<EppResourceIndexBucket> bucket;
/** Although this field holds a {@link Key} it is named "reference" for historical reasons. */
Key<? extends EppResource> reference;
@Index String kind;
public String getId() {
return id;
}
public String getKind() {
return kind;
}
public Key<? extends EppResource> getKey() {
return reference;
}
@VisibleForTesting
public Key<EppResourceIndexBucket> getBucket() {
return bucket;
}
@VisibleForTesting
public static <T extends EppResource> EppResourceIndex create(
Key<EppResourceIndexBucket> bucket, Key<T> resourceKey) {
EppResourceIndex instance = instantiate(EppResourceIndex.class);
instance.reference = resourceKey;
instance.kind = resourceKey.getKind();
// creates a web-safe key string, this value is never used
// TODO(b/211785379): remove unused id
instance.id = VKey.from(resourceKey).stringify();
instance.bucket = bucket;
return instance;
}
public static <T extends EppResource> EppResourceIndex create(Key<T> resourceKey) {
return create(EppResourceIndexBucket.getBucketKey(resourceKey), resourceKey);
}
}
@@ -1,68 +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.index;
import static google.registry.config.RegistryConfig.getEppResourceIndexBucketCount;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.Hashing;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.annotations.DeleteAfterMigration;
import google.registry.model.annotations.VirtualEntity;
/** A virtual entity to represent buckets to which EppResourceIndex objects are randomly added. */
@Entity
@VirtualEntity
@DeleteAfterMigration
public class EppResourceIndexBucket extends ImmutableObject {
@SuppressWarnings("unused")
@Id
private long bucketId;
/**
* Deterministic function that returns a bucket id based on the resource's roid.
* NB: At the moment, nothing depends on this being deterministic, so we have the ability to
* change the number of buckets and utilize a random distribution once we do.
*/
private static long getBucketIdFromEppResource(Key<? extends EppResource> resourceKey) {
int numBuckets = getEppResourceIndexBucketCount();
// IDs can't be 0, so add 1 to the hash.
return Hashing.consistentHash(resourceKey.getName().hashCode(), numBuckets) + 1L;
}
/** Gets a bucket key as a function of an EppResource to be indexed. */
public static Key<EppResourceIndexBucket> getBucketKey(Key<? extends EppResource> resourceKey) {
return Key.create(EppResourceIndexBucket.class, getBucketIdFromEppResource(resourceKey));
}
/** Gets the specified numbered bucket key. */
public static Key<EppResourceIndexBucket> getBucketKey(int bucketId) {
return Key.create(EppResourceIndexBucket.class, bucketId);
}
/** Returns the keys to all buckets. */
public static Iterable<Key<EppResourceIndexBucket>> getAllBuckets() {
ImmutableList.Builder<Key<EppResourceIndexBucket>> builder = new ImmutableList.Builder<>();
for (int bucketId = 1; bucketId <= getEppResourceIndexBucketCount(); bucketId++) {
builder.add(getBucketKey(bucketId));
}
return builder.build();
}
}
@@ -1,311 +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.index;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static google.registry.config.RegistryConfig.getEppResourceCachingDuration;
import static google.registry.config.RegistryConfig.getEppResourceMaxCachedEntries;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.replicaJpaTm;
import static google.registry.util.CollectionUtils.entriesToImmutableMap;
import static google.registry.util.TypeUtils.instantiate;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableBiMap;
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.Multimaps;
import com.google.common.collect.Streams;
import google.registry.config.RegistryConfig;
import google.registry.model.BackupGroupRoot;
import google.registry.model.CacheUtils;
import google.registry.model.EppResource;
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.CriteriaQueryBuilder;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.util.NonFinalForTesting;
import java.time.Duration;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import org.joda.time.DateTime;
/**
* Class to map a foreign key to the active instance of {@link EppResource} whose unique id matches
* the foreign key string. The instance is never deleted, but it is updated if a newer entity
* becomes the active entity.
*/
public abstract class ForeignKeyIndex<E extends EppResource> extends BackupGroupRoot {
/** The {@link ForeignKeyIndex} type for {@link Contact} entities. */
public static class ForeignKeyContactIndex extends ForeignKeyIndex<Contact> {}
/** The {@link ForeignKeyIndex} type for {@link Domain} entities. */
public static class ForeignKeyDomainIndex extends ForeignKeyIndex<Domain> {}
/** The {@link ForeignKeyIndex} type for {@link Host} entities. */
public static class ForeignKeyHostIndex extends ForeignKeyIndex<Host> {}
private static final ImmutableBiMap<
Class<? extends EppResource>, Class<? extends ForeignKeyIndex<?>>>
RESOURCE_CLASS_TO_FKI_CLASS =
ImmutableBiMap.of(
Contact.class, ForeignKeyContactIndex.class,
Domain.class, ForeignKeyDomainIndex.class,
Host.class, ForeignKeyHostIndex.class);
private static final ImmutableMap<Class<? extends EppResource>, String>
RESOURCE_CLASS_TO_FKI_PROPERTY =
ImmutableMap.of(
Contact.class, "contactId",
Domain.class, "fullyQualifiedDomainName",
Host.class, "fullyQualifiedHostName");
String foreignKey;
/**
* The deletion time of this {@link ForeignKeyIndex}.
*
* <p>This will generally be equal to the deletion time of {@link #reference}. However, in the
* case of a {@link Host} that was renamed, this field will hold the time of the rename.
*/
DateTime deletionTime;
/** The referenced resource. */
VKey<E> reference;
public String getForeignKey() {
return foreignKey;
}
public DateTime getDeletionTime() {
return deletionTime;
}
public VKey<E> getResourceKey() {
return reference;
}
@SuppressWarnings("unchecked")
public static <T extends EppResource> Class<ForeignKeyIndex<T>> mapToFkiClass(
Class<T> resourceClass) {
return (Class<ForeignKeyIndex<T>>) RESOURCE_CLASS_TO_FKI_CLASS.get(resourceClass);
}
/** Create a {@link ForeignKeyIndex} instance for a resource, expiring at a specified time. */
@SuppressWarnings("unchecked")
private static <E extends EppResource> ForeignKeyIndex<E> create(
E resource, DateTime deletionTime) {
Class<E> resourceClass = (Class<E>) resource.getClass();
ForeignKeyIndex<E> instance = instantiate(mapToFkiClass(resourceClass));
instance.reference = (VKey<E>) resource.createVKey();
instance.foreignKey = resource.getForeignKey();
instance.deletionTime = deletionTime;
return instance;
}
/**
* Loads a {@link VKey} to an {@link EppResource} from the database by foreign key.
*
* <p>Returns null if no foreign key index with this foreign key was ever created, or if the most
* recently created foreign key index was deleted before time "now". This method does not actually
* check that the referenced resource actually exists. However, for normal epp resources, it is
* safe to assume that the referenced resource exists if the foreign key index does.
*
* @param clazz the resource type to load
* @param foreignKey id 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> loadAndGetKey(
Class<E> clazz, String foreignKey, DateTime now) {
ForeignKeyIndex<E> index = load(clazz, foreignKey, now);
return index == null ? null : index.getResourceKey();
}
/**
* Load a {@link ForeignKeyIndex} by class and id string that is active at or after the specified
* moment in time.
*
* <p>This will return null if the {@link ForeignKeyIndex} doesn't exist or has been soft deleted.
*/
@Nullable
public static <E extends EppResource> ForeignKeyIndex<E> load(
Class<E> clazz, String foreignKey, DateTime now) {
return load(clazz, ImmutableList.of(foreignKey), now).get(foreignKey);
}
/**
* Load a map of {@link ForeignKeyIndex} instances by class and id strings that are active at or
* after the specified moment in time.
*
* <p>The returned map will omit any keys for which the {@link ForeignKeyIndex} doesn't exist or
* has been soft deleted.
*/
public static <E extends EppResource> ImmutableMap<String, ForeignKeyIndex<E>> load(
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
return loadIndexesFromStore(clazz, foreignKeys, false).entrySet().stream()
.filter(e -> now.isBefore(e.getValue().getDeletionTime()))
.collect(entriesToImmutableMap());
}
/**
* Helper method to load all of the most recent {@link ForeignKeyIndex}es 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.
*/
private static <E extends EppResource>
ImmutableMap<String, ForeignKeyIndex<E>> loadIndexesFromStore(
Class<E> clazz, Collection<String> foreignKeys, boolean useReplicaJpaTm) {
String property = RESOURCE_CLASS_TO_FKI_PROPERTY.get(clazz);
JpaTransactionManager jpaTmToUse = useReplicaJpaTm ? replicaJpaTm() : jpaTm();
ImmutableList<ForeignKeyIndex<E>> indexes =
jpaTmToUse.transact(
() ->
jpaTmToUse
.criteriaQuery(
CriteriaQueryBuilder.create(clazz)
.whereFieldIsIn(property, foreignKeys)
.build())
.getResultStream()
.map(e -> create(e, e.getDeletionTime()))
.collect(toImmutableList()));
// We need to find and return the entities with the maximum deletionTime for each foreign key.
return Multimaps.index(indexes, ForeignKeyIndex::getForeignKey).asMap().entrySet().stream()
.map(
entry ->
Maps.immutableEntry(
entry.getKey(),
entry.getValue().stream()
.max(Comparator.comparing(ForeignKeyIndex::getDeletionTime))
.get()))
.collect(entriesToImmutableMap());
}
static final CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> CACHE_LOADER =
new CacheLoader<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>() {
@Override
public Optional<ForeignKeyIndex<?>> load(VKey<ForeignKeyIndex<?>> key) {
String foreignKey = key.getSqlKey().toString();
return Optional.ofNullable(
loadIndexesFromStore(
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(key.getKind()),
ImmutableSet.of(foreignKey),
true)
.get(foreignKey));
}
@Override
public Map<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>> loadAll(
Iterable<? extends VKey<ForeignKeyIndex<?>>> keys) {
if (!keys.iterator().hasNext()) {
return ImmutableMap.of();
}
Class<? extends EppResource> resourceClass =
RESOURCE_CLASS_TO_FKI_CLASS.inverse().get(keys.iterator().next().getKind());
ImmutableSet<String> foreignKeys =
Streams.stream(keys).map(v -> v.getSqlKey().toString()).collect(toImmutableSet());
ImmutableSet<VKey<ForeignKeyIndex<?>>> typedKeys = ImmutableSet.copyOf(keys);
ImmutableMap<String, ? extends ForeignKeyIndex<? extends EppResource>> existingFkis =
loadIndexesFromStore(resourceClass, foreignKeys, true);
// ofy omits keys that don't have values in Datastore, so re-add them in
// here with Optional.empty() values.
return Maps.asMap(
typedKeys,
(VKey<ForeignKeyIndex<?>> key) ->
Optional.ofNullable(existingFkis.getOrDefault(key.getSqlKey().toString(), null)));
}
};
/**
* A limited size, limited time cache for foreign key entities.
*
* <p>This is only used to cache foreign key 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 the value type of this cache is 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(), and
* 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<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>
cacheForeignKeyIndexes = createForeignKeyIndexesCache(getEppResourceCachingDuration());
private static LoadingCache<VKey<ForeignKeyIndex<?>>, Optional<ForeignKeyIndex<?>>>
createForeignKeyIndexesCache(Duration expiry) {
return CacheUtils.newCacheBuilder(expiry)
.maximumSize(getEppResourceMaxCachedEntries())
.build(CACHE_LOADER);
}
@VisibleForTesting
public static void setCacheForTest(Optional<Duration> expiry) {
Duration effectiveExpiry = expiry.orElse(getEppResourceCachingDuration());
cacheForeignKeyIndexes = createForeignKeyIndexesCache(effectiveExpiry);
}
/**
* Load a list of {@link ForeignKeyIndex} instances by class and id 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 ForeignKeyIndex} 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, ForeignKeyIndex<E>> loadCached(
Class<E> clazz, Collection<String> foreignKeys, final DateTime now) {
if (!RegistryConfig.isEppResourceCachingEnabled()) {
return load(clazz, foreignKeys, now);
}
Class<? extends ForeignKeyIndex<?>> fkiClass = mapToFkiClass(clazz);
// Safe to cast VKey<FKI<E>> to VKey<FKI<?>>
@SuppressWarnings("unchecked")
ImmutableList<VKey<ForeignKeyIndex<?>>> fkiVKeys =
foreignKeys.stream()
.map(fk -> (VKey<ForeignKeyIndex<?>>) VKey.createSql(fkiClass, fk))
.collect(toImmutableList());
// This cast is safe because when we loaded ForeignKeyIndexes above we used type clazz, which
// is scoped to E.
@SuppressWarnings("unchecked")
ImmutableMap<String, ForeignKeyIndex<E>> fkisFromCache =
cacheForeignKeyIndexes.getAll(fkiVKeys).entrySet().stream()
.filter(entry -> entry.getValue().isPresent())
.filter(entry -> now.isBefore(entry.getValue().get().getDeletionTime()))
.collect(
toImmutableMap(
entry -> entry.getKey().getSqlKey().toString(),
entry -> (ForeignKeyIndex<E>) entry.getValue().get()));
return fkisFromCache;
}
}
@@ -91,10 +91,10 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
public static DomainPendingActionNotificationResponse create(
String fullyQualifiedDomainName, boolean actionResult, Trid trid, DateTime processedDate) {
String domainName, boolean actionResult, Trid trid, DateTime processedDate) {
return init(
new DomainPendingActionNotificationResponse(),
fullyQualifiedDomainName,
domainName,
actionResult,
trid,
processedDate);
@@ -140,13 +140,9 @@ public class PendingActionNotificationResponse extends ImmutableObject
}
public static HostPendingActionNotificationResponse create(
String fullyQualifiedHostName, boolean actionResult, Trid trid, DateTime processedDate) {
String hostName, boolean actionResult, Trid trid, DateTime processedDate) {
return init(
new HostPendingActionNotificationResponse(),
fullyQualifiedHostName,
actionResult,
trid,
processedDate);
new HostPendingActionNotificationResponse(), hostName, actionResult, trid, processedDate);
}
}
}
@@ -46,7 +46,7 @@ import google.registry.model.transfer.TransferResponse;
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
import google.registry.persistence.VKey;
import google.registry.persistence.WithLongVKey;
import google.registry.persistence.WithVKey;
import google.registry.util.NullIgnoringCollectionBuilder;
import java.util.Optional;
import javax.persistence.AttributeOverride;
@@ -342,7 +342,7 @@ public abstract class PollMessage extends ImmutableObject
*/
@Entity
@DiscriminatorValue("ONE_TIME")
@WithLongVKey(compositeKey = true)
@WithVKey(Long.class)
public static class OneTime extends PollMessage {
@Embedded
@@ -386,7 +386,7 @@ public abstract class PollMessage extends ImmutableObject
TransferResponse transferResponse;
@Column(name = "transfer_response_domain_name")
String fullyQualifiedDomainName;
String domainName;
@Column(name = "transfer_response_domain_expiration_time")
DateTime extendedRegistrationExpirationTime;
@@ -427,7 +427,7 @@ public abstract class PollMessage extends ImmutableObject
pendingActionNotificationResponse.getActionResult(),
pendingActionNotificationResponse.getTrid(),
pendingActionNotificationResponse.processedDate);
} else if (fullyQualifiedDomainName != null) {
} else if (domainName != null) {
pendingActionNotificationResponse =
DomainPendingActionNotificationResponse.create(
pendingActionNotificationResponse.nameOrId.value,
@@ -457,10 +457,10 @@ public abstract class PollMessage extends ImmutableObject
.setPendingTransferExpirationTime(
transferResponse.getPendingTransferExpirationTime())
.build();
} else if (fullyQualifiedDomainName != null) {
} else if (domainName != null) {
transferResponse =
new DomainTransferResponse.Builder()
.setFullyQualifiedDomainName(fullyQualifiedDomainName)
.setDomainName(domainName)
.setGainingRegistrarId(transferResponse.getGainingRegistrarId())
.setLosingRegistrarId(transferResponse.getLosingRegistrarId())
.setTransferStatus(transferResponse.getTransferStatus())
@@ -486,7 +486,7 @@ public abstract class PollMessage extends ImmutableObject
OneTime instance = getInstance();
// Note: In its current form, the code will basically just ignore everything but the first
// PendingActionNotificationResponse and TransferResponse in responseData, and will override
// any identifier fields (e.g. contactId, fullyQualifiedDomainName) obtained from the
// any identifier fields (e.g. contactId, domainName) obtained from the
// PendingActionNotificationResponse if a TransferResponse is found with different values
// for those fields. It is not clear what the constraints should be on this data or
// whether we should enforce them here, though historically we have not, so the current
@@ -507,8 +507,7 @@ public abstract class PollMessage extends ImmutableObject
instance.contactId = instance.pendingActionNotificationResponse.nameOrId.value;
} else if (instance.pendingActionNotificationResponse
instanceof DomainPendingActionNotificationResponse) {
instance.fullyQualifiedDomainName =
instance.pendingActionNotificationResponse.nameOrId.value;
instance.domainName = instance.pendingActionNotificationResponse.nameOrId.value;
} else if (instance.pendingActionNotificationResponse
instanceof HostPendingActionNotificationResponse) {
instance.hostId = instance.pendingActionNotificationResponse.nameOrId.value;
@@ -527,7 +526,7 @@ public abstract class PollMessage extends ImmutableObject
instance.contactId = ((ContactTransferResponse) instance.transferResponse).getContactId();
} else if (instance.transferResponse instanceof DomainTransferResponse) {
DomainTransferResponse response = (DomainTransferResponse) instance.transferResponse;
instance.fullyQualifiedDomainName = response.getFullyQualifiedDomainName();
instance.domainName = response.getDomainName();
instance.extendedRegistrationExpirationTime =
response.getExtendedRegistrationExpirationTime();
}
@@ -545,7 +544,7 @@ public abstract class PollMessage extends ImmutableObject
*/
@Entity
@DiscriminatorValue("AUTORENEW")
@WithLongVKey(compositeKey = true)
@WithVKey(Long.class)
public static class Autorenew extends PollMessage {
/** The target id of the autorenew event. */
@@ -28,10 +28,10 @@ public interface PremiumPricingEngine {
/**
* Returns the prices for the given fully qualified domain name at the given time.
*
* <p>Note that the fullyQualifiedDomainName must only contain a single part left of the TLD, i.e.
* subdomains are not allowed, but multi-part TLDs are.
* <p>Note that the domainName must only contain a single part left of the TLD, i.e. subdomains
* are not allowed, but multi-part TLDs are.
*/
DomainPrices getDomainPrices(String fullyQualifiedDomainName, DateTime priceTime);
DomainPrices getDomainPrices(String domainName, DateTime priceTime);
/**
* A class containing information on premium prices for a specific domain name.
@@ -34,9 +34,9 @@ public final class StaticPremiumListPricingEngine implements PremiumPricingEngin
@Inject StaticPremiumListPricingEngine() {}
@Override
public DomainPrices getDomainPrices(String fullyQualifiedDomainName, DateTime priceTime) {
String tld = getTldFromDomainName(fullyQualifiedDomainName);
String label = InternetDomainName.from(fullyQualifiedDomainName).parts().get(0);
public DomainPrices getDomainPrices(String domainName, DateTime priceTime) {
String tld = getTldFromDomainName(domainName);
String label = InternetDomainName.from(domainName).parts().get(0);
Registry registry = Registry.get(checkNotNull(tld, "tld"));
Optional<Money> premiumPrice =
registry.getPremiumListName().flatMap(pl -> PremiumListDao.getPremiumPrice(pl, label));
@@ -207,11 +207,10 @@ public class Registrar extends ImmutableObject
* Unique registrar client id. Must conform to "clIDType" as defined in RFC5730.
*
* @see <a href="http://tools.ietf.org/html/rfc5730#section-4.2">Shared Structure Schema</a>
* <p>TODO(b/177567432): Rename this field to registrarId.
*/
@Id
@Column(name = "registrarId", nullable = false)
String clientIdentifier;
@Column(nullable = false)
String registrarId;
/**
* Registrar name. This is a distinct from the client identifier since there are no restrictions
@@ -270,9 +269,7 @@ public class Registrar extends ImmutableObject
String failoverClientCertificateHash;
/** An allow list of netmasks (in CIDR notation) which the client is allowed to connect from. */
// TODO(b/177567432): Rename to ipAddressAllowList once Cloud SQL migration is complete.
@Column(name = "ip_address_allow_list")
List<CidrAddressBlock> ipAddressWhitelist;
List<CidrAddressBlock> ipAddressAllowList;
/** A hashed password for EPP access. The hash is a base64 encoded SHA256 string. */
String passwordHash;
@@ -408,7 +405,7 @@ public class Registrar extends ImmutableObject
boolean registryLockAllowed = false;
public String getRegistrarId() {
return clientIdentifier;
return registrarId;
}
public DateTime getCreationTime() {
@@ -498,7 +495,7 @@ public class Registrar extends ImmutableObject
}
public ImmutableList<CidrAddressBlock> getIpAddressAllowList() {
return nullToEmptyImmutableCopy(ipAddressWhitelist);
return nullToEmptyImmutableCopy(ipAddressAllowList);
}
public RegistrarAddress getLocalizedAddress() {
@@ -587,7 +584,7 @@ public class Registrar extends ImmutableObject
() ->
jpaTm()
.query("FROM RegistrarPoc WHERE registrarId = :registrarId", RegistrarPoc.class)
.setParameter("registrarId", clientIdentifier)
.setParameter("registrarId", registrarId)
.getResultStream()
.collect(toImmutableList()));
}
@@ -595,7 +592,7 @@ public class Registrar extends ImmutableObject
@Override
public Map<String, Object> toJsonMap() {
return new JsonMapBuilder()
.put("clientIdentifier", clientIdentifier)
.put("registrarId", registrarId)
.put("ianaIdentifier", ianaIdentifier)
.putString("creationTime", creationTime.getTimestamp())
.putString("lastUpdateTime", lastUpdateTime.getTimestamp())
@@ -655,7 +652,7 @@ public class Registrar extends ImmutableObject
/** Creates a {@link VKey} for this instance. */
@Override
public VKey<Registrar> createVKey() {
return createVKey(clientIdentifier);
return createVKey(registrarId);
}
/** Creates a {@link VKey} for the given {@code registrarId}. */
@@ -678,7 +675,7 @@ public class Registrar extends ImmutableObject
checkArgument(
Range.closed(3, 16).contains(registrarId.length()),
"Registrar ID must be 3-16 characters long.");
getInstance().clientIdentifier = registrarId;
getInstance().registrarId = registrarId;
return this;
}
@@ -796,7 +793,7 @@ public class Registrar extends ImmutableObject
}
public Builder setIpAddressAllowList(Iterable<CidrAddressBlock> ipAddressAllowList) {
getInstance().ipAddressWhitelist = ImmutableList.copyOf(ipAddressAllowList);
getInstance().ipAddressAllowList = ImmutableList.copyOf(ipAddressAllowList);
return this;
}
@@ -72,11 +72,9 @@ public class ClaimsList extends ImmutableObject {
* <p>Note that the value of this field is parsed from the claims list file(See this <a
* href="https://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.1">RFC</>), it is
* the DNL List creation datetime from the rfc.
*
* <p>TODO(b/177567432): Rename this field to tmdbGenerationTime.
*/
@Column(name = "tmdb_generation_time", nullable = false)
DateTime creationTime;
@Column(nullable = false)
DateTime tmdbGenerationTime;
/**
* A map from labels to claims keys.
@@ -143,7 +141,7 @@ public class ClaimsList extends ImmutableObject {
* creation datetime</a>
*/
public DateTime getTmdbGenerationTime() {
return creationTime;
return tmdbGenerationTime;
}
/** Returns the creation time of this claims list. */
@@ -225,7 +223,7 @@ public class ClaimsList extends ImmutableObject {
public static ClaimsList create(
DateTime tmdbGenerationTime, ImmutableMap<String, String> labelsToKeys) {
ClaimsList instance = new ClaimsList();
instance.creationTime = checkNotNull(tmdbGenerationTime);
instance.tmdbGenerationTime = checkNotNull(tmdbGenerationTime);
instance.labelsToKeys = checkNotNull(labelsToKeys);
return instance;
}
@@ -18,7 +18,7 @@ import javax.persistence.Embeddable;
/** Transfer data for contact. */
@Embeddable
public class ContactTransferData extends TransferData<ContactTransferData.Builder> {
public class ContactTransferData extends TransferData {
public static final ContactTransferData EMPTY = new ContactTransferData();
@Override
@@ -26,6 +26,11 @@ public class ContactTransferData extends TransferData<ContactTransferData.Builde
return EMPTY.equals(this);
}
@Override
protected Builder createEmptyBuilder() {
return new Builder();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -34,7 +34,7 @@ import org.joda.time.DateTime;
/** Transfer data for domain. */
@Embeddable
public class DomainTransferData extends TransferData<DomainTransferData.Builder> {
public class DomainTransferData extends TransferData {
public static final DomainTransferData EMPTY = new DomainTransferData();
/**
@@ -107,7 +107,7 @@ public class DomainTransferData extends TransferData<DomainTransferData.Builder>
@Override
public Builder copyConstantFieldsToBuilder() {
return super.copyConstantFieldsToBuilder().setTransferPeriod(transferPeriod);
return ((Builder) super.copyConstantFieldsToBuilder()).setTransferPeriod(transferPeriod);
}
public Period getTransferPeriod() {
@@ -157,6 +157,11 @@ public class DomainTransferData extends TransferData<DomainTransferData.Builder>
return EMPTY.equals(this);
}
@Override
protected Builder createEmptyBuilder() {
return new Builder();
}
@Override
public Builder asBuilder() {
return new Builder(clone(this));
@@ -27,7 +27,6 @@ import google.registry.model.eppcommon.Trid;
import google.registry.model.poll.PollMessage;
import google.registry.persistence.VKey;
import google.registry.util.NullIgnoringCollectionBuilder;
import google.registry.util.TypeUtils.TypeInstantiator;
import java.util.Set;
import javax.annotation.Nullable;
import javax.persistence.AttributeOverride;
@@ -41,10 +40,7 @@ import javax.persistence.MappedSuperclass;
* are implicitly transferred with their superordinate domain.
*/
@MappedSuperclass
public abstract class TransferData<
B extends
TransferData.Builder<? extends TransferData<?>, ? extends TransferData.Builder<?, ?>>>
extends BaseTransferObject implements Buildable {
public abstract class TransferData extends BaseTransferObject implements Buildable {
/** The transaction id of the most recent transfer request (or null if there never was one). */
@Embedded
@@ -102,6 +98,8 @@ public abstract class TransferData<
@Override
public abstract Builder<?, ?> asBuilder();
protected abstract Builder<?, ?> createEmptyBuilder();
/**
* Returns a fresh Builder populated only with the constant fields of this TransferData, i.e.
* those that are fixed and unchanging throughout the transfer process.
@@ -116,8 +114,8 @@ public abstract class TransferData<
* <li>transferPeriod
* </ul>
*/
public B copyConstantFieldsToBuilder() {
B newBuilder = new TypeInstantiator<B>(getClass()) {}.instantiate();
public Builder<?, ?> copyConstantFieldsToBuilder() {
Builder<?, ?> newBuilder = createEmptyBuilder();
newBuilder
.setTransferRequestTrid(transferRequestTrid)
.setTransferRequestTime(transferRequestTime)
@@ -129,7 +127,7 @@ public abstract class TransferData<
/** Maps serverApproveEntities set to the individual fields. */
static void mapServerApproveEntitiesToFields(
Set<VKey<? extends TransferServerApproveEntity>> serverApproveEntities,
TransferData<?> transferData) {
TransferData transferData) {
if (isNullOrEmpty(serverApproveEntities)) {
transferData.pollMessageId1 = null;
transferData.pollMessageId2 = null;
@@ -161,7 +159,7 @@ public abstract class TransferData<
}
/** Builder for {@link TransferData} because it is immutable. */
public abstract static class Builder<T extends TransferData<?>, B extends Builder<T, B>>
public abstract static class Builder<T extends TransferData, B extends Builder<T, B>>
extends BaseTransferObject.Builder<T, B> {
/** Create a {@link Builder} wrapping a new instance. */
@@ -33,22 +33,24 @@ public class TransferResponse extends BaseTransferObject implements ResponseData
/** An adapter to output the XML in response to a transfer command on a domain. */
@XmlRootElement(name = "trnData", namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(propOrder = {
"fullyQualifiedDomainName",
"transferStatus",
"gainingClientId",
"transferRequestTime",
"losingClientId",
"pendingTransferExpirationTime",
"extendedRegistrationExpirationTime"},
namespace = "urn:ietf:params:xml:ns:domain-1.0")
@XmlType(
propOrder = {
"domainName",
"transferStatus",
"gainingClientId",
"transferRequestTime",
"losingClientId",
"pendingTransferExpirationTime",
"extendedRegistrationExpirationTime"
},
namespace = "urn:ietf:params:xml:ns:domain-1.0")
public static class DomainTransferResponse extends TransferResponse {
@XmlElement(name = "name")
String fullyQualifiedDomainName;
String domainName;
public String getFullyQualifiedDomainName() {
return fullyQualifiedDomainName;
public String getDomainName() {
return domainName;
}
/**
@@ -65,8 +67,8 @@ public class TransferResponse extends BaseTransferObject implements ResponseData
/** Builder for {@link DomainTransferResponse}. */
public static class Builder
extends BaseTransferObject.Builder<DomainTransferResponse, Builder> {
public Builder setFullyQualifiedDomainName(String fullyQualifiedDomainName) {
getInstance().fullyQualifiedDomainName = fullyQualifiedDomainName;
public Builder setDomainName(String domainName) {
getInstance().domainName = domainName;
return this;
}
@@ -278,7 +278,7 @@ public abstract class PersistenceModule {
replicaInstanceConnectionName.ifPresent(
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
overrides.put(
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_READ_COMMITTED.name());
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ.name());
return new JpaTransactionManagerImpl(create(overrides), clock);
}
@@ -294,7 +294,7 @@ public abstract class PersistenceModule {
replicaInstanceConnectionName.ifPresent(
name -> overrides.put(HIKARI_DS_CLOUD_SQL_INSTANCE, name));
overrides.put(
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_READ_COMMITTED.name());
Environment.ISOLATION, TransactionIsolationLevel.TRANSACTION_REPEATABLE_READ.name());
return new JpaTransactionManagerImpl(create(overrides), clock);
}
@@ -1,42 +0,0 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.persistence;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import javax.persistence.AttributeConverter;
import javax.persistence.Entity;
/**
* Annotation for {@link Entity} which id is string type and needs an {@link AttributeConverter} for
* its VKey.
*/
@Target({ElementType.TYPE})
public @interface WithStringVKey {
/**
* Sets the suffix of the class name for the {@link AttributeConverter} generated by
* StringVKeyProcessor. If not set, the suffix will be the type name of the VKey. Note that the
* class name will be "VKeyConverter_" concatenated with the suffix.
*/
String classNameSuffix() default "";
/**
* Set to true if this is a composite vkey.
*
* <p>For composite VKeys, we don't attempt to define an objectify key when loading from SQL: the
* enclosing class has to take care of that.
*/
boolean compositeKey() default false;
}
@@ -1,4 +1,4 @@
// Copyright 2020 The Nomulus Authors. All Rights Reserved.
// Copyright 2022 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,29 +14,22 @@
package google.registry.persistence;
import java.io.Serializable;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import javax.persistence.AttributeConverter;
import javax.persistence.Entity;
/**
* Annotation for {@link Entity} which id is long type and needs an {@link AttributeConverter} for
* its VKey.
* Annotation for {@link Entity} that can be saved as a foreign key in the form of a {@link VKey} in
* another table.
*
* <p>A {@link AttributeConverter} named {@code VKeyConverter_[EntityClassSimpleName]} will be
* automatically generated by {@code google.registry.processors.VKeyProcessor}, this class must be
* manually added to {@code persistence.xml} in order for it to be picked up by Hibernate.
*/
@Target({ElementType.TYPE})
public @interface WithLongVKey {
/**
* Sets the suffix of the class name for the {@link AttributeConverter} generated by
* LongVKeyProcessor. If not set, the suffix will be the type name of the VKey. Note that the
* class name will be "VKeyConverter_" concatenated with the suffix.
*/
String classNameSuffix() default "";
/**
* Set to true if this is a composite vkey.
*
* <p>For composite VKeys, we don't attempt to define an objectify key when loading from SQL: the
* enclosing class has to take care of that.
*/
boolean compositeKey() default false;
public @interface WithVKey {
/** The type of the SQL primary ID of the entity that is saved in the {@link VKey} */
Class<? extends Serializable> value();
}

Some files were not shown because too many files have changed in this diff Show More