// Copyright 2016 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.index; import static com.google.common.collect.Maps.filterValues; import static google.registry.model.ofy.ObjectifyService.ofy; import static google.registry.model.ofy.Ofy.RECOMMENDED_MEMCACHE_EXPIRATION; import static google.registry.util.TypeUtils.instantiate; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Cache; import com.googlecode.objectify.annotation.Entity; import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Index; import google.registry.model.BackupGroupRoot; import google.registry.model.EppResource; import google.registry.model.contact.ContactResource; import google.registry.model.domain.DomainResource; import google.registry.model.host.HostResource; import java.util.Map; import org.joda.time.DateTime; /** * Class to map a foreign key to the active instance of {@link EppResource} whose unique id matches * the foreign key string. The instance is never deleted, but it is updated if a newer entity * becomes the active entity. */ public abstract class ForeignKeyIndex extends BackupGroupRoot { /** The {@link ForeignKeyIndex} type for {@link ContactResource} entities. */ @Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION) @Entity public static class ForeignKeyContactIndex extends ForeignKeyIndex {} /** The {@link ForeignKeyIndex} type for {@link DomainResource} entities. */ @Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION) @Entity public static class ForeignKeyDomainIndex extends ForeignKeyIndex {} /** The {@link ForeignKeyIndex} type for {@link HostResource} entities. */ @Cache(expirationSeconds = RECOMMENDED_MEMCACHE_EXPIRATION) @Entity public static class ForeignKeyHostIndex extends ForeignKeyIndex {} private static final Map< Class, Class>> RESOURCE_CLASS_TO_FKI_CLASS = ImmutableMap., Class>>of( ContactResource.class, ForeignKeyContactIndex.class, DomainResource.class, ForeignKeyDomainIndex.class, HostResource.class, ForeignKeyHostIndex.class); @Id String foreignKey; /** * The deletion time of this {@link ForeignKeyIndex}. * *

This will generally be equal to the deletion time of {@link #topReference}. However, in the * case of a {@link HostResource} that was renamed, this field will hold the time of the rename. */ @Index DateTime deletionTime; /** * The referenced resource. * *

This field holds a key to the only referenced resource. It is named "topReference" for * historical reasons. */ Key topReference; public String getForeignKey() { return foreignKey; } public DateTime getDeletionTime() { return deletionTime; } public Key getResourceKey() { return topReference; } /** * Create a {@link ForeignKeyIndex} instance for a resource, expiring at a specified time. */ public static ForeignKeyIndex create( E resource, DateTime deletionTime) { ForeignKeyIndex instance = instantiate(RESOURCE_CLASS_TO_FKI_CLASS.get(resource.getClass())); instance.topReference = Key.create(resource); instance.foreignKey = resource.getForeignKey(); instance.deletionTime = deletionTime; return instance; } /** Create a {@link ForeignKeyIndex} key for a resource. */ public static Key> createKey(EppResource resource) { return Key.>create( RESOURCE_CLASS_TO_FKI_CLASS.get(resource.getClass()), resource.getForeignKey()); } /** * Loads a {@link Key} to an {@link EppResource} from the datastore by foreign key. * *

Returns absent if no foreign key index with this foreign key was ever created, or if the * most recently created foreign key index was deleted before time "now". This method does not * actually check that the referenced resource actually exists. However, for normal epp resources, * it is safe to assume that the referenced resource exists if the foreign key index does. * * @param clazz the resource type to load * @param foreignKey id to match * @param now the current logical time to use when checking for soft deletion of the foreign key * index */ public static Key loadAndGetKey( Class clazz, String foreignKey, DateTime now) { ForeignKeyIndex index = load(clazz, foreignKey, now); return (index == null) ? null : index.getResourceKey(); } /** * Load a {@link ForeignKeyIndex} by class and id string that is active at or after the specified * moment in time. * *

This will return null if the {@link ForeignKeyIndex} doesn't exist or has been soft deleted. */ public static ForeignKeyIndex load( Class clazz, String foreignKey, DateTime now) { return load(clazz, ImmutableList.of(foreignKey), now).get(foreignKey); } /** * Load a list of {@link ForeignKeyIndex} instances by class and id strings that are active at or * after the specified moment in time. * *

The returned map will omit any keys for which the {@link ForeignKeyIndex} doesn't exist or * has been soft deleted. */ @SuppressWarnings("unchecked") public static Map> load( Class clazz, Iterable foreignKeys, final DateTime now) { return (Map>) filterValues( ofy().load().type(RESOURCE_CLASS_TO_FKI_CLASS.get(clazz)).ids(foreignKeys), new Predicate>() { @Override public boolean apply(ForeignKeyIndex fki) { return now.isBefore(fki.deletionTime); }}); } }