mirror of
https://github.com/google/nomulus.git
synced 2025-07-22 02:36:03 +02:00
Remove Ofy (#1863)
So long, farewell, adios, ciao, sayonara, 再见! TESTED=deployed to alpha and used `nomulus list_tlds` to confirm that the web app can receive and serve requests. <!-- Reviewable:start --> - - - This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/google/nomulus/1863) <!-- Reviewable:end -->
This commit is contained in:
parent
601aed388c
commit
1d7dfe4e07
58 changed files with 31 additions and 2381 deletions
|
@ -6,5 +6,4 @@ node_modules/
|
||||||
repos/**
|
repos/**
|
||||||
**/.idea/
|
**/.idea/
|
||||||
*.jar
|
*.jar
|
||||||
!third_party/**/*.jar
|
|
||||||
!/gradle/wrapper/**/*.jar
|
!/gradle/wrapper/**/*.jar
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -14,7 +14,6 @@ gjf.out
|
||||||
*.jar
|
*.jar
|
||||||
*.war
|
*.war
|
||||||
*.ear
|
*.ear
|
||||||
!/third_party/**/*.jar
|
|
||||||
|
|
||||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
hs_err_pid*
|
hs_err_pid*
|
||||||
|
|
|
@ -355,8 +355,6 @@ subprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.name == 'third_party') return
|
|
||||||
|
|
||||||
project.tasks.test.dependsOn runPresubmits
|
project.tasks.test.dependsOn runPresubmits
|
||||||
|
|
||||||
def commonlyExcludedResources = ['**/*.java', '**/BUILD']
|
def commonlyExcludedResources = ['**/*.java', '**/BUILD']
|
||||||
|
|
|
@ -43,12 +43,6 @@ by Joshua Bloch in his book Effective Java -->
|
||||||
<property name="message" value='Your Javadocs appear to use invalid <a> tag syntax in @see tags. Please use the correct syntax: @see <a href="http(s)://your_url">url_description</a>'/>
|
<property name="message" value='Your Javadocs appear to use invalid <a> tag syntax in @see tags. Please use the correct syntax: @see <a href="http(s)://your_url">url_description</a>'/>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
<!-- Checks that our Ofy wrapper is used instead of the "real" ofy(). -->
|
|
||||||
<module name="RegexpSingleline">
|
|
||||||
<property name="format" value="com\.googlecode\.objectify\.ObjectifyService\.ofy"/>
|
|
||||||
<property name="message" value="Use google.registry.model.ofy.ObjectifyService.ofy(). Do not use com.googlecode.objectify.v4.ObjectifyService.ofy()."/>
|
|
||||||
</module>
|
|
||||||
|
|
||||||
<!-- Checks that java.util.Optional is used instead of Guava's Optional. -->
|
<!-- Checks that java.util.Optional is used instead of Guava's Optional. -->
|
||||||
<module name="RegexpSingleline">
|
<module name="RegexpSingleline">
|
||||||
<property name="format" value="com\.google\.common\.base\.Optional"/>
|
<property name="format" value="com\.google\.common\.base\.Optional"/>
|
||||||
|
@ -58,7 +52,7 @@ by Joshua Bloch in his book Effective Java -->
|
||||||
<!-- Checks that the deprecated ExpectedException is not used. -->
|
<!-- Checks that the deprecated ExpectedException is not used. -->
|
||||||
<module name="RegexpSingleline">
|
<module name="RegexpSingleline">
|
||||||
<property name="format" value="org\.junit\.rules\.ExpectedException"/>
|
<property name="format" value="org\.junit\.rules\.ExpectedException"/>
|
||||||
<property name="message" value="Use assertThrows and expectThrows from JUnitBackports instead of the deprecated methods on ExpectedException."/>
|
<property name="message" value="Use assertThrows and expectThrows instead of the deprecated methods on ExpectedException."/>
|
||||||
</module>
|
</module>
|
||||||
|
|
||||||
<module name="LineLength">
|
<module name="LineLength">
|
||||||
|
|
|
@ -163,11 +163,6 @@ configurations {
|
||||||
dependencies {
|
dependencies {
|
||||||
def deps = rootProject.dependencyMap
|
def deps = rootProject.dependencyMap
|
||||||
|
|
||||||
// Custom-built objectify jar at commit ecd5165, included in Nomulus
|
|
||||||
// release.
|
|
||||||
implementation files(
|
|
||||||
"${rootDir}/third_party/objectify/v4_1/objectify-4.1.3.jar")
|
|
||||||
|
|
||||||
testRuntimeOnly files(sourceSets.test.resources.srcDirs)
|
testRuntimeOnly files(sourceSets.test.resources.srcDirs)
|
||||||
|
|
||||||
implementation deps['com.beust:jcommander']
|
implementation deps['com.beust:jcommander']
|
||||||
|
|
|
@ -21,7 +21,6 @@ import com.google.common.flogger.FluentLogger;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.RegistryEnvironment;
|
||||||
import google.registry.config.SystemPropertySetter;
|
import google.registry.config.SystemPropertySetter;
|
||||||
import google.registry.model.AppEngineEnvironment;
|
|
||||||
import google.registry.model.IdService;
|
import google.registry.model.IdService;
|
||||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||||
|
@ -63,10 +62,6 @@ public class RegistryPipelineWorkerInitializer implements JvmInitializer {
|
||||||
transactionManagerLazy = registryPipelineComponent.getJpaTransactionManager();
|
transactionManagerLazy = registryPipelineComponent.getJpaTransactionManager();
|
||||||
}
|
}
|
||||||
TransactionManagerFactory.setJpaTmOnBeamWorker(transactionManagerLazy::get);
|
TransactionManagerFactory.setJpaTmOnBeamWorker(transactionManagerLazy::get);
|
||||||
// Masquerade all threads as App Engine threads, so we can create Ofy keys in the pipeline. Also
|
|
||||||
// loads all ofy entities.
|
|
||||||
new AppEngineEnvironment("s~" + registryPipelineComponent.getProjectId())
|
|
||||||
.setEnvironmentForAllThreads();
|
|
||||||
SystemPropertySetter.PRODUCTION_IMPL.setProperty(PROPERTY, "true");
|
SystemPropertySetter.PRODUCTION_IMPL.setProperty(PROPERTY, "true");
|
||||||
// Use self-allocated IDs if requested. Note that this inevitably results in duplicate IDs from
|
// Use self-allocated IDs if requested. Note that this inevitably results in duplicate IDs from
|
||||||
// multiple workers, which can also collide with existing IDs in the database. So they cannot be
|
// multiple workers, which can also collide with existing IDs in the database. So they cannot be
|
||||||
|
|
|
@ -341,24 +341,4 @@ have been in the database for a certain period of time. -->
|
||||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||||
</user-data-constraint>
|
</user-data-constraint>
|
||||||
</security-constraint>
|
</security-constraint>
|
||||||
|
|
||||||
<!-- See: https://code.google.com/p/objectify-appengine/wiki/Setup -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
<!-- Register types with Objectify. -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<filter-class>google.registry.model.ofy.OfyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
</web-app>
|
</web-app>
|
||||||
|
|
|
@ -139,24 +139,4 @@
|
||||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||||
</user-data-constraint>
|
</user-data-constraint>
|
||||||
</security-constraint>
|
</security-constraint>
|
||||||
|
|
||||||
<!-- See: https://code.google.com/p/objectify-appengine/wiki/Setup -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
<!-- Register types with Objectify. -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<filter-class>google.registry.model.ofy.OfyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
</web-app>
|
</web-app>
|
||||||
|
|
|
@ -105,24 +105,4 @@
|
||||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||||
</user-data-constraint>
|
</user-data-constraint>
|
||||||
</security-constraint>
|
</security-constraint>
|
||||||
|
|
||||||
<!-- See: https://code.google.com/p/objectify-appengine/wiki/Setup -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
<!-- Register types with Objectify. -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<filter-class>google.registry.model.ofy.OfyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
</web-app>
|
</web-app>
|
||||||
|
|
|
@ -135,24 +135,4 @@
|
||||||
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
|
||||||
</user-data-constraint>
|
</user-data-constraint>
|
||||||
</security-constraint>
|
</security-constraint>
|
||||||
|
|
||||||
<!-- See: https://code.google.com/p/objectify-appengine/wiki/Setup -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<filter-class>com.googlecode.objectify.ObjectifyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>ObjectifyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
|
|
||||||
<!-- Register types with Objectify. -->
|
|
||||||
<filter>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<filter-class>google.registry.model.ofy.OfyFilter</filter-class>
|
|
||||||
</filter>
|
|
||||||
<filter-mapping>
|
|
||||||
<filter-name>OfyFilter</filter-name>
|
|
||||||
<url-pattern>/*</url-pattern>
|
|
||||||
</filter-mapping>
|
|
||||||
</web-app>
|
</web-app>
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
// Copyright 2020 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;
|
|
||||||
|
|
||||||
import com.google.apphosting.api.ApiProxy;
|
|
||||||
import com.google.apphosting.api.ApiProxy.Environment;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.model.ofy.ObjectifyService;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Proxy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up a fake {@link Environment} so that the following operations can be performed without the
|
|
||||||
* Datastore service:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>Create Objectify {@code Keys}.
|
|
||||||
* <li>Instantiate Objectify objects.
|
|
||||||
* <li>Convert Datastore {@code Entities} to their corresponding Objectify objects.
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>User has the option to specify their desired {@code appId} string, which forms part of an
|
|
||||||
* Objectify {@code Key} and is included in the equality check. This feature makes it easy to
|
|
||||||
* compare a migrated object in SQL with the original in Objectify.
|
|
||||||
*
|
|
||||||
* <p>Note that conversion from Objectify objects to Datastore {@code Entities} still requires the
|
|
||||||
* Datastore service.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class AppEngineEnvironment {
|
|
||||||
|
|
||||||
private Environment environment;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor for use by tests.
|
|
||||||
*
|
|
||||||
* <p>All test suites must use the same appId for environments, since when tearing down we do not
|
|
||||||
* clear cached environments in spawned threads. See {@link #unsetEnvironmentForAllThreads} for
|
|
||||||
* more information.
|
|
||||||
*/
|
|
||||||
public AppEngineEnvironment() {
|
|
||||||
/**
|
|
||||||
* Use AppEngineExtension's appId here so that ofy and sql entities can be compared with {@code
|
|
||||||
* Objects#equals()}. The choice of this value does not impact functional correctness.
|
|
||||||
*/
|
|
||||||
this("test");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Constructor for use by applications, e.g., BEAM pipelines. */
|
|
||||||
public AppEngineEnvironment(String appId) {
|
|
||||||
environment = createAppEngineEnvironment(appId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnvironmentForCurrentThread() {
|
|
||||||
ApiProxy.setEnvironmentForCurrentThread(environment);
|
|
||||||
ObjectifyService.initOfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setEnvironmentForAllThreads() {
|
|
||||||
setEnvironmentForCurrentThread();
|
|
||||||
ApiProxy.setEnvironmentFactory(() -> environment);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unsetEnvironmentForCurrentThread() {
|
|
||||||
ApiProxy.clearEnvironmentForCurrentThread();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsets the test environment in all threads with best effort.
|
|
||||||
*
|
|
||||||
* <p>This method unsets the environment factory and clears the cached environment in the current
|
|
||||||
* thread (the main test runner thread). We do not clear the cache in spawned threads, even though
|
|
||||||
* they may be reused. This is not a problem as long as the appId stays the same: those threads
|
|
||||||
* are used only in AppEngine or BEAM tests, and expect the presence of an environment.
|
|
||||||
*/
|
|
||||||
public void unsetEnvironmentForAllThreads() {
|
|
||||||
unsetEnvironmentForCurrentThread();
|
|
||||||
|
|
||||||
try {
|
|
||||||
Method method = ApiProxy.class.getDeclaredMethod("clearEnvironmentFactory");
|
|
||||||
method.setAccessible(true);
|
|
||||||
method.invoke(null);
|
|
||||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a placeholder {@link Environment} that can return hardcoded AppId and Attributes. */
|
|
||||||
private static Environment createAppEngineEnvironment(String appId) {
|
|
||||||
return (Environment)
|
|
||||||
Proxy.newProxyInstance(
|
|
||||||
Environment.class.getClassLoader(),
|
|
||||||
new Class[] {Environment.class},
|
|
||||||
(Object proxy, Method method, Object[] args) -> {
|
|
||||||
switch (method.getName()) {
|
|
||||||
case "getAppId":
|
|
||||||
return appId;
|
|
||||||
case "getAttributes":
|
|
||||||
return ImmutableMap.<String, Object>of();
|
|
||||||
default:
|
|
||||||
throw new UnsupportedOperationException(method.getName());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns true if the current thread is in an App Engine Environment. */
|
|
||||||
public static boolean isInAppEngineEnvironment() {
|
|
||||||
return ApiProxy.getCurrentEnvironment() != null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,13 +18,9 @@ import static com.google.common.base.Suppliers.memoizeWithExpiration;
|
||||||
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
|
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import com.github.benmanes.caffeine.cache.CacheLoader;
|
|
||||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||||
import com.google.common.base.Supplier;
|
import com.google.common.base.Supplier;
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
|
|
||||||
/** Utility methods related to caching Datastore entities. */
|
/** Utility methods related to caching Datastore entities. */
|
||||||
public class CacheUtils {
|
public class CacheUtils {
|
||||||
|
@ -77,29 +73,4 @@ public class CacheUtils {
|
||||||
}
|
}
|
||||||
return caffeine;
|
return caffeine;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link CacheLoader} that automatically masquerade the background thread where the refresh
|
|
||||||
* action runs in to be an GAE thread.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public abstract static class AppEngineEnvironmentCacheLoader<K, V> implements CacheLoader<K, V> {
|
|
||||||
|
|
||||||
private static final AppEngineEnvironment environment = new AppEngineEnvironment();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable V reload(@NonNull K key, @NonNull V oldValue) throws Exception {
|
|
||||||
V value;
|
|
||||||
boolean isMasqueraded = false;
|
|
||||||
if (!AppEngineEnvironment.isInAppEngineEnvironment()) {
|
|
||||||
environment.setEnvironmentForCurrentThread();
|
|
||||||
isMasqueraded = true;
|
|
||||||
}
|
|
||||||
value = load(key);
|
|
||||||
if (isMasqueraded) {
|
|
||||||
environment.unsetEnvironmentForCurrentThread();
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import google.registry.config.RegistryConfig;
|
import google.registry.config.RegistryConfig;
|
||||||
import google.registry.model.CacheUtils.AppEngineEnvironmentCacheLoader;
|
|
||||||
import google.registry.model.annotations.IdAllocation;
|
import google.registry.model.annotations.IdAllocation;
|
||||||
import google.registry.model.eppcommon.StatusValue;
|
import google.registry.model.eppcommon.StatusValue;
|
||||||
import google.registry.model.transfer.TransferData;
|
import google.registry.model.transfer.TransferData;
|
||||||
|
@ -354,7 +353,7 @@ public abstract class EppResource extends UpdateAutoTimestampEntity implements B
|
||||||
}
|
}
|
||||||
|
|
||||||
static final CacheLoader<VKey<? extends EppResource>, EppResource> CACHE_LOADER =
|
static final CacheLoader<VKey<? extends EppResource>, EppResource> CACHE_LOADER =
|
||||||
new AppEngineEnvironmentCacheLoader<VKey<? extends EppResource>, EppResource>() {
|
new CacheLoader<VKey<? extends EppResource>, EppResource>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public EppResource load(VKey<? extends EppResource> key) {
|
public EppResource load(VKey<? extends EppResource> key) {
|
||||||
|
|
|
@ -22,7 +22,6 @@ import com.google.appengine.api.datastore.DatastoreServiceFactory;
|
||||||
import com.google.common.flogger.FluentLogger;
|
import com.google.common.flogger.FluentLogger;
|
||||||
import google.registry.beam.common.RegistryPipelineWorkerInitializer;
|
import google.registry.beam.common.RegistryPipelineWorkerInitializer;
|
||||||
import google.registry.config.RegistryEnvironment;
|
import google.registry.config.RegistryEnvironment;
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.model.common.DatabaseMigrationStateSchedule;
|
import google.registry.model.common.DatabaseMigrationStateSchedule;
|
||||||
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
|
import google.registry.model.common.DatabaseMigrationStateSchedule.MigrationState;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
@ -33,7 +32,6 @@ import org.joda.time.DateTime;
|
||||||
/**
|
/**
|
||||||
* Allocates a {@link long} to use as a {@code @Id}, (part) of the primary SQL key for an entity.
|
* Allocates a {@link long} to use as a {@code @Id}, (part) of the primary SQL key for an entity.
|
||||||
*/
|
*/
|
||||||
@DeleteAfterMigration
|
|
||||||
public final class IdService {
|
public final class IdService {
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||||
|
|
|
@ -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.
|
|
||||||
|
|
||||||
package google.registry.model.annotations;
|
|
||||||
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for an Objectify {@link Entity} to indicate that it should not be backed up by the
|
|
||||||
* default Datastore backup configuration (it may be backed up by something else).
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface NotBackedUp {
|
|
||||||
Reason reason();
|
|
||||||
|
|
||||||
/** Reasons why a given entity does not need to be be backed up. */
|
|
||||||
enum Reason {
|
|
||||||
/** This entity is transient by design and has only a short-term useful lifetime. */
|
|
||||||
TRANSIENT,
|
|
||||||
|
|
||||||
/** This entity's data is already regularly pulled down from an external source. */
|
|
||||||
EXTERNALLY_SOURCED,
|
|
||||||
|
|
||||||
/** This entity is generated automatically by the app and will be recreated if need be. */
|
|
||||||
AUTO_GENERATED,
|
|
||||||
|
|
||||||
/** Commit log entities are exported separately from the regular backups, by design. */
|
|
||||||
COMMIT_LOGS
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +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.
|
|
||||||
|
|
||||||
package google.registry.model.annotations;
|
|
||||||
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import java.lang.annotation.ElementType;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Annotation for an Objectify {@link Entity} to indicate that it is a "virtual entity".
|
|
||||||
*
|
|
||||||
* <p>A virtual entity type exists only to define part of the parentage key hierarchy for its child
|
|
||||||
* entities, and is never actually persisted and thus has no fields besides its ID field.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
public @interface VirtualEntity {}
|
|
|
@ -1,73 +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.
|
|
||||||
|
|
||||||
package google.registry.model.common;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static google.registry.model.IdService.allocateId;
|
|
||||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
|
||||||
|
|
||||||
import com.google.appengine.api.users.User;
|
|
||||||
import com.google.common.base.Splitter;
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import com.googlecode.objectify.annotation.Id;
|
|
||||||
import google.registry.model.ImmutableObject;
|
|
||||||
import google.registry.model.annotations.NotBackedUp;
|
|
||||||
import google.registry.model.annotations.NotBackedUp.Reason;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper class to convert email addresses to GAE user ids. It does so by persisting a User
|
|
||||||
* object with the email address to Datastore, and then immediately reading it back.
|
|
||||||
*/
|
|
||||||
@Entity
|
|
||||||
@NotBackedUp(reason = Reason.TRANSIENT)
|
|
||||||
public class GaeUserIdConverter extends ImmutableObject {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
public long id;
|
|
||||||
|
|
||||||
User user;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an email address to a GAE user id.
|
|
||||||
*
|
|
||||||
* @return Numeric GAE user id (in String form), or null if email address has no GAE id
|
|
||||||
*/
|
|
||||||
public static String convertEmailAddressToGaeUserId(String emailAddress) {
|
|
||||||
final GaeUserIdConverter gaeUserIdConverter = new GaeUserIdConverter();
|
|
||||||
gaeUserIdConverter.id = allocateId();
|
|
||||||
List<String> emailParts = Splitter.on('@').splitToList(emailAddress);
|
|
||||||
checkState(emailParts.size() == 2, "'%s' is not a valid email address", emailAddress);
|
|
||||||
gaeUserIdConverter.user = new User(emailAddress, emailParts.get(1));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Perform these operations in a transactionless context to avoid enlisting in some outer
|
|
||||||
// transaction (if any).
|
|
||||||
auditedOfy()
|
|
||||||
.doTransactionless(
|
|
||||||
() -> {
|
|
||||||
auditedOfy().saveWithoutBackup().entity(gaeUserIdConverter).now();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
// The read must be done in its own transaction to avoid reading from the session cache.
|
|
||||||
return auditedOfy()
|
|
||||||
.transactNew(() -> auditedOfy().load().entity(gaeUserIdConverter).now().user.getUserId());
|
|
||||||
} finally {
|
|
||||||
auditedOfy()
|
|
||||||
.doTransactionless(
|
|
||||||
() -> auditedOfy().deleteWithoutBackup().entity(gaeUserIdConverter).now());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
|
||||||
import static com.googlecode.objectify.ObjectifyService.ofy;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.Streams;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.Result;
|
|
||||||
import com.googlecode.objectify.cmd.DeleteType;
|
|
||||||
import com.googlecode.objectify.cmd.Deleter;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Deleter that forwards to {@code auditedOfy().delete()}, but can be augmented via subclassing to
|
|
||||||
* do custom processing on the keys to be deleted prior to their deletion.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
abstract class AugmentedDeleter implements Deleter {
|
|
||||||
private final Deleter delegate = ofy().delete();
|
|
||||||
|
|
||||||
/** Extension method to allow this Deleter to do extra work prior to the actual delete. */
|
|
||||||
protected abstract void handleDeletion(Iterable<Key<?>> keys);
|
|
||||||
|
|
||||||
private void handleDeletionStream(Stream<?> entityStream) {
|
|
||||||
handleDeletion(entityStream.map(Key::create).collect(toImmutableList()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Void> entities(Iterable<?> entities) {
|
|
||||||
handleDeletionStream(Streams.stream(entities));
|
|
||||||
return delegate.entities(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Void> entities(Object... entities) {
|
|
||||||
handleDeletionStream(Arrays.stream(entities));
|
|
||||||
return delegate.entities(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Void> entity(Object entity) {
|
|
||||||
handleDeletionStream(Stream.of(entity));
|
|
||||||
return delegate.entity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Void> key(Key<?> key) {
|
|
||||||
handleDeletion(ImmutableList.of(key));
|
|
||||||
return delegate.keys(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Void> keys(Iterable<? extends Key<?>> keys) {
|
|
||||||
// Magic to convert the type Iterable<? extends Key<?>> (a family of types which allows for
|
|
||||||
// homogeneous iterables of a fixed Key<T> type, e.g. List<Key<Lock>>, and is convenient for
|
|
||||||
// callers) into the type Iterable<Key<?>> (a concrete type of heterogeneous keys, which is
|
|
||||||
// convenient for users).
|
|
||||||
handleDeletion(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.keys(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Result<Void> keys(Key<?>... keys) {
|
|
||||||
handleDeletion(Arrays.asList(keys));
|
|
||||||
return delegate.keys(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Augmenting this gets ugly; you can always just use keys(Key.create(...)) instead. */
|
|
||||||
@Override
|
|
||||||
public DeleteType type(Class<?> clazz) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.googlecode.objectify.ObjectifyService.ofy;
|
|
||||||
|
|
||||||
import com.google.appengine.api.datastore.Entity;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.Result;
|
|
||||||
import com.googlecode.objectify.cmd.Saver;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Saver that forwards to {@code ofy().save()}, but can be augmented via subclassing to do custom
|
|
||||||
* processing on the entities to be saved prior to their saving.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
abstract class AugmentedSaver implements Saver {
|
|
||||||
private final Saver delegate = ofy().save();
|
|
||||||
|
|
||||||
/** Extension method to allow this Saver to do extra work prior to the actual save. */
|
|
||||||
protected abstract void handleSave(Iterable<?> entities);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <E> Result<Map<Key<E>, E>> entities(Iterable<E> entities) {
|
|
||||||
handleSave(entities);
|
|
||||||
return delegate.entities(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SafeVarargs
|
|
||||||
public final <E> Result<Map<Key<E>, E>> entities(E... entities) {
|
|
||||||
handleSave(Arrays.asList(entities));
|
|
||||||
return delegate.entities(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <E> Result<Key<E>> entity(E entity) {
|
|
||||||
handleSave(ImmutableList.of(entity));
|
|
||||||
return delegate.entity(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Entity toEntity(Object pojo) {
|
|
||||||
// No call to the extension method, since toEntity() doesn't do any actual saving.
|
|
||||||
return delegate.toEntity(pojo);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import google.registry.model.ImmutableObject;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.util.Clock;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/** Wrapper for {@link Supplier} that associates a time with each attempt. */
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class CommitLoggedWork<R> implements Runnable {
|
|
||||||
|
|
||||||
private final Supplier<R> work;
|
|
||||||
private final Clock clock;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary place to store the result of a non-void work.
|
|
||||||
*
|
|
||||||
* <p>We don't want to return the result directly because we are going to try to recover from a
|
|
||||||
* {@link com.google.appengine.api.datastore.DatastoreTimeoutException} deep inside Objectify when
|
|
||||||
* it tries to commit the transaction. When an exception is thrown the return value would be lost,
|
|
||||||
* but sometimes we will be able to determine that we actually succeeded despite the timeout, and
|
|
||||||
* we'll want to get the result.
|
|
||||||
*/
|
|
||||||
private R result;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Temporary place to store the mutations belonging to the commit log manifest.
|
|
||||||
*
|
|
||||||
* <p>These are used along with the manifest to determine whether a transaction succeeded.
|
|
||||||
*/
|
|
||||||
protected ImmutableSet<ImmutableObject> mutations = ImmutableSet.of();
|
|
||||||
|
|
||||||
/** Lifecycle marker to track whether {@link #run} has been called. */
|
|
||||||
private boolean runCalled;
|
|
||||||
|
|
||||||
CommitLoggedWork(Supplier<R> work, Clock clock) {
|
|
||||||
this.work = work;
|
|
||||||
this.clock = clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TransactionInfo createNewTransactionInfo() {
|
|
||||||
return new TransactionInfo(clock.nowUtc());
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean hasRun() {
|
|
||||||
return runCalled;
|
|
||||||
}
|
|
||||||
|
|
||||||
R getResult() {
|
|
||||||
checkState(runCalled, "Cannot call getResult() before run()");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// The previous time will generally be null, except when using transactNew.
|
|
||||||
TransactionInfo previous = Ofy.TRANSACTION_INFO.get();
|
|
||||||
// Set the time to be used for "now" within the transaction.
|
|
||||||
try {
|
|
||||||
Ofy.TRANSACTION_INFO.set(createNewTransactionInfo());
|
|
||||||
result = work.get();
|
|
||||||
} finally {
|
|
||||||
Ofy.TRANSACTION_INFO.set(previous);
|
|
||||||
}
|
|
||||||
runCalled = true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
|
||||||
import static com.googlecode.objectify.ObjectifyService.factory;
|
|
||||||
import static google.registry.util.TypeUtils.hasAnnotation;
|
|
||||||
|
|
||||||
import com.google.appengine.api.datastore.AsyncDatastoreService;
|
|
||||||
import com.google.appengine.api.datastore.DatastoreServiceConfig;
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import com.google.common.collect.Streams;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.ObjectifyFactory;
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import com.googlecode.objectify.annotation.EntitySubclass;
|
|
||||||
import google.registry.config.RegistryEnvironment;
|
|
||||||
import google.registry.model.Buildable;
|
|
||||||
import google.registry.model.ImmutableObject;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.model.common.GaeUserIdConverter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An instance of Ofy, obtained via {@code #auditedOfy()}, should be used to access all persistable
|
|
||||||
* objects. The class contains a static initializer to call factory().register(...) on all
|
|
||||||
* persistable objects in this package.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class ObjectifyService {
|
|
||||||
|
|
||||||
/** A singleton instance of our Ofy wrapper. */
|
|
||||||
private static final Ofy OFY = new Ofy(null);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the singleton {@link Ofy} instance, signifying that the caller has been audited for the
|
|
||||||
* Registry 3.0 conversion.
|
|
||||||
*/
|
|
||||||
public static Ofy auditedOfy() {
|
|
||||||
return OFY;
|
|
||||||
}
|
|
||||||
|
|
||||||
static {
|
|
||||||
initOfyOnce();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Ensures that Objectify has been fully initialized. */
|
|
||||||
public static void initOfy() {
|
|
||||||
// This method doesn't actually do anything; it's here so that callers have something to call
|
|
||||||
// to ensure that the static initialization of ObjectifyService has been performed (which Java
|
|
||||||
// guarantees will happen exactly once, before any static methods are invoked).
|
|
||||||
//
|
|
||||||
// See JLS section 12.4: http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs static initialization for Objectify to register types and do other setup.
|
|
||||||
*
|
|
||||||
* <p>This method is non-idempotent, so it should only be called exactly once, which is achieved
|
|
||||||
* by calling it from this class's static initializer block.
|
|
||||||
*/
|
|
||||||
private static void initOfyOnce() {
|
|
||||||
// Set an ObjectifyFactory that uses our extended ObjectifyImpl.
|
|
||||||
// The "false" argument means that we are not using the v5-style Objectify embedded entities.
|
|
||||||
com.googlecode.objectify.ObjectifyService.setFactory(
|
|
||||||
new ObjectifyFactory(false) {
|
|
||||||
@Override
|
|
||||||
protected AsyncDatastoreService createRawAsyncDatastoreService(
|
|
||||||
DatastoreServiceConfig cfg) {
|
|
||||||
// In the unit test environment, wrap the Datastore service in a proxy that can be used
|
|
||||||
// to examine the number of requests sent to Datastore.
|
|
||||||
AsyncDatastoreService service = super.createRawAsyncDatastoreService(cfg);
|
|
||||||
return RegistryEnvironment.get().equals(RegistryEnvironment.UNITTEST)
|
|
||||||
? new RequestCapturingAsyncDatastoreService(service)
|
|
||||||
: service;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
registerEntityClasses(ImmutableSet.of(GaeUserIdConverter.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Register classes that can be persisted via Objectify as Datastore entities. */
|
|
||||||
private static void registerEntityClasses(
|
|
||||||
ImmutableSet<Class<? extends ImmutableObject>> entityClasses) {
|
|
||||||
// Register all the @Entity classes before any @EntitySubclass classes so that we can check
|
|
||||||
// that every @Entity registration is a new kind and every @EntitySubclass registration is not.
|
|
||||||
// This is future-proofing for Objectify 5.x where the registration logic gets less lenient.
|
|
||||||
|
|
||||||
for (Class<?> clazz :
|
|
||||||
Streams.concat(
|
|
||||||
entityClasses.stream().filter(hasAnnotation(Entity.class)),
|
|
||||||
entityClasses.stream().filter(hasAnnotation(Entity.class).negate()))
|
|
||||||
.collect(toImmutableSet())) {
|
|
||||||
String kind = Key.getKind(clazz);
|
|
||||||
boolean registered = factory().getMetadata(kind) != null;
|
|
||||||
if (clazz.isAnnotationPresent(Entity.class)) {
|
|
||||||
// Objectify silently replaces current registration for a given kind string when a different
|
|
||||||
// class is registered again for this kind. For simplicity's sake, throw an exception on any
|
|
||||||
// re-registration.
|
|
||||||
checkState(
|
|
||||||
!registered,
|
|
||||||
"Kind '%s' already registered, cannot register new @Entity %s",
|
|
||||||
kind,
|
|
||||||
clazz.getCanonicalName());
|
|
||||||
} else if (clazz.isAnnotationPresent(EntitySubclass.class)) {
|
|
||||||
// Ensure that any @EntitySubclass classes have also had their parent @Entity registered,
|
|
||||||
// which Objectify nominally requires but doesn't enforce in 4.x (though it may in 5.x).
|
|
||||||
checkState(
|
|
||||||
registered,
|
|
||||||
"No base entity for kind '%s' registered yet, cannot register new @EntitySubclass %s",
|
|
||||||
kind,
|
|
||||||
clazz.getCanonicalName());
|
|
||||||
}
|
|
||||||
com.googlecode.objectify.ObjectifyService.register(clazz);
|
|
||||||
// Autogenerated ids make the commit log code very difficult since we won't always be able
|
|
||||||
// to create a key for an entity immediately when requesting a save. So, we require such
|
|
||||||
// entities to implement google.registry.model.Buildable as its build() function allocates the
|
|
||||||
// id to the entity.
|
|
||||||
if (factory().getMetadata(clazz).getKeyMetadata().isIdGeneratable()) {
|
|
||||||
checkState(
|
|
||||||
Buildable.class.isAssignableFrom(clazz),
|
|
||||||
"Can't register %s: Entity with autogenerated ids (@Id on a Long) must implement"
|
|
||||||
+ " google.registry.model.Buildable.",
|
|
||||||
kind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,374 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static com.google.common.collect.Maps.uniqueIndex;
|
|
||||||
import static com.googlecode.objectify.ObjectifyService.ofy;
|
|
||||||
import static google.registry.config.RegistryConfig.getBaseOfyRetryDuration;
|
|
||||||
|
|
||||||
import com.google.appengine.api.datastore.DatastoreFailureException;
|
|
||||||
import com.google.appengine.api.datastore.DatastoreTimeoutException;
|
|
||||||
import com.google.appengine.api.taskqueue.TransientFailureException;
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.collect.Streams;
|
|
||||||
import com.google.common.flogger.FluentLogger;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.Objectify;
|
|
||||||
import com.googlecode.objectify.ObjectifyFactory;
|
|
||||||
import com.googlecode.objectify.cmd.Deleter;
|
|
||||||
import com.googlecode.objectify.cmd.Loader;
|
|
||||||
import com.googlecode.objectify.cmd.Saver;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.model.annotations.NotBackedUp;
|
|
||||||
import google.registry.model.annotations.VirtualEntity;
|
|
||||||
import google.registry.model.ofy.ReadOnlyWork.KillTransactionException;
|
|
||||||
import google.registry.util.Clock;
|
|
||||||
import google.registry.util.NonFinalForTesting;
|
|
||||||
import google.registry.util.Sleeper;
|
|
||||||
import google.registry.util.SystemClock;
|
|
||||||
import google.registry.util.SystemSleeper;
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
import org.joda.time.Duration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A wrapper around ofy().
|
|
||||||
*
|
|
||||||
* <p>The primary purpose of this class is to add functionality to support commit logs. It is
|
|
||||||
* simpler to wrap {@link Objectify} rather than extend it because this way we can remove some
|
|
||||||
* methods that we don't really want exposed and add some shortcuts.
|
|
||||||
*/
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class Ofy {
|
|
||||||
|
|
||||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
|
||||||
|
|
||||||
/** Default clock for transactions that don't provide one. */
|
|
||||||
@NonFinalForTesting
|
|
||||||
static Clock clock = new SystemClock();
|
|
||||||
|
|
||||||
/** Default sleeper for transactions that don't provide one. */
|
|
||||||
@NonFinalForTesting
|
|
||||||
static Sleeper sleeper = new SystemSleeper();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An injected clock that overrides the static clock.
|
|
||||||
*
|
|
||||||
* <p>Eventually the static clock should go away when we are 100% injected, but for now we need to
|
|
||||||
* preserve the old way of overriding the clock in tests by changing the static field.
|
|
||||||
*/
|
|
||||||
private final Clock injectedClock;
|
|
||||||
|
|
||||||
/** Retry for 8^2 * 100ms = ~25 seconds. */
|
|
||||||
private static final int NUM_RETRIES = 8;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public Ofy(Clock injectedClock) {
|
|
||||||
this.injectedClock = injectedClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Thread local transaction info. There can only be one active transaction on a thread at a given
|
|
||||||
* time, and this will hold metadata for it.
|
|
||||||
*/
|
|
||||||
static final ThreadLocal<TransactionInfo> TRANSACTION_INFO = new ThreadLocal<>();
|
|
||||||
|
|
||||||
/** Returns the wrapped Objectify's ObjectifyFactory. */
|
|
||||||
public ObjectifyFactory factory() {
|
|
||||||
return ofy().factory();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clears the session cache. */
|
|
||||||
public void clearSessionCache() {
|
|
||||||
ofy().clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean inTransaction() {
|
|
||||||
return ofy().getTransaction() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void assertInTransaction() {
|
|
||||||
checkState(inTransaction(), "Must be called in a transaction");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Load from Datastore. */
|
|
||||||
public Loader load() {
|
|
||||||
return ofy().load();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete, augmented to enroll the deleted entities in a commit log.
|
|
||||||
*
|
|
||||||
* <p>We only allow this in transactions so commit logs can be written in tandem with the delete.
|
|
||||||
*/
|
|
||||||
public Deleter delete() {
|
|
||||||
return deleteIgnoringReadOnlyWithBackup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete, without any augmentations except to check that we're not saving any virtual entities.
|
|
||||||
*
|
|
||||||
* <p>No backups get written.
|
|
||||||
*/
|
|
||||||
public Deleter deleteWithoutBackup() {
|
|
||||||
return deleteIgnoringReadOnlyWithoutBackup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save, augmented to enroll the saved entities in a commit log and to check that we're not saving
|
|
||||||
* virtual entities.
|
|
||||||
*
|
|
||||||
* <p>We only allow this in transactions so commit logs can be written in tandem with the save.
|
|
||||||
*/
|
|
||||||
public Saver save() {
|
|
||||||
return saveIgnoringReadOnlyWithBackup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save, without any augmentations except to check that we're not saving any virtual entities.
|
|
||||||
*
|
|
||||||
* <p>No backups get written.
|
|
||||||
*/
|
|
||||||
public Saver saveWithoutBackup() {
|
|
||||||
return saveIgnoringReadOnlyWithoutBackup();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Save, ignoring any backups or any read-only settings. */
|
|
||||||
public Saver saveIgnoringReadOnlyWithoutBackup() {
|
|
||||||
return new AugmentedSaver() {
|
|
||||||
@Override
|
|
||||||
protected void handleSave(Iterable<?> entities) {
|
|
||||||
checkProhibitedAnnotations(entities, VirtualEntity.class);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delete, ignoring any backups or any read-only settings. */
|
|
||||||
public Deleter deleteIgnoringReadOnlyWithoutBackup() {
|
|
||||||
return new AugmentedDeleter() {
|
|
||||||
@Override
|
|
||||||
protected void handleDeletion(Iterable<Key<?>> keys) {
|
|
||||||
checkProhibitedAnnotations(keys, VirtualEntity.class);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Save, ignoring any read-only settings (but still write commit logs). */
|
|
||||||
public Saver saveIgnoringReadOnlyWithBackup() {
|
|
||||||
return new AugmentedSaver() {
|
|
||||||
@Override
|
|
||||||
protected void handleSave(Iterable<?> entities) {
|
|
||||||
assertInTransaction();
|
|
||||||
checkState(
|
|
||||||
Streams.stream(entities).allMatch(Objects::nonNull), "Can't save a null entity.");
|
|
||||||
checkProhibitedAnnotations(entities, NotBackedUp.class, VirtualEntity.class);
|
|
||||||
ImmutableMap<Key<?>, ?> keysToEntities = uniqueIndex(entities, Key::create);
|
|
||||||
TRANSACTION_INFO.get().putSaves(keysToEntities);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Delete, ignoring any read-only settings (but still write commit logs). */
|
|
||||||
public Deleter deleteIgnoringReadOnlyWithBackup() {
|
|
||||||
return new AugmentedDeleter() {
|
|
||||||
@Override
|
|
||||||
protected void handleDeletion(Iterable<Key<?>> keys) {
|
|
||||||
assertInTransaction();
|
|
||||||
checkState(Streams.stream(keys).allMatch(Objects::nonNull), "Can't delete a null key.");
|
|
||||||
checkProhibitedAnnotations(keys, NotBackedUp.class, VirtualEntity.class);
|
|
||||||
TRANSACTION_INFO.get().putDeletes(keys);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private Clock getClock() {
|
|
||||||
return injectedClock == null ? clock : injectedClock;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Execute a transaction. */
|
|
||||||
<R> R transact(Supplier<R> work) {
|
|
||||||
// If we are already in a transaction, don't wrap in a CommitLoggedWork.
|
|
||||||
return inTransaction() ? work.get() : transactNew(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a transaction.
|
|
||||||
*
|
|
||||||
* <p>This overload is used for transactions that don't return a value, formerly implemented using
|
|
||||||
* VoidWork.
|
|
||||||
*/
|
|
||||||
void transact(Runnable work) {
|
|
||||||
transact(
|
|
||||||
() -> {
|
|
||||||
work.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Pause the current transaction (if any) and complete this one before returning to it. */
|
|
||||||
public <R> R transactNew(Supplier<R> work) {
|
|
||||||
// Wrap the Work in a CommitLoggedWork so that we can give transactions a frozen view of time.
|
|
||||||
return transactCommitLoggedWork(new CommitLoggedWork<>(work, getClock()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pause the current transaction (if any) and complete this one before returning to it.
|
|
||||||
*
|
|
||||||
* <p>This overload is used for transactions that don't return a value, formerly implemented using
|
|
||||||
* VoidWork.
|
|
||||||
*/
|
|
||||||
void transactNew(Runnable work) {
|
|
||||||
transactNew(
|
|
||||||
() -> {
|
|
||||||
work.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transact with commit logs and retry with exponential backoff.
|
|
||||||
*
|
|
||||||
* <p>This method is broken out from {@link #transactNew(Supplier)} for testing purposes.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
<R> R transactCommitLoggedWork(CommitLoggedWork<R> work) {
|
|
||||||
long baseRetryMillis = getBaseOfyRetryDuration().getMillis();
|
|
||||||
for (long attempt = 0, sleepMillis = baseRetryMillis;
|
|
||||||
true;
|
|
||||||
attempt++, sleepMillis *= 2) {
|
|
||||||
try {
|
|
||||||
ofy().transactNew(() -> {
|
|
||||||
work.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
return work.getResult();
|
|
||||||
} catch (TransientFailureException
|
|
||||||
| DatastoreTimeoutException
|
|
||||||
| DatastoreFailureException e) {
|
|
||||||
// TransientFailureExceptions come from task queues and always mean nothing committed.
|
|
||||||
// TimestampInversionExceptions are thrown by our code and are always retryable as well.
|
|
||||||
// However, Datastore exceptions might get thrown even if the transaction succeeded.
|
|
||||||
if ((e instanceof DatastoreTimeoutException || e instanceof DatastoreFailureException)
|
|
||||||
&& work.hasRun()) {
|
|
||||||
return work.getResult();
|
|
||||||
}
|
|
||||||
if (attempt == NUM_RETRIES) {
|
|
||||||
throw e; // Give up.
|
|
||||||
}
|
|
||||||
sleeper.sleepUninterruptibly(Duration.millis(sleepMillis));
|
|
||||||
logger.atInfo().withCause(e).log(
|
|
||||||
"Retrying %s, attempt %d.", e.getClass().getSimpleName(), attempt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A read-only transaction is useful to get strongly consistent reads at a shared timestamp. */
|
|
||||||
<R> R transactNewReadOnly(Supplier<R> work) {
|
|
||||||
ReadOnlyWork<R> readOnlyWork = new ReadOnlyWork<>(work, getClock());
|
|
||||||
try {
|
|
||||||
ofy().transactNew(() -> {
|
|
||||||
readOnlyWork.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
} catch (TransientFailureException | DatastoreTimeoutException | DatastoreFailureException e) {
|
|
||||||
// These are always retryable for a read-only operation.
|
|
||||||
return transactNewReadOnly(work);
|
|
||||||
} catch (KillTransactionException e) {
|
|
||||||
// Expected; we killed the transaction as a safety measure, and now we can return the result.
|
|
||||||
return readOnlyWork.getResult();
|
|
||||||
}
|
|
||||||
throw new AssertionError(); // How on earth did we get here?
|
|
||||||
}
|
|
||||||
|
|
||||||
void transactNewReadOnly(Runnable work) {
|
|
||||||
transactNewReadOnly(
|
|
||||||
() -> {
|
|
||||||
work.run();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Execute some work in a transactionless context. */
|
|
||||||
public <R> R doTransactionless(Supplier<R> work) {
|
|
||||||
try {
|
|
||||||
com.googlecode.objectify.ObjectifyService.push(
|
|
||||||
com.googlecode.objectify.ObjectifyService.ofy().transactionless());
|
|
||||||
return work.get();
|
|
||||||
} finally {
|
|
||||||
com.googlecode.objectify.ObjectifyService.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute some work with a fresh session cache.
|
|
||||||
*
|
|
||||||
* <p>This is useful in cases where we want to load the latest possible data from Datastore but
|
|
||||||
* don't need point-in-time consistency across loads and consequently don't need a transaction.
|
|
||||||
* Note that unlike a transaction's fresh session cache, the contents of this cache will be
|
|
||||||
* discarded once the work completes, rather than being propagated into the enclosing session.
|
|
||||||
*/
|
|
||||||
public <R> R doWithFreshSessionCache(Supplier<R> work) {
|
|
||||||
try {
|
|
||||||
com.googlecode.objectify.ObjectifyService.push(
|
|
||||||
com.googlecode.objectify.ObjectifyService.factory().begin());
|
|
||||||
return work.get();
|
|
||||||
} finally {
|
|
||||||
com.googlecode.objectify.ObjectifyService.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get the time associated with the start of this particular transaction attempt. */
|
|
||||||
DateTime getTransactionTime() {
|
|
||||||
assertInTransaction();
|
|
||||||
return TRANSACTION_INFO.get().transactionTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the @Entity-annotated base class for an object that is either an {@code Key<?>} or an
|
|
||||||
* object of an entity class registered with Objectify.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
static Class<?> getBaseEntityClassFromEntityOrKey(Object entityOrKey) {
|
|
||||||
// Convert both keys and entities into keys, so that we get consistent behavior in either case.
|
|
||||||
Key<?> key = (entityOrKey instanceof Key<?> ? (Key<?>) entityOrKey : Key.create(entityOrKey));
|
|
||||||
// Get the entity class associated with this key's kind, which should be the base @Entity class
|
|
||||||
// from which the kind name is derived. Don't be tempted to use getMetadata(String kind) or
|
|
||||||
// getMetadataForEntity(T pojo) instead; the former won't throw an exception for an unknown
|
|
||||||
// kind (it just returns null) and the latter will return the @EntitySubclass if there is one.
|
|
||||||
return ofy().factory().getMetadata(key).getEntityClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks that the base @Entity classes for the provided entities or keys don't have any of the
|
|
||||||
* specified forbidden annotations.
|
|
||||||
*/
|
|
||||||
@SafeVarargs
|
|
||||||
private static void checkProhibitedAnnotations(
|
|
||||||
Iterable<?> entitiesOrKeys, Class<? extends Annotation>... annotations) {
|
|
||||||
for (Object entityOrKey : entitiesOrKeys) {
|
|
||||||
Class<?> entityClass = getBaseEntityClassFromEntityOrKey(entityOrKey);
|
|
||||||
for (Class<? extends Annotation> annotation : annotations) {
|
|
||||||
checkArgument(!entityClass.isAnnotationPresent(annotation),
|
|
||||||
"Can't save/delete a @%s entity: %s", annotation.getSimpleName(), entityClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.FilterConfig;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
|
|
||||||
/** A filter that statically registers types with Objectify. */
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class OfyFilter implements Filter {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(FilterConfig config) {
|
|
||||||
// Make sure that we've registered all types before we do anything else with Objectify.
|
|
||||||
ObjectifyService.initOfy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void destroy() {}
|
|
||||||
}
|
|
|
@ -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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.util.Clock;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
/** Wrapper for {@link Supplier} that disallows mutations and fails the transaction at the end. */
|
|
||||||
@DeleteAfterMigration
|
|
||||||
class ReadOnlyWork<R> extends CommitLoggedWork<R> {
|
|
||||||
|
|
||||||
ReadOnlyWork(Supplier<R> work, Clock clock) {
|
|
||||||
super(work, clock);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TransactionInfo createNewTransactionInfo() {
|
|
||||||
return super.createNewTransactionInfo().setReadOnly();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
super.run();
|
|
||||||
throw new KillTransactionException();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Exception used to exit a transaction. */
|
|
||||||
static class KillTransactionException extends RuntimeException {}
|
|
||||||
}
|
|
|
@ -1,194 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static java.util.Collections.synchronizedList;
|
|
||||||
|
|
||||||
import com.google.appengine.api.datastore.AsyncDatastoreService;
|
|
||||||
import com.google.appengine.api.datastore.DatastoreAttributes;
|
|
||||||
import com.google.appengine.api.datastore.Entity;
|
|
||||||
import com.google.appengine.api.datastore.Index;
|
|
||||||
import com.google.appengine.api.datastore.Index.IndexState;
|
|
||||||
import com.google.appengine.api.datastore.Key;
|
|
||||||
import com.google.appengine.api.datastore.KeyRange;
|
|
||||||
import com.google.appengine.api.datastore.PreparedQuery;
|
|
||||||
import com.google.appengine.api.datastore.Query;
|
|
||||||
import com.google.appengine.api.datastore.Transaction;
|
|
||||||
import com.google.appengine.api.datastore.TransactionOptions;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.Future;
|
|
||||||
|
|
||||||
/** A proxy for {@link AsyncDatastoreService} that exposes call counts. */
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class RequestCapturingAsyncDatastoreService implements AsyncDatastoreService {
|
|
||||||
|
|
||||||
private final AsyncDatastoreService delegate;
|
|
||||||
|
|
||||||
// Each outer lists represents Datastore operations, with inner lists representing the keys or
|
|
||||||
// entities involved in that operation. We use static lists because we care about overall calls to
|
|
||||||
// Datastore, not calls via a specific instance of the service.
|
|
||||||
|
|
||||||
private static List<List<Key>> reads = synchronizedList(new ArrayList<List<Key>>());
|
|
||||||
private static List<List<Key>> deletes = synchronizedList(new ArrayList<List<Key>>());
|
|
||||||
private static List<List<Entity>> puts = synchronizedList(new ArrayList<List<Entity>>());
|
|
||||||
|
|
||||||
RequestCapturingAsyncDatastoreService(AsyncDatastoreService delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<List<Key>> getReads() {
|
|
||||||
return reads;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<List<Key>> getDeletes() {
|
|
||||||
return deletes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<List<Entity>> getPuts() {
|
|
||||||
return puts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Transaction> getActiveTransactions() {
|
|
||||||
return delegate.getActiveTransactions();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Transaction getCurrentTransaction() {
|
|
||||||
return delegate.getCurrentTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Transaction getCurrentTransaction(Transaction transaction) {
|
|
||||||
return delegate.getCurrentTransaction(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PreparedQuery prepare(Query query) {
|
|
||||||
return delegate.prepare(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PreparedQuery prepare(Transaction transaction, Query query) {
|
|
||||||
return delegate.prepare(transaction, query);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<KeyRange> allocateIds(String kind, long num) {
|
|
||||||
return delegate.allocateIds(kind, num);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<KeyRange> allocateIds(Key parent, String kind, long num) {
|
|
||||||
return delegate.allocateIds(parent, kind, num);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Transaction> beginTransaction() {
|
|
||||||
return delegate.beginTransaction();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Transaction> beginTransaction(TransactionOptions transaction) {
|
|
||||||
return delegate.beginTransaction(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Void> delete(Key... keys) {
|
|
||||||
deletes.add(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.delete(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Void> delete(Iterable<Key> keys) {
|
|
||||||
deletes.add(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.delete(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Void> delete(Transaction transaction, Key... keys) {
|
|
||||||
deletes.add(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.delete(transaction, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Void> delete(Transaction transaction, Iterable<Key> keys) {
|
|
||||||
deletes.add(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.delete(transaction, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Entity> get(Key key) {
|
|
||||||
reads.add(ImmutableList.of(key));
|
|
||||||
return delegate.get(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Map<Key, Entity>> get(Iterable<Key> keys) {
|
|
||||||
reads.add(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.get(keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Entity> get(Transaction transaction, Key key) {
|
|
||||||
reads.add(ImmutableList.of(key));
|
|
||||||
return delegate.get(transaction, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Map<Key, Entity>> get(Transaction transaction, Iterable<Key> keys) {
|
|
||||||
reads.add(ImmutableList.copyOf(keys));
|
|
||||||
return delegate.get(transaction, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<DatastoreAttributes> getDatastoreAttributes() {
|
|
||||||
return delegate.getDatastoreAttributes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Map<Index, IndexState>> getIndexes() {
|
|
||||||
return delegate.getIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Key> put(Entity entity) {
|
|
||||||
puts.add(ImmutableList.of(entity));
|
|
||||||
return delegate.put(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<List<Key>> put(Iterable<Entity> entities) {
|
|
||||||
puts.add(ImmutableList.copyOf(entities));
|
|
||||||
return delegate.put(entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<Key> put(Transaction transaction, Entity entity) {
|
|
||||||
puts.add(ImmutableList.of(entity));
|
|
||||||
return delegate.put(transaction, entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Future<List<Key>> put(Transaction transaction, Iterable<Entity> entities) {
|
|
||||||
puts.add(ImmutableList.copyOf(entities));
|
|
||||||
return delegate.put(transaction, entities);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
import static com.google.common.collect.Maps.toMap;
|
|
||||||
|
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.joda.time.DateTime;
|
|
||||||
|
|
||||||
/** Metadata for an {@link Ofy} transaction that saves commit logs. */
|
|
||||||
@DeleteAfterMigration
|
|
||||||
public class TransactionInfo {
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
public enum Delete {
|
|
||||||
SENTINEL
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Logical "now" of the transaction. */
|
|
||||||
DateTime transactionTime;
|
|
||||||
|
|
||||||
/** Whether this is a read-only transaction. */
|
|
||||||
private boolean readOnly;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accumulator of save/delete operations performed in transaction.
|
|
||||||
*
|
|
||||||
* <p>The {@link ImmutableMap} builder provides us the benefit of not permitting duplicates.
|
|
||||||
* This allows us to avoid potential race conditions where the same key is mutated twice in a
|
|
||||||
* transaction.
|
|
||||||
*/
|
|
||||||
private final ImmutableMap.Builder<Key<?>, Object> changesBuilder = new ImmutableMap.Builder<>();
|
|
||||||
|
|
||||||
TransactionInfo(DateTime now) {
|
|
||||||
this.transactionTime = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
TransactionInfo setReadOnly() {
|
|
||||||
this.readOnly = true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void assertNotReadOnly() {
|
|
||||||
checkState(!readOnly, "This is a read only transaction.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void putSaves(Map<Key<?>, ?> keysToEntities) {
|
|
||||||
assertNotReadOnly();
|
|
||||||
changesBuilder.putAll(keysToEntities);
|
|
||||||
}
|
|
||||||
|
|
||||||
void putDeletes(Iterable<Key<?>> keys) {
|
|
||||||
assertNotReadOnly();
|
|
||||||
changesBuilder.putAll(toMap(keys, k -> Delete.SENTINEL));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -75,7 +75,7 @@ public class VKey<T> extends ImmutableObject implements Serializable {
|
||||||
* Constructs a {@link VKey} for an {@link EppResource } from the string representation.
|
* Constructs a {@link VKey} for an {@link EppResource } from the string representation.
|
||||||
*
|
*
|
||||||
* <p>The string representation is obtained from the {@link #stringify()} function and like this:
|
* <p>The string representation is obtained from the {@link #stringify()} function and like this:
|
||||||
* {@code kind:TestObject@sql:rO0ABXQAA2Zvbw}
|
* {@code kind:SomeEntity@sql:rO0ABXQAA2Zvbw}
|
||||||
*/
|
*/
|
||||||
public static <T extends EppResource> VKey<T> createEppVKeyFromString(String keyString) {
|
public static <T extends EppResource> VKey<T> createEppVKeyFromString(String keyString) {
|
||||||
ImmutableMap<String, String> kvs =
|
ImmutableMap<String, String> kvs =
|
||||||
|
|
|
@ -149,14 +149,6 @@ public abstract class QueryComposer<T> {
|
||||||
/**
|
/**
|
||||||
* Enum used to specify comparison operations, e.g. {@code where("fieldName", Comparator.NE,
|
* Enum used to specify comparison operations, e.g. {@code where("fieldName", Comparator.NE,
|
||||||
* "someval")'}.
|
* "someval")'}.
|
||||||
*
|
|
||||||
* <p>These contain values that specify the comparison behavior for both objectify and criteria
|
|
||||||
* queries. For objectify, we provide a string to be appended to the field name in a {@code
|
|
||||||
* filter()} expression. For criteria queries we provide a function that knows how to obtain a
|
|
||||||
* {@link WhereOperator} from a {@link CriteriaBuilder}.
|
|
||||||
*
|
|
||||||
* <p>Note that the objectify strings for comparators other than equality are preceded by a space
|
|
||||||
* because {@code filter()} expects the fieldname to be separated from the operator by a space.
|
|
||||||
*/
|
*/
|
||||||
public enum Comparator {
|
public enum Comparator {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,12 +19,12 @@ import static google.registry.config.RegistryConfig.ConfigModule.TmchCaMode.PROD
|
||||||
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
|
import static google.registry.config.RegistryConfig.getSingletonCacheRefreshDuration;
|
||||||
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
import static google.registry.util.ResourceUtils.readResourceUtf8;
|
||||||
|
|
||||||
|
import com.github.benmanes.caffeine.cache.CacheLoader;
|
||||||
import com.github.benmanes.caffeine.cache.LoadingCache;
|
import com.github.benmanes.caffeine.cache.LoadingCache;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
|
import google.registry.config.RegistryConfig.ConfigModule.TmchCaMode;
|
||||||
import google.registry.model.CacheUtils;
|
import google.registry.model.CacheUtils;
|
||||||
import google.registry.model.CacheUtils.AppEngineEnvironmentCacheLoader;
|
|
||||||
import google.registry.model.tmch.TmchCrl;
|
import google.registry.model.tmch.TmchCrl;
|
||||||
import google.registry.util.Clock;
|
import google.registry.util.Clock;
|
||||||
import google.registry.util.X509Utils;
|
import google.registry.util.X509Utils;
|
||||||
|
@ -78,7 +78,7 @@ public final class TmchCertificateAuthority {
|
||||||
private static final LoadingCache<TmchCaMode, X509CRL> CRL_CACHE =
|
private static final LoadingCache<TmchCaMode, X509CRL> CRL_CACHE =
|
||||||
CacheUtils.newCacheBuilder(getSingletonCacheRefreshDuration())
|
CacheUtils.newCacheBuilder(getSingletonCacheRefreshDuration())
|
||||||
.build(
|
.build(
|
||||||
new AppEngineEnvironmentCacheLoader<TmchCaMode, X509CRL>() {
|
new CacheLoader<TmchCaMode, X509CRL>() {
|
||||||
@Override
|
@Override
|
||||||
public X509CRL load(final TmchCaMode tmchCaMode) throws GeneralSecurityException {
|
public X509CRL load(final TmchCaMode tmchCaMode) throws GeneralSecurityException {
|
||||||
Optional<TmchCrl> storedCrl = TmchCrl.get();
|
Optional<TmchCrl> storedCrl = TmchCrl.get();
|
||||||
|
|
|
@ -1,80 +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.
|
|
||||||
|
|
||||||
package google.registry.tools;
|
|
||||||
|
|
||||||
import com.google.appengine.api.datastore.Entity;
|
|
||||||
import com.google.auto.value.AutoValue;
|
|
||||||
import com.google.common.base.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps {@link Entity} for ease of processing in collections.
|
|
||||||
*
|
|
||||||
* <p>Note that the {@link #hashCode}/{@link #equals} methods are based on both the entity's key and
|
|
||||||
* its properties.
|
|
||||||
*/
|
|
||||||
final class EntityWrapper {
|
|
||||||
private static final String TEST_ENTITY_KIND = "TestEntity";
|
|
||||||
|
|
||||||
private final Entity entity;
|
|
||||||
|
|
||||||
EntityWrapper(Entity entity) {
|
|
||||||
this.entity = entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entity getEntity() {
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object that) {
|
|
||||||
if (that instanceof EntityWrapper) {
|
|
||||||
EntityWrapper thatEntity = (EntityWrapper) that;
|
|
||||||
return entity.equals(thatEntity.entity)
|
|
||||||
&& entity.getProperties().equals(thatEntity.entity.getProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hashCode(entity.getKey(), entity.getProperties());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "EntityWrapper(" + entity + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EntityWrapper from(int id, Property... properties) {
|
|
||||||
Entity entity = new Entity(TEST_ENTITY_KIND, id);
|
|
||||||
for (Property prop : properties) {
|
|
||||||
entity.setProperty(prop.name(), prop.value());
|
|
||||||
}
|
|
||||||
return new EntityWrapper(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AutoValue
|
|
||||||
abstract static class Property {
|
|
||||||
|
|
||||||
static Property create(String name, Object value) {
|
|
||||||
return new AutoValue_EntityWrapper_Property(name, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract String name();
|
|
||||||
|
|
||||||
abstract Object value();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import google.registry.config.RegistryConfig;
|
import google.registry.config.RegistryConfig;
|
||||||
import google.registry.model.ofy.ObjectifyService;
|
|
||||||
import google.registry.persistence.transaction.TransactionManagerFactory;
|
import google.registry.persistence.transaction.TransactionManagerFactory;
|
||||||
import google.registry.tools.AuthModule.LoginRequiredException;
|
import google.registry.tools.AuthModule.LoginRequiredException;
|
||||||
import google.registry.tools.params.ParameterFactory;
|
import google.registry.tools.params.ParameterFactory;
|
||||||
|
@ -256,15 +255,6 @@ final class RegistryCli implements AutoCloseable, CommandRunner {
|
||||||
}
|
}
|
||||||
installer.install(options);
|
installer.install(options);
|
||||||
|
|
||||||
// Database setup -- we also only ever do this if "installer" is null, just so that it's
|
|
||||||
// only done once.
|
|
||||||
|
|
||||||
// Ensure that all entity classes are loaded before command code runs.
|
|
||||||
ObjectifyService.initOfy();
|
|
||||||
// Make sure we start the command with a clean cache, so that any previous command won't
|
|
||||||
// interfere with this one.
|
|
||||||
ObjectifyService.auditedOfy().clearSessionCache();
|
|
||||||
|
|
||||||
// Enable Cloud SQL for command that needs remote API as they will very likely use
|
// Enable Cloud SQL for command that needs remote API as they will very likely use
|
||||||
// Cloud SQL after the database migration. Note that the DB password is stored in Datastore
|
// Cloud SQL after the database migration. Note that the DB password is stored in Datastore
|
||||||
// and it is already initialized above.
|
// and it is already initialized above.
|
||||||
|
|
|
@ -27,7 +27,6 @@ import google.registry.config.CredentialModule;
|
||||||
import google.registry.config.RegistryConfig;
|
import google.registry.config.RegistryConfig;
|
||||||
import google.registry.config.RegistryConfig.Config;
|
import google.registry.config.RegistryConfig.Config;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.ofy.ObjectifyService;
|
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.tools.CommandWithConnection;
|
import google.registry.tools.CommandWithConnection;
|
||||||
|
@ -199,7 +198,6 @@ public class CreateSyntheticDomainHistoriesCommand extends ConfirmingCommand
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
ObjectifyService.initOfy();
|
|
||||||
return installer;
|
return installer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -928,7 +928,6 @@ soy.$$cleanHtml = function(value, opt_safeTags) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// LINT.IfChange(htmlToText)
|
|
||||||
/**
|
/**
|
||||||
* Converts HTML to plain text by removing tags, normalizing spaces and
|
* Converts HTML to plain text by removing tags, normalizing spaces and
|
||||||
* converting entities.
|
* converting entities.
|
||||||
|
@ -1008,10 +1007,6 @@ soy.$$htmlToText = function(value) {
|
||||||
/** @private @const */
|
/** @private @const */
|
||||||
soy.BLOCK_TAGS_RE_ =
|
soy.BLOCK_TAGS_RE_ =
|
||||||
/^\/?(address|blockquote|dd|div|dl|dt|h[1-6]|hr|li|ol|p|pre|table|tr|ul)$/i;
|
/^\/?(address|blockquote|dd|div|dl|dt|h[1-6]|hr|li|ol|p|pre|table|tr|ul)$/i;
|
||||||
// LINT.ThenChange(
|
|
||||||
// ../../../third_party/java_src/soy/java/com/google/template/soy/basicfunctions/HtmlToText.java,
|
|
||||||
// ../../../third_party/java_src/soy/python/runtime/sanitize.py:htmlToText)
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Escapes HTML, except preserves entities.
|
* Escapes HTML, except preserves entities.
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
package google.registry.beam;
|
package google.registry.beam;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
|
||||||
|
@ -28,7 +27,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -298,15 +296,6 @@ public class TestPipelineExtension extends Pipeline
|
||||||
enableAbandonedNodeEnforcement(true);
|
enableAbandonedNodeEnforcement(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear this property so that when default Guava ThreadFactory is created
|
|
||||||
// it will not think that it is in App Engine and return an unusable
|
|
||||||
// ThreadFactory.
|
|
||||||
System.clearProperty("com.google.appengine.runtime.environment");
|
|
||||||
assertWithMessage(
|
|
||||||
"Beam pipelines don't run in an App Engine environment, and thus"
|
|
||||||
+ " the tests shouldn't be mocking one either.")
|
|
||||||
.that(isAppEngine())
|
|
||||||
.isFalse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -512,25 +501,6 @@ public class TestPipelineExtension extends Pipeline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapted from Guava's MoreExecutors (where it is a private method)
|
|
||||||
private static boolean isAppEngine() {
|
|
||||||
if (System.getProperty("com.google.appengine.runtime.environment") == null) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
return Class.forName("com.google.apphosting.api.ApiProxy")
|
|
||||||
.getMethod("getCurrentEnvironment")
|
|
||||||
.invoke(null)
|
|
||||||
!= null;
|
|
||||||
} catch (ClassNotFoundException
|
|
||||||
| InvocationTargetException
|
|
||||||
| IllegalAccessException
|
|
||||||
| NoSuchMethodException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class IsEmptyVisitor extends PipelineVisitor.Defaults {
|
private static class IsEmptyVisitor extends PipelineVisitor.Defaults {
|
||||||
private boolean empty = true;
|
private boolean empty = true;
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,7 @@ import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.googlecode.objectify.Key;
|
import google.registry.persistence.VKey;
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import com.googlecode.objectify.annotation.Id;
|
|
||||||
import google.registry.testing.AppEngineExtension;
|
import google.registry.testing.AppEngineExtension;
|
||||||
import google.registry.util.CidrAddressBlock;
|
import google.registry.util.CidrAddressBlock;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
@ -38,6 +36,8 @@ import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
import org.joda.time.DateTime;
|
import org.joda.time.DateTime;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -50,7 +50,6 @@ public class ImmutableObjectTest {
|
||||||
AppEngineExtension.builder()
|
AppEngineExtension.builder()
|
||||||
.withCloudSql()
|
.withCloudSql()
|
||||||
.withJpaUnitTestEntities(ValueObject.class)
|
.withJpaUnitTestEntities(ValueObject.class)
|
||||||
.withOfyTestEntities(ValueObject.class)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
/** Simple subclass of ImmutableObject. */
|
/** Simple subclass of ImmutableObject. */
|
||||||
|
@ -266,21 +265,19 @@ public class ImmutableObjectTest {
|
||||||
/** Subclass of ImmutableObject with keys to other objects. */
|
/** Subclass of ImmutableObject with keys to other objects. */
|
||||||
public static class RootObject extends ImmutableObject {
|
public static class RootObject extends ImmutableObject {
|
||||||
|
|
||||||
Key<ValueObject> hydrateMe;
|
VKey<ValueObject> hydrateMe;
|
||||||
|
|
||||||
@DoNotHydrate
|
@DoNotHydrate VKey<ValueObject> skipMe;
|
||||||
Key<ValueObject> skipMe;
|
|
||||||
|
|
||||||
Map<String, Key<ValueObject>> map;
|
Map<String, VKey<ValueObject>> map;
|
||||||
|
|
||||||
Set<Key<ValueObject>> set;
|
Set<VKey<ValueObject>> set;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Simple subclass of ImmutableObject. */
|
/** Simple subclass of ImmutableObject. */
|
||||||
@Entity
|
@Entity
|
||||||
@javax.persistence.Entity
|
|
||||||
public static class ValueObject extends ImmutableObject {
|
public static class ValueObject extends ImmutableObject {
|
||||||
@Id @javax.persistence.Id long id;
|
@Id long id;
|
||||||
|
|
||||||
String value;
|
String value;
|
||||||
|
|
||||||
|
|
|
@ -1,52 +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.
|
|
||||||
|
|
||||||
package google.registry.model.common;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static google.registry.model.ofy.ObjectifyService.auditedOfy;
|
|
||||||
|
|
||||||
import google.registry.testing.AppEngineExtension;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
||||||
|
|
||||||
/** Unit tests for {@link GaeUserIdConverter}. */
|
|
||||||
public class GaeUserIdConverterTest {
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
public final AppEngineExtension appEngine = AppEngineExtension.builder().withCloudSql().build();
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void verifyNoLingeringEntities() {
|
|
||||||
assertThat(auditedOfy().load().type(GaeUserIdConverter.class).count()).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess() {
|
|
||||||
assertThat(GaeUserIdConverter.convertEmailAddressToGaeUserId("example@example.com"))
|
|
||||||
.matches("[0-9]+");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testSuccess_inTransaction() {
|
|
||||||
auditedOfy()
|
|
||||||
.transactNew(
|
|
||||||
() -> {
|
|
||||||
assertThat(GaeUserIdConverter.convertEmailAddressToGaeUserId("example@example.com"))
|
|
||||||
.matches("[0-9]+");
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import google.registry.testing.AppEngineExtension;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
||||||
|
|
||||||
/** Tests for our replacement for ObjectifyService. */
|
|
||||||
public class ObjectifyServiceTest {
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
public final AppEngineExtension appEngine = AppEngineExtension.builder().withCloudSql().build();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void test_initOfy_canBeCalledTwice() {
|
|
||||||
ObjectifyService.initOfy();
|
|
||||||
ObjectifyService.initOfy();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,104 +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.
|
|
||||||
|
|
||||||
package google.registry.model.ofy;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static google.registry.model.ofy.ObjectifyService.initOfy;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
||||||
|
|
||||||
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
|
|
||||||
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.ObjectifyFactory;
|
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import com.googlecode.objectify.ObjectifyService;
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import com.googlecode.objectify.annotation.Id;
|
|
||||||
import google.registry.model.common.GaeUserIdConverter;
|
|
||||||
import google.registry.persistence.transaction.JpaTestExtensions;
|
|
||||||
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
||||||
|
|
||||||
/** Tests for our replacement Objectify filter. */
|
|
||||||
class OfyFilterTest {
|
|
||||||
|
|
||||||
private LocalServiceTestHelper helper;
|
|
||||||
private ObjectifyFactory factory;
|
|
||||||
|
|
||||||
// We can't use AppEngineExtension, because it triggers the precise behavior that we are testing.
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void beforeEach() {
|
|
||||||
helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig()).setUp();
|
|
||||||
// Clear out the factory so that it requires re-registration on each test method.
|
|
||||||
// Otherwise, static registration of types in one method would persist across methods.
|
|
||||||
initOfy();
|
|
||||||
factory = ObjectifyService.factory();
|
|
||||||
ObjectifyService.setFactory(new ObjectifyFactory(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void afterEach() {
|
|
||||||
ObjectifyFilter.complete();
|
|
||||||
ObjectifyService.setFactory(factory);
|
|
||||||
ObjectifyFilter.complete();
|
|
||||||
helper.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
final JpaIntegrationTestExtension database =
|
|
||||||
new JpaTestExtensions.Builder().buildIntegrationTestExtension();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key.create looks up kind metadata for the class of the object it is given. If this happens
|
|
||||||
* before the first reference to ObjectifyService, which statically triggers type registrations,
|
|
||||||
* then the create will fail. Note that this is only a problem if the type in question doesn't
|
|
||||||
* call ObjectifyService.allocateId() inside its own builder or create method, since if it does
|
|
||||||
* that would trigger the statics as well. In this example, Registrar has a string id, so the bug
|
|
||||||
* occurs, were it not for OfyFilter.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void testFilterRegistersTypes() {
|
|
||||||
UnregisteredEntity entity = new UnregisteredEntity(5L);
|
|
||||||
IllegalStateException e = assertThrows(IllegalStateException.class, () -> Key.create(entity));
|
|
||||||
assertThat(e)
|
|
||||||
.hasMessageThat()
|
|
||||||
.isEqualTo(
|
|
||||||
"class google.registry.model.ofy.OfyFilterTest$UnregisteredEntity "
|
|
||||||
+ "has not been registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The filter should register all types for us. */
|
|
||||||
@Test
|
|
||||||
void testKeyCreateAfterFilter() {
|
|
||||||
new OfyFilter().init(null);
|
|
||||||
GaeUserIdConverter userIdConverter = new GaeUserIdConverter();
|
|
||||||
userIdConverter.id = 1;
|
|
||||||
Key.create(userIdConverter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
private static class UnregisteredEntity {
|
|
||||||
|
|
||||||
@Id long id;
|
|
||||||
|
|
||||||
UnregisteredEntity(long id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,13 +21,10 @@ import static google.registry.util.BuildPathUtils.getResourcesDir;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.net.HostAndPort;
|
import com.google.common.net.HostAndPort;
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import google.registry.model.ofy.OfyFilter;
|
|
||||||
import google.registry.module.backend.BackendServlet;
|
import google.registry.module.backend.BackendServlet;
|
||||||
import google.registry.module.frontend.FrontendServlet;
|
import google.registry.module.frontend.FrontendServlet;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import javax.servlet.Filter;
|
|
||||||
|
|
||||||
/** Lightweight HTTP server for testing the Nomulus Admin and Registrar consoles. */
|
/** Lightweight HTTP server for testing the Nomulus Admin and Registrar consoles. */
|
||||||
public final class RegistryTestServer {
|
public final class RegistryTestServer {
|
||||||
|
@ -87,15 +84,11 @@ public final class RegistryTestServer {
|
||||||
route("/registry-lock-post", FrontendServlet.class),
|
route("/registry-lock-post", FrontendServlet.class),
|
||||||
route("/registry-lock-verify", FrontendServlet.class));
|
route("/registry-lock-verify", FrontendServlet.class));
|
||||||
|
|
||||||
private static final ImmutableList<Class<? extends Filter>> FILTERS = ImmutableList.of(
|
|
||||||
ObjectifyFilter.class,
|
|
||||||
OfyFilter.class);
|
|
||||||
|
|
||||||
private final TestServer server;
|
private final TestServer server;
|
||||||
|
|
||||||
/** @see TestServer#TestServer(HostAndPort, ImmutableMap, ImmutableList, ImmutableList) */
|
/** @see TestServer#TestServer(HostAndPort, ImmutableMap, ImmutableList, ImmutableList) */
|
||||||
public RegistryTestServer(HostAndPort address) {
|
public RegistryTestServer(HostAndPort address) {
|
||||||
server = new TestServer(address, RUNFILES, ROUTES, FILTERS);
|
server = new TestServer(address, RUNFILES, ROUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see TestServer#start() */
|
/** @see TestServer#start() */
|
||||||
|
|
|
@ -18,20 +18,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Throwables.throwIfInstanceOf;
|
import static com.google.common.base.Throwables.throwIfInstanceOf;
|
||||||
import static google.registry.util.TypeUtils.instantiate;
|
import static google.registry.util.TypeUtils.instantiate;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.util.concurrent.Uninterruptibles;
|
import com.google.common.util.concurrent.Uninterruptibles;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletRequest;
|
|
||||||
import javax.servlet.ServletResponse;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
@ -39,49 +33,33 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
/**
|
/**
|
||||||
* Servlet that wraps a servlet and delegates request execution to a queue.
|
* Servlet that wraps a servlet and delegates request execution to a queue.
|
||||||
*
|
*
|
||||||
* <p>The actual invocation of the delegate does not happen within this servlet's lifecycle.
|
|
||||||
* Therefore, the task on the queue must manually invoke filters within the queue task.
|
|
||||||
*
|
|
||||||
* @see TestServer
|
* @see TestServer
|
||||||
*/
|
*/
|
||||||
public final class ServletWrapperDelegatorServlet extends HttpServlet {
|
public final class ServletWrapperDelegatorServlet extends HttpServlet {
|
||||||
|
|
||||||
private final Queue<FutureTask<Void>> requestQueue;
|
private final Queue<FutureTask<Void>> requestQueue;
|
||||||
private final Class<? extends HttpServlet> servletClass;
|
private final Class<? extends HttpServlet> servletClass;
|
||||||
private final ImmutableList<Class<? extends Filter>> filterClasses;
|
|
||||||
|
|
||||||
ServletWrapperDelegatorServlet(
|
ServletWrapperDelegatorServlet(
|
||||||
Class<? extends HttpServlet> servletClass,
|
Class<? extends HttpServlet> servletClass,
|
||||||
ImmutableList<Class<? extends Filter>> filterClasses,
|
|
||||||
Queue<FutureTask<Void>> requestQueue) {
|
Queue<FutureTask<Void>> requestQueue) {
|
||||||
this.servletClass = servletClass;
|
this.servletClass = servletClass;
|
||||||
this.filterClasses = filterClasses;
|
|
||||||
this.requestQueue = checkNotNull(requestQueue, "requestQueue");
|
this.requestQueue = checkNotNull(requestQueue, "requestQueue");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void service(final HttpServletRequest req, final HttpServletResponse rsp)
|
public void service(final HttpServletRequest req, final HttpServletResponse rsp)
|
||||||
throws ServletException, IOException {
|
throws ServletException, IOException {
|
||||||
FutureTask<Void> task = new FutureTask<>(new Callable<Void>() {
|
FutureTask<Void> task =
|
||||||
@Nullable
|
new FutureTask<>(
|
||||||
@Override
|
new Callable<Void>() {
|
||||||
public Void call() throws ServletException, IOException {
|
@Nullable
|
||||||
// Simulate the full filter chain with the servlet at the end.
|
|
||||||
final Iterator<Class<? extends Filter>> filtersIter = filterClasses.iterator();
|
|
||||||
FilterChain filterChain =
|
|
||||||
new FilterChain() {
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response)
|
public Void call() throws ServletException, IOException {
|
||||||
throws IOException, ServletException {
|
instantiate(servletClass).service(req, rsp);
|
||||||
if (filtersIter.hasNext()) {
|
return null;
|
||||||
instantiate(filtersIter.next()).doFilter(request, response, this);
|
}
|
||||||
} else {
|
});
|
||||||
instantiate(servletClass).service(request, response);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
filterChain.doFilter(req, rsp);
|
|
||||||
return null;
|
|
||||||
}});
|
|
||||||
requestQueue.add(task);
|
requestQueue.add(task);
|
||||||
try {
|
try {
|
||||||
Uninterruptibles.getUninterruptibly(task);
|
Uninterruptibles.getUninterruptibly(task);
|
||||||
|
|
|
@ -34,7 +34,6 @@ import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.Filter;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import org.mortbay.jetty.Connector;
|
import org.mortbay.jetty.Connector;
|
||||||
import org.mortbay.jetty.Server;
|
import org.mortbay.jetty.Server;
|
||||||
|
@ -85,13 +84,10 @@ public final class TestServer {
|
||||||
* @param routes list of servlet endpoints
|
* @param routes list of servlet endpoints
|
||||||
*/
|
*/
|
||||||
public TestServer(
|
public TestServer(
|
||||||
HostAndPort address,
|
HostAndPort address, ImmutableMap<String, Path> runfiles, ImmutableList<Route> routes) {
|
||||||
ImmutableMap<String, Path> runfiles,
|
|
||||||
ImmutableList<Route> routes,
|
|
||||||
ImmutableList<Class<? extends Filter>> filters) {
|
|
||||||
urlAddress = createUrlAddress(address);
|
urlAddress = createUrlAddress(address);
|
||||||
server.addConnector(createConnector(address));
|
server.addConnector(createConnector(address));
|
||||||
server.addHandler(createHandler(runfiles, routes, filters));
|
server.addHandler(createHandler(runfiles, routes));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Starts the HTTP server in a new thread and returns once it's online. */
|
/** Starts the HTTP server in a new thread and returns once it's online. */
|
||||||
|
@ -156,10 +152,7 @@ public final class TestServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Context createHandler(
|
private Context createHandler(Map<String, Path> runfiles, ImmutableList<Route> routes) {
|
||||||
Map<String, Path> runfiles,
|
|
||||||
ImmutableList<Route> routes,
|
|
||||||
ImmutableList<Class<? extends Filter>> filters) {
|
|
||||||
Context context = new Context(server, CONTEXT_PATH, Context.SESSIONS);
|
Context context = new Context(server, CONTEXT_PATH, Context.SESSIONS);
|
||||||
context.addServlet(new ServletHolder(HealthzServlet.class), "/healthz");
|
context.addServlet(new ServletHolder(HealthzServlet.class), "/healthz");
|
||||||
for (Map.Entry<String, Path> runfile : runfiles.entrySet()) {
|
for (Map.Entry<String, Path> runfile : runfiles.entrySet()) {
|
||||||
|
@ -168,8 +161,7 @@ public final class TestServer {
|
||||||
runfile.getKey());
|
runfile.getKey());
|
||||||
}
|
}
|
||||||
for (Route route : routes) {
|
for (Route route : routes) {
|
||||||
context.addServlet(
|
context.addServlet(new ServletHolder(wrapServlet(route.servletClass())), route.path());
|
||||||
new ServletHolder(wrapServlet(route.servletClass(), filters)), route.path());
|
|
||||||
}
|
}
|
||||||
ServletHolder holder = new ServletHolder(DefaultServlet.class);
|
ServletHolder holder = new ServletHolder(DefaultServlet.class);
|
||||||
holder.setInitParameter("aliases", "1");
|
holder.setInitParameter("aliases", "1");
|
||||||
|
@ -177,9 +169,8 @@ public final class TestServer {
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpServlet wrapServlet(
|
private HttpServlet wrapServlet(Class<? extends HttpServlet> servletClass) {
|
||||||
Class<? extends HttpServlet> servletClass, ImmutableList<Class<? extends Filter>> filters) {
|
return new ServletWrapperDelegatorServlet(servletClass, requestQueue);
|
||||||
return new ServletWrapperDelegatorServlet(servletClass, filters, requestQueue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Connector createConnector(HostAndPort address) {
|
private static Connector createConnector(HostAndPort address) {
|
||||||
|
|
|
@ -39,9 +39,6 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import com.google.common.io.Files;
|
import com.google.common.io.Files;
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import google.registry.model.ofy.ObjectifyService;
|
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.model.registrar.Registrar.State;
|
import google.registry.model.registrar.Registrar.State;
|
||||||
import google.registry.model.registrar.RegistrarAddress;
|
import google.registry.model.registrar.RegistrarAddress;
|
||||||
|
@ -135,7 +132,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
private UserInfo userInfo;
|
private UserInfo userInfo;
|
||||||
|
|
||||||
// Test Objectify entity classes to be used with this AppEngineExtension instance.
|
// Test Objectify entity classes to be used with this AppEngineExtension instance.
|
||||||
private ImmutableList<Class<?>> ofyTestEntities;
|
|
||||||
private ImmutableList<Class<?>> jpaTestEntities;
|
private ImmutableList<Class<?>> jpaTestEntities;
|
||||||
|
|
||||||
public Optional<JpaIntegrationTestExtension> getJpaIntegrationTestExtension() {
|
public Optional<JpaIntegrationTestExtension> getJpaIntegrationTestExtension() {
|
||||||
|
@ -146,7 +142,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private AppEngineExtension extension = new AppEngineExtension();
|
private AppEngineExtension extension = new AppEngineExtension();
|
||||||
private ImmutableList.Builder<Class<?>> ofyTestEntities = new ImmutableList.Builder<>();
|
|
||||||
private ImmutableList.Builder<Class<?>> jpaTestEntities = new ImmutableList.Builder<>();
|
private ImmutableList.Builder<Class<?>> jpaTestEntities = new ImmutableList.Builder<>();
|
||||||
|
|
||||||
/** Turns on Cloud SQL only, for use by test data generators. */
|
/** Turns on Cloud SQL only, for use by test data generators. */
|
||||||
|
@ -205,24 +200,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Declares test-only entities to be registered with {@code ObjectifyService}.
|
|
||||||
*
|
|
||||||
* <p>Note that {@code ObjectifyService} silently replaces the current registration for a given
|
|
||||||
* kind when a different class is registered for this kind. Since {@code ObjectifyService} does
|
|
||||||
* not support de-registration, each test entity class must be of a unique kind across the
|
|
||||||
* entire code base. Although this requirement can be worked around by using different {@code
|
|
||||||
* ObjectifyService} instances for each test (class), the setup overhead would rise
|
|
||||||
* significantly.
|
|
||||||
*
|
|
||||||
* @see AppEngineExtension#register(Class)
|
|
||||||
*/
|
|
||||||
@SafeVarargs
|
|
||||||
public final Builder withOfyTestEntities(Class<?>... entities) {
|
|
||||||
ofyTestEntities.add(entities);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Builder withJpaUnitTestEntities(Class<?>... entities) {
|
public Builder withJpaUnitTestEntities(Class<?>... entities) {
|
||||||
jpaTestEntities.add(entities);
|
jpaTestEntities.add(entities);
|
||||||
extension.withJpaUnitTest = true;
|
extension.withJpaUnitTest = true;
|
||||||
|
@ -239,7 +216,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
checkState(
|
checkState(
|
||||||
!extension.withJpaUnitTest || !extension.enableJpaEntityCoverageCheck,
|
!extension.withJpaUnitTest || !extension.enableJpaEntityCoverageCheck,
|
||||||
"withJpaUnitTestEntities cannot be set when enableJpaEntityCoverageCheck");
|
"withJpaUnitTestEntities cannot be set when enableJpaEntityCoverageCheck");
|
||||||
extension.ofyTestEntities = this.ofyTestEntities.build();
|
|
||||||
extension.jpaTestEntities = this.jpaTestEntities.build();
|
extension.jpaTestEntities = this.jpaTestEntities.build();
|
||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
|
@ -440,9 +416,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
helper.setEnvInstance("0");
|
helper.setEnvInstance("0");
|
||||||
}
|
}
|
||||||
helper.setUp();
|
helper.setUp();
|
||||||
|
|
||||||
ObjectifyService.initOfy();
|
|
||||||
this.ofyTestEntities.forEach(AppEngineExtension::register);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Called after each test method. */
|
/** Called after each test method. */
|
||||||
|
@ -472,7 +445,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
// Resets Objectify. Although it would seem more obvious to do this at the start of a request
|
// Resets Objectify. Although it would seem more obvious to do this at the start of a request
|
||||||
// instead of at the end, this is more consistent with what ObjectifyFilter does in real code.
|
// instead of at the end, this is more consistent with what ObjectifyFilter does in real code.
|
||||||
ObjectifyFilter.complete();
|
|
||||||
helper.tearDown();
|
helper.tearDown();
|
||||||
helper = null;
|
helper = null;
|
||||||
// Test that Datastore didn't need any indexes we don't have listed in our index file.
|
// Test that Datastore didn't need any indexes we don't have listed in our index file.
|
||||||
|
@ -502,24 +474,6 @@ public final class AppEngineExtension implements BeforeEachCallback, AfterEachCa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers test-only Objectify entities and checks for re-registrations for the same kind by
|
|
||||||
* different classes.
|
|
||||||
*/
|
|
||||||
private static void register(Class<?> entityClass) {
|
|
||||||
String kind = Key.getKind(entityClass);
|
|
||||||
Optional.ofNullable(com.googlecode.objectify.ObjectifyService.factory().getMetadata(kind))
|
|
||||||
.ifPresent(
|
|
||||||
meta ->
|
|
||||||
checkState(
|
|
||||||
meta.getEntityClass() == entityClass,
|
|
||||||
"Cannot register %s. The Kind %s is already registered with %s.",
|
|
||||||
entityClass.getName(),
|
|
||||||
kind,
|
|
||||||
meta.getEntityClass().getName()));
|
|
||||||
com.googlecode.objectify.ObjectifyService.register(entityClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Install {@code testing/logging.properties} so logging is less noisy. */
|
/** Install {@code testing/logging.properties} so logging is less noisy. */
|
||||||
private static void setupLogging() throws IOException {
|
private static void setupLogging() throws IOException {
|
||||||
LogManager.getLogManager()
|
LogManager.getLogManager()
|
||||||
|
|
|
@ -15,27 +15,15 @@
|
||||||
package google.registry.testing;
|
package google.registry.testing;
|
||||||
|
|
||||||
import static com.google.common.io.Files.asCharSink;
|
import static com.google.common.io.Files.asCharSink;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static com.google.common.truth.Truth.assertWithMessage;
|
import static com.google.common.truth.Truth.assertWithMessage;
|
||||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||||
import static google.registry.util.CollectionUtils.entriesToImmutableMap;
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.Multimap;
|
|
||||||
import com.google.common.collect.MultimapBuilder;
|
|
||||||
import com.google.common.collect.Multimaps;
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import com.googlecode.objectify.annotation.Id;
|
|
||||||
import google.registry.persistence.transaction.JpaTransactionManager;
|
import google.registry.persistence.transaction.JpaTransactionManager;
|
||||||
import io.github.classgraph.ClassGraph;
|
|
||||||
import io.github.classgraph.ScanResult;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
@ -120,65 +108,7 @@ class AppEngineExtensionTest {
|
||||||
assertThrows(AssertionError.class, () -> appEngine.afterEach(context.getContext()));
|
assertThrows(AssertionError.class, () -> appEngine.afterEach(context.getContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void testRegisterOfyEntities_duplicateEntitiesWithSameName_fails() throws Exception {
|
|
||||||
AppEngineExtension appEngineExtension =
|
|
||||||
AppEngineExtension.builder()
|
|
||||||
.withCloudSql()
|
|
||||||
.withOfyTestEntities(google.registry.testing.TestObject.class, TestObject.class)
|
|
||||||
.build();
|
|
||||||
// Thrown before JPA is set up, therefore no need to call afterEach.
|
|
||||||
IllegalStateException thrown =
|
|
||||||
assertThrows(
|
|
||||||
IllegalStateException.class, () -> appEngineExtension.beforeEach(context.getContext()));
|
|
||||||
assertThat(thrown)
|
|
||||||
.hasMessageThat()
|
|
||||||
.isEqualTo(
|
|
||||||
String.format(
|
|
||||||
"Cannot register %s. The Kind %s is already registered with %s.",
|
|
||||||
TestObject.class.getName(),
|
|
||||||
"TestObject",
|
|
||||||
google.registry.testing.TestObject.class.getName()));
|
|
||||||
// The class level extension.
|
|
||||||
appEngine.afterEach(context.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testOfyEntities_uniqueKinds() throws Exception {
|
|
||||||
try (ScanResult scanResult =
|
|
||||||
new ClassGraph()
|
|
||||||
.enableAnnotationInfo()
|
|
||||||
.ignoreClassVisibility()
|
|
||||||
.whitelistPackages("google.registry")
|
|
||||||
.scan()) {
|
|
||||||
Multimap<String, Class<?>> kindToEntityMultiMap =
|
|
||||||
scanResult.getClassesWithAnnotation(Entity.class.getName()).stream()
|
|
||||||
.filter(clazz -> !clazz.getName().equals(TestObject.class.getName()))
|
|
||||||
.map(clazz -> clazz.loadClass())
|
|
||||||
.collect(
|
|
||||||
Multimaps.toMultimap(
|
|
||||||
Key::getKind,
|
|
||||||
clazz -> clazz,
|
|
||||||
MultimapBuilder.hashKeys().linkedListValues()::build));
|
|
||||||
Map<String, Collection<Class<?>>> conflictingKinds =
|
|
||||||
kindToEntityMultiMap.asMap().entrySet().stream()
|
|
||||||
.filter(e -> e.getValue().size() > 1)
|
|
||||||
.collect(entriesToImmutableMap());
|
|
||||||
assertWithMessage(
|
|
||||||
"Conflicting Ofy kinds found. Tests will break if they are registered with "
|
|
||||||
+ " AppEngineExtension in the same test executor.")
|
|
||||||
.that(conflictingKinds)
|
|
||||||
.isEmpty();
|
|
||||||
}
|
|
||||||
appEngine.afterEach(context.getContext());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void writeAutoIndexFile(String content) throws IOException {
|
private void writeAutoIndexFile(String content) throws IOException {
|
||||||
asCharSink(new File(appEngine.tmpDir, "datastore-indexes-auto.xml"), UTF_8).write(content);
|
asCharSink(new File(appEngine.tmpDir, "datastore-indexes-auto.xml"), UTF_8).write(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Entity
|
|
||||||
private static final class TestObject {
|
|
||||||
@Id long id;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,78 +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.
|
|
||||||
|
|
||||||
package google.registry.testing;
|
|
||||||
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import com.googlecode.objectify.annotation.Entity;
|
|
||||||
import com.googlecode.objectify.annotation.Id;
|
|
||||||
import google.registry.model.ImmutableObject;
|
|
||||||
import google.registry.model.annotations.DeleteAfterMigration;
|
|
||||||
import google.registry.model.annotations.VirtualEntity;
|
|
||||||
import google.registry.persistence.VKey;
|
|
||||||
|
|
||||||
/** A test model object that can be persisted in any entity group. */
|
|
||||||
@DeleteAfterMigration
|
|
||||||
@Entity
|
|
||||||
public class TestObject extends ImmutableObject {
|
|
||||||
|
|
||||||
@Id @javax.persistence.Id String id;
|
|
||||||
|
|
||||||
String field;
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getField() {
|
|
||||||
return field;
|
|
||||||
}
|
|
||||||
|
|
||||||
public VKey<TestObject> key() {
|
|
||||||
return VKey.create(TestObject.class, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TestObject create(String id) {
|
|
||||||
return create(id, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TestObject create(String id, String field) {
|
|
||||||
TestObject instance = new TestObject();
|
|
||||||
instance.id = id;
|
|
||||||
instance.field = field;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A test @VirtualEntity model object, which should not be persisted. */
|
|
||||||
@Entity
|
|
||||||
@VirtualEntity
|
|
||||||
public static class TestVirtualObject extends ImmutableObject {
|
|
||||||
|
|
||||||
@Id String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose a factory method for testing saves of virtual entities; in real life this would never
|
|
||||||
* be needed for an actual @VirtualEntity.
|
|
||||||
*/
|
|
||||||
public static TestVirtualObject create(String id) {
|
|
||||||
TestVirtualObject instance = new TestVirtualObject();
|
|
||||||
instance.id = id;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Key<TestVirtualObject> createKey(String id) {
|
|
||||||
return Key.create(TestVirtualObject.class, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,123 +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.
|
|
||||||
|
|
||||||
package google.registry.tools;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import com.google.appengine.api.datastore.Entity;
|
|
||||||
import com.google.appengine.api.datastore.EntityTranslator;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.storage.onestore.v3.OnestoreEntity.EntityProto;
|
|
||||||
import com.google.storage.onestore.v3.OnestoreEntity.Property;
|
|
||||||
import google.registry.testing.AppEngineExtension;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
|
||||||
|
|
||||||
/** Unit tests for {@link EntityWrapper}. */
|
|
||||||
public final class EntityWrapperTest {
|
|
||||||
|
|
||||||
private static final String TEST_ENTITY_KIND = "TestEntity";
|
|
||||||
private static final int ARBITRARY_KEY_ID = 1001;
|
|
||||||
|
|
||||||
@RegisterExtension
|
|
||||||
public final AppEngineExtension appEngine = AppEngineExtension.builder().withCloudSql().build();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testEquals() {
|
|
||||||
// Create an entity with a key and some properties.
|
|
||||||
Entity entity = new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID);
|
|
||||||
// Note that we need to specify these as long for property comparisons to work because that's
|
|
||||||
// how they are deserialized from protos.
|
|
||||||
entity.setProperty("eeny", 100L);
|
|
||||||
entity.setProperty("meeny", 200L);
|
|
||||||
entity.setProperty("miney", 300L);
|
|
||||||
|
|
||||||
EntityProto proto1 = EntityTranslator.convertToPb(entity);
|
|
||||||
EntityProto proto2 = EntityTranslator.convertToPb(entity);
|
|
||||||
|
|
||||||
// Reorder the property list of proto2 (the protobuf stores this as a repeated field, so
|
|
||||||
// we just have to clear and re-add them in a different order).
|
|
||||||
ImmutableList<Property> properties =
|
|
||||||
ImmutableList.of(proto2.getProperty(2), proto2.getProperty(0), proto2.getProperty(1));
|
|
||||||
proto2.clearProperty();
|
|
||||||
for (Property property : properties) {
|
|
||||||
proto2.addProperty(property);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct entity objects from the two protos.
|
|
||||||
Entity e1 = EntityTranslator.createFromPb(proto1);
|
|
||||||
Entity e2 = EntityTranslator.createFromPb(proto2);
|
|
||||||
|
|
||||||
// Ensure that we have a normalized representation.
|
|
||||||
EntityWrapper ce1 = new EntityWrapper(e1);
|
|
||||||
EntityWrapper ce2 = new EntityWrapper(e2);
|
|
||||||
assertThat(ce1).isEqualTo(ce2);
|
|
||||||
assertThat(ce1.hashCode()).isEqualTo(ce2.hashCode());
|
|
||||||
|
|
||||||
// Ensure that the original entity is equal.
|
|
||||||
assertThat(new EntityWrapper(entity)).isEqualTo(ce1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDifferentPropertiesNotEqual() {
|
|
||||||
Entity entity = new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID);
|
|
||||||
// Note that we need to specify these as long for property comparisons to work because that's
|
|
||||||
// how they are deserialized from protos.
|
|
||||||
entity.setProperty("eeny", 100L);
|
|
||||||
entity.setProperty("meeny", 200L);
|
|
||||||
entity.setProperty("miney", 300L);
|
|
||||||
|
|
||||||
EntityProto proto1 = EntityTranslator.convertToPb(entity);
|
|
||||||
|
|
||||||
entity.setProperty("tiger!", 400);
|
|
||||||
EntityProto proto2 = EntityTranslator.convertToPb(entity);
|
|
||||||
|
|
||||||
// Construct entity objects from the two protos.
|
|
||||||
Entity e1 = EntityTranslator.createFromPb(proto1);
|
|
||||||
Entity e2 = EntityTranslator.createFromPb(proto2);
|
|
||||||
|
|
||||||
EntityWrapper ce1 = new EntityWrapper(e1);
|
|
||||||
EntityWrapper ce2 = new EntityWrapper(e2);
|
|
||||||
assertThat(e1).isEqualTo(e2); // The keys should still be the same.
|
|
||||||
assertThat(ce1).isNotEqualTo(ce2);
|
|
||||||
assertThat(ce1.hashCode()).isNotEqualTo(ce2.hashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testDifferentKeysNotEqual() {
|
|
||||||
EntityProto proto1 =
|
|
||||||
EntityTranslator.convertToPb(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID));
|
|
||||||
EntityProto proto2 =
|
|
||||||
EntityTranslator.convertToPb(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID + 1));
|
|
||||||
|
|
||||||
// Construct entity objects from the two protos.
|
|
||||||
Entity e1 = EntityTranslator.createFromPb(proto1);
|
|
||||||
Entity e2 = EntityTranslator.createFromPb(proto2);
|
|
||||||
|
|
||||||
EntityWrapper ce1 = new EntityWrapper(e1);
|
|
||||||
EntityWrapper ce2 = new EntityWrapper(e2);
|
|
||||||
assertThat(ce1).isNotEqualTo(ce2);
|
|
||||||
assertThat(ce1.hashCode()).isNotEqualTo(ce2.hashCode());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void testComparisonAgainstNonComparableEntities() {
|
|
||||||
EntityWrapper ce = new EntityWrapper(new Entity(TEST_ENTITY_KIND, ARBITRARY_KEY_ID));
|
|
||||||
// Note: this has to be "isNotEqualTo()" and not isNotNull() because we want to test the
|
|
||||||
// equals() method and isNotNull() just checks for "ce != null".
|
|
||||||
assertThat(ce).isNotEqualTo(null);
|
|
||||||
assertThat(ce).isNotEqualTo(new Object());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,8 +17,6 @@ package google.registry.webdriver;
|
||||||
import static google.registry.server.Fixture.BASIC;
|
import static google.registry.server.Fixture.BASIC;
|
||||||
import static google.registry.server.Route.route;
|
import static google.registry.server.Route.route;
|
||||||
|
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import google.registry.model.ofy.OfyFilter;
|
|
||||||
import google.registry.module.frontend.FrontendServlet;
|
import google.registry.module.frontend.FrontendServlet;
|
||||||
import google.registry.server.RegistryTestServer;
|
import google.registry.server.RegistryTestServer;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -33,7 +31,6 @@ public class OteSetupConsoleScreenshotTest extends WebDriverTestCase {
|
||||||
new TestServerExtension.Builder()
|
new TestServerExtension.Builder()
|
||||||
.setRunfiles(RegistryTestServer.RUNFILES)
|
.setRunfiles(RegistryTestServer.RUNFILES)
|
||||||
.setRoutes(route("/registrar-ote-setup", FrontendServlet.class))
|
.setRoutes(route("/registrar-ote-setup", FrontendServlet.class))
|
||||||
.setFilters(ObjectifyFilter.class, OfyFilter.class)
|
|
||||||
.setFixtures(BASIC)
|
.setFixtures(BASIC)
|
||||||
.setEmail("Marla.Singer@google.com")
|
.setEmail("Marla.Singer@google.com")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -28,10 +28,8 @@ import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STAT
|
||||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.domain.RegistryLock;
|
import google.registry.model.domain.RegistryLock;
|
||||||
import google.registry.model.ofy.OfyFilter;
|
|
||||||
import google.registry.model.registrar.Registrar.State;
|
import google.registry.model.registrar.Registrar.State;
|
||||||
import google.registry.model.registrar.RegistrarPoc;
|
import google.registry.model.registrar.RegistrarPoc;
|
||||||
import google.registry.module.frontend.FrontendServlet;
|
import google.registry.module.frontend.FrontendServlet;
|
||||||
|
@ -59,7 +57,6 @@ class RegistrarConsoleScreenshotTest extends WebDriverTestCase {
|
||||||
route("/registrar-settings", FrontendServlet.class),
|
route("/registrar-settings", FrontendServlet.class),
|
||||||
route("/registry-lock-get", FrontendServlet.class),
|
route("/registry-lock-get", FrontendServlet.class),
|
||||||
route("/registry-lock-verify", FrontendServlet.class))
|
route("/registry-lock-verify", FrontendServlet.class))
|
||||||
.setFilters(ObjectifyFilter.class, OfyFilter.class)
|
|
||||||
.setFixtures(BASIC)
|
.setFixtures(BASIC)
|
||||||
.setEmail("Marla.Singer@crr.com") // from AppEngineExtension.makeRegistrarContact3
|
.setEmail("Marla.Singer@crr.com") // from AppEngineExtension.makeRegistrarContact3
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -21,8 +21,6 @@ import static google.registry.testing.DatabaseHelper.loadRegistrar;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import google.registry.model.ofy.OfyFilter;
|
|
||||||
import google.registry.model.registrar.Registrar;
|
import google.registry.model.registrar.Registrar;
|
||||||
import google.registry.model.registrar.RegistrarAddress;
|
import google.registry.model.registrar.RegistrarAddress;
|
||||||
import google.registry.model.registrar.RegistrarPoc;
|
import google.registry.model.registrar.RegistrarPoc;
|
||||||
|
@ -43,7 +41,6 @@ public class RegistrarConsoleWebTest extends WebDriverTestCase {
|
||||||
.setRoutes(
|
.setRoutes(
|
||||||
route("/registrar", FrontendServlet.class),
|
route("/registrar", FrontendServlet.class),
|
||||||
route("/registrar-settings", FrontendServlet.class))
|
route("/registrar-settings", FrontendServlet.class))
|
||||||
.setFilters(ObjectifyFilter.class, OfyFilter.class)
|
|
||||||
.setFixtures(BASIC)
|
.setFixtures(BASIC)
|
||||||
.setEmail("Marla.Singer@crr.com")
|
.setEmail("Marla.Singer@crr.com")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -17,8 +17,6 @@ package google.registry.webdriver;
|
||||||
import static google.registry.server.Fixture.BASIC;
|
import static google.registry.server.Fixture.BASIC;
|
||||||
import static google.registry.server.Route.route;
|
import static google.registry.server.Route.route;
|
||||||
|
|
||||||
import com.googlecode.objectify.ObjectifyFilter;
|
|
||||||
import google.registry.model.ofy.OfyFilter;
|
|
||||||
import google.registry.module.frontend.FrontendServlet;
|
import google.registry.module.frontend.FrontendServlet;
|
||||||
import google.registry.server.RegistryTestServer;
|
import google.registry.server.RegistryTestServer;
|
||||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||||
|
@ -33,7 +31,6 @@ class RegistrarCreateConsoleScreenshotTest extends WebDriverTestCase {
|
||||||
new TestServerExtension.Builder()
|
new TestServerExtension.Builder()
|
||||||
.setRunfiles(RegistryTestServer.RUNFILES)
|
.setRunfiles(RegistryTestServer.RUNFILES)
|
||||||
.setRoutes(route("/registrar-create", FrontendServlet.class))
|
.setRoutes(route("/registrar-create", FrontendServlet.class))
|
||||||
.setFilters(ObjectifyFilter.class, OfyFilter.class)
|
|
||||||
.setFixtures(BASIC)
|
.setFixtures(BASIC)
|
||||||
.setEmail("Marla.Singer@google.com")
|
.setEmail("Marla.Singer@google.com")
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -37,7 +37,6 @@ import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
import java.util.concurrent.LinkedBlockingDeque;
|
||||||
import javax.servlet.Filter;
|
|
||||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||||
import org.junit.jupiter.api.extension.ExtensionContext;
|
import org.junit.jupiter.api.extension.ExtensionContext;
|
||||||
|
@ -59,7 +58,6 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
|
||||||
private final BlockingQueue<FutureTask<?>> jobs = new LinkedBlockingDeque<>();
|
private final BlockingQueue<FutureTask<?>> jobs = new LinkedBlockingDeque<>();
|
||||||
private final ImmutableMap<String, Path> runfiles;
|
private final ImmutableMap<String, Path> runfiles;
|
||||||
private final ImmutableList<Route> routes;
|
private final ImmutableList<Route> routes;
|
||||||
private final ImmutableList<Class<? extends Filter>> filters;
|
|
||||||
|
|
||||||
private TestServer testServer;
|
private TestServer testServer;
|
||||||
private Thread serverThread;
|
private Thread serverThread;
|
||||||
|
@ -67,12 +65,10 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
|
||||||
private TestServerExtension(
|
private TestServerExtension(
|
||||||
ImmutableMap<String, Path> runfiles,
|
ImmutableMap<String, Path> runfiles,
|
||||||
ImmutableList<Route> routes,
|
ImmutableList<Route> routes,
|
||||||
ImmutableList<Class<? extends Filter>> filters,
|
|
||||||
ImmutableList<Fixture> fixtures,
|
ImmutableList<Fixture> fixtures,
|
||||||
String email) {
|
String email) {
|
||||||
this.runfiles = runfiles;
|
this.runfiles = runfiles;
|
||||||
this.routes = routes;
|
this.routes = routes;
|
||||||
this.filters = filters;
|
|
||||||
this.fixtures = fixtures;
|
this.fixtures = fixtures;
|
||||||
// We create an GAE-Admin user, and then use AuthenticatedRegistrarAccessor.bypassAdminCheck to
|
// We create an GAE-Admin user, and then use AuthenticatedRegistrarAccessor.bypassAdminCheck to
|
||||||
// choose whether the user is an admin or not.
|
// choose whether the user is an admin or not.
|
||||||
|
@ -96,8 +92,7 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
|
||||||
// can access this server.
|
// can access this server.
|
||||||
getExternalAddressOfLocalSystem().getHostAddress(), pickUnusedPort()),
|
getExternalAddressOfLocalSystem().getHostAddress(), pickUnusedPort()),
|
||||||
runfiles,
|
runfiles,
|
||||||
routes,
|
routes);
|
||||||
filters);
|
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
|
@ -239,14 +234,12 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
|
||||||
/**
|
/**
|
||||||
* Builder for {@link TestServerExtension}.
|
* Builder for {@link TestServerExtension}.
|
||||||
*
|
*
|
||||||
* <p>This builder has three required fields: {@link #setRunfiles}, {@link #setRoutes}, and {@link
|
* <p>This builder has two required fields: {@link #setRunfiles} and {@link #setRoutes}.
|
||||||
* #setFilters}.
|
|
||||||
*/
|
*/
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
|
|
||||||
private ImmutableMap<String, Path> runfiles;
|
private ImmutableMap<String, Path> runfiles;
|
||||||
private ImmutableList<Route> routes;
|
private ImmutableList<Route> routes;
|
||||||
ImmutableList<Class<? extends Filter>> filters;
|
|
||||||
private ImmutableList<Fixture> fixtures = ImmutableList.of();
|
private ImmutableList<Fixture> fixtures = ImmutableList.of();
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
|
@ -263,13 +256,6 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the list of servlet {@link Filter} objects for {@link TestServer}. */
|
|
||||||
@SafeVarargs
|
|
||||||
public final Builder setFilters(Class<? extends Filter>... filters) {
|
|
||||||
this.filters = ImmutableList.copyOf(filters);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets an ordered list of Datastore fixtures that should be loaded on startup. */
|
/** Sets an ordered list of Datastore fixtures that should be loaded on startup. */
|
||||||
public Builder setFixtures(Fixture... fixtures) {
|
public Builder setFixtures(Fixture... fixtures) {
|
||||||
this.fixtures = ImmutableList.copyOf(fixtures);
|
this.fixtures = ImmutableList.copyOf(fixtures);
|
||||||
|
@ -291,7 +277,6 @@ public final class TestServerExtension implements BeforeEachCallback, AfterEachC
|
||||||
return new TestServerExtension(
|
return new TestServerExtension(
|
||||||
checkNotNull(this.runfiles),
|
checkNotNull(this.runfiles),
|
||||||
checkNotNull(this.routes),
|
checkNotNull(this.routes),
|
||||||
checkNotNull(this.filters),
|
|
||||||
checkNotNull(this.fixtures),
|
checkNotNull(this.fixtures),
|
||||||
checkNotNull(this.email));
|
checkNotNull(this.email));
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@ also defined:
|
||||||
incrementally.
|
incrementally.
|
||||||
* `python` -- Some Python reporting scripts
|
* `python` -- Some Python reporting scripts
|
||||||
* `release` -- Configuration for our continuous integration process.
|
* `release` -- Configuration for our continuous integration process.
|
||||||
* `third_party` -- External dependencies.
|
|
||||||
|
|
||||||
## Build the codebase
|
## Build the codebase
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,6 @@ plugins {
|
||||||
dependencies {
|
dependencies {
|
||||||
def deps = rootProject.dependencyMap
|
def deps = rootProject.dependencyMap
|
||||||
|
|
||||||
// Custom-built objectify jar at commit ecd5165, included in Nomulus
|
|
||||||
// release.
|
|
||||||
implementation files(
|
|
||||||
"${rootDir}/third_party/objectify/v4_1/objectify-4.1.3.jar")
|
|
||||||
|
|
||||||
implementation deps['com.google.code.findbugs:jsr305']
|
implementation deps['com.google.code.findbugs:jsr305']
|
||||||
implementation deps['com.google.guava:guava']
|
implementation deps['com.google.guava:guava']
|
||||||
implementation deps['com.squareup:javapoet']
|
implementation deps['com.squareup:javapoet']
|
||||||
|
|
22
third_party/objectify/v4_1/LICENSE
vendored
22
third_party/objectify/v4_1/LICENSE
vendored
|
@ -1,22 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2009-2013
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
|
|
19
third_party/objectify/v4_1/README.md
vendored
19
third_party/objectify/v4_1/README.md
vendored
|
@ -1,19 +0,0 @@
|
||||||
This library includes custom serializers for AppEngine classes in
|
|
||||||
com.google.appengine.api.* packages. This is necessary because serializers
|
|
||||||
are discovered by AppEngine using a naming pattern that requires that the
|
|
||||||
data and serializer classes be in the same package (similar to how the Java
|
|
||||||
Beans introspector finds BeanInfo classes).
|
|
||||||
|
|
||||||
In Objectify versions 4.1 and later, the GWT emulation classes were broken
|
|
||||||
out into a separate versioned jar. Since we are jarjar repackaging the core
|
|
||||||
Objectify library to include a version number in the package, we need to
|
|
||||||
include the GWT files in this folder and apply the same changes to them.
|
|
||||||
|
|
||||||
The specific patches are:
|
|
||||||
|
|
||||||
* Fix ofy().load().fromEntity(...) to respect @OnLoad callbacks.
|
|
||||||
* Add Session.getKeys() to enumerate everything read in a session.
|
|
||||||
|
|
||||||
These changes are already in upstream, but no 4.x release has been made
|
|
||||||
that incorporates them. Therefore we need to backport them and vendor the
|
|
||||||
Objectify libarary here.
|
|
BIN
third_party/objectify/v4_1/objectify-4.1.3-src.jar
vendored
BIN
third_party/objectify/v4_1/objectify-4.1.3-src.jar
vendored
Binary file not shown.
BIN
third_party/objectify/v4_1/objectify-4.1.3.jar
vendored
BIN
third_party/objectify/v4_1/objectify-4.1.3.jar
vendored
Binary file not shown.
|
@ -57,7 +57,6 @@ dependencies {
|
||||||
testImplementation deps['org.mockito:mockito-core']
|
testImplementation deps['org.mockito:mockito-core']
|
||||||
testImplementation deps['org.mockito:mockito-junit-jupiter']
|
testImplementation deps['org.mockito:mockito-junit-jupiter']
|
||||||
testImplementation deps['org.testcontainers:junit-jupiter']
|
testImplementation deps['org.testcontainers:junit-jupiter']
|
||||||
testImplementation files("${rootDir}/third_party/objectify/v4_1/objectify-4.1.3.jar")
|
|
||||||
testImplementation project(path: ':common', configuration: 'testing')
|
testImplementation project(path: ':common', configuration: 'testing')
|
||||||
testRuntimeOnly deps['com.google.flogger:flogger-system-backend']
|
testRuntimeOnly deps['com.google.flogger:flogger-system-backend']
|
||||||
annotationProcessor deps['com.google.auto.value:auto-value']
|
annotationProcessor deps['com.google.auto.value:auto-value']
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
// Copyright 2020 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.apphosting.api.ApiProxy.Environment;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/** A placeholder GAE environment class that is used when masquerading a thread as a GAE thread. */
|
|
||||||
public final class PlaceholderEnvironment implements Environment {
|
|
||||||
|
|
||||||
private static final PlaceholderEnvironment INSTANCE = new PlaceholderEnvironment();
|
|
||||||
|
|
||||||
public static PlaceholderEnvironment get() {
|
|
||||||
return INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PlaceholderEnvironment() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAppId() {
|
|
||||||
return "PlaceholderAppId";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getAttributes() {
|
|
||||||
return ImmutableMap.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getModuleId() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getVersionId() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getEmail() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isLoggedIn() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAdmin() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthDomain() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
|
||||||
public String getRequestNamespace() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getRemainingMillis() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue