diff --git a/java/google/registry/config/ConfigModule.java b/java/google/registry/config/ConfigModule.java index 3258d96f1..c818bfb6e 100644 --- a/java/google/registry/config/ConfigModule.java +++ b/java/google/registry/config/ConfigModule.java @@ -47,6 +47,10 @@ import org.joda.time.Duration; * in the user's repository. For this to work, other files need to be copied too, such as * {@link google.registry.module.backend.BackendComponent BackendComponent}. This allows modules to * be substituted at the {@code @Component} level. + * + *

There's also a deprecated configuration class that needs to be overridden and supplied via a + * system property. See the instructions in {@link ProductionRegistryConfigExample} and + * {@link RegistryConfigLoader}. */ @Module public final class ConfigModule { diff --git a/java/google/registry/config/ProductionRegistryConfigExample.java b/java/google/registry/config/ProductionRegistryConfigExample.java new file mode 100644 index 000000000..ccabf8735 --- /dev/null +++ b/java/google/registry/config/ProductionRegistryConfigExample.java @@ -0,0 +1,236 @@ +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.config; + +import static com.google.common.base.Preconditions.checkNotNull; +import static google.registry.config.ConfigUtils.makeUrl; +import static org.joda.time.Duration.standardDays; + +import com.google.appengine.api.utils.SystemProperty; +import com.google.common.base.Ascii; +import com.google.common.base.Joiner; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.common.net.HostAndPort; +import java.net.URL; +import javax.annotation.concurrent.Immutable; +import org.joda.time.Duration; + +/** + * Default production configuration for global constants that can't be injected. + * + *

