1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Weimin Yu 2b794347e6 Refactor LevelDbFileBuilder to accept DS Entity (#599)
* Refactor LevelDbFileBuilder to accept DS Entity

Builder now can directly work with Datastore Entity objects.
No need to wrap data in ComparableEntity.
2020-05-28 13:38:00 -04:00
Shicong Huang 26fb5388a4 Generate sql schema for BillingEvent (#565)
* Generate sql schema for BillingEvent

* Change to use sequence

* Address comments

* Resolve warnings and remove duplicate cost related fields

* Increase the flayway file version to V25

* Remove extra space

* Split to 3 tables, merge VKey

* Rename talbes

* Rename repoId to domainRepoId

* Exclude VKey in schema.txt

* Rename target_id to domain_name

* Fix javadoc

* Resolve comments
2020-05-27 15:59:19 -04:00
Lai Jiang bd443633f6 Add a task to compile javadoc across all packages (#597)
Also fixes various issues that prevent javadoc compliation.
2020-05-27 10:33:46 -04:00
Weimin Yu d87f119b36 Add a test for SQL logging config (#598)
* Add a test for SQL logging config

Verifies that SQL statements are logged by Hibernate when
configured to do so.
2020-05-26 16:25:33 -04:00
Weimin Yu 54f1357d83 Fix show-sql which stopped working (#596)
* Fix show-sql which stopped working

Made show-sql property configurable in JpaUnitTestRules.

Added a few comments on foreign key constraint behavior.
2020-05-21 12:20:56 -04:00
Lai Jiang c73d154084 Do not enqueue update snapshot task if import fails (#578)
If the import from Datastore to BigQuery fails, there is no point
enqueuing a job to update the snapshot view.

Also when there's an error updating the snapshot view, log it at severe
level. The HTTP exception thrown is logged at info and triggers a retry
implicitly. I'm not sure if we want this behavior though. Do we want to
retry upon snapshot updating failures? Unless the failurs are transient,
retrying doesn't help. In our case the failure (End of time out of range
in Standard SQL) is not transient.
2020-05-21 11:40:45 -04:00
gbrodman 259d2e2cdc Run "npm audit fix" to fix a vulnerability (#592) 2020-05-20 15:12:27 -04:00
Shicong Huang 0f174d9ce0 Add all existing entities to VKeyTranslatorFactory (#595)
EntityClasses.ALL_CLASSES has all of our registered entities so
we can use it to initialize VKeyTranslatorFactory.classRegistry
to avoid adding them one by one.

Also, this PR changed to use Key.getKind() to get the kind of
the entity to solve the problem that when the entity class
is an inner class, its kind should still be the class name
instead of OuterClass$InnerClass.
2020-05-20 14:24:45 -04:00
Weimin Yu ca2edb6a17 Close input channel in LevelDbLogReader (#594)
* Close input channel in LevelDbLogReader

Input channel should be closed when all data has been read.
2020-05-20 12:54:13 -04:00
Weimin Yu 3947ac6ef7 Read LevelDb incrementally (#593)
* Read LevelDb incrementally

Made LevelDbLogReader an iterator over a LevelDb data stream,
Reducing memory footprint which is important when used in a
Dataflow pipeline.
2020-05-20 10:26:34 -04:00
110 changed files with 1582 additions and 650 deletions
+23
View File
@@ -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 {
@@ -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 (&gt; 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 &lt;pw&gt; and &lt;newPW&gt;
* tags) as follows:
*
* <ul>
* <li>A control character (in ranges [0 - 1F] and [7F - 9F]) is replaced with 'C'.
@@ -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&gt;=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&gt;=0.12 of the fee extension.
*/
public abstract DateTime asOfDate();
@@ -534,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
@@ -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&amp;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
@@ -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
@@ -72,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")
@@ -103,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. */
@@ -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 {
@@ -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 &lt;delete&gt; 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;
@@ -34,10 +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.contact.ContactResource;
import google.registry.model.host.HostResource;
import google.registry.model.translators.BloomFilterOfStringTranslatorFactory;
import google.registry.model.translators.CidrAddressBlockTranslatorFactory;
import google.registry.model.translators.CommitLogRevisionsTranslatorFactory;
@@ -131,7 +130,7 @@ public class ObjectifyService {
new InetAddressTranslatorFactory(),
new MoneyStringTranslatorFactory(),
new ReadableInstantUtcTranslatorFactory(),
new VKeyTranslatorFactory(HostResource.class, ContactResource.class),
new VKeyTranslatorFactory(),
new UpdateAutoTimestampTranslatorFactory())) {
factory().getTranslators().add(translatorFactory);
}
@@ -169,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 - &ltpoll&gt
* @see <a href="https://tools.ietf.org/html/rfc5730#section-2.9.2.3">RFC5730 - EPP - &lt;poll&gt;
* 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&lt;ReservedList&gt; 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"),
@@ -16,13 +16,12 @@ package google.registry.model.translators;
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.google.appengine.api.datastore.Key;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableMap;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.persistence.VKey;
import java.util.List;
import java.util.stream.Stream;
/**
* Translator factory for VKey.
@@ -34,21 +33,16 @@ public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey,
// Class registry allowing us to restore the original class object from the unqualified class
// name, which is all the datastore key gives us.
private final ImmutableMap<String, Class> classRegistry;
// 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(Class... refClasses) {
public VKeyTranslatorFactory() {
super(VKey.class);
// Store a registry of all classes by their unqualified name.
classRegistry =
Stream.of(refClasses)
.collect(
toImmutableMap(
clazz -> {
List<String> nameComponent = Splitter.on('.').splitToList(clazz.getName());
return nameComponent.get(nameComponent.size() - 1);
},
identity()));
}
@Override
@@ -57,14 +51,16 @@ public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey,
@Override
public VKey loadValue(Key datastoreValue) {
// TODO(mmuller): we need to call a method on refClass to also reconstitute the SQL key.
return VKey.createOfy(
classRegistry.get(datastoreValue.getKind()),
com.googlecode.objectify.Key.create(datastoreValue));
return datastoreValue == null
? null
: VKey.createOfy(
CLASS_REGISTRY.get(datastoreValue.getKind()),
com.googlecode.objectify.Key.create(datastoreValue));
}
@Override
public Key saveValue(VKey key) {
return key.getOfyKey().getRaw();
return key == null ? null : key.getOfyKey().getRaw();
}
};
}
@@ -15,7 +15,6 @@
package google.registry.persistence.converter;
import avro.shaded.com.google.common.collect.Maps;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.registry.Registry.BillingCostTransition;
import java.util.Map;
import javax.persistence.Converter;
@@ -23,8 +22,8 @@ import org.joda.money.Money;
import org.joda.time.DateTime;
/**
* JPA converter for storing/retrieving {@link TimedTransitionProperty <Money, BillingCostTransition
* >} objects.
* JPA converter for storing/retrieving {@code TimedTransitionProperty<Money,BillingCostTransition>}
* objects.
*/
@Converter(autoApply = true)
public class BillingCostTransitionConverter
@@ -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);
}
}
@@ -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)
@@ -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> {
@@ -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> {
@@ -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
@@ -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> {
@@ -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> {
@@ -15,7 +15,6 @@
package google.registry.persistence.converter;
import com.google.common.collect.Maps;
import google.registry.model.common.TimedTransitionProperty;
import google.registry.model.registry.Registry.TldState;
import google.registry.model.registry.Registry.TldStateTransition;
import java.util.Map;
@@ -23,7 +22,7 @@ import javax.persistence.Converter;
import org.joda.time.DateTime;
/**
* JPA converter for storing/retrieving {@link TimedTransitionProperty<TldState,
* JPA converter for storing/retrieving {@code TimedTransitionProperty<TldState,
* TldStateTransition>} objects.
*/
@Converter(autoApply = true)
@@ -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;
@@ -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",
@@ -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&amp;a=x&amp;b=y&amp;c=z", and this method is
* called with parameterName = "b" and parameterValues of "p" and "q", the result will be
* "a=w&amp;a=x&amp;c=z&amp;b=p&amp;b=q". The new values of parameter "b" replace the old ones.
*/
protected String getRequestUrlWithExtraParameter(
String parameterName, List<String> parameterValues) {
@@ -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://&lt;projectId&gt;-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,
@@ -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&amp;bar=there hello
* <li>/foo?bar= 400 error (empty)
* <li>/foo?bar=&bar=there 400 error (empty)
* <li>/foo?bar=&amp;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 &gt;= 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 &gt;= 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);
}
}
@@ -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);
}
@@ -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());
@@ -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>
@@ -36,6 +39,7 @@
<!-- 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>
@@ -53,6 +57,8 @@
<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>
@@ -55,7 +55,7 @@
{template .resultSuccess}
{@param baseClientId: string} /** The base clientId used for the OT&amp;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-&gt;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. */
@@ -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
@@ -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;
@@ -339,7 +340,8 @@ public class EppTestCase extends ShardableTestCase {
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setEventTime(deleteTime)
.setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel))
.setOneTimeEventKey(
VKey.createOfy(OneTime.class, findKeyToActualOneTimeBillingEvent(billingEventToCancel)))
.setBillingTime(createTime.plus(Registry.get(domain.getTld()).getAddGracePeriodLength()))
.setReason(Reason.CREATE)
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE))
@@ -353,7 +355,8 @@ public class EppTestCase extends ShardableTestCase {
.setTargetId(domain.getFullyQualifiedDomainName())
.setClientId(domain.getCurrentSponsorClientId())
.setEventTime(deleteTime)
.setOneTimeEventKey(findKeyToActualOneTimeBillingEvent(billingEventToCancel))
.setOneTimeEventKey(
VKey.createOfy(OneTime.class, findKeyToActualOneTimeBillingEvent(billingEventToCancel)))
.setBillingTime(renewTime.plus(Registry.get(domain.getTld()).getRenewGracePeriodLength()))
.setReason(Reason.RENEW)
.setParent(getOnlyHistoryEntryOfType(domain, Type.DOMAIN_DELETE))
@@ -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 =
@@ -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());
}
@@ -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
@@ -17,9 +17,11 @@ package google.registry.model.billing;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.domain.token.AllocationToken.TokenType.UNLIMITED_USE;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.testing.DatastoreHelper.createTld;
import static google.registry.testing.DatastoreHelper.persistActiveDomain;
import static google.registry.testing.DatastoreHelper.persistResource;
import static google.registry.testing.SqlHelper.saveRegistrar;
import static google.registry.util.DateTimeUtils.END_OF_TIME;
import static org.joda.money.CurrencyUnit.USD;
import static org.joda.time.DateTimeZone.UTC;
@@ -37,19 +39,25 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.domain.token.AllocationToken.TokenStatus;
import google.registry.model.reporting.HistoryEntry;
import google.registry.persistence.VKey;
import google.registry.util.DateTimeUtils;
import org.joda.money.Money;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link BillingEvent}. */
public class BillingEventTest extends EntityTestCase {
private final DateTime now = DateTime.now(UTC);
public BillingEventTest() {
super(true);
}
HistoryEntry historyEntry;
HistoryEntry historyEntry2;
DomainBase domain;
BillingEvent.OneTime sqlOneTime;
BillingEvent.OneTime oneTime;
BillingEvent.OneTime oneTimeSynthetic;
BillingEvent.Recurring recurring;
@@ -57,7 +65,7 @@ public class BillingEventTest extends EntityTestCase {
BillingEvent.Cancellation cancellationRecurring;
BillingEvent.Modification modification;
@Before
@BeforeEach
public void setUp() {
createTld("tld");
domain = persistActiveDomain("foo.tld");
@@ -97,7 +105,18 @@ public class BillingEventTest extends EntityTestCase {
.setCost(Money.of(USD, 1))
.setEventTime(now)
.setBillingTime(now.plusDays(5))
.setAllocationToken(Key.create(allocationToken))));
.setAllocationToken(allocationToken.createVKey())));
sqlOneTime =
oneTime
.asBuilder()
.setDomainRepoId(domain.getRepoId())
.setDomainHistoryRevisionId(1L)
.setAllocationToken(
VKey.create(
AllocationToken.class, allocationToken.getToken(), Key.create(allocationToken)))
.build();
recurring =
persistResource(
commonInit(
@@ -107,31 +126,41 @@ public class BillingEventTest extends EntityTestCase {
.setReason(Reason.RENEW)
.setEventTime(now.plusYears(1))
.setRecurrenceEndTime(END_OF_TIME)));
oneTimeSynthetic = persistResource(commonInit(
new BillingEvent.OneTime.Builder()
.setParent(historyEntry)
.setReason(Reason.CREATE)
.setFlags(ImmutableSet.of(BillingEvent.Flag.ANCHOR_TENANT, BillingEvent.Flag.SYNTHETIC))
.setSyntheticCreationTime(now.plusDays(10))
.setCancellationMatchingBillingEvent(Key.create(recurring))
.setPeriodYears(2)
.setCost(Money.of(USD, 1))
.setEventTime(now)
.setBillingTime(now.plusDays(5))));
cancellationOneTime = persistResource(commonInit(
new BillingEvent.Cancellation.Builder()
.setParent(historyEntry2)
.setReason(Reason.CREATE)
.setEventTime(now.plusDays(1))
.setBillingTime(now.plusDays(5))
.setOneTimeEventKey(Key.create(oneTime))));
cancellationRecurring = persistResource(commonInit(
new BillingEvent.Cancellation.Builder()
.setParent(historyEntry2)
.setReason(Reason.RENEW)
.setEventTime(now.plusDays(1))
.setBillingTime(now.plusYears(1).plusDays(45))
.setRecurringEventKey(Key.create(recurring))));
oneTimeSynthetic =
persistResource(
commonInit(
new BillingEvent.OneTime.Builder()
.setParent(historyEntry)
.setReason(Reason.CREATE)
.setFlags(
ImmutableSet.of(
BillingEvent.Flag.ANCHOR_TENANT, BillingEvent.Flag.SYNTHETIC))
.setSyntheticCreationTime(now.plusDays(10))
.setCancellationMatchingBillingEvent(recurring.createVKey())
.setPeriodYears(2)
.setCost(Money.of(USD, 1))
.setEventTime(now)
.setBillingTime(now.plusDays(5))));
cancellationOneTime =
persistResource(
commonInit(
new BillingEvent.Cancellation.Builder()
.setParent(historyEntry2)
.setReason(Reason.CREATE)
.setEventTime(now.plusDays(1))
.setBillingTime(now.plusDays(5))
.setOneTimeEventKey(oneTime.createVKey())));
cancellationRecurring =
persistResource(
commonInit(
new BillingEvent.Cancellation.Builder()
.setParent(historyEntry2)
.setReason(Reason.RENEW)
.setEventTime(now.plusDays(1))
.setBillingTime(now.plusYears(1).plusDays(45))
.setRecurringEventKey(recurring.createVKey())));
modification = persistResource(commonInit(
new BillingEvent.Modification.Builder()
.setParent(historyEntry2)
@@ -149,6 +178,83 @@ public class BillingEventTest extends EntityTestCase {
.build();
}
private void saveNewBillingEvent(BillingEvent billingEvent) {
billingEvent.id = null;
jpaTm().transact(() -> jpaTm().saveNew(billingEvent));
}
@Test
public void testCloudSqlPersistence_OneTime() {
saveRegistrar("a registrar");
saveNewBillingEvent(sqlOneTime);
BillingEvent.OneTime persisted =
jpaTm()
.transact(
() -> jpaTm().load(VKey.createSql(BillingEvent.OneTime.class, sqlOneTime.id)));
// TODO(shicong): Remove these fixes after the entities are fully compatible
BillingEvent.OneTime fixed =
persisted
.asBuilder()
.setParent(sqlOneTime.getParentKey())
.setAllocationToken(sqlOneTime.getAllocationToken().get())
.build();
assertThat(fixed).isEqualTo(sqlOneTime);
}
@Test
public void testCloudSqlPersistence_Cancellation() {
saveRegistrar("a registrar");
saveNewBillingEvent(sqlOneTime);
VKey<BillingEvent.OneTime> sqlVKey = VKey.createSql(BillingEvent.OneTime.class, sqlOneTime.id);
BillingEvent sqlCancellationOneTime =
cancellationOneTime
.asBuilder()
.setOneTimeEventKey(sqlVKey)
.setDomainRepoId(domain.getRepoId())
.setDomainHistoryRevisionId(1L)
.build();
saveNewBillingEvent(sqlCancellationOneTime);
BillingEvent.Cancellation persisted =
jpaTm()
.transact(
() ->
jpaTm()
.load(
VKey.createSql(
BillingEvent.Cancellation.class, sqlCancellationOneTime.id)));
// TODO(shicong): Remove these fixes after the entities are fully compatible
BillingEvent.Cancellation fixed =
persisted
.asBuilder()
.setParent(sqlCancellationOneTime.getParentKey())
.setOneTimeEventKey(sqlVKey)
.build();
assertThat(fixed).isEqualTo(sqlCancellationOneTime);
}
@Test
public void testCloudSqlPersistence_Recurring() {
saveRegistrar("a registrar");
BillingEvent.Recurring sqlRecurring =
recurring
.asBuilder()
.setDomainRepoId(domain.getRepoId())
.setDomainHistoryRevisionId(1L)
.build();
saveNewBillingEvent(sqlRecurring);
BillingEvent.Recurring persisted =
jpaTm()
.transact(
() -> jpaTm().load(VKey.createSql(BillingEvent.Recurring.class, sqlRecurring.id)));
// TODO(shicong): Remove these fixes after the entities are fully compatible
BillingEvent.Recurring fixed =
persisted.asBuilder().setParent(sqlRecurring.getParentKey()).build();
assertThat(fixed).isEqualTo(sqlRecurring);
}
@Test
public void testPersistence() {
assertThat(ofy().load().entity(oneTime).now()).isEqualTo(oneTime);
@@ -183,8 +289,13 @@ public class BillingEventTest extends EntityTestCase {
@Test
public void testCancellationMatching() {
Key<?> recurringKey = ofy().load().entity(oneTimeSynthetic).now()
.getCancellationMatchingBillingEvent();
Key<?> recurringKey =
ofy()
.load()
.entity(oneTimeSynthetic)
.now()
.getCancellationMatchingBillingEvent()
.getOfyKey();
assertThat(ofy().load().key(recurringKey).now()).isEqualTo(recurring);
}
@@ -219,7 +330,7 @@ public class BillingEventTest extends EntityTestCase {
oneTime
.asBuilder()
.setFlags(ImmutableSet.of(BillingEvent.Flag.SYNTHETIC))
.setCancellationMatchingBillingEvent(Key.create(recurring))
.setCancellationMatchingBillingEvent(recurring.createVKey())
.build());
assertThat(thrown)
.hasMessageThat()
@@ -263,7 +374,7 @@ public class BillingEventTest extends EntityTestCase {
() ->
oneTime
.asBuilder()
.setCancellationMatchingBillingEvent(Key.create(recurring))
.setCancellationMatchingBillingEvent(recurring.createVKey())
.build());
assertThat(thrown)
.hasMessageThat()
@@ -334,8 +445,8 @@ public class BillingEventTest extends EntityTestCase {
() ->
cancellationOneTime
.asBuilder()
.setOneTimeEventKey(Key.create(oneTime))
.setRecurringEventKey(Key.create(recurring))
.setOneTimeEventKey(oneTime.createVKey())
.setRecurringEventKey(recurring.createVKey())
.build());
assertThat(thrown).hasMessageThat().contains("exactly one billing event");
}
@@ -119,7 +119,8 @@ public class DomainBaseSqlTest {
.transact(
() -> {
// Persist the contacts. Note that these need to be persisted before the domain
// otherwise we get a foreign key constraint error.
// otherwise we get a foreign key constraint error. If we ever decide to defer the
// relevant foreign key checks to commit time, then the order would not matter.
jpaTm().saveNew(contact);
jpaTm().saveNew(contact2);
@@ -127,7 +128,8 @@ public class DomainBaseSqlTest {
jpaTm().saveNew(domain);
// Persist the host. This does _not_ need to be persisted before the domain,
// presumably because its relationship is stored in a join table.
// because only the row in the join table (DomainHost) is subject to foreign key
// constraints, and Hibernate knows to insert it after domain and host.
jpaTm().saveNew(host);
});
@@ -45,7 +45,7 @@ public class JpaEntityCoverage extends ExternalResource {
private static final ImmutableSet<Class> ALL_JPA_ENTITIES =
PersistenceXmlUtility.getManagedClasses().stream()
.filter(e -> !IGNORE_ENTITIES.contains(e.getSimpleName()))
.filter(e -> e.getAnnotation(Entity.class) != null)
.filter(e -> e.isAnnotationPresent(Entity.class))
.collect(ImmutableSet.toImmutableSet());
private static final Set<Class> allCoveredJpaEntities = Sets.newHashSet();
// Map of test class name to boolean flag indicating if it tests any JPA entities.
@@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.hibernate.cfg.Environment;
import org.joda.time.DateTime;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
@@ -180,6 +181,17 @@ public class JpaTestRules {
return this;
}
/**
* Enables logging of SQL statements.
*
* <p>SQL logging is very noisy and disabled by default. This method maybe useful when
* troubleshooting a specific test.
*/
public Builder withSqlLogging() {
withProperty(Environment.SHOW_SQL, "true");
return this;
}
/** Builds a {@link JpaIntegrationTestRule} instance. */
public JpaIntegrationTestRule buildIntegrationTestRule() {
return new JpaIntegrationTestRule(
@@ -0,0 +1,57 @@
// 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.transaction;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static java.nio.charset.StandardCharsets.UTF_8;
import google.registry.persistence.transaction.JpaTestRules.JpaUnitTestRule;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit test for {@link JpaTestRules.Builder#withSqlLogging()}. */
public class JpaTestRulesSqlLoggingTest {
// Entity under test: configured to log SQL statements to Stdout.
@RegisterExtension
JpaUnitTestRule jpaRule = new JpaTestRules.Builder().withSqlLogging().buildUnitTestRule();
private PrintStream orgStdout;
private ByteArrayOutputStream stdoutBuffer;
@BeforeEach
public void beforeEach() {
orgStdout = System.out;
System.setOut(new PrintStream(stdoutBuffer = new ByteArrayOutputStream()));
}
@AfterEach
public void afterEach() {
System.setOut(orgStdout);
}
@Test
void sqlLog_displayed() throws UnsupportedEncodingException {
jpaTm()
.transact(() -> jpaTm().getEntityManager().createNativeQuery("select 1").getSingleResult());
assertThat(stdoutBuffer.toString(UTF_8.name())).contains("select 1");
}
}
@@ -41,6 +41,8 @@ import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
@@ -146,13 +148,15 @@ abstract class JpaTransactionManagerRule extends ExternalResource {
ImmutableMap properties = PersistenceModule.providesDefaultDatabaseConfigs();
if (!userProperties.isEmpty()) {
// If there are user properties, create a new properties object with these added.
ImmutableMap.Builder builder = properties.builder();
builder.putAll(userProperties);
// Forbid Hibernate push to stay consistent with flyway-based schema management.
builder.put(Environment.HBM2DDL_AUTO, "none");
builder.put(Environment.SHOW_SQL, "true");
properties = builder.build();
Map<String, String> mergedProperties = Maps.newHashMap();
mergedProperties.putAll(properties);
mergedProperties.putAll(userProperties);
properties = ImmutableMap.copyOf(mergedProperties);
}
// Forbid Hibernate push to stay consistent with flyway-based schema management.
checkState(
Objects.equals(properties.get(Environment.HBM2DDL_AUTO), "none"),
"The HBM2DDL_AUTO property must be 'none'.");
assertReasonableNumDbConnections();
emf =
createEntityManagerFactory(
@@ -16,6 +16,7 @@ package google.registry.schema.integration;
import static com.google.common.truth.Truth.assert_;
import google.registry.model.billing.BillingEventTest;
import google.registry.model.contact.ContactResourceTest;
import google.registry.model.domain.DomainBaseSqlTest;
import google.registry.model.registry.RegistryLockDaoTest;
@@ -68,6 +69,7 @@ import org.junit.runner.RunWith;
@SelectClasses({
// BeforeSuiteTest must be the first entry. See class javadoc for details.
BeforeSuiteTest.class,
BillingEventTest.class,
ClaimsListDaoTest.class,
ContactResourceTest.class,
CursorDaoTest.class,
@@ -19,7 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.io.Resources;
import google.registry.testing.DatastoreEntityExtension;
import google.registry.tools.LevelDbFileBuilder.Property;
import google.registry.tools.EntityWrapper.Property;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
@@ -71,30 +71,38 @@ public class CompareDbBackupsTest {
// Create two directories corresponding to data dumps.
File dump1 = tempFs.newFolder("dump1");
LevelDbFileBuilder builder = new LevelDbFileBuilder(new File(dump1, "output-data1"));
builder.addEntityProto(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
builder.addEntityProto(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L));
builder.addEntity(
EntityWrapper.from(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L))
.getEntity());
builder.addEntity(
EntityWrapper.from(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L))
.getEntity());
builder.build();
File dump2 = tempFs.newFolder("dump2");
builder = new LevelDbFileBuilder(new File(dump2, "output-data2"));
builder.addEntityProto(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L));
builder.addEntityProto(
BASE_ID + 2,
Property.create("blutzy", 100L),
Property.create("fishey", 200L),
Property.create("strutz", 300L));
builder.addEntity(
EntityWrapper.from(
BASE_ID + 1,
Property.create("moxey", 100L),
Property.create("minney", 200L),
Property.create("motz", 300L))
.getEntity());
builder.addEntity(
EntityWrapper.from(
BASE_ID + 2,
Property.create("blutzy", 100L),
Property.create("fishey", 200L),
Property.create("strutz", 300L))
.getEntity());
builder.build();
CompareDbBackups.main(new String[] {dump1.getCanonicalPath(), dump2.getCanonicalPath()});
@@ -28,7 +28,7 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class ComparableEntityTest {
public final class EntityWrapperTest {
private static final String TEST_ENTITY_KIND = "TestEntity";
private static final int ARBITRARY_KEY_ID = 1001;
@@ -63,13 +63,13 @@ public final class ComparableEntityTest {
Entity e2 = EntityTranslator.createFromPb(proto2);
// Ensure that we have a normalized representation.
ComparableEntity ce1 = new ComparableEntity(e1);
ComparableEntity ce2 = new ComparableEntity(e2);
EntityWrapper ce1 = new EntityWrapper(e1);
EntityWrapper ce2 = new EntityWrapper(e2);
assertThat(ce1).isEqualTo(ce2);
assertThat(ce1.hashCode()).isEqualTo(ce2.hashCode());
// Ensure that the original entity is equal.
assertThat(new ComparableEntity(entity)).isEqualTo(ce1);
assertThat(new EntityWrapper(entity)).isEqualTo(ce1);
}
@Test
@@ -90,8 +90,8 @@ public final class ComparableEntityTest {
Entity e1 = EntityTranslator.createFromPb(proto1);
Entity e2 = EntityTranslator.createFromPb(proto2);
ComparableEntity ce1 = new ComparableEntity(e1);
ComparableEntity ce2 = new ComparableEntity(e2);
EntityWrapper ce1 = new EntityWrapper(e1);
EntityWrapper ce2 = new EntityWrapper(e2);
assertThat(e1).isEqualTo(e2); // The keys should still be the same.
assertThat(ce1).isNotEqualTo(ce2);
assertThat(ce1.hashCode()).isNotEqualTo(ce2.hashCode());
@@ -108,15 +108,15 @@ public final class ComparableEntityTest {
Entity e1 = EntityTranslator.createFromPb(proto1);
Entity e2 = EntityTranslator.createFromPb(proto2);
ComparableEntity ce1 = new ComparableEntity(e1);
ComparableEntity ce2 = new ComparableEntity(e2);
EntityWrapper ce1 = new EntityWrapper(e1);
EntityWrapper ce2 = new EntityWrapper(e2);
assertThat(ce1).isNotEqualTo(ce2);
assertThat(ce1.hashCode()).isNotEqualTo(ce2.hashCode());
}
@Test
public void testComparisonAgainstNonComparableEntities() {
ComparableEntity ce = new ComparableEntity(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID));
EntityWrapper ce = new EntityWrapper(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID));
// Note: this has to be "isNotEqualTo()" and not isNotNull() because we want to test the
// equals() method and isNotNull() just checks for "ce != null".
assertThat(ce).isNotEqualTo(null);
@@ -19,7 +19,6 @@ import static google.registry.tools.LevelDbLogReader.HEADER_SIZE;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.auto.value.AutoValue;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.tools.LevelDbLogReader.ChunkType;
import java.io.File;
@@ -28,30 +27,19 @@ import java.io.FileOutputStream;
import java.io.IOException;
/** Utility class for building a leveldb logfile. */
final class LevelDbFileBuilder {
private static final String TEST_ENTITY_KIND = "TestEntity";
public final class LevelDbFileBuilder {
private final FileOutputStream out;
private byte[] currentBlock = new byte[BLOCK_SIZE];
// Write position in the current block.
private int currentPos = 0;
LevelDbFileBuilder(File file) throws FileNotFoundException {
public LevelDbFileBuilder(File file) throws FileNotFoundException {
out = new FileOutputStream(file);
}
/**
* Adds a record containing a new entity protobuf to the file.
*
* <p>Returns the ComparableEntity object rather than "this" so that we can check for the presence
* of the entity in the result set.
*/
ComparableEntity addEntityProto(int id, Property... properties) throws IOException {
Entity entity = new Entity(TEST_ENTITY_KIND, id);
for (Property prop : properties) {
entity.setProperty(prop.name(), prop.value());
}
/** Adds an {@link Entity Datastore Entity object} to the leveldb log file. */
LevelDbFileBuilder addEntity(Entity entity) throws IOException {
EntityProto proto = EntityTranslator.convertToPb(entity);
byte[] protoBytes = proto.toByteArray();
if (protoBytes.length > BLOCK_SIZE - (currentPos + HEADER_SIZE)) {
@@ -61,7 +49,7 @@ final class LevelDbFileBuilder {
}
currentPos = LevelDbUtil.addRecord(currentBlock, currentPos, ChunkType.FULL, protoBytes);
return new ComparableEntity(entity);
return this;
}
/** Writes all remaining data and closes the block. */
@@ -69,15 +57,4 @@ final class LevelDbFileBuilder {
out.write(currentBlock);
out.close();
}
@AutoValue
abstract static class Property {
static Property create(String name, Object value) {
return new AutoValue_LevelDbFileBuilder_Property(name, value);
}
abstract String name();
abstract Object value();
}
}
@@ -15,15 +15,18 @@
package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityTranslator;
import com.google.common.collect.ImmutableList;
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
import google.registry.model.contact.ContactResource;
import google.registry.testing.AppEngineRule;
import google.registry.tools.LevelDbFileBuilder.Property;
import google.registry.testing.DatastoreHelper;
import google.registry.tools.EntityWrapper.Property;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
@@ -46,22 +49,18 @@ public class LevelDbFileBuilderTest {
File subdir = tempFs.newFolder("folder");
File logFile = new File(subdir, "testfile");
LevelDbFileBuilder builder = new LevelDbFileBuilder(logFile);
ComparableEntity entity =
builder.addEntityProto(
EntityWrapper entity =
EntityWrapper.from(
BASE_ID, Property.create("first", 100L), Property.create("second", 200L));
builder.addEntity(entity.getEntity());
builder.build();
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new FileInputStream(logFile));
ImmutableList<byte[]> records = reader.getRecords();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1);
// Reconstitute an entity, make sure that what we've got is the same as what we started with.
EntityProto proto = new EntityProto();
proto.parseFrom(records.get(0));
Entity materializedEntity = EntityTranslator.createFromPb(proto);
assertThat(new ComparableEntity(materializedEntity)).isEqualTo(entity);
Entity materializedEntity = rawRecordToEntity(records.get(0));
assertThat(new EntityWrapper(materializedEntity)).isEqualTo(entity);
}
@Test
@@ -72,28 +71,50 @@ public class LevelDbFileBuilderTest {
// Generate enough records to cross a block boundary. These records end up being around 80
// bytes, so 1000 works.
ImmutableList.Builder<ComparableEntity> originalEntitiesBuilder = new ImmutableList.Builder<>();
ImmutableList.Builder<EntityWrapper> originalEntitiesBuilder = new ImmutableList.Builder<>();
for (int i = 0; i < 1000; ++i) {
ComparableEntity entity =
builder.addEntityProto(
EntityWrapper entity =
EntityWrapper.from(
BASE_ID + i, Property.create("first", 100L), Property.create("second", 200L));
builder.addEntity(entity.getEntity());
originalEntitiesBuilder.add(entity);
}
builder.build();
ImmutableList<ComparableEntity> originalEntities = originalEntitiesBuilder.build();
ImmutableList<EntityWrapper> originalEntities = originalEntitiesBuilder.build();
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new FileInputStream(logFile));
ImmutableList<byte[]> records = reader.getRecords();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1000);
int index = 0;
for (byte[] record : records) {
EntityProto proto = new EntityProto();
proto.parseFrom(record);
Entity materializedEntity = EntityTranslator.createFromPb(proto);
assertThat(new ComparableEntity(materializedEntity)).isEqualTo(originalEntities.get(index));
Entity materializedEntity = rawRecordToEntity(record);
assertThat(new EntityWrapper(materializedEntity)).isEqualTo(originalEntities.get(index));
++index;
}
}
@Test
public void testOfyEntityWrite() throws Exception {
File subdir = tempFs.newFolder("folder");
File logFile = new File(subdir, "testfile");
LevelDbFileBuilder builder = new LevelDbFileBuilder(logFile);
ContactResource contact = DatastoreHelper.newContactResource("contact");
builder.addEntity(tm().transact(() -> ofy().save().toEntity(contact)));
builder.build();
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(logFile.getPath()));
assertThat(records).hasSize(1);
ContactResource ofyEntity = rawRecordToOfyEntity(records.get(0), ContactResource.class);
assertThat(ofyEntity.getContactId()).isEqualTo(contact.getContactId());
}
private static Entity rawRecordToEntity(byte[] record) {
EntityProto proto = new EntityProto();
proto.parseFrom(record);
return EntityTranslator.createFromPb(proto);
}
private static <T> T rawRecordToOfyEntity(byte[] record, Class<T> expectedType) {
return expectedType.cast(ofy().load().fromEntity(rawRecordToEntity(record)));
}
}
@@ -17,6 +17,10 @@ package google.registry.tools;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.tools.LevelDbUtil.MAX_RECORD;
import static google.registry.tools.LevelDbUtil.addRecord;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
@@ -24,12 +28,9 @@ import google.registry.tools.LevelDbLogReader.ChunkType;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.junit.jupiter.api.Test;
/** LevelDbLogReader tests. */
@RunWith(JUnit4.class)
/** Unit tests of {@link LevelDbLogReader}. */
public final class LevelDbLogReaderTest {
// Size of the test record. Any value < 256 will do.
@@ -54,28 +55,23 @@ public final class LevelDbLogReaderTest {
@Test
public void testSimpleBlock() throws IOException {
TestBlock block = makeBlockOfRepeatingBytes(0);
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new ByteArrayInputStream(block.data));
ImmutableList<byte[]> records = reader.getRecords();
assertThat(records).hasSize(block.recordCount);
assertThat(readIncrementally(block.data)).hasSize(block.recordCount);
}
@Test
public void testLargeRecord() throws IOException {
byte[] block = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block, 0, ChunkType.FIRST, MAX_RECORD, (byte) 1);
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(new ByteArrayInputStream(block));
assertThat(reader.getRecords()).isEmpty();
byte[] block0 = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block0, 0, ChunkType.FIRST, MAX_RECORD, (byte) 1);
assertThat(readIncrementally(block0)).isEmpty();
addRecord(block, 0, ChunkType.MIDDLE, MAX_RECORD, (byte) 2);
reader.readFrom(new ByteArrayInputStream(block));
assertThat(reader.getRecords()).isEmpty();
byte[] block1 = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block1, 0, ChunkType.MIDDLE, MAX_RECORD, (byte) 2);
assertThat(readIncrementally(block0, block1)).isEmpty();
addRecord(block, 0, ChunkType.LAST, MAX_RECORD, (byte) 3);
reader.readFrom(new ByteArrayInputStream(block));
byte[] block2 = new byte[LevelDbLogReader.BLOCK_SIZE];
addRecord(block2, 0, ChunkType.LAST, MAX_RECORD, (byte) 3);
List<byte[]> records = reader.getRecords();
List<byte[]> records = readIncrementally(block0, block1, block2);
assertThat(records).hasSize(1);
byte[] record = records.get(0);
@@ -95,11 +91,24 @@ public final class LevelDbLogReaderTest {
public void readFromMultiBlockStream() throws IOException {
TestBlock block0 = makeBlockOfRepeatingBytes(0);
TestBlock block1 = makeBlockOfRepeatingBytes(138);
ByteArrayInputStream source = new ByteArrayInputStream(Bytes.concat(block0.data, block1.data));
assertThat(readIncrementally(block0.data, block1.data))
.hasSize(block0.recordCount + block1.recordCount);
}
LevelDbLogReader reader = new LevelDbLogReader();
reader.readFrom(source);
assertThat(reader.getRecords()).hasSize(block0.recordCount + block1.recordCount);
@Test
void read_noData() throws IOException {
assertThat(readIncrementally(new byte[0])).isEmpty();
}
@Test
void read_failBadFirstBlock() {
assertThrows(IllegalStateException.class, () -> readIncrementally(new byte[1]));
}
@Test
void read_failBadTrailingBlock() {
TestBlock block = makeBlockOfRepeatingBytes(0);
assertThrows(IllegalStateException.class, () -> readIncrementally(block.data, new byte[2]));
}
@Test
@@ -112,6 +121,14 @@ public final class LevelDbLogReaderTest {
assertThat(ChunkType.fromCode(ChunkType.LAST.getCode())).isEqualTo(ChunkType.LAST);
}
@SafeVarargs
private static ImmutableList<byte[]> readIncrementally(byte[]... blocks) throws IOException {
ByteArrayInputStream source = spy(new ByteArrayInputStream(Bytes.concat(blocks)));
ImmutableList<byte[]> records = ImmutableList.copyOf(LevelDbLogReader.from(source));
verify(source, times(1)).close();
return records;
}
/** Aggregates the bytes of a test block with the record count. */
private static final class TestBlock {
final byte[] data;
@@ -18,7 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableSet;
import google.registry.testing.AppEngineRule;
import google.registry.tools.LevelDbFileBuilder.Property;
import google.registry.tools.EntityWrapper.Property;
import java.io.File;
import java.io.IOException;
import org.junit.Rule;
@@ -44,39 +44,44 @@ public class RecordAccumulatorTest {
// Note that we need to specify property values as "Long" for property comparisons to work
// correctly because that's how they are deserialized from protos.
ComparableEntity e1 =
builder.addEntityProto(
EntityWrapper e1 =
EntityWrapper.from(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
ComparableEntity e2 =
builder.addEntityProto(
builder.addEntity(e1.getEntity());
EntityWrapper e2 =
EntityWrapper.from(
BASE_ID + 1,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
builder.addEntity(e2.getEntity());
builder.build();
builder = new LevelDbFileBuilder(new File(subdir, "data2"));
// Duplicate of the record in the other file.
builder.addEntityProto(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L));
builder.addEntity(
EntityWrapper.from(
BASE_ID,
Property.create("eeny", 100L),
Property.create("meeny", 200L),
Property.create("miney", 300L))
.getEntity());
ComparableEntity e3 =
builder.addEntityProto(
EntityWrapper e3 =
EntityWrapper.from(
BASE_ID + 2,
Property.create("moxy", 100L),
Property.create("fruvis", 200L),
Property.create("cortex", 300L));
builder.addEntity(e3.getEntity());
builder.build();
ImmutableSet<ComparableEntity> entities =
new RecordAccumulator().readDirectory(subdir, any -> true).getComparableEntitySet();
ImmutableSet<EntityWrapper> entities =
RecordAccumulator.readDirectory(subdir, any -> true).getEntityWrapperSet();
assertThat(entities).containsExactly(e1, e2, e3);
}
}
@@ -5,11 +5,11 @@ class google.registry.model.UpdateAutoTimestamp {
org.joda.time.DateTime timestamp;
}
class google.registry.model.billing.BillingEvent$Cancellation {
@Id long id;
@Id java.lang.Long id;
@Parent com.googlecode.objectify.Key<google.registry.model.reporting.HistoryEntry> parent;
com.googlecode.objectify.Key<google.registry.model.billing.BillingEvent$OneTime> refOneTime;
com.googlecode.objectify.Key<google.registry.model.billing.BillingEvent$Recurring> refRecurring;
google.registry.model.billing.BillingEvent$Reason reason;
google.registry.persistence.VKey<google.registry.model.billing.BillingEvent$OneTime> refOneTime;
google.registry.persistence.VKey<google.registry.model.billing.BillingEvent$Recurring> refRecurring;
java.lang.String clientId;
java.lang.String targetId;
java.util.Set<google.registry.model.billing.BillingEvent$Flag> flags;
@@ -26,7 +26,7 @@ enum google.registry.model.billing.BillingEvent$Flag {
SYNTHETIC;
}
class google.registry.model.billing.BillingEvent$Modification {
@Id long id;
@Id java.lang.Long id;
@Parent com.googlecode.objectify.Key<google.registry.model.reporting.HistoryEntry> parent;
com.googlecode.objectify.Key<google.registry.model.billing.BillingEvent$OneTime> eventRef;
google.registry.model.billing.BillingEvent$Reason reason;
@@ -38,11 +38,11 @@ class google.registry.model.billing.BillingEvent$Modification {
org.joda.time.DateTime eventTime;
}
class google.registry.model.billing.BillingEvent$OneTime {
@Id long id;
@Id java.lang.Long id;
@Parent com.googlecode.objectify.Key<google.registry.model.reporting.HistoryEntry> parent;
com.googlecode.objectify.Key<? extends google.registry.model.billing.BillingEvent> cancellationMatchingBillingEvent;
com.googlecode.objectify.Key<google.registry.model.domain.token.AllocationToken> allocationToken;
google.registry.model.billing.BillingEvent$Reason reason;
google.registry.persistence.VKey<? extends google.registry.model.billing.BillingEvent> cancellationMatchingBillingEvent;
google.registry.persistence.VKey<google.registry.model.domain.token.AllocationToken> allocationToken;
java.lang.Integer periodYears;
java.lang.String clientId;
java.lang.String targetId;
@@ -62,7 +62,7 @@ enum google.registry.model.billing.BillingEvent$Reason {
TRANSFER;
}
class google.registry.model.billing.BillingEvent$Recurring {
@Id long id;
@Id java.lang.Long id;
@Parent com.googlecode.objectify.Key<google.registry.model.reporting.HistoryEntry> parent;
google.registry.model.billing.BillingEvent$Reason reason;
google.registry.model.common.TimeOfYear recurrenceTimeOfYear;
@@ -0,0 +1,104 @@
-- 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.
create table "BillingCancellation" (
billing_cancellation_id bigserial not null,
client_id text not null,
domain_history_revision_id int8 not null,
domain_repo_id text not null,
event_time timestamptz not null,
flags text[],
reason text not null,
domain_name text not null,
billing_time timestamptz,
billing_event_id int8,
billing_recurrence_id int8,
primary key (billing_cancellation_id)
);
create table "BillingEvent" (
billing_event_id bigserial not null,
client_id text not null,
domain_history_revision_id int8 not null,
domain_repo_id text not null,
event_time timestamptz not null,
flags text[],
reason text not null,
domain_name text not null,
allocation_token_id text,
billing_time timestamptz,
cancellation_matching_billing_recurrence_id int8,
cost_amount numeric(19, 2),
cost_currency text,
period_years int4,
synthetic_creation_time timestamptz,
primary key (billing_event_id)
);
create table "BillingRecurrence" (
billing_recurrence_id bigserial not null,
client_id text not null,
domain_history_revision_id int8 not null,
domain_repo_id text not null,
event_time timestamptz not null,
flags text[],
reason text not null,
domain_name text not null,
recurrence_end_time timestamptz,
recurrence_time_of_year text,
primary key (billing_recurrence_id)
);
create index IDXeokttmxtpq2hohcioe5t2242b on "BillingCancellation" (client_id);
create index IDX2exdfbx6oiiwnhr8j6gjpqt2j on "BillingCancellation" (event_time);
create index IDXqa3g92jc17e8dtiaviy4fet4x on "BillingCancellation" (billing_time);
create index IDX73l103vc5900ig3p4odf0cngt on "BillingEvent" (client_id);
create index IDX5yfbr88439pxw0v3j86c74fp8 on "BillingEvent" (event_time);
create index IDX6py6ocrab0ivr76srcd2okpnq on "BillingEvent" (billing_time);
create index IDXplxf9v56p0wg8ws6qsvd082hk on "BillingEvent" (synthetic_creation_time);
create index IDXhmv411mdqo5ibn4vy7ykxpmlv on "BillingEvent" (allocation_token_id);
create index IDXn898pb9mwcg359cdwvolb11ck on "BillingRecurrence" (client_id);
create index IDX6syykou4nkc7hqa5p8r92cpch on "BillingRecurrence" (event_time);
create index IDXp3usbtvk0v1m14i5tdp4xnxgc on "BillingRecurrence" (recurrence_end_time);
create index IDXjny8wuot75b5e6p38r47wdawu on "BillingRecurrence" (recurrence_time_of_year);
alter table if exists "BillingEvent"
add constraint fk_billing_event_client_id
foreign key (client_id)
references "Registrar";
alter table if exists "BillingEvent"
add constraint fk_billing_event_cancellation_matching_billing_recurrence_id
foreign key (cancellation_matching_billing_recurrence_id)
references "BillingRecurrence";
alter table if exists "BillingCancellation"
add constraint fk_billing_cancellation_client_id
foreign key (client_id)
references "Registrar";
alter table if exists "BillingCancellation"
add constraint fk_billing_cancellation_billing_event_id
foreign key (billing_event_id)
references "BillingEvent";
alter table if exists "BillingCancellation"
add constraint fk_billing_cancellation_billing_recurrence_id
foreign key (billing_recurrence_id)
references "BillingRecurrence";
alter table if exists "BillingRecurrence"
add constraint fk_billing_recurrence_client_id
foreign key (client_id)
references "Registrar";
@@ -12,6 +12,54 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
create table "BillingCancellation" (
billing_cancellation_id bigserial not null,
client_id text not null,
domain_history_revision_id int8 not null,
domain_repo_id text not null,
event_time timestamptz not null,
flags text[],
reason text not null,
domain_name text not null,
billing_time timestamptz,
billing_event_id int8,
billing_recurrence_id int8,
primary key (billing_cancellation_id)
);
create table "BillingEvent" (
billing_event_id bigserial not null,
client_id text not null,
domain_history_revision_id int8 not null,
domain_repo_id text not null,
event_time timestamptz not null,
flags text[],
reason text not null,
domain_name text not null,
allocation_token_id text,
billing_time timestamptz,
cancellation_matching_billing_recurrence_id int8,
cost_amount numeric(19, 2),
cost_currency text,
period_years int4,
synthetic_creation_time timestamptz,
primary key (billing_event_id)
);
create table "BillingRecurrence" (
billing_recurrence_id bigserial not null,
client_id text not null,
domain_history_revision_id int8 not null,
domain_repo_id text not null,
event_time timestamptz not null,
flags text[],
reason text not null,
domain_name text not null,
recurrence_end_time timestamptz,
recurrence_time_of_year text,
primary key (billing_recurrence_id)
);
create table "ClaimsEntry" (
revision_id int8 not null,
claim_key text not null,
@@ -280,6 +328,18 @@
should_publish boolean not null,
primary key (revision_id)
);
create index IDXeokttmxtpq2hohcioe5t2242b on "BillingCancellation" (client_id);
create index IDX2exdfbx6oiiwnhr8j6gjpqt2j on "BillingCancellation" (event_time);
create index IDXqa3g92jc17e8dtiaviy4fet4x on "BillingCancellation" (billing_time);
create index IDX73l103vc5900ig3p4odf0cngt on "BillingEvent" (client_id);
create index IDX5yfbr88439pxw0v3j86c74fp8 on "BillingEvent" (event_time);
create index IDX6py6ocrab0ivr76srcd2okpnq on "BillingEvent" (billing_time);
create index IDXplxf9v56p0wg8ws6qsvd082hk on "BillingEvent" (synthetic_creation_time);
create index IDXhmv411mdqo5ibn4vy7ykxpmlv on "BillingEvent" (allocation_token_id);
create index IDXn898pb9mwcg359cdwvolb11ck on "BillingRecurrence" (client_id);
create index IDX6syykou4nkc7hqa5p8r92cpch on "BillingRecurrence" (event_time);
create index IDXp3usbtvk0v1m14i5tdp4xnxgc on "BillingRecurrence" (recurrence_end_time);
create index IDXjny8wuot75b5e6p38r47wdawu on "BillingRecurrence" (recurrence_time_of_year);
create index IDX3y752kr9uh4kh6uig54vemx0l on "Contact" (creation_time);
create index IDXbn8t4wp85fgxjl8q4ctlscx55 on "Contact" (current_sponsor_client_id);
create index IDXn1f711wicdnooa2mqb7g1m55o on "Contact" (deletion_time);
@@ -34,6 +34,123 @@ SET default_tablespace = '';
SET default_with_oids = false;
--
-- Name: BillingCancellation; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."BillingCancellation" (
billing_cancellation_id bigint NOT NULL,
client_id text NOT NULL,
domain_history_revision_id bigint NOT NULL,
domain_repo_id text NOT NULL,
event_time timestamp with time zone NOT NULL,
flags text[],
reason text NOT NULL,
domain_name text NOT NULL,
billing_time timestamp with time zone,
billing_event_id bigint,
billing_recurrence_id bigint
);
--
-- Name: BillingCancellation_billing_cancellation_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."BillingCancellation_billing_cancellation_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: BillingCancellation_billing_cancellation_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."BillingCancellation_billing_cancellation_id_seq" OWNED BY public."BillingCancellation".billing_cancellation_id;
--
-- Name: BillingEvent; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."BillingEvent" (
billing_event_id bigint NOT NULL,
client_id text NOT NULL,
domain_history_revision_id bigint NOT NULL,
domain_repo_id text NOT NULL,
event_time timestamp with time zone NOT NULL,
flags text[],
reason text NOT NULL,
domain_name text NOT NULL,
allocation_token_id text,
billing_time timestamp with time zone,
cancellation_matching_billing_recurrence_id bigint,
cost_amount numeric(19,2),
cost_currency text,
period_years integer,
synthetic_creation_time timestamp with time zone
);
--
-- Name: BillingEvent_billing_event_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."BillingEvent_billing_event_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: BillingEvent_billing_event_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."BillingEvent_billing_event_id_seq" OWNED BY public."BillingEvent".billing_event_id;
--
-- Name: BillingRecurrence; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public."BillingRecurrence" (
billing_recurrence_id bigint NOT NULL,
client_id text NOT NULL,
domain_history_revision_id bigint NOT NULL,
domain_repo_id text NOT NULL,
event_time timestamp with time zone NOT NULL,
flags text[],
reason text NOT NULL,
domain_name text NOT NULL,
recurrence_end_time timestamp with time zone,
recurrence_time_of_year text
);
--
-- Name: BillingRecurrence_billing_recurrence_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--
CREATE SEQUENCE public."BillingRecurrence_billing_recurrence_id_seq"
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
--
-- Name: BillingRecurrence_billing_recurrence_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--
ALTER SEQUENCE public."BillingRecurrence_billing_recurrence_id_seq" OWNED BY public."BillingRecurrence".billing_recurrence_id;
--
-- Name: ClaimsEntry; Type: TABLE; Schema: public; Owner: -
--
@@ -427,6 +544,27 @@ CREATE SEQUENCE public."ReservedList_revision_id_seq"
ALTER SEQUENCE public."ReservedList_revision_id_seq" OWNED BY public."ReservedList".revision_id;
--
-- Name: BillingCancellation billing_cancellation_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingCancellation" ALTER COLUMN billing_cancellation_id SET DEFAULT nextval('public."BillingCancellation_billing_cancellation_id_seq"'::regclass);
--
-- Name: BillingEvent billing_event_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingEvent" ALTER COLUMN billing_event_id SET DEFAULT nextval('public."BillingEvent_billing_event_id_seq"'::regclass);
--
-- Name: BillingRecurrence billing_recurrence_id; Type: DEFAULT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingRecurrence" ALTER COLUMN billing_recurrence_id SET DEFAULT nextval('public."BillingRecurrence_billing_recurrence_id_seq"'::regclass);
--
-- Name: ClaimsList revision_id; Type: DEFAULT; Schema: public; Owner: -
--
@@ -455,6 +593,30 @@ ALTER TABLE ONLY public."RegistryLock" ALTER COLUMN revision_id SET DEFAULT next
ALTER TABLE ONLY public."ReservedList" ALTER COLUMN revision_id SET DEFAULT nextval('public."ReservedList_revision_id_seq"'::regclass);
--
-- Name: BillingCancellation BillingCancellation_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingCancellation"
ADD CONSTRAINT "BillingCancellation_pkey" PRIMARY KEY (billing_cancellation_id);
--
-- Name: BillingEvent BillingEvent_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingEvent"
ADD CONSTRAINT "BillingEvent_pkey" PRIMARY KEY (billing_event_id);
--
-- Name: BillingRecurrence BillingRecurrence_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingRecurrence"
ADD CONSTRAINT "BillingRecurrence_pkey" PRIMARY KEY (billing_recurrence_id);
--
-- Name: ClaimsEntry ClaimsEntry_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
@@ -597,6 +759,13 @@ CREATE INDEX idx1p3esngcwwu6hstyua6itn6ff ON public."Contact" USING btree (searc
CREATE INDEX idx1rcgkdd777bpvj0r94sltwd5y ON public."Domain" USING btree (fully_qualified_domain_name);
--
-- Name: idx2exdfbx6oiiwnhr8j6gjpqt2j; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx2exdfbx6oiiwnhr8j6gjpqt2j ON public."BillingCancellation" USING btree (event_time);
--
-- Name: idx3y752kr9uh4kh6uig54vemx0l; Type: INDEX; Schema: public; Owner: -
--
@@ -611,6 +780,34 @@ CREATE INDEX idx3y752kr9uh4kh6uig54vemx0l ON public."Contact" USING btree (creat
CREATE INDEX idx5mnf0wn20tno4b9do88j61klr ON public."Domain" USING btree (deletion_time);
--
-- Name: idx5yfbr88439pxw0v3j86c74fp8; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx5yfbr88439pxw0v3j86c74fp8 ON public."BillingEvent" USING btree (event_time);
--
-- Name: idx6py6ocrab0ivr76srcd2okpnq; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx6py6ocrab0ivr76srcd2okpnq ON public."BillingEvent" USING btree (billing_time);
--
-- Name: idx6syykou4nkc7hqa5p8r92cpch; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx6syykou4nkc7hqa5p8r92cpch ON public."BillingRecurrence" USING btree (event_time);
--
-- Name: idx73l103vc5900ig3p4odf0cngt; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idx73l103vc5900ig3p4odf0cngt ON public."BillingEvent" USING btree (client_id);
--
-- Name: idx8nr0ke9mrrx4ewj6pd2ag4rmr; Type: INDEX; Schema: public; Owner: -
--
@@ -639,6 +836,27 @@ CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING
CREATE INDEX idxbn8t4wp85fgxjl8q4ctlscx55 ON public."Contact" USING btree (current_sponsor_client_id);
--
-- Name: idxeokttmxtpq2hohcioe5t2242b; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxeokttmxtpq2hohcioe5t2242b ON public."BillingCancellation" USING btree (client_id);
--
-- Name: idxhmv411mdqo5ibn4vy7ykxpmlv; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxhmv411mdqo5ibn4vy7ykxpmlv ON public."BillingEvent" USING btree (allocation_token_id);
--
-- Name: idxjny8wuot75b5e6p38r47wdawu; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxjny8wuot75b5e6p38r47wdawu ON public."BillingRecurrence" USING btree (recurrence_time_of_year);
--
-- Name: idxkjt9yaq92876dstimd93hwckh; Type: INDEX; Schema: public; Owner: -
--
@@ -653,6 +871,34 @@ CREATE INDEX idxkjt9yaq92876dstimd93hwckh ON public."Domain" USING btree (curren
CREATE INDEX idxn1f711wicdnooa2mqb7g1m55o ON public."Contact" USING btree (deletion_time);
--
-- Name: idxn898pb9mwcg359cdwvolb11ck; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxn898pb9mwcg359cdwvolb11ck ON public."BillingRecurrence" USING btree (client_id);
--
-- Name: idxp3usbtvk0v1m14i5tdp4xnxgc; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxp3usbtvk0v1m14i5tdp4xnxgc ON public."BillingRecurrence" USING btree (recurrence_end_time);
--
-- Name: idxplxf9v56p0wg8ws6qsvd082hk; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxplxf9v56p0wg8ws6qsvd082hk ON public."BillingEvent" USING btree (synthetic_creation_time);
--
-- Name: idxqa3g92jc17e8dtiaviy4fet4x; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX idxqa3g92jc17e8dtiaviy4fet4x ON public."BillingCancellation" USING btree (billing_time);
--
-- Name: idxrwl38wwkli1j7gkvtywi9jokq; Type: INDEX; Schema: public; Owner: -
--
@@ -751,6 +997,54 @@ ALTER TABLE ONLY public."Contact"
ADD CONSTRAINT fk93c185fx7chn68uv7nl6uv2s0 FOREIGN KEY (current_sponsor_client_id) REFERENCES public."Registrar"(client_id);
--
-- Name: BillingCancellation fk_billing_cancellation_billing_event_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingCancellation"
ADD CONSTRAINT fk_billing_cancellation_billing_event_id FOREIGN KEY (billing_event_id) REFERENCES public."BillingEvent"(billing_event_id);
--
-- Name: BillingCancellation fk_billing_cancellation_billing_recurrence_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingCancellation"
ADD CONSTRAINT fk_billing_cancellation_billing_recurrence_id FOREIGN KEY (billing_recurrence_id) REFERENCES public."BillingRecurrence"(billing_recurrence_id);
--
-- Name: BillingCancellation fk_billing_cancellation_client_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingCancellation"
ADD CONSTRAINT fk_billing_cancellation_client_id FOREIGN KEY (client_id) REFERENCES public."Registrar"(client_id);
--
-- Name: BillingEvent fk_billing_event_cancellation_matching_billing_recurrence_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingEvent"
ADD CONSTRAINT fk_billing_event_cancellation_matching_billing_recurrence_id FOREIGN KEY (cancellation_matching_billing_recurrence_id) REFERENCES public."BillingRecurrence"(billing_recurrence_id);
--
-- Name: BillingEvent fk_billing_event_client_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingEvent"
ADD CONSTRAINT fk_billing_event_client_id FOREIGN KEY (client_id) REFERENCES public."Registrar"(client_id);
--
-- Name: BillingRecurrence fk_billing_recurrence_client_id; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public."BillingRecurrence"
ADD CONSTRAINT fk_billing_recurrence_client_id FOREIGN KEY (client_id) REFERENCES public."Registrar"(client_id);
--
-- Name: Domain fk_domain_admin_contact; Type: FK CONSTRAINT; Schema: public; Owner: -
--
+6 -6
View File
@@ -473,9 +473,9 @@
"dev": true
},
"eventemitter3": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
"integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==",
"dev": true
},
"extend": {
@@ -673,9 +673,9 @@
}
},
"http-proxy": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz",
"integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==",
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
"dev": true,
"requires": {
"eventemitter3": "^4.0.0",
@@ -33,8 +33,8 @@ import io.netty.channel.EventLoopGroup;
* <p>Inherits from {@link CircularList}, with element type of {@link ProbingStep} as the manner in
* which the sequence is carried out is similar to the {@link CircularList}. However, the {@link
* Builder} of {@link ProbingSequence} override {@link CircularList.AbstractBuilder} allowing for
* more customized flows, that are looped, but not necessarily entirely circular. Example: first ->
* second -> third -> fourth -> second -> third -> fourth -> second -> ...
* more customized flows, that are looped, but not necessarily entirely circular. Example: first
* -&gt; second -&gt; third -&gt; fourth -&gt; second -&gt; third -&gt; fourth -&gt; second -&gt;...
*
* <p>Created with {@link Builder} where we specify {@link EventLoopGroup}, {@link AbstractChannel}
* class type, then sequentially add in the {@link ProbingStep.Builder}s in order and mark which one
@@ -47,9 +47,8 @@ import org.joda.time.Duration;
/**
* AutoValue class that represents action generated by {@link ProbingStep}
*
* <p>Inherits from {@link Callable<ChannelFuture>}, as it has can be called to perform its
* specified task, and return the {@link ChannelFuture} that will be informed when the task has been
* completed
* <p>Inherits from {@link Callable}, as it has can be called to perform its specified task, and
* return the {@link ChannelFuture} that will be informed when the task has been completed
*
* <p>Is an immutable class, as it is comprised of the tools necessary for making a specific type of
* connection. It goes hand in hand with {@link Protocol}, which specifies the kind of overall
@@ -27,8 +27,8 @@ import io.netty.channel.SimpleChannelInboundHandler;
/**
* Superclass of all {@link io.netty.channel.ChannelHandler}s placed at end of channel pipeline
*
* <p>{@link ActionHandler} inherits from {@link SimpleChannelInboundHandler<InboundMessageType>},
* as it should only be passed in messages that implement the {@link InboundMessageType} interface.
* <p>{@link ActionHandler} inherits from {@link SimpleChannelInboundHandler}, as it should only be
* passed in messages that implement the {@link InboundMessageType} interface.
*
* <p>The {@link ActionHandler} skeleton exists for a few main purposes. First, it returns a {@link
* ChannelPromise}, which informs the {@link ProbingAction} in charge that a response has been read.
@@ -40,7 +40,7 @@ import org.joda.time.Duration;
*
* <p>Main purpose is to verify {@link HttpResponseMessage} received is valid. If the response
* implies a redirection it follows the redirection until either an Error Response is received, or
* {@link HttpResponseStatus.OK} is received
* {@link HttpResponseStatus#OK} is received
*/
public class WebWhoisActionHandler extends ActionHandler {
@@ -209,8 +209,7 @@ public class EppMessage {
* @param xml the XML Document containing the EPP reponse to verify
* @param expressions a list of XPath expressions to query the document with.
* @param validate a boolean flag to control if schema validation occurs (useful for testing)
* @throws IOException if InputStream throws one
* @throws EppClientException if the EPP response cannot be read, parsed, or doesn't containing
* @throws FailureException if the EPP response cannot be read, parsed, or doesn't containing
* matching data specified in expressions
*/
protected static void verifyEppResponse(Document xml, List<String> expressions, boolean validate)
@@ -309,7 +308,7 @@ public class EppMessage {
*
* @param responseBuffer the byte array to transform
* @return the resulting Document
* @throws EppClientException if the transform fails
* @throws FailureException if the transform fails
*/
public static Document byteArrayToXmlDoc(byte[] responseBuffer) throws FailureException {
Document xml;
@@ -117,7 +117,7 @@ public class EppRequestMessage extends EppMessage implements OutboundMessageType
*
* @return the {@link ByteBuf} instance that stores the bytes representing the requisite EPP
* Request
* @throws EppClientException- On the occasion that the prober can't appropriately convert the EPP
* @throws EppClientException On the occasion that the prober can't appropriately convert the EPP
* XML document to a {@link ByteBuf}, the blame falls on the prober, not the server, so it
* throws an {@link EppClientException}, which is a subclass of the {@link
* UndeterminedStateException}.
@@ -76,18 +76,19 @@ public class EppServiceHandler extends HttpsRelayServiceHandler {
}
/**
* Write <hello> to the server after SSL handshake completion to request <greeting>
* Write {@code <hello>} to the server after SSL handshake completion to request {@code
* <greeting>}
*
* <p>When handling EPP over TCP, the server should issue a <greeting> to the client when a
* connection is established. Nomulus app however does not automatically sends the <greeting> upon
* connection. The proxy therefore first sends a <hello> to registry to request a <greeting>
* response.
* <p>When handling EPP over TCP, the server should issue a {@code <greeting>} to the client when
* a connection is established. Nomulus app however does not automatically sends the {@code
* <greeting>} upon connection. The proxy therefore first sends a {@code <hello>} to registry to
* request a {@code <greeting>} response.
*
* <p>The <hello> request is only sent after SSL handshake is completed between the client and the
* proxy so that the client certificate hash is available, which is needed to communicate with the
* server. Because {@link SslHandshakeCompletionEvent} is triggered before any calls to {@link
* #channelRead} are scheduled by the event loop executor, the <hello> request is guaranteed to be
* the first message sent to the server.
* <p>The {@code <hello>} request is only sent after SSL handshake is completed between the client
* and the proxy so that the client certificate hash is available, which is needed to communicate
* with the server. Because {@link SslHandshakeCompletionEvent} is triggered before any calls to
* {@link #channelRead} are scheduled by the event loop executor, the {@code <hello>} request is
* guaranteed to be the first message sent to the server.
*
* @see <a href="https://tools.ietf.org/html/rfc5734">RFC 5732 EPP Transport over TCP</a>
* @see <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">The Proxy

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