diff --git a/java/google/registry/rde/RdeImportUtils.java b/java/google/registry/rde/RdeImportUtils.java new file mode 100644 index 000000000..f3ce5dc23 --- /dev/null +++ b/java/google/registry/rde/RdeImportUtils.java @@ -0,0 +1,101 @@ +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.rde; + +import static com.google.common.base.Preconditions.checkState; + +import com.googlecode.objectify.Key; +import com.googlecode.objectify.Work; +import google.registry.model.contact.ContactResource; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.index.ForeignKeyIndex; +import google.registry.model.ofy.Ofy; +import google.registry.util.Clock; +import google.registry.util.FormattingLogger; +import javax.inject.Inject; + +/** Utility functions for escrow file import. */ +public final class RdeImportUtils { + + private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); + + private final Ofy ofy; + private final Clock clock; + + @Inject + public RdeImportUtils(Ofy ofy, Clock clock) { + this.ofy = ofy; + this.clock = clock; + } + + /** + * Imports a contact from an escrow file. + * + *

The contact will only be imported if it has not been previously imported. + * + *

If the contact is imported, {@link ForeignKeyIndex} and {@link EppResourceIndex} are also + * created. + * + * @return true if the contact was created or updated, false otherwise. + */ + public boolean importContact(final ContactResource resource) { + return ofy.transact( + new Work() { + @Override + public Boolean run() { + ContactResource existing = ofy.load().key(Key.create(resource)).now(); + if (existing == null) { + ForeignKeyIndex existingForeignKeyIndex = + ForeignKeyIndex.load( + ContactResource.class, resource.getContactId(), clock.nowUtc()); + // foreign key index should not exist, since existing contact was not found. + checkState( + existingForeignKeyIndex == null, + String.format( + "New contact resource has existing foreign key index. " + + "contactId=%s, repoId=%s", + resource.getContactId(), resource.getRepoId())); + ofy.save().entity(resource); + ofy.save().entity(ForeignKeyIndex.create(resource, resource.getDeletionTime())); + ofy.save().entity(EppResourceIndex.create(Key.create(resource))); + logger.infofmt( + "Imported contact resource - ROID=%s, id=%s", + resource.getRepoId(), resource.getContactId()); + return true; + } else if (!existing.getRepoId().equals(resource.getRepoId())) { + logger.warningfmt( + "Existing contact with same contact id but different ROID. " + + "contactId=%s, existing ROID=%s, new ROID=%s", + resource.getContactId(), existing.getRepoId(), resource.getRepoId()); + } + return false; + } + }); + } +} diff --git a/javatests/google/registry/rde/RdeImportUtilsTest.java b/javatests/google/registry/rde/RdeImportUtilsTest.java new file mode 100644 index 000000000..29bbc2c06 --- /dev/null +++ b/javatests/google/registry/rde/RdeImportUtilsTest.java @@ -0,0 +1,153 @@ +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Copyright 2016 The Domain Registry Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package google.registry.rde; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.model.ofy.ObjectifyService.ofy; +import static google.registry.testing.DatastoreHelper.persistResource; + +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.ImmutableList; +import com.googlecode.objectify.Key; +import com.googlecode.objectify.Work; +import google.registry.model.EppResource; +import google.registry.model.contact.ContactResource; +import google.registry.model.index.EppResourceIndex; +import google.registry.model.index.EppResourceIndexBucket; +import google.registry.model.index.ForeignKeyIndex; +import google.registry.testing.AppEngineRule; +import google.registry.testing.FakeClock; +import google.registry.testing.ShardableTestCase; +import org.joda.time.DateTime; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** Unit tests for {@link RdeImportUtils} */ +@RunWith(JUnit4.class) +public class RdeImportUtilsTest extends ShardableTestCase { + + @Rule + public final AppEngineRule appEngine = AppEngineRule.builder() + .withDatastore() + .build(); + + private RdeImportUtils rdeImportUtils; + private FakeClock clock; + + @Before + public void before() { + clock = new FakeClock(); + clock.setTo(DateTime.now()); + rdeImportUtils = new RdeImportUtils(ofy(), clock); + } + + /** Verifies import of a contact that has not been previously imported */ + @Test + public void testImportNewContact() { + ContactResource newContact = buildNewContact(); + assertThat(rdeImportUtils.importContact(newContact)).isTrue(); + assertEppResourceIndexEntityFor(newContact); + assertForeignKeyIndexFor(newContact); + + // verify the new contact was saved + ContactResource saved = getContact("TEST-123"); + assertThat(saved).isNotNull(); + assertThat(saved.getContactId()).isEqualTo(newContact.getContactId()); + assertThat(saved.getEmailAddress()).isEqualTo(newContact.getEmailAddress()); + assertThat(saved.getLastEppUpdateTime()).isEqualTo(newContact.getLastEppUpdateTime()); + } + + /** Verifies that a contact will not be imported more than once */ + @Test + public void testImportExistingContact() { + ContactResource newContact = buildNewContact(); + persistResource(newContact); + ContactResource updatedContact = newContact.asBuilder() + .setLastEppUpdateTime(newContact.getLastEppUpdateTime().plusSeconds(1)) + .build(); + assertThat(rdeImportUtils.importContact(updatedContact)).isFalse(); + + // verify the updated contact was saved + ContactResource saved = getContact("TEST-123"); + assertThat(saved).isNotNull(); + assertThat(saved.getContactId()).isEqualTo(newContact.getContactId()); + assertThat(saved.getEmailAddress()).isEqualTo(newContact.getEmailAddress()); + assertThat(saved.getLastEppUpdateTime()).isEqualTo(newContact.getLastEppUpdateTime()); + } + + private static ContactResource buildNewContact() { + return new ContactResource.Builder() + .setContactId("sh8013") + .setEmailAddress("jdoe@example.com") + .setLastEppUpdateTime(DateTime.parse("2010-10-10T00:00:00.000Z")) + .setRepoId("TEST-123") + .build(); + } + + private static ContactResource getContact(String repoId) { + final Key key = Key.create(ContactResource.class, repoId); + return ofy().transact(new Work() { + + @Override + public ContactResource run() { + return ofy().load().key(key).now(); + }}); + } + + /** + * Confirms that a ForeignKeyIndex exists in the datastore for a given resource. + */ + private static void assertForeignKeyIndexFor(final T resource) { + assertThat(ForeignKeyIndex.load(resource.getClass(), resource.getForeignKey(), DateTime.now())) + .isNotNull(); + } + + /** + * Confirms that an EppResourceIndex entity exists in datastore for a given resource. + */ + private static void assertEppResourceIndexEntityFor(final T resource) { + ImmutableList indices = FluentIterable + .from(ofy().load() + .type(EppResourceIndex.class) + .filter("kind", Key.getKind(resource.getClass()))) + .filter(new Predicate() { + @Override + public boolean apply(EppResourceIndex index) { + return index.getReference().get().equals(resource); + }}) + .toList(); + assertThat(indices).hasSize(1); + assertThat(indices.get(0).getBucket()) + .isEqualTo(EppResourceIndexBucket.getBucketKey(Key.create(resource))); + } +}