diff --git a/common/build.gradle b/common/build.gradle
index 6ce5873c6..d8cec296f 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -58,6 +58,7 @@ dependencies {
implementation deps['com.google.code.findbugs:jsr305']
implementation deps['com.google.guava:guava']
implementation deps['jakarta.inject:jakarta.inject-api']
+ implementation deps['org.freemarker:freemarker']
implementation deps['com.google.flogger:flogger']
implementation deps['io.github.java-diff-utils:java-diff-utils']
implementation deps['com.google.truth:truth']
diff --git a/common/gradle.lockfile b/common/gradle.lockfile
index eea2bea6b..040629b7b 100644
--- a/common/gradle.lockfile
+++ b/common/gradle.lockfile
@@ -34,7 +34,7 @@ commons-collections:commons-collections:3.2.2=checkstyle
info.picocli:picocli:4.7.7=checkstyle
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.16=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
+io.github.java-diff-utils:java-diff-utils:4.17=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
jakarta.inject:jakarta.inject-api:2.0.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
javax.inject:javax.inject:1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
@@ -60,6 +60,7 @@ org.codehaus.plexus:plexus-classworlds:2.6.0=checkstyle
org.codehaus.plexus:plexus-component-annotations:2.1.0=checkstyle
org.codehaus.plexus:plexus-container-default:2.1.0=checkstyle
org.codehaus.plexus:plexus-utils:3.3.0=checkstyle
+org.freemarker:freemarker:2.3.34=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.hamcrest:hamcrest-core:1.3=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
org.jacoco:org.jacoco.agent:0.8.14=jacocoAgent,jacocoAnt
org.jacoco:org.jacoco.ant:0.8.14=jacocoAnt
diff --git a/common/src/main/java/google/registry/util/TemplateRenderer.java b/common/src/main/java/google/registry/util/TemplateRenderer.java
new file mode 100644
index 000000000..56dadfd60
--- /dev/null
+++ b/common/src/main/java/google/registry/util/TemplateRenderer.java
@@ -0,0 +1,67 @@
+// Copyright 2026 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.util;
+
+import com.google.common.collect.ImmutableMap;
+import freemarker.core.HTMLOutputFormat;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import jakarta.inject.Inject;
+import java.io.StringWriter;
+
+/**
+ * A utility class for rendering FreeMarker templates.
+ *
+ *
This renderer is configured to use HTML as the default output format, which enables automatic
+ * escaping of all interpolated variables. It also uses the "computer" number format to ensure
+ * consistent formatting of numeric values across different locales.
+ */
+public class TemplateRenderer {
+
+ private final Configuration configuration;
+
+ @Inject
+ public TemplateRenderer() {
+ this.configuration = new Configuration(Configuration.VERSION_2_3_32);
+ this.configuration.setClassLoaderForTemplateLoading(getClass().getClassLoader(), "");
+ this.configuration.setDefaultEncoding("UTF-8");
+ this.configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+ this.configuration.setLogTemplateExceptions(false);
+ this.configuration.setWrapUncheckedExceptions(true);
+ this.configuration.setFallbackOnNullLoopVariable(false);
+ this.configuration.setOutputFormat(HTMLOutputFormat.INSTANCE);
+ this.configuration.setNumberFormat("computer");
+ }
+
+ /**
+ * Renders the specified template with the given data model.
+ *
+ * @param templatePath the path to the template file relative to the classpath root
+ * @param dataModel an immutable map containing the data to be used in the template
+ * @return the rendered template as a string
+ * @throws RuntimeException if the template cannot be found, parsed, or processed
+ */
+ public String render(String templatePath, ImmutableMap dataModel) {
+ try {
+ Template template = configuration.getTemplate(templatePath);
+ StringWriter writer = new StringWriter();
+ template.process(dataModel, writer);
+ return writer.toString();
+ } catch (Exception e) {
+ throw new RuntimeException(String.format("Error rendering template %s", templatePath), e);
+ }
+ }
+}
diff --git a/common/src/test/java/google/registry/util/TemplateRendererTest.java b/common/src/test/java/google/registry/util/TemplateRendererTest.java
new file mode 100644
index 000000000..1b523b214
--- /dev/null
+++ b/common/src/test/java/google/registry/util/TemplateRendererTest.java
@@ -0,0 +1,83 @@
+// Copyright 2026 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.jupiter.api.Test;
+
+/** Unit tests for {@link TemplateRenderer}. */
+class TemplateRendererTest {
+
+ private final TemplateRenderer renderer = new TemplateRenderer();
+
+ @Test
+ void testRender_success() {
+ ImmutableMap data =
+ ImmutableMap.of(
+ "name", "World", "score", 42, "showMessage", true, "message", "Keep going!");
+ String result = renderer.render("google/registry/util/test_template.ftl", data);
+ assertThat(result).isEqualTo("Hello World!\nYour score is 42.\nMessage: Keep going!\n");
+ }
+
+ @Test
+ void testRender_conditional_false() {
+ ImmutableMap data =
+ ImmutableMap.of("name", "User", "score", 0, "showMessage", false);
+ String result = renderer.render("google/registry/util/test_template.ftl", data);
+ assertThat(result).isEqualTo("Hello User!\nYour score is 0.\n");
+ }
+
+ @Test
+ void testRender_htmlEscaping() {
+ ImmutableMap data =
+ ImmutableMap.of("name", "World", "score", 42, "showMessage", false);
+ String result = renderer.render("google/registry/util/test_template.ftl", data);
+ assertThat(result).contains("Hello <b>World</b>!");
+ }
+
+ @Test
+ void testRender_missingTemplate_throwsException() {
+ assertThrows(
+ RuntimeException.class,
+ () -> renderer.render("non/existent/template.ftl", ImmutableMap.of()));
+ }
+
+ @Test
+ void testRender_missingVariable_throwsException() {
+ // The template expects 'name', 'score', and 'showMessage', but the map is empty.
+ assertThrows(
+ RuntimeException.class,
+ () -> renderer.render("google/registry/util/test_template.ftl", ImmutableMap.of()));
+ }
+
+ @Test
+ void testRender_unusedVariable_ignored() {
+ ImmutableMap data =
+ ImmutableMap.of(
+ "name",
+ "User",
+ "score",
+ 100,
+ "showMessage",
+ false,
+ "unusedKey",
+ "This should be ignored");
+ String result = renderer.render("google/registry/util/test_template.ftl", data);
+ assertThat(result).isEqualTo("Hello User!\nYour score is 100.\n");
+ }
+}
diff --git a/common/src/test/resources/google/registry/util/test_template.ftl b/common/src/test/resources/google/registry/util/test_template.ftl
new file mode 100644
index 000000000..f871f4970
--- /dev/null
+++ b/common/src/test/resources/google/registry/util/test_template.ftl
@@ -0,0 +1,6 @@
+<#-- Copyright 2026 The Nomulus Authors. All Rights Reserved. -->
+Hello ${name}!
+Your score is ${score}.
+<#if showMessage>
+Message: ${message}
+#if>
diff --git a/config/presubmits.py b/config/presubmits.py
index 2b500ad34..50b731673 100644
--- a/config/presubmits.py
+++ b/config/presubmits.py
@@ -90,15 +90,15 @@ PRESUBMITS = {
# License check
PresubmitCheck(
r".*Copyright 20\d{2} The Nomulus Authors\. All Rights Reserved\.",
- ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts"), {
+ ("java", "js", "sql", "py", "sh", "gradle", "ts", "ftl"), {
".git", "/build/", "node_modules/", "LoggerConfig.java", "registrar_bin.",
"registrar_dbg.", "google-java-format-diff.py",
- "nomulus.golden.sql", "soyutils_usegoog.js", "javascript/checks.js"
+ "nomulus.golden.sql", "javascript/checks.js"
}, REQUIRED):
"File did not include the license header.",
# Files must end in a newline
- PresubmitCheck(r".*\n$", ("java", "js", "soy", "sql", "py", "sh", "gradle", "ts", "xml"),
+ PresubmitCheck(r".*\n$", ("java", "js", "sql", "py", "sh", "gradle", "ts", "xml", "ftl"),
{"node_modules/", ".idea"}, REQUIRED):
"Source files must end in a newline.",
@@ -127,33 +127,6 @@ PRESUBMITS = {
"System.(out|err).println is only allowed in tools/ packages. Please "
"use a logger instead.",
- # Various Soy linting checks
- PresubmitCheck(
- r".* (/\*)?\* {?@param ",
- "soy",
- {},
- ):
- "In SOY please use the ({@param name: string} /** User name. */) style"
- " parameter passing instead of the ( * @param name User name.) style "
- "parameter passing.",
- PresubmitCheck(
- r'.*\{[^}]+\w+:\s+"',
- "soy",
- {},
- ):
- "Please don't use double-quoted string literals in Soy parameters",
- PresubmitCheck(
- r'.*autoescape\s*=\s*"[^s]',
- "soy",
- {},
- ):
- "All soy templates must use strict autoescaping",
- PresubmitCheck(
- r".*noAutoescape",
- "soy",
- {},
- ):
- "All soy templates must use strict autoescaping",
PresubmitCheck(
r".*\nimport\s+(?:static\s+)?.*\.shaded\..*",
"java",
diff --git a/core/build.gradle b/core/build.gradle
index 3cb8cbaea..13849341e 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -94,7 +94,6 @@ processTestResources {
configurations {
jaxb
- soy
devtool
nonprodImplementation.extendsFrom implementation
@@ -120,7 +119,7 @@ configurations {
// For reasons we do not understand, marking the following dependencies as
// compileOnly instead of compile does not exclude them from runtimeClasspath.
all {
- // servlet-api:3.1 pulled in but not used by soy compiler
+ // servlet-api:3.1 pulled in but not used
exclude group: 'javax.servlet', module: 'javax.servlet-api'
}
}
@@ -181,7 +180,7 @@ dependencies {
implementation deps['com.google.oauth-client:google-oauth-client-jetty']
implementation deps['com.google.oauth-client:google-oauth-client-servlet']
implementation deps['com.google.re2j:re2j']
- implementation deps['com.google.template:soy']
+ implementation deps['org.freemarker:freemarker']
implementation deps['com.googlecode.json-simple:json-simple']
implementation deps['com.jcraft:jsch']
implementation deps['com.zaxxer:HikariCP']
@@ -299,9 +298,6 @@ dependencies {
jaxb deps['org.glassfish.jaxb:jaxb-runtime']
jaxb deps['org.glassfish.jaxb:jaxb-xjc']
- // Dependency needed for soy to java compilation.
- soy deps['com.google.template:soy']
-
// Flyway classes needed to generate the golden file.
implementation deps['org.flywaydb:flyway-core']
implementation deps['org.flywaydb:flyway-database-postgresql']
@@ -371,62 +367,7 @@ task jaxbToJava {
}
}
-task soyToJava {
- // Relative paths of soy directories.
- def spec11SoyDir = "google/registry/reporting/spec11/soy"
- def toolsSoyDir = "google/registry/tools/soy"
-
- def soyRelativeDirs = [spec11SoyDir, toolsSoyDir]
- soyRelativeDirs.each {
- inputs.dir "${resourcesSourceDir}/${it}"
- outputs.dir "${generatedDir}/${it}"
- }
-
- ext.soyToJava = { javaPackage, outputDirectory, soyFiles ->
- project.services.get(ExecOperations).javaexec {
- mainClass = "com.google.template.soy.SoyParseInfoGenerator"
- classpath = configurations.soy
- jvmArgs = ["--sun-misc-unsafe-memory-access=allow", "--enable-native-access=ALL-UNNAMED"]
- args = ["--javaPackage", "${javaPackage}",
- "--outputDirectory", "${outputDirectory}",
- "--javaClassNameSource", "filename",
- "--srcs", "${soyFiles.join(',')}"]
- }
-
- // Replace the "@link" tags after the "@deprecated" tags in the generated
- // files. The soy compiler doesn't generate imports for these, causing
- // us to get warnings when we generate javadocs.
- // TODO(b/200296387): To be fair, the deprecations are accurate: we're
- // using the old "SoyInfo" classes instead of the new "Templates" files.
- // When we convert to the new classes, this hack can go away.
- def outputs = fileTree(outputDirectory) {
- include '**/*.java'
- }
-
- outputs.each { file ->
- project.services.get(ExecOperations).exec {
- commandLine = ['sed', '-i""', '-e', 's/@link/LINK/g', file.getCanonicalPath()]
- }
- }
- }
-
- doLast {
- soyToJava('google.registry.tools.soy',
- "${generatedDir}/${toolsSoyDir}",
- fileTree(
- dir: "${resourcesSourceDir}/${toolsSoyDir}",
- include: ['**/*.soy']))
-
- soyToJava('google.registry.reporting.spec11.soy',
- "${generatedDir}/${spec11SoyDir}",
- fileTree(
- dir: "${resourcesSourceDir}/${spec11SoyDir}",
- include: ['**/*.soy']))
- }
-}
-
compileJava.dependsOn jaxbToJava
-compileJava.dependsOn soyToJava
// Make testing artifacts available to be depended up on by other projects.
// TODO: factor out google.registry.testing to be a separate project.
diff --git a/core/gradle.lockfile b/core/gradle.lockfile
index a86c04e04..af60e9f17 100644
--- a/core/gradle.lockfile
+++ b/core/gradle.lockfile
@@ -1,8 +1,7 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
-aopalliance:aopalliance:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
-args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
+args4j:args4j:2.33=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.charleskorn.kaml:kaml:0.20.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -81,13 +80,13 @@ com.google.api:gax-grpc:2.80.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:gax-httpjson:2.80.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.apis:google-api-services-admin-directory:directory_v1-rev20260227-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.google.apis:google-api-services-admin-directory:directory_v1-rev20260421-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-bigquery:v2-rev20251012-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-cloudresourcemanager:v1-rev20250606-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.apis:google-api-services-dataflow:v1b3-rev20260405-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.google.apis:google-api-services-dataflow:v1b3-rev20260503-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-dns:v1-rev20260421-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-drive:v3-rev20260428-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.apis:google-api-services-gmail:v1-rev20260427-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.google.apis:google-api-services-gmail:v1-rev20260511-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-groupssettings:v1-rev20220614-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-healthcare:v1-rev20240130-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-iam:v2-rev20250502-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -138,33 +137,26 @@ com.google.cloud:google-cloud-tasks:2.51.0=compileClasspath,deploy_jar,nonprodCo
com.google.cloud:grpc-gcp:1.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:libraries-bom:26.48.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:proto-google-cloud-firestore-bundle-v1:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
-com.google.code.gson:gson:2.10.1=soy
+com.google.code.findbugs:jsr305:3.0.2=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.13.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.common.html.types:types:1.0.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.dagger:dagger-compiler:2.59.2=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger-spi:2.59.2=annotationProcessor,testAnnotationProcessor
com.google.dagger:dagger:2.59.2=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.devtools.ksp:symbol-processing-api:2.2.20-2.0.3=annotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotation:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
-com.google.errorprone:error_prone_annotations:2.20.0=soy
com.google.errorprone:error_prone_annotations:2.36.0=checkstyle
com.google.errorprone:error_prone_annotations:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.errorprone:error_prone_check_api:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.errorprone:error_prone_core:2.48.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
-com.google.escapevelocity:escapevelocity:1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.flatbuffers:flatbuffers-java:24.3.25=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.flogger:flogger-system-backend:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
-com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
-com.google.flogger:google-extensions:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
+com.google.flogger:flogger-system-backend:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.google.flogger:flogger:0.7.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.google.flogger:google-extensions:0.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.googlejavaformat:google-java-format:1.34.1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
-com.google.guava:failureaccess:1.0.1=soy
com.google.guava:failureaccess:1.0.3=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
-com.google.guava:guava-parent:32.1.1-jre=soy
com.google.guava:guava-testlib:33.3.0-jre=testRuntimeClasspath
com.google.guava:guava-testlib:33.6.0-jre=testCompileClasspath
-com.google.guava:guava:32.1.1-jre=soy
com.google.guava:guava:33.4.8-jre=checkstyle
com.google.guava:guava:33.5.0-jre=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.google.guava:guava:33.6.0-jre=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -179,10 +171,8 @@ com.google.http-client:google-http-client-jackson2:1.46.3=compileClasspath,deplo
com.google.http-client:google-http-client-jackson2:2.1.0=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-protobuf:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.inject:guice:7.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.j2objc:j2objc-annotations:3.0.0=checkstyle
com.google.j2objc:j2objc-annotations:3.1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
-com.google.jsinterop:jsinterop-annotations:1.0.1=soy
com.google.jsinterop:jsinterop-annotations:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:contrib:1.0.7=testCompileClasspath,testRuntimeClasspath
com.google.monitoring-client:metrics:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -195,15 +185,13 @@ com.google.oauth-client:google-oauth-client-servlet:1.36.0=deploy_jar,nonprodRun
com.google.oauth-client:google-oauth-client-servlet:1.39.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.oauth-client:google-oauth-client:1.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java-util:4.33.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
-com.google.protobuf:protobuf-java-util:4.35.0-RC2=testCompileClasspath,testRuntimeClasspath
-com.google.protobuf:protobuf-java:3.21.7=soy
+com.google.protobuf:protobuf-java-util:4.35.0=testCompileClasspath,testRuntimeClasspath
com.google.protobuf:protobuf-java:4.33.2=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
-com.google.protobuf:protobuf-java:4.35.0-RC2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+com.google.protobuf:protobuf-java:4.35.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.re2j:re2j:1.8=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.google.template:soy:2024-02-26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
com.google.truth:truth:1.4.5=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.googlecode.json-simple:json-simple:1.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-com.ibm.icu:icu4j:73.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
+com.ibm.icu:icu4j:73.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.jcraft:jsch:0.1.55=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.lmax:disruptor:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.puppycrawl.tools:checkstyle:10.24.0=checkstyle
@@ -252,7 +240,7 @@ io.apicurio:apicurio-registry-protobuf-schema-utilities:3.0.0.M2=compileClasspat
io.github.classgraph:classgraph:4.8.162=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.eisop:dataflow-errorprone:3.41.0-eisop1=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
-io.github.java-diff-utils:java-diff-utils:4.16=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+io.github.java-diff-utils:java-diff-utils:4.17=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.github.ss-bhatt:testcontainers-valkey:1.0.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-alts:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-alts:1.81.0=testCompileClasspath,testRuntimeClasspath
@@ -351,7 +339,7 @@ jakarta-regexp:jakarta-regexp:1.4=compileClasspath,deploy_jar,nonprodCompileClas
jakarta.activation:jakarta.activation-api:2.1.4=jaxb
jakarta.activation:jakarta.activation-api:2.2.0-M1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
jakarta.activation:jakarta.activation-api:2.2.0-M2=compileClasspath,nonprodCompileClasspath,testCompileClasspath
-jakarta.inject:jakarta.inject-api:2.0.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
+jakarta.inject:jakarta.inject-api:2.0.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
jakarta.mail:jakarta.mail-api:2.2.0-M1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.persistence:jakarta.persistence-api:3.2.0=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -360,8 +348,7 @@ jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=deploy_jar,nonprodRuntimeClasspath,r
jakarta.xml.bind:jakarta.xml.bind-api:4.0.5=jaxb
jakarta.xml.bind:jakarta.xml.bind-api:4.1.0-M1=compileClasspath,nonprodCompileClasspath,testCompileClasspath
javax.annotation:javax.annotation-api:1.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-javax.annotation:jsr250-api:1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testCompileClasspath,testRuntimeClasspath
-javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,soy,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
+javax.inject:javax.inject:1=annotationProcessor,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
javax.jdo:jdo2-api:2.3-20090302111651=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
javax.validation:validation-api:1.0.0.GA=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
joda-time:joda-time:2.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -438,10 +425,9 @@ org.bouncycastle:bcpg-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileCla
org.bouncycastle:bcpkix-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcprov-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.bouncycastle:bcutil-jdk18on:1.84=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,soy,testAnnotationProcessor,testCompileClasspath
+org.checkerframework:checker-compat-qual:2.5.3=annotationProcessor,compileClasspath,nonprodCompileClasspath,testAnnotationProcessor,testCompileClasspath
org.checkerframework:checker-compat-qual:2.5.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.19.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
-org.checkerframework:checker-qual:3.33.0=soy
org.checkerframework:checker-qual:3.49.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.checkerframework:checker-qual:3.49.3=checkstyle
org.codehaus.mojo:animal-sniffer-annotations:1.24=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
@@ -465,8 +451,9 @@ org.eclipse.jetty:jetty-server:12.1.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-session:12.1.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-util:12.1.9=testCompileClasspath,testRuntimeClasspath
org.eclipse.jetty:jetty-xml:12.1.9=testCompileClasspath,testRuntimeClasspath
-org.flywaydb:flyway-core:12.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.flywaydb:flyway-database-postgresql:12.6.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.flywaydb:flyway-core:12.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.flywaydb:flyway-database-postgresql:12.6.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+org.freemarker:freemarker:2.3.34=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.glassfish.jaxb:codemodel:4.0.8=jaxb
org.glassfish.jaxb:jaxb-core:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.glassfish.jaxb:jaxb-core:4.0.8=jaxb
@@ -514,7 +501,6 @@ org.jetbrains:annotations:13.0=annotationProcessor,testAnnotationProcessor
org.jetbrains:annotations:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jline:jline:3.30.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.joda:joda-money:2.0.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.json:json:20230618=soy
org.json:json:20251224=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jsoup:jsoup:1.22.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
@@ -535,17 +521,12 @@ org.mockito:mockito-junit-jupiter:5.23.0=testCompileClasspath,testRuntimeClasspa
org.objenesis:objenesis:3.3=testRuntimeClasspath
org.ogce:xpp3:1.1.6=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
-org.ow2.asm:asm-analysis:9.5=soy
org.ow2.asm:asm-analysis:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.ow2.asm:asm-commons:9.5=soy
org.ow2.asm:asm-commons:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.9=jacocoAnt
-org.ow2.asm:asm-tree:9.5=soy
org.ow2.asm:asm-tree:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-tree:9.9=jacocoAnt
-org.ow2.asm:asm-util:9.5=soy
org.ow2.asm:asm-util:9.7.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
-org.ow2.asm:asm:9.5=soy
org.ow2.asm:asm:9.7.1=compileClasspath,nonprodCompileClasspath
org.ow2.asm:asm:9.8=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm:9.9=jacocoAnt
diff --git a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java
index 3a1d010c8..935ee3de6 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainFlowUtils.java
@@ -43,8 +43,8 @@ import static google.registry.util.CollectionUtils.nullToEmpty;
import static google.registry.util.DateTimeUtils.END_INSTANT;
import static google.registry.util.DateTimeUtils.isAtOrAfter;
import static google.registry.util.DateTimeUtils.minusDays;
+import static google.registry.util.DateTimeUtils.plusYears;
import static google.registry.util.DomainNameUtils.ACE_PREFIX;
-import static java.time.ZoneOffset.UTC;
import static java.util.stream.Collectors.joining;
import com.google.common.base.CharMatcher;
@@ -154,7 +154,7 @@ public class DomainFlowUtils {
/** Warning message for allocation of collision domains in sunrise. */
public static final String COLLISION_MESSAGE =
"Domain on the name collision list was allocated. But by policy, the domain will not be "
- + "delegated. Please visit https://www.icann.org/namecollision for more information on "
+ + "delegated. Please visit https://www.icann.org/namecollision for more information on "
+ "name collision.";
/** Strict validator for ascii lowercase letters, digits, and "-", allowing "." as a separator */
@@ -581,13 +581,12 @@ public class DomainFlowUtils {
InternetDomainName domainName,
Optional domain,
@Nullable CurrencyUnit topLevelCurrency,
- Instant currentDate,
+ Instant now,
DomainPricingLogic pricingLogic,
Optional allocationToken,
boolean isAvailable,
@Nullable BillingRecurrence billingRecurrence)
throws EppException {
- Instant now = currentDate;
// Use the custom effective date specified in the fee check request, if there is one.
if (feeRequest.getEffectiveDate().isPresent()) {
now = feeRequest.getEffectiveDate().get();
@@ -816,7 +815,7 @@ public class DomainFlowUtils {
return fee.getType();
}
ImmutableList types = fee.parseDescriptionForTypes();
- if (types.size() == 0) {
+ if (types.isEmpty()) {
throw new FeeDescriptionParseException(fee.getDescription());
} else if (types.size() > 1) {
throw new FeeDescriptionMultipleMatchesException(fee.getDescription(), types);
@@ -848,7 +847,7 @@ public class DomainFlowUtils {
*/
public static void validateRegistrationPeriod(Instant now, Instant newExpirationTime)
throws EppException {
- if (now.atZone(UTC).plusYears(MAX_REGISTRATION_YEARS).toInstant().isBefore(newExpirationTime)) {
+ if (plusYears(now, MAX_REGISTRATION_YEARS).isBefore(newExpirationTime)) {
throw new ExceedsMaxRegistrationYearsException();
}
}
@@ -907,7 +906,7 @@ public class DomainFlowUtils {
return ImmutableSet.copyOf(union(difference(oldDsData, toRemove), toAdd));
}
- /** If a domain "clientUpdateProhibited" set, updates must clear it or fail. */
+ /** If a domain has "clientUpdateProhibited" set, updates must clear it or fail. */
static void verifyClientUpdateNotProhibited(Update command, Domain existingResource)
throws ResourceHasClientUpdateProhibitedException {
if (existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
@@ -996,7 +995,13 @@ public class DomainFlowUtils {
}
}
- /** Check that the claims period hasn't ended. */
+ /**
+ * Check that the claims period hasn't ended.
+ *
+ * @param tld the {@link Tld} to check
+ * @param now the current {@link Instant}
+ * @throws ClaimsPeriodEndedException if the claims period has ended
+ */
static void verifyClaimsPeriodNotEnded(Tld tld, Instant now) throws ClaimsPeriodEndedException {
if (!now.isBefore(tld.getClaimsPeriodEnd())) {
throw new ClaimsPeriodEndedException(tld.getTldStr());
@@ -1008,6 +1013,9 @@ public class DomainFlowUtils {
*
* {@link BigDecimal} has a concept of significant figures, so zero is not always zero. E.g.
* zero in USD is 0.00, whereas zero in Yen is 0, and zero in Dinars is 0.000 (!).
+ *
+ * @param currencyUnit the {@link CurrencyUnit}
+ * @return zero in the given currency
*/
static BigDecimal zeroInCurrency(CurrencyUnit currencyUnit) {
return Money.of(currencyUnit, BigDecimal.ZERO).getAmount();
@@ -1016,6 +1024,12 @@ public class DomainFlowUtils {
/**
* Check that if there's a claims notice it's on the claims list, and that if there's not one it's
* not on the claims list.
+ *
+ * @param domainName the {@link InternetDomainName} to check
+ * @param claimsList the current {@link ClaimsList}
+ * @param hasSignedMarks whether signed marks are present
+ * @param hasClaimsNotice whether a claims notice is present
+ * @throws EppException if the claims notice status is incorrect
*/
static void verifyClaimsNoticeIfAndOnlyIfNeeded(
InternetDomainName domainName,
@@ -1032,7 +1046,12 @@ public class DomainFlowUtils {
}
}
- /** Check that there are no code marks, which is a type of mark we don't support. */
+ /**
+ * Check that there are no code marks, which is a type of mark we don't support.
+ *
+ * @param launchCreate the {@link LaunchCreateExtension}
+ * @throws UnsupportedMarkTypeException if code marks are present
+ */
static void verifyNoCodeMarks(LaunchCreateExtension launchCreate)
throws UnsupportedMarkTypeException {
if (launchCreate.hasCodeMarks()) {
@@ -1040,7 +1059,13 @@ public class DomainFlowUtils {
}
}
- /** Create a response extension listing the fees on a domain create. */
+ /**
+ * Create a response extension listing the fees on a domain create.
+ *
+ * @param feeCreate the {@link FeeTransformCommandExtension}
+ * @param feesAndCredits the {@link FeesAndCredits}
+ * @return the {@link FeeTransformResponseExtension}
+ */
static FeeTransformResponseExtension createFeeCreateResponse(
FeeTransformCommandExtension feeCreate, FeesAndCredits feesAndCredits) {
return feeCreate
@@ -1058,10 +1083,21 @@ public class DomainFlowUtils {
* their flow. For example, if a grace period delete occurs, we must add -1 counters for the
* associated NET_ADDS_#_YRS field, if it exists.
*
- *
The steps are as follows: 1. Find all HistoryEntries under the domain modified in the past,
- * up to the maxSearchPeriod. 2. Only keep HistoryEntries with a DomainTransactionRecord that a)
- * hasn't been reported yet and b) matches the predicate 3. Return the transactionRecords under
- * the most recent HistoryEntry that fits the above criteria, with negated reportAmounts.
+ *
The steps are as follows:
+ *
+ *
+ * - Find all HistoryEntries under the domain modified in the past, up to the maxSearchPeriod.
+ *
- Only keep HistoryEntries with a DomainTransactionRecord that a) hasn't been reported yet
+ * and b) matches the predicate
+ *
- Return the transactionRecords under the most recent HistoryEntry that fits the above
+ * criteria, with negated reportAmounts.
+ *
+ *
+ * @param domain the {@link Domain} to create records for
+ * @param now the current {@link Instant}
+ * @param maxSearchPeriod the {@link Duration} to search back
+ * @param cancelableFields the set of {@link TransactionReportField}s that can be canceled
+ * @return the set of canceling {@link DomainTransactionRecord}s
*/
public static ImmutableSet createCancelingRecords(
Domain domain,
@@ -1225,13 +1261,6 @@ public class DomainFlowUtils {
}
}
- /** Having a registrant is prohibited by registry policy. */
- public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
- public RegistrantProhibitedException() {
- super("Having a registrant is prohibited by registry policy");
- }
- }
-
/** Too many nameservers set on this domain. */
static class TooManyNameserversException extends ParameterValuePolicyErrorException {
public TooManyNameserversException(String message) {
@@ -1384,6 +1413,13 @@ public class DomainFlowUtils {
}
}
+ /** Having a registrant is prohibited by registry policy. */
+ public static class RegistrantProhibitedException extends ParameterValuePolicyErrorException {
+ public RegistrantProhibitedException() {
+ super("Having a registrant is prohibited by registry policy");
+ }
+ }
+
/** The fee description passed in the transform command cannot be parsed. */
public static class FeeDescriptionParseException extends ParameterValuePolicyErrorException {
public FeeDescriptionParseException(String description) {
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 f561d2e6a..780de3602 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainRenewFlow.java
@@ -34,8 +34,8 @@ import static google.registry.flows.domain.token.AllocationTokenFlowUtils.maybeA
import static google.registry.flows.domain.token.AllocationTokenFlowUtils.verifyBulkTokenAllowedOnDomain;
import static google.registry.model.reporting.HistoryEntry.Type.DOMAIN_RENEW;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
+import static google.registry.util.DateTimeUtils.plusYears;
import static google.registry.util.DateTimeUtils.toLocalDate;
-import static java.time.ZoneOffset.UTC;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -192,11 +192,7 @@ public final class DomainRenewFlow implements MutatingFlow {
existingDomain = maybeApplyBulkPricingRemovalToken(existingDomain, allocationToken);
Instant newExpirationTime =
- existingDomain
- .getRegistrationExpirationTime()
- .atZone(UTC)
- .plusYears(years)
- .toInstant(); // Uncapped
+ plusYears(existingDomain.getRegistrationExpirationTime(), years); // Uncapped
validateRegistrationPeriod(now, newExpirationTime);
Optional feeRenew =
eppInput.getSingleExtension(FeeRenewCommandExtension.class);
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 92d65f864..adb13b925 100644
--- a/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java
+++ b/core/src/main/java/google/registry/flows/domain/DomainUpdateFlow.java
@@ -14,7 +14,6 @@
package google.registry.flows.domain;
-import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static com.google.common.collect.Sets.symmetricDifference;
import static com.google.common.collect.Sets.union;
@@ -58,17 +57,17 @@ import google.registry.flows.custom.DomainUpdateFlowCustomLogic.BeforeSaveParame
import google.registry.flows.custom.EntityChanges;
import google.registry.flows.domain.DomainFlowUtils.NameserversNotSpecifiedForTldWithNameserverAllowListException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
+import google.registry.flows.exceptions.ContactsProhibitedException;
import google.registry.model.ImmutableObject;
import google.registry.model.billing.BillingBase.Reason;
import google.registry.model.billing.BillingEvent;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand.Update;
-import google.registry.model.domain.DomainCommand.Update.AddRemove;
import google.registry.model.domain.DomainCommand.Update.Change;
+import google.registry.model.domain.DomainCommand.Update.DomainAddRemove;
import google.registry.model.domain.DomainHistory;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import google.registry.model.domain.metadata.MetadataExtension;
-import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
@@ -118,6 +117,7 @@ import java.util.Optional;
* @error {@link NameserversNotSpecifiedForTldWithNameserverAllowListException}
* @error {@link DomainFlowUtils.NotAuthorizedForTldException}
* @error {@link RegistrantProhibitedException}
+ * @error {@link ContactsProhibitedException}
* @error {@link DomainFlowUtils.SecDnsAllUsageException}
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
* @error {@link DomainFlowUtils.TooManyNameserversException}
@@ -214,8 +214,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
private void verifyUpdateAllowed(Update command, Domain existingDomain, Instant now)
throws EppException {
verifyOptionalAuthInfo(authInfo, existingDomain);
- AddRemove add = command.getInnerAdd();
- AddRemove remove = command.getInnerRemove();
+ DomainAddRemove add = command.getInnerAdd();
+ DomainAddRemove remove = command.getInnerRemove();
String tldStr = existingDomain.getTld();
if (!isSuperuser) {
verifyNoDisallowedStatuses(existingDomain, UPDATE_DISALLOWED_STATUSES);
@@ -234,8 +234,8 @@ public final class DomainUpdateFlow implements MutatingFlow {
}
private Domain performUpdate(Update command, Domain domain, Instant now) throws EppException {
- AddRemove add = command.getInnerAdd();
- AddRemove remove = command.getInnerRemove();
+ DomainAddRemove add = command.getInnerAdd();
+ DomainAddRemove remove = command.getInnerRemove();
Optional secDnsUpdate =
eppInput.getSingleExtension(SecDnsUpdateExtension.class);
verifyAddsAndRemoves(domain.getNameservers(), add.getNameservers(), remove.getNameservers());
@@ -251,28 +251,29 @@ public final class DomainUpdateFlow implements MutatingFlow {
Domain.Builder domainBuilder =
domain
.asBuilder()
- // Handle the secDNS extension. As dsData in secDnsUpdate is read from EPP input and
- // does not have domainRepoId set, we create a copy of the existing dsData without
- // domainRepoId for comparison.
+ // Handle the secDNS extension.
.setDsData(
secDnsUpdate.isPresent()
- ? updateDsData(
- domain.getDsData().stream()
- .map(DomainDsData::cloneWithoutDomainRepoId)
- .collect(toImmutableSet()),
- secDnsUpdate.get())
+ ? updateDsData(domain.getDsData(), secDnsUpdate.get())
: domain.getDsData())
.setLastEppUpdateTime(now)
- .setLastEppUpdateRegistrarId(registrarId)
- .addStatusValues(add.getStatusValues())
- .removeStatusValues(remove.getStatusValues())
- .setAuthInfo(Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
+ .setLastEppUpdateRegistrarId(registrarId);
+
+ if (!add.getStatusValues().isEmpty()) {
+ domainBuilder.addStatusValues(add.getStatusValues());
+ }
+ if (!remove.getStatusValues().isEmpty()) {
+ domainBuilder.removeStatusValues(remove.getStatusValues());
+ }
+
+ domainBuilder.setAuthInfo(
+ Optional.ofNullable(change.getAuthInfo()).orElse(domain.getAuthInfo()));
if (!add.getNameservers().isEmpty()) {
- domainBuilder.addNameservers(add.getNameservers().stream().collect(toImmutableSet()));
+ domainBuilder.addNameservers(add.getNameservers());
}
if (!remove.getNameservers().isEmpty()) {
- domainBuilder.removeNameservers(remove.getNameservers().stream().collect(toImmutableSet()));
+ domainBuilder.removeNameservers(remove.getNameservers());
}
Optional superuserExt =
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 59171dc4c..c5a2c243f 100644
--- a/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java
+++ b/core/src/main/java/google/registry/flows/host/HostUpdateFlow.java
@@ -36,7 +36,6 @@ import static google.registry.util.CollectionUtils.isNullOrEmpty;
import com.google.cloud.tasks.v2.Task;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
-import google.registry.batch.AsyncTaskEnqueuer;
import google.registry.batch.CloudTasksUtils;
import google.registry.dns.RefreshDnsOnHostRenameAction;
import google.registry.flows.EppException;
@@ -59,8 +58,8 @@ import google.registry.model.eppinput.ResourceCommand;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.host.Host;
import google.registry.model.host.HostCommand.Update;
-import google.registry.model.host.HostCommand.Update.AddRemove;
import google.registry.model.host.HostCommand.Update.Change;
+import google.registry.model.host.HostCommand.Update.HostAddRemove;
import google.registry.model.host.HostHistory;
import google.registry.model.reporting.IcannReportingTypes.ActivityReportField;
import google.registry.persistence.VKey;
@@ -122,7 +121,6 @@ public final class HostUpdateFlow implements MutatingFlow {
@Inject @TargetId String targetId;
@Inject @Superuser boolean isSuperuser;
@Inject HostHistory.Builder historyBuilder;
- @Inject AsyncTaskEnqueuer asyncTaskEnqueuer;
@Inject EppResponse.Builder responseBuilder;
@Inject CloudTasksUtils cloudTasksUtils;
@@ -148,6 +146,7 @@ public final class HostUpdateFlow implements MutatingFlow {
? tm().loadByKey(existingHost.getSuperordinateDomain()).cloneProjectedAtTime(now)
: null;
// Note that lookupSuperordinateDomain calls cloneProjectedAtTime on the domain for us.
+
Optional newSuperordinateDomain =
lookupSuperordinateDomain(validateHostName(newHostName), now);
verifySuperordinateDomainNotInPendingDelete(newSuperordinateDomain.orElse(null));
@@ -157,8 +156,8 @@ public final class HostUpdateFlow implements MutatingFlow {
if (isHostRename && ForeignKeyUtils.loadKey(Host.class, newHostName, now).isPresent()) {
throw new HostAlreadyExistsException(newHostName);
}
- AddRemove add = command.getInnerAdd();
- AddRemove remove = command.getInnerRemove();
+ HostAddRemove add = command.getInnerAdd();
+ HostAddRemove remove = command.getInnerRemove();
verifyAddsAndRemoves(
existingHost.getStatusValues(), add.getStatusValues(), remove.getStatusValues());
verifyAddsAndRemoves(
diff --git a/core/src/main/java/google/registry/model/ImmutableObject.java b/core/src/main/java/google/registry/model/ImmutableObject.java
index 576144ff2..fe4775c6d 100644
--- a/core/src/main/java/google/registry/model/ImmutableObject.java
+++ b/core/src/main/java/google/registry/model/ImmutableObject.java
@@ -106,19 +106,24 @@ public abstract class ImmutableObject implements Cloneable {
return hashCode;
}
- /** Returns a clone of the given object. */
- @SuppressWarnings("unchecked")
- protected static T clone(T t) {
+ @Override
+ @SuppressWarnings("AmbiguousMethodReference")
+ public ImmutableObject clone() {
try {
- T clone = (T) t.clone();
- // Clear the hashCode since we often mutate clones before handing them out.
+ ImmutableObject clone = (ImmutableObject) super.clone();
clone.hashCode = null;
return clone;
- } catch (CloneNotSupportedException e) { // Yes it is.
- throw new IllegalStateException();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
}
}
+ /** Returns a clone of the given object. */
+ @SuppressWarnings({"unchecked", "AmbiguousMethodReference"})
+ protected static T clone(T t) {
+ return (T) t.clone();
+ }
+
/** Returns a clone of the given object with empty fields set to null. */
protected static T cloneEmptyToNull(T t) {
return ModelUtils.cloneEmptyToNull(t);
@@ -233,7 +238,7 @@ public abstract class ImmutableObject implements Cloneable {
}
}
- /** Marker to indicate that this filed should be ignored by {@link #toDiffableFieldMap}. */
+ /** Marker to indicate that this field should be ignored by {@link #toDiffableFieldMap}. */
@Documented
@Retention(RUNTIME)
@Target(FIELD)
diff --git a/core/src/main/java/google/registry/model/domain/DomainCommand.java b/core/src/main/java/google/registry/model/domain/DomainCommand.java
index d85235b62..9f59502ca 100644
--- a/core/src/main/java/google/registry/model/domain/DomainCommand.java
+++ b/core/src/main/java/google/registry/model/domain/DomainCommand.java
@@ -17,7 +17,6 @@ package google.registry.model.domain;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.difference;
-import static google.registry.util.CollectionUtils.difference;
import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
@@ -28,8 +27,11 @@ import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException.ParameterValuePolicyErrorException;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;
import google.registry.flows.exceptions.ContactsProhibitedException;
+import google.registry.model.Buildable;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.ImmutableObject;
+import google.registry.model.eppcommon.AuthInfo;
+import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
@@ -37,6 +39,8 @@ import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import google.registry.model.host.Host;
import google.registry.persistence.VKey;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
@@ -68,10 +72,10 @@ public class DomainCommand {
throws InvalidReferencesException, ParameterValuePolicyErrorException;
}
- /** The fields on "chgType" from RFC5731. */
+ /** The fields on "chgType" from RFC5731. */
@XmlTransient
- public static class DomainCreateOrChange extends ImmutableObject
- implements ResourceCreateOrChange {
+ public abstract static class DomainCreateOrChange
+ extends ImmutableObject implements ResourceCreateOrChange {
/** The contactId of the registrant who registered this domain. */
@XmlElement(name = "registrant")
@@ -92,9 +96,10 @@ public class DomainCommand {
/**
* A create command for a {@link Domain}, mapping "createType" from RFC5731.
+ * href="https://tools.ietf.org/html/rfc5731">RFC5731.
*/
@XmlRootElement
+ @XmlAccessorType(XmlAccessType.FIELD)
@XmlType(
propOrder = {
"domainName",
@@ -147,17 +152,12 @@ public class DomainCommand {
return nullToEmptyImmutableCopy(nameservers);
}
- @Override
- public DomainAuthInfo getAuthInfo() {
- return authInfo;
- }
-
/** Creates a copy of this {@link Create} with hard links to hosts and contacts. */
@Override
public Create cloneAndLinkReferences(Instant now)
throws InvalidReferencesException, ParameterValuePolicyErrorException {
Create clone = clone(this);
- clone.nameservers = linkHosts(clone.nameserverHostNames, now);
+ clone.nameservers = linkHosts(nullSafeImmutableCopy(clone.nameserverHostNames), now);
if (registrantContactId != null) {
throw new RegistrantProhibitedException();
}
@@ -166,14 +166,65 @@ public class DomainCommand {
}
return clone;
}
+
+ /** Builder for {@link Create}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setDomainName(String domainName) {
+ getInstance().domainName = domainName;
+ return this;
+ }
+
+ public Builder setPeriod(Period period) {
+ getInstance().period = period;
+ return this;
+ }
+
+ public Builder setNameserverHostNames(ImmutableSet nameserverHostNames) {
+ getInstance().nameserverHostNames =
+ isNullOrEmpty(nameserverHostNames) ? null : nameserverHostNames;
+ return this;
+ }
+
+ public Builder setForeignKeyedDesignatedContacts(
+ ImmutableSet foreignKeyedDesignatedContacts) {
+ getInstance().foreignKeyedDesignatedContacts =
+ isNullOrEmpty(foreignKeyedDesignatedContacts) ? null : foreignKeyedDesignatedContacts;
+ return this;
+ }
+
+ public Builder setRegistrant(String registrant) {
+ getInstance().registrantContactId = registrant;
+ return this;
+ }
+
+ public Builder setAuthInfo(DomainAuthInfo authInfo) {
+ getInstance().authInfo = authInfo;
+ return this;
+ }
+ }
}
/** A delete command for a {@link Domain}. */
@XmlRootElement
- public static class Delete extends AbstractSingleResourceCommand {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Delete extends AbstractSingleResourceCommand {
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+ }
/** An info request for a {@link Domain}. */
@XmlRootElement
+ @XmlAccessorType(XmlAccessType.FIELD)
public static class Info extends ImmutableObject implements SingleResourceCommand {
/** The name of the domain to look up, and an attribute specifying the host lookup type. */
@@ -226,7 +277,7 @@ public class DomainCommand {
}
@Override
- public DomainAuthInfo getAuthInfo() {
+ public AuthInfo getAuthInfo() {
return authInfo;
}
}
@@ -237,12 +288,27 @@ public class DomainCommand {
/** A renew command for a {@link Domain}. */
@XmlRootElement
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"name", "currentExpirationDate", "period"})
public static class Renew extends AbstractSingleResourceCommand {
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+
@XmlElement(name = "curExpDate")
LocalDate currentExpirationDate;
/** The period that this domain's state was set to last for. */
- Period period;
+ @XmlElement Period period;
public LocalDate getCurrentExpirationDate() {
return currentExpirationDate;
@@ -251,13 +317,46 @@ public class DomainCommand {
public Period getPeriod() {
return firstNonNull(period, DEFAULT_PERIOD);
}
+
+ /** Builder for {@link Renew}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setTargetId(String targetId) {
+ getInstance().setTargetId(targetId);
+ return this;
+ }
+
+ public Builder setCurrentExpirationDate(LocalDate currentExpirationDate) {
+ getInstance().currentExpirationDate = currentExpirationDate;
+ return this;
+ }
+
+ public Builder setPeriod(Period period) {
+ getInstance().period = period;
+ return this;
+ }
+ }
}
/** A transfer operation for a {@link Domain}. */
@XmlRootElement
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"name", "period", "authInfo"})
public static class Transfer extends AbstractSingleResourceCommand {
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+
/** The period to extend this domain's registration upon completion of the transfer. */
- Period period;
+ @XmlElement Period period;
/** Authorization info used to validate if client has permissions to perform this operation. */
DomainAuthInfo authInfo;
@@ -267,25 +366,40 @@ public class DomainCommand {
}
@Override
- public DomainAuthInfo getAuthInfo() {
+ public AuthInfo getAuthInfo() {
return authInfo;
}
}
/** An update to a {@link Domain}. */
@XmlRootElement
- @XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
- public static class Update extends ResourceUpdate
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"name", "innerAdd", "innerRemove", "innerChange"})
+ public static class Update
+ extends ResourceUpdate
implements CreateOrUpdate {
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
- protected AddRemove innerAdd;
+ protected DomainAddRemove innerAdd;
@XmlElement(name = "rem")
- protected AddRemove innerRemove;
+ protected DomainAddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
@@ -293,25 +407,49 @@ public class DomainCommand {
}
@Override
- protected AddRemove getNullableInnerAdd() {
+ protected DomainAddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
- protected AddRemove getNullableInnerRemove() {
+ protected DomainAddRemove getNullableInnerRemove() {
return innerRemove;
}
public boolean noChangesPresent() {
- AddRemove emptyAddRemove = new AddRemove();
+ DomainAddRemove emptyAddRemove = new DomainAddRemove();
return emptyAddRemove.equals(getInnerAdd())
&& emptyAddRemove.equals(getInnerRemove())
&& new Change().equals(getInnerChange());
}
+ /** Builder for {@link Update}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setTargetId(String targetId) {
+ getInstance().setTargetId(targetId);
+ return this;
+ }
+
+ public Builder setInnerAdd(DomainAddRemove innerAdd) {
+ getInstance().innerAdd = innerAdd;
+ return this;
+ }
+
+ public Builder setInnerRemove(DomainAddRemove innerRemove) {
+ getInstance().innerRemove = innerRemove;
+ return this;
+ }
+
+ public Builder setInnerChange(Change innerChange) {
+ getInstance().innerChange = innerChange;
+ return this;
+ }
+ }
+
/** The inner change type on a domain update command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"nameserverHostNames", "foreignKeyedDesignatedContacts", "statusValues"})
- public static class AddRemove extends ResourceUpdate.AddRemove {
+ public static class DomainAddRemove extends ResourceUpdate.AddRemove {
/** Fully qualified host names of the hosts that are the nameservers for the domain. */
@XmlElementWrapper(name = "ns")
@XmlElement(name = "hostObj")
@@ -324,6 +462,25 @@ public class DomainCommand {
@XmlElement(name = "contact")
Set foreignKeyedDesignatedContacts;
+ @XmlElement(name = "status")
+ Set statusValues;
+
+ public boolean isEmpty() {
+ return isNullOrEmpty(nameserverHostNames)
+ && isNullOrEmpty(foreignKeyedDesignatedContacts)
+ && isNullOrEmpty(statusValues);
+ }
+
+ @Override
+ public void setStatusValues(ImmutableSet statusValues) {
+ this.statusValues = statusValues;
+ }
+
+ @Override
+ public ImmutableSet getStatusValues() {
+ return nullToEmptyImmutableCopy(statusValues);
+ }
+
public ImmutableSet getNameserverHostNames() {
return nullSafeImmutableCopy(nameserverHostNames);
}
@@ -332,11 +489,25 @@ public class DomainCommand {
return nullToEmptyImmutableCopy(nameservers);
}
- /** Creates a copy of this {@link AddRemove} with hard links to hosts and contacts. */
- private AddRemove cloneAndLinkReferences(Instant now)
+ /** Builder for {@link DomainAddRemove}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setNameserverHostNames(ImmutableSet nameserverHostNames) {
+ getInstance().nameserverHostNames =
+ isNullOrEmpty(nameserverHostNames) ? null : nameserverHostNames;
+ return this;
+ }
+
+ public Builder setStatusValues(ImmutableSet statusValues) {
+ getInstance().statusValues = isNullOrEmpty(statusValues) ? null : statusValues;
+ return this;
+ }
+ }
+
+ /** Creates a copy of this {@link DomainAddRemove} with hard links to hosts and contacts. */
+ private DomainAddRemove cloneAndLinkReferences(Instant now)
throws InvalidReferencesException, ContactsProhibitedException {
- AddRemove clone = clone(this);
- clone.nameservers = linkHosts(clone.nameserverHostNames, now);
+ DomainAddRemove clone = clone(this);
+ clone.nameservers = linkHosts(nullSafeImmutableCopy(clone.nameserverHostNames), now);
if (!isNullOrEmpty(foreignKeyedDesignatedContacts)) {
throw new ContactsProhibitedException();
}
@@ -345,8 +516,17 @@ public class DomainCommand {
}
/** The inner change type on a domain update command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"registrantContactId", "authInfo"})
public static class Change extends DomainCreateOrChange {
+ /** Builder for {@link Change}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setAuthInfo(DomainAuthInfo authInfo) {
+ getInstance().authInfo = authInfo;
+ return this;
+ }
+ }
+
Change cloneAndLinkReferences() throws RegistrantProhibitedException {
Change clone = clone(this);
if (clone.registrantContactId != null) {
@@ -373,7 +553,7 @@ public class DomainCommand {
}
}
- private static Set> linkHosts(Set hostNames, Instant now)
+ private static ImmutableSet> linkHosts(ImmutableSet hostNames, Instant now)
throws InvalidReferencesException {
if (hostNames == null) {
return null;
@@ -383,7 +563,7 @@ public class DomainCommand {
/** Loads host keys to cached EPP resources by their foreign keys. */
private static ImmutableMap> loadByForeignKeysCached(
- Set foreignKeys, Instant now) throws InvalidReferencesException {
+ ImmutableSet foreignKeys, Instant now) throws InvalidReferencesException {
ImmutableMap> fks =
ForeignKeyUtils.loadKeysByCacheIfEnabled(Host.class, foreignKeys, now);
if (!fks.keySet().equals(foreignKeys)) {
@@ -394,14 +574,14 @@ public class DomainCommand {
}
/** Exception to throw when referenced objects don't exist. */
- public static class InvalidReferencesException extends Exception {
+ public static class InvalidReferencesException extends ParameterValuePolicyErrorException {
private final ImmutableSet foreignKeys;
private final Class> type;
- InvalidReferencesException(Class> type, ImmutableSet foreignKeys) {
+ public InvalidReferencesException(Class> type, Set foreignKeys) {
super(String.format("Invalid %s reference IDs: %s", type.getSimpleName(), foreignKeys));
this.type = checkNotNull(type);
- this.foreignKeys = foreignKeys;
+ this.foreignKeys = nullToEmptyImmutableCopy(foreignKeys);
}
public ImmutableSet getForeignKeys() {
diff --git a/core/src/main/java/google/registry/model/domain/fee/Fee.java b/core/src/main/java/google/registry/model/domain/fee/Fee.java
index cefa86fcc..a5ce86241 100644
--- a/core/src/main/java/google/registry/model/domain/fee/Fee.java
+++ b/core/src/main/java/google/registry/model/domain/fee/Fee.java
@@ -20,16 +20,22 @@ import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
+import google.registry.model.Buildable;
import google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
import java.math.BigDecimal;
import java.time.Instant;
+import java.time.Period;
/**
- * A fee, in currency units specified elsewhere in the xml, with type of the fee an optional fee
- * description.
+ * A fee, in currency units specified elsewhere in the XML, with a type and an optional description.
*/
public class Fee extends BaseFee {
+ @Override
+ public Fee clone() {
+ return (Fee) super.clone();
+ }
+
/** Creates a Fee for the given cost and type with the default description. */
public static Fee create(
BigDecimal cost, FeeType type, boolean isPremium, Object... descriptionArgs) {
@@ -55,7 +61,7 @@ public class Fee extends BaseFee {
BigDecimal cost, FeeType type, boolean isPremium, String description) {
Fee instance = new Fee();
instance.cost = checkNotNull(cost);
- checkArgument(instance.cost.signum() >= 0, "Cost must be a positive number");
+ checkArgument(instance.cost.signum() >= 0, "Cost must be a non-negative number");
instance.type = checkNotNull(type);
instance.isPremium = isPremium;
instance.description = description;
@@ -68,4 +74,38 @@ public class Fee extends BaseFee {
ServiceExtension.FEE_0_12.getUri(),
ServiceExtension.FEE_0_11.getUri(),
ServiceExtension.FEE_0_6.getUri());
+
+ /** Builder for {@link Fee}. */
+ public static class Builder extends Buildable.Builder {
+
+ /** Sets the cost of the fee. */
+ public Builder setCost(BigDecimal cost) {
+ getInstance().cost = cost;
+ return this;
+ }
+
+ /** Sets the description of the fee. */
+ public Builder setDescription(String description) {
+ getInstance().description = description;
+ return this;
+ }
+
+ /** Sets whether the fee is refundable. */
+ public Builder setRefundable(Boolean refundable) {
+ getInstance().refundable = refundable;
+ return this;
+ }
+
+ /** Sets the grace period of the fee. */
+ public Builder setGracePeriod(Period gracePeriod) {
+ getInstance().gracePeriod = gracePeriod;
+ return this;
+ }
+
+ /** Sets when the fee is applied. */
+ public Builder setApplied(AppliedType applied) {
+ getInstance().applied = applied;
+ return this;
+ }
+ }
}
diff --git a/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java b/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java
index 18bce08cc..d14f5e2f8 100644
--- a/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java
+++ b/core/src/main/java/google/registry/model/domain/fee/FeeQueryCommandExtensionItem.java
@@ -78,6 +78,10 @@ public abstract class FeeQueryCommandExtensionItem extends ImmutableObject {
/** The period for the command being checked. */
Period period;
+ public void setPeriod(Period period) {
+ this.period = period;
+ }
+
/**
* Three-character ISO4217 currency code.
*
diff --git a/core/src/main/java/google/registry/model/domain/fee/FeeTransformCommandExtension.java b/core/src/main/java/google/registry/model/domain/fee/FeeTransformCommandExtension.java
index 6245fc062..df36b88cd 100644
--- a/core/src/main/java/google/registry/model/domain/fee/FeeTransformCommandExtension.java
+++ b/core/src/main/java/google/registry/model/domain/fee/FeeTransformCommandExtension.java
@@ -30,7 +30,7 @@ public abstract class FeeTransformCommandExtension
extends ImmutableObject implements CommandExtension {
/** The currency of the fee. */
- CurrencyUnit currency;
+ @XmlElement public CurrencyUnit currency;
/**
* The magnitude of the fee, in the specified units, with an optional description.
@@ -38,7 +38,7 @@ public abstract class FeeTransformCommandExtension
* This is a list because a single operation can involve multiple fees.
*/
@XmlElement(name = "fee")
- List fees;
+ public List fees;
public CurrencyUnit getCurrency() {
return currency;
diff --git a/core/src/main/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java b/core/src/main/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java
index b7afae101..9570790c8 100644
--- a/core/src/main/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java
+++ b/core/src/main/java/google/registry/model/domain/fee/FeeTransformResponseExtension.java
@@ -31,7 +31,7 @@ import org.joda.money.CurrencyUnit;
public class FeeTransformResponseExtension extends ImmutableObject implements ResponseExtension {
/** The currency of the fee. */
- CurrencyUnit currency;
+ @XmlElement CurrencyUnit currency;
/**
* The magnitude of the fee, in the specified units, with an optional description.
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionItemV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionItemV06.java
index 7719ce724..d06afae28 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionItemV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionItemV06.java
@@ -14,6 +14,7 @@
package google.registry.model.domain.fee06;
+import google.registry.model.domain.Period;
import google.registry.model.domain.fee.FeeCheckCommandExtensionItem;
import google.registry.model.domain.fee.FeeExtensionCommandDescriptor;
import jakarta.xml.bind.annotation.XmlType;
@@ -33,6 +34,16 @@ public class FeeCheckCommandExtensionItemV06 extends FeeCheckCommandExtensionIte
/** The command being checked. */
FeeExtensionCommandDescriptor command;
+ public static FeeCheckCommandExtensionItemV06 create(
+ String name, CurrencyUnit currency, FeeExtensionCommandDescriptor command, Period period) {
+ FeeCheckCommandExtensionItemV06 instance = new FeeCheckCommandExtensionItemV06();
+ instance.name = name;
+ instance.currency = currency;
+ instance.command = command;
+ instance.setPeriod(period);
+ return instance;
+ }
+
/** The name of the command being checked. */
@Override
public CommandName getCommandName() {
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionV06.java
index 6d75de1d7..1a96fc24e 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeCheckCommandExtensionV06.java
@@ -25,16 +25,22 @@ import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.List;
import org.joda.money.CurrencyUnit;
-/** Version 0.6 of the fee extension that may be present on domain check commands. */
+/**
+ * An XML data object that represents version 0.6 of the fee extension that may be present on EPP
+ * domain check commands.
+ */
@XmlRootElement(name = "check")
public class FeeCheckCommandExtensionV06 extends ImmutableObject
implements FeeCheckCommandExtension<
- FeeCheckCommandExtensionItemV06,
- FeeCheckResponseExtensionV06> {
+ FeeCheckCommandExtensionItemV06, FeeCheckResponseExtensionV06> {
@XmlElement(name = "domain")
List items;
+ public void setItems(ImmutableList items) {
+ this.items = items;
+ }
+
@Override
public CurrencyUnit getCurrency() {
return null; // This version of the fee extension doesn't specify a top-level currency.
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeCreateCommandExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeCreateCommandExtensionV06.java
index 7b4071496..80eb5ad47 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeCreateCommandExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeCreateCommandExtensionV06.java
@@ -15,13 +15,20 @@
package google.registry.model.domain.fee06;
import com.google.common.collect.ImmutableList;
+import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
+import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
+import org.joda.money.CurrencyUnit;
-/** A fee extension that may be present on domain create commands. */
+/**
+ * An XML data object that represents a fee extension that may be present on EPP domain create
+ * commands.
+ */
@XmlRootElement(name = "create")
@XmlType(propOrder = {"currency", "fees"})
public class FeeCreateCommandExtensionV06 extends FeeCreateCommandExtension {
@@ -31,12 +38,23 @@ public class FeeCreateCommandExtensionV06 extends FeeCreateCommandExtension {
return new FeeTransformResponseExtension.Builder(new FeeCreateResponseExtensionV06());
}
- /**
- * This method is overridden and not annotated for JAXB because this version of the extension
- * doesn't support the "credit" field.
- */
+ /** This version of the extension doesn't support the "credit" field. */
@Override
+ @XmlTransient
public ImmutableList getCredits() {
return ImmutableList.of();
}
+
+ /** Builder for {@link FeeCreateCommandExtensionV06}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setCurrency(CurrencyUnit currency) {
+ getInstance().currency = currency;
+ return this;
+ }
+
+ public Builder setFees(ImmutableList fees) {
+ getInstance().fees = fees;
+ return this;
+ }
+ }
}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeCreateResponseExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeCreateResponseExtensionV06.java
index b58dde263..410c2e2f2 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeCreateResponseExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeCreateResponseExtensionV06.java
@@ -14,8 +14,6 @@
package google.registry.model.domain.fee06;
-import com.google.common.collect.ImmutableList;
-import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@@ -25,12 +23,5 @@ import jakarta.xml.bind.annotation.XmlType;
* domain create commands.
*/
@XmlRootElement(name = "creData")
-@XmlType(propOrder = {"currency", "fees"})
-public class FeeCreateResponseExtensionV06 extends FeeTransformResponseExtension {
-
- /** This version of the extension doesn't support the "credit" field. */
- @Override
- public ImmutableList getCredits() {
- return ImmutableList.of();
- }
-}
+@XmlType(propOrder = {"currency", "fees", "credits"})
+public class FeeCreateResponseExtensionV06 extends FeeTransformResponseExtension {}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeDeleteResponseExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeDeleteResponseExtensionV06.java
index 749865610..e7cbf4a10 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeDeleteResponseExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeDeleteResponseExtensionV06.java
@@ -20,7 +20,7 @@ import jakarta.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
- * domain create commands.
+ * domain delete commands.
*/
@XmlRootElement(name = "delData")
@XmlType(propOrder = {"currency", "fees", "credits"})
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeRenewCommandExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeRenewCommandExtensionV06.java
index 73e593913..8a43d77fa 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeRenewCommandExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeRenewCommandExtensionV06.java
@@ -19,9 +19,13 @@ import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeRenewCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
-/** A fee extension that may be present on domain renew commands. */
+/**
+ * An XML data object that represents a fee extension that may be present on EPP domain renew
+ * commands.
+ */
@XmlRootElement(name = "renew")
@XmlType(propOrder = {"currency", "fees"})
public class FeeRenewCommandExtensionV06 extends FeeRenewCommandExtension {
@@ -33,6 +37,7 @@ public class FeeRenewCommandExtensionV06 extends FeeRenewCommandExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
+ @XmlTransient
public ImmutableList getCredits() {
return ImmutableList.of();
}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeRenewResponseExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeRenewResponseExtensionV06.java
index 7f11aa5bc..07c58c3b9 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeRenewResponseExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeRenewResponseExtensionV06.java
@@ -14,8 +14,6 @@
package google.registry.model.domain.fee06;
-import com.google.common.collect.ImmutableList;
-import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@@ -25,12 +23,5 @@ import jakarta.xml.bind.annotation.XmlType;
* domain renew commands.
*/
@XmlRootElement(name = "renData")
-@XmlType(propOrder = {"currency", "fees"})
-public class FeeRenewResponseExtensionV06 extends FeeTransformResponseExtension {
-
- /** This version of the extension doesn't support the "credit" field. */
- @Override
- public ImmutableList getCredits() {
- return super.getCredits();
- }
-}
+@XmlType(propOrder = {"currency", "fees", "credits"})
+public class FeeRenewResponseExtensionV06 extends FeeTransformResponseExtension {}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeTransferCommandExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeTransferCommandExtensionV06.java
index e3c1ec1f7..194586f25 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeTransferCommandExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeTransferCommandExtensionV06.java
@@ -19,9 +19,13 @@ import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
-/** A fee extension that may be present on domain transfer requests. */
+/**
+ * An XML data object that represents a fee extension that may be present on EPP domain transfer
+ * commands.
+ */
@XmlRootElement(name = "transfer")
@XmlType(propOrder = {"currency", "fees"})
public class FeeTransferCommandExtensionV06 extends FeeTransferCommandExtension {
@@ -33,6 +37,7 @@ public class FeeTransferCommandExtensionV06 extends FeeTransferCommandExtension
/** This version of the extension doesn't support the "credit" field. */
@Override
+ @XmlTransient
public ImmutableList getCredits() {
return ImmutableList.of();
}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeTransferResponseExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeTransferResponseExtensionV06.java
index 1d92391b2..b632d43ac 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeTransferResponseExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeTransferResponseExtensionV06.java
@@ -14,23 +14,14 @@
package google.registry.model.domain.fee06;
-import com.google.common.collect.ImmutableList;
-import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a fee extension that may be present on the response to EPP
- * domain transfer requests.
+ * domain transfer commands.
*/
@XmlRootElement(name = "trnData")
-@XmlType(propOrder = {"currency", "fees"})
-public class FeeTransferResponseExtensionV06 extends FeeTransformResponseExtension {
-
- /** This version of the extension doesn't support the "credit" field. */
- @Override
- public ImmutableList getCredits() {
- return super.getCredits();
- }
-}
+@XmlType(propOrder = {"currency", "fees", "credits"})
+public class FeeTransferResponseExtensionV06 extends FeeTransformResponseExtension {}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateCommandExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateCommandExtensionV06.java
index 3fce12cba..d6327098d 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateCommandExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateCommandExtensionV06.java
@@ -19,9 +19,13 @@ import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
-/** A fee extension that may be present on domain update commands. */
+/**
+ * An XML data object that represents a fee extension that may be present on EPP domain update
+ * commands.
+ */
@XmlRootElement(name = "update")
@XmlType(propOrder = {"currency", "fees"})
public class FeeUpdateCommandExtensionV06 extends FeeUpdateCommandExtension {
@@ -33,6 +37,7 @@ public class FeeUpdateCommandExtensionV06 extends FeeUpdateCommandExtension {
/** This version of the extension doesn't support the "credit" field. */
@Override
+ @XmlTransient
public ImmutableList getCredits() {
return ImmutableList.of();
}
diff --git a/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateResponseExtensionV06.java b/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateResponseExtensionV06.java
index 154b78d76..9b6245929 100644
--- a/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateResponseExtensionV06.java
+++ b/core/src/main/java/google/registry/model/domain/fee06/FeeUpdateResponseExtensionV06.java
@@ -14,8 +14,6 @@
package google.registry.model.domain.fee06;
-import com.google.common.collect.ImmutableList;
-import google.registry.model.domain.fee.Credit;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
@@ -25,12 +23,5 @@ import jakarta.xml.bind.annotation.XmlType;
* domain update commands.
*/
@XmlRootElement(name = "updData")
-@XmlType(propOrder = {"currency", "fees"})
-public class FeeUpdateResponseExtensionV06 extends FeeTransformResponseExtension {
-
- /** This version of the extension doesn't support the "credit" field. */
- @Override
- public ImmutableList getCredits() {
- return super.getCredits();
- }
-}
+@XmlType(propOrder = {"currency", "fees", "credits"})
+public class FeeUpdateResponseExtensionV06 extends FeeTransformResponseExtension {}
diff --git a/core/src/main/java/google/registry/model/domain/fee12/FeeCreateCommandExtensionV12.java b/core/src/main/java/google/registry/model/domain/fee12/FeeCreateCommandExtensionV12.java
index 2ff8fcbe1..9aed6ef09 100644
--- a/core/src/main/java/google/registry/model/domain/fee12/FeeCreateCommandExtensionV12.java
+++ b/core/src/main/java/google/registry/model/domain/fee12/FeeCreateCommandExtensionV12.java
@@ -17,13 +17,16 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
+import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
+import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCreateCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
+import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain create commands. */
@XmlRootElement(name = "create")
@@ -42,4 +45,22 @@ public class FeeCreateCommandExtensionV12 extends FeeCreateCommandExtension {
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeCreateResponseExtensionV12());
}
+
+ /** Builder for {@link FeeCreateCommandExtensionV12}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setCurrency(CurrencyUnit currency) {
+ getInstance().currency = currency;
+ return this;
+ }
+
+ public Builder setFees(ImmutableList fees) {
+ getInstance().fees = fees;
+ return this;
+ }
+
+ public Builder setCredits(ImmutableList credits) {
+ getInstance().credits = credits;
+ return this;
+ }
+ }
}
diff --git a/core/src/main/java/google/registry/model/domain/fee12/FeeRenewCommandExtensionV12.java b/core/src/main/java/google/registry/model/domain/fee12/FeeRenewCommandExtensionV12.java
index d1a0b120f..f9f9e886c 100644
--- a/core/src/main/java/google/registry/model/domain/fee12/FeeRenewCommandExtensionV12.java
+++ b/core/src/main/java/google/registry/model/domain/fee12/FeeRenewCommandExtensionV12.java
@@ -17,18 +17,21 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
+import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
+import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeRenewCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
+import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain renew commands. */
@XmlRootElement(name = "renew")
@XmlType(propOrder = {"currency", "fees", "credits"})
-public class FeeRenewCommandExtensionV12 extends FeeRenewCommandExtension {
+public class FeeRenewCommandExtensionV12 extends FeeRenewCommandExtension {
@XmlElement(name = "credit")
List credits;
@@ -42,4 +45,22 @@ public class FeeRenewCommandExtensionV12 extends FeeRenewCommandExtension {
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeRenewResponseExtensionV12());
}
+
+ /** Builder for {@link FeeRenewCommandExtensionV12}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setCurrency(CurrencyUnit currency) {
+ getInstance().currency = currency;
+ return this;
+ }
+
+ public Builder setFees(ImmutableList fees) {
+ getInstance().fees = fees;
+ return this;
+ }
+
+ public Builder setCredits(ImmutableList credits) {
+ getInstance().credits = credits;
+ return this;
+ }
+ }
}
diff --git a/core/src/main/java/google/registry/model/domain/fee12/FeeTransferCommandExtensionV12.java b/core/src/main/java/google/registry/model/domain/fee12/FeeTransferCommandExtensionV12.java
index 53049bb9d..f9800a1c3 100644
--- a/core/src/main/java/google/registry/model/domain/fee12/FeeTransferCommandExtensionV12.java
+++ b/core/src/main/java/google/registry/model/domain/fee12/FeeTransferCommandExtensionV12.java
@@ -17,13 +17,16 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
+import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
+import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransferCommandExtension;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
+import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain transfer requests. */
@XmlRootElement(name = "transfer")
@@ -42,4 +45,22 @@ public class FeeTransferCommandExtensionV12 extends FeeTransferCommandExtension
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeTransferResponseExtensionV12());
}
+
+ /** Builder for {@link FeeTransferCommandExtensionV12}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setCurrency(CurrencyUnit currency) {
+ getInstance().currency = currency;
+ return this;
+ }
+
+ public Builder setFees(ImmutableList fees) {
+ getInstance().fees = fees;
+ return this;
+ }
+
+ public Builder setCredits(ImmutableList credits) {
+ getInstance().credits = credits;
+ return this;
+ }
+ }
}
diff --git a/core/src/main/java/google/registry/model/domain/fee12/FeeUpdateCommandExtensionV12.java b/core/src/main/java/google/registry/model/domain/fee12/FeeUpdateCommandExtensionV12.java
index 71a430093..86a600487 100644
--- a/core/src/main/java/google/registry/model/domain/fee12/FeeUpdateCommandExtensionV12.java
+++ b/core/src/main/java/google/registry/model/domain/fee12/FeeUpdateCommandExtensionV12.java
@@ -17,13 +17,16 @@ package google.registry.model.domain.fee12;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
+import google.registry.model.Buildable;
import google.registry.model.domain.fee.Credit;
+import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeTransformResponseExtension;
import google.registry.model.domain.fee.FeeUpdateCommandExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.List;
+import org.joda.money.CurrencyUnit;
/** A fee extension that may be present on domain update commands. */
@XmlRootElement(name = "update")
@@ -42,4 +45,22 @@ public class FeeUpdateCommandExtensionV12 extends FeeUpdateCommandExtension {
public FeeTransformResponseExtension.Builder createResponseBuilder() {
return new FeeTransformResponseExtension.Builder(new FeeUpdateResponseExtensionV12());
}
+
+ /** Builder for {@link FeeUpdateCommandExtensionV12}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setCurrency(CurrencyUnit currency) {
+ getInstance().currency = currency;
+ return this;
+ }
+
+ public Builder setFees(ImmutableList fees) {
+ getInstance().fees = fees;
+ return this;
+ }
+
+ public Builder setCredits(ImmutableList credits) {
+ getInstance().credits = credits;
+ return this;
+ }
+ }
}
diff --git a/core/src/main/java/google/registry/model/domain/launch/LaunchCheckExtension.java b/core/src/main/java/google/registry/model/domain/launch/LaunchCheckExtension.java
index 5c3d2d0b8..b3072f7fb 100644
--- a/core/src/main/java/google/registry/model/domain/launch/LaunchCheckExtension.java
+++ b/core/src/main/java/google/registry/model/domain/launch/LaunchCheckExtension.java
@@ -19,34 +19,37 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlAttribute;
+import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlEnumValue;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
/**
* An XML data object that represents a launch extension that may be present on EPP domain check
* commands.
*
* This object holds XML data which JAXB will unmarshal from an EPP domain check command
- * extension. The XML will have the following enclosing structure:
+ * extension. The XML will have the following enclosing structure:
*
- *
{@code
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * }
+ * {@code
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * }
*
* @see CommandExtension
*/
@XmlRootElement(name = "check")
+@XmlType(propOrder = "phase")
public class LaunchCheckExtension extends ImmutableObject implements CommandExtension {
/** The default check type is "claims" if not specified. */
@@ -67,11 +70,18 @@ public class LaunchCheckExtension extends ImmutableObject implements CommandExte
* The launch phase this command is intended to run against. If it does not match the server's
* current launch phase, the command will be rejected.
*/
- LaunchPhase phase;
+ @XmlElement LaunchPhase phase;
@XmlAttribute
CheckType type;
+ public static LaunchCheckExtension create(CheckType type, LaunchPhase phase) {
+ LaunchCheckExtension instance = new LaunchCheckExtension();
+ instance.type = type;
+ instance.phase = phase;
+ return instance;
+ }
+
public CheckType getCheckType() {
return firstNonNull(type, DEFAULT_CHECK_TYPE);
}
diff --git a/core/src/main/java/google/registry/model/domain/metadata/MetadataExtension.java b/core/src/main/java/google/registry/model/domain/metadata/MetadataExtension.java
index f098982c7..5d7f41fff 100644
--- a/core/src/main/java/google/registry/model/domain/metadata/MetadataExtension.java
+++ b/core/src/main/java/google/registry/model/domain/metadata/MetadataExtension.java
@@ -14,39 +14,62 @@
package google.registry.model.domain.metadata;
+import static com.google.common.base.MoreObjects.firstNonNull;
+
+import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
+import javax.annotation.Nullable;
-/** A metadata extension that may be present on EPP create/mutate commands. */
+/**
+ * Extension for EPP commands that provides metadata.
+ *
+ * @see EPP Metadata Extension
+ */
@XmlRootElement(name = "metadata")
+@XmlType(propOrder = {"reason", "requestedByRegistrar", "isAnchorTenant"})
public class MetadataExtension extends ImmutableObject implements CommandExtension {
- /** The reason for the change. */
- @XmlElement(name = "reason")
- String reason;
+ /** Reason for the command. */
+ @XmlElement @Nullable String reason;
- /** Whether a change was requested by a registrar. */
- @XmlElement(name = "requestedByRegistrar")
- boolean requestedByRegistrar;
+ /** Whether the command was requested by a registrar. */
+ @XmlElement Boolean requestedByRegistrar;
- /**
- * Whether a domain is being created for an anchor tenant. This field is only
- * relevant for domain creates, and should be omitted for all other operations.
- */
+ /** Whether this is an anchor tenant. */
@XmlElement(name = "anchorTenant")
- boolean isAnchorTenant;
+ Boolean isAnchorTenant;
public String getReason() {
return reason;
}
- public boolean getRequestedByRegistrar() {
+ public Boolean getRequestedByRegistrar() {
return requestedByRegistrar;
}
- public boolean getIsAnchorTenant() {
- return isAnchorTenant;
+ public Boolean getIsAnchorTenant() {
+ return firstNonNull(isAnchorTenant, false);
+ }
+
+ /** Builder for {@link MetadataExtension}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setReason(String reason) {
+ getInstance().reason = reason;
+ return this;
+ }
+
+ public Builder setRequestedByRegistrar(Boolean requestedByRegistrar) {
+ getInstance().requestedByRegistrar = requestedByRegistrar;
+ return this;
+ }
+
+ public Builder setAnchorTenant(Boolean isAnchorTenant) {
+ getInstance().isAnchorTenant = isAnchorTenant;
+ return this;
+ }
}
}
diff --git a/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java b/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java
index 82195b819..81509fac3 100644
--- a/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java
+++ b/core/src/main/java/google/registry/model/domain/secdns/DomainDsDataBase.java
@@ -23,12 +23,14 @@ import jakarta.persistence.Transient;
import jakarta.xml.bind.DatatypeConverter;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlTransient;
+import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
/** Base class for {@link DomainDsData} and {@link DomainDsDataHistory}. */
@MappedSuperclass
@Access(AccessType.FIELD)
+@XmlType(propOrder = {"keyTag", "algorithm", "digestType", "digest"})
public abstract class DomainDsDataBase extends ImmutableObject implements UnsafeSerializable {
@XmlTransient @Transient @Insignificant String domainRepoId;
diff --git a/core/src/main/java/google/registry/model/domain/secdns/SecDnsCreateExtension.java b/core/src/main/java/google/registry/model/domain/secdns/SecDnsCreateExtension.java
index a7a12e796..b3297915e 100644
--- a/core/src/main/java/google/registry/model/domain/secdns/SecDnsCreateExtension.java
+++ b/core/src/main/java/google/registry/model/domain/secdns/SecDnsCreateExtension.java
@@ -14,11 +14,13 @@
package google.registry.model.domain.secdns;
-import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
+import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
+import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
+import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import java.util.Set;
@@ -33,9 +35,10 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
* We do not support expirations, but we need this field to be able to return appropriate
* errors.
*/
- Long maxSigLife;
+ @XmlElement Long maxSigLife;
/** Signatures for this domain. */
+ @XmlElement(name = "dsData")
Set dsData;
public Long getMaxSigLife() {
@@ -43,6 +46,19 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
}
public ImmutableSet getDsData() {
- return nullSafeImmutableCopy(dsData);
+ return nullToEmptyImmutableCopy(dsData);
+ }
+
+ /** Builder for {@link SecDnsCreateExtension}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setDsData(ImmutableSet dsData) {
+ getInstance().dsData = dsData;
+ return this;
+ }
+
+ public Builder setMaxSigLife(Long maxSigLife) {
+ getInstance().maxSigLife = maxSigLife;
+ return this;
+ }
}
}
diff --git a/core/src/main/java/google/registry/model/domain/secdns/SecDnsUpdateExtension.java b/core/src/main/java/google/registry/model/domain/secdns/SecDnsUpdateExtension.java
index a9032c277..9001a0863 100644
--- a/core/src/main/java/google/registry/model/domain/secdns/SecDnsUpdateExtension.java
+++ b/core/src/main/java/google/registry/model/domain/secdns/SecDnsUpdateExtension.java
@@ -17,6 +17,7 @@ package google.registry.model.domain.secdns;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
+import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.eppinput.EppInput.CommandExtension;
import jakarta.xml.bind.annotation.XmlAttribute;
@@ -46,7 +47,7 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
Remove remove;
/** Allows adding new delegations. */
- Add add;
+ @XmlElement Add add;
/** Would allow changing maxSigLife except that we don't support it. */
@XmlElement(name = "chg")
@@ -68,31 +69,88 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
return Optional.ofNullable(change);
}
+ /** Builder for {@link SecDnsUpdateExtension}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setUrgent(Boolean urgent) {
+ getInstance().urgent = urgent;
+ return this;
+ }
+
+ public Builder setRemove(Remove remove) {
+ getInstance().remove = remove;
+ return this;
+ }
+
+ public Builder setAdd(Add add) {
+ getInstance().add = add;
+ return this;
+ }
+ }
+
@XmlTransient
abstract static class AddRemoveBase extends ImmutableObject {
- /** Delegations to add or remove. */
+ abstract static class Builder>
+ extends Buildable.Builder {
+ public abstract B setDsData(ImmutableSet dsData);
+ }
+ }
+
+ /** The inner add type on the update extension. */
+ @XmlType(propOrder = "dsData")
+ public static class Add extends AddRemoveBase {
+ /** Delegations to add. */
+ @XmlElement(name = "dsData")
Set dsData;
public ImmutableSet getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
- }
- /** The inner add type on the update extension. */
- public static class Add extends AddRemoveBase {}
+ /** Builder for {@link Add}. */
+ public static class Builder extends AddRemoveBase.Builder {
+ @Override
+ public Builder setDsData(ImmutableSet dsData) {
+ getInstance().dsData = dsData;
+ return this;
+ }
+ }
+ }
/** The inner remove type on the update extension. */
@XmlType(propOrder = {"all", "dsData"})
public static class Remove extends AddRemoveBase {
/** Whether to remove all delegations. */
- Boolean all;
+ @XmlElement Boolean all;
+
+ /** Delegations to remove. */
+ @XmlElement(name = "dsData")
+ Set dsData;
public Boolean getAll() {
return all;
}
+
+ public ImmutableSet getDsData() {
+ return nullToEmptyImmutableCopy(dsData);
+ }
+
+ /** Builder for {@link Remove}. */
+ public static class Builder extends AddRemoveBase.Builder {
+ public Builder setAll(Boolean all) {
+ getInstance().all = all;
+ return this;
+ }
+
+ @Override
+ public Builder setDsData(ImmutableSet dsData) {
+ getInstance().dsData = dsData;
+ return this;
+ }
+ }
}
/** The inner change type on the update extension, though we don't actually support changes. */
+ @XmlType(propOrder = "maxSigLife")
public static class Change extends ImmutableObject {
/**
* Time in seconds until the signature should expire.
@@ -100,6 +158,7 @@ public class SecDnsUpdateExtension extends ImmutableObject implements CommandExt
* We do not support expirations, but we need this field to be able to return appropriate
* errors.
*/
+ @XmlElement(name = "maxSigLife")
Long maxSigLife;
}
}
diff --git a/core/src/main/java/google/registry/model/domain/superuser/DomainDeleteSuperuserExtension.java b/core/src/main/java/google/registry/model/domain/superuser/DomainDeleteSuperuserExtension.java
index e63d895b0..2b7151f68 100644
--- a/core/src/main/java/google/registry/model/domain/superuser/DomainDeleteSuperuserExtension.java
+++ b/core/src/main/java/google/registry/model/domain/superuser/DomainDeleteSuperuserExtension.java
@@ -16,9 +16,11 @@ package google.registry.model.domain.superuser;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
/** A superuser extension that may be present on domain delete commands. */
@XmlRootElement(name = "domainDelete")
+@XmlType(propOrder = {"redemptionGracePeriodDays", "pendingDeleteDays"})
public class DomainDeleteSuperuserExtension extends SuperuserExtension {
@XmlElement(name = "redemptionGracePeriodDays")
@@ -27,6 +29,14 @@ public class DomainDeleteSuperuserExtension extends SuperuserExtension {
@XmlElement(name = "pendingDeleteDays")
int pendingDeleteDays;
+ public static DomainDeleteSuperuserExtension create(
+ int redemptionGracePeriodDays, int pendingDeleteDays) {
+ DomainDeleteSuperuserExtension instance = new DomainDeleteSuperuserExtension();
+ instance.redemptionGracePeriodDays = redemptionGracePeriodDays;
+ instance.pendingDeleteDays = pendingDeleteDays;
+ return instance;
+ }
+
public int getRedemptionGracePeriodDays() {
return redemptionGracePeriodDays;
}
diff --git a/core/src/main/java/google/registry/model/domain/superuser/DomainUpdateSuperuserExtension.java b/core/src/main/java/google/registry/model/domain/superuser/DomainUpdateSuperuserExtension.java
index ac715d90c..54552bd3a 100644
--- a/core/src/main/java/google/registry/model/domain/superuser/DomainUpdateSuperuserExtension.java
+++ b/core/src/main/java/google/registry/model/domain/superuser/DomainUpdateSuperuserExtension.java
@@ -14,22 +14,28 @@
package google.registry.model.domain.superuser;
-import static com.google.common.base.Strings.isNullOrEmpty;
-
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
import java.util.Optional;
import javax.annotation.Nullable;
/** A superuser extension that may be present on domain update commands. */
@XmlRootElement(name = "domainUpdate")
+@XmlType(propOrder = "autorenews")
public class DomainUpdateSuperuserExtension extends SuperuserExtension {
@XmlElement(name = "autorenews")
@Nullable
- String autorenews;
+ Boolean autorenews;
+
+ public static DomainUpdateSuperuserExtension create(@Nullable Boolean autorenews) {
+ DomainUpdateSuperuserExtension instance = new DomainUpdateSuperuserExtension();
+ instance.autorenews = autorenews;
+ return instance;
+ }
public Optional getAutorenews() {
- return Optional.ofNullable(isNullOrEmpty(autorenews) ? null : Boolean.valueOf(autorenews));
+ return Optional.ofNullable(autorenews);
}
}
diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationTokenExtension.java b/core/src/main/java/google/registry/model/domain/token/AllocationTokenExtension.java
index fdeee6285..327d1d7ea 100644
--- a/core/src/main/java/google/registry/model/domain/token/AllocationTokenExtension.java
+++ b/core/src/main/java/google/registry/model/domain/token/AllocationTokenExtension.java
@@ -35,6 +35,12 @@ public class AllocationTokenExtension extends ImmutableObject implements Command
@XmlJavaTypeAdapter(TrimWhitespaceAdapter.class)
String allocationToken;
+ public static AllocationTokenExtension create(String allocationToken) {
+ AllocationTokenExtension instance = new AllocationTokenExtension();
+ instance.allocationToken = allocationToken;
+ return instance;
+ }
+
public String getAllocationToken() {
return allocationToken;
}
diff --git a/core/src/main/java/google/registry/model/eppinput/EppExtensions.java b/core/src/main/java/google/registry/model/eppinput/EppExtensions.java
new file mode 100644
index 000000000..43891fbfe
--- /dev/null
+++ b/core/src/main/java/google/registry/model/eppinput/EppExtensions.java
@@ -0,0 +1,181 @@
+// Copyright 2026 The Nomulus Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package google.registry.model.eppinput;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+import static google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName.CREATE;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import google.registry.model.domain.Period;
+import google.registry.model.domain.fee.Fee;
+import google.registry.model.domain.fee.FeeExtensionCommandDescriptor;
+import google.registry.model.domain.fee06.FeeCheckCommandExtensionItemV06;
+import google.registry.model.domain.fee06.FeeCheckCommandExtensionV06;
+import google.registry.model.domain.fee06.FeeCreateCommandExtensionV06;
+import google.registry.model.domain.fee12.FeeCreateCommandExtensionV12;
+import google.registry.model.domain.launch.LaunchCheckExtension;
+import google.registry.model.domain.launch.LaunchCheckExtension.CheckType;
+import google.registry.model.domain.launch.LaunchPhase;
+import google.registry.model.domain.metadata.MetadataExtension;
+import google.registry.model.domain.secdns.DomainDsData;
+import google.registry.model.domain.secdns.SecDnsCreateExtension;
+import google.registry.model.domain.secdns.SecDnsUpdateExtension;
+import google.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
+import google.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
+import google.registry.model.domain.superuser.DomainDeleteSuperuserExtension;
+import google.registry.model.domain.superuser.DomainUpdateSuperuserExtension;
+import google.registry.model.domain.token.AllocationTokenExtension;
+import java.math.BigDecimal;
+import javax.annotation.Nullable;
+import org.joda.money.CurrencyUnit;
+import org.joda.money.Money;
+
+/** Static helpers for creating common EPP extensions. */
+public class EppExtensions {
+
+ /**
+ * Returns a metadata extension with the specified reason and flags.
+ *
+ * @param reason the reason for the change, recorded in history entries
+ * @param requestedByRegistrar whether the change was requested by a registrar
+ * @param isAnchorTenant whether the domain is an anchor tenant
+ */
+ @Nullable
+ public static MetadataExtension metadata(
+ @Nullable String reason,
+ @Nullable Boolean requestedByRegistrar,
+ @Nullable Boolean isAnchorTenant) {
+ if (isNullOrEmpty(reason) && requestedByRegistrar == null && isAnchorTenant == null) {
+ return null;
+ }
+ return new MetadataExtension.Builder()
+ .setReason(reason)
+ .setRequestedByRegistrar(requestedByRegistrar)
+ .setAnchorTenant(isAnchorTenant)
+ .build();
+ }
+
+ /** Returns a metadata extension for standard tool commands. */
+ @Nullable
+ public static MetadataExtension toolMetadata(
+ @Nullable String reason, @Nullable Boolean requestedByRegistrar) {
+ return metadata(reason, requestedByRegistrar, null);
+ }
+
+ /** Returns an allocation token extension for the specified token string. */
+ @Nullable
+ public static AllocationTokenExtension allocationToken(@Nullable String token) {
+ return isNullOrEmpty(token) ? null : AllocationTokenExtension.create(token);
+ }
+
+ /** Returns a domain update superuser extension with the specified autorenew flag. */
+ @Nullable
+ public static DomainUpdateSuperuserExtension updateSuperuser(@Nullable Boolean autorenews) {
+ return autorenews == null ? null : DomainUpdateSuperuserExtension.create(autorenews);
+ }
+
+ /** Returns a domain delete superuser extension for immediate deletion if requested. */
+ @Nullable
+ public static DomainDeleteSuperuserExtension deleteSuperuser(boolean immediately) {
+ return immediately ? DomainDeleteSuperuserExtension.create(0, 0) : null;
+ }
+
+ /** Returns a fee create extension (V12) for a single fee. */
+ @Nullable
+ public static FeeCreateCommandExtensionV12 feeCreate(@Nullable Money cost) {
+ return cost == null ? null : feeCreate(cost.getCurrencyUnit(), cost.getAmount());
+ }
+
+ /** Returns a fee create extension (V12) for a single fee with a simple currency and cost. */
+ @Nullable
+ public static FeeCreateCommandExtensionV12 feeCreate(
+ @Nullable CurrencyUnit currency, @Nullable BigDecimal cost) {
+ if (currency == null || cost == null) {
+ return null;
+ }
+ return new FeeCreateCommandExtensionV12.Builder()
+ .setCurrency(currency)
+ .setFees(ImmutableList.of(new Fee.Builder().setCost(cost).build()))
+ .build();
+ }
+
+ /** Returns a fee create extension (V06) for a single fee. */
+ @Nullable
+ public static FeeCreateCommandExtensionV06 feeCreateV06(@Nullable Money cost) {
+ if (cost == null) {
+ return null;
+ }
+ return new FeeCreateCommandExtensionV06.Builder()
+ .setCurrency(cost.getCurrencyUnit())
+ .setFees(ImmutableList.of(new Fee.Builder().setCost(cost.getAmount()).build()))
+ .build();
+ }
+
+ /** Returns a secDNS create extension with the specified DS records. */
+ @Nullable
+ public static SecDnsCreateExtension secDnsCreate(ImmutableSet dsData) {
+ if (dsData.isEmpty()) {
+ return null;
+ }
+ return new SecDnsCreateExtension.Builder().setDsData(dsData).build();
+ }
+
+ /** Returns a secDNS update extension to replace or modify DS records. */
+ @Nullable
+ public static SecDnsUpdateExtension secDnsUpdate(
+ ImmutableSet add, ImmutableSet remove, boolean removeAll) {
+ if (add.isEmpty() && remove.isEmpty() && !removeAll) {
+ return null;
+ }
+ SecDnsUpdateExtension.Builder builder = new SecDnsUpdateExtension.Builder();
+ if (removeAll) {
+ builder.setRemove(new Remove.Builder().setAll(true).build());
+ } else if (!remove.isEmpty()) {
+ builder.setRemove(new Remove.Builder().setDsData(remove).build());
+ }
+ if (!add.isEmpty()) {
+ builder.setAdd(new Add.Builder().setDsData(add).build());
+ }
+ return builder.build();
+ }
+
+ /** Returns a fee check extension for domain creations (V06). */
+ public static FeeCheckCommandExtensionV06 feeCheckCreateV06(ImmutableList domainNames) {
+ return feeCheckCreateV06(domainNames, 1);
+ }
+
+ /** Returns a fee check extension for domain creations (V06) with a specific period. */
+ public static FeeCheckCommandExtensionV06 feeCheckCreateV06(
+ ImmutableList domainNames, int years) {
+ FeeCheckCommandExtensionV06 feeCheck = new FeeCheckCommandExtensionV06();
+ ImmutableList.Builder items = new ImmutableList.Builder<>();
+ for (String domainName : domainNames) {
+ items.add(
+ FeeCheckCommandExtensionItemV06.create(
+ domainName,
+ null,
+ FeeExtensionCommandDescriptor.create(CREATE, null, null),
+ Period.create(years, Period.Unit.YEARS)));
+ }
+ feeCheck.setItems(items.build());
+ return feeCheck;
+ }
+
+ /** Returns a launch check extension for claims. */
+ public static LaunchCheckExtension launchCheckClaims() {
+ return LaunchCheckExtension.create(CheckType.CLAIMS, LaunchPhase.CLAIMS);
+ }
+}
diff --git a/core/src/main/java/google/registry/model/eppinput/EppInput.java b/core/src/main/java/google/registry/model/eppinput/EppInput.java
index 90425f95c..a16b6b74c 100644
--- a/core/src/main/java/google/registry/model/eppinput/EppInput.java
+++ b/core/src/main/java/google/registry/model/eppinput/EppInput.java
@@ -14,12 +14,14 @@
package google.registry.model.eppinput;
+import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import google.registry.model.Buildable;
import google.registry.model.ImmutableObject;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.bulktoken.BulkTokenExtension;
@@ -60,6 +62,8 @@ import google.registry.model.domain.token.AllocationTokenExtension;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
import google.registry.model.host.HostCommand;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementRef;
@@ -69,21 +73,26 @@ import jakarta.xml.bind.annotation.XmlElements;
import jakarta.xml.bind.annotation.XmlEnumValue;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSchema;
+import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;
/** This class represents the root EPP XML element for input. */
@XmlRootElement(name = "epp")
+@XmlAccessorType(XmlAccessType.FIELD)
public class EppInput extends ImmutableObject {
@XmlElements({
- @XmlElement(name = "command", type = CommandWrapper.class),
- @XmlElement(name = "hello", type = Hello.class) })
+ @XmlElement(name = "command", type = CommandWrapper.class),
+ @XmlElement(name = "hello", type = Hello.class)
+ })
CommandWrapper commandWrapper;
public CommandWrapper getCommandWrapper() {
@@ -107,11 +116,11 @@ public class EppInput extends ImmutableObject {
public Optional getResourceType() {
ResourceCommand resourceCommand = getResourceCommand();
if (resourceCommand != null) {
- XmlSchema xmlSchemaAnnotation =
- resourceCommand.getClass().getPackage().getAnnotation(XmlSchema.class);
- if (xmlSchemaAnnotation != null && xmlSchemaAnnotation.xmlns().length > 0) {
- return Optional.of(xmlSchemaAnnotation.xmlns()[0].prefix());
- }
+ XmlSchema xmlSchemaAnnotation =
+ resourceCommand.getClass().getPackage().getAnnotation(XmlSchema.class);
+ if (xmlSchemaAnnotation != null && xmlSchemaAnnotation.xmlns().length > 0) {
+ return Optional.of(xmlSchemaAnnotation.xmlns()[0].prefix());
+ }
}
return Optional.empty();
}
@@ -123,6 +132,9 @@ public class EppInput extends ImmutableObject {
@Nullable
private ResourceCommand getResourceCommand() {
+ if (commandWrapper == null) {
+ return null;
+ }
InnerCommand innerCommand = commandWrapper.getCommand();
return innerCommand instanceof ResourceCommandWrapper resourceCommandWrapper
? resourceCommandWrapper.getResourceCommand()
@@ -136,7 +148,7 @@ public class EppInput extends ImmutableObject {
public Optional getSingleTargetId() {
ResourceCommand resourceCommand = getResourceCommand();
return resourceCommand instanceof SingleResourceCommand singleResourceCommand
- ? Optional.of(singleResourceCommand.getTargetId())
+ ? Optional.ofNullable(singleResourceCommand.getTargetId())
: Optional.empty();
}
@@ -147,7 +159,8 @@ public class EppInput extends ImmutableObject {
public ImmutableList getTargetIds() {
ResourceCommand resourceCommand = getResourceCommand();
if (resourceCommand instanceof SingleResourceCommand singleResourceCommand) {
- return ImmutableList.of(singleResourceCommand.getTargetId());
+ String targetId = singleResourceCommand.getTargetId();
+ return targetId == null ? ImmutableList.of() : ImmutableList.of(targetId);
} else if (resourceCommand instanceof ResourceCheck resourceCheck) {
return resourceCheck.getTargetIds();
} else {
@@ -157,17 +170,53 @@ public class EppInput extends ImmutableObject {
/** Get the extension based on type, or null. If there are multiple, it chooses the first. */
public Optional getSingleExtension(Class clazz) {
- return getCommandWrapper().getExtensions().stream()
+ if (commandWrapper == null) {
+ return Optional.empty();
+ }
+ return commandWrapper.getExtensions().stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.findFirst();
}
+ /**
+ * Static factory method to create an {@link EppInput} from an {@link InnerCommand} and
+ * extensions.
+ */
+ public static EppInput create(InnerCommand command, CommandExtension... extensions) {
+ EppInput instance = new EppInput();
+ instance.commandWrapper = new CommandWrapper();
+ instance.commandWrapper.command = command;
+ ImmutableList validExtensions =
+ Arrays.stream(extensions).filter(Objects::nonNull).collect(ImmutableList.toImmutableList());
+ if (!validExtensions.isEmpty()) {
+ instance.commandWrapper.extension = validExtensions;
+ }
+ return instance;
+ }
+
+ public EppInput withClTrid(String clTrid) {
+ this.commandWrapper.clTrid = clTrid;
+ return this;
+ }
+
+ /** Builder for {@link EppInput}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setCommandWrapper(CommandWrapper commandWrapper) {
+ getInstance().commandWrapper = commandWrapper;
+ return this;
+ }
+ }
+
/** A tag that goes inside an EPP {@literal }. */
- public static class InnerCommand extends ImmutableObject {}
+ @XmlTransient
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public abstract static class InnerCommand extends ImmutableObject {}
/** A command that has an extension inside of it. */
- public static class ResourceCommandWrapper extends InnerCommand {
+ @XmlTransient
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public abstract static class ResourceCommandWrapper extends InnerCommand {
@XmlElementRefs({
@XmlElementRef(type = DomainCommand.Check.class),
@XmlElementRef(type = DomainCommand.Create.class),
@@ -189,21 +238,65 @@ public class EppInput extends ImmutableObject {
}
/** Epp envelope wrapper for check on some objects. */
- public static class Check extends ResourceCommandWrapper {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Check extends ResourceCommandWrapper {
+ public static Check create(ResourceCommand resourceCommand) {
+ Check instance = new Check();
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
+ }
/** Epp envelope wrapper for create of some object. */
- public static class Create extends ResourceCommandWrapper {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Create extends ResourceCommandWrapper {
+ public static Create create(ResourceCommand resourceCommand) {
+ Create instance = new Create();
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
+
+ /** Builder for {@link Create}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setResourceCommand(ResourceCommand resourceCommand) {
+ getInstance().resourceCommand = resourceCommand;
+ return this;
+ }
+ }
+ }
/** Epp envelope wrapper for delete of some object. */
- public static class Delete extends ResourceCommandWrapper {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Delete extends ResourceCommandWrapper {
+ public static Delete create(ResourceCommand resourceCommand) {
+ Delete instance = new Delete();
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
+ }
/** Epp envelope wrapper for info on some object. */
- public static class Info extends ResourceCommandWrapper {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Info extends ResourceCommandWrapper {
+ public static Info create(ResourceCommand resourceCommand) {
+ Info instance = new Info();
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
+ }
/** Epp envelope wrapper for renewing some object. */
- public static class Renew extends ResourceCommandWrapper {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Renew extends ResourceCommandWrapper {
+ public static Renew create(ResourceCommand resourceCommand) {
+ Renew instance = new Renew();
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
+ }
/** Epp envelope wrapper for transferring some object. */
+ @XmlAccessorType(XmlAccessType.FIELD)
public static class Transfer extends ResourceCommandWrapper {
/** Enum of the possible values for the "op" attribute in transfer flows. */
@@ -230,12 +323,35 @@ public class EppInput extends ImmutableObject {
public TransferOp getTransferOp() {
return transferOp;
}
+
+ public static Transfer create(TransferOp transferOp, ResourceCommand resourceCommand) {
+ Transfer instance = new Transfer();
+ instance.transferOp = transferOp;
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
}
/** Epp envelope wrapper for update of some object. */
- public static class Update extends ResourceCommandWrapper {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Update extends ResourceCommandWrapper {
+ public static Update create(ResourceCommand resourceCommand) {
+ Update instance = new Update();
+ instance.resourceCommand = resourceCommand;
+ return instance;
+ }
+
+ /** Builder for {@link Update}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setResourceCommand(ResourceCommand resourceCommand) {
+ getInstance().resourceCommand = resourceCommand;
+ return this;
+ }
+ }
+ }
/** Poll command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
public static class Poll extends InnerCommand {
/** Enum of the possible values for the "op" attribute in poll commands. */
@@ -253,19 +369,28 @@ public class EppInput extends ImmutableObject {
@XmlAttribute
PollOp op;
- @XmlAttribute
- String msgID;
+ @XmlAttribute(name = "msgID")
+ String msgId;
public PollOp getPollOp() {
return op;
}
public String getMessageId() {
- return msgID;
+ return msgId;
+ }
+
+ public static Poll create(PollOp op, @Nullable String msgId) {
+ Poll instance = new Poll();
+ instance.op = op;
+ instance.msgId = msgId;
+ return instance;
}
}
/** Login command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"clientId", "password", "newPassword", "options", "services"})
public static class Login extends InnerCommand {
@XmlElement(name = "clID")
String clientId;
@@ -303,10 +428,12 @@ public class EppInput extends ImmutableObject {
}
/** Logout command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
public static class Logout extends InnerCommand {}
/** The "command" element that holds an actual command inside of it. */
- @XmlType(propOrder = {"command", "extension", "clTRID"})
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"command", "extension", "clTrid"})
public static class CommandWrapper extends ImmutableObject {
@XmlElements({
@XmlElement(name = "check", type = Check.class),
@@ -376,7 +503,9 @@ public class EppInput extends ImmutableObject {
@XmlElementWrapper
List extension;
- @Nullable String clTRID;
+ @XmlElement(name = "clTRID")
+ @Nullable
+ String clTrid;
/**
* Returns the client transaction ID.
@@ -384,7 +513,7 @@ public class EppInput extends ImmutableObject {
* This is optional (i.e. it may not be specified) per RFC 5730.
*/
public Optional getClTrid() {
- return Optional.ofNullable(clTRID);
+ return Optional.ofNullable(clTrid);
}
public InnerCommand getCommand() {
@@ -394,12 +523,34 @@ public class EppInput extends ImmutableObject {
public ImmutableList getExtensions() {
return nullToEmptyImmutableCopy(extension);
}
+
+ /** Builder for {@link CommandWrapper}. */
+ public static class Builder extends Buildable.Builder {
+
+ public Builder setCommand(InnerCommand command) {
+ getInstance().command = command;
+ return this;
+ }
+
+ public Builder setExtensions(ImmutableList extension) {
+ getInstance().extension = isNullOrEmpty(extension) ? null : extension;
+ return this;
+ }
+
+ public Builder setClTrid(String clTrid) {
+ getInstance().clTrid = clTrid;
+ return this;
+ }
+ }
}
/** Empty type to represent the empty "hello" command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
public static class Hello extends CommandWrapper {}
/** An options object inside of {@link Login}. */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"version", "language"})
public static class Options extends ImmutableObject {
@XmlJavaTypeAdapter(VersionAdapter.class)
String version;
@@ -413,6 +564,8 @@ public class EppInput extends ImmutableObject {
}
/** A services object inside of {@link Login}. */
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"objectServices", "serviceExtensions"})
public static class Services extends ImmutableObject {
@XmlElement(name = "objURI")
Set objectServices;
@@ -431,15 +584,15 @@ public class EppInput extends ImmutableObject {
}
/**
- * RFC 5730 says we should check the version and return special error code 2100 if it isn't
- * what we support, but it also specifies a schema that only allows 1.0 in the version field, so
- * any other version doesn't validate. As a result, if we didn't do this here it would throw a
- * {@code SyntaxErrorException} when it failed to validate.
+ * RFC 5730 says we should check the version and return special error code 2100 if it isn't what
+ * we support, but it also specifies a schema that only allows 1.0 in the version field, so any
+ * other version doesn't validate. As a result, if we didn't do this here it would throw a {@code
+ * SyntaxErrorException} when it failed to validate.
*
- * @see
- * RFC 5730 - EPP - Command error responses
+ * @see RFC 5730 - EPP - Command error
+ * responses
*/
- public static class VersionAdapter extends XmlAdapter {
+ public static class VersionAdapter extends XmlAdapter {
@Override
public String unmarshal(String version) throws Exception {
if (!"1.0".equals(version)) {
@@ -449,8 +602,8 @@ public class EppInput extends ImmutableObject {
}
@Override
- public String marshal(String ignored) {
- throw new UnsupportedOperationException();
+ public String marshal(String version) {
+ return version;
}
}
diff --git a/core/src/main/java/google/registry/model/eppinput/ResourceCommand.java b/core/src/main/java/google/registry/model/eppinput/ResourceCommand.java
index de81fce7d..369204ee4 100644
--- a/core/src/main/java/google/registry/model/eppinput/ResourceCommand.java
+++ b/core/src/main/java/google/registry/model/eppinput/ResourceCommand.java
@@ -15,7 +15,6 @@
package google.registry.model.eppinput;
import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
-import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -24,41 +23,50 @@ import google.registry.model.EppResource;
import google.registry.model.ImmutableObject;
import google.registry.model.eppcommon.AuthInfo;
import google.registry.model.eppcommon.StatusValue;
-import google.registry.model.eppinput.ResourceCommand.ResourceUpdate.AddRemove;
import google.registry.util.TypeUtils.TypeInstantiator;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElements;
import jakarta.xml.bind.annotation.XmlTransient;
import java.util.List;
-import java.util.Set;
/** Commands for EPP resources. */
public interface ResourceCommand {
- /**
- * A command for a single {@link EppResource}.
- *
- * In general commands should extend {@link AbstractSingleResourceCommand} instead of
- * implementing this directly, but "Create" commands can't do that since they need to inherit from
- * a base class that gives them all of the resource's fields. The domain "Info" command also can't
- * do that since it's "name" field is overloaded with a "hosts" attribute.
- */
+ /** Interface for EPP commands that operate on a single resource. */
interface SingleResourceCommand extends ResourceCommand {
+ @Override
String getTargetId();
+ @Override
AuthInfo getAuthInfo();
}
+ /** Returns the target ID for single-resource commands, or null otherwise. */
+ default String getTargetId() {
+ return null;
+ }
+
+ /** Returns the auth info for single-resource commands, or null otherwise. */
+ default AuthInfo getAuthInfo() {
+ return null;
+ }
+
/** Abstract implementation of {@link ResourceCommand}. */
@XmlTransient
- abstract class AbstractSingleResourceCommand extends ImmutableObject
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public abstract class AbstractSingleResourceCommand extends ImmutableObject
implements SingleResourceCommand {
- @XmlElements({
- @XmlElement(name = "id"),
- @XmlElement(name = "name") })
- String targetId;
+
+ @XmlTransient public String targetId;
+
+ public void setTargetId(String targetId) {
+ this.targetId = targetId;
+ }
@Override
+ @XmlTransient
public String getTargetId() {
return targetId;
}
@@ -71,11 +79,14 @@ public interface ResourceCommand {
/** A check command for an {@link EppResource}. */
@XmlTransient
- class ResourceCheck extends ImmutableObject implements ResourceCommand {
- @XmlElements({
- @XmlElement(name = "id"),
- @XmlElement(name = "name") })
- List targetUniqueIds;
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public class ResourceCheck extends ImmutableObject implements ResourceCommand {
+ @XmlElements({@XmlElement(name = "id"), @XmlElement(name = "name")})
+ public List targetUniqueIds;
+
+ public void setTargetIds(ImmutableList targetUniqueIds) {
+ this.targetUniqueIds = targetUniqueIds;
+ }
public ImmutableList getTargetIds() {
return nullSafeImmutableCopy(targetUniqueIds);
@@ -83,7 +94,7 @@ public interface ResourceCommand {
}
/** A create command, or the inner change (as opposed to add or remove) part of an update. */
- interface ResourceCreateOrChange> {}
+ public interface ResourceCreateOrChange> {}
/**
* An update command for an {@link EppResource}.
@@ -92,21 +103,19 @@ public interface ResourceCommand {
* @param the change type
*/
@XmlTransient
- abstract class ResourceUpdate<
- A extends AddRemove,
+ public abstract class ResourceUpdate<
+ A extends ResourceUpdate.AddRemove,
B extends EppResource.Builder, ?>,
C extends ResourceCreateOrChange>
extends AbstractSingleResourceCommand {
/** Part of an update command that specifies set values to add or remove. */
@XmlTransient
+ @XmlAccessorType(XmlAccessType.FIELD)
public abstract static class AddRemove extends ImmutableObject {
- @XmlElement(name = "status")
- Set statusValues;
+ public abstract void setStatusValues(ImmutableSet statusValues);
- public ImmutableSet getStatusValues() {
- return nullToEmptyImmutableCopy(statusValues);
- }
+ public abstract ImmutableSet getStatusValues();
}
protected abstract C getNullableInnerChange();
diff --git a/core/src/main/java/google/registry/model/host/HostCommand.java b/core/src/main/java/google/registry/model/host/HostCommand.java
index b1baa0f68..2fadaa5e0 100644
--- a/core/src/main/java/google/registry/model/host/HostCommand.java
+++ b/core/src/main/java/google/registry/model/host/HostCommand.java
@@ -14,14 +14,18 @@
package google.registry.model.host;
-import static google.registry.util.CollectionUtils.nullSafeImmutableCopy;
+import static google.registry.util.CollectionUtils.isNullOrEmpty;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
import com.google.common.collect.ImmutableSet;
+import google.registry.model.Buildable;
+import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.ResourceCommand.AbstractSingleResourceCommand;
import google.registry.model.eppinput.ResourceCommand.ResourceCheck;
import google.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
import google.registry.model.eppinput.ResourceCommand.ResourceUpdate;
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlTransient;
@@ -32,39 +36,95 @@ import java.util.Set;
/** A collection of {@link Host} commands. */
public class HostCommand {
- /** The fields on "chgType" from RFC5732. */
+ /** The fields on "chgType" from RFC5732. */
@XmlTransient
- abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
implements ResourceCreateOrChange {
+
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+
public String getHostName() {
- return getTargetId();
+ return name;
}
}
/**
* A create command for a {@link Host}, mapping "createType" from RFC5732.
+ * href="https://tools.ietf.org/html/rfc5732">RFC5732.
*/
- @XmlType(propOrder = {"targetId", "inetAddresses"})
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"name", "inetAddresses"})
@XmlRootElement
- public static class Create extends HostCreateOrChange
- implements ResourceCreateOrChange {
+ public static class Create extends HostCreateOrChange {
/** IP Addresses for this host. Can be null if this is an external host. */
@XmlElement(name = "addr")
Set inetAddresses;
public ImmutableSet getInetAddresses() {
- return nullSafeImmutableCopy(inetAddresses);
+ return nullToEmptyImmutableCopy(inetAddresses);
+ }
+
+ /** Builder for {@link Create}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setTargetId(String targetId) {
+ getInstance().setTargetId(targetId);
+ return this;
+ }
+
+ public Builder setInetAddresses(ImmutableSet inetAddresses) {
+ getInstance().inetAddresses = inetAddresses;
+ return this;
+ }
}
}
/** A delete command for a {@link Host}. */
@XmlRootElement
- public static class Delete extends AbstractSingleResourceCommand {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Delete extends AbstractSingleResourceCommand {
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+ }
/** An info request for a {@link Host}. */
@XmlRootElement
- public static class Info extends AbstractSingleResourceCommand {}
+ @XmlAccessorType(XmlAccessType.FIELD)
+ public static class Info extends AbstractSingleResourceCommand {
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
+ }
/** A check request for {@link Host}. */
@XmlRootElement
@@ -72,17 +132,32 @@ public class HostCommand {
/** An update to a {@link Host}. */
@XmlRootElement
- @XmlType(propOrder = {"targetId", "innerAdd", "innerRemove", "innerChange"})
- public static class Update extends ResourceUpdate {
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"name", "innerAdd", "innerRemove", "innerChange"})
+ public static class Update
+ extends ResourceUpdate {
+
+ @XmlElement(name = "name")
+ String name;
+
+ @Override
+ public String getTargetId() {
+ return name;
+ }
+
+ @Override
+ public void setTargetId(String targetId) {
+ this.name = targetId;
+ }
@XmlElement(name = "chg")
protected Change innerChange;
@XmlElement(name = "add")
- protected AddRemove innerAdd;
+ protected HostAddRemove innerAdd;
@XmlElement(name = "rem")
- protected AddRemove innerRemove;
+ protected HostAddRemove innerRemove;
@Override
protected Change getNullableInnerChange() {
@@ -90,28 +165,55 @@ public class HostCommand {
}
@Override
- protected AddRemove getNullableInnerAdd() {
+ protected HostAddRemove getNullableInnerAdd() {
return innerAdd;
}
@Override
- protected AddRemove getNullableInnerRemove() {
+ protected HostAddRemove getNullableInnerRemove() {
return innerRemove;
}
/** The add/remove type on a host update command. */
- @XmlType(propOrder = { "inetAddresses", "statusValues" })
- public static class AddRemove extends ResourceUpdate.AddRemove {
+ @XmlAccessorType(XmlAccessType.FIELD)
+ @XmlType(propOrder = {"inetAddresses", "statusValues"})
+ public static class HostAddRemove extends ResourceUpdate.AddRemove {
/** IP Addresses for this host. Can be null if this is an external host. */
@XmlElement(name = "addr")
Set inetAddresses;
+ @XmlElement(name = "status")
+ Set statusValues;
+
+ @Override
+ public void setStatusValues(ImmutableSet statusValues) {
+ this.statusValues = statusValues;
+ }
+
+ @Override
+ public ImmutableSet getStatusValues() {
+ return nullToEmptyImmutableCopy(statusValues);
+ }
+
public ImmutableSet getInetAddresses() {
return nullToEmptyImmutableCopy(inetAddresses);
}
+
+ /** Builder for {@link HostAddRemove}. */
+ public static class Builder extends Buildable.Builder {
+ public Builder setInetAddresses(ImmutableSet inetAddresses) {
+ getInstance().inetAddresses = isNullOrEmpty(inetAddresses) ? null : inetAddresses;
+ return this;
+ }
+
+ public Builder setStatusValues(ImmutableSet statusValues) {
+ getInstance().statusValues = isNullOrEmpty(statusValues) ? null : statusValues;
+ return this;
+ }
+ }
}
- /** The inner change type on a host update command. */
+ @XmlAccessorType(XmlAccessType.FIELD)
public static class Change extends HostCreateOrChange {}
}
}
diff --git a/core/src/main/java/google/registry/reporting/spec11/PublishSpec11ReportAction.java b/core/src/main/java/google/registry/reporting/spec11/PublishSpec11ReportAction.java
index fe38f03f9..0cf62abaa 100644
--- a/core/src/main/java/google/registry/reporting/spec11/PublishSpec11ReportAction.java
+++ b/core/src/main/java/google/registry/reporting/spec11/PublishSpec11ReportAction.java
@@ -24,7 +24,6 @@ import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import com.google.api.services.dataflow.Dataflow;
import com.google.api.services.dataflow.model.Job;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
@@ -32,11 +31,10 @@ import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
-import com.google.template.soy.parseinfo.SoyTemplateInfo;
import google.registry.beam.spec11.ThreatMatch;
import google.registry.config.RegistryConfig.Config;
import google.registry.reporting.ReportingModule;
-import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
+import google.registry.reporting.spec11.Spec11EmailUtils.Spec11EmailTemplate;
import google.registry.request.Action;
import google.registry.request.Parameter;
import google.registry.request.Response;
@@ -45,14 +43,13 @@ import jakarta.inject.Inject;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Optional;
-import java.util.Set;
import org.json.JSONException;
/**
* Retries until a {@code Dataflow} job with a given {@code jobId} completes, continuing the Spec11
* pipeline accordingly.
*
- * This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, SoyTemplateInfo, String,
+ *
This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, Spec11EmailTemplate, String,
* ImmutableSet)} on success or {@link Spec11EmailUtils#sendAlertEmail(String, String)} on failure.
*/
@Action(
@@ -134,7 +131,7 @@ public class PublishSpec11ReportAction implements Runnable {
String.format("Spec11 %s job %s ended in status failure.", date, jobId));
}
default -> {
- logger.atInfo().log("Job in non-terminal state %s, retrying:", state);
+ logger.atInfo().log("Job in non-terminal state %s, retrying.", state);
response.setStatus(SC_SERVICE_UNAVAILABLE);
}
}
@@ -153,8 +150,7 @@ public class PublishSpec11ReportAction implements Runnable {
ImmutableSet monthlyMatchesSet =
spec11RegistrarThreatMatchesParser.getRegistrarThreatMatches(date);
String subject = String.format("%s Monthly Threat Detector [%s]", registryName, date);
- emailUtils.emailSpec11Reports(
- date, Spec11EmailSoyInfo.MONTHLY_SPEC_11_EMAIL, subject, monthlyMatchesSet);
+ emailUtils.emailSpec11Reports(date, Spec11EmailTemplate.MONTHLY, subject, monthlyMatchesSet);
}
private void processDailyDiff(LocalDate previousDate) throws IOException, JSONException {
@@ -165,7 +161,7 @@ public class PublishSpec11ReportAction implements Runnable {
String dailySubject = String.format("%s Daily Threat Detector [%s]", registryName, date);
emailUtils.emailSpec11Reports(
date,
- Spec11EmailSoyInfo.DAILY_SPEC_11_EMAIL,
+ Spec11EmailTemplate.DAILY,
dailySubject,
getNewMatches(previousMatches, currentMatches));
}
@@ -173,19 +169,20 @@ public class PublishSpec11ReportAction implements Runnable {
private ImmutableSet getNewMatches(
ImmutableSet previousMatchesSet,
ImmutableSet currentMatchesSet) {
- ImmutableMap> previousMatchesByEmail =
+ ImmutableMap> previousMatchesByRegistrarId =
groupByKeyAndFlatMap(previousMatchesSet);
- ImmutableMap> currentMatchesByEmail =
+ ImmutableMap> currentMatchesByRegistrarId =
groupByKeyAndFlatMap(currentMatchesSet);
ImmutableSet.Builder resultsBuilder = ImmutableSet.builder();
- for (String email : currentMatchesByEmail.keySet()) {
+ for (String registrarId : currentMatchesByRegistrarId.keySet()) {
// Only include matches in the result if they're non-empty
- Set difference =
- Sets.difference(
- currentMatchesByEmail.get(email),
- previousMatchesByEmail.getOrDefault(email, ImmutableSet.of()));
+ ImmutableSet difference =
+ ImmutableSet.copyOf(
+ Sets.difference(
+ currentMatchesByRegistrarId.get(registrarId),
+ previousMatchesByRegistrarId.getOrDefault(registrarId, ImmutableSet.of())));
if (!difference.isEmpty()) {
- resultsBuilder.add(RegistrarThreatMatches.create(email, ImmutableList.copyOf(difference)));
+ resultsBuilder.add(RegistrarThreatMatches.create(registrarId, difference.asList()));
}
}
return resultsBuilder.build();
@@ -193,13 +190,13 @@ public class PublishSpec11ReportAction implements Runnable {
private ImmutableMap> groupByKeyAndFlatMap(
ImmutableSet registrarThreatMatches) {
- // Group by email address then flat-map all of the ThreatMatch objects together
+ // Group by registrarId then flat-map all of the ThreatMatch objects together
return ImmutableMap.copyOf(
Maps.transformValues(
- Multimaps.index(registrarThreatMatches, RegistrarThreatMatches::clientId).asMap(),
+ Multimaps.index(registrarThreatMatches, RegistrarThreatMatches::registrarId).asMap(),
registrarThreatMatchesCollection ->
registrarThreatMatchesCollection.stream()
- .flatMap(matches -> matches.threatMatches().stream())
+ .flatMap(rtm -> rtm.threatMatches().stream())
.collect(toImmutableSet())));
}
diff --git a/core/src/main/java/google/registry/reporting/spec11/RegistrarThreatMatches.java b/core/src/main/java/google/registry/reporting/spec11/RegistrarThreatMatches.java
index 6ad156339..257cb6aff 100644
--- a/core/src/main/java/google/registry/reporting/spec11/RegistrarThreatMatches.java
+++ b/core/src/main/java/google/registry/reporting/spec11/RegistrarThreatMatches.java
@@ -16,12 +16,17 @@ package google.registry.reporting.spec11;
import com.google.common.collect.ImmutableList;
import google.registry.beam.spec11.ThreatMatch;
-import java.util.List;
-/** Value record representing the registrar and list-of-threat-matches pair stored in GCS. */
-public record RegistrarThreatMatches(String clientId, ImmutableList threatMatches) {
-
- static RegistrarThreatMatches create(String clientId, List threatMatches) {
- return new RegistrarThreatMatches(clientId, ImmutableList.copyOf(threatMatches));
+/**
+ * A value record representing a registrar and its associated list of threat matches.
+ *
+ * @param registrarId the ID of the registrar
+ * @param threatMatches the list of {@link ThreatMatch} objects associated with the registrar
+ */
+public record RegistrarThreatMatches(String registrarId, ImmutableList threatMatches) {
+ /** Creates a new {@link RegistrarThreatMatches} instance. */
+ static RegistrarThreatMatches create(
+ String registrarId, ImmutableList threatMatches) {
+ return new RegistrarThreatMatches(registrarId, threatMatches);
}
}
diff --git a/core/src/main/java/google/registry/reporting/spec11/Spec11EmailUtils.java b/core/src/main/java/google/registry/reporting/spec11/Spec11EmailUtils.java
index 2d105a730..61edcdd5a 100644
--- a/core/src/main/java/google/registry/reporting/spec11/Spec11EmailUtils.java
+++ b/core/src/main/java/google/registry/reporting/spec11/Spec11EmailUtils.java
@@ -16,7 +16,6 @@ package google.registry.reporting.spec11;
import static com.google.common.base.Throwables.getRootCause;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.common.io.Resources.getResource;
import static google.registry.persistence.transaction.QueryComposer.Comparator;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
@@ -25,41 +24,46 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import com.google.common.net.MediaType;
-import com.google.template.soy.SoyFileSet;
-import com.google.template.soy.parseinfo.SoyTemplateInfo;
-import com.google.template.soy.tofu.SoyTofu;
-import com.google.template.soy.tofu.SoyTofu.Renderer;
import google.registry.beam.spec11.ThreatMatch;
import google.registry.config.RegistryConfig.Config;
import google.registry.groups.GmailClient;
import google.registry.model.domain.Domain;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.RegistrarPoc;
-import google.registry.reporting.spec11.soy.Spec11EmailSoyInfo;
import google.registry.util.EmailMessage;
import google.registry.util.Sleeper;
+import google.registry.util.TemplateRenderer;
import jakarta.inject.Inject;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.InternetAddress;
import java.time.Duration;
import java.time.LocalDate;
-import java.util.List;
import java.util.Map;
/** Provides e-mail functionality for Spec11 tasks, such as sending Spec11 reports to registrars. */
public class Spec11EmailUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
- private static final SoyTofu SOY_SAUCE =
- SoyFileSet.builder()
- .add(
- getResource(
- Spec11EmailSoyInfo.getInstance().getClass(),
- Spec11EmailSoyInfo.getInstance().getFileName()))
- .build()
- .compileToTofu();
+
+ /** Enum of Spec11 email templates. */
+ public enum Spec11EmailTemplate {
+ DAILY("daily_spec11_email.ftl"),
+ MONTHLY("monthly_spec11_email.ftl");
+
+ private final String ftlPath;
+
+ Spec11EmailTemplate(String ftlPath) {
+ this.ftlPath = "google/registry/reporting/spec11/ftl/" + ftlPath;
+ }
+
+ public String getFtlPath() {
+ return ftlPath;
+ }
+ }
+
private final GmailClient gmailClient;
private final Sleeper sleeper;
+ private final TemplateRenderer templateRenderer;
private final Duration emailThrottleDuration;
private final InternetAddress outgoingEmailAddress;
private final ImmutableList spec11BccEmailAddresses;
@@ -71,6 +75,7 @@ public class Spec11EmailUtils {
Spec11EmailUtils(
GmailClient gmailClient,
Sleeper sleeper,
+ TemplateRenderer templateRenderer,
@Config("emailThrottleDuration") Duration emailThrottleDuration,
@Config("newAlertRecipientEmailAddress") InternetAddress alertRecipientAddress,
@Config("spec11OutgoingEmailAddress") InternetAddress spec11OutgoingEmailAddress,
@@ -79,6 +84,7 @@ public class Spec11EmailUtils {
@Config("registryName") String registryName) {
this.gmailClient = gmailClient;
this.sleeper = sleeper;
+ this.templateRenderer = templateRenderer;
this.emailThrottleDuration = emailThrottleDuration;
this.outgoingEmailAddress = spec11OutgoingEmailAddress;
this.spec11BccEmailAddresses = spec11BccEmailAddresses;
@@ -88,12 +94,18 @@ public class Spec11EmailUtils {
}
/**
- * Processes a list of registrar/list-of-threat pairings and sends a notification email to the
- * appropriate address.
+ * Processes a list of registrar/list-of-threat pairings and sends notification emails to the
+ * appropriate addresses.
+ *
+ * @param date the date the report was generated
+ * @param template the email template to use
+ * @param subject the subject line for the emails
+ * @param registrarThreatMatchesSet a set of {@link RegistrarThreatMatches} to be emailed
+ * @throws RuntimeException if emailing fails for one or more registrars
*/
void emailSpec11Reports(
LocalDate date,
- SoyTemplateInfo soyTemplateInfo,
+ Spec11EmailTemplate template,
String subject,
ImmutableSet registrarThreatMatchesSet) {
ImmutableMap.Builder failedMatchesBuilder =
@@ -108,14 +120,15 @@ public class Spec11EmailUtils {
try {
// Handle exceptions individually per registrar so that one failed email doesn't prevent
// the rest from being sent.
- emailRegistrar(date, soyTemplateInfo, subject, filteredMatches);
+ emailRegistrar(date, template, subject, filteredMatches);
numRegistrarsEmailed++;
} catch (Throwable e) {
failedMatchesBuilder.put(registrarThreatMatches, getRootCause(e));
}
}
}
- logger.atInfo().log("Emailed daily diffs to %s registrars.", numRegistrarsEmailed);
+ logger.atInfo().log("Emailed Spec11 reports to %s registrars.", numRegistrarsEmailed);
+
ImmutableMap failedMatches = failedMatchesBuilder.build();
if (!failedMatches.isEmpty()) {
ImmutableList> failedMatchesList =
@@ -130,7 +143,7 @@ public class Spec11EmailUtils {
logger.atSevere().withCause(failedMatchesList.get(i).getValue()).log(
"Additional exception thrown when sending email to registrar %s, in addition to the"
+ " re-thrown exception.",
- failedMatchesList.get(i).getKey().clientId());
+ failedMatchesList.get(i).getKey().registrarId());
}
throw new RuntimeException(
"Emailing Spec11 reports failed, first exception:", firstThrowable);
@@ -144,43 +157,49 @@ public class Spec11EmailUtils {
RegistrarThreatMatches registrarThreatMatches) {
ImmutableList filteredMatches =
tm().transact(
- () -> {
- return registrarThreatMatches.threatMatches().stream()
- .filter(
- threatMatch ->
- tm()
- .createQueryComposer(Domain.class)
- .where("domainName", Comparator.EQ, threatMatch.domainName())
- .stream()
- .anyMatch(Domain::shouldPublishToDns))
- .collect(toImmutableList());
- });
- return RegistrarThreatMatches.create(registrarThreatMatches.clientId(), filteredMatches);
+ () ->
+ registrarThreatMatches.threatMatches().stream()
+ .filter(
+ threatMatch ->
+ tm()
+ .createQueryComposer(Domain.class)
+ .where("domainName", Comparator.EQ, threatMatch.domainName())
+ .stream()
+ .anyMatch(Domain::shouldPublishToDns))
+ .collect(toImmutableList()));
+ return RegistrarThreatMatches.create(registrarThreatMatches.registrarId(), filteredMatches);
}
private void emailRegistrar(
LocalDate date,
- SoyTemplateInfo soyTemplateInfo,
+ Spec11EmailTemplate template,
String subject,
RegistrarThreatMatches registrarThreatMatches)
throws MessagingException {
gmailClient.sendEmail(
EmailMessage.newBuilder()
.setSubject(subject)
- .setBody(getEmailBody(date, soyTemplateInfo, registrarThreatMatches))
+ .setBody(getEmailBody(date, template, registrarThreatMatches))
.setContentType(MediaType.HTML_UTF_8)
- .addRecipient(getEmailAddressForRegistrar(registrarThreatMatches.clientId()))
+ .addRecipient(getEmailAddressForRegistrar(registrarThreatMatches.registrarId()))
.setBccs(spec11BccEmailAddresses)
.build());
}
+ /**
+ * Renders the email body using the specified template and registrar threat matches.
+ *
+ * @param date the date the report was generated
+ * @param template the email template to use
+ * @param registrarThreatMatches the matches for a specific registrar
+ * @return the rendered email body as an HTML string
+ */
private String getEmailBody(
- LocalDate date,
- SoyTemplateInfo soyTemplateInfo,
- RegistrarThreatMatches registrarThreatMatches) {
- Renderer renderer = SOY_SAUCE.newRenderer(soyTemplateInfo);
- // Soy templates require that data be in raw map/list form.
- List