diff --git a/core/src/main/java/google/registry/flows/EppController.java b/core/src/main/java/google/registry/flows/EppController.java index 1ef1c015c..51008c0f3 100644 --- a/core/src/main/java/google/registry/flows/EppController.java +++ b/core/src/main/java/google/registry/flows/EppController.java @@ -143,9 +143,6 @@ public final class EppController { /** Creates a response indicating an EPP failure. */ @VisibleForTesting static EppOutput getErrorResponse(Result result, Trid trid) { - return EppOutput.create(new EppResponse.Builder() - .setResult(result) - .setTrid(trid) - .build()); + return EppOutput.create(new EppResponse.Builder().setResult(result).setTrid(trid).build()); } } diff --git a/core/src/main/java/google/registry/flows/EppException.java b/core/src/main/java/google/registry/flows/EppException.java index 6ef2dd3fe..c4dd987f8 100644 --- a/core/src/main/java/google/registry/flows/EppException.java +++ b/core/src/main/java/google/registry/flows/EppException.java @@ -25,10 +25,12 @@ import google.registry.model.eppinput.EppInput.InnerCommand; import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; import google.registry.model.eppoutput.Result; import google.registry.model.eppoutput.Result.Code; +import google.registry.persistence.transaction.TransactionManagerFactory.ReadOnlyModeException; import java.lang.annotation.Documented; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.Target; +import javax.annotation.Nullable; /** Exception used to propagate all failures containing one or more EPP responses. */ public abstract class EppException extends Exception { @@ -37,7 +39,12 @@ public abstract class EppException extends Exception { /** Create an EppException with a custom message. */ private EppException(String message) { - super(message); + this(message, null); + } + + /** Create an EppException with a custom message and cause. */ + private EppException(String message, @Nullable Throwable cause) { + super(message, cause); Code code = getClass().getAnnotation(EppResultCode.class).value(); Preconditions.checkState(!code.isSuccess()); this.result = Result.create(code, message); @@ -255,4 +262,12 @@ public abstract class EppException extends Exception { super("Specified protocol version is not implemented"); } } + + /** Registry is currently undergoing maintenance and is in read-only mode. */ + @EppResultCode(Code.COMMAND_FAILED) + public static class ReadOnlyModeEppException extends EppException { + ReadOnlyModeEppException(ReadOnlyModeException cause) { + super("Registry is currently undergoing maintenance and is in read-only mode", cause); + } + } } diff --git a/core/src/main/java/google/registry/flows/FlowRunner.java b/core/src/main/java/google/registry/flows/FlowRunner.java index e1859ba7c..d7747fc98 100644 --- a/core/src/main/java/google/registry/flows/FlowRunner.java +++ b/core/src/main/java/google/registry/flows/FlowRunner.java @@ -19,6 +19,7 @@ import static google.registry.xml.XmlTransformer.prettyPrint; import com.google.common.base.Strings; import com.google.common.flogger.FluentLogger; +import google.registry.flows.EppException.ReadOnlyModeEppException; import google.registry.flows.FlowModule.DryRun; import google.registry.flows.FlowModule.InputXml; import google.registry.flows.FlowModule.RegistrarId; @@ -28,6 +29,7 @@ import google.registry.flows.session.LoginFlow; import google.registry.model.eppcommon.Trid; import google.registry.model.eppoutput.EppOutput; import google.registry.monitoring.whitebox.EppMetric; +import google.registry.persistence.transaction.TransactionManagerFactory.ReadOnlyModeException; import javax.inject.Inject; import javax.inject.Provider; @@ -97,6 +99,8 @@ public class FlowRunner { return e.output; } catch (EppRuntimeException e) { throw e.getCause(); + } catch (ReadOnlyModeException e) { + throw new ReadOnlyModeEppException(e); } } diff --git a/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java b/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java index c8931935b..a249d9226 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactCreateFlow.java @@ -50,6 +50,7 @@ import org.joda.time.DateTime; /** * An EPP flow that creates a new contact. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link ResourceAlreadyExistsForThisClientException} * @error {@link ResourceCreateContentionException} * @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException} diff --git a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java index b2129a546..6e7133368 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactDeleteFlow.java @@ -60,6 +60,7 @@ import org.joda.time.DateTime; * references to the host before the deletion is allowed to proceed. A poll message will be written * with the success or failure message when the process is complete. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java index 6b28d61fd..32cae77fd 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferApproveFlow.java @@ -54,6 +54,7 @@ import org.joda.time.DateTime; * transfer is automatically approved. Within that window, this flow allows the losing client to * explicitly approve the transfer request, which then becomes effective immediately. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java index 16382ed68..3c1cfcc24 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferCancelFlow.java @@ -54,6 +54,7 @@ import org.joda.time.DateTime; * transfer is automatically approved. Within that window, this flow allows the gaining client to * withdraw the transfer request. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.exceptions.NotPendingTransferException} diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java index 30173c80b..cb2428ddb 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferRejectFlow.java @@ -53,6 +53,7 @@ import org.joda.time.DateTime; * transfer is automatically approved. Within that window, this flow allows the losing client to * reject the transfer request. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} diff --git a/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java b/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java index 163caa97a..97833699d 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactTransferRequestFlow.java @@ -63,6 +63,7 @@ import org.joda.time.Duration; * by the losing registrar or rejected, and the gaining registrar can also cancel the transfer * request. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.exceptions.AlreadyPendingTransferException} diff --git a/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java b/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java index b93e15b2e..71080c1d5 100644 --- a/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/contact/ContactUpdateFlow.java @@ -55,6 +55,7 @@ import org.joda.time.DateTime; /** * An EPP flow that updates a contact. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java index ec4c55f61..d15e2bd79 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainCreateFlow.java @@ -133,6 +133,7 @@ import org.joda.time.Duration; * google.registry.flows.domain.token.AllocationTokenFlowUtils.AlreadyRedeemedAllocationTokenException} * @error {@link * google.registry.flows.domain.token.AllocationTokenFlowUtils.InvalidAllocationTokenException} + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.exceptions.OnlyToolCanPassMetadataException} * @error {@link ResourceAlreadyExistsForThisClientException} * @error {@link ResourceCreateContentionException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java index c74de57e9..1190da927 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainDeleteFlow.java @@ -103,6 +103,7 @@ import org.joda.time.Duration; /** * An EPP flow that deletes a domain. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java index e561f94a1..363a1e0f3 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java @@ -94,6 +94,7 @@ import org.joda.time.Duration; * longer than 10 years unless it comes in at the exact millisecond that the domain would have * expired. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.FlowUtils.UnknownCurrencyEppException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java index 2ad97eb6e..f23d58a75 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainRestoreRequestFlow.java @@ -93,6 +93,7 @@ import org.joda.time.DateTime; * restored to a single year expiration starting at the restore time, regardless of what the * original expiration time was. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} * @error {@link google.registry.flows.FlowUtils.UnknownCurrencyEppException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} @@ -110,7 +111,7 @@ import org.joda.time.DateTime; * @error {@link DomainRestoreRequestFlow.RestoreCommandIncludesChangesException} */ @ReportingSpec(ActivityReportField.DOMAIN_RGP_RESTORE_REQUEST) -public final class DomainRestoreRequestFlow implements TransactionalFlow { +public final class DomainRestoreRequestFlow implements TransactionalFlow { @Inject ResourceCommand resourceCommand; @Inject ExtensionManager extensionManager; diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java index d4cacca10..a313151f7 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferApproveFlow.java @@ -78,6 +78,7 @@ import org.joda.time.DateTime; * timestamps such that they only would become active when the transfer period passed. In this flow, * those speculative objects are deleted and replaced with new ones with the correct approval time. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java index eb8e1f326..4cdf94c52 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferCancelFlow.java @@ -65,6 +65,7 @@ import org.joda.time.DateTime; * timestamps such that they only would become active when the transfer period passed. In this flow, * those speculative objects are deleted. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.exceptions.NotPendingTransferException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java index 8428c0ff0..fd334826c 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRejectFlow.java @@ -67,6 +67,7 @@ import org.joda.time.DateTime; * timestamps such that they only would become active when the transfer period passed. In this flow, * those speculative objects are deleted. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java index 9e3f591bf..775191ccc 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainTransferRequestFlow.java @@ -93,6 +93,7 @@ import org.joda.time.DateTime; * rejection or cancellation of the request, they will be deleted (and in the approval case, * replaced with new ones with the correct approval time). * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.FlowUtils.UnknownCurrencyEppException} * @error {@link google.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} @@ -102,7 +103,8 @@ import org.joda.time.DateTime; * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} * @error {@link google.registry.flows.exceptions.TransferPeriodMustBeOneYearException} * @error {@link InvalidTransferPeriodValueException} - * @error {@link google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException} + * @error {@link + * google.registry.flows.exceptions.TransferPeriodZeroAndFeeTransferExtensionException} * @error {@link DomainFlowUtils.BadPeriodUnitException} * @error {@link DomainFlowUtils.CurrencyUnitMismatchException} * @error {@link DomainFlowUtils.CurrencyValueScaleException} diff --git a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java index 07af4c385..b788770b5 100644 --- a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java @@ -97,6 +97,7 @@ import org.joda.time.DateTime; * superuser. As such, adding or removing these statuses incurs a billing event. There will be only * one charge per update, even if several such statuses are updated at once. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.EppException.UnimplementedExtensionException} * @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} diff --git a/core/src/main/java/google/registry/flows/host/HostCreateFlow.java b/core/src/main/java/google/registry/flows/host/HostCreateFlow.java index 6155ba6c3..a272e4c2c 100644 --- a/core/src/main/java/google/registry/flows/host/HostCreateFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostCreateFlow.java @@ -65,6 +65,7 @@ import org.joda.time.DateTime; * hosts cannot have any. This flow allows creating a host name, and if necessary enqueues tasks to * update DNS. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.FlowUtils.IpAddressVersionMismatchException} * @error {@link ResourceAlreadyExistsForThisClientException} * @error {@link ResourceCreateContentionException} diff --git a/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java b/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java index f5188fb4a..46359a2bf 100644 --- a/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostDeleteFlow.java @@ -58,6 +58,7 @@ import org.joda.time.DateTime; * references to the host before the deletion is allowed to proceed. A poll message will be written * with the success or failure message when the process is complete. * + * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException} * @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException} * @error {@link google.registry.flows.exceptions.ResourceStatusProhibitsOperationException} diff --git a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java index 69f8029ef..5045b68db 100644 --- a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java +++ b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java @@ -73,11 +73,12 @@ import org.joda.time.DateTime; * hosts. Internal hosts must have at least one IP address associated with them, whereas external * hosts cannot have any. * - *
This flow allows changing a host name, and adding or removing IP addresses to hosts. When - * a host is renamed from internal to external all IP addresses must be simultaneously removed, and + *
This flow allows changing a host name, and adding or removing IP addresses to hosts. When a
+ * host is renamed from internal to external all IP addresses must be simultaneously removed, and
* when it is renamed from external to internal at least one must be added. If the host is renamed
* or IP addresses are added, tasks are enqueued to update DNS accordingly.
*
+ * @error {@link google.registry.flows.EppException.ReadOnlyModeEppException}
* @error {@link google.registry.flows.ResourceFlowUtils.AddRemoveSameValueException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceDoesNotExistException}
* @error {@link google.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
diff --git a/core/src/main/java/google/registry/persistence/transaction/TransactionManagerFactory.java b/core/src/main/java/google/registry/persistence/transaction/TransactionManagerFactory.java
index 84f71be0c..4f053abff 100644
--- a/core/src/main/java/google/registry/persistence/transaction/TransactionManagerFactory.java
+++ b/core/src/main/java/google/registry/persistence/transaction/TransactionManagerFactory.java
@@ -157,7 +157,7 @@ public class TransactionManagerFactory {
/** Registry is currently undergoing maintenance and is in read-only mode. */
public static class ReadOnlyModeException extends IllegalStateException {
- ReadOnlyModeException() {
+ public ReadOnlyModeException() {
super("Registry is currently undergoing maintenance and is in read-only mode");
}
}
diff --git a/core/src/test/java/google/registry/flows/contact/ContactCreateFlowTest.java b/core/src/test/java/google/registry/flows/contact/ContactCreateFlowTest.java
index 1571cd893..239bee1dc 100644
--- a/core/src/test/java/google/registry/flows/contact/ContactCreateFlowTest.java
+++ b/core/src/test/java/google/registry/flows/contact/ContactCreateFlowTest.java
@@ -26,15 +26,18 @@ import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptio
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.flows.EppException;
+import google.registry.flows.EppException.ReadOnlyModeEppException;
import google.registry.flows.ResourceFlowTestCase;
import google.registry.flows.contact.ContactFlowUtils.BadInternationalizedPostalInfoException;
import google.registry.flows.contact.ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException;
import google.registry.flows.exceptions.ResourceAlreadyExistsForThisClientException;
import google.registry.flows.exceptions.ResourceCreateContentionException;
import google.registry.model.contact.ContactResource;
+import google.registry.testing.DatabaseHelper;
import google.registry.testing.DualDatabaseTest;
import google.registry.testing.ReplayExtension;
import google.registry.testing.TestOfyAndSql;
+import google.registry.testing.TestOfyOnly;
import org.joda.time.DateTime;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.extension.RegisterExtension;
@@ -137,4 +140,12 @@ class ContactCreateFlowTest extends ResourceFlowTestCase