Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ domain name registrar acting on their behalf to interact with the registry.
Nomulus runs on [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine)
and is written primarily in Java. It is the software that
[Google Registry](https://www.registry.google/) uses to operate TLDs such as .google,
.app, .how, .soy, and .みんな. It can run any number of TLDs in a single shared registry
.app, .how, and .みんな. It can run any number of TLDs in a single shared registry
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this change?

system using horizontal scaling. Its source code is publicly available in this
repository under the [Apache 2.0 free and open source license](https://www.apache.org/licenses/LICENSE-2.0).

Expand Down
1 change: 1 addition & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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['joda-time:joda-time']
implementation deps['com.google.flogger:flogger']
implementation deps['io.github.java-diff-utils:java-diff-utils']
Expand Down
7 changes: 4 additions & 3 deletions common/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.ben-manes.caffeine:caffeine:3.0.5=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.github.ben-manes.caffeine:caffeine:3.2.3=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.github.ben-manes.caffeine:caffeine:3.2.4=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.github.kevinstern:software-and-algorithms:1.0=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.google.auto.service:auto-service-annotations:1.0.1=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.google.auto.value:auto-value-annotations:1.11.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
Expand All @@ -11,8 +11,8 @@ com.google.auto:auto-common:1.2.2=annotationProcessor,testAnnotationProcessor,te
com.google.code.findbugs:jsr305:3.0.2=checkstyle,compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.errorprone:error_prone_annotation:2.48.0=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.36.0=checkstyle
com.google.errorprone:error_prone_annotations:2.43.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.errorprone:error_prone_annotations:2.48.0=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_annotations:2.49.0=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
com.google.errorprone:error_prone_check_api:2.48.0=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.google.errorprone:error_prone_core:2.48.0=annotationProcessor,testAnnotationProcessor,testingAnnotationProcessor
com.google.flogger:flogger:0.9=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
Expand All @@ -37,7 +37,7 @@ io.github.java-diff-utils:java-diff-utils:4.12=annotationProcessor,testAnnotatio
io.github.java-diff-utils:java-diff-utils:4.16=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
joda-time:joda-time:2.14.1=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
joda-time:joda-time:2.14.2=compileClasspath,deploy_jar,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
junit:junit:4.13.2=testCompileClasspath,testRuntimeClasspath,testing,testingCompileClasspath
net.sf.saxon:Saxon-HE:12.5=checkstyle
org.antlr:antlr4-runtime:4.13.2=checkstyle
Expand All @@ -61,6 +61,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.32=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
Expand Down
67 changes: 67 additions & 0 deletions common/src/main/java/google/registry/util/TemplateRenderer.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.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_invalidTemplate_throwsException() {
// This will fail during parsing or rendering if the template is broken.
assertThrows(
RuntimeException.class,
() -> renderer.render("google/registry/util/test_template.ftl", ImmutableMap.of()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Hello ${name}!
Your score is ${score}.
<#if showMessage>
Message: ${message}
</#if>
33 changes: 3 additions & 30 deletions config/presubmits.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,15 @@ def fails(self, file):
# 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"), {
".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"),
{"node_modules/", ".idea"}, REQUIRED):
"Source files must end in a newline.",

Expand All @@ -111,33 +111,6 @@ def fails(self, file):
"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",
Expand Down
63 changes: 2 additions & 61 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ processTestResources {

configurations {
jaxb
soy
devtool

nonprodImplementation.extendsFrom implementation
Expand All @@ -120,7 +119,7 @@ configurations {
// For reasons we do not understand, marking the following dependencies as
// compileOnly instead of compile does not exclude them from runtimeClasspath.
all {
// servlet-api:3.1 pulled in but not used by soy compiler
// servlet-api:3.1 pulled in but not used
exclude group: 'javax.servlet', module: 'javax.servlet-api'
}
}
Expand Down Expand Up @@ -181,7 +180,7 @@ dependencies {
implementation deps['com.google.oauth-client:google-oauth-client-jetty']
implementation deps['com.google.oauth-client:google-oauth-client-servlet']
implementation deps['com.google.re2j:re2j']
implementation deps['com.google.template:soy']
implementation deps['org.freemarker:freemarker']
implementation deps['com.googlecode.json-simple:json-simple']
implementation deps['com.jcraft:jsch']
implementation deps['com.zaxxer:HikariCP']
Expand Down Expand Up @@ -300,9 +299,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']
Expand Down Expand Up @@ -372,62 +368,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.
Expand Down
Loading
Loading