1
0
mirror of https://github.com/google/nomulus synced 2026-06-09 16:33:02 +00:00

Compare commits

...

18 Commits

Author SHA1 Message Date
Ben McIlwain
c5abd2a7c9 Uncap most remaining dependencies (#3073)
This commit relaxes the upper bounds on several dependencies that were previously hardcapped to specific versions:
- com.google.protobuf to [3.25.5,) and [3.17.3,)
- org.apache.beam to [2.72.0,)
- io.github.ss-bhatt to [1.0.0,)
- io.protostuff to [1.8.0,)
- redis.clients:jedis to [7.4.1,)
- org.junit.jupiter and org.junit.platform to [5.6.2,) and [1.6.2,)
- org.jcommander to [2.0,)
- org.jline to [3.0,)
- jakarta.servlet to [6.0,)

Upgrading to the modern versions of jline introduced a breaking change where DefaultParser().parse(line, line.length()) strips trailing spaces when using the default ParseContext.UNSPECIFIED. This caused the autocompletion to misbehave and tests to fail. This commit fixes ShellCommandTest.java by explicitly passing ParseContext.COMPLETE when parsing test strings to perfectly mimic the real-world JLine completion context.

Additionally, SqlIntegrationTestSuite was migrated to JUnit 5's @Suite annotation, fixing a NoClassDefFoundError introduced by uncapping the JUnit Platform dependencies, and the test suite was re-integrated into the standard :build lifecycle.

The following dependencies remain explicitly capped:
1. Hibernate & Jakarta Persistence (Blocked by -Werror):
   These are held back because newer Jakarta Persistence versions deprecate executeUpdate(), setMaxResults(), and getResultStream() on Query.
   - org.hibernate.orm:hibernate-core:7.3.4.Final
   - org.hibernate.orm:hibernate-hikaricp:7.3.4.Final
   - org.hibernate.orm:hibernate-ant:7.3.4.Final
   - jakarta.persistence:jakarta.persistence-api:[3.2.0,4.0.0)

2. Netty (Blocked by abandoned v5):
   Netty 5.0.0 was an experimental release abandoned in 2015. We explicitly cap beneath 5.0.0 so Gradle doesn't resolve dead-end alphas.
   - io.netty:netty-codec-http:[4.1.59.Final, 5.0.0)!!
   - io.netty:netty-codec:[4.1.59.Final, 5.0.0)!!
   - io.netty:netty-common:[4.1.59.Final, 5.0.0)!!
   - io.netty:netty-handler:[4.1.59.Final, 5.0.0)!!
   - io.netty:netty-transport:[4.1.59.Final, 5.0.0)!!
   - io.netty:netty-buffer:[4.1.59.Final, 5.0.0)!!

3. Google API Services:
   Capped beneath their respective unstable beta/v1b4 versions:
   - com.google.apis:google-api-services-dataflow:[v1b3-rev20240430-2.0.0, v1b4)!!
   - com.google.apis:google-api-services-dns:[v1-rev20240419-2.0.0, v2beta)

The lockfiles have been fully regenerated and all test suites ran successfully against the latest available transitive versions.
2026-05-30 02:33:02 +00:00
Ben McIlwain
5286b1a0dc Optimize build and fix fragile tests (#3068)
This commit dramatically optimizes the local Gradle build time, shaving over 5 minutes off a full build execution:
- Instrumented the build to identify fragileTest taking > 3 minutes.
- Refactored TestServer.java to dynamically bind to ephemeral port 0, resolving race conditions.
- Updated UploadBsaUnavailableDomainsActionTest to use the thread-safe TestServer, allowing it to run in parallel.
- Removed outdated exclusions for HostInfoFlowTest and RegistryPipelineWorkerInitializerTest.
- Moved these tests to the highly parallelized standardTest suite.
- Removed the redundant sqlIntegrationTest execution from the standard test phase.
- Stripped heavy Docker (buildNomulusImage) and 5x frontend (buildConsoleForAll) staging dependencies from the standard build task, ensuring they are only run when explicitly deployed.
2026-05-29 02:29:20 +00:00
Ben McIlwain
cd65a4c9d8 Apply continuous self-improvement to skills (#3072)
- pr-polisher: Relaxed the package-lock.json strictness. If package.json or dependencies.gradle are modified, changes to package-lock.json now correctly trigger a WARNING rather than a fatal ERROR, streamlining intentional dependency updates.
- java-ast-refactoring: Replaced the reliance on a local google-java-format binary with the project's native ./gradlew javaIncrementalFormatApply task for post-AST format fixes.
- Updated GEMINI.md and skill instructions to explicitly authorize and mandate the agent to proactively propose systemic infrastructure fixes to the user when it encounters recurring friction, false positives, or brittle workarounds.
- Overhauled the PR polisher "When to Use" instructions in GEMINI.md and SKILL.md into a critical mandate explicitly tying the execution of the polisher to the action of making or amending a commit to prevent agent forgetfulness.
2026-05-28 22:32:18 +00:00
Ben McIlwain
beb7fcc16e Update dependencies and improve workflow docs (#3071)
- Relaxed Google Cloud and gRPC upper bounds to dynamic versions in dependencies.gradle.
- Relaxed JUnit testing framework bounds to < 6.0.0 and < 2.0.0.
- Aligned beam-runners-core-construction-java to 2.72.0!!
- Regenerated all gradle lockfiles across the project.
- Updated GEMINI.md and MEMORY.md with the optimized lockfile/dependency update workflow.
- Fixed presubmit errors in check_diff.py (added Apache 2.0 license header and fixed syntax).
2026-05-28 21:45:43 +00:00
Ben McIlwain
d68f3e5cc0 Centralize Fee validation and add tests (#3070)
This commit:
- Centralizes all creation validation checks into Fee.create().
- Inlines the redundant private createWithCustomDescription() method.
- Removes the awkward ternary operator for null type evaluation.
- Adds FeeTest.java to completely cover the Fee instance creation logic.
2026-05-28 20:50:04 +00:00
Ben McIlwain
33d30ea6a1 Improve PR polisher (#3067)
Enhances the `pr-polisher` skill to enforce stricter PR pre-flight checks:
- Added checks for extraneous files (package-lock.json).
- Added checks for missing license headers on new files across multiple languages.
- Added regex checks for codebase anti-patterns (FQNs, package visibility, UTC ZoneId, un-injected clocks, redundant transactions, etc.).
- Added regex checks for test anti-patterns (generic Exception catching, Truth Optional assertions, Thread.sleep).
- Enforced commit message body presence.
- Added a workflow step to explicitly verify commit message accuracy against the diff.
- Updated GEMINI.md to mandate the usage of the pr-polisher skill.
2026-05-28 19:32:33 +00:00
Ben McIlwain
00ee62f877 Update Netty and OkHttp dependencies and resolve test failures (#3066)
This commit updates several dependencies in dependencies.gradle and resolves the resulting test failures:
- Netty: Updated strict upper bound from <4.2.0 to <5.0.0 (resolved to 4.2.14.Final).
- OkHttp: Removed strict upper bound of <5.0.0 and moved to dynamic versions section (resolved to 5.3.2).
- Cleaned up dependencies.gradle by moving strict dependencies (secretmanager, common-protos, gax) out of the dynamic versions section.
- Updated all gradle.lockfile files.

Resulting test fixes:
- Netty: Replaced NioEventLoopGroup with DefaultEventLoopGroup in NettyExtension and ProbingStepTest. In Netty 4.2+, LocalServerChannel no longer supports registration with NIO-backed event loops, throwing an IllegalArgumentException.
- OkHttp: Refactored ServiceMonitoringClient to safely handle empty responses using Optional.orElse(). In OkHttp 5.x, a 204 No Content response returns a non-null ResponseBody with an empty string, causing an assertion failure where MosApiException was incorrectly bypassed.
2026-05-28 15:39:16 +00:00
Ben McIlwain
2bc07349a4 Address technical debt and improve safety in domain flows and models (#3065)
* Address technical debt and improve safety in domain flows and models

- Addressed unhandled empty lists and swallowed exceptions in DomainFlowTmchUtils.
- Improved null safety and immutability guarantees in Fee and LaunchPhase.
- Applied defensive copying in FeeTransformResponseExtension.
  Note: This uses the forceEmptyToNull(nullToEmptyImmutableCopy(...))
  pattern. This defensive copy ensures immutability, while forceEmptyToNull
  is required because JAXB will serialize an empty collection as an empty
  XML tag (which violates EPP XML schemas). Setting it to null ensures
  JAXB omits the tag entirely.
- Corrected JAXB property suppression in FeeCheckResponseExtensionItemStdV1.

* Add pr-polisher skill for automated PR pre-flight checks

* Enhance pr-polisher with more GEMINI.md constraints

Added checks for:
- Incorrect @Nullable imports.
- Unstatically imported utility methods (DateTimeUtils/CacheUtils).
- Redundant transaction wrapping (tm().transact -> tm().reTransact).
- Mutable collection instantiations (ArrayList/HashMap).
2026-05-27 21:45:11 +00:00
Juan Celhay
00522fb618 Change Kubernetes Services selectors to target "traffic" label (#3054)
* change spec.selector for kubernetes services

* now really change the selector
2026-05-27 18:59:37 +00:00
Ben McIlwain
ad992beff9 Add java-ast-refactoring skill (#3064)
This adds a Gemini CLI skill that leverages OpenRewrite to perform Abstract Syntax Tree (AST) based refactoring on Java codebases. It is highly preferred over text-based regex or python scripts because it understands Java semantics, correctly updates imports, and preserves formatting. A custom Python script is also included as a fallback for renaming fields and local variables.
2026-05-27 18:33:38 +00:00
Juan Celhay
ba91141505 Add postdeploy task in CD pipeline to tag deployed image (#3063)
* add variable for pipeline and region

* remove new lines from script

* add postdeploy task to tag images
2026-05-27 16:24:38 +00:00
gbrodman
c1ce73db49 Remove old unused GWT dependency (#3056) 2026-05-27 16:24:16 +00:00
Pavlo Tkach
00ceb6a7df Add Google Vacuum MGMT to schema allowed diff (#3062) 2026-05-27 15:48:31 +00:00
gbrodman
c731b18304 Replace unmaintained jsch dependency (#3061)
The old version https://mvnrepository.com/artifact/com.jcraft/jsch
hasn't been updated for like 8 years. This replaces it with the
community fork, https://github.com/mwiede/jsch.

It's theoretically possible to replace this with
https://github.com/apache/mina-sshd but it would require a lot more
changes and risk for not really any benefit.
2026-05-26 15:43:59 +00:00
Juan Celhay
5fd6e6cdaa Add a step to cloudbuild-nomulus CB job to create a release in Cloud Deploy (#3059)
* add step to create cd release in cb-nomulus job

* add variable for pipeline and region

* pass image with digest to release

* add source and skaffold to release command

* remove new lines from script
2026-05-26 14:32:38 +00:00
Ben McIlwain
53b92d602e Migrate EPP/Email from Soy to JAXB/FreeMarker (#3038)
- Replace deprecated Soy templates for EPP XML with JAXB models and a refined Fluent DSL.
- Migrate Spec11 and administrative emails to FreeMarker with HTML auto-escaping.
- Remove Soy compiler, Gradle tasks, and library dependencies.
- Address PR feedback regarding shadowing, version locking, and security warnings.
- Enhance tests with comprehensive XML equality assertions using Java 15 text blocks.
- Improve Javadocs and maintain strict temporal consistency using java.time.

FreeMarker replaces Soy for email templating, providing native HTML auto-escaping and allowing the removal of the complex 'soyToJava' compilation step from the build process. This significantly simplifies the build system and reduces maintenance overhead. For EPP XML, migrating to JAXB allows tool-generated commands to use the same model classes as the server-side EPP flows. This ensures that tool-generated XML is always schema-compliant and eliminates the risk of divergence between tool templates and actual server-side implementation. This unified approach provides compile-time type safety and improves developer ergonomics via a refined fluent DSL.

The base ImmutableObject class now provides a public clone() override that correctly resets the cached hashCode to null. This centralizes the custom cloning logic previously handled by a static helper and ensures that all subclasses—including the newly added JAXB models—satisfy CodeQL security requirements without needing redundant per-class overrides. The legacy static clone(T) helper has been updated to delegate to this instance method to maintain compatibility and architectural consistency.
2026-05-22 19:33:47 +00:00
gbrodman
b3fc57c7f7 Inject a singleton Gson instead of creating it many times (#3058)
Creation of Gson objects is nontrivial and it's thread-safe so we might
as well just use some singleton objects as much as possible rather than
recreating them.
2026-05-22 15:20:23 +00:00
Juan Celhay
bd70cd91a5 Add canary strategy to crash stage in Cloud Deploy pipeline (#3057)
* Add customCanaryDeployment strategy to crash

* change key to partialDeploymentAlertPolicyChecks

* add more indentation to alert policies config
2026-05-21 19:39:46 +00:00
143 changed files with 4204 additions and 2736 deletions

View File

@@ -0,0 +1,30 @@
---
name: java-ast-refactoring
description: "AST-aware Java refactoring using OpenRewrite. Use when asked to structurally refactor Java code, change class names, change method signatures/overloads, replace builder patterns, modify annotations, or perform cross-file structural replacements. Note: Renaming fields or local variables/parameters is not supported natively via simple YAML recipes in the standard openrewrite modules."
---
# AST-Aware Java Refactoring
This skill uses OpenRewrite to perform Abstract Syntax Tree (AST) based refactoring on Java codebases. This is highly preferred over text-based regex or python scripts because it understands Java semantics, correctly updates imports, and preserves formatting.
## Parameter and Field Renaming (Last Resort)
Because OpenRewrite's YAML recipes do not natively support simple variable or field renaming, a custom script is provided:
```bash
python3 .gemini/skills/java-ast-refactoring/scripts/safe_rename.py <filepath> <old_name> <new_name>
```
**CRITICAL:** Running this python script is a LAST RESORT. It is a regex-based token replacement that ignores strings and comments, but it lacks true AST understanding. ALWAYS prefer using OpenRewrite recipes (`rewrite.yml`) for structural changes like renaming classes, methods, or moving targets, as OpenRewrite correctly handles imports, types, and cross-file references safely.
## Usage
1. Create a `rewrite.yml` recipe file in the workspace root. Refer to `.gemini/skills/java-ast-refactoring/references/rewrite_recipes.md` for syntax.
2. Execute the script:
```bash
./.gemini/skills/java-ast-refactoring/scripts/run_rewrite.sh rewrite.yml
```
3. The script will safely apply the AST transformations and then automatically run `./gradlew spotlessApply` and `./gradlew javaIncrementalFormatApply` on the modified files to automatically fix any Checkstyle line-length and import ordering issues caused by longer/shorter identifiers. Verify the output using `git diff`.
4. **MANDATORY:** Always run `./gradlew build -x test` (or the equivalent compile task) after running OpenRewrite to ensure no compilation errors were introduced.
## Known Limitations & Troubleshooting
* **Static Imports Dropped on Class Rename:** When using `ChangeType` to rename a class, OpenRewrite may sometimes drop static imports for fields/constants belonging to the old class instead of updating them to the new class. If compilation fails due to "cannot find symbol" for a constant after a class rename, manually restore the static import (e.g., `import static com.new.ClassName.CONSTANT;`).
* **Continuous Improvement:** If any new issues or edge cases are found while running the refactoring (e.g., build failures, formatting issues, or missed transformations), you **MUST** proactively ask the user if you should permanently update this skill file (`SKILL.md`) and its accompanying scripts (`scripts/run_rewrite.sh`, `scripts/safe_rename.py`) to fix the issue for future use. Do not wait for the user to prompt you to fix the infrastructure.

View File

@@ -0,0 +1,82 @@
# OpenRewrite Recipe Reference
OpenRewrite uses declarative YAML recipes to perform structural refactorings.
## Recipe Structure
A recipe file must have a `type`, a `name` (which you will activate), and a `recipeList` containing specific core recipes to execute sequentially.
```yaml
type: specs.openrewrite.org/v1beta/recipe
name: com.example.MyRefactoring
recipeList:
- <CoreRecipe>:
<argument1>: <value>
```
## Core Recipes for Common Operations
### 1. Change Method Name
```yaml
- org.openrewrite.java.ChangeMethodName:
methodPattern: java.util.Collections emptyList()
newMethodName: emptyList
```
### 2. Change Method Target to Static
Moves a method call to a new static method target. Useful for replacing custom utility methods with standard ones.
```yaml
- org.openrewrite.java.ChangeMethodTargetToStatic:
methodPattern: google.registry.model.eppinput.EppInputs createDomain(java.lang.String, java.lang.String)
fullyQualifiedTargetTypeName: google.registry.model.domain.DomainCommand.Create
returnType: google.registry.model.domain.DomainCommand.Create.Builder
```
### 3. Change Type (Rename/Move Class)
Updates the class name and automatically updates all imports across the codebase.
*Note: OpenRewrite occasionally drops `import static` references to fields inside the renamed class. Be prepared to manually restore them if a compilation error occurs.*
```yaml
- org.openrewrite.java.ChangeType:
oldFullyQualifiedTypeName: org.joda.time.Instant
newFullyQualifiedTypeName: java.time.Instant
```
### 4. Remove Unused Imports
```yaml
- org.openrewrite.java.RemoveUnusedImports
```
### 5. Change Annotation
```yaml
- org.openrewrite.java.ChangeAnnotation:
annotationPattern: @org.junit.Ignore
newAnnotation: @org.junit.jupiter.api.Disabled
```
### 6. Remove Annotation
```yaml
- org.openrewrite.java.RemoveAnnotation:
annotationPattern: @java.lang.SuppressWarnings("unchecked")
```
### 7. Change Method Arguments
Reorders or removes arguments based on a target signature. `newArgumentTemplate` uses 0-based indexing.
```yaml
- org.openrewrite.java.ChangeMethodAccessLevel:
methodPattern: com.google.common.collect.ImmutableList of(..)
newAccessLevel: protected
```
### 8. Add Import
```yaml
- org.openrewrite.java.AddImport:
type: java.util.List
```
## Method Patterns
OpenRewrite uses a specific pointcut expression language for `methodPattern`:
* `[return-type] [fully-qualified-class-name] [method-name]([parameter-types])`
* `*` matches any type.
* `..` matches any number of parameters.
* Example: `java.lang.String split(java.lang.String, int)`
* Example: `* java.util.List add(..)`

View File

@@ -0,0 +1,80 @@
#!/bin/bash
# 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.
# Wrapper script to dynamically execute OpenRewrite without modifying build.gradle
if [ -z "$1" ]; then
echo "Usage: $0 <path-to-rewrite.yml>"
exit 1
fi
RECIPE_FILE=$(realpath "$1")
if [ ! -f "$RECIPE_FILE" ]; then
echo "Error: Recipe file $RECIPE_FILE not found."
exit 1
fi
# Extract the name of the recipe from the YAML to activate it
RECIPE_NAME=$(grep -oP '(?<=name: ).*' "$RECIPE_FILE" | head -n 1)
if [ -z "$RECIPE_NAME" ]; then
echo "Error: Could not extract 'name:' from $RECIPE_FILE"
exit 1
fi
INIT_SCRIPT="rewrite-init.gradle"
cat << EOF > "$INIT_SCRIPT"
initscript {
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath("org.openrewrite.rewrite:org.openrewrite.rewrite.gradle.plugin:7.33.0")
}
}
rootProject {
apply plugin: org.openrewrite.gradle.RewritePlugin
rewrite {
activeRecipe("$RECIPE_NAME")
}
dependencies {
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:2.14.0")
rewrite("org.openrewrite.recipe:rewrite-migrate-java:2.11.0")
rewrite("org.openrewrite.recipe:rewrite-spring:5.7.0")
}
}
allprojects {
apply plugin: org.openrewrite.gradle.RewritePlugin
}
EOF
# Copy the recipe file to the workspace root temporarily so OpenRewrite finds it
cp "$RECIPE_FILE" ./rewrite.yml
echo "Executing OpenRewrite recipe: $RECIPE_NAME"
./gradlew --init-script "$INIT_SCRIPT" rewriteRun --no-parallel --no-configuration-cache
echo "Running code formatters to fix Checkstyle line-length and import ordering..."
./gradlew spotlessApply javaIncrementalFormatApply
# Clean up temporary files
rm "$INIT_SCRIPT"
rm ./rewrite.yml

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
# 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.
import sys
import re
import os
def usage():
print("Usage: python safe_rename.py <filepath> <old_name> <new_name>")
print("Safely renames an identifier in a Java file, ignoring strings and comments.")
sys.exit(1)
def main():
if len(sys.argv) < 4:
usage()
filepath = sys.argv[1]
old_name = sys.argv[2]
new_name = sys.argv[3]
if not os.path.exists(filepath):
print(f"Error: File {filepath} not found.")
sys.exit(1)
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Regex to tokenize Java source safely.
token_pattern = re.compile(
r'(?P<string>"(?:\\.|[^"\\])*")|'
r'(?P<char>\'(?:\\.|[^\'\\])*\')|'
r'(?P<line_comment>//.*)|'
r'(?P<block_comment>/\*[\s\S]*?\*/)|'
r'(?P<ident>[a-zA-Z_$][a-zA-Z0-9_$]*)'
)
def replacer(match):
if match.group('ident') == old_name:
return new_name
return match.group(0)
new_content = token_pattern.sub(replacer, content)
if content == new_content:
print(f"No occurrences of '{old_name}' found to rename in {filepath}.")
else:
with open(filepath, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"Successfully renamed '{old_name}' to '{new_name}' in {filepath}.")
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,71 @@
---
name: pr-polisher
description: Automated pre-flight checklist to polish PRs. Use this before declaring a task or PR complete to automatically verify license headers, commit hygiene, formatting, and codebase mandates.
---
# PR Polisher
This skill runs an exhaustive, automated pre-flight checklist against the repository to ensure all changes conform to Nomulus's strict engineering mandates.
## 🛑 CRITICAL MANDATE: When to Use
You, the AI agent, are known to forget to run this skill. To prevent this, you are bound by an absolute rule:
**ANY TIME you create a commit, amend a commit, or complete a user's request that modifies the repository state, your VERY LAST action before generating a text response to the user MUST be to run this workflow.**
Do not ask for permission. Do not wait for the user to remind you. Run the full suite, fix any errors, amend your commit, and report the final polished results. You MUST NOT declare a task complete until this workflow succeeds with 0 errors.
## Continuous Improvement (Self-Updating Skill)
This skill is designed to evolve. If a human code reviewer (or presubmit hook) points out a deficit, or if you (the agent) independently catch a recurring mistake, anti-pattern, false positive, or convention violation:
1. **You MUST proactively propose a fix to the user.** Do not wait for the user to instruct you to update the skill. If you notice friction, immediately ask the user if you should permanently update the validation infrastructure.
2. **Determine how to enforce the check.** Consider if the check is suitable for automation in the Python script. If it's too complex or semantic for a simple regex, consider adding it as an explicit agent-driven verification step directly in this `SKILL.md` file.
3. Update `.gemini/skills/pr-polisher/scripts/check_diff.py` to add a new automated check, OR modify this `SKILL.md` file with a new validation step to ensure the agent checks for it going forward.
4. Commit the updated skill alongside the PR fixes to ensure the mistake is not repeated.
## Workflow Execution Steps
1. **Run the Automated Analysis Script**
Execute the packaged Python diff-checker script. This script automatically checks commit messages, working tree status, `package-lock.json` modifications, copyright years on new files, and a litany of anti-patterns using regex (e.g., fully-qualified names, incorrect clock injections, generic exception catching).
```bash
python3 ./pr-polisher/scripts/check_diff.py
```
2. **Run Formatting Validation**
Always run the project's formatting tools to ensure checkstyle and Google Java Format passes.
```bash
./gradlew spotlessCheck javaIncrementalFormatCheck
# OR if formatting is needed:
./gradlew spotlessApply javaIncrementalFormatApply
```
3. **Run Presubmits and Compilation**
Ensure that the project builds correctly and all presubmit checks pass. Use scoped builds when possible to save time and avoid unwanted side effects (like modifying `console-webapp/package-lock.json`).
```bash
# Run presubmits
./gradlew runPresubmits
# Verify compilation (use a scoped build if you only modified one module, e.g., :core)
./gradlew :core:build -x test
# Run standard test suite if modifying core
./gradlew :core:standardTest
```
4. **Verify PR Scope and Extraneous Files (Line-by-Line Inspection)**
You must carefully review the entirety of your changes (`git diff HEAD^` or `git diff --cached`). Examine every single file and line changed to explicitly verify that the change *belongs* in this PR. You MUST look for and revert:
* **Irrelevant changes:** Formatting or refactoring in files unrelated to the PR's core purpose.
* **Accidental files:** Test output files, temp scripts, plan files (e.g., `codebase_review_plan.md`), scratchpads, or anything else generated during your workflow that shouldn't be committed.
* **Unintended side effects:** Changes to configuration files like `package-lock.json` unless explicitly required.
5. **Verify Test Coverage Additions (Line-by-Line Inspection)**
While looking at all the diffs, thoroughly check every single line to determine if any test changes or additions are necessary. If you have modified existing logic, added new branches, added a null check, or added new public methods, you MUST manually verify that the corresponding `Test.java` file covers these changes. If the test file or specific test cases do not exist, you must create them. A code review is not thorough if it only checks for compilation.
6. **Verify Commit Description Accuracy**
Re-read your commit message (`git log -1 --pretty=format:%B`). Compare it directly against the actual diff (`git diff HEAD^`) to verify that it completely and accurately describes ONLY the changes present in the commit.
* If the scope of the PR changed during prompting, you MUST update the commit message to reflect the final state of the code.
* Ensure that the primary purpose of the PR is mentioned first, and that no irrelevant or outdated changes from previous attempts are listed.
7. **Address Errors, Amend, and Re-Run (Iterative Checking)**
If any script throws an error, if scope was reduced, if the commit message was inaccurate, or if formatting changes were applied, you must stage those fixes and amend your commit:
```bash
git add -u
git commit --amend # Or git commit --amend --no-edit if only files changed
```
**CRITICAL:** You must loop back to Step 1 and run `python3 ./pr-polisher/scripts/check_diff.py` again. Continue this loop of checking and amending until the script definitively returns `0 ERRORS`, the build/presubmits pass, and the working directory is perfectly clean. Do not assume your fixes worked without re-running the check.

View File

@@ -0,0 +1,310 @@
#!/usr/bin/env python3
# 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.
import subprocess
import re
import sys
import datetime
# Color codes
RED = "\03.3[91m"
YELLOW = "\03.3[93m"
GREEN = "\03.3[92m"
RESET = "\03.3[0m"
errors_found = 0
warnings_found = 0
def log_error(msg):
global errors_found
errors_found += 1
print(f"{RED}[ERROR]{RESET} {msg}")
def log_warning(msg):
global warnings_found
warnings_found += 1
print(f"{YELLOW}[WARNING]{RESET} {msg}")
def log_success(msg):
print(f"{GREEN}[OK]{RESET} {msg}")
def run_cmd(cmd):
return subprocess.check_output(cmd, shell=True, text=True).strip()
def check_single_commit():
print("--- Checking Commit Count ---")
try:
count = int(run_cmd("git rev-list --count HEAD ^master"))
if count > 1:
log_error(f"Branch contains {count} commits ahead of master. All changes for a single PR must be squashed into a single commit.")
else:
log_success("Branch contains a single commit.")
except Exception:
# Ignore errors if the git command fails (e.g. not a git repo or no master branch)
pass
def check_commit_message():
print("--- Checking Commit Message ---")
try:
msg = run_cmd("git log -1 --pretty=format:%B")
lines = msg.split('\n')
subject = lines[0]
if len(subject) > 50:
log_error(f"Commit subject exceeds 50 characters ({len(subject)} chars): '{subject}'")
if not subject[0].isupper():
log_error(f"Commit subject must be capitalized: '{subject}'")
if subject[-1] in ['.', '!', '?']:
log_error(f"Commit subject must not end with punctuation: '{subject}'")
has_body = False
for line in lines[1:]:
if line.strip() != "":
has_body = True
break
if not has_body:
log_error("Commit message must contain a comprehensive body detailing the 'what' and 'why'.")
if errors_found == 0:
log_success("Commit message format looks good.")
except Exception as e:
log_error(f"Failed to check commit message: {e}")
def check_workspace_clean():
print("\n--- Checking Workspace State ---")
status = run_cmd("git status --porcelain")
if status:
log_error("Workspace is not clean. Uncommitted changes found:\n" + status)
else:
log_success("Working directory is clean.")
def check_package_lock():
print("\n--- Checking package-lock.json ---")
diff_files = run_cmd("git diff HEAD^ --name-only").split('\n')
if "console-webapp/package-lock.json" in diff_files:
if "console-webapp/package.json" in diff_files or "dependencies.gradle" in diff_files:
log_warning("console-webapp/package-lock.json is modified in the diff. This is expected since dependencies were updated.")
else:
log_error("console-webapp/package-lock.json is modified in the diff. Unless NPM dependencies were explicitly changed, revert this file using: git checkout console-webapp/package-lock.json")
else:
log_success("console-webapp/package-lock.json is untouched.")
def check_license_headers():
print("\n--- Checking License Headers on New Files ---")
current_year = str(datetime.datetime.now().year)
added_files = run_cmd("git diff HEAD^ --name-status --diff-filter=A").split('\n')
java_header = f"// Copyright {current_year} The Nomulus Authors. All Rights Reserved."
script_header = f"# Copyright {current_year} The Nomulus Authors. All Rights Reserved."
sql_header = f"-- Copyright {current_year} The Nomulus Authors. All Rights Reserved."
ftl_header = f"<#-- Copyright {current_year} The Nomulus Authors. All Rights Reserved."
files_checked = 0
for f_line in added_files:
if not f_line:
continue
f = f_line.split('\t')[-1]
expected_header = None
if f.endswith('.java') or f.endswith('.js') or f.endswith('.gradle') or f.endswith('.ts'):
expected_header = java_header
elif f.endswith('.py') or f.endswith('.sh'):
expected_header = script_header
elif f.endswith('.sql'):
expected_header = sql_header
elif f.endswith('.ftl'):
expected_header = ftl_header
if expected_header:
files_checked += 1
try:
with open(f, 'r') as file:
content = file.read()
if expected_header not in content:
log_error(f"Missing or incorrect copyright year in {f}. Expected: {expected_header}")
except FileNotFoundError:
# File might have been deleted or renamed; ignore missing files.
pass
if files_checked == 0:
log_success("No new files requiring license headers added.")
def check_formatting():
print("\n--- Checking Project Formatting ---")
try:
run_cmd("./gradlew spotlessCheck javaIncrementalFormatCheck")
log_success("All formatting checks (spotless and javaIncrementalFormat) passed.")
except Exception as e:
log_error("Formatting checks failed. Run './gradlew spotlessApply javaIncrementalFormatApply' to fix.")
def check_diff_anti_patterns():
print("\n--- Checking Code Anti-Patterns in Diff ---")
diff = run_cmd("git diff HEAD^ -U0")
current_file = ""
# Regex Patterns
fqn_pattern = re.compile(r'(?<!import\s)(java|google\.registry|com\.google|org)\.[a-z0-9.]+\.[A-Z][a-zA-Z0-9]+')
visibility_pattern = re.compile(r'/\*\s*package\s*\*/')
utc_pattern = re.compile(r'ZoneId\.of\("UTC"\)')
now_pattern = re.compile(r'(Instant\.now\(\)|OffsetDateTime\.now\(\)|System\.currentTimeMillis\(\))')
catch_generic_pattern = re.compile(r'catch\s*\(\s*(Exception|Throwable)\s+[a-zA-Z0-9_]+\s*\)')
is_equal_optional_pattern = re.compile(r'\.isEqualTo\(Optional\.of\(')
sleep_pattern = re.compile(r'Thread\.sleep\(')
suppress_pattern = re.compile(r'@SuppressWarnings\(')
wrong_nullable_pattern = re.compile(r'import\s+(?!javax\.annotation\.Nullable;)[a-zA-Z0-9_.]+\.Nullable;')
utility_class_pattern = re.compile(r'\b(DateTimeUtils|CacheUtils)\.[a-z]')
redundant_tx_pattern = re.compile(r'tm\(\)\.transact\(\s*\(\)\s*->\s*tm\(\)\.reTransact')
mutable_collection_pattern = re.compile(r'new\s+(ArrayList|HashMap|HashSet)\s*[<()]')
trailing_space_pattern = re.compile(r'[ \t]+$')
debug_print_pattern = re.compile(r'(System\.(out|err)\.print|\.printStackTrace\(\))')
todo_pattern = re.compile(r'\b(TODO|FIXME)\b')
unnecessary_cast_pattern = re.compile(r'\(\s*(?:Instant|ImmutableSet|ImmutableList|ImmutableMap)(?:<[^>]+>)?\s*\)')
instant_tostring_pattern = re.compile(r'(?i)(?:instant|time|now|clock\.now\(\))\.toString\(\)')
dao_transact_pattern = re.compile(r'tm\(\)\.transact\(')
retransact_txtime_pattern = re.compile(r'tm\(\)\.reTransact\(\s*(?:\(\)\s*->\s*)?tm\(\)(?:\.|::)get(?:Transaction|Tx)Time\(\)?\s*\)')
inject_command_pattern = re.compile(r'inject\(Command\s+[a-zA-Z0-9_]+\)')
clock_now_pattern = re.compile(r'clock\.now\(\)')
suppress_count = 0
for line in diff.split('\n'):
if line.startswith('+++ b/'):
current_file = line[6:]
suppress_count = 0
continue
if line.startswith('+') and not line.startswith('+++'):
code_line = line[1:]
# Trailing whitespace
if trailing_space_pattern.search(code_line):
log_error(f"[{current_file}] Found trailing whitespace.")
# Skip regex definitions in this script from triggering false positives
if 're.compile' not in code_line:
# Debug prints
if debug_print_pattern.search(code_line):
log_error(f"[{current_file}] Found leftover debug print or stack trace (System. out. println / printStackTrace).")
# TODOs / FIXMEs
if todo_pattern.search(code_line):
log_warning(f"[{current_file}] Found new T" "ODO or F" "IXME. Ensure this is intentional before completing the PR.")
if not current_file.endswith('.java'):
continue
# FQN Check
fqn_matches = fqn_pattern.findall(code_line)
if fqn_matches:
# Skip if the match is exactly part of an import or package declaration
if not code_line.strip().startswith('import') and not code_line.strip().startswith('package'):
log_warning(f"[{current_file}] Potential Fully-Qualified Name found: {fqn_matches}. Use imports instead.")
# Package visibility
if visibility_pattern.search(code_line):
log_error(f"[{current_file}] Found '/* package */' modifier. Leave modifier blank instead.")
# Time zones
if utc_pattern.search(code_line):
log_error(f"[{current_file}] Found ZoneId.of(\"UTC\"). Use statically imported ZoneOffset.UTC instead.")
# System clocks
if now_pattern.search(code_line):
log_error(f"[{current_file}] Found un-injected clock (Instant.now / System.currentTimeMillis). Inject Clock instead.")
# Catch generic
if catch_generic_pattern.search(code_line):
log_warning(f"[{current_file}] Catching generic Exception/Throwable. Use specific exceptions.")
# Truth Optionals
if is_equal_optional_pattern.search(code_line):
log_warning(f"[{current_file}] Found .isEqualTo(Optional.of(...)). Use Truth's .hasValue(...) instead.")
# Thread.sleep
if sleep_pattern.search(code_line):
log_warning(f"[{current_file}] Found Thread.sleep(). Use Sleeper instead in tests.")
# SuppressWarnings
if suppress_pattern.search(code_line):
suppress_count += 1
if suppress_count > 1:
log_error(f"[{current_file}] Multiple @SuppressWarnings detected. They must be merged (e.g. {{\"unchecked\", \"foo\"}}).")
else:
suppress_count = 0
# Wrong Nullable
if wrong_nullable_pattern.search(code_line):
log_error(f"[{current_file}] Found incorrect Nullable import. Always use javax.annotation.Nullable.")
# Missing static imports for utilities
if utility_class_pattern.search(code_line):
if not code_line.strip().startswith('import'):
log_warning(f"[{current_file}] Found un-statically imported method from DateTimeUtils/CacheUtils. Use static imports.")
# Redundant transaction wrapping
if redundant_tx_pattern.search(code_line):
log_error(f"[{current_file}] Found redundant transaction wrapping (tm().transact(() -> tm().reTransact(...))).")
# Mutable collection instantiation
if mutable_collection_pattern.search(code_line):
log_warning(f"[{current_file}] Found mutable collection instantiation (ArrayList/HashMap/HashSet). Prefer Guava Immutable collections.")
# Unnecessary casts
if unnecessary_cast_pattern.search(code_line):
log_warning(f"[{current_file}] Potential unnecessary cast to Instant or Guava Immutable type. Remove if it compiles without it.")
# Instant toString
if instant_tostring_pattern.search(code_line):
log_error(f"[{current_file}] Found potential Instant.toString(). Use DateTimeUtils.formatInstant(...) to preserve .000Z precision.")
# DAO transactions
if current_file.lower().endswith('dao.java') and dao_transact_pattern.search(code_line):
log_error(f"[{current_file}] Found tm().transact(...) inside a DAO. Use tm().assertInTransaction() instead.")
# reTransact around getTxTime in production code
if 'src/main/' in current_file and retransact_txtime_pattern.search(code_line):
log_error(f"[{current_file}] Unnecessary reTransact() around getTxTime() in production code. Wrap the caller in a transaction instead.")
# inject(Command)
if inject_command_pattern.search(code_line):
log_error(f"[{current_file}] Generic inject(Command) methods do not work with Dagger. Use explicit concrete types.")
# clock.now() in tests
if 'src/test/' in current_file and clock_now_pattern.search(code_line):
log_warning(f"[{current_file}] Prefer using a fixed, static constant Instant over capturing clock.now() in tests to prevent flakiness.")
def main():
print("========================================")
print(" NOMULUS PR POLISHER CHECKLIST ")
print("========================================\n")
check_single_commit()
check_commit_message()
check_workspace_clean()
check_package_lock()
check_license_headers()
check_formatting()
check_diff_anti_patterns()
print("\n========================================")
if errors_found == 0 and warnings_found == 0:
print(f"{GREEN}SUCCESS: All checks passed. PR is polished!{RESET}")
else:
print(f"RESULTS: {RED}{errors_found} ERRORS{RESET}, {YELLOW}{warnings_found} WARNINGS{RESET}")
print("Please address the above issues before declaring the PR complete.")
sys.exit(1 if errors_found > 0 else 0)
if __name__ == "__main__":
main()

View File

@@ -53,6 +53,13 @@ This document outlines foundational mandates, architectural patterns, and projec
### 6. Project Dependencies
- **Common Module:** When using `Clock` or other core utilities in a new or separate module (like `load-testing`), ensure `implementation project(':common')` is added to the module's `build.gradle`.
- **Updating Dependencies:** When updating third-party dependencies, you MUST follow this exact, non-negotiable sequence. NEVER run the update script without first verifying the lockfiles locally.
1. Modify `dependencies.gradle`.
2. Run `./gradlew dependencies --write-locks --update-locks '*:*'` to regenerate all Gradle lockfiles locally to resolve new versions.
3. Run `./gradlew clean build` to exhaustively compile and run all tests against the new transitive dependency tree. If there are compilation or test errors due to breaking API changes or output formatting changes in the new dependency version, your primary goal is to **fix our codebase** to be compliant with the new dependency version. Only revert the dependency bump or cap the version if the required code changes are prohibitively complex or outside the scope of the current task.
4. Only once the build passes and `git status` shows modified lockfiles, commit the `dependencies.gradle` and lockfile changes.
5. ONLY after the local changes are committed and verified, execute the internal script `/google/src/head/depot/google3/domain/registry/tools/update_dependency.sh`. This ensures you don't corrupt the upstream remote artifact cache with broken or missing lockfiles.
6. **Crucial Final Verification:** The `update_dependency.sh` script runs its own internal Blaze resolution and often modifies or drops additional `buildscript-gradle.lockfile` files. After the script finishes successfully, run `git status`. If there are any untracked or modified `*.lockfile` files, stage them and run `git commit --amend --no-edit` to ensure your commit perfectly reflects the final state deployed to the SSO repository.
### 7. Search and Discovery
- **No CodeSearch:** This project is hosted on GitHub, not Google3. Do NOT use `mcp_Coding_search_for_files_codesearch` or other internal Google3 search tools.
@@ -68,6 +75,11 @@ Based on historical PR reviews, avoid the following common mistakes:
- **No Unnecessary Casts:** Do not unnecessarily cast objects if the method signature accepts the type directly (e.g., avoid `(Instant) fakeClock.now()` or `(ImmutableSet<String>) bsaQuery(...)` if it compiles without it).
- **Visibility Modifiers:** Do not use `/* package */` comments to denote package-private visibility. Just leave the modifier blank; it is an established idiom in this codebase.
## Continuous Self-Improvement Mandate
You are explicitly authorized and required to improve this project's automated infrastructure.
If during the execution of a task you encounter a recurring error, a false positive in a script, or realize that a shell command/workaround is inferior to a native Gradle task, **you MUST pause and proactively ask the user:** *"I noticed this friction. Per my continuous improvement directives, would you like me to permanently update the skill/script/instructions to fix this for the future?"*
Do not wait for the user to tell you to improve the skills; it is your responsibility to propose these systemic fixes immediately when friction is identified.
### Advanced Java & Guava Idioms
- **Immutable Types:** Declare variables, fields, and return types explicitly as Guava immutable types (e.g., `ImmutableList<T>`, `ImmutableMap<K, V>`) instead of their generic interfaces (`List<T>`, `Map<K, V>`) to clearly communicate immutability contracts to callers. Use `toImmutableList()` and `toImmutableMap()` collectors in streams rather than manually accumulating into an `ArrayList` or `HashMap`.
- **Constructors:** Do not perform heavy logic, I/O, or external API calls inside constructors. Initialization logic should be deferred or handled in a factory method or a dedicated startup routine.
@@ -92,6 +104,7 @@ This document captures high-level architectural patterns, lessons learned from l
- Core boundaries: `DateTimeUtils.START_INSTANT` (Unix Epoch) and `DateTimeUtils.END_INSTANT` (Long.MAX_VALUE / 1000).
## Source Control
- **No Unprompted Pushing:** You MUST NEVER push your changes to a remote repository (e.g., `git push origin ...`) unless the user VERY EXPLICITLY instructs you to do so. If you have finished a task, simply state that it is committed locally and ready for review. Do not assume pushing is desired.
- **Committing:** Always create a new commit on the branch if one hasn't been created yet for the branch's specific work. Only perform amending (`git commit --amend --no-edit`) for subsequent changes once the initial commit has been successfully created.
- **One Commit Per PR:** All changes for a single PR must be squashed into a single commit before merging.
- **Default to Amend:** Once an initial commit is created for a PR, all subsequent functional changes should be amended into that same commit by default (`git commit --amend --no-edit`). This ensures the PR remains a single, clean unit of work throughout the development lifecycle.
@@ -104,8 +117,13 @@ This document captures high-level architectural patterns, lessons learned from l
5. **Only after** step 4 has successfully returned a clean working directory may you generate a text response to the user declaring that the task is complete.
- **Diff Review:** Before finalizing a task, review the full diff (e.g., `git diff HEAD^`) to ensure all changes are functional and relevant. Identify and revert any formatting-only changes in files that do not contain functional updates to keep the commit focused.
## Self-Review Guidelines
Before finalizing any PR or declaring a task complete, you MUST perform a thorough, rigorous self-review of your entire diff. Run `git diff HEAD^` (or review the staged changes) and actively verify the following against every modified line:
## 🛑 CRITICAL: MANDATORY PR POLISHER EXECUTION
You, the AI agent, have a known failure mode of forgetting to run the PR polisher after making changes because you focus too narrowly on the immediate task. To prevent this, you are bound by the following absolute rule:
**ANY TIME you create a commit, amend a commit, or complete a user's request that modifies the repository state, your VERY LAST action before generating a text response to the user MUST be to execute the `pr-polisher` skill.**
Do not ask for permission. Do not wait for the user to remind you. Run the full suite (`python3 .gemini/skills/pr-polisher/scripts/check_diff.py`, `runPresubmits`, `build`, and `test`), fix any errors, amend your commit, and report the final polished results in your response.
You MUST NOT declare a task done until this automated script returns 0 errors, the presubmits and tests pass, and the workspace is fully clean.
1. **Imports & FQNs:** Did I leave any fully-qualified class names or static variables inline? Did I add the necessary imports for them? *Crucial Exception:* If the file already imports a class with the identical name (e.g., it uses both `java.util.Date` and `java.sql.Date`), one MUST remain fully qualified to avoid a compilation conflict.
2. **Diff Scope:** Are there any formatting-only changes in files that I did not functionally modify? If so, revert them. Does the total line count of the diff align with the approved scope (e.g., ~1,000 lines for migrations)?
@@ -113,7 +131,7 @@ Before finalizing any PR or declaring a task complete, you MUST perform a thorou
4. **Missing Tests & Coverage:** *Perform a structured check for any new methods or modified behavior.* Did I add a new utility method (like `plusMonths(Instant, int)`) or change core logic? If so, I MUST open the corresponding test file and write tests to cover the new functionality (including edge cases, negative values, and leap years) before considering the task complete. A code review is not thorough if it only checks for compilation. I must actively ensure every new branch of logic has a test.
5. **Package Lock:** Did I include `console-webapp/package-lock.json` in my diff? If so, I MUST revert it (`git checkout console-webapp/package-lock.json`) unless I explicitly intended to modify NPM dependencies. This file is often modified by the build process and should not be committed accidentally.
Only after actively confirming these checks against your diff are you permitted to finalize the task.
Only after actively executing the `pr-polisher` skill and confirming these checks against your diff are you permitted to finalize the task.
## Refactoring & Migration Guardrails

View File

@@ -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']

View File

@@ -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
@@ -67,12 +68,12 @@ org.jacoco:org.jacoco.core:0.8.14=jacocoAnt
org.jacoco:org.jacoco.report:0.8.14=jacocoAnt
org.javassist:javassist:3.28.0-GA=checkstyle
org.jspecify:jspecify:1.0.0=annotationProcessor,checkstyle,compileClasspath,deploy_jar,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath,testing,testingAnnotationProcessor,testingCompileClasspath
org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.13.4=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:6.1.0=testCompileClasspath,testRuntimeClasspath
org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath
org.ow2.asm:asm-commons:9.9=jacocoAnt
org.ow2.asm:asm-tree:9.9=jacocoAnt

View File

@@ -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.
*
* <p>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<String, Object> 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);
}
}
}

View File

@@ -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<String, Object> 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<String, Object> 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<String, Object> data =
ImmutableMap.of("name", "<b>World</b>", "score", 42, "showMessage", false);
String result = renderer.render("google/registry/util/test_template.ftl", data);
assertThat(result).contains("Hello &lt;b&gt;World&lt;/b&gt;!");
}
@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<String, Object> 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");
}
}

View File

@@ -0,0 +1,6 @@
<#-- Copyright 2026 The Nomulus Authors. All Rights Reserved. -->
Hello ${name}!
Your score is ${score}.
<#if showMessage>
Message: ${message}
</#if>

View File

@@ -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",

View File

@@ -39,9 +39,9 @@ task runConsoleWebappUnitTests(type: Exec) {
task buildConsoleWebapp(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'
executable 'npx'
def configuration = project.getProperty('configuration')
args 'run', "build", "--configuration=${configuration}"
args 'ng', 'build', '--base-href=/console/', "--configuration=${configuration}", "--output-path=staged/dist"
doFirst {
println "Building console for environment: ${configuration}"
}
@@ -52,18 +52,11 @@ task buildConsoleForAll() {}
def createConsoleTask = { env ->
project.tasks.register("buildConsoleFor${env.capitalize()}", Exec) {
workingDir "${consoleDir}/"
executable 'npm'
args 'run', 'build', "--configuration=${env}"
executable 'npx'
args 'ng', 'build', '--base-href=/console/', "--configuration=${env}", "--output-path=staged/console-${env}"
doFirst {
println "Building console for environment: ${env}"
}
doLast {
copy {
from "${consoleDir}/staged/dist/"
into "${consoleDir}/staged/console-${env}"
}
delete "${consoleDir}/staged/dist"
}
dependsOn(tasks.npmInstallDeps)
}
project.tasks.register("deleteConsoleFor${env.capitalize()}", Delete) {
@@ -81,14 +74,6 @@ def createConsoleTask = { env ->
createConsoleTask(env)
}
// Force an order so we don't run these tasks in parallel.
tasks.buildConsoleForCrash.mustRunAfter(tasks.buildConsoleForAlpha)
tasks.buildConsoleForQa.mustRunAfter(tasks.buildConsoleForCrash)
tasks.buildConsoleForSandbox.mustRunAfter(tasks.buildConsoleForQa)
tasks.buildConsoleForProduction.mustRunAfter(tasks.buildConsoleForSandbox)
// This task must run last, otherwise the previous tasks will have deleted the "dist" folder.
tasks.buildConsoleWebapp.mustRunAfter(tasks.buildConsoleForProduction)
task applyFormatting(type: Exec) {
workingDir "${consoleDir}/"
executable 'npm'

View File

@@ -629,15 +629,15 @@
}
},
"node_modules/@angular/build/node_modules/@types/node": {
"version": "25.7.0",
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/@types/node/-/node-25.7.0.tgz",
"integrity": "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==",
"version": "25.9.1",
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/@types/node/-/node-25.9.1.tgz",
"integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
"dev": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"undici-types": "~7.21.0"
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/@angular/build/node_modules/@vitejs/plugin-basic-ssl": {
@@ -744,9 +744,9 @@
}
},
"node_modules/@angular/build/node_modules/undici-types": {
"version": "7.21.0",
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/undici-types/-/undici-types-7.21.0.tgz",
"integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==",
"version": "7.24.6",
"resolved": "https://us-npm.pkg.dev/artifact-foundry-prod/ah-3p-staging-npm/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"dev": true,
"license": "MIT",
"optional": true,

View File

@@ -53,14 +53,7 @@ def dockerIncompatibleTestPatterns = [
// affected by global states outside of Nomulus classes, e.g., threads and
// objects retained by frameworks.
// TODO(weiminyu): identify cause and fix offending tests.
def fragileTestPatterns = [
// Breaks random other tests when running with standardTests.
"google/registry/bsa/UploadBsaUnavailableDomainsActionTest.*",
// Currently changes a global configuration parameter that for some reason
// results in timestamp inversions for other tests. TODO(mmuller): fix.
"google/registry/flows/host/HostInfoFlowTest.*",
"google/registry/beam/common/RegistryPipelineWorkerInitializerTest.*",
] + dockerIncompatibleTestPatterns
def fragileTestPatterns = dockerIncompatibleTestPatterns
sourceSets {
main {
@@ -94,7 +87,6 @@ processTestResources {
configurations {
jaxb
soy
devtool
nonprodImplementation.extendsFrom implementation
@@ -120,7 +112,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'
}
}
@@ -166,10 +158,6 @@ dependencies {
implementation deps['com.google.flogger:flogger']
implementation deps['com.google.guava:guava']
implementation deps['com.google.protobuf:protobuf-java']
// Might need to add this back if we re-add nebula-lint
// gradleLint.ignore('unused-dependency') {
implementation deps['com.google.gwt:gwt-user']
// }
implementation deps['com.google.cloud:google-cloud-compute']
implementation deps['com.google.cloud:google-cloud-core']
implementation deps['com.google.cloud:google-cloud-storage']
@@ -181,9 +169,9 @@ 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.github.mwiede:jsch']
implementation deps['com.zaxxer:HikariCP']
implementation deps['com.squareup.okhttp3:okhttp']
implementation deps['dnsjava:dnsjava']
@@ -286,6 +274,7 @@ dependencies {
testImplementation deps['org.junit-pioneer:junit-pioneer']
testImplementation deps['org.junit.platform:junit-platform-runner']
testImplementation deps['org.junit.platform:junit-platform-suite-api']
testImplementation deps['org.junit.platform:junit-platform-suite']
testImplementation deps['org.mockito:mockito-core']
testImplementation deps['org.mockito:mockito-junit-jupiter']
@@ -299,9 +288,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 +357,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.
@@ -764,10 +695,7 @@ task fragileTest(type: FilteringTest) {
// Dedicated test suite for schema-dependent tests.
task sqlIntegrationTest(type: FilteringTest) {
// TestSuite still requires a JUnit 4 runner, which knows how to handle JUnit 5 tests.
// Here we need to override parent's choice of JUnit 5. If changing this, remember to
// change :integration:sqlIntegrationTest too.
useJUnit()
useJUnitPlatform()
excludeTestCases = false
tests = ['google/registry/schema/integration/SqlIntegrationTestSuite.*']
@@ -814,13 +742,9 @@ test {
// Don't run any tests from this task, all testing gets done in the
// FilteringTest tasks.
exclude "**"
// TODO(weiminyu): Remove dependency on sqlIntegrationTest
}.dependsOn(fragileTest, standardTest, registryToolIntegrationTest, sqlIntegrationTest)
}.dependsOn(standardTest, registryToolIntegrationTest, sqlIntegrationTest)
// When we override tests, we also break the cleanTest command.
cleanTest.dependsOn(cleanFragileTest, cleanStandardTest,
cleanRegistryToolIntegrationTest, cleanSqlIntegrationTest)
cleanTest.dependsOn(cleanStandardTest, cleanRegistryToolIntegrationTest, cleanSqlIntegrationTest)
project.build.dependsOn devtool
project.build.dependsOn buildToolImage
project.build.dependsOn ':stage'

View File

@@ -1,18 +1,17 @@
# 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
com.fasterxml.jackson.core:jackson-databind:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.20.2=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.20.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.woodstox:woodstox-core:7.0.0=testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-joda:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.jackson:jackson-bom:2.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.fasterxml.woodstox:woodstox-core:7.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.2.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.docker-java:docker-java-api:3.4.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -27,6 +26,7 @@ com.github.jnr:jnr-posix:3.1.22=compileClasspath,deploy_jar,nonprodCompileClassp
com.github.jnr:jnr-unixsocket:0.38.25=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.jnr:jnr-x86asm:1.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,nonprodAnnotationProcessor,testAnnotationProcessor
com.github.mwiede:jsch:2.28.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.android:annotations:4.1.1.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-jackson2:2.0.1=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api-client:google-api-client-jackson2:2.7.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
@@ -34,60 +34,64 @@ com.google.api-client:google-api-client-java6:2.1.4=compileClasspath,deploy_jar,
com.google.api-client:google-api-client-servlet:2.7.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api-client:google-api-client-servlet:2.9.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api-client:google-api-client:2.9.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.68.0=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.64.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:gapic-google-cloud-storage-v2:2.68.0=testCompileClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1:3.24.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta1:0.196.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigquerystorage-v1beta2:0.196.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-bigtable-v2:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-pubsub-v1:1.130.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-pubsub-v1:1.132.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-database-v1:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-admin-instance-v1:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-spanner-v1:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-control-v2:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.68.0=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-common-protos:2.65.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.193.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.64.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:grpc-google-cloud-storage-v2:2.68.0=testCompileClasspath
com.google.api.grpc:grpc-google-common-protos:2.67.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1:3.24.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1alpha:3.24.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta1:0.196.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta2:0.196.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigquerystorage-v1beta:3.24.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-admin-v2:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-v2:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-compute-v1:1.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-datastore-v1:0.125.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-firestore-v1:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-bigtable-v2:2.75.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-compute-v1:1.102.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-datastore-v1:0.128.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-firestore-v1:3.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-logging-v2:0.118.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-monitoring-v3:3.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.130.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-v1:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-pubsub-v1:1.132.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1:2.92.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta1:2.92.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-secretmanager-v1beta2:2.92.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-database-v1:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-admin-instance-v1:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-spanner-v1:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-control-v2:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.64.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-cloud-storage-v2:2.68.0=testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-common-protos:2.60.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.60.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.66.0=testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.57.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.api:api-common:2.63.0=testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.74.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
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.api.grpc:proto-google-cloud-tasks-v2:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2:2.92.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.141.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta2:0.182.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.141.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-cloud-tasks-v2beta3:0.182.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.api.grpc:proto-google-common-protos:2.71.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.62.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
com.google.api.grpc:proto-google-iam-v1:1.66.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:api-common:2.63.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-grpc:2.80.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax-httpjson:2.80.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.api:gax:2.80.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-admin-directory:directory_v1-rev20260522-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-rev20260525-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
@@ -95,13 +99,10 @@ com.google.apis:google-api-services-iamcredentials:v1-rev20211203-2.0.0=compileC
com.google.apis:google-api-services-monitoring:v3-rev20260129-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-pubsub:v1-rev20220904-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sheets:v4-rev20260213-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20260317-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20251118-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.apis:google-api-services-storage:v1-rev20260204-2.0.0=testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-credentials:1.46.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.auth:google-auth-library-credentials:1.47.0=testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.46.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.47.0=testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-sqladmin:v1beta4-rev20260510-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.apis:google-api-services-storage:v1-rev20260204-2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-credentials:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auth:google-auth-library-oauth2-http:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service-annotations:1.0.1=nonprodAnnotationProcessor,testAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.1.1=annotationProcessor,compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.auto.service:auto-service:1.1.1=annotationProcessor
@@ -112,78 +113,70 @@ com.google.auto:auto-common:1.2.2=annotationProcessor,nonprodAnnotationProcessor
com.google.cloud.bigdataoss:gcsio:2.2.26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.bigdataoss:util:2.2.26=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.bigtable:bigtable-client-core-config:1.28.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.datastore:datastore-v1-proto-client:2.34.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.datastore:datastore-v1-proto-client:2.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:detector-resources-support:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:exporter-metrics:0.33.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.opentelemetry:shared-resourcemapping:0.33.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.28.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.28.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigquerystorage:3.21.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:jdbc-socket-factory-core:1.28.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud.sql:postgres-socket-factory:1.28.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigquerystorage:3.24.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-bigtable:2.73.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-compute:1.82.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.64.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-compute:1.102.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-grpc:2.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core-grpc:2.70.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.54.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core-http:2.70.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-firestore:3.37.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-core-http:2.70.0=testCompileClasspath
com.google.cloud:google-cloud-core:2.66.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.cloud:google-cloud-core:2.70.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-firestore:3.39.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-logging:3.29.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-monitoring:3.85.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.132.0=testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-pubsub:1.148.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-spanner:6.111.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.127.24=testRuntimeClasspath
com.google.cloud:google-cloud-nio:0.132.0=testCompileClasspath
com.google.cloud:google-cloud-pubsub:1.150.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-secretmanager:2.92.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
com.google.cloud:google-cloud-spanner:6.113.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage-control:2.44.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.64.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-storage:2.68.0=testCompileClasspath
com.google.cloud:google-cloud-tasks:2.51.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.google.cloud:google-cloud-tasks:2.92.0=compileClasspath,nonprodCompileClasspath,testCompileClasspath
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.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.cloud:proto-google-cloud-firestore-bundle-v1:3.39.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,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.code.gson:gson:2.14.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,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
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=annotationProcessor,checkstyle,compileClasspath,deploy_jar,nonprodAnnotationProcessor,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testAnnotationProcessor,testCompileClasspath,testRuntimeClasspath
com.google.gwt:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:2.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.http-client:google-http-client-apache-v2:2.1.0=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:1.46.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.http-client:google-http-client-appengine:2.1.0=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-apache-v2:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-appengine:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-gson:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:1.46.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
com.google.http-client:google-http-client-jackson2:2.1.0=testCompileClasspath,testRuntimeClasspath
com.google.http-client:google-http-client-jackson2:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,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
com.google.monitoring-client:stackdriver:1.0.7=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -194,25 +187,22 @@ com.google.oauth-client:google-oauth-client-jetty:1.39.0=compileClasspath,nonpro
com.google.oauth-client:google-oauth-client-servlet:1.36.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
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.33.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,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.jcraft:jsch:0.1.55=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.ibm.icu:icu4j:73.2=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
com.squareup.okhttp3:okhttp:4.12.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okhttp3:okhttp-jvm:5.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okhttp3:okhttp:5.3.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio-bom:3.0.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.squareup.okio:okio-fakefilesystem-jvm:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio-fakefilesystem:3.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio-jvm:3.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio:3.6.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio-jvm:3.16.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.okio:okio:3.16.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.wire:wire-compiler:4.5.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.squareup.wire:wire-grpc-server-generator:4.5.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.squareup.wire:wire-grpc-server:4.5.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
@@ -229,8 +219,8 @@ com.squareup:kotlinpoet:1.11.0=annotationProcessor,testAnnotationProcessor
com.squareup:kotlinpoet:1.15.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
com.sun.istack:istack-commons-runtime:4.1.2=deploy_jar,jaxb,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
com.sun.istack:istack-commons-tools:4.1.2=jaxb
com.sun.xml.bind.external:relaxng-datatype:4.0.8=jaxb
com.sun.xml.bind.external:rngom:4.0.8=jaxb
com.sun.xml.bind.external:relaxng-datatype:4.0.9=jaxb
com.sun.xml.bind.external:rngom:4.0.9=jaxb
com.sun.xml.dtd-parser:dtd-parser:1.5.1=jaxb
com.zaxxer:HikariCP:7.0.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-beanutils:commons-beanutils:1.10.1=checkstyle
@@ -239,7 +229,7 @@ commons-codec:commons-codec:1.19.0=compileClasspath,deploy_jar,nonprodCompileCla
commons-collections:commons-collections:3.2.2=checkstyle
commons-io:commons-io:2.20.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
commons-logging:commons-logging:1.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
dnsjava:dnsjava:3.6.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
dnsjava:dnsjava:3.6.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
guru.nidi.com.eclipsesource.j2v8:j2v8_linux_x86_64:4.6.0=testRuntimeClasspath
guru.nidi.com.eclipsesource.j2v8:j2v8_macosx_x86_64:4.6.0=testRuntimeClasspath
guru.nidi.com.eclipsesource.j2v8:j2v8_win32_x86:4.6.0=testRuntimeClasspath
@@ -252,44 +242,33 @@ 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
io.grpc:grpc-api:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-api:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-auth:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-auth:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-census:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-context:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-context:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-core:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-googleapis:1.76.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-googleapis:1.81.0=testRuntimeClasspath
io.grpc:grpc-grpclb:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-alts:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-api:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-auth:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-census:1.76.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-context:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-core:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-googleapis:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-grpclb:1.76.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-grpclb:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-inprocess:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-inprocess:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-netty-shaded:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-inprocess:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty-shaded:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-netty:1.76.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-opentelemetry:1.76.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-opentelemetry:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf-lite:1.76.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-protobuf-lite:1.81.0=testRuntimeClasspath
io.grpc:grpc-protobuf:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-protobuf:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-rls:1.76.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-protobuf-lite:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-protobuf:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-rls:1.76.3=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-rls:1.81.0=testRuntimeClasspath
io.grpc:grpc-services:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
io.grpc:grpc-services:1.81.0=testRuntimeClasspath
io.grpc:grpc-stub:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.grpc:grpc-stub:1.81.0=testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-util:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
io.grpc:grpc-util:1.81.0=testRuntimeClasspath
io.grpc:grpc-xds:1.76.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath
io.grpc:grpc-xds:1.81.0=testRuntimeClasspath
io.grpc:grpc-services:1.76.3=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-services:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-stub:1.81.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.grpc:grpc-util:1.76.3=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-util:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.grpc:grpc-xds:1.76.3=compileClasspath,nonprodCompileClasspath,testCompileClasspath
io.grpc:grpc-xds:1.81.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.netty:netty-buffer:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http2:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.netty:netty-codec-http:4.1.124.Final=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -314,8 +293,8 @@ io.opencensus:opencensus-exporter-metrics-util:0.31.0=compileClasspath,deploy_ja
io.opencensus:opencensus-exporter-stats-stackdriver:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opencensus:opencensus-impl-core:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opencensus:opencensus-impl:0.31.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.45.0-alpha=testCompileClasspath,testRuntimeClasspath
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.37.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.opentelemetry.contrib:opentelemetry-gcp-resources:1.45.0-alpha=testCompileClasspath
io.opentelemetry.instrumentation:opentelemetry-grpc-1.6:2.1.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator:2.1.0-alpha=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
io.opentelemetry.instrumentation:opentelemetry-instrumentation-api:2.1.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -330,7 +309,7 @@ io.opentelemetry:opentelemetry-exporter-logging:1.62.0=testCompileClasspath,test
io.opentelemetry:opentelemetry-extension-incubator:1.35.0-alpha=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-common:1.62.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.47.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi:1.62.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-extension-autoconfigure:1.62.0=testCompileClasspath,testRuntimeClasspath
io.opentelemetry:opentelemetry-sdk-logs:1.51.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
@@ -351,19 +330,18 @@ 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
jakarta.servlet:jakarta.servlet-api:6.0.0=testCompileClasspath,testRuntimeClasspath
jakarta.servlet:jakarta.servlet-api:6.2.0-M2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
jakarta.transaction:jakarta.transaction-api:2.0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
jakarta.xml.bind:jakarta.xml.bind-api:4.0.4=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
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
junit:junit:4.13.2=nonprodCompileClasspath,nonprodRuntimeClasspath,testCompileClasspath,testRuntimeClasspath
net.arnx:nashorn-promise:0.1.1=testRuntimeClasspath
@@ -383,24 +361,24 @@ org.apache.arrow:arrow-format:17.0.0=compileClasspath,deploy_jar,nonprodCompileC
org.apache.arrow:arrow-memory-core:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.arrow:arrow-vector:17.0.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.avro:avro:1.11.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-fn-execution:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-job-management:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-pipeline:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-fn-execution:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-job-management:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-model-pipeline:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-core-construction-java:2.54.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-core-java:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-direct-java:2.72.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-google-cloud-dataflow-java:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-java-fn-execution:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-core:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-expansion-service:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-arrow:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-avro:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-protobuf:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-core-java:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-direct-java:2.73.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-google-cloud-dataflow-java:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-runners-java-fn-execution:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-core:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-expansion-service:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-arrow:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-avro:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-google-cloud-platform-core:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-extensions-protobuf:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-fn-execution:2.54.0=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-harness:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-transform-service-launcher:2.72.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-harness:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-io-google-cloud-platform:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-sdks-java-transform-service-launcher:2.73.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-vendor-grpc-1_60_1:0.1=testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-vendor-grpc-1_69_0:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.beam:beam-vendor-guava-32_1_2-jre:0.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -410,7 +388,7 @@ org.apache.commons:commons-exec:1.3=testRuntimeClasspath
org.apache.commons:commons-lang3:3.18.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath
org.apache.commons:commons-lang3:3.20.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-lang3:3.8.1=checkstyle
org.apache.commons:commons-pool2:2.12.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-pool2:2.13.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.15.0=testCompileClasspath,testRuntimeClasspath
org.apache.commons:commons-text:1.3=checkstyle
org.apache.ftpserver:ftplet-api:1.2.1=testCompileClasspath,testRuntimeClasspath
@@ -427,10 +405,10 @@ org.apache.maven.doxia:doxia-logging-api:1.12.0=checkstyle
org.apache.maven.doxia:doxia-module-xdoc:1.12.0=checkstyle
org.apache.maven.doxia:doxia-sink-api:1.12.0=checkstyle
org.apache.mina:mina-core:2.2.4=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-common:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-core:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-scp:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-sftp:3.0.0-M3=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-common:3.0.0-M4=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-core:3.0.0-M4=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-scp:3.0.0-M4=testCompileClasspath,testRuntimeClasspath
org.apache.sshd:sshd-sftp:3.0.0-M4=testCompileClasspath,testRuntimeClasspath
org.apache.tomcat:tomcat-annotations-api:11.0.22=testCompileClasspath,testRuntimeClasspath
org.apache.xbean:xbean-reflect:3.7=checkstyle
org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath
@@ -438,19 +416,17 @@ 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
org.codehaus.mojo:animal-sniffer-annotations:1.27=testCompileClasspath,testRuntimeClasspath
org.codehaus.mojo:animal-sniffer-annotations:1.27=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
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.codehaus.woodstox:stax2-api:4.2.2=testCompileClasspath,testRuntimeClasspath
org.codehaus.woodstox:stax2-api:4.2.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.conscrypt:conscrypt-openjdk-uber:2.5.2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.eclipse.angus:angus-activation:2.0.3=jaxb
org.eclipse.angus:angus-activation:2.1.0-M1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
@@ -465,18 +441,18 @@ 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.glassfish.jaxb:codemodel:4.0.8=jaxb
org.flywaydb:flyway-core:12.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.flywaydb:flyway-database-postgresql:12.7.0=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.9=jaxb
org.glassfish.jaxb:jaxb-core:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.glassfish.jaxb:jaxb-core:4.0.8=jaxb
org.glassfish.jaxb:jaxb-core:4.0.9=jaxb
org.glassfish.jaxb:jaxb-runtime:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.glassfish.jaxb:jaxb-runtime:4.0.8=jaxb
org.glassfish.jaxb:jaxb-xjc:4.0.8=jaxb
org.glassfish.jaxb:jaxb-runtime:4.0.9=jaxb
org.glassfish.jaxb:jaxb-xjc:4.0.9=jaxb
org.glassfish.jaxb:txw2:4.0.6=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.glassfish.jaxb:txw2:4.0.8=jaxb
org.glassfish.jaxb:xsom:4.0.8=jaxb
org.gwtproject:gwt-user:2.10.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.glassfish.jaxb:txw2:4.0.9=jaxb
org.glassfish.jaxb:xsom:4.0.9=jaxb
org.hamcrest:hamcrest-core:1.3=nonprodCompileClasspath,nonprodRuntimeClasspath
org.hamcrest:hamcrest-core:3.0=testCompileClasspath,testRuntimeClasspath
org.hamcrest:hamcrest-library:3.0=testCompileClasspath,testRuntimeClasspath
@@ -492,18 +468,18 @@ org.jacoco:org.jacoco.core:0.8.14=jacocoAnt
org.jacoco:org.jacoco.report:0.8.14=jacocoAnt
org.javassist:javassist:3.28.0-GA=checkstyle
org.jboss.logging:jboss-logging:3.6.3.Final=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jcommander:jcommander:2.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jcommander:jcommander:3.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-bom:1.4.0=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-metadata-jvm:2.2.20=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-reflect:1.6.10=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-reflect:1.9.20=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.20=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:2.2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.0=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.0=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.9.20=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:2.2.20=annotationProcessor,testAnnotationProcessor
org.jetbrains.kotlin:kotlin-stdlib:2.2.21=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.4.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -512,40 +488,36 @@ org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.0.1=deploy_jar,nonprodRun
org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
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.jline:jline:4.1.3=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.json:json:20260522=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
org.junit-pioneer:junit-pioneer:2.3.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-migrationsupport:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:5.13.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:1.14.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:1.14.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:1.14.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.13.3=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:1.14.4=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-engine:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-migrationsupport:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-params:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-commons:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-engine:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-launcher:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-runner:1.14.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-api:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-commons:1.14.4=testRuntimeClasspath
org.junit:junit-bom:5.14.4=testCompileClasspath,testRuntimeClasspath
org.junit.platform:junit-platform-suite-engine:6.1.0=testRuntimeClasspath
org.junit.platform:junit-platform-suite:6.1.0=testCompileClasspath,testRuntimeClasspath
org.junit:junit-bom:6.1.0=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-core:5.23.0=testCompileClasspath,testRuntimeClasspath
org.mockito:mockito-junit-jupiter:5.23.0=testCompileClasspath,testRuntimeClasspath
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
@@ -572,7 +544,7 @@ org.seleniumhq.selenium:selenium-safari-driver:4.44.0=testCompileClasspath,testR
org.seleniumhq.selenium:selenium-support:4.44.0=testCompileClasspath,testRuntimeClasspath
org.slf4j:jcl-over-slf4j:1.7.36=testCompileClasspath,testRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.30=testRuntimeClasspath
org.slf4j:slf4j-api:2.0.17=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-api:2.0.18=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.slf4j:slf4j-jdk14:2.0.17=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.snakeyaml:snakeyaml-engine:2.1=deploy_jar,nonprodRuntimeClasspath,runtimeClasspath,testRuntimeClasspath
org.testcontainers:database-commons:1.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
@@ -582,13 +554,12 @@ org.testcontainers:postgresql:1.21.4=compileClasspath,deploy_jar,nonprodCompileC
org.testcontainers:selenium:1.21.4=testCompileClasspath,testRuntimeClasspath
org.testcontainers:testcontainers:1.21.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.threeten:threetenbp:1.7.0=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.w3c.css:sac:1.3=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.webjars.npm:viz.js-graphviz-java:2.1.3=testRuntimeClasspath
org.xerial.snappy:snappy-java:1.1.10.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.xmlresolver:xmlresolver:5.2.2=checkstyle
org.yaml:snakeyaml:2.4=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
org.yaml:snakeyaml:2.5=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
redis.clients.authentication:redis-authx-core:0.1.1-beta2=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
redis.clients:jedis:7.4.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
redis.clients:jedis:8.0.0-beta1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
tools.jackson.core:jackson-core:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
tools.jackson.core:jackson-databind:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
tools.jackson:jackson-bom:3.1.1=compileClasspath,deploy_jar,nonprodCompileClasspath,nonprodRuntimeClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath

View File

@@ -68,6 +68,8 @@ public class BsaCredential {
private final Keyring keyring;
private final Gson gson;
private final Clock clock;
@Nullable private String authToken;
@@ -79,11 +81,13 @@ public class BsaCredential {
@Config("bsaAuthUrl") String authUrl,
@Config("bsaAuthTokenExpiry") Duration authTokenExpiry,
Keyring keyring,
Gson gson,
Clock clock) {
this.urlConnectionService = urlConnectionService;
this.authUrl = authUrl;
this.authTokenExpiry = authTokenExpiry;
this.keyring = keyring;
this.gson = gson;
this.clock = clock;
}
@@ -143,8 +147,7 @@ public class BsaCredential {
// TODO: catch json syntax exception
@SuppressWarnings("unchecked")
String idToken =
new Gson()
.fromJson(new String(getResponseBytes(connection), UTF_8), Map.class)
gson.fromJson(new String(getResponseBytes(connection), UTF_8), Map.class)
.getOrDefault(ID_TOKEN, "")
.toString();
if (idToken.isEmpty()) {

View File

@@ -57,6 +57,9 @@ public final class DomainFlowTmchUtils {
public SignedMark verifySignedMarks(
ImmutableList<AbstractSignedMark> signedMarks, String domainLabel, Instant now)
throws EppException {
if (signedMarks.isEmpty()) {
throw new SignedMarksListEmptyException();
}
if (signedMarks.size() > 1) {
throw new TooManySignedMarksException();
}
@@ -77,21 +80,21 @@ public final class DomainFlowTmchUtils {
public SignedMark verifyEncodedSignedMark(EncodedSignedMark encodedSignedMark, Instant now)
throws EppException {
if (!encodedSignedMark.getEncoding().equals("base64")) {
if (!"base64".equals(encodedSignedMark.getEncoding())) {
throw new Base64RequiredForEncodedSignedMarksException();
}
byte[] signedMarkData;
try {
signedMarkData = encodedSignedMark.getBytes();
} catch (IllegalStateException e) {
throw new SignedMarkEncodingErrorException();
throw new SignedMarkEncodingErrorException(e);
}
SignedMark signedMark;
try {
signedMark = unmarshalEpp(SignedMark.class, signedMarkData);
} catch (EppException e) {
throw new SignedMarkParsingErrorException();
throw new SignedMarkParsingErrorException(e);
}
if (SignedMarkRevocationList.get().isSmdRevoked(signedMark.getId(), now)) {
@@ -101,22 +104,22 @@ public final class DomainFlowTmchUtils {
try {
tmchXmlSignature.verify(signedMarkData);
} catch (CertificateExpiredException e) {
throw new SignedMarkCertificateExpiredException();
throw new SignedMarkCertificateExpiredException(e);
} catch (CertificateNotYetValidException e) {
throw new SignedMarkCertificateNotYetValidException();
throw new SignedMarkCertificateNotYetValidException(e);
} catch (CertificateRevokedException e) {
throw new SignedMarkCertificateRevokedException();
throw new SignedMarkCertificateRevokedException(e);
} catch (CertificateSignatureException e) {
throw new SignedMarkCertificateSignatureException();
throw new SignedMarkCertificateSignatureException(e);
} catch (SignatureException | XMLSignatureException e) {
throw new SignedMarkSignatureException();
throw new SignedMarkSignatureException(e);
} catch (GeneralSecurityException e) {
throw new SignedMarkCertificateInvalidException();
throw new SignedMarkCertificateInvalidException(e);
} catch (IOException
| MarshalException
| SAXException
| ParserConfigurationException e) {
throw new SignedMarkParsingErrorException();
throw new SignedMarkParsingErrorException(e);
}
if (now.isBefore(signedMark.getCreationTime())) {
@@ -181,6 +184,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkCertificateRevokedException() {
super("Signed mark certificate was revoked");
}
public SignedMarkCertificateRevokedException(Throwable cause) {
this();
initCause(cause);
}
}
/** Certificate used in signed mark signature has expired. */
@@ -189,6 +197,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkCertificateNotYetValidException() {
super("Signed mark certificate not yet valid");
}
public SignedMarkCertificateNotYetValidException(Throwable cause) {
this();
initCause(cause);
}
}
/** Certificate used in signed mark signature has expired. */
@@ -196,6 +209,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkCertificateExpiredException() {
super("Signed mark certificate has expired");
}
public SignedMarkCertificateExpiredException(Throwable cause) {
this();
initCause(cause);
}
}
/** Certificate parsing error, or possibly a bad provider or algorithm. */
@@ -203,6 +221,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkCertificateInvalidException() {
super("Signed mark certificate is invalid");
}
public SignedMarkCertificateInvalidException(Throwable cause) {
this();
initCause(cause);
}
}
/** Invalid signature on a signed mark. */
@@ -210,6 +233,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkCertificateSignatureException() {
super("Signed mark certificate not signed by ICANN");
}
public SignedMarkCertificateSignatureException(Throwable cause) {
this();
initCause(cause);
}
}
/** Invalid signature on a signed mark. */
@@ -217,6 +245,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkSignatureException() {
super("Signed mark signature is invalid");
}
public SignedMarkSignatureException(Throwable cause) {
this();
initCause(cause);
}
}
/** Signed marks must be encoded. */
@@ -226,6 +259,13 @@ public final class DomainFlowTmchUtils {
}
}
/** Signed marks list cannot be empty. */
static class SignedMarksListEmptyException extends RequiredParameterMissingException {
public SignedMarksListEmptyException() {
super("Signed marks list cannot be empty");
}
}
/** Only one signed mark is allowed per application. */
static class TooManySignedMarksException extends ParameterValuePolicyErrorException {
public TooManySignedMarksException() {
@@ -245,6 +285,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkParsingErrorException() {
super("Error while parsing encoded signed mark data");
}
public SignedMarkParsingErrorException(Throwable cause) {
this();
initCause(cause);
}
}
/** Signed mark data is improperly encoded. */
@@ -252,6 +297,11 @@ public final class DomainFlowTmchUtils {
public SignedMarkEncodingErrorException() {
super("Signed mark data is improperly encoded");
}
public SignedMarkEncodingErrorException(Throwable cause) {
this();
initCause(cause);
}
}
}

View File

@@ -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> domain,
@Nullable CurrencyUnit topLevelCurrency,
Instant currentDate,
Instant now,
DomainPricingLogic pricingLogic,
Optional<AllocationToken> 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<FeeType> 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 {
*
* <p>{@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.
*
* <p>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.
* <p>The steps are as follows:
*
* <ol>
* <li>Find all HistoryEntries under the domain modified in the past, up to the maxSearchPeriod.
* <li>Only keep HistoryEntries with a DomainTransactionRecord that a) hasn't been reported yet
* and b) matches the predicate
* <li>Return the transactionRecords under the most recent HistoryEntry that fits the above
* criteria, with negated reportAmounts.
* </ol>
*
* @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<DomainTransactionRecord> 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) {

View File

@@ -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<FeeRenewCommandExtension> feeRenew =
eppInput.getSingleExtension(FeeRenewCommandExtension.class);

View File

@@ -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<SecDnsUpdateExtension> 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<DomainUpdateSuperuserExtension> superuserExt =

View File

@@ -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<Domain> 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(

View File

@@ -106,19 +106,24 @@ public abstract class ImmutableObject implements Cloneable {
return hashCode;
}
/** Returns a clone of the given object. */
@SuppressWarnings("unchecked")
protected static <T extends ImmutableObject> 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 extends ImmutableObject> T clone(T t) {
return (T) t.clone();
}
/** Returns a clone of the given object with empty fields set to null. */
protected static <T extends ImmutableObject> 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)

View File

@@ -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 <a href="http://tools.ietf.org/html/rfc5731">RFC5731</a>. */
/** The fields on "chgType" from <a href="https://tools.ietf.org/html/rfc5731">RFC5731</a>. */
@XmlTransient
public static class DomainCreateOrChange<B extends Domain.Builder> extends ImmutableObject
implements ResourceCreateOrChange<B> {
public abstract static class DomainCreateOrChange<B extends Domain.Builder>
extends ImmutableObject implements ResourceCreateOrChange<B> {
/** 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 <a
* href="http://tools.ietf.org/html/rfc5731">RFC5731</a>.
* href="https://tools.ietf.org/html/rfc5731">RFC5731</a>.
*/
@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<Create> {
public Builder setDomainName(String domainName) {
getInstance().domainName = domainName;
return this;
}
public Builder setPeriod(Period period) {
getInstance().period = period;
return this;
}
public Builder setNameserverHostNames(ImmutableSet<String> nameserverHostNames) {
getInstance().nameserverHostNames =
isNullOrEmpty(nameserverHostNames) ? null : nameserverHostNames;
return this;
}
public Builder setForeignKeyedDesignatedContacts(
ImmutableSet<ForeignKeyedDesignatedContact> 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<Renew> {
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<Update.AddRemove, Domain.Builder, Update.Change>
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.DomainAddRemove, Domain.Builder, Update.Change>
implements CreateOrUpdate<Update> {
@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<Update> {
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<ForeignKeyedDesignatedContact> foreignKeyedDesignatedContacts;
@XmlElement(name = "status")
Set<StatusValue> statusValues;
public boolean isEmpty() {
return isNullOrEmpty(nameserverHostNames)
&& isNullOrEmpty(foreignKeyedDesignatedContacts)
&& isNullOrEmpty(statusValues);
}
@Override
public void setStatusValues(ImmutableSet<StatusValue> statusValues) {
this.statusValues = statusValues;
}
@Override
public ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(statusValues);
}
public ImmutableSet<String> 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<DomainAddRemove> {
public Builder setNameserverHostNames(ImmutableSet<String> nameserverHostNames) {
getInstance().nameserverHostNames =
isNullOrEmpty(nameserverHostNames) ? null : nameserverHostNames;
return this;
}
public Builder setStatusValues(ImmutableSet<StatusValue> 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<Domain.Builder> {
/** Builder for {@link Change}. */
public static class Builder extends Buildable.Builder<Change> {
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<VKey<Host>> linkHosts(Set<String> hostNames, Instant now)
private static ImmutableSet<VKey<Host>> linkHosts(ImmutableSet<String> 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<String, VKey<Host>> loadByForeignKeysCached(
Set<String> foreignKeys, Instant now) throws InvalidReferencesException {
ImmutableSet<String> foreignKeys, Instant now) throws InvalidReferencesException {
ImmutableMap<String, VKey<Host>> 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<String> foreignKeys;
private final Class<?> type;
InvalidReferencesException(Class<?> type, ImmutableSet<String> foreignKeys) {
public InvalidReferencesException(Class<?> type, Set<String> 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<String> getForeignKeys() {

View File

@@ -15,27 +15,45 @@
package google.registry.model.domain.fee;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
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 {
public static final ImmutableSet<String> FEE_EXTENSION_URIS =
ImmutableSet.of(
ServiceExtension.FEE_1_00.getUri(),
ServiceExtension.FEE_0_12.getUri(),
ServiceExtension.FEE_0_11.getUri(),
ServiceExtension.FEE_0_6.getUri());
@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) {
checkArgumentNotNull(type, "Must specify the type of the fee");
return createWithCustomDescription(
cost, type, isPremium, type.renderDescription(descriptionArgs));
checkArgumentNotNull(cost, "Cost cannot be null");
checkArgument(cost.signum() >= 0, "Cost must be a non-negative number");
Fee instance = new Fee();
instance.cost = cost;
instance.type = type;
instance.isPremium = isPremium;
instance.description = type.renderDescription(descriptionArgs);
return instance;
}
/** Creates a Fee for the given cost, type, and valid date range with the default description. */
@@ -50,22 +68,37 @@ public class Fee extends BaseFee {
return instance;
}
/** Creates a Fee for the given cost and type with a custom description. */
private static Fee createWithCustomDescription(
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");
instance.type = checkNotNull(type);
instance.isPremium = isPremium;
instance.description = description;
return instance;
}
/** Builder for {@link Fee}. */
public static class Builder extends Buildable.Builder<Fee> {
public static final ImmutableSet<String> FEE_EXTENSION_URIS =
ImmutableSet.of(
ServiceExtension.FEE_1_00.getUri(),
ServiceExtension.FEE_0_12.getUri(),
ServiceExtension.FEE_0_11.getUri(),
ServiceExtension.FEE_0_6.getUri());
/** 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;
}
}
}

View File

@@ -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.
*

View File

@@ -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
* <p>This is a list because a single operation can involve multiple fees.
*/
@XmlElement(name = "fee")
List<Fee> fees;
public List<Fee> fees;
public CurrencyUnit getCurrency() {
return currency;

View File

@@ -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.
@@ -63,12 +63,12 @@ public class FeeTransformResponseExtension extends ImmutableObject implements Re
}
public Builder setFees(List<Fee> fees) {
getInstance().fees = fees;
getInstance().fees = forceEmptyToNull(nullToEmptyImmutableCopy(fees));
return this;
}
public Builder setCredits(List<Credit> credits) {
getInstance().credits = forceEmptyToNull(credits);
getInstance().credits = forceEmptyToNull(nullToEmptyImmutableCopy(credits));
return this;
}
}

View File

@@ -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() {

View File

@@ -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<FeeCheckCommandExtensionItemV06> items;
public void setItems(ImmutableList<FeeCheckCommandExtensionItemV06> items) {
this.items = items;
}
@Override
public CurrencyUnit getCurrency() {
return null; // This version of the fee extension doesn't specify a top-level currency.

View File

@@ -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<Credit> getCredits() {
return ImmutableList.of();
}
/** Builder for {@link FeeCreateCommandExtensionV06}. */
public static class Builder extends Buildable.Builder<FeeCreateCommandExtensionV06> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
}
}

View File

@@ -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<Credit> getCredits() {
return ImmutableList.of();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeCreateResponseExtensionV06 extends FeeTransformResponseExtension {}

View File

@@ -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"})

View File

@@ -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<Credit> getCredits() {
return ImmutableList.of();
}

View File

@@ -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<Credit> getCredits() {
return super.getCredits();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeRenewResponseExtensionV06 extends FeeTransformResponseExtension {}

View File

@@ -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<Credit> getCredits() {
return ImmutableList.of();
}

View File

@@ -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<Credit> getCredits() {
return super.getCredits();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeTransferResponseExtensionV06 extends FeeTransformResponseExtension {}

View File

@@ -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<Credit> getCredits() {
return ImmutableList.of();
}

View File

@@ -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<Credit> getCredits() {
return super.getCredits();
}
}
@XmlType(propOrder = {"currency", "fees", "credits"})
public class FeeUpdateResponseExtensionV06 extends FeeTransformResponseExtension {}

View File

@@ -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<FeeCreateCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}

View File

@@ -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<Credit> 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<FeeRenewCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}

View File

@@ -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<FeeTransferCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}

View File

@@ -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<FeeUpdateCommandExtensionV12> {
public Builder setCurrency(CurrencyUnit currency) {
getInstance().currency = currency;
return this;
}
public Builder setFees(ImmutableList<Fee> fees) {
getInstance().fees = fees;
return this;
}
public Builder setCredits(ImmutableList<Credit> credits) {
getInstance().credits = credits;
return this;
}
}
}

View File

@@ -14,13 +14,12 @@
package google.registry.model.domain.feestdv1;
import static google.registry.util.CollectionUtils.forceEmptyToNull;
import com.google.common.collect.ImmutableList;
import google.registry.model.domain.Period;
import google.registry.model.domain.fee.Fee;
import google.registry.model.domain.fee.FeeCheckResponseExtensionItem;
import google.registry.model.domain.fee.FeeQueryCommandExtensionItem.CommandName;
import jakarta.xml.bind.annotation.XmlTransient;
import jakarta.xml.bind.annotation.XmlType;
/** The version 1.0 response for a domain check on a single resource. */
@@ -38,6 +37,7 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
* doesn't support "period".
*/
@Override
@XmlTransient
public Period getPeriod() {
return super.getPeriod();
}
@@ -47,6 +47,7 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
* doesn't support "fee".
*/
@Override
@XmlTransient
public ImmutableList<Fee> getFees() {
return super.getFees();
}
@@ -74,7 +75,7 @@ public class FeeCheckResponseExtensionItemStdV1 extends FeeCheckResponseExtensio
@Override
public Builder setFees(ImmutableList<Fee> fees) {
commandBuilder.setFee(forceEmptyToNull(ImmutableList.copyOf(fees)));
commandBuilder.setFee(fees);
return this;
}

View File

@@ -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.
*
* <p>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:
*
* <pre> {@code
* <epp>
* <command>
* <create>
* <!-- domain check XML data -->
* </create>
* <extension>
* <launch:check>
* <!-- launch check XML payload data -->
* </launch:check>
* </extension>
* </command>
* </epp>
* } </pre>
* <pre>{@code
* <epp>
* <command>
* <create>
* <!-- domain check XML data -->
* </create>
* <extension>
* <launch:check>
* <!-- launch check XML payload data -->
* </launch:check>
* </extension>
* </command>
* </epp>
* }</pre>
*
* @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);
}

View File

@@ -20,6 +20,7 @@ import google.registry.model.ImmutableObject;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlValue;
import java.util.Objects;
import javax.annotation.Nullable;
/**
* The launch phase of the TLD being addressed by this command.
@@ -46,7 +47,7 @@ import java.util.Objects;
* sets it is the one that needs to make sure the domain isn't a trademark and that the fields are
* correct.
*/
public class LaunchPhase extends ImmutableObject {
public final class LaunchPhase extends ImmutableObject {
/**
* The phase during which trademark holders can submit domain registrations with trademark
@@ -70,6 +71,9 @@ public class LaunchPhase extends ImmutableObject {
return instance;
}
/** Private no-arg constructor required for JAXB and to enforce immutability elsewhere. */
private LaunchPhase() {}
@XmlValue String phase;
/**
@@ -79,6 +83,7 @@ public class LaunchPhase extends ImmutableObject {
* <p>This is currently unused, but is retained so that incoming XMLs that include a subphase can
* have it be reflected back.
*/
@Nullable
@XmlAttribute(name = "name")
String subphase;

View File

@@ -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 <a href="https://www.google.com/search?q=EPP+metadata+extension">EPP Metadata Extension</a>
*/
@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<MetadataExtension> {
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;
}
}
}

View File

@@ -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;

View File

@@ -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
* <p>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<DomainDsData> dsData;
public Long getMaxSigLife() {
@@ -43,6 +46,19 @@ public class SecDnsCreateExtension extends ImmutableObject implements CommandExt
}
public ImmutableSet<DomainDsData> getDsData() {
return nullSafeImmutableCopy(dsData);
return nullToEmptyImmutableCopy(dsData);
}
/** Builder for {@link SecDnsCreateExtension}. */
public static class Builder extends Buildable.Builder<SecDnsCreateExtension> {
public Builder setDsData(ImmutableSet<DomainDsData> dsData) {
getInstance().dsData = dsData;
return this;
}
public Builder setMaxSigLife(Long maxSigLife) {
getInstance().maxSigLife = maxSigLife;
return this;
}
}
}

View File

@@ -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<SecDnsUpdateExtension> {
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<T extends AddRemoveBase, B extends Builder<T, B>>
extends Buildable.Builder<T> {
public abstract B setDsData(ImmutableSet<DomainDsData> 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<DomainDsData> dsData;
public ImmutableSet<DomainDsData> 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<Add, Builder> {
@Override
public Builder setDsData(ImmutableSet<DomainDsData> 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<DomainDsData> dsData;
public Boolean getAll() {
return all;
}
public ImmutableSet<DomainDsData> getDsData() {
return nullToEmptyImmutableCopy(dsData);
}
/** Builder for {@link Remove}. */
public static class Builder extends AddRemoveBase.Builder<Remove, Builder> {
public Builder setAll(Boolean all) {
getInstance().all = all;
return this;
}
@Override
public Builder setDsData(ImmutableSet<DomainDsData> 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
* <p>We do not support expirations, but we need this field to be able to return appropriate
* errors.
*/
@XmlElement(name = "maxSigLife")
Long maxSigLife;
}
}

View File

@@ -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;
}

View File

@@ -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<Boolean> getAutorenews() {
return Optional.ofNullable(isNullOrEmpty(autorenews) ? null : Boolean.valueOf(autorenews));
return Optional.ofNullable(autorenews);
}
}

View File

@@ -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;
}

View File

@@ -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<DomainDsData> 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<DomainDsData> add, ImmutableSet<DomainDsData> 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<String> domainNames) {
return feeCheckCreateV06(domainNames, 1);
}
/** Returns a fee check extension for domain creations (V06) with a specific period. */
public static FeeCheckCommandExtensionV06 feeCheckCreateV06(
ImmutableList<String> domainNames, int years) {
FeeCheckCommandExtensionV06 feeCheck = new FeeCheckCommandExtensionV06();
ImmutableList.Builder<FeeCheckCommandExtensionItemV06> 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);
}
}

View File

@@ -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<String> 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<String> 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<String> 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 <E extends CommandExtension> Optional<E> getSingleExtension(Class<E> 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<CommandExtension> 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<EppInput> {
public Builder setCommandWrapper(CommandWrapper commandWrapper) {
getInstance().commandWrapper = commandWrapper;
return this;
}
}
/** A tag that goes inside an EPP {@literal <command>}. */
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<Create> {
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<Update> {
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<CommandExtension> extension;
@Nullable String clTRID;
@XmlElement(name = "clTRID")
@Nullable
String clTrid;
/**
* Returns the client transaction ID.
@@ -384,7 +513,7 @@ public class EppInput extends ImmutableObject {
* <p>This is optional (i.e. it may not be specified) per RFC 5730.
*/
public Optional<String> getClTrid() {
return Optional.ofNullable(clTRID);
return Optional.ofNullable(clTrid);
}
public InnerCommand getCommand() {
@@ -394,12 +523,34 @@ public class EppInput extends ImmutableObject {
public ImmutableList<CommandExtension> getExtensions() {
return nullToEmptyImmutableCopy(extension);
}
/** Builder for {@link CommandWrapper}. */
public static class Builder extends Buildable.Builder<CommandWrapper> {
public Builder setCommand(InnerCommand command) {
getInstance().command = command;
return this;
}
public Builder setExtensions(ImmutableList<CommandExtension> 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<String> 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 <a href="http://tools.ietf.org/html/rfc5730#page-41">
* RFC 5730 - EPP - Command error responses</a>
* @see <a href="http://tools.ietf.org/html/rfc5730#page-41">RFC 5730 - EPP - Command error
* responses</a>
*/
public static class VersionAdapter extends XmlAdapter<String, String> {
public static class VersionAdapter extends XmlAdapter<String, String> {
@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;
}
}

View File

@@ -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}.
*
* <p>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<String> targetUniqueIds;
@XmlAccessorType(XmlAccessType.FIELD)
public class ResourceCheck extends ImmutableObject implements ResourceCommand {
@XmlElements({@XmlElement(name = "id"), @XmlElement(name = "name")})
public List<String> targetUniqueIds;
public void setTargetIds(ImmutableList<String> targetUniqueIds) {
this.targetUniqueIds = targetUniqueIds;
}
public ImmutableList<String> 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<B extends Buildable.Builder<?>> {}
public interface ResourceCreateOrChange<B extends Buildable.Builder<?>> {}
/**
* An update command for an {@link EppResource}.
@@ -92,21 +103,19 @@ public interface ResourceCommand {
* @param <C> 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<B>>
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<StatusValue> statusValues;
public abstract void setStatusValues(ImmutableSet<StatusValue> statusValues);
public ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(statusValues);
}
public abstract ImmutableSet<StatusValue> getStatusValues();
}
protected abstract C getNullableInnerChange();

View File

@@ -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 <a href="http://tools.ietf.org/html/rfc5732">RFC5732</a>. */
/** The fields on "chgType" from <a href="https://tools.ietf.org/html/rfc5732">RFC5732</a>. */
@XmlTransient
abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
@XmlAccessorType(XmlAccessType.FIELD)
public abstract static class HostCreateOrChange extends AbstractSingleResourceCommand
implements ResourceCreateOrChange<Host.Builder> {
@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 <a
* href="http://tools.ietf.org/html/rfc5732">RFC5732</a>.
* href="https://tools.ietf.org/html/rfc5732">RFC5732</a>.
*/
@XmlType(propOrder = {"targetId", "inetAddresses"})
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "inetAddresses"})
@XmlRootElement
public static class Create extends HostCreateOrChange
implements ResourceCreateOrChange<Host.Builder> {
public static class Create extends HostCreateOrChange {
/** IP Addresses for this host. Can be null if this is an external host. */
@XmlElement(name = "addr")
Set<InetAddress> inetAddresses;
public ImmutableSet<InetAddress> getInetAddresses() {
return nullSafeImmutableCopy(inetAddresses);
return nullToEmptyImmutableCopy(inetAddresses);
}
/** Builder for {@link Create}. */
public static class Builder extends Buildable.Builder<Create> {
public Builder setTargetId(String targetId) {
getInstance().setTargetId(targetId);
return this;
}
public Builder setInetAddresses(ImmutableSet<InetAddress> 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<Update.AddRemove, Host.Builder, Update.Change> {
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder = {"name", "innerAdd", "innerRemove", "innerChange"})
public static class Update
extends ResourceUpdate<Update.HostAddRemove, Host.Builder, Update.Change> {
@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<InetAddress> inetAddresses;
@XmlElement(name = "status")
Set<StatusValue> statusValues;
@Override
public void setStatusValues(ImmutableSet<StatusValue> statusValues) {
this.statusValues = statusValues;
}
@Override
public ImmutableSet<StatusValue> getStatusValues() {
return nullToEmptyImmutableCopy(statusValues);
}
public ImmutableSet<InetAddress> getInetAddresses() {
return nullToEmptyImmutableCopy(inetAddresses);
}
/** Builder for {@link HostAddRemove}. */
public static class Builder extends Buildable.Builder<HostAddRemove> {
public Builder setInetAddresses(ImmutableSet<InetAddress> inetAddresses) {
getInstance().inetAddresses = isNullOrEmpty(inetAddresses) ? null : inetAddresses;
return this;
}
public Builder setStatusValues(ImmutableSet<StatusValue> 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 {}
}
}

View File

@@ -49,10 +49,10 @@ public class ServiceMonitoringClient {
tld, MONITORING_STATE_ENDPOINT, Collections.emptyMap(), Collections.emptyMap())) {
ResponseBody responseBody = response.body();
if (responseBody == null) {
if (responseBody == null || responseBody.contentLength() == 0) {
throw new MosApiException(
String.format(
"MoSAPI Service Monitoring API " + "returned an empty body with status: %d",
"MoSAPI Service Monitoring API returned an empty body with status: %d",
response.code()));
}
String bodyString = responseBody.string();

View File

@@ -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.
*
* <p>This calls {@link Spec11EmailUtils#emailSpec11Reports(LocalDate, SoyTemplateInfo, String,
* <p>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<RegistrarThreatMatches> 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<RegistrarThreatMatches> getNewMatches(
ImmutableSet<RegistrarThreatMatches> previousMatchesSet,
ImmutableSet<RegistrarThreatMatches> currentMatchesSet) {
ImmutableMap<String, ImmutableSet<ThreatMatch>> previousMatchesByEmail =
ImmutableMap<String, ImmutableSet<ThreatMatch>> previousMatchesByRegistrarId =
groupByKeyAndFlatMap(previousMatchesSet);
ImmutableMap<String, ImmutableSet<ThreatMatch>> currentMatchesByEmail =
ImmutableMap<String, ImmutableSet<ThreatMatch>> currentMatchesByRegistrarId =
groupByKeyAndFlatMap(currentMatchesSet);
ImmutableSet.Builder<RegistrarThreatMatches> 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<ThreatMatch> difference =
Sets.difference(
currentMatchesByEmail.get(email),
previousMatchesByEmail.getOrDefault(email, ImmutableSet.of()));
ImmutableSet<ThreatMatch> 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<String, ImmutableSet<ThreatMatch>> groupByKeyAndFlatMap(
ImmutableSet<RegistrarThreatMatches> 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())));
}

View File

@@ -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<ThreatMatch> threatMatches) {
static RegistrarThreatMatches create(String clientId, List<ThreatMatch> 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<ThreatMatch> threatMatches) {
/** Creates a new {@link RegistrarThreatMatches} instance. */
static RegistrarThreatMatches create(
String registrarId, ImmutableList<ThreatMatch> threatMatches) {
return new RegistrarThreatMatches(registrarId, threatMatches);
}
}

View File

@@ -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<InternetAddress> 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<RegistrarThreatMatches> registrarThreatMatchesSet) {
ImmutableMap.Builder<RegistrarThreatMatches, Throwable> 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<RegistrarThreatMatches, Throwable> failedMatches = failedMatchesBuilder.build();
if (!failedMatches.isEmpty()) {
ImmutableList<Map.Entry<RegistrarThreatMatches, Throwable>> 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<ThreatMatch> 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<Map<String, String>> threatMatchMap =
LocalDate date, Spec11EmailTemplate template, RegistrarThreatMatches registrarThreatMatches) {
// FreeMarker templates require that data be in raw map/list form or bean-style POJOs.
// We convert the ThreatMatch records to maps here to ensure compatibility and to
// apply email-safe domain name transformations.
ImmutableList<ImmutableMap<String, String>> threatMatchMap =
registrarThreatMatches.threatMatches().stream()
.map(
threatMatch ->
@@ -189,15 +208,14 @@ public class Spec11EmailUtils {
"threatType", threatMatch.threatType()))
.collect(toImmutableList());
Map<String, Object> data =
ImmutableMap<String, Object> data =
ImmutableMap.of(
"date", date.toString(),
"registry", registryName,
"replyToEmail", outgoingEmailAddress.getAddress(),
"threats", threatMatchMap,
"resources", spec11WebResources);
renderer.setData(data);
return renderer.render();
return templateRenderer.render(template.getFtlPath(), data);
}
// Mutates a known bad domain to pass spam checks by Email sender and clients, as suggested by

View File

@@ -18,8 +18,10 @@ import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.gson.Gson;
import dagger.Module;
import dagger.Provides;
import google.registry.tools.GsonUtils;
import jakarta.inject.Singleton;
import java.net.HttpURLConnection;
import javax.net.ssl.HttpsURLConnection;
@@ -49,9 +51,16 @@ public final class Modules {
@Module
public static final class GsonModule {
@Provides
@Singleton
static JsonFactory provideJsonFactory() {
return GsonFactory.getDefaultInstance();
}
@Provides
@Singleton
public static Gson provideGson() {
return GsonUtils.provideGson();
}
}
/** Dagger module that provides standard {@link NetHttpTransport}. */

View File

@@ -44,7 +44,6 @@ import google.registry.request.HttpException.UnsupportedMediaTypeException;
import google.registry.request.auth.AuthResult;
import google.registry.request.lock.LockHandler;
import google.registry.request.lock.LockHandlerImpl;
import google.registry.tools.GsonUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
@@ -74,13 +73,6 @@ public final class RequestModule {
this.authResult = authResult;
}
@RequestScope
@VisibleForTesting
@Provides
public static Gson provideGson() {
return GsonUtils.provideGson();
}
@Provides
@Parameter(RequestParameters.PARAM_TLD)
static String provideTld(HttpServletRequest req) {

View File

@@ -146,20 +146,20 @@ public class AuthModule {
@LocalCredentialJson
public static String provideLocalCredentialJson(
Lazy<GoogleClientSecrets> clientSecrets,
Gson gson,
@StoredCredential Lazy<Credential> credential,
@Nullable @Config("credentialFilePath") String credentialFilePath) {
try {
if (credentialFilePath != null) {
return Files.readString(Paths.get(credentialFilePath));
} else {
return new Gson()
.toJson(
ImmutableMap.<String, String>builder()
.put("type", "authorized_user")
.put("client_id", clientSecrets.get().getDetails().getClientId())
.put("client_secret", clientSecrets.get().getDetails().getClientSecret())
.put("refresh_token", credential.get().getRefreshToken())
.build());
return gson.toJson(
ImmutableMap.<String, String>builder()
.put("type", "authorized_user")
.put("client_id", clientSecrets.get().getDetails().getClientId())
.put("client_secret", clientSecrets.get().getDetails().getClientSecret())
.put("refresh_token", credential.get().getRefreshToken())
.build());
}
} catch (IOException e) {
throw new RuntimeException(e);

View File

@@ -16,10 +16,12 @@ package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.template.soy.data.SoyMapData;
import google.registry.config.RegistryConfig.Config;
import google.registry.tools.soy.DomainCheckClaimsSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.List;
@@ -50,11 +52,15 @@ final class CheckDomainClaimsCommand extends NonMutatingEppToolCommand {
clientId = registryAdminClientId;
}
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
Multimap<String, String> domainNameMap =
validateAndGroupDomainNamesByTld(ImmutableList.copyOf(mainParameters));
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(
DomainCheckClaimsSoyInfo.getInstance(), DomainCheckClaimsSoyInfo.DOMAINCHECKCLAIMS);
addSoyRecord(clientId, new SoyMapData("domainNames", values));
DomainCommand.Check checkCommand = new DomainCommand.Check();
checkCommand.setTargetIds(ImmutableList.copyOf(values));
addEppInput(
clientId,
EppInput.create(EppInput.Check.create(checkCommand), EppExtensions.launchCheckClaims())
.withClTrid("RegistryTool"));
}
}
}

View File

@@ -14,14 +14,14 @@
package google.registry.tools;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Multimap;
import com.google.template.soy.data.SoyMapData;
import google.registry.config.RegistryConfig.Config;
import google.registry.tools.soy.DomainCheckSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import jakarta.inject.Inject;
import java.util.Collection;
import java.util.List;
@@ -57,14 +57,20 @@ final class CheckDomainCommand extends NonMutatingEppToolCommand {
clientId = registryAdminClientId;
}
Multimap<String, String> domainNameMap = validateAndGroupDomainNamesByTld(mainParameters);
Multimap<String, String> domainNameMap =
validateAndGroupDomainNamesByTld(ImmutableList.copyOf(mainParameters));
for (Collection<String> values : domainNameMap.asMap().values()) {
setSoyTemplate(DomainCheckSoyInfo.getInstance(), DomainCheckSoyInfo.DOMAINCHECK);
SoyMapData soyMapData = new SoyMapData("domainNames", values);
if (!isNullOrEmpty(allocationToken)) {
soyMapData.put("allocationToken", allocationToken);
}
addSoyRecord(clientId, soyMapData);
ImmutableList<String> domainNames = ImmutableList.copyOf(values);
DomainCommand.Check checkCommand = new DomainCommand.Check();
checkCommand.setTargetIds(domainNames);
addEppInput(
clientId,
EppInput.create(
EppInput.Check.create(checkCommand),
EppExtensions.feeCheckCreateV06(domainNames),
EppExtensions.allocationToken(allocationToken))
.withClTrid("RegistryTool"));
}
}
}

View File

@@ -23,8 +23,12 @@ import static google.registry.util.StringGenerator.DEFAULT_PASSWORD_LENGTH;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.net.InternetDomainName;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.CreateAnchorTenantSoyInfo;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.util.StringGenerator;
import jakarta.inject.Inject;
import jakarta.inject.Named;
@@ -48,10 +52,12 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand {
required = true)
private String domainName;
@SuppressWarnings("UnusedVariable")
@Parameter(
names = {"--contact"},
description = "Contact ID for the request. This will be used for registrant, admin contact, "
+ "and tech contact.",
description =
"Contact ID for the request. This will be used for registrant, admin contact, "
+ "and tech contact.",
required = true)
private String contact;
@@ -87,15 +93,18 @@ final class CreateAnchorTenantCommand extends MutatingEppToolCommand {
cost = getDomainCreateCost(domainName, clock.now(), DEFAULT_ANCHOR_TENANT_PERIOD_YEARS);
}
setSoyTemplate(CreateAnchorTenantSoyInfo.getInstance(),
CreateAnchorTenantSoyInfo.CREATEANCHORTENANT);
addSoyRecord(clientId, new SoyMapData(
"domainName", domainName,
"contactId", contact,
"reason", reason,
"password", password,
"period", DEFAULT_ANCHOR_TENANT_PERIOD_YEARS,
"feeCurrency", cost != null ? cost.getCurrencyUnit().toString() : null,
"fee", cost != null ? cost.getAmount().toString() : null));
DomainCommand.Create.Builder createBuilder =
new DomainCommand.Create.Builder()
.setDomainName(domainName)
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create(password)))
.setPeriod(Period.create(DEFAULT_ANCHOR_TENANT_PERIOD_YEARS, Period.Unit.YEARS));
addEppInput(
clientId,
EppInput.create(
EppInput.Create.create(createBuilder.build()),
EppExtensions.metadata(reason, false, true),
EppExtensions.feeCreateV06(cost))
.withClTrid("RegistryTool"));
}
}

View File

@@ -20,12 +20,20 @@ import static google.registry.pricing.PricingEngineProxy.getPricesForDomainName;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.template.soy.data.SoyMapData;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.Period;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.model.pricing.PremiumPricingEngine.DomainPrices;
import google.registry.tools.soy.DomainCreateSoyInfo;
import google.registry.util.StringGenerator;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import java.math.BigDecimal;
import org.joda.money.CurrencyUnit;
import org.joda.money.Money;
/** A command to create a new domain via EPP. */
@@ -60,8 +68,8 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
}
for (String domain : domains) {
String currency = null;
String cost = null;
CurrencyUnit currency = null;
BigDecimal cost = null;
DomainPrices prices = getPricesForDomainName(domain, clock.now());
// Check if the domain is premium and set the fee on the create command if so.
@@ -70,29 +78,32 @@ final class CreateDomainCommand extends CreateOrUpdateDomainCommand {
!force || forcePremiums,
"Forced creates on premium domain(s) require --force_premiums");
Money createCost = prices.getCreateCost();
currency = createCost.getCurrencyUnit().getCode();
cost = createCost.multipliedBy(period).getAmount().toString();
currency = createCost.getCurrencyUnit();
cost = createCost.multipliedBy(period).getAmount();
printStream.printf(
"NOTE: %s is premium at %s per year; sending total cost for %d year(s) of %s %s.\n",
domain, createCost, period, currency, cost);
}
setSoyTemplate(DomainCreateSoyInfo.getInstance(), DomainCreateSoyInfo.DOMAINCREATE);
SoyMapData soyMapData =
new SoyMapData(
"domain", domain,
"period", period,
"nameservers", nameservers,
"password", password,
"currency", currency,
"price", cost,
"dsRecords", DsRecord.convertToSoy(dsRecords),
"reason", reason,
"allocationToken", allocationToken);
if (requestedByRegistrar != null) {
soyMapData.put("requestedByRegistrar", requestedByRegistrar.toString());
}
addSoyRecord(clientId, soyMapData);
DomainCommand.Create.Builder createBuilder =
new DomainCommand.Create.Builder()
.setDomainName(domain)
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create(password)))
.setPeriod(Period.create(period, Period.Unit.YEARS))
.setNameserverHostNames(ImmutableSortedSet.copyOf(nameservers));
addEppInput(
clientId,
EppInput.create(
EppInput.Create.create(createBuilder.build()),
EppExtensions.feeCreate(currency, cost),
EppExtensions.secDnsCreate(
dsRecords.stream()
.map(DsRecord::toDsData)
.collect(ImmutableSet.toImmutableSet())),
EppExtensions.toolMetadata(reason, requestedByRegistrar),
EppExtensions.allocationToken(allocationToken))
.withClTrid("RegistryTool"));
}
}
}

View File

@@ -18,14 +18,14 @@ import static google.registry.util.CollectionUtils.nullToEmpty;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.net.InetAddresses;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.HostCreateSoyInfo;
import google.registry.model.eppinput.EppInput;
import google.registry.model.host.HostCommand;
import google.registry.util.DomainNameUtils;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.Comparator;
import java.util.List;
/** A command to create a new host via EPP. */
@@ -52,25 +52,19 @@ final class CreateHostCommand extends MutatingEppToolCommand {
@Override
protected void initMutatingEppToolCommand() {
setSoyTemplate(HostCreateSoyInfo.getInstance(), HostCreateSoyInfo.HOSTCREATE);
ImmutableList.Builder<String> ipv4Addresses = new ImmutableList.Builder<>();
ImmutableList.Builder<String> ipv6Addresses = new ImmutableList.Builder<>();
ImmutableSet.Builder<InetAddress> inetAddresses = new ImmutableSet.Builder<>();
for (String address : nullToEmpty(addresses)) {
InetAddress inetAddress = InetAddresses.forString(address);
if (inetAddress instanceof Inet4Address) {
ipv4Addresses.add(inetAddress.getHostAddress());
} else if (inetAddress instanceof Inet6Address) {
ipv6Addresses.add(inetAddress.getHostAddress());
} else {
throw new IllegalArgumentException(
String.format("IP address in unknown format: %s", address));
}
inetAddresses.add(InetAddresses.forString(address));
}
addSoyRecord(
HostCommand.Create.Builder createBuilder = new HostCommand.Create.Builder();
createBuilder.setTargetId(DomainNameUtils.canonicalizeHostname(hostName));
createBuilder.setInetAddresses(
ImmutableSortedSet.copyOf(
Comparator.comparing(InetAddresses::toAddrString), inetAddresses.build()));
addEppInput(
clientId,
new SoyMapData(
"hostname", DomainNameUtils.canonicalizeHostname(hostName),
"ipv4addresses", ipv4Addresses.build(),
"ipv6addresses", ipv6Addresses.build()));
EppInput.create(EppInput.Create.create(createBuilder.build())).withClTrid("RegistryTool"));
}
}

View File

@@ -16,8 +16,9 @@ package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.DomainDeleteSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
/** A command to delete a domain via EPP. */
@Parameters(separators = " =", commandDescription = "Delete domain")
@@ -60,11 +61,17 @@ final class DeleteDomainCommand extends MutatingEppToolCommand {
// Immediate deletion is accomplished using the superuser extension.
superuser = true;
}
setSoyTemplate(DomainDeleteSoyInfo.getInstance(), DomainDeleteSoyInfo.DELETEDOMAIN);
addSoyRecord(clientId, new SoyMapData(
"domainName", domainName,
"immediately", immediately,
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
DomainCommand.Delete deleteCommand = new DomainCommand.Delete();
deleteCommand.setTargetId(domainName);
addEppInput(
clientId,
EppInput.create(
EppInput.Delete.create(deleteCommand),
EppExtensions.toolMetadata(
"Deleted by registry administrator: " + reason, requestedByRegistrar),
EppExtensions.deleteSuperuser(immediately))
.withClTrid("RegistryTool"));
}
}

View File

@@ -16,8 +16,9 @@ package google.registry.tools;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.template.soy.data.SoyMapData;
import google.registry.tools.soy.HostDeleteSoyInfo;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.model.host.HostCommand;
import google.registry.util.DomainNameUtils;
/** A command to delete a host via EPP. */
@@ -50,12 +51,15 @@ final class DeleteHostCommand extends MutatingEppToolCommand {
@Override
protected void initMutatingEppToolCommand() {
setSoyTemplate(HostDeleteSoyInfo.getInstance(), HostDeleteSoyInfo.DELETEHOST);
addSoyRecord(
HostCommand.Delete deleteCommand = new HostCommand.Delete();
deleteCommand.setTargetId(DomainNameUtils.canonicalizeHostname(hostName));
addEppInput(
clientId,
new SoyMapData(
"hostName", DomainNameUtils.canonicalizeHostname(hostName),
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
EppInput.create(
EppInput.Delete.create(deleteCommand),
EppExtensions.toolMetadata(
"Deleted by registry administrator: " + reason, requestedByRegistrar))
.withClTrid("RegistryTool"));
}
}

View File

@@ -15,7 +15,6 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static google.registry.util.PreconditionsUtils.checkArgumentPresent;
import com.beust.jcommander.IStringConverter;
@@ -23,9 +22,8 @@ import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Splitter;
import com.google.common.io.BaseEncoding;
import com.google.template.soy.data.SoyListData;
import com.google.template.soy.data.SoyMapData;
import google.registry.flows.domain.DomainFlowUtils;
import google.registry.model.domain.secdns.DomainDsData;
import java.util.List;
record DsRecord(int keyTag, int alg, int digestType, String digest) {
@@ -76,16 +74,8 @@ record DsRecord(int keyTag, int alg, int digestType, String digest) {
elements.get(3));
}
public SoyMapData toSoyData() {
return new SoyMapData(
"keyTag", keyTag(),
"alg", alg(),
"digestType", digestType(),
"digest", digest());
}
public static SoyListData convertToSoy(List<DsRecord> dsRecords) {
return new SoyListData(dsRecords.stream().map(DsRecord::toSoyData).collect(toImmutableList()));
public DomainDsData toDsData() {
return DomainDsData.create(keyTag(), alg(), digestType(), digest());
}
public static class Converter implements IStringConverter<DsRecord> {

View File

@@ -14,10 +14,8 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.nullToEmpty;
import static com.google.common.collect.Maps.filterValues;
import static com.google.common.io.Resources.getResource;
import static google.registry.model.tld.Tlds.findTldForNameOrThrow;
import static google.registry.tools.CommandUtilities.addHeader;
import static google.registry.util.DomainNameUtils.canonicalizeHostname;
@@ -33,19 +31,16 @@ import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.common.net.InternetDomainName;
import com.google.common.net.MediaType;
import com.google.template.soy.SoyFileSet;
import com.google.template.soy.data.SoyRecord;
import com.google.template.soy.parseinfo.SoyFileInfo;
import com.google.template.soy.parseinfo.SoyTemplateInfo;
import google.registry.model.eppcommon.EppXmlTransformer;
import google.registry.model.eppinput.EppInput;
import google.registry.model.registrar.Registrar;
import google.registry.util.Clock;
import google.registry.xml.ValidationMode;
import jakarta.inject.Inject;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/** A command to execute an epp command. */
@@ -58,9 +53,6 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
description = "Run in superuser mode")
boolean superuser = false;
private SoyFileInfo soyFileInfo;
private SoyTemplateInfo soyRenderer;
private List<XmlEppParameters> commands = new ArrayList<>();
private ServiceConnection connection;
@@ -74,10 +66,11 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
}
/**
* Helper function for grouping sets of domain names into respective TLDs. Useful for batched
* EPP calls when invoking commands (i.e. domain check) with sets of domains across multiple TLDs.
* Helper function for grouping sets of domain names into respective TLDs. Useful for batched EPP
* calls when invoking commands (i.e. domain check) with sets of domains across multiple TLDs.
*/
protected static Multimap<String, String> validateAndGroupDomainNamesByTld(List<String> names) {
protected static Multimap<String, String> validateAndGroupDomainNamesByTld(
ImmutableList<String> names) {
ImmutableMultimap.Builder<String, String> builder = new ImmutableMultimap.Builder<>();
for (String name : names) {
String canonicalDomain = canonicalizeHostname(name);
@@ -87,11 +80,6 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
return builder.build();
}
protected void setSoyTemplate(SoyFileInfo soyFileInfo, SoyTemplateInfo soyRenderer) {
this.soyFileInfo = soyFileInfo;
this.soyRenderer = soyRenderer;
}
@Override
public void setConnection(ServiceConnection connection) {
this.connection = connection;
@@ -103,16 +91,20 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
commands.add(new XmlEppParameters(clientId, xml));
}
protected void addSoyRecord(String clientId, SoyRecord record) {
checkNotNull(soyFileInfo, "SoyFileInfo is missing, cannot add record.");
checkNotNull(soyRenderer, "SoyRenderer is missing, cannot add record.");
addXmlCommand(clientId, SoyFileSet.builder()
.add(getResource(soyFileInfo.getClass(), soyFileInfo.getFileName()))
.build()
.compileToTofu()
.newRenderer(soyRenderer)
.setData(record)
.render());
/**
* Adds an EPP command to the list of commands to be executed.
*
* @param clientId the registrar client ID to execute the command as
* @param eppInput the EPP input object to marshal and send
*/
protected void addEppInput(String clientId, EppInput eppInput) {
try {
String xml =
new String(EppXmlTransformer.marshalInput(eppInput, ValidationMode.STRICT), UTF_8);
addXmlCommand(clientId, xml);
} catch (Exception e) {
throw new RuntimeException("Failed to marshal EppInput", e);
}
}
/** Subclasses can override to implement a dry run flag. False by default. */
@@ -133,21 +125,23 @@ abstract class EppToolCommand extends ConfirmingCommand implements CommandWithCo
return prompt;
}
private List<String> processCommands(boolean dryRun) throws IOException {
private ImmutableList<String> processCommands(boolean dryRun) throws IOException {
ImmutableList.Builder<String> responses = new ImmutableList.Builder<>();
for (XmlEppParameters command : commands) {
Map<String, Object> params = new HashMap<>();
params.put("dryRun", dryRun);
params.put("clientId", command.clientId);
params.put("superuser", superuser);
params.put("xml", URLEncoder.encode(command.xml, UTF_8));
ImmutableMap<String, Object> params =
ImmutableMap.<String, Object>builder()
.put("dryRun", dryRun)
.put("clientId", command.clientId)
.put("superuser", superuser)
.put("xml", URLEncoder.encode(command.xml, UTF_8))
.build();
String requestBody =
Joiner.on('&').withKeyValueSeparator("=").join(filterValues(params, Objects::nonNull));
responses.add(
nullToEmpty(
connection.sendPostRequest(
"/_dr/epptool",
ImmutableMap.<String, String>of(),
ImmutableMap.of(),
MediaType.FORM_DATA,
requestBody.getBytes(UTF_8))));
}

View File

@@ -18,18 +18,20 @@ import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Strings.isNullOrEmpty;
import static google.registry.util.CollectionUtils.findDuplicates;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.time.ZoneOffset.UTC;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import com.google.common.base.Joiner;
import com.google.template.soy.data.SoyMapData;
import google.registry.flows.ResourceFlowUtils;
import google.registry.model.domain.Domain;
import google.registry.tools.soy.DomainRenewSoyInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.Period;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Set;
/** A command to renew domain(s) via EPP. */
@Parameters(separators = " =", commandDescription = "Renew domain(s) via EPP.")
@@ -61,35 +63,36 @@ final class RenewDomainCommand extends MutatingEppToolCommand {
arity = 1)
Boolean requestedByRegistrar;
private static final DateTimeFormatter DATE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneOffset.UTC);
@Override
protected void initMutatingEppToolCommand()
throws ResourceFlowUtils.ResourceDoesNotExistException {
String duplicates = Joiner.on(", ").join(findDuplicates(mainParameters));
checkArgument(duplicates.isEmpty(), "Duplicate domain arguments found: '%s'", duplicates);
protected void initMutatingEppToolCommand() throws Exception {
Set<String> duplicates = findDuplicates(mainParameters);
checkArgument(
duplicates.isEmpty(),
"Duplicate domain arguments found: '%s'",
Joiner.on(", ").join(duplicates));
checkArgument(period < 10, "Cannot renew domains for 10 or more years");
Instant now = clock.now();
for (String domainName : mainParameters) {
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
SoyMapData soyMapData =
new SoyMapData(
"domainName", domain.getDomainName(),
"expirationDate", DATE_FORMATTER.format(domain.getRegistrationExpirationTime()),
"period", String.valueOf(period));
if (requestedByRegistrar != null) {
soyMapData.put("requestedByRegistrar", requestedByRegistrar.toString());
}
if (reason != null) {
checkArgumentNotNull(
requestedByRegistrar, "--registrar_request is required when --reason is specified");
soyMapData.put("reason", reason);
}
addSoyRecord(
isNullOrEmpty(clientId) ? domain.getCurrentSponsorRegistrarId() : clientId, soyMapData);
DomainCommand.Renew.Builder renewBuilder =
new DomainCommand.Renew.Builder()
.setTargetId(domain.getDomainName())
.setPeriod(Period.create(period, Period.Unit.YEARS))
.setCurrentExpirationDate(
domain.getRegistrationExpirationTime().atZone(UTC).toLocalDate());
addEppInput(
isNullOrEmpty(clientId) ? domain.getCurrentSponsorRegistrarId() : clientId,
EppInput.create(
EppInput.Renew.create(renewBuilder.build()),
EppExtensions.toolMetadata(reason, requestedByRegistrar))
.withClTrid("RegistryTool"));
}
}
}

View File

@@ -15,7 +15,7 @@
package google.registry.tools;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static java.time.ZoneOffset.UTC;
@@ -27,36 +27,38 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.template.soy.data.SoyListData;
import com.google.template.soy.data.SoyMapData;
import google.registry.flows.ResourceFlowUtils;
import google.registry.model.ForeignKeyUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.Period;
import google.registry.model.domain.secdns.DomainDsData;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.model.host.Host;
import google.registry.tools.params.NameserversParameter;
import google.registry.tools.soy.DomainRenewSoyInfo;
import google.registry.tools.soy.UniformRapidSuspensionSoyInfo;
import jakarta.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** A command to suspend a domain for the Uniform Rapid Suspension process. */
@Parameters(separators = " =",
@Parameters(
separators = " =",
commandDescription = "Suspend a domain for Uniform Rapid Suspension.")
final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
private static final ImmutableSet<String> URS_LOCKS = ImmutableSet.of(
StatusValue.SERVER_DELETE_PROHIBITED.getXmlName(),
StatusValue.SERVER_TRANSFER_PROHIBITED.getXmlName(),
StatusValue.SERVER_UPDATE_PROHIBITED.getXmlName());
private static final ImmutableSet<String> URS_LOCKS =
ImmutableSet.of(
StatusValue.SERVER_DELETE_PROHIBITED.getXmlName(),
StatusValue.SERVER_TRANSFER_PROHIBITED.getXmlName(),
StatusValue.SERVER_UPDATE_PROHIBITED.getXmlName());
/** Client id that made this change. Only recorded in the history entry. **/
/** Client id that made this change. Only recorded in the history entry. * */
private static final String CLIENT_ID = "CharlestonRoad";
@Parameter(
@@ -127,12 +129,12 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
superuser = true;
Instant now = clock.now();
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
Set<String> missingHosts =
difference(newHosts, ForeignKeyUtils.loadKeys(Host.class, newHosts, now).keySet());
ImmutableSet<String> missingHosts =
ImmutableSet.copyOf(
difference(newHosts, ForeignKeyUtils.loadKeys(Host.class, newHosts, now).keySet()));
checkArgument(missingHosts.isEmpty(), "Hosts do not exist: %s", missingHosts);
checkArgument(
locksToPreserve.isEmpty() || undo,
"Locks can only be preserved when running with --undo");
locksToPreserve.isEmpty() || undo, "Locks can only be preserved when running with --undo");
existingNameservers = getExistingNameservers(domain);
existingLocks = getExistingLocks(domain);
existingDsData = getExistingDsData(domain);
@@ -152,54 +154,92 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
// trigger renew flow
if (renewOneYear) {
setSoyTemplate(DomainRenewSoyInfo.getInstance(), DomainRenewSoyInfo.RENEWDOMAIN);
addSoyRecord(
DomainCommand.Renew.Builder renewBuilder =
new DomainCommand.Renew.Builder()
.setTargetId(domain.getDomainName())
.setPeriod(Period.create(1, Period.Unit.YEARS))
.setCurrentExpirationDate(
domain.getRegistrationExpirationTime().atZone(UTC).toLocalDate());
addEppInput(
CLIENT_ID,
new SoyMapData(
"domainName",
domain.getDomainName(),
"expirationDate",
DateTimeFormatter.ofPattern("yyyy-MM-dd")
.withZone(UTC)
.format(domain.getRegistrationExpirationTime()),
// period is the number of years to renew the registration for
"period",
String.valueOf(1),
// use the same values for reason and requestedByRegistrar from update flow
"reason",
(undo ? "Undo " : "") + "Uniform Rapid Suspension",
"requestedByRegistrar",
Boolean.toString(false)));
EppInput.create(
EppInput.Renew.create(renewBuilder.build()),
EppExtensions.toolMetadata(
(undo ? "Undo " : "") + "Uniform Rapid Suspension", false))
.withClTrid("RegistryTool"));
}
// trigger update flow
setSoyTemplate(
UniformRapidSuspensionSoyInfo.getInstance(),
UniformRapidSuspensionSoyInfo.UNIFORMRAPIDSUSPENSION);
addSoyRecord(
DomainCommand.Update.Builder updateBuilder =
new DomainCommand.Update.Builder().setTargetId(domainName);
DomainCommand.Update.DomainAddRemove.Builder addBuilder =
new DomainCommand.Update.DomainAddRemove.Builder();
DomainCommand.Update.DomainAddRemove.Builder removeBuilder =
new DomainCommand.Update.DomainAddRemove.Builder();
boolean hasAdd = false;
boolean hasRemove = false;
if (!statusesToApply.isEmpty()) {
addBuilder.setStatusValues(
statusesToApply.stream()
.map(StatusValue::fromXmlName)
.collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())));
hasAdd = true;
}
ImmutableSet<String> statusesToRemove =
undo
? ImmutableSet.copyOf(difference(URS_LOCKS, ImmutableSet.copyOf(locksToPreserve)))
: removeStatuses;
if (!statusesToRemove.isEmpty()) {
removeBuilder.setStatusValues(
statusesToRemove.stream()
.map(StatusValue::fromXmlName)
.collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())));
hasRemove = true;
}
ImmutableSet<String> addNameservers =
ImmutableSet.copyOf(difference(newHosts, existingNameservers));
if (!addNameservers.isEmpty()) {
addBuilder.setNameserverHostNames(ImmutableSortedSet.copyOf(addNameservers));
hasAdd = true;
}
ImmutableSet<String> removeNameservers =
ImmutableSet.copyOf(difference(existingNameservers, newHosts));
if (!removeNameservers.isEmpty()) {
removeBuilder.setNameserverHostNames(ImmutableSortedSet.copyOf(removeNameservers));
hasRemove = true;
}
if (hasAdd) {
updateBuilder.setInnerAdd(addBuilder.build());
}
if (hasRemove) {
updateBuilder.setInnerRemove(removeBuilder.build());
}
addEppInput(
CLIENT_ID,
new SoyMapData(
"domainName",
domainName,
"hostsToAdd",
difference(newHosts, existingNameservers),
"hostsToRemove",
difference(existingNameservers, newHosts),
"statusesToApply",
statusesToApply,
"statusesToRemove",
undo ? difference(URS_LOCKS, ImmutableSet.copyOf(locksToPreserve)) : removeStatuses,
"newDsData",
newDsData != null ? DsRecord.convertToSoy(newDsData) : new SoyListData(),
"reason",
(undo ? "Undo " : "") + "Uniform Rapid Suspension",
// Domain auto-renewal is disabled as part of URS, and it's re-enabled if URS is undone.
// Therefore, autorenews is set to false by default and it's set to true only if the
// command is run in --undo mode.
"autorenews",
Boolean.toString(undo)));
EppInput.create(
EppInput.Update.create(updateBuilder.build()),
EppExtensions.secDnsUpdate(
newDsData == null
? ImmutableSet.of()
: newDsData.stream().map(DsRecord::toDsData).collect(toImmutableSet()),
ImmutableSet.of(),
true),
EppExtensions.updateSuperuser(undo),
EppExtensions.toolMetadata(
(undo ? "Undo " : "") + "Uniform Rapid Suspension", false))
.withClTrid("RegistryTool"));
}
/** Returns the set of existing nameservers for the specified domain. */
private ImmutableSortedSet<String> getExistingNameservers(Domain domain) {
ImmutableSortedSet.Builder<String> nameservers = ImmutableSortedSet.naturalOrder();
for (Host host : tm().transact(() -> tm().loadByKeys(domain.getNameservers()).values())) {
@@ -208,6 +248,7 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
return nameservers.build();
}
/** Returns the set of existing URS-related locks for the specified domain. */
private ImmutableSortedSet<String> getExistingLocks(Domain domain) {
ImmutableSortedSet.Builder<String> locks = ImmutableSortedSet.naturalOrder();
for (StatusValue lock : domain.getStatusValues()) {
@@ -218,6 +259,7 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
return locks.build();
}
/** Returns whether the specified domain has a CLIENT_HOLD status. */
private boolean hasClientHold(Domain domain) {
for (StatusValue status : domain.getStatusValues()) {
if (status == StatusValue.CLIENT_HOLD) {
@@ -227,6 +269,7 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
return false;
}
/** Returns a list of the existing DS records for the specified domain as JSON-like maps. */
private ImmutableList<ImmutableMap<String, Object>> getExistingDsData(Domain domain) {
ImmutableList.Builder<ImmutableMap<String, Object>> dsDataJsons = new ImmutableList.Builder();
HexBinaryAdapter hexBinaryAdapter = new HexBinaryAdapter();
@@ -273,7 +316,7 @@ final class UniformRapidSuspensionCommand extends MutatingEppToolCommand {
rec.get("digestType"),
rec.get("digest")))
.sorted()
.collect(toImmutableList());
.collect(ImmutableList.toImmutableList());
undoBuilder.append(" --dsdata ").append(Joiner.on(',').join(formattedDsRecords));
}
return undoBuilder.toString();

View File

@@ -18,6 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.model.domain.rgp.GracePeriodStatus.AUTO_RENEW;
import static google.registry.model.eppcommon.StatusValue.PENDING_DELETE;
import static google.registry.model.eppcommon.StatusValue.SERVER_UPDATE_PROHIBITED;
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
import static java.util.function.Predicate.isEqual;
import com.beust.jcommander.Parameter;
@@ -26,19 +27,23 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.google.common.flogger.FluentLogger;
import com.google.template.soy.data.SoyMapData;
import google.registry.flows.ResourceFlowUtils;
import google.registry.model.domain.Domain;
import google.registry.model.domain.DomainAuthInfo;
import google.registry.model.domain.DomainCommand;
import google.registry.model.domain.GracePeriodBase;
import google.registry.model.domain.secdns.SecDnsUpdateExtension;
import google.registry.model.eppcommon.AuthInfo.PasswordAuth;
import google.registry.model.eppcommon.StatusValue;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import google.registry.tools.params.NameserversParameter;
import google.registry.tools.soy.DomainUpdateSoyInfo;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.Nullable;
/** A command to update a new domain via EPP. */
@@ -60,9 +65,8 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
private Set<String> addNameservers = new HashSet<>();
@Parameter(
names = "--add_statuses",
description = "Statuses to add. Cannot be set if --statuses is set."
)
names = "--add_statuses",
description = "Statuses to add. Cannot be set if --statuses is set.")
private List<String> addStatuses = new ArrayList<>();
@Parameter(
@@ -82,9 +86,8 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
private Set<String> removeNameservers = new HashSet<>();
@Parameter(
names = "--remove_statuses",
description = "Statuses to remove. Cannot be set if --statuses is set."
)
names = "--remove_statuses",
description = "Statuses to remove. Cannot be set if --statuses is set.")
private List<String> removeStatuses = new ArrayList<>();
@Parameter(
@@ -95,10 +98,8 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
private List<DsRecord> removeDsRecords = new ArrayList<>();
@Parameter(
names = "--clear_ds_records",
description =
"removes all DS records. Is implied true if --ds_records is set."
)
names = "--clear_ds_records",
description = "removes all DS records. Is implied true if --ds_records is set.")
boolean clearDsRecords = false;
@Nullable
@@ -133,7 +134,7 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
+ "you cannot use the add_statuses and remove_statuses flags.");
}
if (!dsRecords.isEmpty() || clearDsRecords){
if (!dsRecords.isEmpty() || clearDsRecords) {
checkArgument(
addDsRecords.isEmpty() && removeDsRecords.isEmpty(),
"If you provide the ds_records or clear_ds_records flags, "
@@ -146,6 +147,12 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
Instant now = clock.now();
for (String domainName : domains) {
Domain domain = ResourceFlowUtils.loadAndVerifyExistence(Domain.class, domainName, now);
if (reason != null) {
checkArgumentNotNull(
requestedByRegistrar, "--registrar_request is required when --reason is specified");
}
checkArgument(
!domain.getStatusValues().contains(SERVER_UPDATE_PROHIBITED),
"The domain '%s' has status SERVER_UPDATE_PROHIBITED. Verify that you are allowed "
@@ -158,61 +165,18 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
+ "--force_in_pending_delete parameter to allow this update.",
domainName);
// Use TreeSets so that the results are always in the same order (this makes testing easier).
Set<String> addNameserversThisDomain = new TreeSet<>(addNameservers);
Set<String> removeNameserversThisDomain = new TreeSet<>(removeNameservers);
Set<String> addStatusesThisDomain = new TreeSet<>(addStatuses);
Set<String> removeStatusesThisDomain = new TreeSet<>(removeStatuses);
if (!nameservers.isEmpty()) {
ImmutableSortedSet<String> existingNameservers = domain.loadNameserverHostNames();
ImmutableSet<String> targetNameservers = ImmutableSet.copyOf(nameservers);
if (!nameservers.isEmpty() || !statuses.isEmpty()) {
if (!nameservers.isEmpty()) {
ImmutableSortedSet<String> existingNameservers = domain.loadNameserverHostNames();
populateAddRemoveLists(
ImmutableSet.copyOf(nameservers),
existingNameservers,
addNameserversThisDomain,
removeNameserversThisDomain);
int numNameservers =
existingNameservers.size()
+ addNameserversThisDomain.size()
- removeNameserversThisDomain.size();
checkArgument(
numNameservers <= 13,
"The resulting nameservers count for domain %s would be more than 13",
domainName);
}
if (!statuses.isEmpty()) {
Set<String> currentStatusValues = new HashSet<>();
for (StatusValue statusValue : domain.getStatusValues()) {
currentStatusValues.add(statusValue.getXmlName());
}
populateAddRemoveLists(
ImmutableSet.copyOf(statuses),
currentStatusValues,
addStatusesThisDomain,
removeStatusesThisDomain);
}
}
boolean add =
(!addNameserversThisDomain.isEmpty()
|| !addStatusesThisDomain.isEmpty());
boolean remove =
(!removeNameserversThisDomain.isEmpty()
|| !removeStatusesThisDomain.isEmpty());
boolean change = password != null;
boolean secDns =
(!addDsRecords.isEmpty()
|| !removeDsRecords.isEmpty()
|| !dsRecords.isEmpty()
|| clearDsRecords);
if (!add && !remove && !change && !secDns && autorenews == null) {
logger.atInfo().log("No changes need to be made to domain '%s'.", domainName);
continue;
int numNameservers =
existingNameservers.size()
+ Sets.difference(targetNameservers, existingNameservers).size()
- Sets.difference(existingNameservers, targetNameservers).size();
checkArgument(
numNameservers <= 13,
"The resulting nameservers count for domain %s would be more than 13",
domainName);
}
// If autorenew is being turned off and this domain is already in the autorenew grace period,
@@ -225,30 +189,121 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
}
}
setSoyTemplate(DomainUpdateSoyInfo.getInstance(), DomainUpdateSoyInfo.DOMAINUPDATE);
SoyMapData soyMapData =
new SoyMapData(
"domain", domainName,
"add", add,
"addNameservers", addNameserversThisDomain,
"addStatuses", addStatusesThisDomain,
"remove", remove,
"removeNameservers", removeNameserversThisDomain,
"removeStatuses", removeStatusesThisDomain,
"change", change,
"password", password,
"secdns", secDns,
"addDsRecords", DsRecord.convertToSoy(addDsRecords),
"removeDsRecords", DsRecord.convertToSoy(removeDsRecords),
"removeAllDsRecords", clearDsRecords,
"reason", reason);
if (autorenews != null) {
soyMapData.put("autorenews", autorenews.toString());
DomainCommand.Update.Builder updateBuilder =
new DomainCommand.Update.Builder().setTargetId(domainName);
DomainCommand.Update.DomainAddRemove.Builder addBuilder =
new DomainCommand.Update.DomainAddRemove.Builder();
DomainCommand.Update.DomainAddRemove.Builder removeBuilder =
new DomainCommand.Update.DomainAddRemove.Builder();
boolean hasAdd = false;
boolean hasRemove = false;
boolean hasChange = false;
if (!nameservers.isEmpty()) {
ImmutableSortedSet<String> current = domain.loadNameserverHostNames();
ImmutableSet<String> target = ImmutableSet.copyOf(nameservers);
ImmutableSortedSet<String> toAdd =
ImmutableSortedSet.copyOf(Sets.difference(target, current));
ImmutableSortedSet<String> toRemove =
ImmutableSortedSet.copyOf(Sets.difference(current, target));
if (!toAdd.isEmpty()) {
addBuilder.setNameserverHostNames(toAdd);
hasAdd = true;
}
if (!toRemove.isEmpty()) {
removeBuilder.setNameserverHostNames(toRemove);
hasRemove = true;
}
} else {
if (!addNameservers.isEmpty()) {
addBuilder.setNameserverHostNames(ImmutableSortedSet.copyOf(addNameservers));
hasAdd = true;
}
if (!removeNameservers.isEmpty()) {
removeBuilder.setNameserverHostNames(ImmutableSortedSet.copyOf(removeNameservers));
hasRemove = true;
}
}
if (requestedByRegistrar != null) {
soyMapData.put("requestedByRegistrar", requestedByRegistrar.toString());
if (!statuses.isEmpty()) {
ImmutableSortedSet<StatusValue> current =
ImmutableSortedSet.copyOf(domain.getStatusValues());
ImmutableSet<StatusValue> target =
statuses.stream().map(StatusValue::fromXmlName).collect(ImmutableSet.toImmutableSet());
ImmutableSortedSet<StatusValue> toAdd =
ImmutableSortedSet.copyOf(Sets.difference(target, current));
ImmutableSortedSet<StatusValue> toRemove =
ImmutableSortedSet.copyOf(Sets.difference(current, target));
if (!toAdd.isEmpty()) {
addBuilder.setStatusValues(toAdd);
hasAdd = true;
}
if (!toRemove.isEmpty()) {
removeBuilder.setStatusValues(toRemove);
hasRemove = true;
}
} else {
if (!addStatuses.isEmpty()) {
addBuilder.setStatusValues(
addStatuses.stream()
.map(StatusValue::fromXmlName)
.collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())));
hasAdd = true;
}
if (!removeStatuses.isEmpty()) {
removeBuilder.setStatusValues(
removeStatuses.stream()
.map(StatusValue::fromXmlName)
.collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())));
hasRemove = true;
}
}
if (hasAdd) {
updateBuilder.setInnerAdd(addBuilder.build());
}
if (hasRemove) {
updateBuilder.setInnerRemove(removeBuilder.build());
}
if (password != null) {
updateBuilder.setInnerChange(
new DomainCommand.Update.Change.Builder()
.setAuthInfo(DomainAuthInfo.create(PasswordAuth.create(password)))
.build());
hasChange = true;
}
SecDnsUpdateExtension secDnsUpdate = null;
if (!addDsRecords.isEmpty()
|| !removeDsRecords.isEmpty()
|| !dsRecords.isEmpty()
|| clearDsRecords) {
secDnsUpdate =
EppExtensions.secDnsUpdate(
addDsRecords.stream()
.map(DsRecord::toDsData)
.collect(ImmutableSet.toImmutableSet()),
removeDsRecords.stream()
.map(DsRecord::toDsData)
.collect(ImmutableSet.toImmutableSet()),
clearDsRecords);
}
if (hasAdd || hasRemove || hasChange || secDnsUpdate != null || autorenews != null) {
addEppInput(
clientId,
EppInput.create(
EppInput.Update.create(updateBuilder.build()),
EppExtensions.updateSuperuser(autorenews),
EppExtensions.toolMetadata(reason, requestedByRegistrar),
secDnsUpdate)
.withClTrid("RegistryTool"));
} else {
logger.atInfo().log(
"Skipping domain '%s' because there are no changes to make.", domainName);
}
addSoyRecord(clientId, soyMapData);
}
ImmutableSet<String> domainsToWarn = autorenewGracePeriodWarningDomains.build();
@@ -260,10 +315,4 @@ final class UpdateDomainCommand extends CreateOrUpdateDomainCommand {
String.join(", ", domainsToWarn));
}
}
private void populateAddRemoveLists(
Set<String> targetSet, Set<String> oldSet, Set<String> addSet, Set<String> removeSet) {
addSet.addAll(Sets.difference(targetSet, oldSet));
removeSet.addAll(Sets.difference(oldSet, targetSet));
}
}

View File

@@ -21,94 +21,119 @@ import static com.google.common.collect.Sets.intersection;
import static com.google.common.collect.Sets.union;
import com.beust.jcommander.Parameter;
import com.beust.jcommander.ParameterException;
import com.beust.jcommander.Parameters;
import com.google.common.collect.ImmutableSet;
import com.google.template.soy.data.SoyMapData;
import com.google.common.collect.ImmutableSortedSet;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppcommon.StatusValue;
import google.registry.tools.soy.UpdateServerLocksSoyInfo;
import google.registry.model.eppinput.EppExtensions;
import google.registry.model.eppinput.EppInput;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
/** A command to execute a domain check claims epp command. */
@Parameters(separators = " =",
commandDescription = "Toggle server locks on a domain.")
final class UpdateServerLocksCommand extends MutatingEppToolCommand {
@Parameter(
names = {"-c", "--client"},
description = "Client identifier of the registrar to execute the command as",
required = true)
String clientId;
@Parameter(
names = {"-n", "--domain_name"},
description = "Domain to modify.",
description = "Domain to lock/unlock.",
required = true)
private String domainName;
@Parameter(
names = {"-a", "--apply"},
description = "Comma-delimited set of locks to apply (or 'all'). "
+ "Valid locks: serverDeleteProhibited, serverHold, serverRenewProhibited, "
+ "serverTransferProhibited, serverUpdateProhibited")
private List<String> locksToApply = new ArrayList<>();
@Parameter(
names = {"-r", "--remove"},
description = "Comma-delimited set of locks to remove (or 'all'). "
+ "Valid locks: same as for 'apply'.")
private List<String> locksToRemove = new ArrayList<>();
names = {"--client"},
description = "Client ID to use for the EPP command.",
required = true)
private String clientId;
@Parameter(
names = {"--reason"},
description = "Reason for the change. Required if registrar_request = false.")
description = "Reason for the change.")
private String reason;
@Parameter(
names = {"--apply"},
description = "Statuses to apply. Use \"all\" to apply all server locks.")
private List<String> locksToApply = new ArrayList<>();
@Parameter(
names = {"--remove"},
description = "Statuses to remove. Use \"all\" to remove all server locks.")
private List<String> locksToRemove = new ArrayList<>();
@Parameter(
names = {"--registrar_request"},
description = "Whether the change was requested by a registrar.",
required = true,
arity = 1)
private boolean requestedByRegistrar;
private Boolean requestedByRegistrar;
private static final ImmutableSet<String> ALLOWED_VALUES = ImmutableSet.of(
StatusValue.SERVER_DELETE_PROHIBITED.getXmlName(),
StatusValue.SERVER_HOLD.getXmlName(),
StatusValue.SERVER_RENEW_PROHIBITED.getXmlName(),
StatusValue.SERVER_TRANSFER_PROHIBITED.getXmlName(),
StatusValue.SERVER_UPDATE_PROHIBITED.getXmlName());
private static final ImmutableSet<String> ALLOWED_VALUES =
ImmutableSet.of(
StatusValue.SERVER_DELETE_PROHIBITED.getXmlName(),
StatusValue.SERVER_HOLD.getXmlName(),
StatusValue.SERVER_RENEW_PROHIBITED.getXmlName(),
StatusValue.SERVER_TRANSFER_PROHIBITED.getXmlName(),
StatusValue.SERVER_UPDATE_PROHIBITED.getXmlName());
private static Set<String> getStatusValuesSet(List<String> statusValues) {
Set<String> statusValuesSet = ImmutableSet.copyOf(statusValues);
private static ImmutableSet<String> getStatusValuesSet(List<String> statusValues) {
ImmutableSet<String> statusValuesSet = ImmutableSet.copyOf(statusValues);
if (statusValuesSet.contains("all")) {
return ALLOWED_VALUES;
}
Set<String> badValues = difference(statusValuesSet, ALLOWED_VALUES);
ImmutableSet<String> badValues =
ImmutableSet.copyOf(difference(statusValuesSet, ALLOWED_VALUES));
checkArgument(badValues.isEmpty(), "Invalid status values: %s", badValues);
return statusValuesSet;
}
@Override
protected void initMutatingEppToolCommand() {
if (requestedByRegistrar == null) {
throw new ParameterException("--registrar_request must be specified");
}
checkArgument(
requestedByRegistrar || !isNullOrEmpty(reason),
"A reason must be provided when a change is not requested by a registrar.");
Set<String> valuesToApply = getStatusValuesSet(locksToApply);
Set<String> valuesToRemove = getStatusValuesSet(locksToRemove);
ImmutableSet<String> valuesToApply = getStatusValuesSet(locksToApply);
ImmutableSet<String> valuesToRemove = getStatusValuesSet(locksToRemove);
checkArgument(
intersection(valuesToApply, valuesToRemove).isEmpty(),
"Add and remove actions overlap");
checkArgument(
!union(valuesToApply, valuesToRemove).isEmpty(),
"Add and remove actions are both empty");
setSoyTemplate(
UpdateServerLocksSoyInfo.getInstance(), UpdateServerLocksSoyInfo.UPDATESERVERLOCKS);
addSoyRecord(clientId, new SoyMapData(
"domainName", domainName,
"locksToApply", valuesToApply,
"locksToRemove", valuesToRemove,
"reason", reason,
"requestedByRegistrar", requestedByRegistrar));
DomainCommand.Update.Builder updateBuilder =
new DomainCommand.Update.Builder().setTargetId(domainName);
if (!valuesToApply.isEmpty()) {
updateBuilder.setInnerAdd(
new DomainCommand.Update.DomainAddRemove.Builder()
.setStatusValues(
valuesToApply.stream()
.map(StatusValue::fromXmlName)
.collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())))
.build());
}
if (!valuesToRemove.isEmpty()) {
updateBuilder.setInnerRemove(
new DomainCommand.Update.DomainAddRemove.Builder()
.setStatusValues(
valuesToRemove.stream()
.map(StatusValue::fromXmlName)
.collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.naturalOrder())))
.build());
}
addEppInput(
clientId,
EppInput.create(
EppInput.Update.create(updateBuilder.build()),
EppExtensions.toolMetadata(reason, requestedByRegistrar))
.withClTrid("RegistryTool"));
}
}

View File

@@ -0,0 +1,47 @@
<#ftl output_format="HTML">
<#-- Copyright 2026 The Nomulus Authors. All Rights Reserved. -->
<p>Dear registrar partner,</p>
<p>${registry} conducts a daily analysis of all domains registered in its TLDs to
identify potential security concerns. On ${date}, the following domains that your
registrar manages were flagged for potential security concerns:</p>
<table>
<tr>
<th>Domain Name</th>
<th>Threat Type</th>
</tr>
<#list threats as threat>
<tr>
<td>${threat.domainName}</td>
<td>${threat.threatType}</td>
</tr>
</#list>
</table>
<p><b>Please communicate these findings to the registrant and work with the
registrant to mitigate any security issues and have the domains delisted.</b></p>
<#if (resources?size > 0)>
<p>Some helpful resources for getting off a blocked list include:</p>
<ul>
<#list resources as resource>
<li>${resource}</li>
</#list>
</ul>
</#if>
<p>If you believe that any of the domains were reported in error, or are still receiving
reports for issues that have been remediated,
please <a href="https://safebrowsing.google.com/safebrowsing/report_error/?hl=en">submit
a request</a> to have the site reviewed.</p>
<p>You will continue to receive daily notices when new domains managed by your registrar
are flagged for abuse, as well as a monthly summary of all of your domains under management
that remain flagged for abuse.</p>
<p>If you would like to change the email to which these notices are sent, please update your
abuse contact using your registrar portal account.</p>
<p>If you have any questions regarding this notice, please contact ${replyToEmail}.</p>

View File

@@ -0,0 +1,46 @@
<#ftl output_format="HTML">
<#-- Copyright 2026 The Nomulus Authors. All Rights Reserved. -->
<p>Dear registrar partner,</p>
<p>${registry} previously notified you when the following domains managed by your
registrar were flagged for potential security concerns.</p>
<p>The following domains that you manage continue to be flagged by our analysis for
potential security concerns. This may be because the registrants have not completed the
requisite steps to mitigate the potential security abuse and/or have it reviewed and
delisted.</p>
<table>
<tr>
<th>Domain Name</th>
<th>Threat Type</th>
</tr>
<#list threats as threat>
<tr>
<td>${threat.domainName}</td>
<td>${threat.threatType}</td>
</tr>
</#list>
</table>
<p>Please work with the registrant to mitigate any security issues and have the
domains delisted. If you believe that any of the domains were reported in error, or are
still receiving reports for issues that have been remediated,
please <a href="https://safebrowsing.google.com/safebrowsing/report_error/?hl=en">submit a
request</a> to have the site reviewed.</p>
<#if (resources?size > 0)>
<p>Some helpful resources for getting off a blocked list include:</p>
<ul>
<#list resources as resource>
<li>${resource}</li>
</#list>
</ul>
</#if>
<p>You will continue to receive a monthly summary of all domains managed by your registrar
that remain on our lists of potential security threats. You will also receive a daily
notice when any new domains are added to these lists.</p>
<p>If you have any questions regarding this notice, please contact ${replyToEmail}.</p>

View File

@@ -1,130 +0,0 @@
// Copyright 2019 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.
{namespace registry.soy.reporting.spec11}
/**
* Template for the content of the monthly spec11 email
*/
{template monthlySpec11Email}
{@param threats: list<map<string, string>>}
{@param resources: list<string>}
{@param registry: string}
{@param replyToEmail: string}
Dear registrar partner,
<p>{$registry} previously notified you when the following domains managed by your
registrar were flagged for potential security concerns.</p>
<p>The following domains that you manage continue to be flagged by our analysis for potential
security concerns. This may be because the registrants have not completed the requisite steps
to mitigate the potential security abuse and/or have it reviewed and delisted.</p>
{call threatMatchTable}
{param threats: $threats /}
{/call}
<p>Please work with the registrant to mitigate any security issues and have the
domains delisted. If you believe that any of the domains were reported in error, or are still
receiving reports for issues that have been remediated,
please <a href="https://safebrowsing.google.com/safebrowsing/report_error/?hl=en">submit a
request</a> to have the site reviewed.</p>
{call resourceList}
{param resources: $resources /}
{/call}
<p>You will continue to receive a monthly summary of all domains managed by your registrar
that remain on our lists of potential security threats. You will also receive a daily
notice when any new domains are added to these lists.</p>
<p>If you have any questions regarding this notice, please contact {$replyToEmail}.</p>
{/template}
/**
* Template for the content of the daily spec11 email
*/
{template dailySpec11Email}
{@param threats: list<map<string, string>>}
{@param resources: list<string>}
{@param date: string}
{@param registry: string}
{@param replyToEmail: string}
Dear registrar partner,
<p>{$registry} conducts a daily analysis of all domains registered in its TLDs to
identify potential security concerns. On {$date}, the following domains that your
registrar manages were flagged for potential security concerns:</p>
{call threatMatchTable}
{param threats: $threats /}
{/call}
<p><b>Please communicate these findings to the registrant and work with the
registrant to mitigate any security issues and have the domains delisted.</b></p>
{call resourceList}
{param resources: $resources /}
{/call}
<p>If you believe that any of the domains were reported in error, or are still receiving
reports for issues that have been remediated,
please <a href="https://safebrowsing.google.com/safebrowsing/report_error/?hl=en">submit
a request</a> to have the site reviewed.</p>
<p>You will continue to receive daily notices when new domains managed by your registrar
are flagged for abuse, as well as a monthly summary of all of your domains under management
that remain flagged for abuse.</p>
<p>If you would like to change the email to which these notices are sent please update your
abuse contact using your registrar portal account.</p>
<p>If you have any questions regarding this notice, please contact {$replyToEmail}.</p>
{/template}
/**
* Template for the list of potentially-useful resources
*/
{template resourceList}
{@param resources: list<string>}
{if length($resources) > 0}
Some helpful resources for getting off a blocked list include:
<ul>
{for $resource in $resources}
<li>{$resource}</li>
{/for}
</ul>
{/if}
{/template}
/**
* Template for the table containing the threats themselves
*/
{template threatMatchTable}
{@param threats: list<map<string, string>>}
<table>
<tr>
<th>Domain Name</th>
<th>Threat Type</th>
</tr>
{for $threat in $threats}
<tr>
<td>{$threat.get('domainName')}</td>
<td>{$threat.get('threatType')}</td>
</tr>
{/for}
</table>
{/template}

View File

@@ -1,62 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.create_anchor_tenant}
/**
* Create anchor tenant domain
*/
{template createanchortenant stricthtml="false"}
{@param domainName: string}
{@param contactId: string}
{@param password: string}
{@param period: int}
{@param? reason: string|null}
{@param? feeCurrency: string|null}
{@param? fee: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domainName}</domain:name>
<domain:period unit="y">{$period}</domain:period>
<domain:registrant>{$contactId}</domain:registrant>
<domain:contact type="admin">{$contactId}</domain:contact>
<domain:contact type="tech">{$contactId}</domain:contact>
<domain:authInfo>
<domain:pw>{$password}</domain:pw>
</domain:authInfo>
</domain:create>
</create>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
{if $reason}
<metadata:reason>{$reason}</metadata:reason>
{/if}
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
<metadata:anchorTenant>true</metadata:anchorTenant>
</metadata:metadata>
{if $fee}
<fee:create xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
<fee:currency>{$feeCurrency}</fee:currency>
<fee:fee>{$fee}</fee:fee>
</fee:create>
{/if}
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,53 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.domain_check}
/**
* Domain check request
*/
{template domaincheck stricthtml="false"}
{@param domainNames: list<string>}
{@param? allocationToken: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
{for $d in $domainNames}
<domain:name>{$d}</domain:name>
{/for}
</domain:check>
</check>
<extension>
<fee:check xmlns:fee="urn:ietf:params:xml:ns:fee-0.6">
{for $d in $domainNames}
<fee:domain>
<fee:name>{$d}</fee:name>
<fee:command>create</fee:command>
<fee:period unit="y">1</fee:period>
</fee:domain>
{/for}
</fee:check>
{if $allocationToken}
<allocationToken:allocationToken
xmlns:allocationToken="urn:ietf:params:xml:ns:allocationToken-1.0">
{$allocationToken}
</allocationToken:allocationToken>
{/if}
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,42 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.domain_check_claims}
/**
* Domain check claims request
*/
{template domaincheckclaims stricthtml="false"}
{@param domainNames: list<string>}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<check>
<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
{for $d in $domainNames}
<domain:name>{$d}</domain:name>
{/for}
</domain:check>
</check>
<extension>
<launch:check
xmlns:launch="urn:ietf:params:xml:ns:launch-1.0"
type="claims">
<launch:phase>claims</launch:phase>
</launch:check>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,108 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.domain_create}
/**
* Create domain
*/
{template domaincreate stricthtml="false"}
{@param domain: string}
{@param period: int}
{@param nameservers: list<string>}
{@param? registrant: string|null}
{@param? admins: list<string>|null}
{@param? techs: list<string>|null}
{@param password: string}
{@param? currency: string|null}
{@param? price: string|null}
{@param dsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
{@param? reason: string|null}
{@param? requestedByRegistrar: string|null}
{@param? allocationToken: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domain}</domain:name>
<domain:period unit="y">{$period}</domain:period>
{if length($nameservers) > 0}
<domain:ns>
{for $s in $nameservers}
<domain:hostObj>{$s}</domain:hostObj>
{/for}
</domain:ns>
{/if}
{if $registrant != null}
<domain:registrant>{$registrant}</domain:registrant>
{/if}
{if $admins != null}
{for $admin in $admins}
<domain:contact type="admin">{$admin}</domain:contact>
{/for}
{/if}
{if $techs != null}
{for $tech in $techs}
<domain:contact type="tech">{$tech}</domain:contact>
{/for}
{/if}
<domain:authInfo>
<domain:pw>{$password}</domain:pw>
</domain:authInfo>
</domain:create>
</create>
{if length($dsRecords) > 0 || $price != null || $reason || $requestedByRegistrar || $allocationToken}
<extension>
{if $price != null}
<fee:create xmlns:fee="urn:ietf:params:xml:ns:fee-0.12">
<fee:currency>{$currency}</fee:currency>
<fee:fee>{$price}</fee:fee>
</fee:create>
{/if}
{if length($dsRecords) > 0}
<secDNS:create xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
{for $dsRecord in $dsRecords}
<secDNS:dsData>
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:create>
{/if}
{if $reason || $requestedByRegistrar}
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
{if $reason}
<metadata:reason>{$reason}</metadata:reason>
{/if}
{if $requestedByRegistrar}
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
{/if}
</metadata:metadata>
{/if}
{if $allocationToken}
<allocationToken:allocationToken
xmlns:allocationToken=
"urn:ietf:params:xml:ns:allocationToken-1.0">
{$allocationToken}
</allocationToken:allocationToken>
{/if}
</extension>
{/if}
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,49 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.domain_delete}
/**
* Delete domain request
*/
{template deletedomain stricthtml="false"}
{@param domainName: string}
{@param immediately: bool}
{@param reason: string}
{@param requestedByRegistrar: any}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<domain:delete
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domainName}</domain:name>
</domain:delete>
</delete>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Deleted by registry administrator: {$reason}</metadata:reason>
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
</metadata:metadata>
{if $immediately}
<superuser:domainDelete xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
<superuser:redemptionGracePeriodDays>0</superuser:redemptionGracePeriodDays>
<superuser:pendingDeleteDays>0</superuser:pendingDeleteDays>
</superuser:domainDelete>
{/if}
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,52 +0,0 @@
// Copyright 2018 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.
{namespace domain.registry.tools.domain_renew}
/**
* Renew domain request
*/
{template renewdomain stricthtml="false"}
{@param domainName: string}
{@param expirationDate: string}
{@param period: string}
{@param? reason: string|null}
{@param? requestedByRegistrar: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<renew>
<domain:renew
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domainName}</domain:name>
<domain:curExpDate>{$expirationDate}</domain:curExpDate>
<domain:period unit="y">{$period}</domain:period>
</domain:renew>
</renew>
{if $reason || $requestedByRegistrar}
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
{if $reason}
<metadata:reason>{$reason}</metadata:reason>
{/if}
{if $requestedByRegistrar}
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
{/if}
</metadata:metadata>
</extension>
{/if}
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,137 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.domain_update}
/**
* Update domain
*/
{template domainupdate stricthtml="false"}
{@param domain: string}
{@param add: bool}
{@param addNameservers: list<string>}
{@param addStatuses: list<string>}
{@param remove: bool}
{@param removeNameservers: list<string>}
{@param removeStatuses: list<string>}
{@param change: bool}
{@param? password: string|null}
{@param secdns: bool}
{@param addDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
{@param removeDsRecords: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
{@param removeAllDsRecords: bool}
{@param? autorenews: string|null}
{@param? reason: string|null}
{@param? requestedByRegistrar: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domain}</domain:name>
{if $add}
<domain:add>
{if length($addNameservers) > 0}
<domain:ns>
{for $s in $addNameservers}
<domain:hostObj>{$s}</domain:hostObj>
{/for}
</domain:ns>
{/if}
{for $status in $addStatuses}
<domain:status s="{$status}"/>
{/for}
</domain:add>
{/if}
{if $remove}
<domain:rem>
{if length($removeNameservers) > 0}
<domain:ns>
{for $s in $removeNameservers}
<domain:hostObj>{$s}</domain:hostObj>
{/for}
</domain:ns>
{/if}
{for $status in $removeStatuses}
<domain:status s="{$status}"/>
{/for}
</domain:rem>
{/if}
{if $change}
<domain:chg>
{if $password}
<domain:authInfo>
<domain:pw>{$password}</domain:pw>
</domain:authInfo>
{/if}
</domain:chg>
{/if}
</domain:update>
</update>
{if $secdns || $autorenews || $reason || $requestedByRegistrar}
<extension>
{if $secdns}
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
{if $removeAllDsRecords}
<secDNS:rem>
<secDNS:all>true</secDNS:all>
</secDNS:rem>
{/if}
{if length($removeDsRecords) > 0}
<secDNS:rem>
{for $dsRecord in $removeDsRecords}
<secDNS:dsData>
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:rem>
{/if}
{if length($addDsRecords) > 0}
<secDNS:add>
{for $dsRecord in $addDsRecords}
<secDNS:dsData>
<secDNS:keyTag>{$dsRecord.keyTag}</secDNS:keyTag>
<secDNS:alg>{$dsRecord.alg}</secDNS:alg>
<secDNS:digestType>{$dsRecord.digestType}</secDNS:digestType>
<secDNS:digest>{$dsRecord.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:add>
{/if}
</secDNS:update>
{/if}
{if $autorenews}
<superuser:domainUpdate xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
<superuser:autorenews>{$autorenews}</superuser:autorenews>
</superuser:domainUpdate>
{/if}
{if $reason || $requestedByRegistrar}
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
{if $reason}
<metadata:reason>{$reason}</metadata:reason>
{/if}
{if $requestedByRegistrar}
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
{/if}
</metadata:metadata>
{/if}
</extension>
{/if}
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,45 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.host_create}
/**
* Create host
*/
{template hostcreate stricthtml="false"}
{@param hostname: string}
{@param? ipv4addresses: list<string>|null}
{@param? ipv6addresses: list<string>|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<host:create xmlns:host="urn:ietf:params:xml:ns:host-1.0">
<host:name>{$hostname}</host:name>
{if $ipv4addresses}
{for $ipv4 in $ipv4addresses}
<host:addr ip="v4">{$ipv4}</host:addr>
{/for}
{/if}
{if $ipv6addresses}
{for $ipv6 in $ipv6addresses}
<host:addr ip="v6">{$ipv6}</host:addr>
{/for}
{/if}
</host:create>
</create>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,42 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.host_delete}
/**
* Delete host request
*/
{template deletehost stricthtml="false"}
{@param hostName: string}
{@param reason: string}
{@param requestedByRegistrar: any}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<delete>
<host:delete
xmlns:host="urn:ietf:params:xml:ns:host-1.0">
<host:name>{$hostName}</host:name>
</host:delete>
</delete>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>Deleted by registry administrator: {$reason}</metadata:reason>
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,47 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.remove_ip_address}
/**
* Request to remove IP addresses.
*/
{template remove_ip_address stricthtml="false"}
{@param name: string}
{@param ipAddresses: list<legacy_object_map<string, string>>}
{@param requestedByRegistrar: string}
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<host:update
xmlns:host="urn:ietf:params:xml:ns:host-1.0">
<host:name>{$name}</host:name>
{for $ip in $ipAddresses}
<host:rem>
<host:addr ip="{$ip['type']}">{$ip['address']}</host:addr>
</host:rem>
{/for}
</host:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>External IP address removed by registry administrator.</metadata:reason>
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>ABC-12345</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,90 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.uniform_rapid_suspension}
/**
* Uniform Rapid Suspension
*/
{template uniformrapidsuspension stricthtml="false"}
{@param domainName: string}
{@param hostsToAdd: list<string>}
{@param hostsToRemove: list<string>}
{@param statusesToApply: list<string>}
{@param statusesToRemove: list<string>}
{@param newDsData: list<[keyTag:int, alg:int, digestType:int, digest:string]>}
{@param reason: string}
{@param autorenews: string}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domainName}</domain:name>
<domain:add>
{if length($hostsToAdd) > 0}
<domain:ns>
{for $ha in $hostsToAdd}
<domain:hostObj>{$ha}</domain:hostObj>
{/for}
</domain:ns>
{/if}
{for $la in $statusesToApply}
<domain:status s="{$la}" />
{/for}
</domain:add>
<domain:rem>
{if length($hostsToRemove) > 0}
<domain:ns>
{for $hr in $hostsToRemove}
<domain:hostObj>{$hr}</domain:hostObj>
{/for}
</domain:ns>
{/if}
{for $lr in $statusesToRemove}
<domain:status s="{$lr}" />
{/for}
</domain:rem>
</domain:update>
</update>
<extension>
<secDNS:update xmlns:secDNS="urn:ietf:params:xml:ns:secDNS-1.1">
<secDNS:rem>
<secDNS:all>true</secDNS:all>
</secDNS:rem>
{if length($newDsData) > 0}
<secDNS:add>
{for $ds in $newDsData}
<secDNS:dsData>
<secDNS:keyTag>{$ds.keyTag}</secDNS:keyTag>
<secDNS:alg>{$ds.alg}</secDNS:alg>
<secDNS:digestType>{$ds.digestType}</secDNS:digestType>
<secDNS:digest>{$ds.digest}</secDNS:digest>
</secDNS:dsData>
{/for}
</secDNS:add>
{/if}
</secDNS:update>
<superuser:domainUpdate xmlns:superuser="urn:google:params:xml:ns:superuser-1.0">
<superuser:autorenews>{$autorenews}</superuser:autorenews>
</superuser:domainUpdate>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
<metadata:reason>{$reason}</metadata:reason>
<metadata:requestedByRegistrar>false</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -1,56 +0,0 @@
// Copyright 2017 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.
{namespace domain.registry.tools.update_server_locks}
/**
* Update server locks
*/
{template updateserverlocks stricthtml="false"}
{@param domainName: string}
{@param locksToApply: list<string>}
{@param locksToRemove: list<string>}
{@param requestedByRegistrar: any}
{@param? reason: string|null}
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update
xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>{$domainName}</domain:name>
<domain:add>
{for $a in $locksToApply}
<domain:status s="{$a}" lang="en"></domain:status>
{/for}
</domain:add>
<domain:rem>
{for $r in $locksToRemove}
<domain:status s="{$r}" lang="en"></domain:status>
{/for}
</domain:rem>
</domain:update>
</update>
<extension>
<metadata:metadata xmlns:metadata="urn:google:params:xml:ns:metadata-1.0">
{if $reason}
<metadata:reason>{$reason}</metadata:reason>
{/if}
<metadata:requestedByRegistrar>{$requestedByRegistrar}</metadata:requestedByRegistrar>
</metadata:metadata>
</extension>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
{/template}

View File

@@ -23,7 +23,6 @@ import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.LogsSubject.assertAboutLogs;
import static google.registry.util.DateTimeUtils.START_INSTANT;
import static google.registry.util.DateTimeUtils.minusDays;
import static google.registry.util.NetworkUtils.pickUnusedPort;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.mockito.Mockito.times;
@@ -168,7 +167,7 @@ public class UploadBsaUnavailableDomainsActionTest {
private TestServer startTestServer() throws Exception {
TestServer testServer =
new TestServer(
HostAndPort.fromParts(InetAddress.getLocalHost().getHostAddress(), pickUnusedPort()),
HostAndPort.fromParts(InetAddress.getLocalHost().getHostAddress(), 0),
ImmutableMap.of(),
ImmutableList.of(Route.route("/upload", Servlet.class)));
testServer.start();

View File

@@ -28,9 +28,11 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.gson.Gson;
import google.registry.keyring.api.Keyring;
import google.registry.request.UrlConnectionService;
import google.registry.testing.FakeClock;
import google.registry.tools.GsonUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -49,6 +51,7 @@ import org.mockito.junit.jupiter.MockitoExtension;
class BsaCredentialTest {
private static final Duration AUTH_TOKEN_EXPIRY = Duration.ofMinutes(30);
private static final Gson GSON = GsonUtils.provideGson();
@Mock OutputStream connectionOutputStream;
@Mock HttpsURLConnection connection;
@@ -60,7 +63,8 @@ class BsaCredentialTest {
@BeforeEach
void setup() throws Exception {
credential =
new BsaCredential(connectionService, "https://authUrl", AUTH_TOKEN_EXPIRY, keyring, clock);
new BsaCredential(
connectionService, "https://authUrl", AUTH_TOKEN_EXPIRY, keyring, GSON, clock);
}
void setupHttp() throws Exception {

View File

@@ -85,10 +85,9 @@ public abstract class ResourceFlowTestCase<F extends Flow, R extends EppResource
return refreshedResource;
}
private ResourceCommand.SingleResourceCommand getResourceCommand() throws Exception {
return (ResourceCommand.SingleResourceCommand)
((ResourceCommandWrapper) eppLoader.getEpp().getCommandWrapper().getCommand())
.getResourceCommand();
private ResourceCommand getResourceCommand() throws Exception {
return ((ResourceCommandWrapper) eppLoader.getEpp().getCommandWrapper().getCommand())
.getResourceCommand();
}
protected String getUniqueIdFromCommand() throws Exception {

View File

@@ -0,0 +1,56 @@
// 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.flows.domain;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableList;
import google.registry.flows.domain.DomainFlowTmchUtils.SignedMarksListEmptyException;
import google.registry.flows.domain.DomainFlowTmchUtils.SignedMarksMustBeEncodedException;
import google.registry.flows.domain.DomainFlowTmchUtils.TooManySignedMarksException;
import google.registry.model.smd.AbstractSignedMark;
import java.time.Instant;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class DomainFlowTmchUtilsTest {
private final DomainFlowTmchUtils tmchUtils = new DomainFlowTmchUtils(null);
@Test
void test_verifySignedMarks_emptyList() {
assertThrows(
SignedMarksListEmptyException.class,
() -> tmchUtils.verifySignedMarks(ImmutableList.of(), "example", Instant.now()));
}
@Test
void test_verifySignedMarks_tooManyMarks() {
AbstractSignedMark mark1 = Mockito.mock(AbstractSignedMark.class);
AbstractSignedMark mark2 = Mockito.mock(AbstractSignedMark.class);
assertThrows(
TooManySignedMarksException.class,
() ->
tmchUtils.verifySignedMarks(ImmutableList.of(mark1, mark2), "example", Instant.now()));
}
@Test
void test_verifySignedMarks_notEncoded() {
AbstractSignedMark mark1 = Mockito.mock(AbstractSignedMark.class);
assertThrows(
SignedMarksMustBeEncodedException.class,
() -> tmchUtils.verifySignedMarks(ImmutableList.of(mark1), "example", Instant.now()));
}
}

View File

@@ -15,7 +15,7 @@
package google.registry.model.domain;
import static google.registry.testing.DatabaseHelper.persistActiveHost;
import static org.junit.Assert.assertThrows;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.flows.FlowUtils;
import google.registry.flows.domain.DomainFlowUtils.RegistrantProhibitedException;

View File

@@ -0,0 +1,57 @@
// 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.domain.fee;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import google.registry.model.domain.fee.BaseFee.FeeType;
import java.math.BigDecimal;
import org.junit.jupiter.api.Test;
class FeeTest {
@Test
void testCreate_success() {
Fee fee = Fee.create(BigDecimal.valueOf(10.00), FeeType.CREATE, false);
assertThat(fee.getCost()).isEqualTo(BigDecimal.valueOf(10.00));
assertThat(fee.getType()).isEqualTo(FeeType.CREATE);
}
@Test
void testCreate_nullCost() {
IllegalArgumentException thrown =
assertThrows(IllegalArgumentException.class, () -> Fee.create(null, FeeType.CREATE, false));
assertThat(thrown).hasMessageThat().contains("Cost cannot be null");
}
@Test
void testCreate_negativeCost() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> Fee.create(BigDecimal.valueOf(-5.00), FeeType.CREATE, false));
assertThat(thrown).hasMessageThat().contains("Cost must be a non-negative number");
}
@Test
void testCreate_nullType() {
IllegalArgumentException thrown =
assertThrows(
IllegalArgumentException.class,
() -> Fee.create(BigDecimal.valueOf(10.00), null, false));
assertThat(thrown).hasMessageThat().contains("Must specify the type of the fee");
}
}

View File

@@ -14,57 +14,177 @@
package google.registry.model.eppinput;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.eppcommon.EppXmlTransformer.unmarshal;
import static google.registry.testing.TestDataHelper.loadBytes;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static google.registry.xml.XmlTestUtils.assertXmlEquals;
import static java.nio.charset.StandardCharsets.UTF_8;
import google.registry.model.domain.DomainTest;
import google.registry.model.eppinput.EppInput.InnerCommand;
import google.registry.model.eppinput.EppInput.Login;
import google.registry.xml.XmlException;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.model.domain.DomainCommand;
import google.registry.model.eppcommon.EppXmlTransformer;
import google.registry.model.eppcommon.StatusValue;
import google.registry.xml.ValidationMode;
import org.junit.jupiter.api.Test;
/** Unit tests for {@link EppInput}. */
/** Unit tests for {@link EppInput} builders and marshaling. */
class EppInputTest {
@Test
void testUnmarshalling_domainCheck() throws Exception {
EppInput input =
unmarshal(EppInput.class, loadBytes(DomainTest.class, "domain_check.xml").read());
assertThat(input.getCommandWrapper().getClTrid()).hasValue("ABC-12345");
assertThat(input.getCommandType()).isEqualTo("check");
assertThat(input.getResourceType()).hasValue("domain");
assertThat(input.getSingleTargetId()).isEmpty();
assertThat(input.getTargetIds()).containsExactly("example.com", "example.net", "example.org");
void testBuilder_emptyExtensions_omitsExtensionTag() throws Exception {
EppInput eppInput =
new EppInput.Builder()
.setCommandWrapper(
new EppInput.CommandWrapper.Builder()
.setCommand(
new EppInput.Create.Builder()
.setResourceCommand(
new DomainCommand.Create.Builder()
.setDomainName("example.tld")
.build())
.build())
.setExtensions(ImmutableList.of())
.setClTrid("RegistryTool")
.build())
.build();
String xml =
new String(EppXmlTransformer.marshalInput(eppInput, ValidationMode.LENIENT), UTF_8);
assertXmlEquals(
"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:create>
</create>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
""",
xml);
}
@Test
void testUnmarshalling_login() throws Exception {
EppInput input = unmarshal(EppInput.class, loadBytes(getClass(), "login_valid.xml").read());
assertThat(input.getCommandWrapper().getClTrid()).hasValue("ABC-12345");
assertThat(input.getCommandType()).isEqualTo("login");
assertThat(input.getResourceType()).isEmpty();
assertThat(input.getSingleTargetId()).isEmpty();
assertThat(input.getTargetIds()).isEmpty();
InnerCommand command = input.getCommandWrapper().getCommand();
assertThat(command).isInstanceOf(Login.class);
Login loginCommand = (Login) command;
assertThat(loginCommand.clientId).isEqualTo("NewRegistrar");
assertThat(loginCommand.password).isEqualTo("foo-BAR2");
assertThat(loginCommand.newPassword).isNull();
assertThat(loginCommand.options.version).isEqualTo("1.0");
assertThat(loginCommand.options.language).isEqualTo("en");
assertThat(loginCommand.services.objectServices)
.containsExactly("urn:ietf:params:xml:ns:host-1.0", "urn:ietf:params:xml:ns:domain-1.0");
assertThat(loginCommand.services.serviceExtensions)
.containsExactly("urn:ietf:params:xml:ns:launch-1.0", "urn:ietf:params:xml:ns:rgp-1.0");
void testBuilder_nullExtensions_omitsExtensionTag() throws Exception {
EppInput eppInput =
new EppInput.Builder()
.setCommandWrapper(
new EppInput.CommandWrapper.Builder()
.setCommand(
new EppInput.Create.Builder()
.setResourceCommand(
new DomainCommand.Create.Builder()
.setDomainName("example.tld")
.build())
.build())
.setExtensions(null)
.setClTrid("RegistryTool")
.build())
.build();
String xml =
new String(EppXmlTransformer.marshalInput(eppInput, ValidationMode.LENIENT), UTF_8);
assertXmlEquals(
"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<create>
<domain:create xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
</domain:create>
</create>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
""",
xml);
}
@Test
void testUnmarshalling_loginTagInWrongCase_throws() {
assertThrows(
XmlException.class,
() -> unmarshal(EppInput.class, loadBytes(getClass(), "login_wrong_case.xml").read()));
void testBuilder_domainUpdate_emptyAddRemove_omitsInnerTags() throws Exception {
EppInput eppInput =
new EppInput.Builder()
.setCommandWrapper(
new EppInput.CommandWrapper.Builder()
.setCommand(
new EppInput.Update.Builder()
.setResourceCommand(
new DomainCommand.Update.Builder()
.setTargetId("example.tld")
.setInnerAdd(
new DomainCommand.Update.DomainAddRemove.Builder().build())
.setInnerRemove(
new DomainCommand.Update.DomainAddRemove.Builder().build())
.build())
.build())
.setClTrid("RegistryTool")
.build())
.build();
String xml =
new String(EppXmlTransformer.marshalInput(eppInput, ValidationMode.LENIENT), UTF_8);
assertXmlEquals(
"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:add/>
<domain:rem/>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
""",
xml);
}
@Test
void testBuilder_domainUpdate_withStatuses() throws Exception {
EppInput eppInput =
new EppInput.Builder()
.setCommandWrapper(
new EppInput.CommandWrapper.Builder()
.setCommand(
new EppInput.Update.Builder()
.setResourceCommand(
new DomainCommand.Update.Builder()
.setTargetId("example.tld")
.setInnerAdd(
new DomainCommand.Update.DomainAddRemove.Builder()
.setStatusValues(
ImmutableSet.of(StatusValue.CLIENT_HOLD))
.build())
.build())
.build())
.setClTrid("RegistryTool")
.build())
.build();
String xml =
new String(EppXmlTransformer.marshalInput(eppInput, ValidationMode.LENIENT), UTF_8);
assertXmlEquals(
"""
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
<command>
<update>
<domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
<domain:name>example.tld</domain:name>
<domain:add>
<domain:status s="clientHold"/>
</domain:add>
</domain:update>
</update>
<clTRID>RegistryTool</clTRID>
</command>
</epp>
""",
xml);
}
}

Some files were not shown because too many files have changed in this diff Show More