From c9a16f7f119eb88b2c27f5f4d570b5221ce4a00b Mon Sep 17 00:00:00 2001 From: cgoldfeder Date: Mon, 20 Jun 2016 14:51:42 -0700 Subject: [PATCH] Dagger, meet Flows. Flows, meet Dagger. Daggerizes all of the EPP flows. This does not change anything yet about the flows themselves, just how they are invoked, but after this CL it's safe to @Inject things into flow classes. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=125382478 --- java/google/registry/flows/EppController.java | 85 ++--- java/google/registry/flows/FlowComponent.java | 35 ++ java/google/registry/flows/FlowModule.java | 335 ++++++++++++++++++ java/google/registry/flows/FlowRunner.java | 77 ++-- java/google/registry/flows/FlowScope.java | 35 ++ .../flows/contact/ContactCheckFlow.java | 5 + .../flows/contact/ContactCreateFlow.java | 5 + .../flows/contact/ContactDeleteFlow.java | 4 + .../flows/contact/ContactInfoFlow.java | 6 +- .../contact/ContactTransferApproveFlow.java | 5 + .../contact/ContactTransferCancelFlow.java | 5 + .../contact/ContactTransferQueryFlow.java | 3 + .../contact/ContactTransferRejectFlow.java | 5 + .../contact/ContactTransferRequestFlow.java | 4 + .../flows/contact/ContactUpdateFlow.java | 5 + .../flows/domain/ClaimsCheckFlow.java | 4 + .../flows/domain/DomainAllocateFlow.java | 4 + .../domain/DomainApplicationCreateFlow.java | 4 + .../domain/DomainApplicationDeleteFlow.java | 4 + .../domain/DomainApplicationInfoFlow.java | 4 + .../domain/DomainApplicationUpdateFlow.java | 4 + .../flows/domain/DomainCheckFlow.java | 4 + .../flows/domain/DomainCreateFlow.java | 4 + .../flows/domain/DomainDeleteFlow.java | 4 + .../registry/flows/domain/DomainInfoFlow.java | 4 + .../flows/domain/DomainRenewFlow.java | 4 + .../domain/DomainRestoreRequestFlow.java | 4 + .../domain/DomainTransferApproveFlow.java | 4 + .../domain/DomainTransferCancelFlow.java | 4 + .../flows/domain/DomainTransferQueryFlow.java | 6 +- .../domain/DomainTransferRejectFlow.java | 4 + .../domain/DomainTransferRequestFlow.java | 4 + .../flows/domain/DomainUpdateFlow.java | 4 + .../registry/flows/host/HostCheckFlow.java | 5 + .../registry/flows/host/HostCreateFlow.java | 4 + .../registry/flows/host/HostDeleteFlow.java | 4 + .../registry/flows/host/HostInfoFlow.java | 6 +- .../registry/flows/host/HostUpdateFlow.java | 4 + .../registry/flows/poll/PollAckFlow.java | 4 + .../registry/flows/poll/PollRequestFlow.java | 4 + .../registry/flows/session/HelloFlow.java | 5 + .../registry/flows/session/LoginFlow.java | 4 + .../registry/flows/session/LogoutFlow.java | 5 + .../frontend/FrontendRequestComponent.java | 2 + .../module/tools/ToolsRequestComponent.java | 2 + javatests/google/registry/flows/BUILD | 1 + .../registry/flows/CheckApiActionTest.java | 13 +- .../registry/flows/EppCommitLogsTest.java | 171 +++++++++ .../flows/EppLifecycleDomainTest.java | 11 +- .../google/registry/flows/EppTestCase.java | 16 +- .../registry/flows/EppTestComponent.java | 70 ++++ .../google/registry/flows/FlowTestCase.java | 88 +++-- .../ContactTransferRequestFlowTest.java | 2 +- .../domain/DomainTransferRequestFlowTest.java | 6 +- .../testdata/domain_create.xml | 0 .../domain_create_no_hosts_or_dsdata.xml | 2 +- .../flows/testdata/domain_create_response.xml | 2 +- .../registry/flows/testdata/domain_delete.xml | 2 +- .../registry/flows/testdata/domain_info.xml | 2 +- .../domain_info_response_pendingdelete.xml | 2 +- .../testdata/domain_update_dsdata_add.xml | 0 .../testdata/domain_update_dsdata_rem.xml | 0 .../domain_update_restore_request.xml | 2 +- .../poll_response_domain_transfer_request.xml | 2 +- ...e_domain_transfer_server_approve_loser.xml | 2 +- ..._domain_transfer_server_approve_winner.xml | 2 +- .../registry/model/EppResourceUtilsTest.java | 114 ------ .../registry/model/testdata/domain_delete.xml | 11 - .../testing/AbstractEppResourceSubject.java | 7 +- 69 files changed, 973 insertions(+), 292 deletions(-) create mode 100644 java/google/registry/flows/FlowComponent.java create mode 100644 java/google/registry/flows/FlowModule.java create mode 100644 java/google/registry/flows/FlowScope.java create mode 100644 javatests/google/registry/flows/EppCommitLogsTest.java create mode 100644 javatests/google/registry/flows/EppTestComponent.java rename javatests/google/registry/{model => flows}/testdata/domain_create.xml (100%) rename javatests/google/registry/{model => flows}/testdata/domain_update_dsdata_add.xml (100%) rename javatests/google/registry/{model => flows}/testdata/domain_update_dsdata_rem.xml (100%) delete mode 100644 javatests/google/registry/model/testdata/domain_delete.xml diff --git a/java/google/registry/flows/EppController.java b/java/google/registry/flows/EppController.java index 5a308adba..d9c4d981e 100644 --- a/java/google/registry/flows/EppController.java +++ b/java/google/registry/flows/EppController.java @@ -15,12 +15,11 @@ package google.registry.flows; import static google.registry.flows.EppXmlTransformer.unmarshal; -import static google.registry.flows.picker.FlowPicker.getFlowClass; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableList; +import google.registry.flows.FlowModule.EppExceptionInProviderException; import google.registry.model.eppcommon.Trid; import google.registry.model.eppinput.EppInput; import google.registry.model.eppoutput.EppOutput; @@ -43,6 +42,7 @@ public final class EppController { private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); @Inject Clock clock; + @Inject FlowComponent.Builder flowComponentBuilder; @Inject EppMetrics metrics; @Inject EppController() {} @@ -54,57 +54,62 @@ public final class EppController { boolean isDryRun, boolean isSuperuser, byte[] inputXmlBytes) { - Trid trid = null; + metrics.setClientId(sessionMetadata.getClientId()); + metrics.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL"); try { - EppInput eppInput = unmarshal(EppInput.class, inputXmlBytes); - trid = Trid.create(eppInput.getCommandWrapper().getClTrid()); - ImmutableList targetIds = eppInput.getTargetIds(); + EppInput eppInput; + try { + eppInput = unmarshal(EppInput.class, inputXmlBytes); + } catch (EppException e) { + // Send the client an error message, with no clTRID since we couldn't unmarshal it. + metrics.setEppStatus(e.getResult().getCode()); + return getErrorResponse(clock, e.getResult(), Trid.create(null)); + } metrics.setCommandName(eppInput.getCommandName()); - metrics.setClientId(sessionMetadata.getClientId()); - metrics.setPrivilegeLevel(isSuperuser ? "SUPERUSER" : "NORMAL"); - if (!targetIds.isEmpty()) { - metrics.setEppTarget(Joiner.on(",").join(targetIds)); + if (!eppInput.getTargetIds().isEmpty()) { + metrics.setEppTarget(Joiner.on(',').join(eppInput.getTargetIds())); } - FlowRunner flowRunner = new FlowRunner( - getFlowClass(eppInput), - eppInput, - trid, - sessionMetadata, - credentials, - eppRequestSource, - isDryRun, - isSuperuser, - inputXmlBytes, - metrics, - clock); - EppOutput eppOutput = flowRunner.run(); - if (eppOutput.isResponse()) { - metrics.setEppStatus(eppOutput.getResponse().getResult().getCode()); + EppOutput output = runFlowConvertEppErrors(flowComponentBuilder + .flowModule(new FlowModule.Builder() + .setSessionMetadata(sessionMetadata) + .setCredentials(credentials) + .setEppRequestSource(eppRequestSource) + .setIsDryRun(isDryRun) + .setIsSuperuser(isSuperuser) + .setInputXmlBytes(inputXmlBytes) + .setEppInput(eppInput) + .build()) + .build()); + if (output.isResponse()) { + metrics.setEppStatus(output.getResponse().getResult().getCode()); } - return eppOutput; - } catch (EppException e) { - // The command failed. Send the client an error message. - metrics.setEppStatus(e.getResult().getCode()); - return getErrorResponse(clock, e.getResult(), trid); - } catch (Throwable e) { - // Something bad and unexpected happened. Send the client a generic error, and log it. - logger.severe(e, "Unexpected failure"); - metrics.setEppStatus(Code.CommandFailed); - return getErrorResponse(clock, Result.create(Code.CommandFailed), trid); + return output; } finally { metrics.export(); } } - /** Create a response indicating an Epp failure. */ + /** Run an EPP flow and convert known exceptions into EPP error responses. */ + private EppOutput runFlowConvertEppErrors(FlowComponent flowComponent) { + try { + return flowComponent.flowRunner().run(); + } catch (EppException | EppExceptionInProviderException e) { + // The command failed. Send the client an error message. + EppException eppEx = (EppException) (e instanceof EppException ? e : e.getCause()); + return getErrorResponse(clock, eppEx.getResult(), flowComponent.trid()); + } catch (Throwable e) { + // Something bad and unexpected happened. Send the client a generic error, and log it. + logger.severe(e, "Unexpected failure"); + return getErrorResponse(clock, Result.create(Code.CommandFailed), flowComponent.trid()); + } + } + + /** Create a response indicating an EPP failure. */ @VisibleForTesting static EppOutput getErrorResponse(Clock clock, Result result, Trid trid) { - // Create TRID (without a clTRID) if one hasn't been created yet, as it's necessary to construct - // a valid response. This can happen if the error occurred before we could even parse out the - // clTRID (e.g. if a syntax error occurred parsing the supplied XML). return EppOutput.create(new EppResponse.Builder() - .setTrid(trid == null ? Trid.create(null) : trid) .setResult(result) + .setTrid(trid) .setExecutionTime(clock.nowUtc()) .build()); } diff --git a/java/google/registry/flows/FlowComponent.java b/java/google/registry/flows/FlowComponent.java new file mode 100644 index 000000000..0f1545f32 --- /dev/null +++ b/java/google/registry/flows/FlowComponent.java @@ -0,0 +1,35 @@ +// Copyright 2016 The Domain Registry 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; + +import dagger.Subcomponent; + +import google.registry.model.eppcommon.Trid; + +/** Dagger component for flow classes. */ +@FlowScope +@Subcomponent(modules = FlowModule.class) +public interface FlowComponent { + + Trid trid(); + FlowRunner flowRunner(); + + /** Dagger-implemented builder for this subcomponent. */ + @Subcomponent.Builder + interface Builder { + Builder flowModule(FlowModule flowModule); + FlowComponent build(); + } +} diff --git a/java/google/registry/flows/FlowModule.java b/java/google/registry/flows/FlowModule.java new file mode 100644 index 000000000..fdc6fa7b0 --- /dev/null +++ b/java/google/registry/flows/FlowModule.java @@ -0,0 +1,335 @@ +// Copyright 2016 The Domain Registry 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; + +import static com.google.common.base.Preconditions.checkState; + +import com.google.common.collect.ImmutableMap; + +import dagger.Module; +import dagger.Provides; + +import google.registry.flows.contact.ContactCheckFlow; +import google.registry.flows.contact.ContactCreateFlow; +import google.registry.flows.contact.ContactDeleteFlow; +import google.registry.flows.contact.ContactInfoFlow; +import google.registry.flows.contact.ContactTransferApproveFlow; +import google.registry.flows.contact.ContactTransferCancelFlow; +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.domain.ClaimsCheckFlow; +import google.registry.flows.domain.DomainAllocateFlow; +import google.registry.flows.domain.DomainApplicationCreateFlow; +import google.registry.flows.domain.DomainApplicationDeleteFlow; +import google.registry.flows.domain.DomainApplicationInfoFlow; +import google.registry.flows.domain.DomainApplicationUpdateFlow; +import google.registry.flows.domain.DomainCheckFlow; +import google.registry.flows.domain.DomainCreateFlow; +import google.registry.flows.domain.DomainDeleteFlow; +import google.registry.flows.domain.DomainInfoFlow; +import google.registry.flows.domain.DomainRenewFlow; +import google.registry.flows.domain.DomainRestoreRequestFlow; +import google.registry.flows.domain.DomainTransferApproveFlow; +import google.registry.flows.domain.DomainTransferCancelFlow; +import google.registry.flows.domain.DomainTransferQueryFlow; +import google.registry.flows.domain.DomainTransferRejectFlow; +import google.registry.flows.domain.DomainTransferRequestFlow; +import google.registry.flows.domain.DomainUpdateFlow; +import google.registry.flows.host.HostCheckFlow; +import google.registry.flows.host.HostCreateFlow; +import google.registry.flows.host.HostDeleteFlow; +import google.registry.flows.host.HostInfoFlow; +import google.registry.flows.host.HostUpdateFlow; +import google.registry.flows.picker.FlowPicker; +import google.registry.flows.poll.PollAckFlow; +import google.registry.flows.poll.PollRequestFlow; +import google.registry.flows.session.HelloFlow; +import google.registry.flows.session.LoginFlow; +import google.registry.flows.session.LogoutFlow; +import google.registry.model.eppcommon.Trid; +import google.registry.model.eppinput.EppInput; + +import java.lang.annotation.Documented; +import java.util.Map; + +import javax.annotation.Nullable; +import javax.inject.Provider; +import javax.inject.Qualifier; + +/** Module to choose and instantiate an EPP flow. */ +@Module +public class FlowModule { + + private EppInput eppInput; + private byte[] inputXmlBytes; + private SessionMetadata sessionMetadata; + private TransportCredentials credentials; + private boolean isDryRun; + private EppRequestSource eppRequestSource; + private boolean isSuperuser; + + private FlowModule() {} + + /** Builder for {@link FlowModule}. */ + static class Builder { + FlowModule module = new FlowModule(); + + Builder setEppInput(EppInput eppInput) { + module.eppInput = eppInput; + return this; + } + + Builder setInputXmlBytes(byte[] inputXmlBytes) { + module.inputXmlBytes = inputXmlBytes; + return this; + } + + Builder setSessionMetadata(SessionMetadata sessionMetadata) { + module.sessionMetadata = sessionMetadata; + return this; + } + + Builder setIsDryRun(boolean isDryRun) { + module.isDryRun = isDryRun; + return this; + } + + Builder setIsSuperuser(boolean isSuperuser) { + module.isSuperuser = isSuperuser; + return this; + } + + Builder setEppRequestSource(EppRequestSource eppRequestSource) { + module.eppRequestSource = eppRequestSource; + return this; + } + + Builder setCredentials(TransportCredentials credentials) { + module.credentials = credentials; + return this; + } + + FlowModule build() { + try { + checkState(module != null, "Already built"); + return module; + } finally { + module = null; + } + } + } + + @Provides + @FlowScope + @InputXml + byte[] provideInputXml() { + return inputXmlBytes; + } + + @Provides + @FlowScope + EppInput provideEppInput() { + return eppInput; + } + + @Provides + @FlowScope + SessionMetadata provideSessionMetadata() { + return sessionMetadata; + } + + @Provides + @FlowScope + @DryRun + boolean provideIsDryRun() { + return isDryRun; + } + + @Provides + @FlowScope + @Transactional + boolean provideIsTransactional(Class flowClass) { + return TransactionalFlow.class.isAssignableFrom(flowClass); + } + + @Provides + @FlowScope + @Superuser + boolean provideIsSuperuser() { + return isSuperuser; + } + + @Provides + @FlowScope + EppRequestSource provideEppRequestSource() { + return eppRequestSource; + } + + @Provides + @FlowScope + TransportCredentials provideTransportCredentials() { + return credentials; + } + + @Provides + @FlowScope + @Nullable + @ClientId + static String provideClientId(SessionMetadata sessionMetadata) { + return sessionMetadata.getClientId(); + } + + @Provides + @FlowScope + static Trid provideTrid(EppInput eppInput) { + return Trid.create(eppInput.getCommandWrapper().getClTrid()); + } + + /** Provides a mapping between flow classes and injected providers. */ + @Provides + @FlowScope + static Map, Provider> provideFlowClassMap( + Provider contactCheckFlowProvider, + Provider contactCreateFlowProvider, + Provider contactDeleteFlowProvider, + Provider contactInfoFlowProvider, + Provider contactTransferApproveFlowProvider, + Provider contactTransferCancelFlowProvider, + Provider contactTransferQueryFlowProvider, + Provider contactTransferRejectFlowProvider, + Provider contactTransferRequestFlowProvider, + Provider contactUpdateFlowProvider, + Provider claimsCheckFlowProvider, + Provider domainAllocateFlowProvider, + Provider domainApplicationCreateFlowProvider, + Provider domainApplicationDeleteFlowProvider, + Provider domainApplicationInfoFlowProvider, + Provider domainApplicationUpdateFlowProvider, + Provider domainCheckFlowProvider, + Provider domainCreateFlowProvider, + Provider domainDeleteFlowProvider, + Provider domainInfoFlowProvider, + Provider domainRenewFlowProvider, + Provider domainRestoreRequestFlowProvider, + Provider domainTransferApproveFlowProvider, + Provider domainTransferCancelFlowProvider, + Provider domainTransferQueryFlowProvider, + Provider domainTransferRejectFlowProvider, + Provider domainTransferRequestFlowProvider, + Provider domainUpdateFlowProvider, + Provider hostCheckFlowProvider, + Provider hostCreateFlowProvider, + Provider hostDeleteFlowProvider, + Provider hostInfoFlowProvider, + Provider hostUpdateFlowProvider, + Provider pollAckFlowProvider, + Provider pollRequestFlowProvider, + Provider helloFlowProvider, + Provider loginFlowProvider, + Provider logoutFlowProvider) { + return new ImmutableMap.Builder, Provider>() + .put(ContactCheckFlow.class, contactCheckFlowProvider) + .put(ContactCreateFlow.class, contactCreateFlowProvider) + .put(ContactDeleteFlow.class, contactDeleteFlowProvider) + .put(ContactInfoFlow.class, contactInfoFlowProvider) + .put(ContactTransferApproveFlow.class, contactTransferApproveFlowProvider) + .put(ContactTransferCancelFlow.class, contactTransferCancelFlowProvider) + .put(ContactTransferQueryFlow.class, contactTransferQueryFlowProvider) + .put(ContactTransferRejectFlow.class, contactTransferRejectFlowProvider) + .put(ContactTransferRequestFlow.class, contactTransferRequestFlowProvider) + .put(ContactUpdateFlow.class, contactUpdateFlowProvider) + .put(ClaimsCheckFlow.class, claimsCheckFlowProvider) + .put(DomainAllocateFlow.class, domainAllocateFlowProvider) + .put(DomainApplicationCreateFlow.class, domainApplicationCreateFlowProvider) + .put(DomainApplicationDeleteFlow.class, domainApplicationDeleteFlowProvider) + .put(DomainApplicationInfoFlow.class, domainApplicationInfoFlowProvider) + .put(DomainApplicationUpdateFlow.class, domainApplicationUpdateFlowProvider) + .put(DomainCheckFlow.class, domainCheckFlowProvider) + .put(DomainCreateFlow.class, domainCreateFlowProvider) + .put(DomainDeleteFlow.class, domainDeleteFlowProvider) + .put(DomainInfoFlow.class, domainInfoFlowProvider) + .put(DomainRenewFlow.class, domainRenewFlowProvider) + .put(DomainRestoreRequestFlow.class, domainRestoreRequestFlowProvider) + .put(DomainTransferApproveFlow.class, domainTransferApproveFlowProvider) + .put(DomainTransferCancelFlow.class, domainTransferCancelFlowProvider) + .put(DomainTransferQueryFlow.class, domainTransferQueryFlowProvider) + .put(DomainTransferRejectFlow.class, domainTransferRejectFlowProvider) + .put(DomainTransferRequestFlow.class, domainTransferRequestFlowProvider) + .put(DomainUpdateFlow.class, domainUpdateFlowProvider) + .put(HostCheckFlow.class, hostCheckFlowProvider) + .put(HostCreateFlow.class, hostCreateFlowProvider) + .put(HostDeleteFlow.class, hostDeleteFlowProvider) + .put(HostInfoFlow.class, hostInfoFlowProvider) + .put(HostUpdateFlow.class, hostUpdateFlowProvider) + .put(PollAckFlow.class, pollAckFlowProvider) + .put(PollRequestFlow.class, pollRequestFlowProvider) + .put(HelloFlow.class, helloFlowProvider) + .put(LoginFlow.class, loginFlowProvider) + .put(LogoutFlow.class, logoutFlowProvider) + .build(); + } + + @Provides + @FlowScope + static Class provideFlowClass(EppInput eppInput) { + try { + return FlowPicker.getFlowClass(eppInput); + } catch (EppException e) { + throw new EppExceptionInProviderException(e); + } + } + + @Provides + @FlowScope + static Flow provideFlow( + Map, Provider> flowProviders, + Class flowClass) { + return flowProviders.get(flowClass).get(); + } + + /** Wrapper class to carry an {@link EppException} to the calling code. */ + static class EppExceptionInProviderException extends RuntimeException { + EppExceptionInProviderException(EppException exception) { + super(exception); + } + } + + /** Dagger qualifier for inputXml. */ + @Qualifier + @Documented + public @interface InputXml {} + + /** Dagger qualifier for registrar client id. */ + @Qualifier + @Documented + public @interface ClientId {} + + /** Dagger qualifier for whether a flow is in dry run mode. */ + @Qualifier + @Documented + public @interface DryRun {} + + /** Dagger qualifier for whether a flow is in superuser mode. */ + @Qualifier + @Documented + public @interface Superuser {} + + /** Dagger qualifier for whether a flow is transactional. */ + @Qualifier + @Documented + public @interface Transactional {} +} diff --git a/java/google/registry/flows/FlowRunner.java b/java/google/registry/flows/FlowRunner.java index 892b068d1..6d6c8778e 100644 --- a/java/google/registry/flows/FlowRunner.java +++ b/java/google/registry/flows/FlowRunner.java @@ -23,16 +23,24 @@ import com.google.common.base.Strings; import com.googlecode.objectify.Work; +import google.registry.flows.FlowModule.ClientId; +import google.registry.flows.FlowModule.DryRun; +import google.registry.flows.FlowModule.InputXml; +import google.registry.flows.FlowModule.Superuser; +import google.registry.flows.FlowModule.Transactional; import google.registry.model.eppcommon.Trid; import google.registry.model.eppinput.EppInput; import google.registry.model.eppoutput.EppOutput; import google.registry.monitoring.whitebox.EppMetrics; import google.registry.util.Clock; import google.registry.util.FormattingLogger; -import google.registry.util.TypeUtils; import org.joda.time.DateTime; +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Provider; + /** Run a flow, either transactionally or not, with logging and retrying as needed. */ public class FlowRunner { @@ -40,43 +48,20 @@ public class FlowRunner { private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); - private final Class flowClass; - private final EppInput eppInput; - private final Trid trid; - private final SessionMetadata sessionMetadata; - private final TransportCredentials credentials; - private final EppRequestSource eppRequestSource; - private final boolean isDryRun; - private final boolean isSuperuser; - private final byte[] inputXmlBytes; - private final EppMetrics metrics; - private final Clock clock; - - - public FlowRunner( - Class flowClass, - EppInput eppInput, - Trid trid, - SessionMetadata sessionMetadata, - TransportCredentials credentials, - EppRequestSource eppRequestSource, - boolean isDryRun, - boolean isSuperuser, - byte[] inputXmlBytes, - final EppMetrics metrics, - Clock clock) { - this.flowClass = flowClass; - this.eppInput = eppInput; - this.trid = trid; - this.sessionMetadata = sessionMetadata; - this.credentials = credentials; - this.eppRequestSource = eppRequestSource; - this.isDryRun = isDryRun; - this.isSuperuser = isSuperuser; - this.inputXmlBytes = inputXmlBytes; - this.metrics = metrics; - this.clock = clock; - } + @Inject @Nullable @ClientId String clientId; + @Inject Clock clock; + @Inject TransportCredentials credentials; + @Inject EppInput eppInput; + @Inject EppRequestSource eppRequestSource; + @Inject Provider flowProvider; + @Inject @InputXml byte[] inputXmlBytes; + @Inject @DryRun boolean isDryRun; + @Inject @Superuser boolean isSuperuser; + @Inject @Transactional boolean isTransactional; + @Inject EppMetrics metrics; + @Inject SessionMetadata sessionMetadata; + @Inject Trid trid; + @Inject FlowRunner() {} public EppOutput run() throws EppException { String clientId = sessionMetadata.getClientId(); @@ -90,10 +75,8 @@ public class FlowRunner { eppRequestSource, isDryRun ? "DRY_RUN" : "LIVE", isSuperuser ? "SUPERUSER" : "NORMAL"); - if (!isTransactional()) { - if (metrics != null) { - metrics.incrementAttempts(); - } + if (!isTransactional) { + metrics.incrementAttempts(); return createAndInitFlow(clock.nowUtc()).run(); } // We log the command in a structured format. Note that we do this before the transaction; @@ -107,9 +90,7 @@ public class FlowRunner { EppOutput flowResult = ofy().transact(new Work() { @Override public EppOutput run() { - if (metrics != null) { - metrics.incrementAttempts(); - } + metrics.incrementAttempts(); try { EppOutput output = createAndInitFlow(ofy().getTransactionTime()).run(); if (isDryRun) { @@ -137,7 +118,7 @@ public class FlowRunner { } private Flow createAndInitFlow(DateTime now) throws EppException { - return TypeUtils.instantiate(flowClass).init( + return flowProvider.get().init( eppInput, trid, sessionMetadata, @@ -148,10 +129,6 @@ public class FlowRunner { inputXmlBytes); } - public boolean isTransactional() { - return TransactionalFlow.class.isAssignableFrom(flowClass); - } - /** * Helper for logging in json format. * diff --git a/java/google/registry/flows/FlowScope.java b/java/google/registry/flows/FlowScope.java new file mode 100644 index 000000000..59b5cc2d8 --- /dev/null +++ b/java/google/registry/flows/FlowScope.java @@ -0,0 +1,35 @@ +// Copyright 2016 The Domain Registry 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; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Scope; + +/** + * Dagger annotation for flow-scoped components. + * + *

Note that this scope survives across transactional retries of a flow. That is, it is scoped to + * the overall execution of a flow, and not to a specific attempt. + */ +@Scope +@Documented +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface FlowScope {} diff --git a/java/google/registry/flows/contact/ContactCheckFlow.java b/java/google/registry/flows/contact/ContactCheckFlow.java index 6e03e2825..d4ca978cd 100644 --- a/java/google/registry/flows/contact/ContactCheckFlow.java +++ b/java/google/registry/flows/contact/ContactCheckFlow.java @@ -27,12 +27,17 @@ import google.registry.model.eppoutput.CheckData.ContactCheckData; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that checks whether a contact can be provisioned. * * @error {@link google.registry.flows.ResourceCheckFlow.TooManyResourceChecksException} */ public class ContactCheckFlow extends ResourceCheckFlow { + + @Inject ContactCheckFlow() {} + @Override protected CheckData getCheckData() { Set existingIds = checkResourcesExist(resourceClass, targetIds, now); diff --git a/java/google/registry/flows/contact/ContactCreateFlow.java b/java/google/registry/flows/contact/ContactCreateFlow.java index 7aaf9eebe..b3818e458 100644 --- a/java/google/registry/flows/contact/ContactCreateFlow.java +++ b/java/google/registry/flows/contact/ContactCreateFlow.java @@ -29,6 +29,8 @@ import google.registry.model.eppoutput.EppOutput; import google.registry.model.ofy.ObjectifyService; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that creates a new contact resource. * @@ -37,6 +39,9 @@ import google.registry.model.reporting.HistoryEntry; * @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException} */ public class ContactCreateFlow extends ResourceCreateFlow { + + @Inject ContactCreateFlow() {} + @Override protected EppOutput getOutput() { return createOutput(Success, ContactCreateData.create(newResource.getContactId(), now)); diff --git a/java/google/registry/flows/contact/ContactDeleteFlow.java b/java/google/registry/flows/contact/ContactDeleteFlow.java index 6651409ff..20378b9cf 100644 --- a/java/google/registry/flows/contact/ContactDeleteFlow.java +++ b/java/google/registry/flows/contact/ContactDeleteFlow.java @@ -36,6 +36,8 @@ import google.registry.model.contact.ContactResource.Builder; import google.registry.model.domain.DomainBase; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that deletes a contact resource. * @@ -49,6 +51,8 @@ public class ContactDeleteFlow extends ResourceAsyncDeleteFlow ref) { // Query for the first few linked domains, and if found, actually load them. The query is diff --git a/java/google/registry/flows/contact/ContactInfoFlow.java b/java/google/registry/flows/contact/ContactInfoFlow.java index 94aceb116..648718391 100644 --- a/java/google/registry/flows/contact/ContactInfoFlow.java +++ b/java/google/registry/flows/contact/ContactInfoFlow.java @@ -18,10 +18,14 @@ import google.registry.flows.ResourceInfoFlow; import google.registry.model.contact.ContactCommand.Info; import google.registry.model.contact.ContactResource; +import javax.inject.Inject; + /** * An EPP flow that reads a contact. * * @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException} */ -public class ContactInfoFlow extends ResourceInfoFlow {} +public class ContactInfoFlow extends ResourceInfoFlow { + @Inject ContactInfoFlow() {} +} diff --git a/java/google/registry/flows/contact/ContactTransferApproveFlow.java b/java/google/registry/flows/contact/ContactTransferApproveFlow.java index 64bca5897..5d955bba5 100644 --- a/java/google/registry/flows/contact/ContactTransferApproveFlow.java +++ b/java/google/registry/flows/contact/ContactTransferApproveFlow.java @@ -20,6 +20,8 @@ import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that approves a pending transfer on a {@link ContactResource}. * @@ -30,6 +32,9 @@ import google.registry.model.reporting.HistoryEntry; */ public class ContactTransferApproveFlow extends ResourceTransferApproveFlow { + + @Inject ContactTransferApproveFlow() {} + @Override protected final HistoryEntry.Type getHistoryEntryType() { return HistoryEntry.Type.CONTACT_TRANSFER_APPROVE; diff --git a/java/google/registry/flows/contact/ContactTransferCancelFlow.java b/java/google/registry/flows/contact/ContactTransferCancelFlow.java index 766c953ea..d70691a54 100644 --- a/java/google/registry/flows/contact/ContactTransferCancelFlow.java +++ b/java/google/registry/flows/contact/ContactTransferCancelFlow.java @@ -20,6 +20,8 @@ import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that cancels a pending transfer on a {@link ContactResource}. * @@ -30,6 +32,9 @@ import google.registry.model.reporting.HistoryEntry; */ public class ContactTransferCancelFlow extends ResourceTransferCancelFlow { + + @Inject ContactTransferCancelFlow() {} + @Override protected final HistoryEntry.Type getHistoryEntryType() { return HistoryEntry.Type.CONTACT_TRANSFER_CANCEL; diff --git a/java/google/registry/flows/contact/ContactTransferQueryFlow.java b/java/google/registry/flows/contact/ContactTransferQueryFlow.java index e48b8b53f..e6450abb6 100644 --- a/java/google/registry/flows/contact/ContactTransferQueryFlow.java +++ b/java/google/registry/flows/contact/ContactTransferQueryFlow.java @@ -18,6 +18,8 @@ import google.registry.flows.ResourceTransferQueryFlow; import google.registry.model.contact.ContactCommand.Transfer; import google.registry.model.contact.ContactResource; +import javax.inject.Inject; + /** * An EPP flow that queries a pending transfer on a {@link ContactResource}. * @@ -27,4 +29,5 @@ import google.registry.model.contact.ContactResource; * @error {@link google.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException} */ public class ContactTransferQueryFlow extends ResourceTransferQueryFlow { + @Inject ContactTransferQueryFlow() {} } diff --git a/java/google/registry/flows/contact/ContactTransferRejectFlow.java b/java/google/registry/flows/contact/ContactTransferRejectFlow.java index 02fa7be2f..ad0f31f13 100644 --- a/java/google/registry/flows/contact/ContactTransferRejectFlow.java +++ b/java/google/registry/flows/contact/ContactTransferRejectFlow.java @@ -20,6 +20,8 @@ import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that rejects a pending transfer on a {@link ContactResource}. * @@ -30,6 +32,9 @@ import google.registry.model.reporting.HistoryEntry; */ public class ContactTransferRejectFlow extends ResourceTransferRejectFlow { + + @Inject ContactTransferRejectFlow() {} + @Override protected final HistoryEntry.Type getHistoryEntryType() { return HistoryEntry.Type.CONTACT_TRANSFER_REJECT; diff --git a/java/google/registry/flows/contact/ContactTransferRequestFlow.java b/java/google/registry/flows/contact/ContactTransferRequestFlow.java index 658960fe3..d867efeb7 100644 --- a/java/google/registry/flows/contact/ContactTransferRequestFlow.java +++ b/java/google/registry/flows/contact/ContactTransferRequestFlow.java @@ -22,6 +22,8 @@ import google.registry.model.reporting.HistoryEntry; import org.joda.time.Duration; +import javax.inject.Inject; + /** * An EPP flow that requests a transfer on a {@link ContactResource}. * @@ -34,6 +36,8 @@ import org.joda.time.Duration; public class ContactTransferRequestFlow extends ResourceTransferRequestFlow { + @Inject ContactTransferRequestFlow() {} + @Override protected final HistoryEntry.Type getHistoryEntryType() { return HistoryEntry.Type.CONTACT_TRANSFER_REQUEST; diff --git a/java/google/registry/flows/contact/ContactUpdateFlow.java b/java/google/registry/flows/contact/ContactUpdateFlow.java index b867088b0..d0821b490 100644 --- a/java/google/registry/flows/contact/ContactUpdateFlow.java +++ b/java/google/registry/flows/contact/ContactUpdateFlow.java @@ -24,6 +24,8 @@ import google.registry.model.contact.ContactResource; import google.registry.model.contact.ContactResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that updates a contact resource. * @@ -36,6 +38,9 @@ import google.registry.model.reporting.HistoryEntry; * @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException} */ public class ContactUpdateFlow extends ResourceUpdateFlow { + + @Inject ContactUpdateFlow() {} + @Override protected void verifyNewUpdatedStateIsAllowed() throws EppException { validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo()); diff --git a/java/google/registry/flows/domain/ClaimsCheckFlow.java b/java/google/registry/flows/domain/ClaimsCheckFlow.java index 4dbe91770..e3f594b70 100644 --- a/java/google/registry/flows/domain/ClaimsCheckFlow.java +++ b/java/google/registry/flows/domain/ClaimsCheckFlow.java @@ -35,6 +35,8 @@ import google.registry.model.tmch.ClaimsListShard; import java.util.Map.Entry; +import javax.inject.Inject; + /** * An EPP flow that checks whether strings are trademarked. * @@ -48,6 +50,8 @@ public class ClaimsCheckFlow extends BaseDomainCheckFlow { public static final ImmutableSet DISALLOWED_TLD_STATES = Sets.immutableEnumSet( TldState.PREDELEGATION, TldState.SUNRISE); + @Inject ClaimsCheckFlow() {} + @Override protected void initDomainCheckFlow() throws EppException { registerExtensions(LaunchCheckExtension.class); diff --git a/java/google/registry/flows/domain/DomainAllocateFlow.java b/java/google/registry/flows/domain/DomainAllocateFlow.java index 452d310de..98fc5d9f7 100644 --- a/java/google/registry/flows/domain/DomainAllocateFlow.java +++ b/java/google/registry/flows/domain/DomainAllocateFlow.java @@ -48,6 +48,8 @@ import google.registry.model.registry.label.ReservationType; import google.registry.model.reporting.HistoryEntry; import google.registry.tmch.LordnTask; +import javax.inject.Inject; + /** * An EPP flow that allocates a new domain resource from a domain application. * @@ -62,6 +64,8 @@ public class DomainAllocateFlow extends DomainCreateOrAllocateFlow { protected AllocateCreateExtension allocateCreate; protected DomainApplication application; + @Inject DomainAllocateFlow() {} + @Override protected final void initDomainCreateOrAllocateFlow() { registerExtensions(AllocateCreateExtension.class); diff --git a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java index bfa9b5e68..e996e8a2a 100644 --- a/java/google/registry/flows/domain/DomainApplicationCreateFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationCreateFlow.java @@ -51,6 +51,8 @@ import google.registry.model.smd.EncodedSignedMark; import java.util.List; +import javax.inject.Inject; + /** * An EPP flow that creates a new application for a domain resource. * @@ -114,6 +116,8 @@ import java.util.List; */ public class DomainApplicationCreateFlow extends BaseDomainCreateFlow { + @Inject DomainApplicationCreateFlow() {} + @Override protected void initDomainCreateFlow() { registerExtensions(FeeCreateExtension.class, LaunchCreateExtension.class); diff --git a/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java b/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java index b459a858e..86a30186b 100644 --- a/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationDeleteFlow.java @@ -36,6 +36,8 @@ import google.registry.model.reporting.HistoryEntry; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that deletes a domain application. * @@ -51,6 +53,8 @@ import java.util.Set; public class DomainApplicationDeleteFlow extends ResourceSyncDeleteFlow { + @Inject DomainApplicationDeleteFlow() {} + @Override protected void initResourceCreateOrMutateFlow() throws EppException { registerExtensions(LaunchDeleteExtension.class); diff --git a/java/google/registry/flows/domain/DomainApplicationInfoFlow.java b/java/google/registry/flows/domain/DomainApplicationInfoFlow.java index e2f7c326e..50264b1e0 100644 --- a/java/google/registry/flows/domain/DomainApplicationInfoFlow.java +++ b/java/google/registry/flows/domain/DomainApplicationInfoFlow.java @@ -32,6 +32,8 @@ import google.registry.model.mark.Mark; import google.registry.model.smd.EncodedSignedMark; import google.registry.model.smd.SignedMark; +import javax.inject.Inject; + /** * An EPP flow that reads a domain application. * @@ -45,6 +47,8 @@ public class DomainApplicationInfoFlow extends BaseDomainInfoFlow { + @Inject DomainApplicationUpdateFlow() {} + @Override protected void initDomainUpdateFlow() throws EppException { registerExtensions(LaunchUpdateExtension.class, SecDnsUpdateExtension.class); diff --git a/java/google/registry/flows/domain/DomainCheckFlow.java b/java/google/registry/flows/domain/DomainCheckFlow.java index 847fa108f..fa34255dc 100644 --- a/java/google/registry/flows/domain/DomainCheckFlow.java +++ b/java/google/registry/flows/domain/DomainCheckFlow.java @@ -48,6 +48,8 @@ import google.registry.model.registry.label.ReservationType; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that checks whether a domain can be provisioned. * @@ -81,6 +83,8 @@ public class DomainCheckFlow extends BaseDomainCheckFlow { protected RegTypeCheckExtension regTypeExtension; + @Inject DomainCheckFlow() {} + @Override protected void initDomainCheckFlow() throws EppException { registerExtensions( diff --git a/java/google/registry/flows/domain/DomainCreateFlow.java b/java/google/registry/flows/domain/DomainCreateFlow.java index 5c5939531..310e3cbcd 100644 --- a/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/java/google/registry/flows/domain/DomainCreateFlow.java @@ -41,6 +41,8 @@ import google.registry.tmch.LordnTask; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that creates a new domain resource. * @@ -101,6 +103,8 @@ public class DomainCreateFlow extends DomainCreateOrAllocateFlow { protected RegTypeCreateExtension regTypeExtension; + @Inject DomainCreateFlow() {} + private boolean isAnchorTenant() { return isAnchorTenantViaReservation || isAnchorTenantViaExtension; } diff --git a/java/google/registry/flows/domain/DomainDeleteFlow.java b/java/google/registry/flows/domain/DomainDeleteFlow.java index 12ae06861..96e7800ab 100644 --- a/java/google/registry/flows/domain/DomainDeleteFlow.java +++ b/java/google/registry/flows/domain/DomainDeleteFlow.java @@ -55,6 +55,8 @@ import org.joda.money.CurrencyUnit; import org.joda.money.Money; import org.joda.time.DateTime; +import javax.inject.Inject; + /** * An EPP flow that deletes a domain resource. * @@ -73,6 +75,8 @@ public class DomainDeleteFlow extends ResourceSyncDeleteFlow credits; + @Inject DomainDeleteFlow() {} + @Override protected void initResourceCreateOrMutateFlow() throws EppException { registerExtensions(SecDnsUpdateExtension.class); diff --git a/java/google/registry/flows/domain/DomainInfoFlow.java b/java/google/registry/flows/domain/DomainInfoFlow.java index 336e7dad3..c3d7fd820 100644 --- a/java/google/registry/flows/domain/DomainInfoFlow.java +++ b/java/google/registry/flows/domain/DomainInfoFlow.java @@ -31,6 +31,8 @@ import google.registry.model.eppoutput.EppResponse.ResponseExtension; import java.util.List; +import javax.inject.Inject; + /** * An EPP flow that reads a domain. * @@ -45,6 +47,8 @@ public class DomainInfoFlow extends BaseDomainInfoFlow protected List registrationTypes; + @Inject DomainInfoFlow() {} + @Override protected void initSingleResourceFlow() throws EppException { registerExtensions(FeeInfoExtension.class); diff --git a/java/google/registry/flows/domain/DomainRenewFlow.java b/java/google/registry/flows/domain/DomainRenewFlow.java index b8d75e0ba..8e4da5503 100644 --- a/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/java/google/registry/flows/domain/DomainRenewFlow.java @@ -59,6 +59,8 @@ import org.joda.time.DateTime; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that updates a domain resource. * @@ -86,6 +88,8 @@ public class DomainRenewFlow extends OwnedResourceMutateFlow getDisallowedStatuses() { return RENEW_DISALLOWED_STATUSES; diff --git a/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index ffe8e89c5..b3aa457cb 100644 --- a/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -54,6 +54,8 @@ import google.registry.model.reporting.HistoryEntry; import org.joda.money.Money; import org.joda.time.DateTime; +import javax.inject.Inject; + /** * An EPP flow that requests that a deleted domain be restored. * @@ -77,6 +79,8 @@ public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow { + @Inject DomainTransferApproveFlow() {} + @Override protected void verifyOwnedResourcePendingTransferMutationAllowed() throws EppException { checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld()); diff --git a/java/google/registry/flows/domain/DomainTransferCancelFlow.java b/java/google/registry/flows/domain/DomainTransferCancelFlow.java index 1c1b85684..c5253db0f 100644 --- a/java/google/registry/flows/domain/DomainTransferCancelFlow.java +++ b/java/google/registry/flows/domain/DomainTransferCancelFlow.java @@ -25,6 +25,8 @@ import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that cancels a pending transfer on a {@link DomainResource}. * @@ -37,6 +39,8 @@ import google.registry.model.reporting.HistoryEntry; public class DomainTransferCancelFlow extends ResourceTransferCancelFlow { + @Inject DomainTransferCancelFlow() {} + /** * Reopen the autorenew event and poll message that we closed for the implicit transfer. * This may end up recreating the autorenew poll message if it was deleted when the transfer diff --git a/java/google/registry/flows/domain/DomainTransferQueryFlow.java b/java/google/registry/flows/domain/DomainTransferQueryFlow.java index 4ce787265..209c333ed 100644 --- a/java/google/registry/flows/domain/DomainTransferQueryFlow.java +++ b/java/google/registry/flows/domain/DomainTransferQueryFlow.java @@ -18,6 +18,8 @@ import google.registry.flows.ResourceTransferQueryFlow; import google.registry.model.domain.DomainCommand.Transfer; import google.registry.model.domain.DomainResource; +import javax.inject.Inject; + /** * An EPP flow that queries a pending transfer on a {@link DomainResource}. * @@ -26,4 +28,6 @@ import google.registry.model.domain.DomainResource; * @error {@link google.registry.flows.ResourceTransferQueryFlow.NoTransferHistoryToQueryException} * @error {@link google.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException} */ -public class DomainTransferQueryFlow extends ResourceTransferQueryFlow {} +public class DomainTransferQueryFlow extends ResourceTransferQueryFlow { + @Inject DomainTransferQueryFlow() {} +} diff --git a/java/google/registry/flows/domain/DomainTransferRejectFlow.java b/java/google/registry/flows/domain/DomainTransferRejectFlow.java index fcc660a28..ef90c07ca 100644 --- a/java/google/registry/flows/domain/DomainTransferRejectFlow.java +++ b/java/google/registry/flows/domain/DomainTransferRejectFlow.java @@ -25,6 +25,8 @@ import google.registry.model.domain.DomainResource; import google.registry.model.domain.DomainResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that rejects a pending transfer on a {@link DomainResource}. * @@ -37,6 +39,8 @@ import google.registry.model.reporting.HistoryEntry; public class DomainTransferRejectFlow extends ResourceTransferRejectFlow { + @Inject DomainTransferRejectFlow() {} + @Override protected void verifyOwnedResourcePendingTransferMutationAllowed() throws EppException { checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld()); diff --git a/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/java/google/registry/flows/domain/DomainTransferRequestFlow.java index 7d31b3eae..1538c23b0 100644 --- a/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -56,6 +56,8 @@ import org.joda.time.Duration; import java.util.HashSet; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that requests a transfer on a {@link DomainResource}. * @@ -97,6 +99,8 @@ public class DomainTransferRequestFlow */ private FeeTransferExtension feeTransfer; + @Inject DomainTransferRequestFlow() {} + @Override protected Duration getAutomaticTransferLength() { return Registry.get(existingResource.getTld()).getAutomaticTransferLength(); diff --git a/java/google/registry/flows/domain/DomainUpdateFlow.java b/java/google/registry/flows/domain/DomainUpdateFlow.java index e9e9df15c..fea0ca152 100644 --- a/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -39,6 +39,8 @@ import org.joda.time.DateTime; import java.util.Set; +import javax.inject.Inject; + /** * An EPP flow that updates a domain resource. * @@ -70,6 +72,8 @@ public class DomainUpdateFlow extends BaseDomainUpdateFlow { + + @Inject HostCheckFlow() {} + @Override protected CheckData getCheckData() { Set existingIds = checkResourcesExist(resourceClass, targetIds, now); diff --git a/java/google/registry/flows/host/HostCreateFlow.java b/java/google/registry/flows/host/HostCreateFlow.java index 6ef87520e..2d0d0d8a0 100644 --- a/java/google/registry/flows/host/HostCreateFlow.java +++ b/java/google/registry/flows/host/HostCreateFlow.java @@ -40,6 +40,8 @@ import google.registry.model.host.HostResource.Builder; import google.registry.model.ofy.ObjectifyService; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that creates a new host resource. * @@ -67,6 +69,8 @@ public class HostCreateFlow extends ResourceCreateFlow> superordinateDomain; + @Inject HostCreateFlow() {} + @Override protected void initResourceCreateOrMutateFlow() throws EppException { superordinateDomain = Optional.fromNullable(lookupSuperordinateDomain( diff --git a/java/google/registry/flows/host/HostDeleteFlow.java b/java/google/registry/flows/host/HostDeleteFlow.java index 526d7876d..138bbc2bc 100644 --- a/java/google/registry/flows/host/HostDeleteFlow.java +++ b/java/google/registry/flows/host/HostDeleteFlow.java @@ -36,6 +36,8 @@ import google.registry.model.host.HostResource; import google.registry.model.host.HostResource.Builder; import google.registry.model.reporting.HistoryEntry; +import javax.inject.Inject; + /** * An EPP flow that deletes a host resource. * @@ -49,6 +51,8 @@ public class HostDeleteFlow extends ResourceAsyncDeleteFlow ref) { // Query for the first few linked domains, and if found, actually load them. The query is diff --git a/java/google/registry/flows/host/HostInfoFlow.java b/java/google/registry/flows/host/HostInfoFlow.java index 7eece7515..67ff20dd8 100644 --- a/java/google/registry/flows/host/HostInfoFlow.java +++ b/java/google/registry/flows/host/HostInfoFlow.java @@ -18,9 +18,13 @@ import google.registry.flows.ResourceInfoFlow; import google.registry.model.host.HostCommand; import google.registry.model.host.HostResource; +import javax.inject.Inject; + /** * An EPP flow that reads a host. * * @error {@link google.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException} */ -public class HostInfoFlow extends ResourceInfoFlow {} +public class HostInfoFlow extends ResourceInfoFlow { + @Inject HostInfoFlow() {} +} diff --git a/java/google/registry/flows/host/HostUpdateFlow.java b/java/google/registry/flows/host/HostUpdateFlow.java index 4ab9a0984..f10c8a4eb 100644 --- a/java/google/registry/flows/host/HostUpdateFlow.java +++ b/java/google/registry/flows/host/HostUpdateFlow.java @@ -47,6 +47,8 @@ import org.joda.time.Duration; import java.util.Objects; +import javax.inject.Inject; + /** * An EPP flow that updates a host resource. * @@ -72,6 +74,8 @@ public class HostUpdateFlow extends ResourceUpdateFlow) JSONValue.parse(((FakeResponse) action.response).getPayload()); } diff --git a/javatests/google/registry/flows/EppCommitLogsTest.java b/javatests/google/registry/flows/EppCommitLogsTest.java new file mode 100644 index 000000000..a932403e9 --- /dev/null +++ b/javatests/google/registry/flows/EppCommitLogsTest.java @@ -0,0 +1,171 @@ +// Copyright 2016 The Domain Registry 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; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.EppResourceUtils.loadAtPointInTime; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.persistActiveContact; +import static google.registry.testing.DatastoreHelper.persistActiveHost; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.joda.time.DateTimeZone.UTC; +import static org.joda.time.Duration.standardDays; + +import com.googlecode.objectify.Key; + +import google.registry.flows.EppTestComponent.FakesAndMocksModule; +import google.registry.model.domain.DomainResource; +import google.registry.model.ofy.Ofy; +import google.registry.testing.AppEngineRule; +import google.registry.testing.EppLoader; +import google.registry.testing.ExceptionRule; +import google.registry.testing.FakeClock; +import google.registry.testing.FakeHttpSession; +import google.registry.testing.InjectRule; +import google.registry.testing.ShardableTestCase; + +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Test that domain flows create the commit logs needed to reload at points in the past. */ +@RunWith(JUnit4.class) +public class EppCommitLogsTest extends ShardableTestCase { + + @Rule + public final AppEngineRule appEngine = AppEngineRule.builder() + .withDatastore() + .withTaskQueue() + .build(); + + @Rule + public final ExceptionRule thrown = new ExceptionRule(); + + @Rule + public final InjectRule inject = new InjectRule(); + + private final FakeClock clock = new FakeClock(DateTime.now(UTC)); + private EppLoader eppLoader; + + @Before + public void init() throws Exception { + createTld("tld"); + inject.setStaticField(Ofy.class, "clock", clock); + } + + private void runFlow() throws Exception { + SessionMetadata sessionMetadata = new HttpSessionMetadata(new FakeHttpSession()); + sessionMetadata.setClientId("TheRegistrar"); + DaggerEppTestComponent.builder() + .fakesAndMocksModule(new FakesAndMocksModule(clock)) + .build() + .startRequest() + .flowComponentBuilder() + .flowModule(new FlowModule.Builder() + .setSessionMetadata(sessionMetadata) + .setCredentials(new PasswordOnlyTransportCredentials()) + .setEppRequestSource(EppRequestSource.UNIT_TEST) + .setIsDryRun(false) + .setIsSuperuser(false) + .setInputXmlBytes(eppLoader.getEppXml().getBytes(UTF_8)) + .setEppInput(eppLoader.getEpp()) + .build()) + .build() + .flowRunner() + .run(); + } + + @Test + public void testLoadAtPointInTime() throws Exception { + clock.setTo(DateTime.parse("1984-12-18T12:30Z")); // not midnight + + persistActiveHost("ns1.example.net"); + persistActiveHost("ns2.example.net"); + persistActiveContact("jd1234"); + persistActiveContact("sh8013"); + + clock.advanceBy(standardDays(1)); + DateTime timeAtCreate = clock.nowUtc(); + clock.setTo(timeAtCreate); + eppLoader = new EppLoader(this, "domain_create.xml"); + runFlow(); + ofy().clearSessionCache(); + Key key = Key.create(ofy().load().type(DomainResource.class).first().now()); + DomainResource domainAfterCreate = ofy().load().key(key).now(); + assertThat(domainAfterCreate.getFullyQualifiedDomainName()).isEqualTo("example.tld"); + + clock.advanceBy(standardDays(2)); + DateTime timeAtFirstUpdate = clock.nowUtc(); + eppLoader = new EppLoader(this, "domain_update_dsdata_add.xml"); + runFlow(); + ofy().clearSessionCache(); + + DomainResource domainAfterFirstUpdate = ofy().load().key(key).now(); + assertThat(domainAfterCreate).isNotEqualTo(domainAfterFirstUpdate); + + clock.advanceOneMilli(); // same day as first update + DateTime timeAtSecondUpdate = clock.nowUtc(); + eppLoader = new EppLoader(this, "domain_update_dsdata_rem.xml"); + runFlow(); + ofy().clearSessionCache(); + DomainResource domainAfterSecondUpdate = ofy().load().key(key).now(); + + clock.advanceBy(standardDays(2)); + DateTime timeAtDelete = clock.nowUtc(); // before 'add' grace period ends + eppLoader = new EppLoader(this, "domain_delete.xml"); + runFlow(); + ofy().clearSessionCache(); + + assertThat(domainAfterFirstUpdate).isNotEqualTo(domainAfterSecondUpdate); + + // Point-in-time can only rewind an object from the current version, not roll forward. + DomainResource latest = ofy().load().key(key).now(); + + // Creation time has millisecond granularity due to isActive() check. + ofy().clearSessionCache(); + assertThat(loadAtPointInTime(latest, timeAtCreate.minusMillis(1)).now()).isNull(); + assertThat(loadAtPointInTime(latest, timeAtCreate).now()).isNotNull(); + assertThat(loadAtPointInTime(latest, timeAtCreate.plusMillis(1)).now()).isNotNull(); + + ofy().clearSessionCache(); + assertThat(loadAtPointInTime(latest, timeAtCreate.plusDays(1)).now()) + .isEqualTo(domainAfterCreate); + + // Both updates happened on the same day. Since the revisions field has day granularity, the + // reference to the first update should have been overwritten by the second, and its timestamp + // rolled forward. So we have to fall back to the last revision before midnight. + ofy().clearSessionCache(); + assertThat(loadAtPointInTime(latest, timeAtFirstUpdate).now()) + .isEqualTo(domainAfterCreate); + + ofy().clearSessionCache(); + assertThat(loadAtPointInTime(latest, timeAtSecondUpdate).now()) + .isEqualTo(domainAfterSecondUpdate); + + ofy().clearSessionCache(); + assertThat(loadAtPointInTime(latest, timeAtSecondUpdate.plusDays(1)).now()) + .isEqualTo(domainAfterSecondUpdate); + + // Deletion time has millisecond granularity due to isActive() check. + ofy().clearSessionCache(); + assertThat(loadAtPointInTime(latest, timeAtDelete.minusMillis(1)).now()).isNotNull(); + assertThat(loadAtPointInTime(latest, timeAtDelete).now()).isNull(); + assertThat(loadAtPointInTime(latest, timeAtDelete.plusMillis(1)).now()).isNull(); + } +} diff --git a/javatests/google/registry/flows/EppLifecycleDomainTest.java b/javatests/google/registry/flows/EppLifecycleDomainTest.java index 3903ec728..a9e0190b9 100644 --- a/javatests/google/registry/flows/EppLifecycleDomainTest.java +++ b/javatests/google/registry/flows/EppLifecycleDomainTest.java @@ -15,6 +15,7 @@ package google.registry.flows; import static google.registry.testing.DatastoreHelper.createTld; +import static google.registry.testing.DatastoreHelper.createTlds; import static google.registry.util.DateTimeUtils.START_OF_TIME; import com.google.common.collect.ImmutableMap; @@ -44,7 +45,7 @@ public class EppLifecycleDomainTest extends EppTestCase { @Before public void initTld() { - createTld("example"); + createTlds("example", "tld"); } /** Create the two administrative contacts and two hosts. */ @@ -129,7 +130,7 @@ public class EppLifecycleDomainTest extends EppTestCase { "domain_create_response.xml", DateTime.parse("2000-06-01T00:02:00Z")); - // Delete domain example.com after its add grace period has expired. + // Delete domain example.tld after its add grace period has expired. assertCommandAndResponse( "domain_delete.xml", "generic_success_action_pending_response.xml", @@ -338,7 +339,7 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-01T00:01:00Z")); assertCommandAndResponse( "poll_ack.xml", - ImmutableMap.of("ID", "1-A-EXAMPLE-16-20"), + ImmutableMap.of("ID", "1-B-EXAMPLE-17-21"), "poll_ack_response_empty.xml", null, DateTime.parse("2001-01-01T00:01:00Z")); @@ -350,7 +351,7 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-06T00:01:00Z")); assertCommandAndResponse( "poll_ack.xml", - ImmutableMap.of("ID", "1-A-EXAMPLE-16-22"), + ImmutableMap.of("ID", "1-B-EXAMPLE-17-23"), "poll_ack_response_empty.xml", null, DateTime.parse("2001-01-06T00:01:00Z")); @@ -366,7 +367,7 @@ public class EppLifecycleDomainTest extends EppTestCase { DateTime.parse("2001-01-06T00:02:00Z")); assertCommandAndResponse( "poll_ack.xml", - ImmutableMap.of("ID", "1-A-EXAMPLE-16-21"), + ImmutableMap.of("ID", "1-B-EXAMPLE-17-22"), "poll_ack_response_empty.xml", null, DateTime.parse("2001-01-06T00:02:00Z")); diff --git a/javatests/google/registry/flows/EppTestCase.java b/javatests/google/registry/flows/EppTestCase.java index 2a53eecea..aafa6c902 100644 --- a/javatests/google/registry/flows/EppTestCase.java +++ b/javatests/google/registry/flows/EppTestCase.java @@ -21,12 +21,11 @@ import static google.registry.xml.XmlTestUtils.assertXmlEqualsWithMessage; import static java.nio.charset.StandardCharsets.UTF_8; import static javax.servlet.http.HttpServletResponse.SC_OK; import static org.joda.time.DateTimeZone.UTC; -import static org.mockito.Mockito.mock; import com.google.common.net.MediaType; +import google.registry.flows.EppTestComponent.FakesAndMocksModule; import google.registry.model.ofy.Ofy; -import google.registry.monitoring.whitebox.EppMetrics; import google.registry.testing.FakeClock; import google.registry.testing.FakeHttpSession; import google.registry.testing.FakeResponse; @@ -100,8 +99,7 @@ public class EppTestCase extends ShardableTestCase { // When a session is invalidated, reset the sessionMetadata field. super.invalidate(); EppTestCase.this.sessionMetadata = null; - } - }; + }}; } String actualOutput = executeXmlCommand(input); assertXmlEqualsWithMessage( @@ -118,14 +116,16 @@ public class EppTestCase extends ShardableTestCase { EppRequestHandler handler = new EppRequestHandler(); FakeResponse response = new FakeResponse(); handler.response = response; - handler.eppController = new EppController(); - handler.eppController.clock = clock; - handler.eppController.metrics = mock(EppMetrics.class); + handler.eppController = DaggerEppTestComponent.builder() + .fakesAndMocksModule(new FakesAndMocksModule(clock)) + .build() + .startRequest() + .eppController(); handler.executeEpp( sessionMetadata, credentials, EppRequestSource.UNIT_TEST, - false, + false, // Not dryRun. isSuperuser, inputXml.getBytes(UTF_8)); assertThat(response.getStatus()).isEqualTo(SC_OK); diff --git a/javatests/google/registry/flows/EppTestComponent.java b/javatests/google/registry/flows/EppTestComponent.java new file mode 100644 index 000000000..5d319735f --- /dev/null +++ b/javatests/google/registry/flows/EppTestComponent.java @@ -0,0 +1,70 @@ +// Copyright 2016 The Domain Registry 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; + +import static org.mockito.Mockito.mock; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; +import dagger.Subcomponent; + +import google.registry.monitoring.whitebox.EppMetrics; +import google.registry.request.RequestScope; +import google.registry.testing.FakeClock; +import google.registry.util.Clock; + +import javax.inject.Singleton; + +/** Dagger component for running EPP tests. */ +@Singleton +@Component( + modules = { + EppTestComponent.FakesAndMocksModule.class + }) +interface EppTestComponent { + + RequestComponent startRequest(); + + /** Module for injecting fakes and mocks. */ + @Module + static class FakesAndMocksModule { + final FakeClock clock; + final EppMetrics metrics; + + FakesAndMocksModule(FakeClock clock) { + this.clock = clock; + this.metrics = mock(EppMetrics.class); + } + + @Provides + Clock provideClock() { + return clock; + } + + @Provides + EppMetrics provideMetrics() { + return metrics; + } + } + + /** Subcomponent for request scoped injections. */ + @RequestScope + @Subcomponent + interface RequestComponent { + EppController eppController(); + FlowComponent.Builder flowComponentBuilder(); + } +} diff --git a/javatests/google/registry/flows/FlowTestCase.java b/javatests/google/registry/flows/FlowTestCase.java index 1bc959a2e..02ad186df 100644 --- a/javatests/google/registry/flows/FlowTestCase.java +++ b/javatests/google/registry/flows/FlowTestCase.java @@ -34,13 +34,12 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; +import google.registry.flows.EppTestComponent.FakesAndMocksModule; import google.registry.flows.picker.FlowPicker; import google.registry.model.billing.BillingEvent; import google.registry.model.domain.GracePeriod; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.eppcommon.ProtocolDefinition; -import google.registry.model.eppcommon.Trid; -import google.registry.model.eppinput.EppInput; import google.registry.model.eppoutput.EppOutput; import google.registry.model.ofy.Ofy; import google.registry.model.poll.PollMessage; @@ -86,8 +85,6 @@ public abstract class FlowTestCase { @Rule public final InjectRule inject = new InjectRule(); - private Class flowClass; - protected EppLoader eppLoader; protected SessionMetadata sessionMetadata; protected FakeClock clock = new FakeClock(DateTime.now(UTC)); @@ -120,29 +117,8 @@ public abstract class FlowTestCase { return readResourceUtf8(getClass(), "testdata/" + filename); } - /** Load a flow from an epp object. */ - private FlowRunner getFlowRunner(CommitMode commitMode, UserPrivileges userPrivileges) - throws Exception { - EppInput eppInput = eppLoader.getEpp(); - flowClass = firstNonNull(flowClass, FlowPicker.getFlowClass(eppInput)); - Class expectedFlowClass = new TypeInstantiator(getClass()){}.getExactType(); - assertThat(flowClass).isEqualTo(expectedFlowClass); - return new FlowRunner( - flowClass, - eppInput, - getTrid(), - sessionMetadata, - credentials, - eppRequestSource, - commitMode.equals(CommitMode.DRY_RUN), - userPrivileges.equals(UserPrivileges.SUPERUSER), - "".getBytes(), - null, - clock); - } - - protected Trid getTrid() throws Exception { - return Trid.create(eppLoader.getEpp().getCommandWrapper().getClTrid(), "server-trid"); + protected String getClientTrid() throws Exception { + return eppLoader.getEpp().getCommandWrapper().getClTrid(); } /** Gets the client ID that the flow will run as. */ @@ -156,8 +132,15 @@ public abstract class FlowTestCase { } public void assertTransactionalFlow(boolean isTransactional) throws Exception { - assertThat(getFlowRunner(CommitMode.LIVE, UserPrivileges.NORMAL).isTransactional()) - .isEqualTo(isTransactional); + Class flowClass = FlowPicker.getFlowClass(eppLoader.getEpp()); + if (isTransactional) { + assertThat(flowClass).isAssignableTo(TransactionalFlow.class); + } else { + // There's no "isNotAssignableTo" in Truth. + assertThat(TransactionalFlow.class.isAssignableFrom(flowClass)) + .named(flowClass.getSimpleName() + " implements TransactionalFlow") + .isFalse(); + } } public void assertNoHistory() throws Exception { @@ -273,36 +256,67 @@ public abstract class FlowTestCase { .containsExactlyElementsIn(FluentIterable.from(asList(expected)).transform(idStripper)); } - /** Run a flow, and attempt to marshal the result to EPP or throw if it doesn't validate. */ + private EppOutput runFlowInternal(CommitMode commitMode, UserPrivileges userPrivileges) + throws Exception { + // Assert that the xml triggers the flow we expect. + assertThat(FlowPicker.getFlowClass(eppLoader.getEpp())) + .isEqualTo(new TypeInstantiator(getClass()){}.getExactType()); + // Run the flow. + return DaggerEppTestComponent.builder() + .fakesAndMocksModule(new FakesAndMocksModule(clock)) + .build() + .startRequest() + .flowComponentBuilder() + .flowModule(new FlowModule.Builder() + .setSessionMetadata(sessionMetadata) + .setCredentials(credentials) + .setEppRequestSource(eppRequestSource) + .setIsDryRun(commitMode.equals(CommitMode.DRY_RUN)) + .setIsSuperuser(userPrivileges.equals(UserPrivileges.SUPERUSER)) + .setInputXmlBytes(eppLoader.getEppXml().getBytes(UTF_8)) + .setEppInput(eppLoader.getEpp()) + .build()) + .build() + .flowRunner() + .run(); + } + + /** Run a flow and marshal the result to EPP, or throw if it doesn't validate. */ public EppOutput runFlow(CommitMode commitMode, UserPrivileges userPrivileges) throws Exception { - EppOutput output = getFlowRunner(commitMode, userPrivileges).run(); + EppOutput output = runFlowInternal(commitMode, userPrivileges); marshal(output, ValidationMode.STRICT); return output; } + /** Shortcut to call {@link #runFlow(CommitMode, UserPrivileges)} as normal user and live run. */ public EppOutput runFlow() throws Exception { return runFlow(CommitMode.LIVE, UserPrivileges.NORMAL); } + /** Run a flow, marshal the result to EPP, and assert that the output is as expected. */ public void runFlowAssertResponse( CommitMode commitMode, UserPrivileges userPrivileges, String xml, String... ignoredPaths) throws Exception { - EppOutput eppOutput = getFlowRunner(commitMode, userPrivileges).run(); - if (eppOutput.isResponse()) { - assertThat(eppOutput.isSuccess()).isTrue(); + // Always ignore the server trid, since it's generated and meaningless to flow correctness. + String[] ignoredPathsPlusTrid = FluentIterable.from(ignoredPaths) + .append("epp.response.trID.svTRID") + .toArray(String.class); + EppOutput output = runFlowInternal(commitMode, userPrivileges); + if (output.isResponse()) { + assertThat(output.isSuccess()).isTrue(); } try { assertXmlEquals( - xml, new String(marshal(eppOutput, ValidationMode.STRICT), UTF_8), ignoredPaths); + xml, new String(marshal(output, ValidationMode.STRICT), UTF_8), ignoredPathsPlusTrid); } catch (Throwable e) { assertXmlEquals( - xml, new String(marshal(eppOutput, ValidationMode.LENIENT), UTF_8), ignoredPaths); + xml, new String(marshal(output, ValidationMode.LENIENT), UTF_8), ignoredPathsPlusTrid); // If it was a marshaling error, augment the output. throw new Exception( String.format( "Invalid xml.\nExpected:\n%s\n\nActual:\n%s\n", xml, - marshal(eppOutput, ValidationMode.LENIENT)), + marshal(output, ValidationMode.LENIENT)), e); } // Clear the cache so that we don't see stale results in tests. diff --git a/javatests/google/registry/flows/contact/ContactTransferRequestFlowTest.java b/javatests/google/registry/flows/contact/ContactTransferRequestFlowTest.java index 3f0e511b1..3f2418878 100644 --- a/javatests/google/registry/flows/contact/ContactTransferRequestFlowTest.java +++ b/javatests/google/registry/flows/contact/ContactTransferRequestFlowTest.java @@ -71,7 +71,7 @@ public class ContactTransferRequestFlowTest .hasTransferStatus(TransferStatus.PENDING).and() .hasTransferGainingClientId("NewRegistrar").and() .hasTransferLosingClientId("TheRegistrar").and() - .hasTransferRequestTrid(getTrid()).and() + .hasTransferRequestClientTrid(getClientTrid()).and() .hasCurrentSponsorClientId("TheRegistrar").and() .hasPendingTransferExpirationTime(afterTransfer).and() .hasOnlyOneHistoryEntryWhich() diff --git a/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java b/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java index b0935a846..14584b8f7 100644 --- a/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java +++ b/javatests/google/registry/flows/domain/DomainTransferRequestFlowTest.java @@ -64,7 +64,6 @@ import google.registry.model.domain.GracePeriod; import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.eppcommon.AuthInfo.PasswordAuth; import google.registry.model.eppcommon.StatusValue; -import google.registry.model.eppcommon.Trid; import google.registry.model.poll.PendingActionNotificationResponse; import google.registry.model.poll.PollMessage; import google.registry.model.registrar.Registrar; @@ -95,7 +94,7 @@ public class DomainTransferRequestFlowTest .hasTransferStatus(TransferStatus.PENDING).and() .hasTransferGainingClientId("NewRegistrar").and() .hasTransferLosingClientId("TheRegistrar").and() - .hasTransferRequestTrid(getTrid()).and() + .hasTransferRequestClientTrid(getClientTrid()).and() .hasCurrentSponsorClientId("TheRegistrar").and() .hasPendingTransferExpirationTime( clock.nowUtc().plus(Registry.get("tld").getAutomaticTransferLength())).and() @@ -228,8 +227,7 @@ public class DomainTransferRequestFlowTest PendingActionNotificationResponse panData = Iterables.getOnlyElement(FluentIterable .from(transferApprovedPollMessage.getResponseData()) .filter(PendingActionNotificationResponse.class)); - assertThat(panData.getTrid()) - .isEqualTo(Trid.create("ABC-12345", "server-trid")); + assertThat(panData.getTrid().getClientTransactionId()).isEqualTo("ABC-12345"); assertThat(panData.getActionResult()).isTrue(); // Two poll messages on the losing registrar's side at the implicit transfer time: a diff --git a/javatests/google/registry/model/testdata/domain_create.xml b/javatests/google/registry/flows/testdata/domain_create.xml similarity index 100% rename from javatests/google/registry/model/testdata/domain_create.xml rename to javatests/google/registry/flows/testdata/domain_create.xml diff --git a/javatests/google/registry/flows/testdata/domain_create_no_hosts_or_dsdata.xml b/javatests/google/registry/flows/testdata/domain_create_no_hosts_or_dsdata.xml index 88c822e7e..41c6a586e 100644 --- a/javatests/google/registry/flows/testdata/domain_create_no_hosts_or_dsdata.xml +++ b/javatests/google/registry/flows/testdata/domain_create_no_hosts_or_dsdata.xml @@ -3,7 +3,7 @@ - example.example + example.tld 2 jd1234 sh8013 diff --git a/javatests/google/registry/flows/testdata/domain_create_response.xml b/javatests/google/registry/flows/testdata/domain_create_response.xml index 35c386ba7..6df6fd6ab 100644 --- a/javatests/google/registry/flows/testdata/domain_create_response.xml +++ b/javatests/google/registry/flows/testdata/domain_create_response.xml @@ -6,7 +6,7 @@ - example.example + example.tld 2000-06-01T00:02:00.0Z 2002-06-01T00:02:00.0Z diff --git a/javatests/google/registry/flows/testdata/domain_delete.xml b/javatests/google/registry/flows/testdata/domain_delete.xml index 0ef1a47d1..a0f5cc345 100644 --- a/javatests/google/registry/flows/testdata/domain_delete.xml +++ b/javatests/google/registry/flows/testdata/domain_delete.xml @@ -3,7 +3,7 @@ - example.example + example.tld ABC-12345 diff --git a/javatests/google/registry/flows/testdata/domain_info.xml b/javatests/google/registry/flows/testdata/domain_info.xml index e32700d25..15a31a9d1 100644 --- a/javatests/google/registry/flows/testdata/domain_info.xml +++ b/javatests/google/registry/flows/testdata/domain_info.xml @@ -3,7 +3,7 @@ - example.example + example.tld ABC-12345 diff --git a/javatests/google/registry/flows/testdata/domain_info_response_pendingdelete.xml b/javatests/google/registry/flows/testdata/domain_info_response_pendingdelete.xml index 46b2737a8..8471fda0b 100644 --- a/javatests/google/registry/flows/testdata/domain_info_response_pendingdelete.xml +++ b/javatests/google/registry/flows/testdata/domain_info_response_pendingdelete.xml @@ -7,7 +7,7 @@ - example.example + example.tld %ROID% diff --git a/javatests/google/registry/model/testdata/domain_update_dsdata_add.xml b/javatests/google/registry/flows/testdata/domain_update_dsdata_add.xml similarity index 100% rename from javatests/google/registry/model/testdata/domain_update_dsdata_add.xml rename to javatests/google/registry/flows/testdata/domain_update_dsdata_add.xml diff --git a/javatests/google/registry/model/testdata/domain_update_dsdata_rem.xml b/javatests/google/registry/flows/testdata/domain_update_dsdata_rem.xml similarity index 100% rename from javatests/google/registry/model/testdata/domain_update_dsdata_rem.xml rename to javatests/google/registry/flows/testdata/domain_update_dsdata_rem.xml diff --git a/javatests/google/registry/flows/testdata/domain_update_restore_request.xml b/javatests/google/registry/flows/testdata/domain_update_restore_request.xml index 85a294e98..124bc0b89 100644 --- a/javatests/google/registry/flows/testdata/domain_update_restore_request.xml +++ b/javatests/google/registry/flows/testdata/domain_update_restore_request.xml @@ -3,7 +3,7 @@ - example.example + example.tld diff --git a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml index 472dd18b7..cb8a34b05 100644 --- a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml +++ b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_request.xml @@ -3,7 +3,7 @@ Command completed successfully; ack to dequeue - + 2001-01-01T00:00:00Z Transfer requested. diff --git a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml index 640babb06..851c9dc3a 100644 --- a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml +++ b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_loser.xml @@ -3,7 +3,7 @@ Command completed successfully; ack to dequeue - + 2001-01-06T00:00:00Z Transfer approved. diff --git a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml index ed2c57b8e..ce140c107 100644 --- a/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml +++ b/javatests/google/registry/flows/testdata/poll_response_domain_transfer_server_approve_winner.xml @@ -4,7 +4,7 @@ Command completed successfully; ack to dequeue - + 2001-01-06T00:00:00Z Transfer approved. diff --git a/javatests/google/registry/model/EppResourceUtilsTest.java b/javatests/google/registry/model/EppResourceUtilsTest.java index f858a1b6c..095530c85 100644 --- a/javatests/google/registry/model/EppResourceUtilsTest.java +++ b/javatests/google/registry/model/EppResourceUtilsTest.java @@ -15,35 +15,19 @@ package google.registry.model; import static com.google.common.truth.Truth.assertThat; -import static google.registry.flows.picker.FlowPicker.getFlowClass; import static google.registry.model.EppResourceUtils.loadAtPointInTime; -import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.testing.DatastoreHelper.createTld; import static google.registry.testing.DatastoreHelper.newHostResource; -import static google.registry.testing.DatastoreHelper.persistActiveContact; -import static google.registry.testing.DatastoreHelper.persistActiveHost; import static google.registry.testing.DatastoreHelper.persistResource; import static google.registry.testing.DatastoreHelper.persistResourceWithCommitLog; import static google.registry.util.DateTimeUtils.START_OF_TIME; import static org.joda.time.DateTimeZone.UTC; -import static org.joda.time.Duration.standardDays; -import com.googlecode.objectify.Key; - -import google.registry.flows.EppRequestSource; -import google.registry.flows.FlowRunner; -import google.registry.flows.HttpSessionMetadata; -import google.registry.flows.PasswordOnlyTransportCredentials; -import google.registry.flows.SessionMetadata; -import google.registry.model.domain.DomainResource; -import google.registry.model.eppcommon.Trid; import google.registry.model.host.HostResource; import google.registry.model.ofy.Ofy; import google.registry.testing.AppEngineRule; -import google.registry.testing.EppLoader; import google.registry.testing.ExceptionRule; import google.registry.testing.FakeClock; -import google.registry.testing.FakeHttpSession; import google.registry.testing.InjectRule; import org.joda.time.DateTime; @@ -71,7 +55,6 @@ public class EppResourceUtilsTest { public final InjectRule inject = new InjectRule(); private final FakeClock clock = new FakeClock(DateTime.now(UTC)); - private EppLoader eppLoader; @Before public void init() throws Exception { @@ -79,103 +62,6 @@ public class EppResourceUtilsTest { inject.setStaticField(Ofy.class, "clock", clock); } - private void runFlow() throws Exception { - SessionMetadata sessionMetadata = new HttpSessionMetadata(new FakeHttpSession()); - sessionMetadata.setClientId("TheRegistrar"); - new FlowRunner( - getFlowClass(eppLoader.getEpp()), - eppLoader.getEpp(), - Trid.create(null, "server-trid"), - sessionMetadata, - new PasswordOnlyTransportCredentials(), - EppRequestSource.UNIT_TEST, - false, - false, - "".getBytes(), - null, - clock) - .run(); - } - - /** Test that update flow creates commit logs needed to reload at any arbitrary time. */ - @Test - public void testLoadAtPointInTime() throws Exception { - clock.setTo(DateTime.parse("1984-12-18T12:30Z")); // not midnight - - persistActiveHost("ns1.example.net"); - persistActiveHost("ns2.example.net"); - persistActiveContact("jd1234"); - persistActiveContact("sh8013"); - - clock.advanceBy(standardDays(1)); - DateTime timeAtCreate = clock.nowUtc(); - clock.setTo(timeAtCreate); - eppLoader = new EppLoader(this, "domain_create.xml"); - runFlow(); - ofy().clearSessionCache(); - Key key = Key.create(ofy().load().type(DomainResource.class).first().now()); - DomainResource domainAfterCreate = ofy().load().key(key).now(); - assertThat(domainAfterCreate.getFullyQualifiedDomainName()).isEqualTo("example.tld"); - - clock.advanceBy(standardDays(2)); - DateTime timeAtFirstUpdate = clock.nowUtc(); - eppLoader = new EppLoader(this, "domain_update_dsdata_add.xml"); - runFlow(); - ofy().clearSessionCache(); - - DomainResource domainAfterFirstUpdate = ofy().load().key(key).now(); - assertThat(domainAfterCreate).isNotEqualTo(domainAfterFirstUpdate); - - clock.advanceOneMilli(); // same day as first update - DateTime timeAtSecondUpdate = clock.nowUtc(); - eppLoader = new EppLoader(this, "domain_update_dsdata_rem.xml"); - runFlow(); - ofy().clearSessionCache(); - DomainResource domainAfterSecondUpdate = ofy().load().key(key).now(); - - clock.advanceBy(standardDays(2)); - DateTime timeAtDelete = clock.nowUtc(); // before 'add' grace period ends - eppLoader = new EppLoader(this, "domain_delete.xml"); - runFlow(); - ofy().clearSessionCache(); - - assertThat(domainAfterFirstUpdate).isNotEqualTo(domainAfterSecondUpdate); - - // Point-in-time can only rewind an object from the current version, not roll forward. - DomainResource latest = ofy().load().key(key).now(); - - // Creation time has millisecond granularity due to isActive() check. - ofy().clearSessionCache(); - assertThat(loadAtPointInTime(latest, timeAtCreate.minusMillis(1)).now()).isNull(); - assertThat(loadAtPointInTime(latest, timeAtCreate).now()).isNotNull(); - assertThat(loadAtPointInTime(latest, timeAtCreate.plusMillis(1)).now()).isNotNull(); - - ofy().clearSessionCache(); - assertThat(loadAtPointInTime(latest, timeAtCreate.plusDays(1)).now()) - .isEqualTo(domainAfterCreate); - - // Both updates happened on the same day. Since the revisions field has day granularity, the - // reference to the first update should have been overwritten by the second, and its timestamp - // rolled forward. So we have to fall back to the last revision before midnight. - ofy().clearSessionCache(); - assertThat(loadAtPointInTime(latest, timeAtFirstUpdate).now()) - .isEqualTo(domainAfterCreate); - - ofy().clearSessionCache(); - assertThat(loadAtPointInTime(latest, timeAtSecondUpdate).now()) - .isEqualTo(domainAfterSecondUpdate); - - ofy().clearSessionCache(); - assertThat(loadAtPointInTime(latest, timeAtSecondUpdate.plusDays(1)).now()) - .isEqualTo(domainAfterSecondUpdate); - - // Deletion time has millisecond granularity due to isActive() check. - ofy().clearSessionCache(); - assertThat(loadAtPointInTime(latest, timeAtDelete.minusMillis(1)).now()).isNotNull(); - assertThat(loadAtPointInTime(latest, timeAtDelete).now()).isNull(); - assertThat(loadAtPointInTime(latest, timeAtDelete.plusMillis(1)).now()).isNull(); - } - @Test public void testLoadAtPointInTime_beforeCreated_returnsNull() throws Exception { clock.advanceOneMilli(); diff --git a/javatests/google/registry/model/testdata/domain_delete.xml b/javatests/google/registry/model/testdata/domain_delete.xml deleted file mode 100644 index a0f5cc345..000000000 --- a/javatests/google/registry/model/testdata/domain_delete.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - example.tld - - - ABC-12345 - - diff --git a/javatests/google/registry/testing/AbstractEppResourceSubject.java b/javatests/google/registry/testing/AbstractEppResourceSubject.java index bd828f633..3895112af 100644 --- a/javatests/google/registry/testing/AbstractEppResourceSubject.java +++ b/javatests/google/registry/testing/AbstractEppResourceSubject.java @@ -26,7 +26,6 @@ import com.google.common.truth.Subject; import google.registry.model.EppResource; import google.registry.model.eppcommon.StatusValue; -import google.registry.model.eppcommon.Trid; import google.registry.model.reporting.HistoryEntry; import google.registry.model.transfer.TransferStatus; import google.registry.testing.TruthChainer.And; @@ -205,10 +204,10 @@ abstract class AbstractEppResourceSubject "has transferStatus"); } - public And hasTransferRequestTrid(Trid trid) { + public And hasTransferRequestClientTrid(String clTrid) { return hasValue( - trid, - getSubject().getTransferData().getTransferRequestTrid(), + clTrid, + getSubject().getTransferData().getTransferRequestTrid().getClientTransactionId(), "has trid"); }