Warning: Editing this file in a forked repository is not recommended. The recommended + * approach is to copy this file, give it a different name, and then change the system property + * described in the {@link RegistryConfigLoader} documentation. + */ +@Immutable +public final class ProductionRegistryConfigExample implements RegistryConfig { + + private final RegistryEnvironment environment; + + private static final String RESERVED_TERMS_EXPORT_DISCLAIMER = "" + + "# This list contains reserve terms for the TLD. Other terms may be reserved\n" + + "# but not included in this list, including terms EXAMPLE REGISTRY chooses not\n" + + "# to publish, and terms that ICANN commonly mandates to be reserved. This\n" + + "# list is subject to change and the most up-to-date source is always to\n" + + "# check availability directly with the Registry server.\n"; + + public ProductionRegistryConfigExample(RegistryEnvironment environment) { + this.environment = checkNotNull(environment); + } + + @Override + public String getProjectId() { + switch (environment) { + case PRODUCTION: + return "domain-registry"; + default: + return "domain-registry-" + Ascii.toLowerCase(environment.name()); + } + } + + @Override + public int getCommitLogBucketCount() { + return 100; // if you decrease this number, the world ends + } + + /** + * {@inheritDoc} + * + *

Thirty days makes a sane default, because it's highly unlikely we'll ever need to generate a + * deposit older than that. And if we do, we could always bring up a separate App Engine instance + * and replay the commit logs off GCS. + */ + @Override + public Duration getCommitLogDatastoreRetention() { + return Duration.standardDays(30); + } + + @Override + public String getSnapshotsBucket() { + return getProjectId() + "-snapshots"; + } + + @Override + public String getDomainListsBucket() { + return getProjectId() + "-domain-lists"; + } + + @Override + public String getCommitsBucket() { + return getProjectId() + "-commits"; + } + + @Override + public String getZoneFilesBucket() { + return getProjectId() + "-zonefiles"; + } + + @Override + public String getEscrowFileImportBucket() { + return getProjectId() + "-escrow-import"; + } + + @Override + public boolean getTmchCaTestingMode() { + switch (environment) { + case PRODUCTION: + return false; + default: + return true; + } + } + + @Override + public String getTmchMarksdbUrl() { + switch (environment) { + case PRODUCTION: + return "https://ry.marksdb.org"; + default: + return "https://test.ry.marksdb.org"; + } + } + + @Override + public Optional getECatcherAddress() { + throw new UnsupportedOperationException(); // n/a + } + + @Override + public HostAndPort getServer() { + switch (environment) { + case LOCAL: + return HostAndPort.fromParts("localhost", 8080); + default: + String host = Joiner.on(".").join("tools", getProjectId(), "appspot.com"); + return HostAndPort.fromParts(host, 443); + } + } + + @Override + public Duration getSingletonCacheRefreshDuration() { + return Duration.standardMinutes(10); + } + + @Override + public Duration getDomainLabelListCacheDuration() { + return Duration.standardHours(1); + } + + @Override + public Duration getSingletonCachePersistDuration() { + return Duration.standardDays(365); + } + + @Override + public String getReservedTermsExportDisclaimer() { + return RESERVED_TERMS_EXPORT_DISCLAIMER; + } + + @Override + public String getGoogleAppsAdminEmailDisplayName() { + return "Example Registry"; + } + + @Override + public String getGoogleAppsSendFromEmailAddress() { + return String.format("noreply@%s.appspotmail.com", SystemProperty.applicationId.get()); + } + + @Override + public ImmutableList getRegistrarChangesNotificationEmailAddresses() { + switch (environment) { + case PRODUCTION: + return ImmutableList.of("notification@registry.example"); + default: + return ImmutableList.of(); + } + } + + @Override + public String getRegistrarDefaultWhoisServer() { + return "whois.nic.registry.example"; + } + + @Override + public URL getRegistrarDefaultReferralUrl() { + return makeUrl("https://www.registry.example"); + } + + @Override + public String getDocumentationProjectTitle() { + return "Domain Registry"; + } + + @Override + public int getMaxChecks() { + return 50; + } + + @Override + public int getEppResourceIndexBucketCount() { + return 997; + } + + @Override + public Duration getBaseOfyRetryDuration() { + return Duration.millis(100); + } + + @Override + public String getContactAndHostRepositoryIdentifier() { + return "ROID"; + } + + @Override + public Duration getContactAutomaticTransferLength() { + return standardDays(5); + } + + @Override + public String getCheckApiServletRegistrarClientId() { + return "TheRegistrar"; + } + + @Override + public Duration getAsyncDeleteFlowMapreduceDelay() { + return Duration.standardSeconds(90); + } + + @Override + public Duration getAsyncFlowFailureBackoff() { + return Duration.standardMinutes(10); + } +} diff --git a/java/google/registry/config/RegistryConfig.java b/java/google/registry/config/RegistryConfig.java index f2db197fc..85c2c57a8 100644 --- a/java/google/registry/config/RegistryConfig.java +++ b/java/google/registry/config/RegistryConfig.java @@ -21,7 +21,7 @@ import java.net.URL; import org.joda.time.Duration; /** - * Domain Registry configuration. + * Domain Registry configuration for global constants that can't be injected. * *

The goal of this custom configuration system is to have our project environments configured * in type-safe Java code that can be refactored, rather than XML files and system properties. diff --git a/java/google/registry/config/RegistryConfigLoader.java b/java/google/registry/config/RegistryConfigLoader.java new file mode 100644 index 000000000..61383d230 --- /dev/null +++ b/java/google/registry/config/RegistryConfigLoader.java @@ -0,0 +1,75 @@ +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.config; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import javax.annotation.concurrent.ThreadSafe; + +/** + * System property loader of {@link RegistryConfig} instance. + * + *

This class reflectively loads the Java class defined by the system property + * {@value #REGISTRY_CONFIG_PROPERTY} whose default value is {@value #REGISTRY_CONFIG_DEFAULT} and + * can be set in {@code appengine-web.xml}. Once the class is loaded, its constructor is called, + * passing the {@link RegistryEnvironment} as a single parameter. + */ +@ThreadSafe +public final class RegistryConfigLoader { + + public static final String REGISTRY_CONFIG_PROPERTY = "com.google.domain.registry.config"; + public static final String REGISTRY_CONFIG_DEFAULT = + "google.registry.config.ProductionRegistryConfigExample"; + + static RegistryConfig load(RegistryEnvironment environment) { + String className = System.getProperty(REGISTRY_CONFIG_PROPERTY, REGISTRY_CONFIG_DEFAULT); + Class clazz; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + throw new RuntimeException(String.format( + "Failed to load '%s' as specified by system property '%s'", + className, REGISTRY_CONFIG_PROPERTY), e); + } + if (!RegistryConfig.class.isAssignableFrom(clazz)) { + throw new RuntimeException(String.format( + "%s does not implement %s", + clazz.getSimpleName(), RegistryConfig.class.getSimpleName())); + } + @SuppressWarnings("unchecked") + Class clazzy = (Class) clazz; + if (!Modifier.isPublic(clazzy.getModifiers())) { + throw new RuntimeException(String.format( + "Must be a public class: %s", clazzy.getCanonicalName())); + } + Constructor constructor; + try { + constructor = clazzy.getConstructor(RegistryEnvironment.class); + } catch (NoSuchMethodException | SecurityException e) { + throw new RuntimeException(String.format( + "Must have a public constructor(RegistryEnvironment): %s", clazzy.getCanonicalName()), e); + } + try { + return constructor.newInstance(environment); + } catch (InvocationTargetException e) { + throw new RuntimeException( + String.format("%s constructor threw an exception", clazzy.getSimpleName()), e); + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) { + throw new RuntimeException( + String.format("Failed to instantiate: %s", clazz.getCanonicalName()), e); + } + } +} diff --git a/java/google/registry/config/RegistryEnvironment.java b/java/google/registry/config/RegistryEnvironment.java index 16aec6da6..613934763 100644 --- a/java/google/registry/config/RegistryEnvironment.java +++ b/java/google/registry/config/RegistryEnvironment.java @@ -79,11 +79,8 @@ public enum RegistryEnvironment { @Nullable private static RegistryConfig configOverride; - // TODO(b/19247780) Use true dependency injection for this. In the mean time, if you're not - // Google, you'll need to change this to include your own config class implementation at compile - // time. private static final RegistryConfig testingConfig = new TestRegistryConfig(); - private final RegistryConfig config = new TestRegistryConfig(); + private final RegistryConfig config = RegistryConfigLoader.load(this); /** System property for configuring which environment we should use. */ public static final String PROPERTY = "google.registry.environment";