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 Throws an error if the loaded class is not assignable from the expected super type class.
+ */
+ public static