Extract CLASS_REGISTRY from VKeyTranslatorFactory (#1440)

* Add annotation for unit test file

* Extract CLASS_REGISTRY from VKeyTranslatorFactory

* Improve test cases and docs
This commit is contained in:
Rachel Guan 2021-12-06 16:41:17 -05:00 committed by GitHub
parent 9037e2829e
commit 66d246b723
6 changed files with 249 additions and 26 deletions

View file

@ -0,0 +1,63 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.common;
import static com.google.common.base.Functions.identity;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.EntityClasses.ALL_CLASSES;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.EntitySubclass;
import java.util.Map;
import java.util.stream.Collectors;
/** A helper to manage class name and class path mapping. */
public class ClassPathManager {
/**
* Class registry allowing us to restore the original class object from the unqualified class
* name, which is all the datastore key gives us. Note that entities annotated
* with @EntitySubclass are removed because they share the same kind of the key with their parent
* class.
*/
public static final Map<String, Class<?>> CLASS_REGISTRY =
ALL_CLASSES.stream()
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
.collect(Collectors.toMap(com.googlecode.objectify.Key::getKind, identity()));
/**
* Class name registry allowing us to obtain the class name the unqualified class, which is all
* the datastore key gives us. Note that entities annotated with @EntitySubclass are removed
* because they share the same kind of the key with their parent class.
*/
public static final Map<Class<?>, String> CLASS_NAME_REGISTRY =
ALL_CLASSES.stream()
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
.collect(Collectors.toMap(identity(), com.googlecode.objectify.Key::getKind));
@VisibleForTesting
public static void addTestEntityClass(Class<?> clazz) {
CLASS_REGISTRY.put(com.googlecode.objectify.Key.getKind(clazz), clazz);
}
public static <T> Class<T> getClass(String className) {
checkArgument(CLASS_REGISTRY.containsKey(className), "Class not found in class registry");
return (Class<T>) CLASS_REGISTRY.get(className);
}
public static <T> String getClassName(Class<T> clazz) {
checkArgument(CLASS_NAME_REGISTRY.containsKey(clazz), "Class not found in class name registry");
return CLASS_NAME_REGISTRY.get(clazz);
}
}

View file

@ -14,18 +14,13 @@
package google.registry.model.translators;
import static com.google.common.base.Functions.identity;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.EntityClasses.ALL_CLASSES;
import com.google.appengine.api.datastore.Key;
import com.google.common.annotations.VisibleForTesting;
import com.googlecode.objectify.annotation.EntitySubclass;
import google.registry.model.common.ClassPathManager;
import google.registry.persistence.VKey;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
/**
@ -36,15 +31,6 @@ import javax.annotation.Nullable;
*/
public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey, Key> {
// Class registry allowing us to restore the original class object from the unqualified class
// name, which is all the datastore key gives us.
// Note that entities annotated with @EntitySubclass are removed because they share the same
// kind of the key with their parent class.
private static final Map<String, Class<?>> CLASS_REGISTRY =
ALL_CLASSES.stream()
.filter(clazz -> !clazz.isAnnotationPresent(EntitySubclass.class))
.collect(Collectors.toMap(com.googlecode.objectify.Key::getKind, identity()));
public VKeyTranslatorFactory() {
super(VKey.class);
}
@ -67,7 +53,7 @@ public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey,
}
// Try to create the VKey from its reference type.
Class<T> clazz = (Class<T>) CLASS_REGISTRY.get(key.getKind());
Class<T> clazz = ClassPathManager.getClass(key.getKind());
checkArgument(clazz != null, "Unknown Key type: %s", key.getKind());
try {
Method createVKeyMethod =
@ -92,11 +78,6 @@ public class VKeyTranslatorFactory extends AbstractSimpleTranslatorFactory<VKey,
}
}
@VisibleForTesting
public static void addTestEntityClass(Class<?> clazz) {
CLASS_REGISTRY.put(com.googlecode.objectify.Key.getKind(clazz), clazz);
}
@Override
public SimpleTranslator<VKey, Key> createTranslator() {
return new SimpleTranslator<VKey, Key>() {

View file

@ -48,6 +48,7 @@ import com.google.common.truth.Truth8;
import com.google.common.util.concurrent.MoreExecutors;
import com.googlecode.objectify.Key;
import google.registry.gcs.GcsUtils;
import google.registry.model.common.ClassPathManager;
import google.registry.model.common.DatabaseMigrationStateSchedule;
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
import google.registry.model.contact.ContactResource;
@ -67,7 +68,6 @@ import google.registry.model.replay.SqlReplayCheckpoint;
import google.registry.model.server.Lock;
import google.registry.model.tld.label.PremiumList;
import google.registry.model.tld.label.PremiumList.PremiumEntry;
import google.registry.model.translators.VKeyTranslatorFactory;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTransactionManager;
import google.registry.persistence.transaction.TransactionManagerFactory;
@ -130,7 +130,7 @@ public class ReplayCommitLogsToSqlActionTest {
@BeforeAll
static void beforeAll() {
VKeyTranslatorFactory.addTestEntityClass(TestObject.class);
ClassPathManager.addTestEntityClass(TestObject.class);
}
@BeforeEach

View file

@ -0,0 +1,177 @@
// Copyright 2021 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.model.common;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.billing.BillingEvent.Cancellation;
import google.registry.model.billing.BillingEvent.Modification;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.billing.BillingEvent.Recurring;
import google.registry.model.contact.ContactResource;
import google.registry.model.domain.DomainBase;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.token.AllocationToken;
import google.registry.model.host.HostResource;
import google.registry.model.index.EppResourceIndex;
import google.registry.model.index.EppResourceIndexBucket;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyContactIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyDomainIndex;
import google.registry.model.index.ForeignKeyIndex.ForeignKeyHostIndex;
import google.registry.model.ofy.CommitLogBucket;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogCheckpointRoot;
import google.registry.model.ofy.CommitLogManifest;
import google.registry.model.ofy.CommitLogMutation;
import google.registry.model.poll.PollMessage;
import google.registry.model.rde.RdeRevision;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.replay.LastSqlTransaction;
import google.registry.model.reporting.HistoryEntry;
import google.registry.model.server.Lock;
import google.registry.model.server.ServerSecret;
import google.registry.model.tld.Registry;
import google.registry.testing.TestObject;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link ClassPathManager}. */
public class ClassPathManagerTest {
@Test
void getClass_classInClassRegistry_returnsClass() throws ClassNotFoundException {
/**
* Class names are used in stringified vkeys, which can be present in task queues. Class name is
* required to create a vkey. Changing these names could break task queue entries that are
* present during a rollout. If you want to change the names of any of the classses supported in
* CLASS_REGISTRY, you'll need to introduce some mechanism to deal with this. One way is to find
* the corresponding class name by calling ClassPathManager.getClassName(clazz). The classes
* below are all classes supported in CLASS_REGISTRY. This test breaks if someone changes a
* classname without preserving the original name.
*/
assertThat(ClassPathManager.getClass("ForeignKeyContactIndex"))
.isEqualTo(ForeignKeyContactIndex.class);
assertThat(ClassPathManager.getClass("Modification")).isEqualTo(Modification.class);
assertThat(ClassPathManager.getClass("CommitLogCheckpoint"))
.isEqualTo(CommitLogCheckpoint.class);
assertThat(ClassPathManager.getClass("CommitLogManifest")).isEqualTo(CommitLogManifest.class);
assertThat(ClassPathManager.getClass("AllocationToken")).isEqualTo(AllocationToken.class);
assertThat(ClassPathManager.getClass("OneTime")).isEqualTo(OneTime.class);
assertThat(ClassPathManager.getClass("Cursor")).isEqualTo(Cursor.class);
assertThat(ClassPathManager.getClass("RdeRevision")).isEqualTo(RdeRevision.class);
assertThat(ClassPathManager.getClass("HostResource")).isEqualTo(HostResource.class);
assertThat(ClassPathManager.getClass("Recurring")).isEqualTo(Recurring.class);
assertThat(ClassPathManager.getClass("Registrar")).isEqualTo(Registrar.class);
assertThat(ClassPathManager.getClass("ContactResource")).isEqualTo(ContactResource.class);
assertThat(ClassPathManager.getClass("Cancellation")).isEqualTo(Cancellation.class);
assertThat(ClassPathManager.getClass("RegistrarContact")).isEqualTo(RegistrarContact.class);
assertThat(ClassPathManager.getClass("CommitLogBucket")).isEqualTo(CommitLogBucket.class);
assertThat(ClassPathManager.getClass("LastSqlTransaction")).isEqualTo(LastSqlTransaction.class);
assertThat(ClassPathManager.getClass("CommitLogCheckpointRoot"))
.isEqualTo(CommitLogCheckpointRoot.class);
assertThat(ClassPathManager.getClass("GaeUserIdConverter")).isEqualTo(GaeUserIdConverter.class);
assertThat(ClassPathManager.getClass("EppResourceIndexBucket"))
.isEqualTo(EppResourceIndexBucket.class);
assertThat(ClassPathManager.getClass("Registry")).isEqualTo(Registry.class);
assertThat(ClassPathManager.getClass("EntityGroupRoot")).isEqualTo(EntityGroupRoot.class);
assertThat(ClassPathManager.getClass("Lock")).isEqualTo(Lock.class);
assertThat(ClassPathManager.getClass("DomainBase")).isEqualTo(DomainBase.class);
assertThat(ClassPathManager.getClass("CommitLogMutation")).isEqualTo(CommitLogMutation.class);
assertThat(ClassPathManager.getClass("HistoryEntry")).isEqualTo(HistoryEntry.class);
assertThat(ClassPathManager.getClass("PollMessage")).isEqualTo(PollMessage.class);
assertThat(ClassPathManager.getClass("ForeignKeyHostIndex"))
.isEqualTo(ForeignKeyHostIndex.class);
assertThat(ClassPathManager.getClass("ServerSecret")).isEqualTo(ServerSecret.class);
assertThat(ClassPathManager.getClass("EppResourceIndex")).isEqualTo(EppResourceIndex.class);
assertThat(ClassPathManager.getClass("ForeignKeyDomainIndex"))
.isEqualTo(ForeignKeyDomainIndex.class);
}
@Test
void getClass_classNotInClassRegistry_throwsException() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class, () -> ClassPathManager.getClass("DomainHistory"));
assertThat(thrown).hasMessageThat().contains("Class not found in class registry");
}
@Test
void getClassName_classNotInClassRegistry_throwsException() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> ClassPathManager.getClassName(DomainHistory.class));
assertThat(thrown).hasMessageThat().contains("Class not found in class name registry");
}
@Test
void getClassName() {
/**
* Class names are used in stringified vkeys, which can be present in task queues. Class name is
* required to create a vkey. Changing these names could break task queue entries that are
* present during a rollout. If you want to change the names of any of the classses supported in
* CLASS_NAME_REGISTRY, you'll need to introduce some mechanism to deal with this.
* ClassPathManager.getClassName(clazz) allows you to verify the corresponding name of a class.
* The classes below are all classes supported in CLASS_NAME_REGISTRY. This test breaks if
* someone changes a classname without preserving the original name.
*/
assertThat(ClassPathManager.getClassName(ForeignKeyContactIndex.class))
.isEqualTo("ForeignKeyContactIndex");
assertThat(ClassPathManager.getClassName(Modification.class)).isEqualTo("Modification");
assertThat(ClassPathManager.getClassName(CommitLogCheckpoint.class))
.isEqualTo("CommitLogCheckpoint");
assertThat(ClassPathManager.getClassName(CommitLogManifest.class))
.isEqualTo("CommitLogManifest");
assertThat(ClassPathManager.getClassName(AllocationToken.class)).isEqualTo("AllocationToken");
assertThat(ClassPathManager.getClassName(OneTime.class)).isEqualTo("OneTime");
assertThat(ClassPathManager.getClassName(Cursor.class)).isEqualTo("Cursor");
assertThat(ClassPathManager.getClassName(RdeRevision.class)).isEqualTo("RdeRevision");
assertThat(ClassPathManager.getClassName(HostResource.class)).isEqualTo("HostResource");
assertThat(ClassPathManager.getClassName(Recurring.class)).isEqualTo("Recurring");
assertThat(ClassPathManager.getClassName(Registrar.class)).isEqualTo("Registrar");
assertThat(ClassPathManager.getClassName(ContactResource.class)).isEqualTo("ContactResource");
assertThat(ClassPathManager.getClassName(Cancellation.class)).isEqualTo("Cancellation");
assertThat(ClassPathManager.getClassName(RegistrarContact.class)).isEqualTo("RegistrarContact");
assertThat(ClassPathManager.getClassName(CommitLogBucket.class)).isEqualTo("CommitLogBucket");
assertThat(ClassPathManager.getClassName(LastSqlTransaction.class))
.isEqualTo("LastSqlTransaction");
assertThat(ClassPathManager.getClassName(CommitLogCheckpointRoot.class))
.isEqualTo("CommitLogCheckpointRoot");
assertThat(ClassPathManager.getClassName(GaeUserIdConverter.class))
.isEqualTo("GaeUserIdConverter");
assertThat(ClassPathManager.getClassName(EppResourceIndexBucket.class))
.isEqualTo("EppResourceIndexBucket");
assertThat(ClassPathManager.getClassName(Registry.class)).isEqualTo("Registry");
assertThat(ClassPathManager.getClassName(EntityGroupRoot.class)).isEqualTo("EntityGroupRoot");
assertThat(ClassPathManager.getClassName(Lock.class)).isEqualTo("Lock");
assertThat(ClassPathManager.getClassName(DomainBase.class)).isEqualTo("DomainBase");
assertThat(ClassPathManager.getClassName(CommitLogMutation.class))
.isEqualTo("CommitLogMutation");
assertThat(ClassPathManager.getClassName(HistoryEntry.class)).isEqualTo("HistoryEntry");
assertThat(ClassPathManager.getClassName(PollMessage.class)).isEqualTo("PollMessage");
assertThat(ClassPathManager.getClassName(ForeignKeyHostIndex.class))
.isEqualTo("ForeignKeyHostIndex");
assertThat(ClassPathManager.getClassName(ServerSecret.class)).isEqualTo("ServerSecret");
assertThat(ClassPathManager.getClassName(EppResourceIndex.class)).isEqualTo("EppResourceIndex");
assertThat(ClassPathManager.getClassName(ForeignKeyDomainIndex.class))
.isEqualTo("ForeignKeyDomainIndex");
}
@Test
void addTestEntityClass_success() {
ClassPathManager.addTestEntityClass(TestObject.class);
assertThat(ClassPathManager.getClass("TestObject")).isEqualTo(TestObject.class);
}
}

