From 4d2e0941f3af7378c973166160cd871528acbe98 Mon Sep 17 00:00:00 2001 From: mcilwain Date: Tue, 15 Nov 2016 11:01:10 -0800 Subject: [PATCH] Add a custom logic framework to provide pluggable extensibility To add additional logic for flow code, write custom classes that extend the existing custom logic classes (of which DomainCreateFlowCustomLogic is the first provided example), along with a class that extends CustomLogicFactory to provide instances of the new custom logic classes. Then configure the fully qualified class name of your new custom logic factory in ConfigModule.provideCustomLogicFactoryClass(). ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=139221577 --- java/google/registry/config/ConfigModule.java | 7 ++ java/google/registry/flows/FlowComponent.java | 2 + java/google/registry/flows/FlowUtils.java | 9 ++ .../flows/custom/BaseFlowCustomLogic.java | 41 ++++++++ .../flows/custom/CustomLogicFactory.java | 38 +++++++ .../custom/CustomLogicFactoryModule.java | 33 +++++++ .../flows/custom/CustomLogicModule.java | 31 ++++++ .../custom/DomainCreateFlowCustomLogic.java | 98 +++++++++++++++++++ .../registry/flows/custom/EntityChanges.java | 62 ++++++++++++ .../flows/domain/DomainCreateFlow.java | 50 ++++++---- .../module/frontend/FrontendComponent.java | 2 + .../registry/module/tools/ToolsComponent.java | 2 + java/google/registry/util/TypeUtils.java | 22 +++++ .../registry/flows/EppTestComponent.java | 2 + .../google/registry/util/TypeUtilsTest.java | 53 ++++++++++ 15 files changed, 435 insertions(+), 17 deletions(-) create mode 100644 java/google/registry/flows/custom/BaseFlowCustomLogic.java create mode 100644 java/google/registry/flows/custom/CustomLogicFactory.java create mode 100644 java/google/registry/flows/custom/CustomLogicFactoryModule.java create mode 100644 java/google/registry/flows/custom/CustomLogicModule.java create mode 100644 java/google/registry/flows/custom/DomainCreateFlowCustomLogic.java create mode 100644 java/google/registry/flows/custom/EntityChanges.java create mode 100644 javatests/google/registry/util/TypeUtilsTest.java diff --git a/java/google/registry/config/ConfigModule.java b/java/google/registry/config/ConfigModule.java index 2edcb4587..fe3cafa4b 100644 --- a/java/google/registry/config/ConfigModule.java +++ b/java/google/registry/config/ConfigModule.java @@ -914,4 +914,11 @@ public final class ConfigModule { public static String provideGreetingServerId() { return "Charleston Road Registry"; } + + @Provides + @Config("customLogicFactoryClass") + public static String provideCustomLogicFactoryClass() { + // TODO(b/32875427): This will be moved into configuration in a text file in a future refactor. + return "google.registry.flows.custom.CustomLogicFactory"; + } } diff --git a/java/google/registry/flows/FlowComponent.java b/java/google/registry/flows/FlowComponent.java index 08c2d8f31..929792907 100644 --- a/java/google/registry/flows/FlowComponent.java +++ b/java/google/registry/flows/FlowComponent.java @@ -30,6 +30,7 @@ import google.registry.flows.contact.ContactTransferQueryFlow; import google.registry.flows.contact.ContactTransferRejectFlow; import google.registry.flows.contact.ContactTransferRequestFlow; import google.registry.flows.contact.ContactUpdateFlow; +import google.registry.flows.custom.CustomLogicModule; import google.registry.flows.domain.ClaimsCheckFlow; import google.registry.flows.domain.DomainAllocateFlow; import google.registry.flows.domain.DomainApplicationCreateFlow; @@ -66,6 +67,7 @@ import google.registry.util.SystemSleeper.SystemSleeperModule; @Subcomponent(modules = { AsyncFlowsModule.class, ConfigModule.class, + CustomLogicModule.class, DnsModule.class, FlowModule.class, FlowComponent.FlowComponentModule.class, diff --git a/java/google/registry/flows/FlowUtils.java b/java/google/registry/flows/FlowUtils.java index c21d926c6..f9e9fbb94 100644 --- a/java/google/registry/flows/FlowUtils.java +++ b/java/google/registry/flows/FlowUtils.java @@ -14,7 +14,10 @@ package google.registry.flows; +import static google.registry.model.ofy.ObjectifyService.ofy; + import google.registry.flows.EppException.CommandUseErrorException; +import google.registry.flows.custom.EntityChanges; /** Static utility functions for flows. */ public final class FlowUtils { @@ -28,6 +31,12 @@ public final class FlowUtils { } } + /** Persists the saves and deletes in an {@link EntityChanges} to Datastore. */ + public static void persistEntityChanges(EntityChanges entityChanges) { + ofy().save().entities(entityChanges.getSaves()); + ofy().delete().keys(entityChanges.getDeletes()); + } + /** Registrar is not logged in. */ public static class NotLoggedInException extends CommandUseErrorException { public NotLoggedInException() { diff --git a/java/google/registry/flows/custom/BaseFlowCustomLogic.java b/java/google/registry/flows/custom/BaseFlowCustomLogic.java new file mode 100644 index 000000000..fcec87ab2 --- /dev/null +++ b/java/google/registry/flows/custom/BaseFlowCustomLogic.java @@ -0,0 +1,41 @@ +// Copyright 2016 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.flows.custom; + +import google.registry.flows.SessionMetadata; +import google.registry.model.eppinput.EppInput; + +/** + * An abstract base class for all flow custom logic that stores the flow's {@link EppInput} and + * {@link SessionMetadata} for convenience. Both of these are immutable. + */ +public abstract class BaseFlowCustomLogic { + + private final EppInput eppInput; + private final SessionMetadata sessionMetadata; + + protected BaseFlowCustomLogic(EppInput eppInput, SessionMetadata sessionMetadata) { + this.eppInput = eppInput; + this.sessionMetadata = sessionMetadata; + } + + protected EppInput getEppInput() { + return eppInput; + } + + protected SessionMetadata getSessionMetadata() { + return sessionMetadata; + } +} diff --git a/java/google/registry/flows/custom/CustomLogicFactory.java b/java/google/registry/flows/custom/CustomLogicFactory.java new file mode 100644 index 000000000..b32dbde00 --- /dev/null +++ b/java/google/registry/flows/custom/CustomLogicFactory.java @@ -0,0 +1,38 @@ +// Copyright 2016 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.flows.custom; + +import google.registry.config.ConfigModule; +import google.registry.flows.SessionMetadata; +import google.registry.model.eppinput.EppInput; + +/** + * A no-op base custom logic factory. + * + *

To add custom logic, extend this class, then configure it in + * {@link ConfigModule#provideCustomLogicFactoryClass}. The eppInput and sessionMetadata parameters + * are unused in the base implementation, but are provided so that custom implementations can + * optionally determine how to construct/choose which custom logic class to return. A common use + * case might be parsing TLD for domain-specific flows from the EppInput and then using that to + * choose a different custom logic implementation, or switching based on the registrar + * {@code clientId} in sessionMetadata. + */ +public class CustomLogicFactory { + + public DomainCreateFlowCustomLogic forDomainCreateFlow( + EppInput eppInput, SessionMetadata sessionMetadata) { + return new DomainCreateFlowCustomLogic(eppInput, sessionMetadata); + } +} diff --git a/java/google/registry/flows/custom/CustomLogicFactoryModule.java b/java/google/registry/flows/custom/CustomLogicFactoryModule.java new file mode 100644 index 000000000..050c04011 --- /dev/null +++ b/java/google/registry/flows/custom/CustomLogicFactoryModule.java @@ -0,0 +1,33 @@ +// Copyright 2016 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.flows.custom; + +import static google.registry.util.TypeUtils.getClassFromString; +import static google.registry.util.TypeUtils.instantiate; + +import dagger.Module; +import dagger.Provides; +import google.registry.config.ConfigModule.Config; + +/** Dagger module for custom logic factories. */ +@Module +public class CustomLogicFactoryModule { + + @Provides + static CustomLogicFactory provideCustomLogicFactory( + @Config("customLogicFactoryClass") String factoryClass) { + return instantiate(getClassFromString(factoryClass, CustomLogicFactory.class)); + } +} diff --git a/java/google/registry/flows/custom/CustomLogicModule.java b/java/google/registry/flows/custom/CustomLogicModule.java new file mode 100644 index 000000000..dd2f9f02b --- /dev/null +++ b/java/google/registry/flows/custom/CustomLogicModule.java @@ -0,0 +1,31 @@ +// Copyright 2016 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.flows.custom; + +import dagger.Module; +import dagger.Provides; +import google.registry.flows.SessionMetadata; +import google.registry.model.eppinput.EppInput; + +/** Dagger module to provide instances of custom logic classes for EPP flows. */ +@Module +public class CustomLogicModule { + + @Provides + static DomainCreateFlowCustomLogic provideDomainCreateFlowCustomLogic( + CustomLogicFactory factory, EppInput eppInput, SessionMetadata sessionMetadata) { + return factory.forDomainCreateFlow(eppInput, sessionMetadata); + } +} diff --git a/java/google/registry/flows/custom/DomainCreateFlowCustomLogic.java b/java/google/registry/flows/custom/DomainCreateFlowCustomLogic.java new file mode 100644 index 000000000..63da19ed2 --- /dev/null +++ b/java/google/registry/flows/custom/DomainCreateFlowCustomLogic.java @@ -0,0 +1,98 @@ +// Copyright 2016 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.flows.custom; + +import com.google.auto.value.AutoValue; +import com.google.common.net.InternetDomainName; +import google.registry.flows.EppException; +import google.registry.flows.SessionMetadata; +import google.registry.model.ImmutableObject; +import google.registry.model.domain.DomainResource; +import google.registry.model.eppinput.EppInput; +import google.registry.model.reporting.HistoryEntry; + +/** + * A no-op base class for domain create flow custom logic. + * + *

Extend this class and override the hooks to perform custom logic. + */ +public class DomainCreateFlowCustomLogic extends BaseFlowCustomLogic { + + protected DomainCreateFlowCustomLogic(EppInput eppInput, SessionMetadata sessionMetadata) { + super(eppInput, sessionMetadata); + } + + /** A hook that runs at the end of the validation step to perform additional validation. */ + @SuppressWarnings("unused") + public void afterValidation(AfterValidationParameters parameters) throws EppException { + // Do nothing. + } + + /** + * A hook that runs before new entities are persisted. + * + *

This takes the new entities as input and returns the actual entities to save. It is + * important to be careful when changing the flow behavior for existing entities, because the core + * logic across many different flows expects the existence of these entities and many of the + * fields on them. + */ + @SuppressWarnings("unused") + public EntityChanges beforeSave(BeforeSaveParameters parameters, EntityChanges entityChanges) + throws EppException { + return entityChanges; + } + + /** A class to encapsulate parameters for a call to {@link #afterValidation}. */ + @AutoValue + public abstract static class AfterValidationParameters extends ImmutableObject { + + public abstract InternetDomainName domainName(); + public abstract int years(); + + public static Builder newBuilder() { + return new AutoValue_DomainCreateFlowCustomLogic_AfterValidationParameters.Builder(); + } + + /** Builder for {@link AfterValidationParameters}. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setDomainName(InternetDomainName domainName); + public abstract Builder setYears(int years); + public abstract AfterValidationParameters build(); + } + } + + /** A class to encapsulate parameters for a call to {@link #beforeSave}. */ + @AutoValue + public abstract static class BeforeSaveParameters extends ImmutableObject { + + public abstract DomainResource newDomain(); + public abstract HistoryEntry historyEntry(); + public abstract int years(); + + public static Builder newBuilder() { + return new AutoValue_DomainCreateFlowCustomLogic_BeforeSaveParameters.Builder(); + } + + /** Builder for {@link BeforeSaveParameters}. */ + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setNewDomain(DomainResource newDomain); + public abstract Builder setHistoryEntry(HistoryEntry historyEntry); + public abstract Builder setYears(int years); + public abstract BeforeSaveParameters build(); + } + } +} diff --git a/java/google/registry/flows/custom/EntityChanges.java b/java/google/registry/flows/custom/EntityChanges.java new file mode 100644 index 000000000..0e9776f54 --- /dev/null +++ b/java/google/registry/flows/custom/EntityChanges.java @@ -0,0 +1,62 @@ +// Copyright 2016 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.flows.custom; + +import com.google.auto.value.AutoValue; +import com.google.common.collect.ImmutableSet; +import com.googlecode.objectify.Key; +import google.registry.model.ImmutableObject; + +/** A wrapper class that encapsulates Datastore entities to both save and delete. */ +@AutoValue +public abstract class EntityChanges { + + public abstract ImmutableSet getSaves(); + + public abstract ImmutableSet> getDeletes(); + + public static Builder newBuilder() { + // Default both entities to save and entities to delete to empty sets, so that the build() + // method won't subsequently throw an exception if one doesn't end up being applicable. + return new AutoValue_EntityChanges.Builder() + .setSaves(ImmutableSet.of()) + .setDeletes(ImmutableSet.>of()); + } + + /** Builder for {@link EntityChanges}. */ + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder setSaves(ImmutableSet entitiesToSave); + + public abstract ImmutableSet.Builder savesBuilder(); + + public Builder addSave(ImmutableObject entityToSave) { + savesBuilder().add(entityToSave); + return this; + } + + public abstract Builder setDeletes(ImmutableSet> entitiesToDelete); + + public abstract ImmutableSet.Builder> deletesBuilder(); + + public Builder addDelete(Key entityToDelete) { + deletesBuilder().add(entityToDelete); + return this; + } + + public abstract EntityChanges build(); + } +} diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 7bab600f8..520e38fc7 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -14,6 +14,7 @@ package google.registry.flows.domain; +import static google.registry.flows.FlowUtils.persistEntityChanges; import static google.registry.flows.FlowUtils.validateClientIsLoggedIn; import static google.registry.flows.ResourceFlowUtils.verifyResourceDoesNotExist; import static google.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld; @@ -57,6 +58,8 @@ import google.registry.flows.FlowModule.ClientId; import google.registry.flows.FlowModule.Superuser; import google.registry.flows.FlowModule.TargetId; import google.registry.flows.TransactionalFlow; +import google.registry.flows.custom.DomainCreateFlowCustomLogic; +import google.registry.flows.custom.EntityChanges; import google.registry.flows.domain.TldSpecificLogicProxy.EppCommandOperations; import google.registry.model.ImmutableObject; import google.registry.model.billing.BillingEvent; @@ -162,6 +165,7 @@ public class DomainCreateFlow implements TransactionalFlow { @Inject @Superuser boolean isSuperuser; @Inject HistoryEntry.Builder historyBuilder; @Inject EppResponse.Builder responseBuilder; + @Inject DomainCreateFlowCustomLogic customLogic; @Inject DomainCreateFlow() {} @Override @@ -198,6 +202,12 @@ public class DomainCreateFlow implements TransactionalFlow { if (hasSignedMarks) { verifySignedMarksAllowed(tldState, isAnchorTenant); } + customLogic.afterValidation( + DomainCreateFlowCustomLogic.AfterValidationParameters.newBuilder() + .setDomainName(domainName) + .setYears(years) + .build()); + FeeCreateCommandExtension feeCreate = eppInput.getSingleExtension(FeeCreateCommandExtension.class); EppCommandOperations commandOperations = TldSpecificLogicProxy.getCreatePrice( @@ -269,7 +279,6 @@ public class DomainCreateFlow implements TransactionalFlow { .setContacts(command.getContacts()) .addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createBillingEvent)) .build(); - handleExtraFlowLogic(registry.getTldStr(), years, historyEntry, newDomain); entitiesToSave.add( newDomain, ForeignKeyIndex.create(newDomain, newDomain.getDeletionTime()), @@ -282,7 +291,29 @@ public class DomainCreateFlow implements TransactionalFlow { prepareMarkedLrpTokenEntity(authInfo.getPw().getValue(), domainName, historyEntry)); } enqueueTasks(hasSignedMarks, hasClaimsNotice, newDomain); - ofy().save().entities(entitiesToSave.build()); + + // TODO: Remove this section and only use the customLogic. + Optional extraFlowLogic = + RegistryExtraFlowLogicProxy.newInstanceForTld(registry.getTldStr()); + if (extraFlowLogic.isPresent()) { + extraFlowLogic.get().performAdditionalDomainCreateLogic( + newDomain, + clientId, + years, + eppInput, + historyEntry); + } + + EntityChanges entityChanges = + customLogic.beforeSave( + DomainCreateFlowCustomLogic.BeforeSaveParameters.newBuilder() + .setNewDomain(newDomain) + .setHistoryEntry(historyEntry) + .setYears(years) + .build(), + EntityChanges.newBuilder().setSaves(entitiesToSave.build()).build()); + persistEntityChanges(entityChanges); + return responseBuilder .setResData(DomainCreateData.create(targetId, now, registrationExpirationTime)) .setExtensions(createResponseExtensions(feeCreate, commandOperations)) @@ -395,21 +426,6 @@ public class DomainCreateFlow implements TransactionalFlow { return registry.getLrpPeriod().contains(now) && !isAnchorTenant; } - private void handleExtraFlowLogic( - String tld, int years, HistoryEntry historyEntry, DomainResource newDomain) - throws EppException { - Optional extraFlowLogic = - RegistryExtraFlowLogicProxy.newInstanceForTld(tld); - if (extraFlowLogic.isPresent()) { - extraFlowLogic.get().performAdditionalDomainCreateLogic( - newDomain, - clientId, - years, - eppInput, - historyEntry); - } - } - private void enqueueTasks( boolean hasSignedMarks, boolean hasClaimsNotice, DomainResource newDomain) { if (newDomain.shouldPublishToDns()) { diff --git a/java/google/registry/module/frontend/FrontendComponent.java b/java/google/registry/module/frontend/FrontendComponent.java index 45015bdc0..22064c160 100644 --- a/java/google/registry/module/frontend/FrontendComponent.java +++ b/java/google/registry/module/frontend/FrontendComponent.java @@ -17,6 +17,7 @@ package google.registry.module.frontend; import dagger.Component; import google.registry.braintree.BraintreeModule; import google.registry.config.ConfigModule; +import google.registry.flows.custom.CustomLogicFactoryModule; import google.registry.keyring.api.DummyKeyringModule; import google.registry.keyring.api.KeyModule; import google.registry.module.frontend.FrontendRequestComponent.FrontendRequestComponentModule; @@ -40,6 +41,7 @@ import javax.inject.Singleton; BraintreeModule.class, ConfigModule.class, ConsoleConfigModule.class, + CustomLogicFactoryModule.class, DummyKeyringModule.class, FrontendMetricsModule.class, FrontendRequestComponentModule.class, diff --git a/java/google/registry/module/tools/ToolsComponent.java b/java/google/registry/module/tools/ToolsComponent.java index 7fd5c54d0..a8fd8f27f 100644 --- a/java/google/registry/module/tools/ToolsComponent.java +++ b/java/google/registry/module/tools/ToolsComponent.java @@ -17,6 +17,7 @@ package google.registry.module.tools; import dagger.Component; import google.registry.config.ConfigModule; import google.registry.export.DriveModule; +import google.registry.flows.custom.CustomLogicFactoryModule; import google.registry.gcs.GcsServiceModule; import google.registry.groups.DirectoryModule; import google.registry.groups.GroupsModule; @@ -42,6 +43,7 @@ import javax.inject.Singleton; modules = { AppIdentityCredentialModule.class, ConfigModule.class, + CustomLogicFactoryModule.class, DatastoreServiceModule.class, DirectoryModule.class, DriveModule.class, diff --git a/java/google/registry/util/TypeUtils.java b/java/google/registry/util/TypeUtils.java index 86aa0655b..c9cda6551 100644 --- a/java/google/registry/util/TypeUtils.java +++ b/java/google/registry/util/TypeUtils.java @@ -56,6 +56,28 @@ public class TypeUtils { } } + /** + * Returns the class referred to by a fully qualified class name string. + * + *

Throws an error if the loaded class is not assignable from the expected super type class. + */ + public static Class getClassFromString(String className, Class expectedSuperType) { + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException(String.format("Failed to load class %s", className), e); + } + checkArgument( + expectedSuperType.isAssignableFrom(clazz), + "%s does not implement/extend %s", + clazz.getSimpleName(), + expectedSuperType.getSimpleName()); + @SuppressWarnings("unchecked") + Class castedClass = (Class) clazz; + return castedClass; + } + /** * Aggregates enum "values" in a typesafe enum pattern into a string->field map. */ diff --git a/javatests/google/registry/flows/EppTestComponent.java b/javatests/google/registry/flows/EppTestComponent.java index a312ca65d..e53b93d84 100644 --- a/javatests/google/registry/flows/EppTestComponent.java +++ b/javatests/google/registry/flows/EppTestComponent.java @@ -23,6 +23,7 @@ import dagger.Provides; import dagger.Subcomponent; import google.registry.config.ConfigModule; import google.registry.dns.DnsQueue; +import google.registry.flows.custom.CustomLogicFactoryModule; import google.registry.monitoring.whitebox.BigQueryMetricsEnqueuer; import google.registry.monitoring.whitebox.EppMetric; import google.registry.request.RequestScope; @@ -35,6 +36,7 @@ import javax.inject.Singleton; @Component( modules = { ConfigModule.class, + CustomLogicFactoryModule.class, EppTestComponent.FakesAndMocksModule.class }) interface EppTestComponent { diff --git a/javatests/google/registry/util/TypeUtilsTest.java b/javatests/google/registry/util/TypeUtilsTest.java new file mode 100644 index 000000000..9420ab77d --- /dev/null +++ b/javatests/google/registry/util/TypeUtilsTest.java @@ -0,0 +1,53 @@ +// Copyright 2016 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.util; + +import static com.google.common.truth.Truth.assertThat; + +import google.registry.testing.ExceptionRule; +import java.io.Serializable; +import java.util.ArrayList; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link TypeUtils}. */ +@RunWith(JUnit4.class) +public class TypeUtilsTest { + + @Rule + public final ExceptionRule thrown = new ExceptionRule(); + + @Test + public void test_getClassFromString_validClass() { + Class clazz = + TypeUtils.getClassFromString("java.util.ArrayList", Serializable.class); + assertThat(clazz).isEqualTo(ArrayList.class); + } + + @Test + public void test_getClassFromString_notAssignableFrom() { + thrown.expect(IllegalArgumentException.class, "ArrayList does not implement/extend Integer"); + TypeUtils.getClassFromString("java.util.ArrayList", Integer.class); + } + + @Test + public void test_getClassFromString_unknownClass() { + thrown.expect( + IllegalArgumentException.class, "Failed to load class com.fake.company.nonexistent.Class"); + TypeUtils.getClassFromString("com.fake.company.nonexistent.Class", Object.class); + } +}