diff --git a/core/build.gradle b/core/build.gradle index e258c05d6..e9ea4c2f2 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -317,6 +317,7 @@ dependencies { testCompile deps['com.google.monitoring-client:contrib'] testCompile deps['com.google.truth:truth'] testCompile deps['com.google.truth.extensions:truth-java8-extension'] + testCompile deps['org.checkerframework:checker-qual'] testCompile deps['org.hamcrest:hamcrest'] testCompile deps['org.hamcrest:hamcrest-core'] testCompile deps['org.hamcrest:hamcrest-library'] diff --git a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java index 7bc9c1cdb..da578c598 100644 --- a/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java +++ b/core/src/main/java/google/registry/model/reporting/HistoryEntryDao.java @@ -14,6 +14,7 @@ package google.registry.model.reporting; +import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableList.toImmutableList; import static google.registry.model.ofy.ObjectifyService.auditedOfy; import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; @@ -49,7 +50,7 @@ import org.joda.time.DateTime; public class HistoryEntryDao { /** Loads all history objects in the times specified, including all types. */ - public static ImmutableList loadAllHistoryObjects( + public static ImmutableList loadAllHistoryObjects( DateTime afterTime, DateTime beforeTime) { if (tm().isOfy()) { return Streams.stream( @@ -77,13 +78,22 @@ public class HistoryEntryDao { } /** Loads all history objects corresponding to the given {@link EppResource}. */ - public static ImmutableList loadHistoryObjectsForResource( + public static ImmutableList loadHistoryObjectsForResource( VKey parentKey) { return loadHistoryObjectsForResource(parentKey, START_OF_TIME, END_OF_TIME); } + /** + * Loads all history objects corresponding to the given {@link EppResource} and casted to the + * appropriate subclass. + */ + public static ImmutableList loadHistoryObjectsForResource( + VKey parentKey, Class subclazz) { + return loadHistoryObjectsForResource(parentKey, START_OF_TIME, END_OF_TIME, subclazz); + } + /** Loads all history objects in the time period specified for the given {@link EppResource}. */ - public static ImmutableList loadHistoryObjectsForResource( + public static ImmutableList loadHistoryObjectsForResource( VKey parentKey, DateTime afterTime, DateTime beforeTime) { if (tm().isOfy()) { return Streams.stream( @@ -102,8 +112,35 @@ public class HistoryEntryDao { } } + /** + * Loads all history objects in the time period specified for the given {@link EppResource} and + * casted to the appropriate subclass. + * + *

Note that the subclass must be explicitly provided because we need the compile time + * information of T to return an {@code ImmutableList}, even though at runtime we can call + * {@link #getHistoryClassFromParent(Class)} to obtain it, which we also did to confirm that the + * provided subclass is indeed correct. + */ + public static ImmutableList loadHistoryObjectsForResource( + VKey parentKey, + DateTime afterTime, + DateTime beforeTime, + Class subclazz) { + Class expectedSubclazz = getHistoryClassFromParent(parentKey.getKind()); + checkArgument( + subclazz.equals(expectedSubclazz), + "The supplied HistoryEntry subclass %s is incompatible with the EppResource %s, " + + "use %s instead", + subclazz.getSimpleName(), + parentKey.getKind().getSimpleName(), + expectedSubclazz.getSimpleName()); + return loadHistoryObjectsForResource(parentKey, afterTime, beforeTime).stream() + .map(subclazz::cast) + .collect(toImmutableList()); + } + /** Loads all history objects from all time from the given registrars. */ - public static Iterable loadHistoryObjectsByRegistrars( + public static Iterable loadHistoryObjectsByRegistrars( ImmutableCollection registrarIds) { if (tm().isOfy()) { return auditedOfy() @@ -124,8 +161,8 @@ public class HistoryEntryDao { } } - private static Stream loadHistoryObjectFromSqlByRegistrars( - Class historyClass, ImmutableCollection registrarIds) { + private static Stream loadHistoryObjectFromSqlByRegistrars( + Class historyClass, ImmutableCollection registrarIds) { return jpaTm() .getEntityManager() .createQuery( @@ -135,7 +172,7 @@ public class HistoryEntryDao { .getResultStream(); } - private static ImmutableList loadHistoryObjectsForResourceFromSql( + private static ImmutableList loadHistoryObjectsForResourceFromSql( VKey parentKey, DateTime afterTime, DateTime beforeTime) { // The class we're searching from is based on which parent type (e.g. Domain) we have Class historyClass = getHistoryClassFromParent(parentKey.getKind()); @@ -174,8 +211,8 @@ public class HistoryEntryDao { : historyClass.equals(DomainHistory.class) ? "domainRepoId" : "hostRepoId"; } - private static List loadAllHistoryObjectsFromSql( - Class historyClass, DateTime afterTime, DateTime beforeTime) { + private static List loadAllHistoryObjectsFromSql( + Class historyClass, DateTime afterTime, DateTime beforeTime) { CriteriaBuilder criteriaBuilder = jpaTm().getEntityManager().getCriteriaBuilder(); return jpaTm() .getEntityManager() diff --git a/core/src/test/java/google/registry/testing/DatabaseHelper.java b/core/src/test/java/google/registry/testing/DatabaseHelper.java index 03328de0c..9a9e111b2 100644 --- a/core/src/test/java/google/registry/testing/DatabaseHelper.java +++ b/core/src/test/java/google/registry/testing/DatabaseHelper.java @@ -1101,10 +1101,19 @@ public class DatabaseHelper { } /** Returns all of the history entries that are parented off the given EppResource. */ - public static List getHistoryEntries(EppResource resource) { + public static List getHistoryEntries(EppResource resource) { return HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey()); } + /** + * Returns all of the history entries that are parented off the given EppResource, casted to the + * corresponding subclass. + */ + public static List getHistoryEntries( + EppResource resource, Class subclazz) { + return HistoryEntryDao.loadHistoryObjectsForResource(resource.createVKey(), subclazz); + } + /** * Returns all of the history entries that are parented off the given EppResource with the given * type. @@ -1112,7 +1121,18 @@ public class DatabaseHelper { public static ImmutableList getHistoryEntriesOfType( EppResource resource, final HistoryEntry.Type type) { return getHistoryEntries(resource).stream() - .filter(entry -> entry.getType() == type) + .filter(entry -> entry.getType().equals(type)) + .collect(toImmutableList()); + } + + /** + * Returns all of the history entries that are parented off the given EppResource with the given + * type and casted to the corresponding subclass. + */ + public static ImmutableList getHistoryEntriesOfType( + EppResource resource, final HistoryEntry.Type type, Class subclazz) { + return getHistoryEntries(resource, subclazz).stream() + .filter(entry -> entry.getType().equals(type)) .collect(toImmutableList()); } @@ -1122,9 +1142,16 @@ public class DatabaseHelper { */ public static HistoryEntry getOnlyHistoryEntryOfType( EppResource resource, final HistoryEntry.Type type) { - List historyEntries = getHistoryEntriesOfType(resource, type); - assertThat(historyEntries).hasSize(1); - return historyEntries.get(0); + return Iterables.getOnlyElement(getHistoryEntriesOfType(resource, type)); + } + + /** + * Returns the only history entry of the given type, casted to the corresponding subtype, and + * throws an AssertionError if there are zero or more than one. + */ + public static T getOnlyHistoryEntryOfType( + EppResource resource, final HistoryEntry.Type type, Class subclazz) { + return Iterables.getOnlyElement(getHistoryEntriesOfType(resource, type, subclazz)); } private static HistoryEntry.Type getHistoryEntryType(EppResource resource) { diff --git a/dependencies.gradle b/dependencies.gradle index d91003603..5cc239236 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -120,6 +120,7 @@ ext { 'jline:jline:1.0', 'joda-time:joda-time:2.9.2', 'junit:junit:4.13', + 'org.checkerframework:checker-qual:3.9.1', 'org.junit.jupiter:junit-jupiter-api:5.6.2', 'org.junit.jupiter:junit-jupiter-engine:5.6.2', 'org.junit.jupiter:junit-jupiter-migrationsupport:5.6.2',