mirror of
https://github.com/google/nomulus
synced 2026-06-09 16:33:02 +00:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b794347e6 | |||
| 26fb5388a4 | |||
| bd443633f6 | |||
| d87f119b36 | |||
| 54f1357d83 | |||
| c73d154084 | |||
| 259d2e2cdc | |||
| 0f174d9ce0 | |||
| ca2edb6a17 | |||
| 3947ac6ef7 | |||
| 579a3d0ac1 | |||
| 5fe929b027 | |||
| fb335b7d89 | |||
| a0f4013d53 | |||
| 5e596bb389 | |||
| f62fd82803 | |||
| b7353ef338 | |||
| 832e1ce047 | |||
| 8087f5bbca | |||
| 7f3dbfb62f | |||
| 04f429c4d6 |
@@ -197,6 +197,10 @@ task runPresubmits(type: Exec) {
|
||||
args('config/presubmits.py')
|
||||
}
|
||||
|
||||
def javadocSource = []
|
||||
def javadocClasspath = []
|
||||
def javadocDependentTasks = []
|
||||
|
||||
subprojects {
|
||||
// Skip no-op project
|
||||
if (project.name == 'services') return
|
||||
@@ -317,6 +321,10 @@ subprojects {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
javadocSource << project.sourceSets.main.allJava
|
||||
javadocClasspath << project.sourceSets.main.compileClasspath
|
||||
javadocDependentTasks << project.tasks.compileJava
|
||||
}
|
||||
|
||||
// If "-P verboseTestOutput=true" is passed in, configure all subprojects to dump all of their
|
||||
@@ -445,3 +453,18 @@ task javaIncrementalFormatApply {
|
||||
}
|
||||
|
||||
tasks.build.dependsOn(tasks.javaIncrementalFormatCheck)
|
||||
|
||||
task javadoc(type: Javadoc) {
|
||||
source javadocSource
|
||||
classpath = files(javadocClasspath)
|
||||
destinationDir = file("${buildDir}/docs/javadoc")
|
||||
// In a lot of places we don't write @return so suppress warnings about that.
|
||||
options.addBooleanOption('Xdoclint:all,-missing', true)
|
||||
options.addBooleanOption("-allow-script-in-comments",true)
|
||||
options.tags = ["type:a:Generic Type",
|
||||
"error:a:Expected Error"]
|
||||
}
|
||||
|
||||
tasks.build.dependsOn(tasks.javadoc)
|
||||
|
||||
javadocDependentTasks.each { tasks.javadoc.shouldRunAfter(it) }
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.joda.time.ReadableDuration;
|
||||
* An object which accepts requests to put the current thread to sleep.
|
||||
*
|
||||
* @see SystemSleeper
|
||||
* @see google.registry.testing.FakeSleeper
|
||||
*/
|
||||
@ThreadSafe
|
||||
public interface Sleeper {
|
||||
|
||||
@@ -880,6 +880,9 @@ task standardTest(type: FilteringTest) {
|
||||
// forkEvery 1
|
||||
|
||||
// Sets the maximum number of test executors that may exist at the same time.
|
||||
// Also, Gradle executes tests in 1 thread and some of our test infrastructures
|
||||
// depend on that, e.g. DualDatabaseTestInvocationContextProvider injects
|
||||
// different implementation of TransactionManager into TransactionManagerFactory.
|
||||
maxParallelForks 5
|
||||
|
||||
systemProperty 'test.projectRoot', rootProject.projectRootDir
|
||||
|
||||
@@ -283,7 +283,9 @@ public class DeleteContactsAndHostsAction implements Runnable {
|
||||
/** Determine whether the target resource is a linked resource on the domain. */
|
||||
private boolean isLinked(DomainBase domain, Key<? extends EppResource> resourceKey) {
|
||||
if (resourceKey.getKind().equals(KIND_CONTACT)) {
|
||||
return domain.getReferencedContacts().contains(resourceKey);
|
||||
return domain
|
||||
.getReferencedContacts()
|
||||
.contains(VKey.createOfy(ContactResource.class, (Key<ContactResource>) resourceKey));
|
||||
} else if (resourceKey.getKind().equals(KIND_HOST)) {
|
||||
return domain
|
||||
.getNameservers()
|
||||
|
||||
@@ -155,89 +155,100 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
|
||||
}
|
||||
int numBillingEventsSaved = 0;
|
||||
try {
|
||||
numBillingEventsSaved = tm().transactNew(() -> {
|
||||
ImmutableSet.Builder<OneTime> syntheticOneTimesBuilder =
|
||||
new ImmutableSet.Builder<>();
|
||||
final Registry tld = Registry.get(getTldFromDomainName(recurring.getTargetId()));
|
||||
numBillingEventsSaved =
|
||||
tm().transactNew(
|
||||
() -> {
|
||||
ImmutableSet.Builder<OneTime> syntheticOneTimesBuilder =
|
||||
new ImmutableSet.Builder<>();
|
||||
final Registry tld =
|
||||
Registry.get(getTldFromDomainName(recurring.getTargetId()));
|
||||
|
||||
// Determine the complete set of times at which this recurring event should occur
|
||||
// (up to and including the runtime of the mapreduce).
|
||||
Iterable<DateTime> eventTimes =
|
||||
recurring.getRecurrenceTimeOfYear().getInstancesInRange(Range.closed(
|
||||
recurring.getEventTime(),
|
||||
earliestOf(recurring.getRecurrenceEndTime(), executeTime)));
|
||||
// Determine the complete set of times at which this recurring event should
|
||||
// occur (up to and including the runtime of the mapreduce).
|
||||
Iterable<DateTime> eventTimes =
|
||||
recurring
|
||||
.getRecurrenceTimeOfYear()
|
||||
.getInstancesInRange(
|
||||
Range.closed(
|
||||
recurring.getEventTime(),
|
||||
earliestOf(recurring.getRecurrenceEndTime(), executeTime)));
|
||||
|
||||
// Convert these event times to billing times
|
||||
final ImmutableSet<DateTime> billingTimes =
|
||||
getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld);
|
||||
// Convert these event times to billing times
|
||||
final ImmutableSet<DateTime> billingTimes =
|
||||
getBillingTimesInScope(eventTimes, cursorTime, executeTime, tld);
|
||||
|
||||
Key<? extends EppResource> domainKey = recurring.getParentKey().getParent();
|
||||
Iterable<OneTime> oneTimesForDomain =
|
||||
ofy().load().type(OneTime.class).ancestor(domainKey);
|
||||
Key<? extends EppResource> domainKey = recurring.getParentKey().getParent();
|
||||
Iterable<OneTime> oneTimesForDomain =
|
||||
ofy().load().type(OneTime.class).ancestor(domainKey);
|
||||
|
||||
// Determine the billing times that already have OneTime events persisted.
|
||||
ImmutableSet<DateTime> existingBillingTimes =
|
||||
getExistingBillingTimes(oneTimesForDomain, recurring);
|
||||
// Determine the billing times that already have OneTime events persisted.
|
||||
ImmutableSet<DateTime> existingBillingTimes =
|
||||
getExistingBillingTimes(oneTimesForDomain, recurring);
|
||||
|
||||
ImmutableSet.Builder<HistoryEntry> historyEntriesBuilder =
|
||||
new ImmutableSet.Builder<>();
|
||||
// Create synthetic OneTime events for all billing times that do not yet have an event
|
||||
// persisted.
|
||||
for (DateTime billingTime : difference(billingTimes, existingBillingTimes)) {
|
||||
// Construct a new HistoryEntry that parents over the OneTime
|
||||
HistoryEntry historyEntry = new HistoryEntry.Builder()
|
||||
.setBySuperuser(false)
|
||||
.setClientId(recurring.getClientId())
|
||||
.setModificationTime(tm().getTransactionTime())
|
||||
.setParent(domainKey)
|
||||
.setPeriod(Period.create(1, YEARS))
|
||||
.setReason("Domain autorenewal by ExpandRecurringBillingEventsAction")
|
||||
.setRequestedByRegistrar(false)
|
||||
.setType(DOMAIN_AUTORENEW)
|
||||
// Don't write a domain transaction record if the recurrence was ended prior to the
|
||||
// billing time (i.e. a domain was deleted during the autorenew grace period).
|
||||
.setDomainTransactionRecords(
|
||||
recurring.getRecurrenceEndTime().isBefore(billingTime)
|
||||
? ImmutableSet.of()
|
||||
: ImmutableSet.of(
|
||||
DomainTransactionRecord.create(
|
||||
tld.getTldStr(),
|
||||
// We report this when the autorenew grace period ends
|
||||
billingTime,
|
||||
TransactionReportField.netRenewsFieldFromYears(1),
|
||||
1)))
|
||||
.build();
|
||||
historyEntriesBuilder.add(historyEntry);
|
||||
ImmutableSet.Builder<HistoryEntry> historyEntriesBuilder =
|
||||
new ImmutableSet.Builder<>();
|
||||
// Create synthetic OneTime events for all billing times that do not yet have
|
||||
// an event persisted.
|
||||
for (DateTime billingTime : difference(billingTimes, existingBillingTimes)) {
|
||||
// Construct a new HistoryEntry that parents over the OneTime
|
||||
HistoryEntry historyEntry =
|
||||
new HistoryEntry.Builder()
|
||||
.setBySuperuser(false)
|
||||
.setClientId(recurring.getClientId())
|
||||
.setModificationTime(tm().getTransactionTime())
|
||||
.setParent(domainKey)
|
||||
.setPeriod(Period.create(1, YEARS))
|
||||
.setReason(
|
||||
"Domain autorenewal by ExpandRecurringBillingEventsAction")
|
||||
.setRequestedByRegistrar(false)
|
||||
.setType(DOMAIN_AUTORENEW)
|
||||
// Don't write a domain transaction record if the recurrence was
|
||||
// ended prior to the billing time (i.e. a domain was deleted
|
||||
// during the autorenew grace period).
|
||||
.setDomainTransactionRecords(
|
||||
recurring.getRecurrenceEndTime().isBefore(billingTime)
|
||||
? ImmutableSet.of()
|
||||
: ImmutableSet.of(
|
||||
DomainTransactionRecord.create(
|
||||
tld.getTldStr(),
|
||||
// We report this when the autorenew grace period
|
||||
// ends
|
||||
billingTime,
|
||||
TransactionReportField.netRenewsFieldFromYears(1),
|
||||
1)))
|
||||
.build();
|
||||
historyEntriesBuilder.add(historyEntry);
|
||||
|
||||
DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength());
|
||||
// Determine the cost for a one-year renewal.
|
||||
Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1);
|
||||
syntheticOneTimesBuilder.add(new OneTime.Builder()
|
||||
.setBillingTime(billingTime)
|
||||
.setClientId(recurring.getClientId())
|
||||
.setCost(renewCost)
|
||||
.setEventTime(eventTime)
|
||||
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
|
||||
.setParent(historyEntry)
|
||||
.setPeriodYears(1)
|
||||
.setReason(recurring.getReason())
|
||||
.setSyntheticCreationTime(executeTime)
|
||||
.setCancellationMatchingBillingEvent(Key.create(recurring))
|
||||
.setTargetId(recurring.getTargetId())
|
||||
.build());
|
||||
}
|
||||
Set<HistoryEntry> historyEntries = historyEntriesBuilder.build();
|
||||
Set<OneTime> syntheticOneTimes = syntheticOneTimesBuilder.build();
|
||||
if (!isDryRun) {
|
||||
ImmutableSet<ImmutableObject> entitiesToSave =
|
||||
new ImmutableSet.Builder<ImmutableObject>()
|
||||
.addAll(historyEntries)
|
||||
.addAll(syntheticOneTimes)
|
||||
.build();
|
||||
ofy().save().entities(entitiesToSave).now();
|
||||
}
|
||||
return syntheticOneTimes.size();
|
||||
});
|
||||
DateTime eventTime = billingTime.minus(tld.getAutoRenewGracePeriodLength());
|
||||
// Determine the cost for a one-year renewal.
|
||||
Money renewCost = getDomainRenewCost(recurring.getTargetId(), eventTime, 1);
|
||||
syntheticOneTimesBuilder.add(
|
||||
new OneTime.Builder()
|
||||
.setBillingTime(billingTime)
|
||||
.setClientId(recurring.getClientId())
|
||||
.setCost(renewCost)
|
||||
.setEventTime(eventTime)
|
||||
.setFlags(union(recurring.getFlags(), Flag.SYNTHETIC))
|
||||
.setParent(historyEntry)
|
||||
.setPeriodYears(1)
|
||||
.setReason(recurring.getReason())
|
||||
.setSyntheticCreationTime(executeTime)
|
||||
.setCancellationMatchingBillingEvent(recurring.createVKey())
|
||||
.setTargetId(recurring.getTargetId())
|
||||
.build());
|
||||
}
|
||||
Set<HistoryEntry> historyEntries = historyEntriesBuilder.build();
|
||||
Set<OneTime> syntheticOneTimes = syntheticOneTimesBuilder.build();
|
||||
if (!isDryRun) {
|
||||
ImmutableSet<ImmutableObject> entitiesToSave =
|
||||
new ImmutableSet.Builder<ImmutableObject>()
|
||||
.addAll(historyEntries)
|
||||
.addAll(syntheticOneTimes)
|
||||
.build();
|
||||
ofy().save().entities(entitiesToSave).now();
|
||||
}
|
||||
return syntheticOneTimes.size();
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
getContext().incrementCounter("error: " + t.getClass().getSimpleName());
|
||||
getContext().incrementCounter(ERROR_COUNTER);
|
||||
@@ -279,7 +290,8 @@ public class ExpandRecurringBillingEventsAction implements Runnable {
|
||||
return Streams.stream(oneTimesForDomain)
|
||||
.filter(
|
||||
billingEvent ->
|
||||
Key.create(recurringEvent)
|
||||
recurringEvent
|
||||
.createVKey()
|
||||
.equals(billingEvent.getCancellationMatchingBillingEvent()))
|
||||
.map(OneTime::getBillingTime)
|
||||
.collect(toImmutableSet());
|
||||
|
||||
@@ -442,10 +442,8 @@ public class BigqueryConnection implements AutoCloseable {
|
||||
* Returns the result of calling queryToLocalTable, but synchronously to avoid spawning new
|
||||
* background threads, which App Engine doesn't support.
|
||||
*
|
||||
* @see <a href="https://cloud.google.com/appengine/docs/standard/java/runtime#Threads">
|
||||
* App Engine Runtime</a>
|
||||
*
|
||||
* <p>Returns the results of the query in an ImmutableTable on success.
|
||||
* @see <a href="https://cloud.google.com/appengine/docs/standard/java/runtime#Threads">App Engine
|
||||
* Runtime</a>
|
||||
*/
|
||||
public ImmutableTable<Integer, TableFieldSchema, Object> queryToLocalTableSync(String querySql) {
|
||||
Job job = new Job()
|
||||
@@ -576,8 +574,6 @@ public class BigqueryConnection implements AutoCloseable {
|
||||
|
||||
/**
|
||||
* Launch a job, wait for it to complete, but <i>do not</i> check for errors.
|
||||
*
|
||||
* @throws BigqueryJobFailureException
|
||||
*/
|
||||
public Job runJob(Job job, @Nullable AbstractInputStreamContent data) {
|
||||
return checkJob(waitForJob(launchJob(job, data)));
|
||||
|
||||
@@ -84,7 +84,7 @@ public class DnsMessageTransport {
|
||||
* @param query a message to send
|
||||
* @return the response received from the server
|
||||
* @throws IOException if the Socket input/output streams throws one
|
||||
* @throws IllegalArgumentException if the query is too large to be sent (> 65535 bytes)
|
||||
* @throws IllegalArgumentException if the query is too large to be sent (> 65535 bytes)
|
||||
*/
|
||||
public Message send(Message query) throws IOException {
|
||||
try (Socket socket = factory.createSocket(InetAddress.getByName(updateHost), DNS_PORT)) {
|
||||
|
||||
@@ -73,8 +73,10 @@ public class BigqueryPollJobAction implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
checkJobOutcome(); // Throws a NotModifiedException if the job hasn't completed.
|
||||
if (payload == null || payload.length == 0) {
|
||||
boolean jobOutcome =
|
||||
checkJobOutcome(); // Throws a NotModifiedException if the job hasn't completed.
|
||||
// If the job failed, do not enqueue the next step.
|
||||
if (!jobOutcome || payload == null || payload.length == 0) {
|
||||
return;
|
||||
}
|
||||
// If there is a payload, it's a chained task, so enqueue it.
|
||||
|
||||
@@ -42,8 +42,8 @@ import javax.xml.stream.events.XMLEvent;
|
||||
/**
|
||||
* Sanitizes sensitive data in incoming/outgoing EPP XML messages.
|
||||
*
|
||||
* <p>Current implementation masks user credentials (text following <pw> and <newPW> tags) as
|
||||
* follows:
|
||||
* <p>Current implementation masks user credentials (text following <pw> and <newPW>
|
||||
* tags) as follows:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A control character (in ranges [0 - 1F] and [7F - 9F]) is replaced with 'C'.
|
||||
|
||||
@@ -48,6 +48,7 @@ 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;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -96,8 +97,9 @@ public final class ResourceFlowUtils {
|
||||
queryForLinkedDomains(fki.getResourceKey(), now)
|
||||
.limit(FAILFAST_CHECK_COUNT)
|
||||
.keys();
|
||||
VKey<R> resourceVKey = VKey.createOfy(resourceClass, fki.getResourceKey());
|
||||
Predicate<DomainBase> predicate =
|
||||
domain -> getPotentialReferences.apply(domain).contains(fki.getResourceKey());
|
||||
domain -> getPotentialReferences.apply(domain).contains(resourceVKey);
|
||||
return ofy().load().keys(keys).values().stream().anyMatch(predicate)
|
||||
? new ResourceToDeleteIsReferencedException()
|
||||
: null;
|
||||
@@ -184,9 +186,8 @@ public final class ResourceFlowUtils {
|
||||
}
|
||||
// The roid should match one of the contacts.
|
||||
Optional<Key<ContactResource>> foundContact =
|
||||
domain
|
||||
.getReferencedContacts()
|
||||
.stream()
|
||||
domain.getReferencedContacts().stream()
|
||||
.map(VKey::getOfyKey)
|
||||
.filter(key -> key.getName().equals(authRepoId))
|
||||
.findFirst();
|
||||
if (!foundContact.isPresent()) {
|
||||
|
||||
@@ -75,7 +75,7 @@ public class DomainCheckFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
|
||||
/**
|
||||
* The time to perform the domain check as of. This defaults to the current time, but can be
|
||||
* overridden in v>=0.12 of the fee extension.
|
||||
* overridden in v>=0.12 of the fee extension.
|
||||
*/
|
||||
public abstract DateTime asOfDate();
|
||||
|
||||
@@ -105,7 +105,7 @@ public class DomainCheckFlowCustomLogic extends BaseFlowCustomLogic {
|
||||
|
||||
/**
|
||||
* The time to perform the domain check as of. This defaults to the current time, but can be
|
||||
* overridden in v>=0.12 of the fee extension.
|
||||
* overridden in v>=0.12 of the fee extension.
|
||||
*/
|
||||
public abstract DateTime asOfDate();
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.billing.BillingEvent.Recurring;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainCommand;
|
||||
import google.registry.model.domain.DomainCommand.Create;
|
||||
@@ -352,7 +353,7 @@ public class DomainCreateFlow implements TransactionalFlow {
|
||||
.setLaunchNotice(hasClaimsNotice ? launchCreate.get().getNotice() : null)
|
||||
.setSmdId(signedMarkId)
|
||||
.setDsData(secDnsCreate.isPresent() ? secDnsCreate.get().getDsData() : null)
|
||||
.setRegistrant(command.getRegistrant())
|
||||
.setRegistrant(VKey.createOfy(ContactResource.class, command.getRegistrant()))
|
||||
.setAuthInfo(command.getAuthInfo())
|
||||
.setFullyQualifiedDomainName(targetId)
|
||||
.setNameservers(
|
||||
@@ -533,7 +534,7 @@ public class DomainCreateFlow implements TransactionalFlow {
|
||||
.setPeriodYears(years)
|
||||
.setCost(feesAndCredits.getCreateCost())
|
||||
.setEventTime(now)
|
||||
.setAllocationToken(allocationToken.map(Key::create).orElse(null))
|
||||
.setAllocationToken(allocationToken.map(AllocationToken::createVKey).orElse(null))
|
||||
.setBillingTime(
|
||||
now.plus(
|
||||
isAnchorTenant
|
||||
|
||||
@@ -212,6 +212,28 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
builder.setDeletePollMessage(Key.create(deletePollMessage));
|
||||
}
|
||||
|
||||
// Cancel any grace periods that were still active, and set the expiration time accordingly.
|
||||
DateTime newExpirationTime = existingDomain.getRegistrationExpirationTime();
|
||||
for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
|
||||
// No cancellation is written if the grace period was not for a billable event.
|
||||
if (gracePeriod.hasBillingEvent()) {
|
||||
entitiesToSave.add(
|
||||
BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId));
|
||||
if (gracePeriod.getOneTimeBillingEvent() != null) {
|
||||
// Take the amount of amount of registration time being refunded off the expiration time.
|
||||
// This can be either add grace periods or renew grace periods.
|
||||
BillingEvent.OneTime oneTime =
|
||||
ofy().load().key(gracePeriod.getOneTimeBillingEvent()).now();
|
||||
newExpirationTime = newExpirationTime.minusYears(oneTime.getPeriodYears());
|
||||
} else if (gracePeriod.getRecurringBillingEvent() != null) {
|
||||
// Take 1 year off the registration if in the autorenew grace period (no need to load the
|
||||
// recurring billing event; all autorenews are for 1 year).
|
||||
newExpirationTime = newExpirationTime.minusYears(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
builder.setRegistrationExpirationTime(newExpirationTime);
|
||||
|
||||
DomainBase newDomain = builder.build();
|
||||
updateForeignKeyIndexDeletionTime(newDomain);
|
||||
handlePendingTransferOnDelete(existingDomain, newDomain, now, historyEntry);
|
||||
@@ -221,14 +243,7 @@ public final class DomainDeleteFlow implements TransactionalFlow {
|
||||
// event and poll message will already have been deleted in
|
||||
// ResourceDeleteFlow since it's listed in serverApproveEntities.
|
||||
dnsQueue.addDomainRefreshTask(existingDomain.getFullyQualifiedDomainName());
|
||||
// Cancel any grace periods that were still active.
|
||||
for (GracePeriod gracePeriod : existingDomain.getGracePeriods()) {
|
||||
// No cancellation is written if the grace period was not for a billable event.
|
||||
if (gracePeriod.hasBillingEvent()) {
|
||||
entitiesToSave.add(
|
||||
BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId));
|
||||
}
|
||||
}
|
||||
|
||||
entitiesToSave.add(newDomain, historyEntry);
|
||||
EntityChanges entityChanges = flowCustomLogic.beforeSave(
|
||||
BeforeSaveParameters.newBuilder()
|
||||
|
||||
@@ -35,6 +35,7 @@ import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRIS
|
||||
import static google.registry.model.registry.label.ReservationType.FULLY_BLOCKED;
|
||||
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_ANCHOR_TENANT;
|
||||
import static google.registry.model.registry.label.ReservationType.RESERVED_FOR_SPECIFIC_USE;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.pricing.PricingEngineProxy.isDomainPremium;
|
||||
import static google.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static google.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
@@ -120,6 +121,7 @@ import google.registry.model.reporting.DomainTransactionRecord;
|
||||
import google.registry.model.reporting.DomainTransactionRecord.TransactionReportField;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.tmch.ClaimsListShard;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.tldconfig.idn.IdnLabelValidator;
|
||||
import google.registry.util.Idn;
|
||||
import java.math.BigDecimal;
|
||||
@@ -309,7 +311,10 @@ public class DomainFlowUtils {
|
||||
Set<Key<HostResource>> nameservers)
|
||||
throws EppException {
|
||||
ImmutableList.Builder<Key<? extends EppResource>> keysToLoad = new ImmutableList.Builder<>();
|
||||
contacts.stream().map(DesignatedContact::getContactKey).forEach(keysToLoad::add);
|
||||
contacts.stream()
|
||||
.map(DesignatedContact::getContactKey)
|
||||
.map(VKey::getOfyKey)
|
||||
.forEach(keysToLoad::add);
|
||||
Optional.ofNullable(registrant).ifPresent(keysToLoad::add);
|
||||
keysToLoad.addAll(nameservers);
|
||||
verifyNotInPendingDelete(EppResource.loadCached(keysToLoad.build()).values());
|
||||
@@ -355,7 +360,7 @@ public class DomainFlowUtils {
|
||||
contacts.stream()
|
||||
.collect(
|
||||
toImmutableSetMultimap(
|
||||
DesignatedContact::getType, DesignatedContact::getContactKey));
|
||||
DesignatedContact::getType, contact -> contact.getContactKey().getOfyKey()));
|
||||
|
||||
// If any contact type has multiple contacts:
|
||||
if (contactsByType.asMap().values().stream().anyMatch(v -> v.size() > 1)) {
|
||||
@@ -978,7 +983,7 @@ public class DomainFlowUtils {
|
||||
for (DesignatedContact contact : contacts) {
|
||||
builder.add(
|
||||
ForeignKeyedDesignatedContact.create(
|
||||
contact.getType(), ofy().load().key(contact.getContactKey()).now().getContactId()));
|
||||
contact.getType(), tm().load(contact.getContactKey()).getContactId()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import static google.registry.flows.domain.DomainFlowUtils.addSecDnsExtensionIfP
|
||||
import static google.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||
import static google.registry.flows.domain.DomainFlowUtils.loadForeignKeyedDesignatedContacts;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -102,16 +101,16 @@ public final class DomainInfoFlow implements Flow {
|
||||
flowCustomLogic.afterValidation(
|
||||
AfterValidationParameters.newBuilder().setDomain(domain).build());
|
||||
// Prefetch all referenced resources. Calling values() blocks until loading is done.
|
||||
// We do nameservers separately since they've been converted to VKey.
|
||||
tm().load(domain.getNameservers());
|
||||
ofy().load().values(domain.getReferencedContacts()).values();
|
||||
tm().load(domain.getReferencedContacts());
|
||||
// Registrars can only see a few fields on unauthorized domains.
|
||||
// This is a policy decision that is left up to us by the rfcs.
|
||||
DomainInfoData.Builder infoBuilder = DomainInfoData.newBuilder()
|
||||
.setFullyQualifiedDomainName(domain.getFullyQualifiedDomainName())
|
||||
.setRepoId(domain.getRepoId())
|
||||
.setCurrentSponsorClientId(domain.getCurrentSponsorClientId())
|
||||
.setRegistrant(ofy().load().key(domain.getRegistrant()).now().getContactId());
|
||||
DomainInfoData.Builder infoBuilder =
|
||||
DomainInfoData.newBuilder()
|
||||
.setFullyQualifiedDomainName(domain.getFullyQualifiedDomainName())
|
||||
.setRepoId(domain.getRepoId())
|
||||
.setCurrentSponsorClientId(domain.getCurrentSponsorClientId())
|
||||
.setRegistrant(tm().load(domain.getRegistrant()).getContactId());
|
||||
// If authInfo is non-null, then the caller is authorized to see the full information since we
|
||||
// will have already verified the authInfo is valid.
|
||||
if (clientId.equals(domain.getCurrentSponsorClientId()) || authInfo.isPresent()) {
|
||||
|
||||
@@ -60,6 +60,7 @@ import google.registry.flows.domain.DomainFlowUtils.MissingRegistrantException;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.billing.BillingEvent;
|
||||
import google.registry.model.billing.BillingEvent.Reason;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
import google.registry.model.domain.DomainCommand.Update;
|
||||
import google.registry.model.domain.DomainCommand.Update.AddRemove;
|
||||
@@ -256,7 +257,12 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
.collect(toImmutableSet()))
|
||||
.addContacts(add.getContacts())
|
||||
.removeContacts(remove.getContacts())
|
||||
.setRegistrant(firstNonNull(change.getRegistrant(), domain.getRegistrant()))
|
||||
.setRegistrant(
|
||||
firstNonNull(
|
||||
change.getRegistrant() != null
|
||||
? VKey.createOfy(ContactResource.class, change.getRegistrant())
|
||||
: null,
|
||||
domain.getRegistrant()))
|
||||
.setAuthInfo(firstNonNull(change.getAuthInfo(), domain.getAuthInfo()));
|
||||
return domainBuilder.build();
|
||||
}
|
||||
@@ -269,7 +275,7 @@ public final class DomainUpdateFlow implements TransactionalFlow {
|
||||
|
||||
private void validateNewState(DomainBase newDomain) throws EppException {
|
||||
validateNoDuplicateContacts(newDomain.getContacts());
|
||||
validateRequiredContactsPresent(newDomain.getRegistrant(), newDomain.getContacts());
|
||||
validateRequiredContactsPresent(newDomain.getRegistrant().getOfyKey(), newDomain.getContacts());
|
||||
validateDsData(newDomain.getDsData());
|
||||
validateNameserversCountForTld(
|
||||
newDomain.getTld(),
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
package google.registry.flows.host;
|
||||
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.flows.FlowUtils.validateClientIsLoggedIn;
|
||||
import static google.registry.flows.ResourceFlowUtils.failfastForAsyncDelete;
|
||||
import static google.registry.flows.ResourceFlowUtils.loadAndVerifyExistence;
|
||||
@@ -82,17 +81,6 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||
@Inject EppResponse.Builder responseBuilder;
|
||||
@Inject HostDeleteFlow() {}
|
||||
|
||||
/**
|
||||
* Hack to convert DomainBase's nameserver VKey's to Ofy Key's.
|
||||
*
|
||||
* <p>We currently need this because {@code failfastForAsyncDelete()} checks to see if a name is
|
||||
* in the ofy keys and is used for both nameservers and contacts. When we convert contacts to
|
||||
* VKey's, we can remove this and do the conversion in {@code failfastForAsyncDelete()}.
|
||||
*/
|
||||
private static ImmutableSet<Key<HostResource>> getNameserverOfyKeys(DomainBase domain) {
|
||||
return domain.getNameservers().stream().map(key -> key.getOfyKey()).collect(toImmutableSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public final EppResponse run() throws EppException {
|
||||
extensionManager.register(MetadataExtension.class);
|
||||
@@ -100,7 +88,7 @@ public final class HostDeleteFlow implements TransactionalFlow {
|
||||
validateClientIsLoggedIn(clientId);
|
||||
DateTime now = tm().getTransactionTime();
|
||||
validateHostName(targetId);
|
||||
failfastForAsyncDelete(targetId, now, HostResource.class, HostDeleteFlow::getNameserverOfyKeys);
|
||||
failfastForAsyncDelete(targetId, now, HostResource.class, DomainBase::getNameservers);
|
||||
HostResource existingHost = loadAndVerifyExistence(HostResource.class, targetId, now);
|
||||
verifyNoDisallowedStatuses(existingHost, DISALLOWED_STATUSES);
|
||||
if (!isSuperuser) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -139,10 +140,13 @@ public class ModelUtils {
|
||||
|
||||
// If the field's type is the same as the field's class object, then it's a non-parameterized
|
||||
// type, and thus we just add it directly. We also don't bother looking at the parameterized
|
||||
// types of Key objects, since they are just references to other objects and don't actually
|
||||
// embed themselves in the persisted object anyway.
|
||||
// types of Key and VKey objects, since they are just references to other objects and don't
|
||||
// actually embed themselves in the persisted object anyway.
|
||||
Class<?> fieldClazz = field.getType();
|
||||
Type fieldType = field.getGenericType();
|
||||
if (VKey.class.equals(fieldClazz)) {
|
||||
continue;
|
||||
}
|
||||
builder.add(fieldClazz);
|
||||
if (fieldType.equals(fieldClazz) || Key.class.equals(clazz)) {
|
||||
continue;
|
||||
|
||||
@@ -50,7 +50,8 @@ import java.util.stream.Collectors;
|
||||
public class OteStats {
|
||||
|
||||
/**
|
||||
* Returns the statistics about the OT&E actions that have been taken by a particular registrar.
|
||||
* Returns the statistics about the OT&E actions that have been taken by a particular
|
||||
* registrar.
|
||||
*/
|
||||
public static OteStats getFromRegistrar(String registrarName) {
|
||||
return new OteStats().recordRegistrarHistory(registrarName);
|
||||
|
||||
@@ -30,7 +30,7 @@ public class UpdateAutoTimestamp extends ImmutableObject {
|
||||
|
||||
DateTime timestamp;
|
||||
|
||||
/** Returns the timestamp, or {@link START_OF_TIME} if it's null. */
|
||||
/** Returns the timestamp, or {@code START_OF_TIME} if it's null. */
|
||||
public DateTime getTimestamp() {
|
||||
return Optional.ofNullable(timestamp).orElse(START_OF_TIME);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.google.common.collect.Sets;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.Id;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import com.googlecode.objectify.annotation.Parent;
|
||||
@@ -43,14 +44,28 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import google.registry.model.domain.token.AllocationToken;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithLongVKey;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.AttributeOverrides;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** A billable event in a domain's lifecycle. */
|
||||
@MappedSuperclass
|
||||
@WithLongVKey
|
||||
public abstract class BillingEvent extends ImmutableObject
|
||||
implements Buildable, TransferServerApproveEntity {
|
||||
|
||||
@@ -93,24 +108,41 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
|
||||
/** Entity id. */
|
||||
@Id
|
||||
long id;
|
||||
@javax.persistence.Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
Long id;
|
||||
|
||||
@Parent
|
||||
@DoNotHydrate
|
||||
Key<HistoryEntry> parent;
|
||||
@Parent @DoNotHydrate @Transient Key<HistoryEntry> parent;
|
||||
|
||||
/** The registrar to bill. */
|
||||
@Index
|
||||
@Column(nullable = false)
|
||||
String clientId;
|
||||
|
||||
/** Revision id of the entry in DomainHistory table that ths bill belongs to. */
|
||||
// TODO(shicong): Add foreign key constraint when DomainHistory table is generated
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
Long domainHistoryRevisionId;
|
||||
|
||||
/** ID of the EPP resource that the bill is for. */
|
||||
// TODO(shicong): Add foreign key constraint when we expand DatastoreHelp for Postgresql
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
String domainRepoId;
|
||||
|
||||
/** When this event was created. For recurring events, this is also the recurrence start time. */
|
||||
@Index
|
||||
@Column(nullable = false)
|
||||
DateTime eventTime;
|
||||
|
||||
/** The reason for the bill. */
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(nullable = false)
|
||||
Reason reason;
|
||||
|
||||
/** The fully qualified domain name of the domain that the bill is for. */
|
||||
@Column(name = "domain_name", nullable = false)
|
||||
String targetId;
|
||||
|
||||
@Nullable
|
||||
@@ -120,6 +152,14 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public long getDomainHistoryRevisionId() {
|
||||
return domainHistoryRevisionId;
|
||||
}
|
||||
|
||||
public String getDomainRepoId() {
|
||||
return domainRepoId;
|
||||
}
|
||||
|
||||
public DateTime getEventTime() {
|
||||
return eventTime;
|
||||
}
|
||||
@@ -163,7 +203,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setId(Long id) {
|
||||
public B setId(long id) {
|
||||
getInstance().id = id;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
@@ -173,6 +213,16 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDomainHistoryRevisionId(long domainHistoryRevisionId) {
|
||||
getInstance().domainHistoryRevisionId = domainHistoryRevisionId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setDomainRepoId(String domainRepoId) {
|
||||
getInstance().domainRepoId = domainRepoId;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public B setEventTime(DateTime eventTime) {
|
||||
getInstance().eventTime = eventTime;
|
||||
return thisCastToDerived();
|
||||
@@ -194,6 +244,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
}
|
||||
|
||||
public B setParent(Key<HistoryEntry> parentKey) {
|
||||
// TODO(shicong): Figure out how to set domainHistoryRevisionId and domainRepoId
|
||||
getInstance().parent = parentKey;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
@@ -213,9 +264,23 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
/** A one-time billable event. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "BillingEvent")
|
||||
@javax.persistence.Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "clientId"),
|
||||
@javax.persistence.Index(columnList = "eventTime"),
|
||||
@javax.persistence.Index(columnList = "billingTime"),
|
||||
@javax.persistence.Index(columnList = "syntheticCreationTime"),
|
||||
@javax.persistence.Index(columnList = "allocation_token_id")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_event_id"))
|
||||
public static class OneTime extends BillingEvent {
|
||||
|
||||
/** The billable value. */
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "money.amount", column = @Column(name = "cost_amount")),
|
||||
@AttributeOverride(name = "money.currency", column = @Column(name = "cost_currency"))
|
||||
})
|
||||
Money cost;
|
||||
|
||||
/** When the cost should be billed. */
|
||||
@@ -223,8 +288,8 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
DateTime billingTime;
|
||||
|
||||
/**
|
||||
* The period in years of the action being billed for, if applicable, otherwise null.
|
||||
* Used for financial reporting.
|
||||
* The period in years of the action being billed for, if applicable, otherwise null. Used for
|
||||
* financial reporting.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
Integer periodYears = null;
|
||||
@@ -240,15 +305,21 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
|
||||
/**
|
||||
* For {@link Flag#SYNTHETIC} events, a {@link Key} to the {@link BillingEvent} from which this
|
||||
* OneTime was created. This is needed in order to properly match billing events against
|
||||
* {@link Cancellation}s.
|
||||
* OneTime was created. This is needed in order to properly match billing events against {@link
|
||||
* Cancellation}s.
|
||||
*/
|
||||
Key<? extends BillingEvent> cancellationMatchingBillingEvent;
|
||||
@Column(name = "cancellation_matching_billing_recurrence_id")
|
||||
VKey<? extends BillingEvent> cancellationMatchingBillingEvent;
|
||||
|
||||
/**
|
||||
* The {@link AllocationToken} used in the creation of this event, or null if one was not used.
|
||||
*
|
||||
* <p>TODO(shicong): Add foreign key constraint when AllocationToken schema is generated
|
||||
*/
|
||||
@Index @Nullable Key<AllocationToken> allocationToken;
|
||||
@Column(name = "allocation_token_id")
|
||||
@Index
|
||||
@Nullable
|
||||
VKey<AllocationToken> allocationToken;
|
||||
|
||||
public Money getCost() {
|
||||
return cost;
|
||||
@@ -266,14 +337,18 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return syntheticCreationTime;
|
||||
}
|
||||
|
||||
public Key<? extends BillingEvent> getCancellationMatchingBillingEvent() {
|
||||
public VKey<? extends BillingEvent> getCancellationMatchingBillingEvent() {
|
||||
return cancellationMatchingBillingEvent;
|
||||
}
|
||||
|
||||
public Optional<Key<AllocationToken>> getAllocationToken() {
|
||||
public Optional<VKey<AllocationToken>> getAllocationToken() {
|
||||
return Optional.ofNullable(allocationToken);
|
||||
}
|
||||
|
||||
public VKey<OneTime> createVKey() {
|
||||
return VKey.createOfy(getClass(), Key.create(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
@@ -311,12 +386,12 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
}
|
||||
|
||||
public Builder setCancellationMatchingBillingEvent(
|
||||
Key<? extends BillingEvent> cancellationMatchingBillingEvent) {
|
||||
VKey<? extends BillingEvent> cancellationMatchingBillingEvent) {
|
||||
getInstance().cancellationMatchingBillingEvent = cancellationMatchingBillingEvent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllocationToken(@Nullable Key<AllocationToken> allocationToken) {
|
||||
public Builder setAllocationToken(@Nullable VKey<AllocationToken> allocationToken) {
|
||||
getInstance().allocationToken = allocationToken;
|
||||
return this;
|
||||
}
|
||||
@@ -361,6 +436,15 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "BillingRecurrence")
|
||||
@javax.persistence.Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "clientId"),
|
||||
@javax.persistence.Index(columnList = "eventTime"),
|
||||
@javax.persistence.Index(columnList = "recurrenceEndTime"),
|
||||
@javax.persistence.Index(columnList = "recurrence_time_of_year")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id"))
|
||||
public static class Recurring extends BillingEvent {
|
||||
|
||||
/**
|
||||
@@ -384,6 +468,10 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* model, whereas the billing time is a fixed {@link org.joda.time.Duration} later.
|
||||
*/
|
||||
@Index
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@AttributeOverride(name = "timeString", column = @Column(name = "recurrence_time_of_year"))
|
||||
})
|
||||
TimeOfYear recurrenceTimeOfYear;
|
||||
|
||||
public DateTime getRecurrenceEndTime() {
|
||||
@@ -394,6 +482,10 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return recurrenceTimeOfYear;
|
||||
}
|
||||
|
||||
public VKey<Recurring> createVKey() {
|
||||
return VKey.createOfy(getClass(), Key.create(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
@@ -434,6 +526,14 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
*/
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@javax.persistence.Entity(name = "BillingCancellation")
|
||||
@javax.persistence.Table(
|
||||
indexes = {
|
||||
@javax.persistence.Index(columnList = "clientId"),
|
||||
@javax.persistence.Index(columnList = "eventTime"),
|
||||
@javax.persistence.Index(columnList = "billingTime")
|
||||
})
|
||||
@AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id"))
|
||||
public static class Cancellation extends BillingEvent {
|
||||
|
||||
/** The billing time of the charge that is being cancelled. */
|
||||
@@ -446,7 +546,8 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* <p>Although the type is {@link Key} the name "ref" is preserved for historical reasons.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
Key<BillingEvent.OneTime> refOneTime = null;
|
||||
@Column(name = "billing_event_id")
|
||||
VKey<BillingEvent.OneTime> refOneTime = null;
|
||||
|
||||
/**
|
||||
* The recurring billing event to cancel, or null for non-autorenew cancellations.
|
||||
@@ -454,13 +555,14 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
* <p>Although the type is {@link Key} the name "ref" is preserved for historical reasons.
|
||||
*/
|
||||
@IgnoreSave(IfNull.class)
|
||||
Key<BillingEvent.Recurring> refRecurring = null;
|
||||
@Column(name = "billing_recurrence_id")
|
||||
VKey<BillingEvent.Recurring> refRecurring = null;
|
||||
|
||||
public DateTime getBillingTime() {
|
||||
return billingTime;
|
||||
}
|
||||
|
||||
public Key<? extends BillingEvent> getEventKey() {
|
||||
public VKey<? extends BillingEvent> getEventKey() {
|
||||
return firstNonNull(refOneTime, refRecurring);
|
||||
}
|
||||
|
||||
@@ -492,13 +594,19 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
.setParent(historyEntry);
|
||||
// Set the grace period's billing event using the appropriate Cancellation builder method.
|
||||
if (gracePeriod.getOneTimeBillingEvent() != null) {
|
||||
builder.setOneTimeEventKey(gracePeriod.getOneTimeBillingEvent());
|
||||
builder.setOneTimeEventKey(
|
||||
VKey.createOfy(BillingEvent.OneTime.class, gracePeriod.getOneTimeBillingEvent()));
|
||||
} else if (gracePeriod.getRecurringBillingEvent() != null) {
|
||||
builder.setRecurringEventKey(gracePeriod.getRecurringBillingEvent());
|
||||
builder.setRecurringEventKey(
|
||||
VKey.createOfy(BillingEvent.Recurring.class, gracePeriod.getRecurringBillingEvent()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public VKey<Cancellation> createVKey() {
|
||||
return VKey.createOfy(getClass(), Key.create(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
@@ -518,12 +626,12 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setOneTimeEventKey(Key<BillingEvent.OneTime> eventKey) {
|
||||
public Builder setOneTimeEventKey(VKey<BillingEvent.OneTime> eventKey) {
|
||||
getInstance().refOneTime = eventKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRecurringEventKey(Key<BillingEvent.Recurring> eventKey) {
|
||||
public Builder setRecurringEventKey(VKey<BillingEvent.Recurring> eventKey) {
|
||||
getInstance().refRecurring = eventKey;
|
||||
return this;
|
||||
}
|
||||
@@ -540,9 +648,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An event representing a modification of an existing one-time billing event.
|
||||
*/
|
||||
/** An event representing a modification of an existing one-time billing event. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
public static class Modification extends BillingEvent {
|
||||
|
||||
@@ -29,20 +29,22 @@ import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import java.util.List;
|
||||
import javax.persistence.Embeddable;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A time of year (month, day, millis of day) that can be stored in a sort-friendly format.
|
||||
*
|
||||
* <p>This is conceptually similar to {@code MonthDay} in Joda or more generally to Joda's
|
||||
* {@code Partial}, but the parts we need are too simple to justify a full implementation of
|
||||
* {@code Partial}.
|
||||
* <p>This is conceptually similar to {@code MonthDay} in Joda or more generally to Joda's {@code
|
||||
* Partial}, but the parts we need are too simple to justify a full implementation of {@code
|
||||
* Partial}.
|
||||
*
|
||||
* <p>For simplicity, the native representation of this class's data is its stored format. This
|
||||
* allows it to be embeddable with no translation needed and also delays parsing of the string on
|
||||
* load until it's actually needed.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
public class TimeOfYear extends ImmutableObject {
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,7 +37,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
/** A collection of {@link ContactResource} commands. */
|
||||
public class ContactCommand {
|
||||
|
||||
/** The fields on "chgType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
|
||||
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@XmlTransient
|
||||
public static class ContactCreateOrChange extends ImmutableObject
|
||||
implements ResourceCreateOrChange<ContactResource.Builder> {
|
||||
@@ -111,8 +111,8 @@ public class ContactCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* A create command for a {@link ContactResource}, mapping "createType" from
|
||||
* {@link "http://tools.ietf.org/html/rfc5733"}.
|
||||
* A create command for a {@link ContactResource}, mapping "createType" from <a
|
||||
* href="http://tools.ietf.org/html/rfc5733">RFC5733</a>}.
|
||||
*/
|
||||
@XmlType(propOrder = {"contactId", "postalInfo", "voice", "fax", "email", "authInfo", "disclose"})
|
||||
@XmlRootElement
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static google.registry.model.EppResourceUtils.projectResourceOntoBuilderAtTime;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Entity;
|
||||
import com.googlecode.objectify.annotation.IgnoreSave;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
@@ -30,6 +31,8 @@ import google.registry.model.annotations.ExternalMessagingName;
|
||||
import google.registry.model.annotations.ReportedOn;
|
||||
import google.registry.model.contact.PostalInfo.Type;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import google.registry.schema.replay.DatastoreAndSqlEntity;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@@ -60,6 +63,7 @@ import org.joda.time.DateTime;
|
||||
@javax.persistence.Index(columnList = "searchName")
|
||||
})
|
||||
@ExternalMessagingName("contact")
|
||||
@WithStringVKey
|
||||
public class ContactResource extends EppResource
|
||||
implements DatastoreAndSqlEntity, ForeignKeyedEppResource, ResourceWithTransferData {
|
||||
|
||||
@@ -193,6 +197,10 @@ public class ContactResource extends EppResource
|
||||
})
|
||||
Disclose disclose;
|
||||
|
||||
public VKey<ContactResource> createVKey() {
|
||||
return VKey.createOfy(ContactResource.class, Key.create(this));
|
||||
}
|
||||
|
||||
public String getContactId() {
|
||||
return contactId;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import javax.persistence.Embedded;
|
||||
import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlType;
|
||||
|
||||
/** The "discloseType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
|
||||
/** The "discloseType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
@Embeddable
|
||||
@XmlType(propOrder = {"name", "org", "addr", "voice", "fax", "email"})
|
||||
@@ -76,7 +76,7 @@ public class Disclose extends ImmutableObject {
|
||||
return flag;
|
||||
}
|
||||
|
||||
/** The "intLocType" from {@link "http://tools.ietf.org/html/rfc5733"}. */
|
||||
/** The "intLocType" from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. */
|
||||
@Embed
|
||||
public static class PostalInfoChoice extends ImmutableObject {
|
||||
@XmlAttribute
|
||||
|
||||
@@ -32,8 +32,8 @@ import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
/**
|
||||
* Implementation of both "postalInfoType" and "chgPostalInfoType" from {@link
|
||||
* "http://tools.ietf.org/html/rfc5733"}.
|
||||
* Implementation of both "postalInfoType" and "chgPostalInfoType" from <a href=
|
||||
* "http://tools.ietf.org/html/rfc5733">RFC5733</a>.
|
||||
*/
|
||||
@Embed
|
||||
@Embeddable
|
||||
|
||||
@@ -18,9 +18,11 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.annotation.Embed;
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.Index;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.xml.bind.annotation.XmlEnumValue;
|
||||
|
||||
@@ -36,6 +38,9 @@ import javax.xml.bind.annotation.XmlEnumValue;
|
||||
* situation with hosts where client-side renames would make that data stale. However, we sometimes
|
||||
* rename contacts internally ourselves, and it's easier to use the same model for both cases.
|
||||
*
|
||||
* <p>This entity type is not persisted in Cloud SQL. The different roles are represented as
|
||||
* separate fields in the Domain table.
|
||||
*
|
||||
* @see <a href="http://tools.ietf.org/html/rfc5731#section-2.2">RFC 5731 - EPP Domain Name Mapping
|
||||
* - Contact and Client Identifiers</a>
|
||||
*/
|
||||
@@ -58,22 +63,28 @@ public class DesignatedContact extends ImmutableObject {
|
||||
REGISTRANT
|
||||
}
|
||||
|
||||
public static DesignatedContact create(Type type, Key<ContactResource> contact) {
|
||||
public static DesignatedContact create(Type type, VKey<ContactResource> contact) {
|
||||
DesignatedContact instance = new DesignatedContact();
|
||||
instance.type = type;
|
||||
instance.contact = checkArgumentNotNull(contact, "Must specify contact key");
|
||||
instance.contactVKey = checkArgumentNotNull(contact, "Must specify contact key");
|
||||
instance.contact = contact.maybeGetOfyKey().orElse(null);
|
||||
return instance;
|
||||
}
|
||||
|
||||
Type type;
|
||||
|
||||
@Index Key<ContactResource> contact;
|
||||
@Ignore VKey<ContactResource> contactVKey;
|
||||
|
||||
public Type getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Key<ContactResource> getContactKey() {
|
||||
return contact;
|
||||
public VKey<ContactResource> getContactKey() {
|
||||
return contactVKey;
|
||||
}
|
||||
|
||||
public DesignatedContact reconstitute() {
|
||||
return create(type, VKey.createOfy(ContactResource.class, contact));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ import javax.persistence.Column;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.JoinTable;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Interval;
|
||||
@@ -135,18 +136,11 @@ public class DomainBase extends EppResource
|
||||
@Index
|
||||
String tld;
|
||||
|
||||
/**
|
||||
* References to hosts that are the nameservers for the domain.
|
||||
*
|
||||
* <p>This is a legacy field: we have to preserve it because it is still persisted and indexed in
|
||||
* the datastore, but all external references go through nsHostVKeys.
|
||||
*/
|
||||
@Index @ElementCollection @Transient Set<Key<HostResource>> nsHosts;
|
||||
|
||||
@Ignore
|
||||
/** References to hosts that are the nameservers for the domain. */
|
||||
@Index
|
||||
@ElementCollection
|
||||
@JoinTable(name = "DomainHost")
|
||||
Set<VKey<HostResource>> nsHostVKeys;
|
||||
Set<VKey<HostResource>> nsHosts;
|
||||
|
||||
/**
|
||||
* The union of the contacts visible via {@link #getContacts} and {@link #getRegistrant}.
|
||||
@@ -155,6 +149,17 @@ public class DomainBase extends EppResource
|
||||
*/
|
||||
@Transient Set<DesignatedContact> allContacts;
|
||||
|
||||
/**
|
||||
* Contacts as they are stored in cloud SQL.
|
||||
*
|
||||
* <p>This information is duplicated in allContacts, and must be kept in sync with it.
|
||||
*/
|
||||
@Ignore VKey<ContactResource> adminContact;
|
||||
|
||||
@Ignore VKey<ContactResource> billingContact;
|
||||
@Ignore VKey<ContactResource> techContact;
|
||||
@Ignore VKey<ContactResource> registrantContact;
|
||||
|
||||
/** Authorization info (aka transfer secret) of the domain. */
|
||||
@Embedded
|
||||
@AttributeOverrides({
|
||||
@@ -257,10 +262,33 @@ public class DomainBase extends EppResource
|
||||
|
||||
@OnLoad
|
||||
void load() {
|
||||
nsHostVKeys =
|
||||
nullToEmptyImmutableCopy(nsHosts).stream()
|
||||
.map(hostKey -> VKey.createOfy(HostResource.class, hostKey))
|
||||
.collect(toImmutableSet());
|
||||
// Reconstitute all of the contacts so that they have VKeys.
|
||||
allContacts =
|
||||
allContacts.stream().map(contact -> contact.reconstitute()).collect(toImmutableSet());
|
||||
setContactFields(allContacts, true);
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
// Reconstitute the contact list.
|
||||
ImmutableSet.Builder<DesignatedContact> contactsBuilder =
|
||||
new ImmutableSet.Builder<DesignatedContact>();
|
||||
|
||||
if (registrantContact != null) {
|
||||
contactsBuilder.add(
|
||||
DesignatedContact.create(DesignatedContact.Type.REGISTRANT, registrantContact));
|
||||
}
|
||||
if (billingContact != null) {
|
||||
contactsBuilder.add(DesignatedContact.create(DesignatedContact.Type.BILLING, billingContact));
|
||||
}
|
||||
if (techContact != null) {
|
||||
contactsBuilder.add(DesignatedContact.create(DesignatedContact.Type.TECH, techContact));
|
||||
}
|
||||
if (adminContact != null) {
|
||||
contactsBuilder.add(DesignatedContact.create(DesignatedContact.Type.ADMIN, adminContact));
|
||||
}
|
||||
|
||||
allContacts = contactsBuilder.build();
|
||||
}
|
||||
|
||||
public ImmutableSet<String> getSubordinateHosts() {
|
||||
@@ -323,9 +351,7 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
public ImmutableSet<VKey<HostResource>> getNameservers() {
|
||||
// Since nsHostVKeys gets initialized both from setNameservers() and the OnLoad method, this
|
||||
// should always be valid.
|
||||
return nullToEmptyImmutableCopy(nsHostVKeys);
|
||||
return nullToEmptyImmutableCopy(nsHosts);
|
||||
}
|
||||
|
||||
public final String getCurrentSponsorClientId() {
|
||||
@@ -515,13 +541,20 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
/** A key to the registrant who registered this domain. */
|
||||
public Key<ContactResource> getRegistrant() {
|
||||
return nullToEmpty(allContacts)
|
||||
.stream()
|
||||
.filter(IS_REGISTRANT)
|
||||
.findFirst()
|
||||
.get()
|
||||
.getContactKey();
|
||||
public VKey<ContactResource> getRegistrant() {
|
||||
return registrantContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getAdminContact() {
|
||||
return adminContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getBillingContact() {
|
||||
return billingContact;
|
||||
}
|
||||
|
||||
public VKey<ContactResource> getTechContact() {
|
||||
return techContact;
|
||||
}
|
||||
|
||||
/** Associated contacts for the domain (other than registrant). */
|
||||
@@ -537,7 +570,7 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
/** Returns all referenced contacts from this domain or application. */
|
||||
public ImmutableSet<Key<ContactResource>> getReferencedContacts() {
|
||||
public ImmutableSet<VKey<ContactResource>> getReferencedContacts() {
|
||||
return nullToEmptyImmutableCopy(allContacts)
|
||||
.stream()
|
||||
.map(DesignatedContact::getContactKey)
|
||||
@@ -549,6 +582,37 @@ public class DomainBase extends EppResource
|
||||
return tld;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the individual contact fields from {@code contacts}.
|
||||
*
|
||||
* <p>The registrant field is only set if {@code includeRegistrant} is true, as this field needs
|
||||
* to be set in some circumstances but not in others.
|
||||
*/
|
||||
private void setContactFields(Set<DesignatedContact> contacts, boolean includeRegistrant) {
|
||||
|
||||
// Set the individual contact fields.
|
||||
for (DesignatedContact contact : contacts) {
|
||||
switch (contact.getType()) {
|
||||
case BILLING:
|
||||
billingContact = contact.getContactKey();
|
||||
break;
|
||||
case TECH:
|
||||
techContact = contact.getContactKey();
|
||||
break;
|
||||
case ADMIN:
|
||||
adminContact = contact.getContactKey();
|
||||
break;
|
||||
case REGISTRANT:
|
||||
if (includeRegistrant) {
|
||||
registrantContact = contact.getContactKey();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown contact resource type: " + contact.getType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Predicate to determine if a given {@link DesignatedContact} is the registrant. */
|
||||
private static final Predicate<DesignatedContact> IS_REGISTRANT =
|
||||
(DesignatedContact contact) -> DesignatedContact.Type.REGISTRANT.equals(contact.type);
|
||||
@@ -567,14 +631,6 @@ public class DomainBase extends EppResource
|
||||
|
||||
Builder(DomainBase instance) {
|
||||
super(instance);
|
||||
|
||||
// Convert nsHosts to nsHostVKeys.
|
||||
if (instance.nsHosts != null) {
|
||||
instance.nsHostVKeys =
|
||||
instance.nsHosts.stream()
|
||||
.map(key -> VKey.createOfy(HostResource.class, key))
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -593,7 +649,11 @@ public class DomainBase extends EppResource
|
||||
|
||||
checkArgumentNotNull(
|
||||
emptyToNull(instance.fullyQualifiedDomainName), "Missing fullyQualifiedDomainName");
|
||||
checkArgument(instance.allContacts.stream().anyMatch(IS_REGISTRANT), "Missing registrant");
|
||||
if (instance.getRegistrant() == null
|
||||
&& instance.allContacts.stream().anyMatch(IS_REGISTRANT)) {
|
||||
throw new IllegalArgumentException("registrant is null but is in allContacts");
|
||||
}
|
||||
checkArgumentNotNull(instance.getRegistrant(), "Missing registrant");
|
||||
instance.tld = getTldFromDomainName(instance.fullyQualifiedDomainName);
|
||||
return super.build();
|
||||
}
|
||||
@@ -611,11 +671,14 @@ public class DomainBase extends EppResource
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public Builder setRegistrant(Key<ContactResource> registrant) {
|
||||
public Builder setRegistrant(VKey<ContactResource> registrant) {
|
||||
// Replace the registrant contact inside allContacts.
|
||||
getInstance().allContacts = union(
|
||||
getInstance().getContacts(),
|
||||
DesignatedContact.create(Type.REGISTRANT, checkArgumentNotNull(registrant)));
|
||||
|
||||
// Set the registrant field specifically.
|
||||
getInstance().registrantContact = registrant;
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -625,27 +688,12 @@ public class DomainBase extends EppResource
|
||||
}
|
||||
|
||||
public Builder setNameservers(VKey<HostResource> nameserver) {
|
||||
Optional<Key<HostResource>> nsKey = nameserver.maybeGetOfyKey();
|
||||
if (nsKey.isPresent()) {
|
||||
getInstance().nsHosts = ImmutableSet.of(nsKey.get());
|
||||
} else {
|
||||
getInstance().nsHosts = null;
|
||||
}
|
||||
getInstance().nsHostVKeys = ImmutableSet.of(nameserver);
|
||||
getInstance().nsHosts = ImmutableSet.of(nameserver);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
public Builder setNameservers(ImmutableSet<VKey<HostResource>> nameservers) {
|
||||
// If we have all of the ofy keys, we can set nsHosts. Otherwise, make it null.
|
||||
if (nameservers != null
|
||||
&& nameservers.stream().allMatch(key -> key.maybeGetOfyKey().isPresent())) {
|
||||
getInstance().nsHosts =
|
||||
nameservers.stream().map(key -> key.getOfyKey()).collect(toImmutableSet());
|
||||
} else {
|
||||
getInstance().nsHosts = null;
|
||||
}
|
||||
|
||||
getInstance().nsHostVKeys = forceEmptyToNull(nameservers);
|
||||
getInstance().nsHosts = forceEmptyToNull(nameservers);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
@@ -673,12 +721,16 @@ public class DomainBase extends EppResource
|
||||
|
||||
public Builder setContacts(ImmutableSet<DesignatedContact> contacts) {
|
||||
checkArgument(contacts.stream().noneMatch(IS_REGISTRANT), "Registrant cannot be a contact");
|
||||
|
||||
// Replace the non-registrant contacts inside allContacts.
|
||||
getInstance().allContacts =
|
||||
Streams.concat(
|
||||
nullToEmpty(getInstance().allContacts).stream().filter(IS_REGISTRANT),
|
||||
contacts.stream())
|
||||
.collect(toImmutableSet());
|
||||
|
||||
// Set the individual fields.
|
||||
getInstance().setContactFields(contacts, false);
|
||||
return thisCastToDerived();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
|
||||
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.index.ForeignKeyIndex;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -71,10 +72,10 @@ public class DomainCommand {
|
||||
T cloneAndLinkReferences(DateTime now) throws InvalidReferencesException;
|
||||
}
|
||||
|
||||
/** The fields on "chgType" from {@link "http://tools.ietf.org/html/rfc5731"}. */
|
||||
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
|
||||
@XmlTransient
|
||||
public static class DomainCreateOrChange<B extends DomainBase.Builder>
|
||||
extends ImmutableObject implements ResourceCreateOrChange<B> {
|
||||
public static class DomainCreateOrChange<B extends DomainBase.Builder> extends ImmutableObject
|
||||
implements ResourceCreateOrChange<B> {
|
||||
|
||||
/** The contactId of the registrant who registered this domain. */
|
||||
@XmlElement(name = "registrant")
|
||||
@@ -102,19 +103,20 @@ public class DomainCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* A create command for a {@link DomainBase}, mapping "createType" from
|
||||
* {@link "http://tools.ietf.org/html/rfc5731"}.
|
||||
* A create command for a {@link DomainBase}, mapping "createType" from <a
|
||||
* href="http://tools.ietf.org/html/rfc5731">RFC5731</a>.
|
||||
*/
|
||||
@XmlRootElement
|
||||
@XmlType(propOrder = {
|
||||
"fullyQualifiedDomainName",
|
||||
"period",
|
||||
"nameserverFullyQualifiedHostNames",
|
||||
"registrantContactId",
|
||||
"foreignKeyedDesignatedContacts",
|
||||
"authInfo"})
|
||||
public static class Create
|
||||
extends DomainCreateOrChange<DomainBase.Builder>
|
||||
@XmlType(
|
||||
propOrder = {
|
||||
"fullyQualifiedDomainName",
|
||||
"period",
|
||||
"nameserverFullyQualifiedHostNames",
|
||||
"registrantContactId",
|
||||
"foreignKeyedDesignatedContacts",
|
||||
"authInfo"
|
||||
})
|
||||
public static class Create extends DomainCreateOrChange<DomainBase.Builder>
|
||||
implements CreateOrUpdate<Create> {
|
||||
|
||||
/** Fully qualified domain name, which serves as a unique identifier for this domain. */
|
||||
@@ -188,7 +190,7 @@ public class DomainCommand {
|
||||
now);
|
||||
for (DesignatedContact contact : contacts) {
|
||||
if (DesignatedContact.Type.REGISTRANT.equals(contact.getType())) {
|
||||
clone.registrant = contact.getContactKey();
|
||||
clone.registrant = contact.getContactKey().getOfyKey();
|
||||
clone.contacts = forceEmptyToNull(difference(contacts, contact));
|
||||
break;
|
||||
}
|
||||
@@ -439,8 +441,10 @@ public class DomainCommand {
|
||||
loadByForeignKeysCached(foreignKeys.build(), ContactResource.class, now);
|
||||
ImmutableSet.Builder<DesignatedContact> linkedContacts = new ImmutableSet.Builder<>();
|
||||
for (ForeignKeyedDesignatedContact contact : contacts) {
|
||||
linkedContacts.add(DesignatedContact.create(
|
||||
contact.type, loadedContacts.get(contact.contactId)));
|
||||
linkedContacts.add(
|
||||
DesignatedContact.create(
|
||||
contact.type,
|
||||
VKey.createOfy(ContactResource.class, loadedContacts.get(contact.contactId))));
|
||||
}
|
||||
return linkedContacts.build();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import javax.xml.bind.annotation.XmlAttribute;
|
||||
import javax.xml.bind.annotation.XmlEnumValue;
|
||||
import javax.xml.bind.annotation.XmlValue;
|
||||
|
||||
/** The "periodType" from {@link "http://tools.ietf.org/html/rfc5731"}. */
|
||||
/** The "periodType" from <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
|
||||
@Embed
|
||||
@javax.persistence.Embeddable
|
||||
public class Period extends ImmutableObject {
|
||||
|
||||
+2
@@ -29,11 +29,13 @@ import org.joda.time.DateTime;
|
||||
* An individual price check item in version 0.12 of the fee extension on domain check commands.
|
||||
* Items look like:
|
||||
*
|
||||
* <pre>{@code
|
||||
* <fee:command name="renew" phase="sunrise" subphase="hello">
|
||||
* <fee:period unit="y">1</fee:period>
|
||||
* <fee:class>premium</fee:class>
|
||||
* <fee:date>2017-05-17T13:22:21.0Z</fee:date>
|
||||
* </fee:command>
|
||||
* }</pre>
|
||||
*
|
||||
* In a change from previous versions of the extension, items do not contain domain names; instead,
|
||||
* the names from the non-extension check element are used.
|
||||
|
||||
@@ -95,9 +95,6 @@ public class LaunchNotice extends ImmutableObject {
|
||||
|
||||
/**
|
||||
* Validate the checksum of the notice against the domain label.
|
||||
*
|
||||
* @throws IllegalArgumentException
|
||||
* @throws InvalidChecksumException
|
||||
*/
|
||||
public void validate(String domainLabel) throws InvalidChecksumException {
|
||||
// According to http://tools.ietf.org/html/draft-lozano-tmch-func-spec-08#section-6.3, a TCNID
|
||||
|
||||
@@ -49,9 +49,9 @@ public enum GracePeriodStatus implements EppEnum {
|
||||
AUTO_RENEW("autoRenewPeriod"),
|
||||
|
||||
/**
|
||||
* This status value is used to describe a domain for which a <delete> command has been received,
|
||||
* but the domain has not yet been purged because an opportunity exists to restore the domain and
|
||||
* abort the deletion process.
|
||||
* This status value is used to describe a domain for which a <delete> command has been
|
||||
* received, but the domain has not yet been purged because an opportunity exists to restore the
|
||||
* domain and abort the deletion process.
|
||||
*/
|
||||
REDEMPTION("redemptionPeriod"),
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.model.common.TimedTransitionProperty.TimeMapper;
|
||||
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.persistence.WithStringVKey;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
@@ -53,6 +55,7 @@ import org.joda.time.DateTime;
|
||||
/** An entity representing an allocation token. */
|
||||
@ReportedOn
|
||||
@Entity
|
||||
@WithStringVKey
|
||||
public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
|
||||
// Promotions should only move forward, and ENDED / CANCELLED are terminal states.
|
||||
@@ -179,6 +182,10 @@ public class AllocationToken extends BackupGroupRoot implements Buildable {
|
||||
return tokenStatusTransitions;
|
||||
}
|
||||
|
||||
public VKey<AllocationToken> createVKey() {
|
||||
return VKey.createOfy(getClass(), Key.create(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder asBuilder() {
|
||||
return new Builder(clone(this));
|
||||
|
||||
@@ -25,7 +25,7 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
* An allocation token extension that may be present on EPP domain commands.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/draft-ietf-regext-allocation-token-04">the IETF
|
||||
* draft</a> for full details.
|
||||
* draft</a>
|
||||
*/
|
||||
@XmlRootElement(name = "allocationToken")
|
||||
public class AllocationTokenExtension extends ImmutableObject implements CommandExtension {
|
||||
|
||||
@@ -44,8 +44,9 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
/**
|
||||
* Container for generic street address.
|
||||
*
|
||||
* <p>This is the "addrType" type from {@link "http://tools.ietf.org/html/rfc5733"}. It also matches
|
||||
* the "addrType" type from {@link "http://tools.ietf.org/html/draft-lozano-tmch-smd"}.
|
||||
* <p>This is the "addrType" type from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. It
|
||||
* also matches the "addrType" type from <a
|
||||
* href="http://tools.ietf.org/html/draft-lozano-tmch-smd">Mark and Signed Mark Objects Mapping</a>.
|
||||
*
|
||||
* @see google.registry.model.contact.ContactAddress
|
||||
* @see google.registry.model.mark.MarkAddress
|
||||
|
||||
@@ -29,8 +29,9 @@ import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
/**
|
||||
* Container for generic E164 phone number.
|
||||
*
|
||||
* <p>This is the "e164" type from {@link "http://tools.ietf.org/html/rfc5733"}. It also matches the
|
||||
* "e164Type" type from {@link "http://tools.ietf.org/html/draft-lozano-tmch-smd"}.
|
||||
* <p>This is the "e164" type from <a href="http://tools.ietf.org/html/rfc5733">RFC5733</a>. It also
|
||||
* matches the "e164Type" type from <a href="http://tools.ietf.org/html/draft-lozano-tmch-smd">Mark
|
||||
* and Signed Mark Objects Mapping</a>
|
||||
*
|
||||
* <blockquote>
|
||||
*
|
||||
|
||||
@@ -24,7 +24,7 @@ import javax.xml.bind.annotation.XmlElementWrapper;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* A greeting, defined in {@link "http://tools.ietf.org/html/rfc5730"}.
|
||||
* A greeting, defined in <a href="http://tools.ietf.org/html/rfc5730">RFC5730</a>.
|
||||
*
|
||||
* <p>It would be nice to make this a singleton, but we need the {@link #svDate} field to stay
|
||||
* current.
|
||||
|
||||
@@ -32,7 +32,7 @@ import javax.xml.bind.annotation.XmlType;
|
||||
/** A collection of {@link HostResource} commands. */
|
||||
public class HostCommand {
|
||||
|
||||
/** The fields on "chgType" from {@link "http://tools.ietf.org/html/rfc5732"}. */
|
||||
/** The fields on "chgType" from <a href="http://tools.ietf.org/html/rfc5732">RFC5732</a>. */
|
||||
@XmlTransient
|
||||
abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
|
||||
implements ResourceCreateOrChange<HostResource.Builder> {
|
||||
@@ -42,13 +42,13 @@ public class HostCommand {
|
||||
}
|
||||
|
||||
/**
|
||||
* A create command for a {@link HostResource}, mapping "createType" from
|
||||
* {@link "http://tools.ietf.org/html/rfc5732"}.
|
||||
* A create command for a {@link HostResource}, mapping "createType" from <a
|
||||
* href="http://tools.ietf.org/html/rfc5732">RFC5732</a>.
|
||||
*/
|
||||
@XmlType(propOrder = {"targetId", "inetAddresses" })
|
||||
@XmlType(propOrder = {"targetId", "inetAddresses"})
|
||||
@XmlRootElement
|
||||
public static class Create
|
||||
extends HostCreateOrChange implements ResourceCreateOrChange<HostResource.Builder> {
|
||||
public static class Create extends HostCreateOrChange
|
||||
implements ResourceCreateOrChange<HostResource.Builder> {
|
||||
/** IP Addresses for this host. Can be null if this is an external host. */
|
||||
@XmlElement(name = "addr")
|
||||
Set<InetAddress> inetAddresses;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package google.registry.model.ofy;
|
||||
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
@@ -94,42 +95,45 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
|
||||
@Override
|
||||
public void saveNew(Object entity) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
getOfy().save().entity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveAllNew(ImmutableCollection<?> entities) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
getOfy().save().entities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewOrUpdate(Object entity) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
getOfy().save().entity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNewOrUpdateAll(ImmutableCollection<?> entities) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
getOfy().save().entities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(Object entity) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
checkArgumentNotNull(entity, "entity must be specified");
|
||||
getOfy().save().entity(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAll(ImmutableCollection<?> entities) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
getOfy().save().entities(entities);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkExists(Object entity) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
return getOfy().load().key(Key.create(entity)).now() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> boolean checkExists(VKey<T> key) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
return getOfy().load().key(key.getOfyKey()).now() != null;
|
||||
}
|
||||
|
||||
// TODO: add tests for these methods. They currently have some degree of test coverage because
|
||||
@@ -138,7 +142,7 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
// interface tests that are applied to both the datastore and SQL implementations.
|
||||
@Override
|
||||
public <T> Optional<T> maybeLoad(VKey<T> key) {
|
||||
return Optional.of(getOfy().load().key(key.getOfyKey()).now());
|
||||
return Optional.ofNullable(getOfy().load().key(key.getOfyKey()).now());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -161,16 +165,12 @@ public class DatastoreTransactionManager implements TransactionManager {
|
||||
|
||||
@Override
|
||||
public <T> ImmutableList<T> loadAll(Class<T> clazz) {
|
||||
// We can do a ofy().load().type(clazz), but this doesn't work in a transaction.
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> int delete(VKey<T> key) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void assertDelete(VKey<T> key) {
|
||||
throw new UnsupportedOperationException("Not available in the Datastore transaction manager");
|
||||
public <T> void delete(VKey<T> key) {
|
||||
getOfy().delete().key(key.getOfyKey()).now();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,9 +34,9 @@ import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import com.googlecode.objectify.impl.translate.TranslatorFactory;
|
||||
import com.googlecode.objectify.impl.translate.opt.joda.MoneyStringTranslatorFactory;
|
||||
import google.registry.config.RegistryEnvironment;
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.EntityClasses;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.model.host.HostResource;
|
||||
import google.registry.model.translators.BloomFilterOfStringTranslatorFactory;
|
||||
import google.registry.model.translators.CidrAddressBlockTranslatorFactory;
|
||||
import google.registry.model.translators.CommitLogRevisionsTranslatorFactory;
|
||||
@@ -130,7 +130,7 @@ public class ObjectifyService {
|
||||
new InetAddressTranslatorFactory(),
|
||||
new MoneyStringTranslatorFactory(),
|
||||
new ReadableInstantUtcTranslatorFactory(),
|
||||
new VKeyTranslatorFactory<HostResource>(HostResource.class),
|
||||
new VKeyTranslatorFactory(),
|
||||
new UpdateAutoTimestampTranslatorFactory())) {
|
||||
factory().getTranslators().add(translatorFactory);
|
||||
}
|
||||
@@ -168,10 +168,16 @@ public class ObjectifyService {
|
||||
}
|
||||
com.googlecode.objectify.ObjectifyService.register(clazz);
|
||||
// Autogenerated ids make the commit log code very difficult since we won't always be able
|
||||
// to create a key for an entity immediately when requesting a save. Disallow that here.
|
||||
checkState(
|
||||
!factory().getMetadata(clazz).getKeyMetadata().isIdGeneratable(),
|
||||
"Can't register %s: Autogenerated ids (@Id on a Long) are not supported.", kind);
|
||||
// to create a key for an entity immediately when requesting a save. So, we require such
|
||||
// entities to implement google.registry.model.Buildable as its build() function allocates the
|
||||
// id to the entity.
|
||||
if (factory().getMetadata(clazz).getKeyMetadata().isIdGeneratable()) {
|
||||
checkState(
|
||||
Buildable.class.isAssignableFrom(clazz),
|
||||
"Can't register %s: Entity with autogenerated ids (@Id on a Long) must implement"
|
||||
+ " google.registry.model.Buildable.",
|
||||
kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ import org.joda.time.DateTime;
|
||||
* <p>Poll messages are identified externally by registrars using the format defined in {@link
|
||||
* PollMessageExternalKeyConverter}.
|
||||
*
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5730#section-2.9.2.3">RFC5730 - EPP - <poll>
|
||||
* @see <a href="https://tools.ietf.org/html/rfc5730#section-2.9.2.3">RFC5730 - EPP - <poll>
|
||||
* Command</a>
|
||||
*/
|
||||
@Entity
|
||||
|
||||
@@ -158,8 +158,8 @@ public final class ReservedList
|
||||
/**
|
||||
* Gets a ReservedList by name using the caching layer.
|
||||
*
|
||||
* @return An Optional<ReservedList> that has a value if a reserved list exists by the given name,
|
||||
* or absent if not.
|
||||
* @return An Optional<ReservedList> that has a value if a reserved list exists by the given
|
||||
* name, or absent if not.
|
||||
* @throws UncheckedExecutionException if some other error occurs while trying to load the
|
||||
* ReservedList from the cache or Datastore.
|
||||
*/
|
||||
|
||||
@@ -22,8 +22,8 @@ public final class IcannReportingTypes {
|
||||
* Represents the set of possible ICANN Monthly Registry Functions Activity Report fields.
|
||||
*
|
||||
* <p>Refer to the <a
|
||||
* href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm#_DV_M278>ICANN
|
||||
* registry agreement Specification 3 Section 2</a> for details.
|
||||
* href="https://newgtlds.icann.org/sites/default/files/agreements/agreement-approved-09jan14-en.htm#_DV_M278">
|
||||
* ICANN registry agreement Specification 3 Section 2</a> for details.
|
||||
*/
|
||||
public enum ActivityReportField {
|
||||
DOMAIN_CHECK("srs-dom-check"),
|
||||
|
||||
@@ -14,13 +14,14 @@
|
||||
|
||||
package google.registry.model.translators;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static com.google.common.base.Functions.identity;
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
import static google.registry.model.EntityClasses.ALL_CLASSES;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.google.appengine.api.datastore.Key;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
|
||||
/**
|
||||
* Translator factory for VKey.
|
||||
@@ -28,57 +29,39 @@ import java.net.URLEncoder;
|
||||
* <p>These get translated to a string containing the URL safe encoding of the objectify key
|
||||
* followed by a (url-unsafe) ampersand delimiter and the SQL key.
|
||||
*/
|
||||
public class VKeyTranslatorFactory<T> extends AbstractSimpleTranslatorFactory<VKey, String> {
|
||||
private final Class<T> refClass;
|
||||
public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey, Key> {
|
||||
|
||||
public VKeyTranslatorFactory(Class<T> refClass) {
|
||||
// Class registry allowing us to restore the original class object from the unqualified class
|
||||
// name, which is all the datastore key gives us.
|
||||
// Note that entities annotated with @EntitySubclass are removed because they share the same
|
||||
// kind of the key with their parent class.
|
||||
private static final ImmutableMap<String, Class> CLASS_REGISTRY =
|
||||
ALL_CLASSES.stream()
|
||||
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
|
||||
.collect(toImmutableMap(com.googlecode.objectify.Key::getKind, identity()));
|
||||
;
|
||||
|
||||
public VKeyTranslatorFactory() {
|
||||
super(VKey.class);
|
||||
this.refClass = refClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleTranslator<VKey, String> createTranslator() {
|
||||
return new SimpleTranslator<VKey, String>() {
|
||||
public SimpleTranslator<VKey, Key> createTranslator() {
|
||||
return new SimpleTranslator<VKey, Key>() {
|
||||
@Override
|
||||
public VKey loadValue(String datastoreValue) {
|
||||
int pos = datastoreValue.indexOf('&');
|
||||
Key ofyKey = null;
|
||||
String sqlKey = null;
|
||||
if (pos > 0) {
|
||||
// We have an objectify key.
|
||||
ofyKey = Key.create(datastoreValue.substring(0, pos));
|
||||
}
|
||||
|
||||
if (pos < datastoreValue.length() - 1) {
|
||||
// We have an SQL key.
|
||||
sqlKey = decode(datastoreValue.substring(pos + 1));
|
||||
}
|
||||
|
||||
return VKey.create(refClass, sqlKey, ofyKey);
|
||||
public VKey loadValue(Key datastoreValue) {
|
||||
// TODO(mmuller): we need to call a method on refClass to also reconstitute the SQL key.
|
||||
return datastoreValue == null
|
||||
? null
|
||||
: VKey.createOfy(
|
||||
CLASS_REGISTRY.get(datastoreValue.getKind()),
|
||||
com.googlecode.objectify.Key.create(datastoreValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String saveValue(VKey key) {
|
||||
return ((key.getOfyKey() == null) ? "" : key.getOfyKey().getString())
|
||||
+ "&"
|
||||
+ ((key.getSqlKey() == null) ? "" : encode(key.getSqlKey().toString()));
|
||||
public Key saveValue(VKey key) {
|
||||
return key == null ? null : key.getOfyKey().getRaw();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static String encode(String val) {
|
||||
try {
|
||||
return URLEncoder.encode(val, UTF_8.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String decode(String encoded) {
|
||||
try {
|
||||
return URLDecoder.decode(encoded, UTF_8.toString());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// 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.converter;
|
||||
|
||||
import avro.shaded.com.google.common.collect.Maps;
|
||||
import google.registry.model.registry.Registry.BillingCostTransition;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Converter;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* JPA converter for storing/retrieving {@code TimedTransitionProperty<Money,BillingCostTransition>}
|
||||
* objects.
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class BillingCostTransitionConverter
|
||||
extends TimedTransitionPropertyConverterBase<Money, BillingCostTransition> {
|
||||
|
||||
@Override
|
||||
Map.Entry<String, String> convertToDatabaseMapEntry(
|
||||
Map.Entry<DateTime, BillingCostTransition> entry) {
|
||||
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
Map.Entry<DateTime, Money> convertToEntityMapEntry(Map.Entry<String, String> entry) {
|
||||
return Maps.immutableEntry(DateTime.parse(entry.getKey()), Money.parse(entry.getValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
Class<BillingCostTransition> getTimedTransitionSubclass() {
|
||||
return BillingCostTransition.class;
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// 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.converter;
|
||||
|
||||
import google.registry.model.billing.BillingEvent.Flag;
|
||||
import java.util.Set;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@link Set}. */
|
||||
@Converter(autoApply = true)
|
||||
public class BillingEventFlagSetConverter extends StringSetConverterBase<Flag> {
|
||||
|
||||
@Override
|
||||
String toString(Flag element) {
|
||||
return element.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
Flag fromString(String value) {
|
||||
return Flag.valueOf(value);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -15,12 +15,11 @@
|
||||
package google.registry.persistence.converter;
|
||||
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import java.util.List;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/**
|
||||
* JPA {@link AttributeConverter} for storing/retrieving {@link List<CidrAddressBlock>} objects.
|
||||
* JPA {@link AttributeConverter} for storing/retrieving {@code List<CidrAddressBlock>} objects.
|
||||
* TODO(shicong): Investigate if we can have one converter for any List type
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import java.util.Map;
|
||||
import javax.persistence.Converter;
|
||||
import org.joda.money.CurrencyUnit;
|
||||
|
||||
/** JPA converter for storing/retrieving {@link Map <CurrencyUnit, BillingAccountEntry>} objects. */
|
||||
/** JPA converter for storing/retrieving {@code Map<CurrencyUnit, BillingAccountEntry>} objects. */
|
||||
@Converter(autoApply = true)
|
||||
public class CurrencyToBillingConverter
|
||||
extends StringMapConverterBase<CurrencyUnit, BillingAccountEntry> {
|
||||
|
||||
+1
-2
@@ -16,11 +16,10 @@ package google.registry.persistence.converter;
|
||||
|
||||
import google.registry.model.contact.Disclose.PostalInfoChoice;
|
||||
import google.registry.model.contact.PostalInfo;
|
||||
import java.util.List;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@link List < PostalInfoChoice >}. */
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@code List<PostalInfoChoice>}. */
|
||||
@Converter(autoApply = true)
|
||||
public class PostalInfoChoiceListConverter extends StringListConverterBase<PostalInfoChoice> {
|
||||
|
||||
|
||||
+1
-2
@@ -15,11 +15,10 @@
|
||||
package google.registry.persistence.converter;
|
||||
|
||||
import google.registry.model.registrar.RegistrarContact.Type;
|
||||
import java.util.Set;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@link Set<Type>}. */
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@code Set<Type>}. */
|
||||
@Converter(autoApply = true)
|
||||
public class RegistrarPocSetConverter extends StringSetConverterBase<Type> {
|
||||
@Override
|
||||
|
||||
+1
-2
@@ -15,11 +15,10 @@
|
||||
package google.registry.persistence.converter;
|
||||
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import java.util.Set;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@link Set<StatusValue>}. */
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@code Set<StatusValue>}. */
|
||||
@Converter(autoApply = true)
|
||||
public class StatusValueSetConverter extends StringSetConverterBase<StatusValue> {
|
||||
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
||||
/**
|
||||
* The {@link JavaTypeDescriptor} and {@link SqlTypeDescriptor} for {@link StringCollection}.
|
||||
*
|
||||
* <p>A {@link StringCollection} object is a simple wrapper for a {@link Collection<String>} which
|
||||
* <p>A {@link StringCollection} object is a simple wrapper for a {@code Collection<String>} which
|
||||
* can be stored as a string array in the database. The {@link JavaTypeDescriptor} and {@link
|
||||
* SqlTypeDescriptor} is used by JPA/Hibernate to map between the collection and {@link Array} which
|
||||
* is the actual type that JDBC uses to read from and write to the database.
|
||||
|
||||
@@ -14,11 +14,10 @@
|
||||
|
||||
package google.registry.persistence.converter;
|
||||
|
||||
import java.util.List;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@link List<String>}. */
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@code List<String>}. */
|
||||
@Converter(autoApply = true)
|
||||
public class StringListConverter extends StringListConverterBase<String> {
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
||||
/**
|
||||
* The {@link JavaTypeDescriptor} and {@link SqlTypeDescriptor} for {@link StringMap}.
|
||||
*
|
||||
* <p>A {@link StringMap} object is a simple wrapper for a {@link Map <String, String>} which can be
|
||||
* <p>A {@link StringMap} object is a simple wrapper for a {@code Map<String, String>} which can be
|
||||
* stored in a column with data type of hstore in the database. The {@link JavaTypeDescriptor} and
|
||||
* {@link SqlTypeDescriptor} is used by JPA/Hibernate to map between the map and hstore which is the
|
||||
* actual type that JDBC uses to read from and write to the database.
|
||||
@@ -156,7 +156,7 @@ public class StringMapDescriptor extends AbstractTypeDescriptor<StringMap>
|
||||
};
|
||||
}
|
||||
|
||||
/** A simple wrapper class for {@link Map<String, String>}. */
|
||||
/** A simple wrapper class for {@code Map<String, String>}. */
|
||||
public static class StringMap {
|
||||
private Map<String, String> map;
|
||||
|
||||
@@ -169,7 +169,7 @@ public class StringMapDescriptor extends AbstractTypeDescriptor<StringMap>
|
||||
return new StringMap(ImmutableMap.copyOf(map));
|
||||
}
|
||||
|
||||
/** Returns the underlying {@link Map<String, String>} object. */
|
||||
/** Returns the underlying {@code Map<String, String>} object. */
|
||||
public Map<String, String> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -14,11 +14,10 @@
|
||||
|
||||
package google.registry.persistence.converter;
|
||||
|
||||
import java.util.Set;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@link Set<String>}. */
|
||||
/** JPA {@link AttributeConverter} for storing/retrieving {@code Set<String>}. */
|
||||
@Converter(autoApply = true)
|
||||
public class StringSetConverter extends StringSetConverterBase<String> {
|
||||
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
// 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.converter;
|
||||
|
||||
import static com.google.common.collect.ImmutableMap.toImmutableMap;
|
||||
|
||||
import com.google.common.collect.ImmutableSortedMap;
|
||||
import google.registry.model.common.TimedTransitionProperty;
|
||||
import google.registry.model.common.TimedTransitionProperty.TimedTransition;
|
||||
import google.registry.persistence.converter.StringMapDescriptor.StringMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* Base JPA converter for {@link TimedTransitionProperty} objects that are stored in a column with
|
||||
* data type of hstore in the database.
|
||||
*/
|
||||
public abstract class TimedTransitionPropertyConverterBase<K, V extends TimedTransition<K>>
|
||||
implements AttributeConverter<TimedTransitionProperty<K, V>, StringMap> {
|
||||
|
||||
abstract Map.Entry<String, String> convertToDatabaseMapEntry(Map.Entry<DateTime, V> entry);
|
||||
|
||||
abstract Map.Entry<DateTime, K> convertToEntityMapEntry(Map.Entry<String, String> entry);
|
||||
|
||||
abstract Class<V> getTimedTransitionSubclass();
|
||||
|
||||
@Override
|
||||
public StringMap convertToDatabaseColumn(@Nullable TimedTransitionProperty<K, V> attribute) {
|
||||
return attribute == null
|
||||
? null
|
||||
: StringMap.create(
|
||||
attribute.entrySet().stream()
|
||||
.map(this::convertToDatabaseMapEntry)
|
||||
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TimedTransitionProperty<K, V> convertToEntityAttribute(@Nullable StringMap dbData) {
|
||||
if (dbData == null) {
|
||||
return null;
|
||||
}
|
||||
Map<DateTime, K> map =
|
||||
dbData.getMap().entrySet().stream()
|
||||
.map(this::convertToEntityMapEntry)
|
||||
.collect(toImmutableMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
return TimedTransitionProperty.fromValueMap(
|
||||
ImmutableSortedMap.copyOf(map), getTimedTransitionSubclass());
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// 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.converter;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import google.registry.model.registry.Registry.TldState;
|
||||
import google.registry.model.registry.Registry.TldStateTransition;
|
||||
import java.util.Map;
|
||||
import javax.persistence.Converter;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* JPA converter for storing/retrieving {@code TimedTransitionProperty<TldState,
|
||||
* TldStateTransition>} objects.
|
||||
*/
|
||||
@Converter(autoApply = true)
|
||||
public class TldStateTransitionConverter
|
||||
extends TimedTransitionPropertyConverterBase<TldState, TldStateTransition> {
|
||||
|
||||
@Override
|
||||
Map.Entry<String, String> convertToDatabaseMapEntry(
|
||||
Map.Entry<DateTime, TldStateTransition> entry) {
|
||||
return Maps.immutableEntry(entry.getKey().toString(), entry.getValue().getValue().name());
|
||||
}
|
||||
|
||||
@Override
|
||||
Map.Entry<DateTime, TldState> convertToEntityMapEntry(Map.Entry<String, String> entry) {
|
||||
return Maps.immutableEntry(DateTime.parse(entry.getKey()), TldState.valueOf(entry.getValue()));
|
||||
}
|
||||
|
||||
@Override
|
||||
Class<TldStateTransition> getTimedTransitionSubclass() {
|
||||
return TldStateTransition.class;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package google.registry.persistence.transaction;
|
||||
|
||||
import google.registry.persistence.VKey;
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
/** Sub-interface of {@link TransactionManager} which defines JPA related methods. */
|
||||
@@ -21,4 +22,7 @@ public interface JpaTransactionManager extends TransactionManager {
|
||||
|
||||
/** Returns the {@link EntityManager} for the current request. */
|
||||
EntityManager getEntityManager();
|
||||
|
||||
/** Deletes the entity by its id, throws exception if the entity is not deleted. */
|
||||
public abstract <T> void assertDelete(VKey<T> key);
|
||||
}
|
||||
|
||||
+8
-4
@@ -77,7 +77,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
@Override
|
||||
public void assertInTransaction() {
|
||||
if (!inTransaction()) {
|
||||
throw new PersistenceException("Not in a transaction");
|
||||
throw new IllegalStateException("Not in a transaction");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,8 +278,7 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
.getResultList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> int delete(VKey<T> key) {
|
||||
private <T> int internalDelete(VKey<T> key) {
|
||||
checkArgumentNotNull(key, "key must be specified");
|
||||
assertInTransaction();
|
||||
EntityType<?> entityType = getEntityType(key.getKind());
|
||||
@@ -291,9 +290,14 @@ public class JpaTransactionManagerImpl implements JpaTransactionManager {
|
||||
return query.executeUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void delete(VKey<T> key) {
|
||||
internalDelete(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> void assertDelete(VKey<T> key) {
|
||||
if (delete(key) != 1) {
|
||||
if (internalDelete(key) != 1) {
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Error deleting the entity of the key: %s", key.getSqlKey()));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.persistence.transaction;
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import org.joda.time.DateTime;
|
||||
@@ -123,9 +124,6 @@ public interface TransactionManager {
|
||||
/** Loads all entities of the given type, returns empty if there is no such entity. */
|
||||
<T> ImmutableList<T> loadAll(Class<T> clazz);
|
||||
|
||||
/** Deletes the entity by its id, returns the number of deleted entity. */
|
||||
<T> int delete(VKey<T> key);
|
||||
|
||||
/** Deletes the entity by its id, throws exception if the entity is not deleted. */
|
||||
<T> void assertDelete(VKey<T> key);
|
||||
/** Deletes the entity by its id. */
|
||||
<T> void delete(VKey<T> key);
|
||||
}
|
||||
|
||||
+18
-6
@@ -16,6 +16,7 @@ package google.registry.persistence.transaction;
|
||||
|
||||
import com.google.appengine.api.utils.SystemProperty;
|
||||
import com.google.appengine.api.utils.SystemProperty.Environment.Value;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Suppliers;
|
||||
import google.registry.model.ofy.DatastoreTransactionManager;
|
||||
import google.registry.persistence.DaggerPersistenceComponent;
|
||||
@@ -26,7 +27,9 @@ import java.util.function.Supplier;
|
||||
// TODO: Rename this to PersistenceFactory and move to persistence package.
|
||||
public class TransactionManagerFactory {
|
||||
|
||||
private static final TransactionManager TM = createTransactionManager();
|
||||
private static final DatastoreTransactionManager ofyTm = createTransactionManager();
|
||||
|
||||
@NonFinalForTesting private static TransactionManager tm = ofyTm;
|
||||
|
||||
/** Supplier for jpaTm so that it is initialized only once, upon first usage. */
|
||||
@NonFinalForTesting
|
||||
@@ -45,10 +48,7 @@ public class TransactionManagerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private static TransactionManager createTransactionManager() {
|
||||
// TODO: Determine how to provision TransactionManager after the dual-write. During the
|
||||
// dual-write transitional phase, we need the TransactionManager for both Datastore and Cloud
|
||||
// SQL, and this method returns the one for Datastore.
|
||||
private static DatastoreTransactionManager createTransactionManager() {
|
||||
return new DatastoreTransactionManager(null);
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class TransactionManagerFactory {
|
||||
|
||||
/** Returns {@link TransactionManager} instance. */
|
||||
public static TransactionManager tm() {
|
||||
return TM;
|
||||
return tm;
|
||||
}
|
||||
|
||||
/** Returns {@link JpaTransactionManager} instance. */
|
||||
@@ -75,8 +75,20 @@ public class TransactionManagerFactory {
|
||||
return jpaTm.get();
|
||||
}
|
||||
|
||||
/** Returns {@link DatastoreTransactionManager} instance. */
|
||||
@VisibleForTesting
|
||||
public static DatastoreTransactionManager ofyTm() {
|
||||
return ofyTm;
|
||||
}
|
||||
|
||||
/** Sets the return of {@link #jpaTm()} to the given instance of {@link JpaTransactionManager}. */
|
||||
public static void setJpaTm(JpaTransactionManager newJpaTm) {
|
||||
jpaTm = Suppliers.ofInstance(newJpaTm);
|
||||
}
|
||||
|
||||
/** Sets the return of {@link #tm()} to the given instance of {@link TransactionManager}. */
|
||||
@VisibleForTesting
|
||||
public static void setTm(TransactionManager newTm) {
|
||||
tm = newTm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,9 @@ import javax.inject.Inject;
|
||||
* (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7483">RFC 7483: JSON Responses for the Registration
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*
|
||||
* TODO(guyben):This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
* deleted, at least until it's actually required.
|
||||
*/
|
||||
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
// deleted, at least until it's actually required.
|
||||
@Action(
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/domains",
|
||||
|
||||
@@ -72,10 +72,9 @@ import javax.inject.Inject;
|
||||
* (RDAP) Query Format</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc7483">RFC 7483: JSON Responses for the Registration
|
||||
* Data Access Protocol (RDAP)</a>
|
||||
*
|
||||
* TODO(guyben):This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
* deleted, at least until it's actually required.
|
||||
*/
|
||||
// TODO: This isn't required by the RDAP Technical Implementation Guide, and hence should be
|
||||
// deleted, at least until it's actually required.
|
||||
@Action(
|
||||
service = Action.Service.PUBAPI,
|
||||
path = "/rdap/entities",
|
||||
|
||||
@@ -52,6 +52,7 @@ import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarAddress;
|
||||
import google.registry.model.registrar.RegistrarContact;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.rdap.RdapDataStructures.Event;
|
||||
import google.registry.rdap.RdapDataStructures.EventAction;
|
||||
import google.registry.rdap.RdapDataStructures.Link;
|
||||
@@ -346,7 +347,12 @@ public class RdapJsonFormatter {
|
||||
ImmutableSet.copyOf(tm().load(domainBase.getNameservers()));
|
||||
// Load the registrant and other contacts and add them to the data.
|
||||
Map<Key<ContactResource>, ContactResource> loadedContacts =
|
||||
ofy().load().keys(domainBase.getReferencedContacts());
|
||||
ofy()
|
||||
.load()
|
||||
.keys(
|
||||
domainBase.getReferencedContacts().stream()
|
||||
.map(VKey::getOfyKey)
|
||||
.collect(toImmutableSet()));
|
||||
// RDAP Response Profile 2.7.3, A domain MUST have the REGISTRANT, ADMIN, TECH roles and MAY
|
||||
// have others. We also add the BILLING.
|
||||
//
|
||||
@@ -361,7 +367,7 @@ public class RdapJsonFormatter {
|
||||
.sorted(DESIGNATED_CONTACT_ORDERING)
|
||||
.collect(
|
||||
toImmutableSetMultimap(
|
||||
DesignatedContact::getContactKey, DesignatedContact::getType));
|
||||
contact -> contact.getContactKey().getOfyKey(), DesignatedContact::getType));
|
||||
|
||||
for (Key<ContactResource> contactKey : contactsToRoles.keySet()) {
|
||||
Set<RdapEntity.Role> roles =
|
||||
|
||||
@@ -221,10 +221,9 @@ public abstract class RdapSearchActionBase extends RdapActionBase {
|
||||
*
|
||||
* <p>This version handles a list of parameter values, all associated with the same name.
|
||||
*
|
||||
* <p>Example: If the original parameters were "a=w&a=x&b=y&c=z", and this method is called with
|
||||
* parameterName = "b" and parameterValues of "p" and "q", the result will be
|
||||
* "a=w&a=x&c=z&b=p&b=q". The new values of parameter "b" replace the old ones.
|
||||
*
|
||||
* <p>Example: If the original parameters were "a=w&a=x&b=y&c=z", and this method is
|
||||
* called with parameterName = "b" and parameterValues of "p" and "q", the result will be
|
||||
* "a=w&a=x&c=z&b=p&b=q". The new values of parameter "b" replace the old ones.
|
||||
*/
|
||||
protected String getRequestUrlWithExtraParameter(
|
||||
String parameterName, List<String> parameterValues) {
|
||||
|
||||
@@ -15,13 +15,12 @@
|
||||
package google.registry.rde;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
|
||||
import com.google.common.base.Ascii;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
import google.registry.model.domain.DesignatedContact;
|
||||
import google.registry.model.domain.DomainBase;
|
||||
@@ -30,6 +29,7 @@ import google.registry.model.domain.secdns.DelegationSignerData;
|
||||
import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.rde.RdeMode;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.util.Idn;
|
||||
import google.registry.xjc.domain.XjcDomainContactAttrType;
|
||||
import google.registry.xjc.domain.XjcDomainContactType;
|
||||
@@ -167,11 +167,11 @@ final class DomainBaseToXjcConverter {
|
||||
// o An OPTIONAL <registrant> element that contain the identifier for
|
||||
// the human or organizational social information object associated
|
||||
// as the holder of the domain name object.
|
||||
Key<ContactResource> registrant = model.getRegistrant();
|
||||
VKey<ContactResource> registrant = model.getRegistrant();
|
||||
if (registrant == null) {
|
||||
logger.atWarning().log("Domain %s has no registrant contact.", domainName);
|
||||
} else {
|
||||
ContactResource registrantContact = ofy().load().key(registrant).now();
|
||||
ContactResource registrantContact = tm().load(registrant);
|
||||
checkState(
|
||||
registrantContact != null,
|
||||
"Registrant contact %s on domain %s does not exist",
|
||||
@@ -304,7 +304,7 @@ final class DomainBaseToXjcConverter {
|
||||
"Contact key for type %s is null on domain %s",
|
||||
model.getType(),
|
||||
domainName);
|
||||
ContactResource contact = ofy().load().key(model.getContactKey()).now();
|
||||
ContactResource contact = tm().load(model.getContactKey());
|
||||
checkState(
|
||||
contact != null,
|
||||
"Contact %s on domain %s does not exist",
|
||||
|
||||
@@ -120,9 +120,6 @@ public final class Ghostryde {
|
||||
|
||||
/**
|
||||
* Creates a ghostryde file from an in-memory byte array.
|
||||
*
|
||||
* @throws PGPException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static byte[] encode(byte[] data, PGPPublicKey key)
|
||||
throws IOException, PGPException {
|
||||
@@ -137,9 +134,6 @@ public final class Ghostryde {
|
||||
|
||||
/**
|
||||
* Deciphers a ghostryde file from an in-memory byte array.
|
||||
*
|
||||
* @throws PGPException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static byte[] decode(byte[] data, PGPPrivateKey key)
|
||||
throws IOException, PGPException {
|
||||
|
||||
@@ -46,9 +46,6 @@ public final class RdeUtil {
|
||||
/**
|
||||
* Look at some bytes from {@code xmlInput} to ensure it appears to be a FULL XML deposit and
|
||||
* then use a regular expression to extract the watermark timestamp which is returned.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws XmlException
|
||||
*/
|
||||
public static DateTime peekWatermark(BufferedInputStream xmlInput)
|
||||
throws IOException, XmlException {
|
||||
|
||||
@@ -34,7 +34,7 @@ import javax.mail.internet.InternetAddress;
|
||||
import org.joda.time.YearMonth;
|
||||
|
||||
/** Utility functions for sending emails involving monthly invoices. */
|
||||
class BillingEmailUtils {
|
||||
public class BillingEmailUtils {
|
||||
|
||||
private final SendEmailService emailService;
|
||||
private final YearMonth yearMonth;
|
||||
|
||||
@@ -43,8 +43,8 @@ import org.joda.time.YearMonth;
|
||||
* PublishInvoicesAction} to publish the subsequent output.
|
||||
*
|
||||
* <p>This action runs the {@link google.registry.beam.invoicing.InvoicingPipeline} beam template,
|
||||
* staged at gs://<projectId>-beam/templates/invoicing. The pipeline then generates invoices for the
|
||||
* month and stores them on GCS.
|
||||
* staged at gs://<projectId>-beam/templates/invoicing. The pipeline then generates invoices
|
||||
* for the month and stores them on GCS.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
|
||||
@@ -53,7 +53,7 @@ import org.json.JSONException;
|
||||
* pipeline accordingly.
|
||||
*
|
||||
* <p>This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, SoyTemplateInfo, String,
|
||||
* Set)} on success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure.
|
||||
* ImmutableSet)} on success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure.
|
||||
*/
|
||||
@Action(
|
||||
service = Action.Service.BACKEND,
|
||||
|
||||
@@ -18,6 +18,7 @@ import static com.google.common.html.HtmlEscapers.htmlEscaper;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Level;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** Base for exceptions that cause an HTTP error response. */
|
||||
@@ -28,11 +29,18 @@ public abstract class HttpException extends RuntimeException {
|
||||
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
private final Level logLevel;
|
||||
|
||||
private final int responseCode;
|
||||
|
||||
protected HttpException(int responseCode, String message, Throwable cause) {
|
||||
protected HttpException(int responseCode, String message, Throwable cause, Level logLevel) {
|
||||
super(message, cause);
|
||||
this.responseCode = responseCode;
|
||||
this.logLevel = logLevel;
|
||||
}
|
||||
|
||||
protected HttpException(int responseCode, String message, Throwable cause) {
|
||||
this(responseCode, message, cause, Level.INFO);
|
||||
}
|
||||
|
||||
public final int getResponseCode() {
|
||||
@@ -57,7 +65,7 @@ public abstract class HttpException extends RuntimeException {
|
||||
*/
|
||||
public final void send(HttpServletResponse rsp) throws IOException {
|
||||
rsp.sendError(getResponseCode(), htmlEscaper().escape(getMessage()));
|
||||
logger.atInfo().withCause(getCause()).log("%s", this);
|
||||
logger.at(logLevel).withCause(getCause()).log("%s", this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,7 +204,7 @@ public abstract class HttpException extends RuntimeException {
|
||||
/** Exception that causes a 500 response. */
|
||||
public static final class InternalServerErrorException extends HttpException {
|
||||
public InternalServerErrorException(String message) {
|
||||
super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null);
|
||||
super(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, message, null, Level.SEVERE);
|
||||
}
|
||||
|
||||
public InternalServerErrorException(String message, Throwable cause) {
|
||||
|
||||
@@ -45,9 +45,9 @@ public final class RequestParameters {
|
||||
*
|
||||
* <ul>
|
||||
* <li>/foo?bar=hello → hello
|
||||
* <li>/foo?bar=hello&bar=there → hello
|
||||
* <li>/foo?bar=hello&bar=there → hello
|
||||
* <li>/foo?bar= → 400 error (empty)
|
||||
* <li>/foo?bar=&bar=there → 400 error (empty)
|
||||
* <li>/foo?bar=&bar=there → 400 error (empty)
|
||||
* <li>/foo → 400 error (absent)
|
||||
* </ul>
|
||||
*
|
||||
|
||||
@@ -59,7 +59,7 @@ public class CursorDao {
|
||||
.transact(() -> jpaTm().getEntityManager().find(Cursor.class, new CursorId(type, scope)));
|
||||
}
|
||||
|
||||
/** If no scope is given, use {@link Cursor.GLOBAL} as the scope. */
|
||||
/** If no scope is given, use {@link Cursor#GLOBAL} as the scope. */
|
||||
public static Cursor load(CursorType type) {
|
||||
checkNotNull(type, "The type of the cursor to load must be specified");
|
||||
return load(type, Cursor.GLOBAL);
|
||||
|
||||
@@ -214,13 +214,13 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
return lockCompletionTimestamp != null && unlockCompletionTimestamp == null;
|
||||
}
|
||||
|
||||
/** Returns true iff the lock was requested >= 1 hour ago and has not been verified. */
|
||||
/** Returns true iff the lock was requested >= 1 hour ago and has not been verified. */
|
||||
public boolean isLockRequestExpired(DateTime now) {
|
||||
return !getLockCompletionTimestamp().isPresent()
|
||||
&& isBeforeOrAt(getLockRequestTimestamp(), now.minusHours(1));
|
||||
}
|
||||
|
||||
/** Returns true iff the unlock was requested >= 1 hour ago and has not been verified. */
|
||||
/** Returns true iff the unlock was requested >= 1 hour ago and has not been verified. */
|
||||
public boolean isUnlockRequestExpired(DateTime now) {
|
||||
Optional<DateTime> unlockRequestTimestamp = getUnlockRequestTimestamp();
|
||||
return unlockRequestTimestamp.isPresent()
|
||||
|
||||
@@ -42,7 +42,7 @@ public class DriveConnection {
|
||||
/**
|
||||
* Creates a folder with the given parent.
|
||||
*
|
||||
* @returns the folder id.
|
||||
* @return the folder id.
|
||||
*/
|
||||
public String createFolder(String title, String parentFolderId) throws IOException {
|
||||
return drive.files()
|
||||
@@ -58,7 +58,7 @@ public class DriveConnection {
|
||||
* existing file is the desired behavior, use {@link #createOrUpdateFile(String, MediaType,
|
||||
* String, byte[])} instead.
|
||||
*
|
||||
* @returns the file id.
|
||||
* @return the file id.
|
||||
*/
|
||||
public String createFile(String title, MediaType mimeType, String parentFolderId, byte[] bytes)
|
||||
throws IOException {
|
||||
@@ -76,13 +76,10 @@ public class DriveConnection {
|
||||
*
|
||||
* @throws IllegalStateException if multiple files with that name exist in the given folder.
|
||||
* @throws IOException if communication with Google Drive fails for any reason.
|
||||
* @returns the file id.
|
||||
* @return the file id.
|
||||
*/
|
||||
public String createOrUpdateFile(
|
||||
String title,
|
||||
MediaType mimeType,
|
||||
String parentFolderId,
|
||||
byte[] bytes) throws IOException {
|
||||
String title, MediaType mimeType, String parentFolderId, byte[] bytes) throws IOException {
|
||||
List<String> existingFiles = listFiles(parentFolderId, String.format("title = '%s'", title));
|
||||
if (existingFiles.size() > 1) {
|
||||
throw new IllegalStateException(String.format(
|
||||
@@ -97,10 +94,10 @@ public class DriveConnection {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the file with the given id in place, setting the title, content, and mime type to
|
||||
* the newly specified values.
|
||||
* Updates the file with the given id in place, setting the title, content, and mime type to the
|
||||
* newly specified values.
|
||||
*
|
||||
* @returns the file id.
|
||||
* @return the file id.
|
||||
*/
|
||||
public String updateFile(String fileId, String title, MediaType mimeType, byte[] bytes)
|
||||
throws IOException {
|
||||
|
||||
@@ -77,10 +77,6 @@ public class TmchXmlSignature {
|
||||
*
|
||||
* @throws GeneralSecurityException for unsupported protocols, certs not signed by the TMCH,
|
||||
* incorrect keys, and for invalid, old, not-yet-valid or revoked certificates.
|
||||
* @throws IOException
|
||||
* @throws MarshalException
|
||||
* @throws ParserConfigurationException
|
||||
* @throws SAXException
|
||||
*/
|
||||
public void verify(byte[] smdXml)
|
||||
throws GeneralSecurityException, IOException, MarshalException, ParserConfigurationException,
|
||||
|
||||
@@ -39,18 +39,14 @@ class CompareDbBackups {
|
||||
return;
|
||||
}
|
||||
|
||||
ImmutableSet<ComparableEntity> entities1 =
|
||||
new RecordAccumulator()
|
||||
.readDirectory(new File(args[0]), DATA_FILE_MATCHER)
|
||||
.getComparableEntitySet();
|
||||
ImmutableSet<ComparableEntity> entities2 =
|
||||
new RecordAccumulator()
|
||||
.readDirectory(new File(args[1]), DATA_FILE_MATCHER)
|
||||
.getComparableEntitySet();
|
||||
ImmutableSet<EntityWrapper> entities1 =
|
||||
RecordAccumulator.readDirectory(new File(args[0]), DATA_FILE_MATCHER).getEntityWrapperSet();
|
||||
ImmutableSet<EntityWrapper> entities2 =
|
||||
RecordAccumulator.readDirectory(new File(args[1]), DATA_FILE_MATCHER).getEntityWrapperSet();
|
||||
|
||||
// Calculate the entities added and removed.
|
||||
SetView<ComparableEntity> added = Sets.difference(entities2, entities1);
|
||||
SetView<ComparableEntity> removed = Sets.difference(entities1, entities2);
|
||||
SetView<EntityWrapper> added = Sets.difference(entities2, entities1);
|
||||
SetView<EntityWrapper> removed = Sets.difference(entities1, entities2);
|
||||
|
||||
printHeader(
|
||||
String.format("First backup: %d records", entities1.size()),
|
||||
@@ -58,14 +54,14 @@ class CompareDbBackups {
|
||||
|
||||
if (!removed.isEmpty()) {
|
||||
printHeader(removed.size() + " records were removed:");
|
||||
for (ComparableEntity entity : removed) {
|
||||
for (EntityWrapper entity : removed) {
|
||||
System.out.println(entity);
|
||||
}
|
||||
}
|
||||
|
||||
if (!added.isEmpty()) {
|
||||
printHeader(added.size() + " records were added:");
|
||||
for (ComparableEntity entity : added) {
|
||||
for (EntityWrapper entity : added) {
|
||||
System.out.println(entity);
|
||||
}
|
||||
}
|
||||
|
||||
+38
-6
@@ -15,20 +15,32 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import com.google.appengine.api.datastore.Entity;
|
||||
import com.google.auto.value.AutoValue;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/** Wraps {@link Entity} to do hashCode/equals based on both the entity's key and its properties. */
|
||||
final class ComparableEntity {
|
||||
/**
|
||||
* Wraps {@link Entity} for ease of processing in collections.
|
||||
*
|
||||
* <p>Note that the {@link #hashCode}/{@link #equals} methods are based on both the entity's key and
|
||||
* its properties.
|
||||
*/
|
||||
final class EntityWrapper {
|
||||
private static final String TEST_ENTITY_KIND = "TestEntity";
|
||||
|
||||
private final Entity entity;
|
||||
|
||||
ComparableEntity(Entity entity) {
|
||||
EntityWrapper(Entity entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public Entity getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object that) {
|
||||
if (that instanceof ComparableEntity) {
|
||||
ComparableEntity thatEntity = (ComparableEntity) that;
|
||||
if (that instanceof EntityWrapper) {
|
||||
EntityWrapper thatEntity = (EntityWrapper) that;
|
||||
return entity.equals(thatEntity.entity)
|
||||
&& entity.getProperties().equals(thatEntity.entity.getProperties());
|
||||
}
|
||||
@@ -43,6 +55,26 @@ final class ComparableEntity {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ComparableEntity(" + entity + ")";
|
||||
return "EntityWrapper(" + entity + ")";
|
||||
}
|
||||
|
||||
public static EntityWrapper from(int id, Property... properties) {
|
||||
Entity entity = new Entity(TEST_ENTITY_KIND, id);
|
||||
for (Property prop : properties) {
|
||||
entity.setProperty(prop.name(), prop.value());
|
||||
}
|
||||
return new EntityWrapper(entity);
|
||||
}
|
||||
|
||||
@AutoValue
|
||||
abstract static class Property {
|
||||
|
||||
static Property create(String name, Object value) {
|
||||
return new AutoValue_EntityWrapper_Property(name, value);
|
||||
}
|
||||
|
||||
abstract String name();
|
||||
|
||||
abstract Object value();
|
||||
}
|
||||
}
|
||||
@@ -14,37 +14,101 @@
|
||||
|
||||
package google.registry.tools;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Reads records from a set of LevelDB files and builds a gigantic ImmutableList from them.
|
||||
* Iterator that incrementally parses binary data in LevelDb format into records.
|
||||
*
|
||||
* <p>See <a
|
||||
* href="https://github.com/google/leveldb/blob/master/doc/log_format.md">log_format.md</a> for the
|
||||
* leveldb log format specification.</a>
|
||||
* <p>The input source is automatically closed when all data have been read.
|
||||
*
|
||||
* <p>There are several other implementations of this, none of which appeared suitable for our use
|
||||
* case: <a href="https://github.com/google/leveldb">The original C++ implementation</a>. <a
|
||||
* href="https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/files/RecordWriteChannel">
|
||||
* com.google.appengine.api.files.RecordWriteChannel</a> - Exactly what we need but deprecated. The
|
||||
* href="https://cloud.google.com/appengine/docs/standard/java/javadoc/com/google/appengine/api/files/RecordReadChannel">
|
||||
* com.google.appengine.api.files.RecordReadChannel</a> - Exactly what we need but deprecated. The
|
||||
* referenced replacement: <a
|
||||
* href="https://github.com/GoogleCloudPlatform/appengine-gcs-client.git">The App Engine GCS
|
||||
* Client</a> - Does not appear to have any support for working with LevelDB.
|
||||
* Client</a> - Does not appear to have any support for working with LevelDB. *
|
||||
*
|
||||
* <p>See <a
|
||||
* href="https://github.com/google/leveldb/blob/master/doc/log_format.md">log_format.md</a>
|
||||
*/
|
||||
public final class LevelDbLogReader {
|
||||
public final class LevelDbLogReader implements Iterator<byte[]> {
|
||||
|
||||
@VisibleForTesting static final int BLOCK_SIZE = 32 * 1024;
|
||||
@VisibleForTesting static final int HEADER_SIZE = 7;
|
||||
|
||||
private final ByteArrayOutputStream recordContents = new ByteArrayOutputStream();
|
||||
private final ImmutableList.Builder<byte[]> recordListBuilder = new ImmutableList.Builder<>();
|
||||
private final LinkedList<byte[]> recordList = Lists.newLinkedList();
|
||||
|
||||
private final ByteBuffer byteBuffer = ByteBuffer.allocate(BLOCK_SIZE);
|
||||
private final ReadableByteChannel channel;
|
||||
|
||||
LevelDbLogReader(ReadableByteChannel channel) {
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while (recordList.isEmpty()) {
|
||||
try {
|
||||
Optional<byte[]> block = readFromChannel();
|
||||
if (!block.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
if (block.get().length != BLOCK_SIZE) {
|
||||
throw new IllegalStateException("Data size is not multiple of " + BLOCK_SIZE);
|
||||
}
|
||||
processBlock(block.get());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] next() {
|
||||
checkState(hasNext(), "The next() method called on empty iterator.");
|
||||
return recordList.removeFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next {@link #BLOCK_SIZE} bytes from the input channel, or {@link
|
||||
* Optional#empty()} if there is no more data.
|
||||
*/
|
||||
// TODO(weiminyu): use ByteBuffer directly.
|
||||
private Optional<byte[]> readFromChannel() throws IOException {
|
||||
while (channel.isOpen()) {
|
||||
int bytesRead = channel.read(byteBuffer);
|
||||
if (!byteBuffer.hasRemaining() || bytesRead < 0) {
|
||||
byteBuffer.flip();
|
||||
if (!byteBuffer.hasRemaining()) {
|
||||
channel.close();
|
||||
return Optional.empty();
|
||||
}
|
||||
byte[] result = new byte[byteBuffer.remaining()];
|
||||
byteBuffer.get(result);
|
||||
byteBuffer.clear();
|
||||
return Optional.of(result);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/** Read a complete block, which must be exactly 32 KB. */
|
||||
private void processBlock(byte[] block) {
|
||||
@@ -63,7 +127,7 @@ public final class LevelDbLogReader {
|
||||
|
||||
// If this is the last (or only) chunk in the record, store the full contents into the List.
|
||||
if (recordHeader.type == ChunkType.FULL || recordHeader.type == ChunkType.LAST) {
|
||||
recordListBuilder.add(recordContents.toByteArray());
|
||||
recordList.add(recordContents.toByteArray());
|
||||
recordContents.reset();
|
||||
}
|
||||
|
||||
@@ -96,40 +160,24 @@ public final class LevelDbLogReader {
|
||||
return new RecordHeader(checksum, size, ChunkType.fromCode(type));
|
||||
}
|
||||
|
||||
/** Reads all records in the Reader into the record set. */
|
||||
public void readFrom(InputStream source) throws IOException {
|
||||
byte[] block = new byte[BLOCK_SIZE];
|
||||
|
||||
// read until we have no more.
|
||||
while (true) {
|
||||
int amountRead = source.read(block, 0, BLOCK_SIZE);
|
||||
if (amountRead <= 0) {
|
||||
break;
|
||||
}
|
||||
assert amountRead == BLOCK_SIZE;
|
||||
|
||||
processBlock(block);
|
||||
}
|
||||
/** Returns a {@link LevelDbLogReader} over a {@link ReadableByteChannel}. */
|
||||
public static LevelDbLogReader from(ReadableByteChannel channel) {
|
||||
return new LevelDbLogReader(channel);
|
||||
}
|
||||
|
||||
/** Reads all records from the file specified by "path" into the record set. */
|
||||
public void readFrom(Path path) throws IOException {
|
||||
readFrom(Files.newInputStream(path));
|
||||
/** Returns a {@link LevelDbLogReader} over an {@link InputStream}. */
|
||||
public static LevelDbLogReader from(InputStream source) {
|
||||
return new LevelDbLogReader(Channels.newChannel(source));
|
||||
}
|
||||
|
||||
/** Reads all records from the specified file into the record set. */
|
||||
public void readFrom(String filename) throws IOException {
|
||||
readFrom(FileSystems.getDefault().getPath(filename));
|
||||
/** Returns a {@link LevelDbLogReader} over a file specified by {@link Path}. */
|
||||
public static LevelDbLogReader from(Path path) throws IOException {
|
||||
return from(Files.newInputStream(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of records constructed so far.
|
||||
*
|
||||
* <p>Note that this does not invalidate the internal state of the object: we return a copy and
|
||||
* this can be called multiple times.
|
||||
*/
|
||||
ImmutableList<byte[]> getRecords() {
|
||||
return recordListBuilder.build();
|
||||
/** Returns a {@link LevelDbLogReader} over a file specified by {@code filename}. */
|
||||
public static LevelDbLogReader from(String filename) throws IOException {
|
||||
return from(FileSystems.getDefault().getPath(filename));
|
||||
}
|
||||
|
||||
/** Aggregates the fields in a record header. */
|
||||
|
||||
@@ -15,42 +15,47 @@
|
||||
package google.registry.tools;
|
||||
|
||||
import com.google.appengine.api.datastore.EntityTranslator;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/** Accumulates Entity records from level db files under a directory hierarchy. */
|
||||
class RecordAccumulator {
|
||||
private final LevelDbLogReader reader = new LevelDbLogReader();
|
||||
private final ImmutableList<byte[]> records;
|
||||
|
||||
RecordAccumulator(ImmutableList<byte[]> records) {
|
||||
this.records = records;
|
||||
}
|
||||
|
||||
/** Recursively reads all records in the directory. */
|
||||
public final RecordAccumulator readDirectory(File dir, Predicate<File> fileMatcher) {
|
||||
public static RecordAccumulator readDirectory(File dir, Predicate<File> fileMatcher) {
|
||||
ImmutableList.Builder<byte[]> builder = new ImmutableList.Builder<>();
|
||||
for (File child : dir.listFiles()) {
|
||||
if (child.isDirectory()) {
|
||||
readDirectory(child, fileMatcher);
|
||||
builder.addAll(readDirectory(child, fileMatcher).records);
|
||||
} else if (fileMatcher.test(child)) {
|
||||
try {
|
||||
reader.readFrom(new FileInputStream(child));
|
||||
builder.addAll(LevelDbLogReader.from(child.getPath()));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("IOException reading from file: " + child, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
return new RecordAccumulator(builder.build());
|
||||
}
|
||||
|
||||
/** Creates an entity set from the current set of raw records. */
|
||||
ImmutableSet<ComparableEntity> getComparableEntitySet() {
|
||||
ImmutableSet.Builder<ComparableEntity> builder = new ImmutableSet.Builder<>();
|
||||
for (byte[] rawRecord : reader.getRecords()) {
|
||||
/** Creates an {@link EntityWrapper} set from the current set of raw records. */
|
||||
ImmutableSet<EntityWrapper> getEntityWrapperSet() {
|
||||
ImmutableSet.Builder<EntityWrapper> builder = new ImmutableSet.Builder<>();
|
||||
for (byte[] rawRecord : records) {
|
||||
// Parse the entity proto and create an Entity object from it.
|
||||
EntityProto proto = new EntityProto();
|
||||
proto.parseFrom(rawRecord);
|
||||
ComparableEntity entity = new ComparableEntity(EntityTranslator.createFromPb(proto));
|
||||
EntityWrapper entity = new EntityWrapper(EntityTranslator.createFromPb(proto));
|
||||
|
||||
builder.add(entity);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
|
||||
import static org.joda.time.DateTimeZone.UTC;
|
||||
|
||||
@@ -291,11 +291,9 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
|
||||
|
||||
ImmutableSet<String> getContactsOfType(
|
||||
DomainBase domainBase, final DesignatedContact.Type contactType) {
|
||||
return domainBase
|
||||
.getContacts()
|
||||
.stream()
|
||||
return domainBase.getContacts().stream()
|
||||
.filter(contact -> contact.getType().equals(contactType))
|
||||
.map(contact -> ofy().load().key(contact.getContactKey()).now().getContactId())
|
||||
.map(contact -> tm().load(contact.getContactKey()).getContactId())
|
||||
.collect(toImmutableSet());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,43 +44,45 @@ import javax.annotation.concurrent.Immutable;
|
||||
* Declarative functional fluent form field converter / validator.
|
||||
*
|
||||
* <p>This class is responsible for converting arbitrary data, sent to us by the web browser, into
|
||||
* validated data structures that the server-side code can use. For example:<pre>
|
||||
* validated data structures that the server-side code can use. For example:
|
||||
*
|
||||
* private enum Gender { MALE, FEMALE }
|
||||
* <pre>{@code
|
||||
* private enum Gender { MALE, FEMALE }
|
||||
*
|
||||
* private static final FormField<String, String> NAME_FIELD = FormField.named("name")
|
||||
* .matches("[a-z]+")
|
||||
* .range(atMost(16))
|
||||
* .required()
|
||||
* .build();
|
||||
* private static final FormField<String, String> NAME_FIELD = FormField.named("name")
|
||||
* .matches("[a-z]+")
|
||||
* .range(atMost(16))
|
||||
* .required()
|
||||
* .build();
|
||||
*
|
||||
* private static final FormField<String, Gender> GENDER_FIELD = FormField.named("gender")
|
||||
* .asEnum(Gender.class)
|
||||
* .required()
|
||||
* .build();
|
||||
* private static final FormField<String, Gender> GENDER_FIELD = FormField.named("gender")
|
||||
* .asEnum(Gender.class)
|
||||
* .required()
|
||||
* .build();
|
||||
*
|
||||
* public Person makePerson(Map<String, String> params) {
|
||||
* Person.Builder person = new Person.Builder();
|
||||
* for (String name : NAME_FIELD.extract(params).asSet()) {
|
||||
* person.setName(name);
|
||||
* }
|
||||
* for (Gender name : GENDER_FIELD.extract(params).asSet()) {
|
||||
* person.setGender(name);
|
||||
* }
|
||||
* return person.build();
|
||||
* }</pre>
|
||||
* public Person makePerson(Map<String, String> params) {
|
||||
* Person.Builder person = new Person.Builder();
|
||||
* for (String name : NAME_FIELD.extract(params).asSet()) {
|
||||
* person.setName(name);
|
||||
* }
|
||||
* for (Gender name : GENDER_FIELD.extract(params).asSet()) {
|
||||
* person.setGender(name);
|
||||
* }
|
||||
* return person.build();
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p>This class provides <b>full type-safety</b> <i>if and only if</i> you statically initialize
|
||||
* your FormField objects and write a unit test that causes the class to be loaded.
|
||||
*
|
||||
* <h3>Exception Handling</h3>
|
||||
*
|
||||
* <p>When values passed to {@link #convert} or {@link #extract} don't meet the contract,
|
||||
* {@link FormFieldException} will be thrown, which provides the field name and a short error
|
||||
* message that's safe to pass along to the client.
|
||||
* <p>When values passed to {@link #convert} or {@link #extract} don't meet the contract, {@link
|
||||
* FormFieldException} will be thrown, which provides the field name and a short error message
|
||||
* that's safe to pass along to the client.
|
||||
*
|
||||
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and
|
||||
* the field name will automatically be propagated into the exception object for you.
|
||||
* <p>You can safely throw {@code FormFieldException} from within your validator functions, and the
|
||||
* field name will automatically be propagated into the exception object for you.
|
||||
*
|
||||
* <p>In situations when you're validating lists or maps, you'll end up with a hierarchical field
|
||||
* naming structure. For example, if you were validating a list of maps, an error generated by the
|
||||
@@ -92,23 +94,25 @@ import javax.annotation.concurrent.Immutable;
|
||||
* <p>You should never assign a partially constructed {@code FormField.Builder} to a variable or
|
||||
* constant. Instead, you should use {@link #asBuilder()} or {@link #asBuilderNamed(String)}.
|
||||
*
|
||||
* <p>Here is an example of how you might go about defining library definitions:<pre>
|
||||
* <p>Here is an example of how you might go about defining library definitions:
|
||||
*
|
||||
* final class FormFields {
|
||||
* private static final FormField<String, String> COUNTRY_CODE =
|
||||
* FormField.named("countryCode")
|
||||
* .range(Range.singleton(2))
|
||||
* .uppercased()
|
||||
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
|
||||
* .build();
|
||||
* }
|
||||
* <pre>{@code
|
||||
* final class FormFields {
|
||||
* private static final FormField<String, String> COUNTRY_CODE =
|
||||
* FormField.named("countryCode")
|
||||
* .range(Range.singleton(2))
|
||||
* .uppercased()
|
||||
* .in(ImmutableSet.copyOf(Locale.getISOCountries()))
|
||||
* .build();
|
||||
* }
|
||||
*
|
||||
* final class Form {
|
||||
* private static final FormField<String, String> COUNTRY_CODE_FIELD =
|
||||
* FormFields.COUNTRY_CODE.asBuilder()
|
||||
* .required()
|
||||
* .build();
|
||||
* }</pre>
|
||||
* final class Form {
|
||||
* private static final FormField<String, String> COUNTRY_CODE_FIELD =
|
||||
* FormFields.COUNTRY_CODE.asBuilder()
|
||||
* .required()
|
||||
* .build();
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* @param <I> input value type
|
||||
* @param <O> output value type
|
||||
|
||||
@@ -105,9 +105,10 @@ public class SendEmailUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** Sends an email from Nomulus to the registrarChangesNotificationEmailAddresses.
|
||||
/**
|
||||
* Sends an email from Nomulus to the registrarChangesNotificationEmailAddresses.
|
||||
*
|
||||
* <p>See {@link #sendEmail(String, String, ImmutableList<String>)}.
|
||||
* <p>See {@link #sendEmail(String, String, ImmutableList)}.
|
||||
*/
|
||||
public boolean sendEmail(final String subject, String body) {
|
||||
return sendEmail(subject, body, ImmutableList.of());
|
||||
|
||||
@@ -22,7 +22,6 @@ import static google.registry.xml.UtcDateTimeAdapter.getFormattedString;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.googlecode.objectify.Key;
|
||||
import google.registry.model.EppResource;
|
||||
import google.registry.model.contact.ContactPhoneNumber;
|
||||
import google.registry.model.contact.ContactResource;
|
||||
@@ -35,6 +34,7 @@ import google.registry.model.eppcommon.StatusValue;
|
||||
import google.registry.model.registrar.Registrar;
|
||||
import google.registry.model.registrar.RegistrarContact;
|
||||
import google.registry.model.translators.EnumToAttributeAdapter.EppEnum;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
@@ -128,7 +128,7 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
}
|
||||
|
||||
/** Returns the contact of the given type. */
|
||||
private Optional<Key<ContactResource>> getContactReference(Type type) {
|
||||
private Optional<VKey<ContactResource>> getContactReference(Type type) {
|
||||
Optional<DesignatedContact> contactOfType =
|
||||
domain.getContacts().stream().filter(d -> d.getType() == type).findFirst();
|
||||
return contactOfType.map(DesignatedContact::getContactKey);
|
||||
@@ -149,14 +149,14 @@ final class DomainWhoisResponse extends WhoisResponseImpl {
|
||||
|
||||
/** Emit the contact entry of the given type. */
|
||||
DomainEmitter emitContact(
|
||||
String contactType, Optional<Key<ContactResource>> contact, boolean preferUnicode) {
|
||||
String contactType, Optional<VKey<ContactResource>> contact, boolean preferUnicode) {
|
||||
if (!contact.isPresent()) {
|
||||
return this;
|
||||
}
|
||||
// If we refer to a contact that doesn't exist, that's a bug. It means referential integrity
|
||||
// has somehow been broken. We skip the rest of this contact, but log it to hopefully bring it
|
||||
// someone's attention.
|
||||
ContactResource contactResource = EppResource.loadCached(contact.get());
|
||||
ContactResource contactResource = EppResource.loadCached(contact.get().getOfyKey());
|
||||
if (contactResource == null) {
|
||||
logger.atSevere().log(
|
||||
"(BUG) Broken reference found from domain %s to contact %s",
|
||||
|
||||
@@ -58,40 +58,38 @@ import org.joda.time.Duration;
|
||||
* <p>You can send AJAX requests to our WHOIS API from your <em>very own</em> website using the
|
||||
* following embed code:
|
||||
*
|
||||
* <pre>
|
||||
* <p>
|
||||
* <input id="query-input" placeholder="Domain, Nameserver, IP, etc." autofocus>
|
||||
* <button id="search-button">Lookup</button>
|
||||
* <p>
|
||||
* <pre id="whois-results"></pre>
|
||||
* <script>
|
||||
* (function() {
|
||||
* var WHOIS_API_URL = 'https://domain-registry-alpha.appspot.com/whois/';
|
||||
* function OnKeyPressQueryInput(ev) {
|
||||
* if (typeof ev == 'undefined' && window.event) {
|
||||
* ev = window.event;
|
||||
* }
|
||||
* if (ev.keyCode == 13) {
|
||||
* document.getElementById('search-button').click();
|
||||
* }
|
||||
* <pre>{@code
|
||||
* <input id="query-input" placeholder="Domain, Nameserver, IP, etc." autofocus>
|
||||
* <button id="search-button">Lookup</button>
|
||||
* <pre id="whois-results"></pre>
|
||||
* <script>
|
||||
* (function() {
|
||||
* var WHOIS_API_URL = 'https://domain-registry-alpha.appspot.com/whois/';
|
||||
* function OnKeyPressQueryInput(ev) {
|
||||
* if (typeof ev == 'undefined' && window.event) {
|
||||
* ev = window.event;
|
||||
* }
|
||||
* function OnClickSearchButton() {
|
||||
* var query = document.getElementById('query-input').value;
|
||||
* var req = new XMLHttpRequest();
|
||||
* req.onreadystatechange = function() {
|
||||
* if (req.readyState == 4) {
|
||||
* var results = document.getElementById('whois-results');
|
||||
* results.textContent = req.responseText;
|
||||
* }
|
||||
* };
|
||||
* req.open('GET', WHOIS_API_URL + escape(query), true);
|
||||
* req.send();
|
||||
* if (ev.keyCode == 13) {
|
||||
* document.getElementById('search-button').click();
|
||||
* }
|
||||
* document.getElementById('search-button').onclick = OnClickSearchButton;
|
||||
* document.getElementById('query-input').onkeypress = OnKeyPressQueryInput;
|
||||
* })();
|
||||
* </script>
|
||||
* </pre>
|
||||
* }
|
||||
* function OnClickSearchButton() {
|
||||
* var query = document.getElementById('query-input').value;
|
||||
* var req = new XMLHttpRequest();
|
||||
* req.onreadystatechange = function() {
|
||||
* if (req.readyState == 4) {
|
||||
* var results = document.getElementById('whois-results');
|
||||
* results.textContent = req.responseText;
|
||||
* }
|
||||
* };
|
||||
* req.open('GET', WHOIS_API_URL + escape(query), true);
|
||||
* req.send();
|
||||
* }
|
||||
* document.getElementById('search-button').onclick = OnClickSearchButton;
|
||||
* document.getElementById('query-input').onkeypress = OnKeyPressQueryInput;
|
||||
* })();
|
||||
* </script>
|
||||
* }</pre>
|
||||
*
|
||||
* @see WhoisAction
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
* Move tests to another (sub)project. This is not a big problem, but feels unnatural.
|
||||
* Use Hibernate's ServiceRegistry for bootstrapping (not JPA-compliant)
|
||||
-->
|
||||
<class>google.registry.model.billing.BillingEvent$Cancellation</class>
|
||||
<class>google.registry.model.billing.BillingEvent$OneTime</class>
|
||||
<class>google.registry.model.billing.BillingEvent$Recurring</class>
|
||||
<class>google.registry.model.contact.ContactResource</class>
|
||||
<class>google.registry.model.domain.DomainBase</class>
|
||||
<class>google.registry.model.host.HostResource</class>
|
||||
@@ -35,6 +38,8 @@
|
||||
<class>google.registry.model.domain.GracePeriod</class>
|
||||
|
||||
<!-- Customized type converters -->
|
||||
<class>google.registry.persistence.converter.BillingCostTransitionConverter</class>
|
||||
<class>google.registry.persistence.converter.BillingEventFlagSetConverter</class>
|
||||
<class>google.registry.persistence.converter.BloomFilterConverter</class>
|
||||
<class>google.registry.persistence.converter.CidrAddressBlockListConverter</class>
|
||||
<class>google.registry.persistence.converter.CreateAutoTimestampConverter</class>
|
||||
@@ -47,11 +52,15 @@
|
||||
<class>google.registry.persistence.converter.StatusValueSetConverter</class>
|
||||
<class>google.registry.persistence.converter.StringListConverter</class>
|
||||
<class>google.registry.persistence.converter.StringSetConverter</class>
|
||||
<class>google.registry.persistence.converter.TldStateTransitionConverter</class>
|
||||
<class>google.registry.persistence.converter.UpdateAutoTimestampConverter</class>
|
||||
<class>google.registry.persistence.converter.ZonedDateTimeConverter</class>
|
||||
|
||||
<!-- Generated converters for VKey -->
|
||||
<class>google.registry.model.billing.VKeyConverter_BillingEvent</class>
|
||||
<class>google.registry.model.domain.token.VKeyConverter_AllocationToken</class>
|
||||
<class>google.registry.model.host.VKeyConverter_HostResource</class>
|
||||
<class>google.registry.model.contact.VKeyConverter_ContactResource</class>
|
||||
|
||||
<!-- TODO(weiminyu): check out application-layer validation. -->
|
||||
<validation-mode>NONE</validation-mode>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
{template .resultSuccess}
|
||||
{@param baseClientId: string} /** The base clientId used for the OT&E setup. */
|
||||
{@param contactEmail: string} /** The contact's email added to the registrars. */
|
||||
{@param clientIdToTld: map<string, string>} /** The created registrars->TLD mapping. */
|
||||
{@param clientIdToTld: map<string, string>} /** The created registrars->TLD mapping. */
|
||||
{@param password: string} /** The password given for the created registrars. */
|
||||
{@param username: string} /** Arbitrary username to display. */
|
||||
{@param logoutUrl: string} /** Generated URL for logging out of Google. */
|
||||
|
||||
@@ -190,7 +190,9 @@ public class DeleteContactsAndHostsActionTest
|
||||
.hasDeletionTime(END_OF_TIME);
|
||||
DomainBase domainReloaded =
|
||||
loadByForeignKey(DomainBase.class, "example.tld", clock.nowUtc()).get();
|
||||
assertThat(domainReloaded.getReferencedContacts()).contains(Key.create(contactUpdated));
|
||||
// We have to check for the objectify key here, specifically, as the SQL side of the key does
|
||||
// not get persisted.
|
||||
assertThat(domainReloaded.getReferencedContacts()).contains(contactUpdated.createVKey());
|
||||
HistoryEntry historyEntry =
|
||||
getOnlyHistoryEntryOfType(contactUpdated, HistoryEntry.Type.CONTACT_DELETE_FAILURE);
|
||||
assertPollMessageFor(
|
||||
|
||||
+19
-15
@@ -155,7 +155,7 @@ public class ExpandRecurringBillingEventsActionTest
|
||||
.setPeriodYears(1)
|
||||
.setReason(Reason.RENEW)
|
||||
.setSyntheticCreationTime(beginningOfTest)
|
||||
.setCancellationMatchingBillingEvent(Key.create(recurring))
|
||||
.setCancellationMatchingBillingEvent(recurring.createVKey())
|
||||
.setTargetId(domain.getFullyQualifiedDomainName());
|
||||
}
|
||||
|
||||
@@ -274,10 +274,12 @@ public class ExpandRecurringBillingEventsActionTest
|
||||
.setParent(persistedEntries.get(0))
|
||||
.build();
|
||||
// Persist an otherwise identical billing event that differs only in recurring event key.
|
||||
BillingEvent.OneTime persisted = expected.asBuilder()
|
||||
.setParent(persistedEntries.get(1))
|
||||
.setCancellationMatchingBillingEvent(Key.create(recurring2))
|
||||
.build();
|
||||
BillingEvent.OneTime persisted =
|
||||
expected
|
||||
.asBuilder()
|
||||
.setParent(persistedEntries.get(1))
|
||||
.setCancellationMatchingBillingEvent(recurring2.createVKey())
|
||||
.build();
|
||||
assertCursorAt(beginningOfTest);
|
||||
assertBillingEventsForResource(domain, persisted, expected, recurring, recurring2);
|
||||
}
|
||||
@@ -604,19 +606,21 @@ public class ExpandRecurringBillingEventsActionTest
|
||||
assertHistoryEntryMatches(
|
||||
domain, persistedEntries.get(0), "TheRegistrar", DateTime.parse("2000-02-19T00:00:00Z"),
|
||||
true);
|
||||
BillingEvent.OneTime expected = defaultOneTimeBuilder()
|
||||
.setParent(persistedEntries.get(0))
|
||||
.setCancellationMatchingBillingEvent(Key.create(recurring))
|
||||
.build();
|
||||
BillingEvent.OneTime expected =
|
||||
defaultOneTimeBuilder()
|
||||
.setParent(persistedEntries.get(0))
|
||||
.setCancellationMatchingBillingEvent(recurring.createVKey())
|
||||
.build();
|
||||
assertHistoryEntryMatches(
|
||||
domain, persistedEntries.get(1), "TheRegistrar", DateTime.parse("2000-05-20T00:00:00Z"),
|
||||
true);
|
||||
BillingEvent.OneTime expected2 = defaultOneTimeBuilder()
|
||||
.setBillingTime(DateTime.parse("2000-05-20T00:00:00Z"))
|
||||
.setEventTime(DateTime.parse("2000-04-05T00:00:00Z"))
|
||||
.setParent(persistedEntries.get(1))
|
||||
.setCancellationMatchingBillingEvent(Key.create(recurring2))
|
||||
.build();
|
||||
BillingEvent.OneTime expected2 =
|
||||
defaultOneTimeBuilder()
|
||||
.setBillingTime(DateTime.parse("2000-05-20T00:00:00Z"))
|
||||
.setEventTime(DateTime.parse("2000-04-05T00:00:00Z"))
|
||||
.setParent(persistedEntries.get(1))
|
||||
.setCancellationMatchingBillingEvent(recurring2.createVKey())
|
||||
.build();
|
||||
assertBillingEventsForResource(domain, expected, expected2, recurring, recurring2);
|
||||
assertCursorAt(beginningOfTest);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package google.registry.export;
|
||||
import static com.google.appengine.api.taskqueue.QueueFactory.getQueue;
|
||||
import static com.google.common.collect.Iterables.getOnlyElement;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.testing.TaskQueueHelper.assertNoTasksEnqueued;
|
||||
import static google.registry.testing.TaskQueueHelper.assertTasksEnqueued;
|
||||
import static google.registry.testing.TestLogHandlerUtils.assertLogMessage;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
@@ -174,6 +175,7 @@ public class BigqueryPollJobActionTest {
|
||||
action.run();
|
||||
assertLogMessage(
|
||||
logHandler, SEVERE, String.format("Bigquery job failed - %s:%s", PROJECT_ID, JOB_ID));
|
||||
assertNoTasksEnqueued(CHAINED_QUEUE_NAME);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -18,6 +18,7 @@ import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_AND_CLOSE;
|
||||
import static google.registry.model.eppoutput.Result.Code.SUCCESS_WITH_ACTION_PENDING;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.model.registry.Registry.TldState.GENERAL_AVAILABILITY;
|
||||
import static google.registry.model.registry.Registry.TldState.PREDELEGATION;
|
||||
import static google.registry.model.registry.Registry.TldState.START_DATE_SUNRISE;
|
||||
@@ -26,6 +27,7 @@ import static google.registry.testing.DatastoreHelper.createTld;
|
||||
import static google.registry.testing.DatastoreHelper.createTlds;
|
||||
import static google.registry.testing.DatastoreHelper.getOnlyHistoryEntryOfType;
|
||||
import static google.registry.testing.DatastoreHelper.persistResource;
|
||||
import static google.registry.testing.DomainBaseSubject.assertAboutDomains;
|
||||
import static google.registry.testing.EppMetricSubject.assertThat;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
import static org.joda.money.CurrencyUnit.USD;
|
||||
@@ -84,19 +86,311 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||
"domain_create_response.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00.0Z",
|
||||
"EXDATE", "2002-06-01T00:02:00.0Z"));
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-07T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_inactive.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z",
|
||||
"UPDATE", "2000-06-06T00:02:00Z"));
|
||||
|
||||
// Delete domain example.tld after its add grace period has expired.
|
||||
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-07-01T00:02:00Z")
|
||||
.hasResponse("generic_success_action_pending_response.xml");
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-07-03T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_redemptionperiod_wildcard.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// The exp. date doesn't change because the deletion didn't cancel any charges.
|
||||
"EXDATE", "2002-06-01T00:02:00Z",
|
||||
"UPDATE", "2000-07-01T00:02:00Z"));
|
||||
|
||||
// Restore the domain.
|
||||
assertThatCommand("domain_update_restore_request.xml")
|
||||
.atTime("2000-07-01T00:03:00Z")
|
||||
.hasResponse("generic_success_response.xml");
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-07-02T00:03:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_inactive.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// TODO(mcilwain): The exp. date should be restored back to 2002-06-01T00:02:00Z,
|
||||
// but this is old behavior of being 1 year after the moment of the restore.
|
||||
"EXDATE", "2001-07-01T00:03:00Z",
|
||||
"UPDATE", "2000-07-01T00:03:00Z"));
|
||||
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDomainDeleteRestore_duringAutorenewGracePeriod() throws Exception {
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
|
||||
|
||||
// Create domain example.tld
|
||||
assertThatCommand(
|
||||
"domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-01T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_create_response.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-07T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_inactive.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z",
|
||||
"UPDATE", "2000-06-06T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2002-06-07T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_graceperiod.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// The exp. date has advanced 1 year because of autorenew.
|
||||
"EXDATE", "2003-06-01T00:02:00Z",
|
||||
// This is the time of the autorenew.
|
||||
"UPDATE", "2002-06-01T00:02:00Z",
|
||||
"GRACEPERIOD", "autoRenewPeriod"));
|
||||
|
||||
// Delete domain example.tld during its autorenew grace period.
|
||||
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2002-07-01T00:02:00Z")
|
||||
.hasResponse("generic_success_action_pending_response.xml");
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2002-07-03T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_redemptionperiod_wildcard.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// The exp. date reverts back to what it was originally because the deletion
|
||||
// canceled out the autorenew.
|
||||
"EXDATE", "2002-06-01T00:02:00Z",
|
||||
"UPDATE", "2002-07-01T00:02:00Z"));
|
||||
|
||||
// Restore the domain.
|
||||
assertThatCommand("domain_update_restore_request.xml")
|
||||
.atTime("2002-07-05T00:03:00Z")
|
||||
.hasResponse("generic_success_response.xml");
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2002-07-07T00:03:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_inactive.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// TODO(mcilwain): The exp. date should be 2003-06-01T00:02:00Z, the same as its
|
||||
// value prior to the deletion, because the year that was taken off when the
|
||||
// autorenew was canceled will be re-added in renewal during the restore.
|
||||
// For now though, the current behavior is 1 year after restore.
|
||||
"EXDATE", "2003-07-05T00:03:00Z",
|
||||
"UPDATE", "2002-07-05T00:03:00Z"));
|
||||
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDomainDeleteRestore_duringRenewalGracePeriod() throws Exception {
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
|
||||
|
||||
// Create domain example.tld
|
||||
assertThatCommand(
|
||||
"domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-01T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_create_response.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-07T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_inactive.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z",
|
||||
"UPDATE", "2000-06-06T00:02:00Z"));
|
||||
|
||||
assertThatCommand(
|
||||
"domain_renew.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "EXPDATE", "2002-06-01", "YEARS", "3"))
|
||||
.atTime("2000-06-08T00:00:00Z")
|
||||
.hasResponse(
|
||||
"domain_renew_response.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2005-06-01T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-10T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_graceperiod.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// The exp. date is 5 years in total after the create.
|
||||
"EXDATE", "2005-06-01T00:02:00Z",
|
||||
// This is the time of the renew.
|
||||
"UPDATE", "2000-06-08T00:00:00Z",
|
||||
"GRACEPERIOD", "renewPeriod"));
|
||||
|
||||
// Delete domain example.tld during its renew grace period.
|
||||
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-12T00:00:00Z")
|
||||
.hasResponse("generic_success_action_pending_response.xml");
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-13T00:00:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_redemptionperiod_wildcard.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// The exp. date reverts back to what it was originally because the deletion
|
||||
// canceled out the 3-year renewal.
|
||||
"EXDATE", "2002-06-01T00:02:00Z",
|
||||
"UPDATE", "2000-06-12T00:00:00Z"));
|
||||
|
||||
// Restore the domain.
|
||||
assertThatCommand("domain_update_restore_request.xml")
|
||||
.atTime("2000-06-20T00:00:00Z")
|
||||
.hasResponse("generic_success_response.xml");
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-21T00:00:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_inactive.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// TODO(mcilwain): The exp. date should be 2002-06-01T00:02:00Z, which is the
|
||||
// current registration expiration time on the (deleted) domain, but for now is
|
||||
// 1 year after restore.
|
||||
"EXDATE", "2001-06-20T00:00:00Z",
|
||||
"UPDATE", "2000-06-20T00:00:00Z"));
|
||||
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDomainDelete_duringAddAndRenewalGracePeriod_deletesImmediately()
|
||||
throws Exception {
|
||||
assertThatLoginSucceeds("NewRegistrar", "foo-BAR2");
|
||||
createContacts(DateTime.parse("2000-06-01T00:00:00Z"));
|
||||
|
||||
DateTime createTime = DateTime.parse("2000-06-01T00:02:00Z");
|
||||
// Create domain example.tld
|
||||
assertThatCommand(
|
||||
"domain_create_no_hosts_or_dsdata.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime(createTime)
|
||||
.hasResponse(
|
||||
"domain_create_response.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-02T00:02:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_addperiod_wildcard.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
"EXDATE", "2002-06-01T00:02:00Z"));
|
||||
|
||||
DateTime renewTime = DateTime.parse("2000-06-03T00:00:00Z");
|
||||
assertThatCommand(
|
||||
"domain_renew.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "EXPDATE", "2002-06-01", "YEARS", "3"))
|
||||
.atTime(renewTime)
|
||||
.hasResponse(
|
||||
"domain_renew_response.xml",
|
||||
ImmutableMap.of("DOMAIN", "example.tld", "EXDATE", "2005-06-01T00:02:00Z"));
|
||||
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-03T03:00:00Z")
|
||||
.hasResponse(
|
||||
"domain_info_response_graceperiod_add_and_renew.xml",
|
||||
ImmutableMap.of(
|
||||
"DOMAIN", "example.tld",
|
||||
"CRDATE", "2000-06-01T00:02:00Z",
|
||||
// The exp. date is 5 years in total after the create.
|
||||
"EXDATE", "2005-06-01T00:02:00Z",
|
||||
// This is the time of the renew.
|
||||
"UPDATE", "2000-06-03T00:00:00Z"));
|
||||
|
||||
DomainBase domain =
|
||||
loadByForeignKey(DomainBase.class, "example.tld", DateTime.parse("2000-06-03T04:00:00Z"))
|
||||
.get();
|
||||
|
||||
DateTime deleteTime = DateTime.parse("2000-06-04T00:00:00Z");
|
||||
// Delete domain example.tld during both grace periods.
|
||||
assertThatCommand("domain_delete.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-04T00:00:00Z")
|
||||
.hasResponse("generic_success_response.xml");
|
||||
|
||||
// Verify that it is immediately non-existent.
|
||||
assertThatCommand("domain_info.xml", ImmutableMap.of("DOMAIN", "example.tld"))
|
||||
.atTime("2000-06-04T00:01:00Z")
|
||||
.hasResponse(
|
||||
"response_error.xml",
|
||||
ImmutableMap.of(
|
||||
"CODE", "2303", "MSG", "The domain with given ID (example.tld) doesn't exist."));
|
||||
|
||||
// The expected one-time billing event, that should have an associated Cancellation.
|
||||
OneTime oneTimeCreateBillingEvent = makeOneTimeCreateBillingEvent(domain, createTime);
|
||||
OneTime oneTimeRenewBillingEvent = makeOneTimeRenewBillingEvent(domain, renewTime);
|
||||
|
||||
// Verify that the OneTime billing event associated with the domain creation is canceled.
|
||||
assertBillingEventsForResource(
|
||||
domain,
|
||||
// There should be one-time billing events for the create and the renew.
|
||||
oneTimeCreateBillingEvent,
|
||||
oneTimeRenewBillingEvent,
|
||||
// There should be two ended recurring billing events, one each from the create and renew.
|
||||
// (The former was ended by the renew and the latter was ended by the delete.)
|
||||
makeRecurringCreateBillingEvent(domain, createTime.plusYears(2), renewTime),
|
||||
makeRecurringRenewBillingEvent(domain, createTime.plusYears(5), deleteTime),
|
||||
// There should be Cancellations offsetting both of the one-times.
|
||||
makeCancellationBillingEventForCreate(
|
||||
domain, oneTimeCreateBillingEvent, createTime, deleteTime),
|
||||
makeCancellationBillingEventForRenew(
|
||||
domain, oneTimeRenewBillingEvent, renewTime, deleteTime));
|
||||
|
||||
// Verify that the registration expiration time was set back to the creation time, because the
|
||||
// entire cost of registration was refunded. We have to do this through the DB instead of EPP
|
||||
// because domains deleted during the add grace period vanish immediately as far as the world
|
||||
// outside our system is concerned.
|
||||
DomainBase deletedDomain = ofy().load().entity(domain).now();
|
||||
assertAboutDomains().that(deletedDomain).hasRegistrationExpirationTime(createTime);
|
||||
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@@ -143,9 +437,16 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||
oneTimeCreateBillingEvent,
|
||||
makeRecurringCreateBillingEvent(domain, createTime.plusYears(2), deleteTime),
|
||||
// Check for the existence of a cancellation for the given one-time billing event.
|
||||
makeCancellationBillingEventFor(
|
||||
makeCancellationBillingEventForCreate(
|
||||
domain, oneTimeCreateBillingEvent, createTime, deleteTime));
|
||||
|
||||
// Verify that the registration expiration time was set back to the creation time, because the
|
||||
// entire cost of registration was refunded. We have to do this through the DB instead of EPP
|
||||
// because domains deleted during the add grace period vanish immediately as far as the world
|
||||
// outside our system is concerned.
|
||||
DomainBase deletedDomain = ofy().load().entity(domain).now();
|
||||
assertAboutDomains().that(deletedDomain).hasRegistrationExpirationTime(createTime);
|
||||
|
||||
assertThatLogoutSucceeds();
|
||||
}
|
||||
|
||||
@@ -268,7 +569,7 @@ public class EppLifecycleDomainTest extends EppTestCase {
|
||||
expectedCreateEapBillingEvent,
|
||||
makeRecurringCreateBillingEvent(domain, createTime.plusYears(2), deleteTime),
|
||||
// ... and verify that the create one-time billing event was canceled ...
|
||||
makeCancellationBillingEventFor(
|
||||
makeCancellationBillingEventForCreate(
|
||||
domain, expectedOneTimeCreateBillingEvent, createTime, deleteTime));
|
||||
// ... but there was NOT a Cancellation for the EAP fee, as this would fail if additional
|
||||
// billing events were present.
|
||||
|
||||
@@ -41,6 +41,7 @@ import google.registry.model.registry.Registry;
|
||||
import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.reporting.HistoryEntry.Type;
|
||||
import google.registry.monitoring.whitebox.EppMetric;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.FakeClock;
|
||||
import google.registry.testing.FakeHttpSession;
|
||||
import google.registry.testing.FakeResponse;
|
||||
@@ -284,20 +285,42 @@ public class EppTestCase extends ShardableTestCase {
|
||||
.setCost(Money.parse("USD 26.00"))
|
||||
.setPeriodYears(2)
|
||||
.setEventTime(createTime)
|
||||
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
|
||||
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getAddGracePeriodLength()))
|
||||
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_CREATE))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Makes a one-time billing event corresponding to the given domain's renewal. */
|
||||
protected static BillingEvent.OneTime makeOneTimeRenewBillingEvent(
|
||||
DomainBase domain, DateTime renewTime) {
|
||||
return new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setTargetId(domain.getFullyQualifiedDomainName())
|
||||
.setClientId(domain.getCurrentSponsorClientId())
|
||||
.setCost(Money.parse("USD 33.00"))
|
||||
.setPeriodYears(3)
|
||||
.setEventTime(renewTime)
|
||||
.setBillingTime(renewTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
|
||||
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_RENEW))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Makes a recurring billing event corresponding to the given domain's creation. */
|
||||
protected static BillingEvent.Recurring makeRecurringCreateBillingEvent(
|
||||
DomainBase domain, DateTime eventTime, DateTime endTime) {
|
||||
return makeRecurringCreateBillingEvent(
|
||||
return makeRecurringBillingEvent(
|
||||
domain, getOnlyHistoryEntryOfType(domain, Type.DOMAIN_CREATE), eventTime, endTime);
|
||||
}
|
||||
|
||||
/** Makes a recurring billing event corresponding to the given domain's renewal. */
|
||||
protected static BillingEvent.Recurring makeRecurringRenewBillingEvent(
|
||||
DomainBase domain, DateTime eventTime, DateTime endTime) {
|
||||
return makeRecurringBillingEvent(
|
||||
domain, getOnlyHistoryEntryOfType(domain, Type.DOMAIN_RENEW), eventTime, endTime);
|
||||
}
|
||||
|
||||
/** Makes a recurring billing event corresponding to the given history entry. */
|
||||
protected static BillingEvent.Recurring makeRecurringCreateBillingEvent(
|
||||
protected static BillingEvent.Recurring makeRecurringBillingEvent(
|
||||
DomainBase domain, HistoryEntry historyEntry, DateTime eventTime, DateTime endTime) {
|
||||
return new BillingEvent.Recurring.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
@@ -311,22 +334,35 @@ public class EppTestCase extends ShardableTestCase {
|
||||
}
|
||||
|
||||
/** Makes a cancellation billing event cancelling out the given domain create billing event. */
|
||||
protected static BillingEvent.Cancellation makeCancellationBillingEventFor(
|
||||
DomainBase domain,
|
||||
OneTime billingEventToCancel,
|
||||
DateTime createTime,
|
||||
DateTime deleteTime) {
|
||||
protected static BillingEvent.Cancellation makeCancellationBillingEventForCreate(
|
||||
DomainBase domain, OneTime billingEventToCancel, DateTime createTime, DateTime deleteTime) {
|
||||
return new BillingEvent.Cancellation.Builder()
|
||||
.setTargetId(domain.getFullyQualifiedDomainName())
|
||||
.setClientId(domain.getCurrentSponsorClientId())
|
||||
.setEventTime(deleteTime)
|
||||
.setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel))
|
||||
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
|
||||
.setOneTimeEventKey(
|
||||
VKey.createOfy(OneTime.class, findKeyToActualOneTimeBillingEvent(billingEventToCancel)))
|
||||
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getAddGracePeriodLength()))
|
||||
.setReason(Reason.CREATE)
|
||||
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE))
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Makes a cancellation billing event cancelling out the given domain renew billing event. */
|
||||
protected static BillingEvent.Cancellation makeCancellationBillingEventForRenew(
|
||||
DomainBase domain, OneTime billingEventToCancel, DateTime renewTime, DateTime deleteTime) {
|
||||
return new BillingEvent.Cancellation.Builder()
|
||||
.setTargetId(domain.getFullyQualifiedDomainName())
|
||||
.setClientId(domain.getCurrentSponsorClientId())
|
||||
.setEventTime(deleteTime)
|
||||
.setOneTimeEventKey(
|
||||
VKey.createOfy(OneTime.class, findKeyToActualOneTimeBillingEvent(billingEventToCancel)))
|
||||
.setBillingTime(renewTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
|
||||
.setReason(Reason.RENEW)
|
||||
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the Key to the actual one-time create billing event associated with a domain's creation.
|
||||
*
|
||||
|
||||
@@ -276,7 +276,7 @@ public class DomainCreateFlowTest extends ResourceFlowTestCase<DomainCreateFlow,
|
||||
.setBillingTime(billingTime)
|
||||
.setFlags(expectedBillingFlags)
|
||||
.setParent(historyEntry)
|
||||
.setAllocationToken(allocationToken == null ? null : Key.create(allocationToken))
|
||||
.setAllocationToken(allocationToken == null ? null : allocationToken.createVKey())
|
||||
.build();
|
||||
|
||||
BillingEvent.Recurring renewBillingEvent =
|
||||
|
||||
@@ -151,7 +151,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setCreationTimeForTest(TIME_BEFORE_FLOW)
|
||||
.setRegistrant(Key.create(contact))
|
||||
.setRegistrant(contact.createVKey())
|
||||
.setRegistrationExpirationTime(expirationTime)
|
||||
.build();
|
||||
earlierHistoryEntry =
|
||||
@@ -214,7 +214,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
|
||||
.setClientId("TheRegistrar")
|
||||
.setEventTime(eventTime)
|
||||
.setBillingTime(TIME_BEFORE_FLOW.plusDays(1))
|
||||
.setOneTimeEventKey(Key.create(graceBillingEvent))
|
||||
.setOneTimeEventKey(graceBillingEvent.createVKey())
|
||||
.setParent(historyEntryDomainDelete)
|
||||
.build());
|
||||
}
|
||||
@@ -391,7 +391,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
|
||||
GracePeriod.create(GracePeriodStatus.TRANSFER, TIME_BEFORE_FLOW.plusDays(1), "foo", null));
|
||||
// We should see exactly one poll message, which is for the autorenew 1 month in the future.
|
||||
assertPollMessages(createAutorenewPollMessage("TheRegistrar").build());
|
||||
DateTime originalExpirationTime = domain.getRegistrationExpirationTime();
|
||||
DateTime expectedExpirationTime = domain.getRegistrationExpirationTime().minusYears(2);
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile(responseFilename, substitutions));
|
||||
DomainBase resource = reloadResourceByForeignKey();
|
||||
@@ -413,7 +413,7 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
|
||||
// deletion time, that means once it passes the domain will experience a "phantom autorenew"
|
||||
// where the expirationTime advances and the grace period appears, but since the delete flow
|
||||
// closed the autorenew recurrences immediately, there are no other autorenew effects.
|
||||
assertAboutDomains().that(resource).hasRegistrationExpirationTime(originalExpirationTime);
|
||||
assertAboutDomains().that(resource).hasRegistrationExpirationTime(expectedExpirationTime);
|
||||
// All existing grace periods that were for billable actions should cause cancellations.
|
||||
assertAutorenewClosedAndCancellationCreatedFor(
|
||||
renewBillingEvent, getOnlyHistoryEntryOfType(resource, DOMAIN_DELETE));
|
||||
@@ -700,7 +700,9 @@ public class DomainDeleteFlowTest extends ResourceFlowTestCase<DomainDeleteFlow,
|
||||
newDomainBase("example1.tld")
|
||||
.asBuilder()
|
||||
.setRegistrant(
|
||||
Key.create(loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()).get()))
|
||||
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc())
|
||||
.get()
|
||||
.createVKey())
|
||||
.setNameservers(ImmutableSet.of(host.createKey()))
|
||||
.setDeletionTime(START_OF_TIME)
|
||||
.build());
|
||||
|
||||
@@ -112,11 +112,11 @@ public class DomainInfoFlowTest extends ResourceFlowTestCase<DomainInfoFlow, Dom
|
||||
.setLastEppUpdateTime(DateTime.parse("1999-12-03T09:00:00.0Z"))
|
||||
.setLastTransferTime(DateTime.parse("2000-04-08T09:00:00.0Z"))
|
||||
.setRegistrationExpirationTime(DateTime.parse("2005-04-03T22:00:00.0Z"))
|
||||
.setRegistrant(Key.create(registrant))
|
||||
.setRegistrant(registrant.createVKey())
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.ADMIN, Key.create(contact)),
|
||||
DesignatedContact.create(Type.TECH, Key.create(contact))))
|
||||
DesignatedContact.create(Type.ADMIN, contact.createVKey()),
|
||||
DesignatedContact.create(Type.TECH, contact.createVKey())))
|
||||
.setNameservers(
|
||||
inactive ? null : ImmutableSet.of(host1.createKey(), host2.createKey()))
|
||||
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create("2fooBAR")))
|
||||
|
||||
@@ -71,6 +71,7 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Stream;
|
||||
import org.joda.money.Money;
|
||||
@@ -402,7 +403,8 @@ public class DomainTransferApproveFlowTest
|
||||
.setEventTime(clock.nowUtc()) // The cancellation happens at the moment of transfer.
|
||||
.setBillingTime(
|
||||
oldExpirationTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()))
|
||||
.setRecurringEventKey(domain.getAutorenewBillingEvent()));
|
||||
.setRecurringEventKey(
|
||||
VKey.createOfy(BillingEvent.Recurring.class, domain.getAutorenewBillingEvent())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -100,6 +100,7 @@ import google.registry.model.reporting.HistoryEntry;
|
||||
import google.registry.model.transfer.TransferData;
|
||||
import google.registry.model.transfer.TransferResponse;
|
||||
import google.registry.model.transfer.TransferStatus;
|
||||
import google.registry.persistence.VKey;
|
||||
import google.registry.testing.TaskQueueHelper.TaskMatcher;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -1136,7 +1137,8 @@ public class DomainTransferRequestFlowTest
|
||||
.setEventTime(clock.nowUtc().plus(Registry.get("tld").getAutomaticTransferLength()))
|
||||
.setBillingTime(autorenewTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()))
|
||||
// The cancellation should refer to the old autorenew billing event.
|
||||
.setRecurringEventKey(existingAutorenewEvent));
|
||||
.setRecurringEventKey(
|
||||
VKey.createOfy(BillingEvent.Recurring.class, existingAutorenewEvent)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1164,7 +1166,8 @@ public class DomainTransferRequestFlowTest
|
||||
.setBillingTime(
|
||||
expirationTime.plus(Registry.get("tld").getAutoRenewGracePeriodLength()))
|
||||
// The cancellation should refer to the old autorenew billing event.
|
||||
.setRecurringEventKey(existingAutorenewEvent));
|
||||
.setRecurringEventKey(
|
||||
VKey.createOfy(BillingEvent.Recurring.class, existingAutorenewEvent)));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -19,8 +19,8 @@ import static com.google.common.io.BaseEncoding.base16;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static google.registry.model.EppResourceUtils.loadByForeignKey;
|
||||
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.model.registry.Registry.TldState.QUIET_PERIOD;
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.testing.DatastoreHelper.assertBillingEvents;
|
||||
import static google.registry.testing.DatastoreHelper.assertNoBillingEvents;
|
||||
import static google.registry.testing.DatastoreHelper.createTld;
|
||||
@@ -135,10 +135,10 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.asBuilder()
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.TECH, Key.create(mak21Contact)),
|
||||
DesignatedContact.create(Type.ADMIN, Key.create(mak21Contact)),
|
||||
DesignatedContact.create(Type.BILLING, Key.create(mak21Contact))))
|
||||
.setRegistrant(Key.create(mak21Contact))
|
||||
DesignatedContact.create(Type.TECH, mak21Contact.createVKey()),
|
||||
DesignatedContact.create(Type.ADMIN, mak21Contact.createVKey()),
|
||||
DesignatedContact.create(Type.BILLING, mak21Contact.createVKey())))
|
||||
.setRegistrant(mak21Contact.createVKey())
|
||||
.setNameservers(ImmutableSet.of(host.createKey()))
|
||||
.build());
|
||||
historyEntryDomainCreate =
|
||||
@@ -160,8 +160,8 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.asBuilder()
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.TECH, Key.create(sh8013Contact)),
|
||||
DesignatedContact.create(Type.ADMIN, Key.create(unusedContact))))
|
||||
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey()),
|
||||
DesignatedContact.create(Type.ADMIN, unusedContact.createVKey())))
|
||||
.setNameservers(ImmutableSet.of(host.createKey()))
|
||||
.build());
|
||||
historyEntryDomainCreate =
|
||||
@@ -298,7 +298,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
contactsBuilder.add(
|
||||
DesignatedContact.create(
|
||||
DesignatedContact.Type.values()[i % 4],
|
||||
Key.create(persistActiveContact(String.format("max_test_%d", i)))));
|
||||
persistActiveContact(String.format("max_test_%d", i)).createVKey()));
|
||||
}
|
||||
ImmutableList<DesignatedContact> contacts = contactsBuilder.build();
|
||||
persistResource(
|
||||
@@ -319,8 +319,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
assertThat(domain.getNameservers()).hasSize(13);
|
||||
// getContacts does not return contacts of type REGISTRANT, so check these separately.
|
||||
assertThat(domain.getContacts()).hasSize(3);
|
||||
assertThat(ofy().load().key(domain.getRegistrant()).now().getContactId())
|
||||
.isEqualTo("max_test_7");
|
||||
assertThat(tm().load(domain.getRegistrant()).getContactId()).isEqualTo("max_test_7");
|
||||
assertNoBillingEvents();
|
||||
assertDnsTasksEnqueued("example.tld");
|
||||
}
|
||||
@@ -403,7 +402,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
persistResource(
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
.setRegistrant(Key.create(sh8013))
|
||||
.setRegistrant(sh8013.createVKey())
|
||||
.build());
|
||||
clock.advanceOneMilli();
|
||||
runFlowAssertResponse(loadFile("generic_success_response.xml"));
|
||||
@@ -415,7 +414,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
persistReferencedEntities();
|
||||
ContactResource sh8013 =
|
||||
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()).get();
|
||||
Key<ContactResource> sh8013Key = Key.create(sh8013);
|
||||
VKey<ContactResource> sh8013Key = sh8013.createVKey();
|
||||
persistResource(
|
||||
newDomainBase(getUniqueIdFromCommand())
|
||||
.asBuilder()
|
||||
@@ -866,8 +865,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.setContacts(
|
||||
DesignatedContact.create(
|
||||
Type.TECH,
|
||||
Key.create(
|
||||
loadByForeignKey(ContactResource.class, "foo", clock.nowUtc()).get())))
|
||||
loadByForeignKey(ContactResource.class, "foo", clock.nowUtc())
|
||||
.get()
|
||||
.createVKey()))
|
||||
.build());
|
||||
EppException thrown = assertThrows(DuplicateContactForRoleException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1077,8 +1077,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.setContacts(
|
||||
DesignatedContact.create(
|
||||
Type.TECH,
|
||||
Key.create(
|
||||
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc()).get())))
|
||||
loadByForeignKey(ContactResource.class, "sh8013", clock.nowUtc())
|
||||
.get()
|
||||
.createVKey()))
|
||||
.build());
|
||||
EppException thrown = assertThrows(AddRemoveSameValueException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1093,8 +1094,8 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.asBuilder()
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.ADMIN, Key.create(sh8013Contact)),
|
||||
DesignatedContact.create(Type.TECH, Key.create(sh8013Contact))))
|
||||
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
|
||||
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
|
||||
.build());
|
||||
EppException thrown = assertThrows(MissingAdminContactException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1109,8 +1110,8 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.asBuilder()
|
||||
.setContacts(
|
||||
ImmutableSet.of(
|
||||
DesignatedContact.create(Type.ADMIN, Key.create(sh8013Contact)),
|
||||
DesignatedContact.create(Type.TECH, Key.create(sh8013Contact))))
|
||||
DesignatedContact.create(Type.ADMIN, sh8013Contact.createVKey()),
|
||||
DesignatedContact.create(Type.TECH, sh8013Contact.createVKey())))
|
||||
.build());
|
||||
EppException thrown = assertThrows(MissingTechnicalContactException.class, this::runFlow);
|
||||
assertAboutEppExceptions().that(thrown).marshalsToXml();
|
||||
@@ -1223,7 +1224,7 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.setAllowedFullyQualifiedHostNames(ImmutableSet.of("ns1.example.foo"))
|
||||
.build());
|
||||
runFlow();
|
||||
assertThat(ofy().load().key(reloadResourceByForeignKey().getRegistrant()).now().getContactId())
|
||||
assertThat(tm().load(reloadResourceByForeignKey().getRegistrant()).getContactId())
|
||||
.isEqualTo("sh8013");
|
||||
}
|
||||
|
||||
@@ -1237,10 +1238,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.getContacts()
|
||||
.forEach(
|
||||
contact -> {
|
||||
assertThat(ofy().load().key(contact.getContactKey()).now().getContactId())
|
||||
.isEqualTo("mak21");
|
||||
assertThat(tm().load(contact.getContactKey()).getContactId()).isEqualTo("mak21");
|
||||
});
|
||||
assertThat(ofy().load().key(reloadResourceByForeignKey().getRegistrant()).now().getContactId())
|
||||
assertThat(tm().load(reloadResourceByForeignKey().getRegistrant()).getContactId())
|
||||
.isEqualTo("mak21");
|
||||
|
||||
runFlow();
|
||||
@@ -1249,10 +1249,9 @@ public class DomainUpdateFlowTest extends ResourceFlowTestCase<DomainUpdateFlow,
|
||||
.getContacts()
|
||||
.forEach(
|
||||
contact -> {
|
||||
assertThat(ofy().load().key(contact.getContactKey()).now().getContactId())
|
||||
.isEqualTo("sh8013");
|
||||
assertThat(tm().load(contact.getContactKey()).getContactId()).isEqualTo("sh8013");
|
||||
});
|
||||
assertThat(ofy().load().key(reloadResourceByForeignKey().getRegistrant()).now().getContactId())
|
||||
assertThat(tm().load(reloadResourceByForeignKey().getRegistrant()).getContactId())
|
||||
.isEqualTo("sh8013");
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user