View file

@ -21,6 +21,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import com.googlecode.objectify.Key;
import google.registry.model.billing.BillingEvent;
import google.registry.model.common.ClassPathManager;
import google.registry.model.domain.DomainBase;
import google.registry.model.ofy.CommitLogCheckpoint;
import google.registry.model.ofy.CommitLogCheckpointRoot;
@ -32,6 +33,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
/** Unit tests for {@link VKeyTranslatorFactory}. */
public class VKeyTranslatorFactoryTest {
@RegisterExtension
@ -45,7 +47,7 @@ public class VKeyTranslatorFactoryTest {
@BeforeAll
static void beforeAll() {
VKeyTranslatorFactory.addTestEntityClass(TestObject.class);
ClassPathManager.addTestEntityClass(TestObject.class);
}
@Test

View file

@ -27,9 +27,9 @@ import com.google.common.collect.ImmutableMap;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.annotation.Entity;
import google.registry.model.billing.BillingEvent.OneTime;
import google.registry.model.common.ClassPathManager;
import google.registry.model.domain.DomainBase;
import google.registry.model.registrar.RegistrarContact;
import google.registry.model.translators.VKeyTranslatorFactory;
import google.registry.testing.AppEngineExtension;
import google.registry.testing.TaskQueueHelper.TaskMatcher;
import google.registry.testing.TestObject;
@ -63,7 +63,7 @@ class VKeyTest {
@BeforeAll
static void beforeAll() {
VKeyTranslatorFactory.addTestEntityClass(TestObject.class);
ClassPathManager.addTestEntityClass(TestObject.class);
}
@Test