diff --git a/core/src/main/java/google/registry/model/billing/BillingEvent.java b/core/src/main/java/google/registry/model/billing/BillingEvent.java index ca1de417c..6c282a65f 100644 --- a/core/src/main/java/google/registry/model/billing/BillingEvent.java +++ b/core/src/main/java/google/registry/model/billing/BillingEvent.java @@ -38,7 +38,7 @@ import google.registry.model.domain.rgp.GracePeriodStatus; import google.registry.model.domain.token.AllocationToken; import google.registry.model.transfer.TransferData.TransferServerApproveEntity; import google.registry.persistence.VKey; -import google.registry.persistence.WithLongVKey; +import google.registry.persistence.WithVKey; import google.registry.persistence.converter.JodaMoneyType; import java.util.Optional; import java.util.Set; @@ -295,7 +295,7 @@ public abstract class BillingEvent extends ImmutableObject @Index(columnList = "cancellation_matching_billing_recurrence_id") }) @AttributeOverride(name = "id", column = @Column(name = "billing_event_id")) - @WithLongVKey(compositeKey = true) + @WithVKey(Long.class) public static class OneTime extends BillingEvent { /** The billable value. */ @@ -473,7 +473,7 @@ public abstract class BillingEvent extends ImmutableObject @Index(columnList = "recurrence_time_of_year") }) @AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id")) - @WithLongVKey(compositeKey = true) + @WithVKey(Long.class) public static class Recurring extends BillingEvent { /** @@ -606,7 +606,7 @@ public abstract class BillingEvent extends ImmutableObject @Index(columnList = "billing_recurrence_id") }) @AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id")) - @WithLongVKey(compositeKey = true) + @WithVKey(Long.class) public static class Cancellation extends BillingEvent { /** The billing time of the charge that is being cancelled. */ diff --git a/core/src/main/java/google/registry/model/bulkquery/DomainLite.java b/core/src/main/java/google/registry/model/bulkquery/DomainLite.java index db1ab725a..a9f436c0d 100644 --- a/core/src/main/java/google/registry/model/bulkquery/DomainLite.java +++ b/core/src/main/java/google/registry/model/bulkquery/DomainLite.java @@ -17,7 +17,7 @@ package google.registry.model.bulkquery; import google.registry.model.domain.Domain; import google.registry.model.domain.DomainBase; import google.registry.persistence.VKey; -import google.registry.persistence.WithStringVKey; +import google.registry.persistence.WithVKey; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Entity; @@ -31,7 +31,7 @@ import javax.persistence.Entity; *

Please refer to {@link BulkQueryEntities} for more information. */ @Entity(name = "Domain") -@WithStringVKey +@WithVKey(String.class) @Access(AccessType.FIELD) public class DomainLite extends DomainBase { diff --git a/core/src/main/java/google/registry/model/contact/Contact.java b/core/src/main/java/google/registry/model/contact/Contact.java index 1b8547756..5a3bbf971 100644 --- a/core/src/main/java/google/registry/model/contact/Contact.java +++ b/core/src/main/java/google/registry/model/contact/Contact.java @@ -18,7 +18,7 @@ import google.registry.model.EppResource.ForeignKeyedEppResource; import google.registry.model.annotations.ExternalMessagingName; import google.registry.model.annotations.ReportedOn; import google.registry.persistence.VKey; -import google.registry.persistence.WithStringVKey; +import google.registry.persistence.WithVKey; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Entity; @@ -45,7 +45,7 @@ import org.joda.time.DateTime; @Index(columnList = "searchName") }) @ExternalMessagingName("contact") -@WithStringVKey(compositeKey = true) +@WithVKey(String.class) @Access(AccessType.FIELD) public class Contact extends ContactBase implements ForeignKeyedEppResource { diff --git a/core/src/main/java/google/registry/model/domain/Domain.java b/core/src/main/java/google/registry/model/domain/Domain.java index fccaa8af7..3ce4259fb 100644 --- a/core/src/main/java/google/registry/model/domain/Domain.java +++ b/core/src/main/java/google/registry/model/domain/Domain.java @@ -22,7 +22,7 @@ import google.registry.model.annotations.ReportedOn; import google.registry.model.domain.secdns.DomainDsData; import google.registry.model.host.Host; import google.registry.persistence.VKey; -import google.registry.persistence.WithStringVKey; +import google.registry.persistence.WithVKey; import java.util.Set; import javax.persistence.Access; import javax.persistence.AccessType; @@ -66,7 +66,7 @@ import org.joda.time.DateTime; @Index(columnList = "transfer_billing_event_id"), @Index(columnList = "transfer_billing_recurrence_id") }) -@WithStringVKey(compositeKey = true) +@WithVKey(String.class) @ExternalMessagingName("domain") @Access(AccessType.FIELD) public class Domain extends DomainBase implements ForeignKeyedEppResource { diff --git a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java index abc8e6851..73552a121 100644 --- a/core/src/main/java/google/registry/model/domain/token/AllocationToken.java +++ b/core/src/main/java/google/registry/model/domain/token/AllocationToken.java @@ -41,7 +41,7 @@ import google.registry.model.common.TimedTransitionProperty; import google.registry.model.reporting.HistoryEntry; import google.registry.persistence.DomainHistoryVKey; import google.registry.persistence.VKey; -import google.registry.persistence.WithStringVKey; +import google.registry.persistence.WithVKey; import java.util.Optional; import java.util.Set; import javax.annotation.Nullable; @@ -58,7 +58,7 @@ import org.joda.time.DateTime; /** An entity representing an allocation token. */ @Entity -@WithStringVKey(compositeKey = true) +@WithVKey(String.class) @Table( indexes = { @Index(columnList = "token", name = "allocation_token_token_idx", unique = true), diff --git a/core/src/main/java/google/registry/model/host/Host.java b/core/src/main/java/google/registry/model/host/Host.java index 6d76972d4..2d9cfde80 100644 --- a/core/src/main/java/google/registry/model/host/Host.java +++ b/core/src/main/java/google/registry/model/host/Host.java @@ -19,7 +19,7 @@ import google.registry.model.EppResource.ForeignKeyedEppResource; import google.registry.model.annotations.ExternalMessagingName; import google.registry.model.annotations.ReportedOn; import google.registry.persistence.VKey; -import google.registry.persistence.WithStringVKey; +import google.registry.persistence.WithVKey; import javax.persistence.Access; import javax.persistence.AccessType; @@ -51,7 +51,7 @@ import javax.persistence.AccessType; @javax.persistence.Index(columnList = "currentSponsorRegistrarId") }) @ExternalMessagingName("host") -@WithStringVKey(compositeKey = true) +@WithVKey(String.class) @Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property) public class Host extends HostBase implements ForeignKeyedEppResource { diff --git a/core/src/main/java/google/registry/model/poll/PollMessage.java b/core/src/main/java/google/registry/model/poll/PollMessage.java index b52c75c95..ecff003e1 100644 --- a/core/src/main/java/google/registry/model/poll/PollMessage.java +++ b/core/src/main/java/google/registry/model/poll/PollMessage.java @@ -46,7 +46,7 @@ import google.registry.model.transfer.TransferResponse; import google.registry.model.transfer.TransferResponse.ContactTransferResponse; import google.registry.model.transfer.TransferResponse.DomainTransferResponse; import google.registry.persistence.VKey; -import google.registry.persistence.WithLongVKey; +import google.registry.persistence.WithVKey; import google.registry.util.NullIgnoringCollectionBuilder; import java.util.Optional; import javax.persistence.AttributeOverride; @@ -342,7 +342,7 @@ public abstract class PollMessage extends ImmutableObject */ @Entity @DiscriminatorValue("ONE_TIME") - @WithLongVKey(compositeKey = true) + @WithVKey(Long.class) public static class OneTime extends PollMessage { @Embedded @@ -544,7 +544,7 @@ public abstract class PollMessage extends ImmutableObject */ @Entity @DiscriminatorValue("AUTORENEW") - @WithLongVKey(compositeKey = true) + @WithVKey(Long.class) public static class Autorenew extends PollMessage { /** The target id of the autorenew event. */ diff --git a/core/src/main/java/google/registry/persistence/WithStringVKey.java b/core/src/main/java/google/registry/persistence/WithStringVKey.java deleted file mode 100644 index 2c7c47faf..000000000 --- a/core/src/main/java/google/registry/persistence/WithStringVKey.java +++ /dev/null @@ -1,42 +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.persistence; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; -import javax.persistence.AttributeConverter; -import javax.persistence.Entity; - -/** - * Annotation for {@link Entity} which id is string type and needs an {@link AttributeConverter} for - * its VKey. - */ -@Target({ElementType.TYPE}) -public @interface WithStringVKey { - /** - * Sets the suffix of the class name for the {@link AttributeConverter} generated by - * StringVKeyProcessor. If not set, the suffix will be the type name of the VKey. Note that the - * class name will be "VKeyConverter_" concatenated with the suffix. - */ - String classNameSuffix() default ""; - - /** - * Set to true if this is a composite vkey. - * - *

For composite VKeys, we don't attempt to define an objectify key when loading from SQL: the - * enclosing class has to take care of that. - */ - boolean compositeKey() default false; -} diff --git a/core/src/main/java/google/registry/persistence/WithLongVKey.java b/core/src/main/java/google/registry/persistence/WithVKey.java similarity index 50% rename from core/src/main/java/google/registry/persistence/WithLongVKey.java rename to core/src/main/java/google/registry/persistence/WithVKey.java index 1f434bca2..283667c57 100644 --- a/core/src/main/java/google/registry/persistence/WithLongVKey.java +++ b/core/src/main/java/google/registry/persistence/WithVKey.java @@ -1,4 +1,4 @@ -// Copyright 2020 The Nomulus Authors. All Rights Reserved. +// Copyright 2022 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. @@ -14,29 +14,22 @@ package google.registry.persistence; +import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Target; import javax.persistence.AttributeConverter; import javax.persistence.Entity; /** - * Annotation for {@link Entity} which id is long type and needs an {@link AttributeConverter} for - * its VKey. + * Annotation for {@link Entity} that can be saved as a foreign key in the form of a {@link VKey} in + * another table. + * + *

A {@link AttributeConverter} named {@code VKeyConverter_[EntityClassSimpleName]} will be + * automatically generated by {@code google.registry.processors.VKeyProcessor}, this class must be + * manually added to {@code persistence.xml} in order for it to be picked up by Hibernate. */ @Target({ElementType.TYPE}) -public @interface WithLongVKey { - /** - * Sets the suffix of the class name for the {@link AttributeConverter} generated by - * LongVKeyProcessor. If not set, the suffix will be the type name of the VKey. Note that the - * class name will be "VKeyConverter_" concatenated with the suffix. - */ - String classNameSuffix() default ""; - - /** - * Set to true if this is a composite vkey. - * - *

For composite VKeys, we don't attempt to define an objectify key when loading from SQL: the - * enclosing class has to take care of that. - */ - boolean compositeKey() default false; +public @interface WithVKey { + /** The type of the SQL primary ID of the entity that is saved in the {@link VKey} */ + Class value(); } diff --git a/core/src/main/java/google/registry/persistence/converter/VKeyConverter.java b/core/src/main/java/google/registry/persistence/converter/VKeyConverter.java index 61bce4ee2..39e8eec11 100644 --- a/core/src/main/java/google/registry/persistence/converter/VKeyConverter.java +++ b/core/src/main/java/google/registry/persistence/converter/VKeyConverter.java @@ -14,20 +14,34 @@ package google.registry.persistence.converter; -import com.googlecode.objectify.Key; import google.registry.persistence.VKey; import java.io.Serializable; import javax.annotation.Nullable; import javax.persistence.AttributeConverter; -/** Converts VKey to a string or long column. */ +/** + * Converts {@link VKey} to/from a type that can be directly stored in the database. + * + *

Typically the converted type is {@link String} or {@link Long}. + */ public abstract class VKeyConverter implements AttributeConverter, C> { + @Override @Nullable - @SuppressWarnings("unchecked") public C convertToDatabaseColumn(@Nullable VKey attribute) { - return attribute == null ? null : (C) attribute.getSqlKey(); + if (attribute == null) { + return null; + } + try { + return getKeyClass().cast(attribute.getSqlKey()); + } catch (ClassCastException e) { + throw new RuntimeException( + String.format( + "Cannot cast SQL key %s of type %s to type %s", + attribute.getSqlKey(), attribute.getSqlKey().getClass(), getKeyClass()), + e); + } } @Override @@ -36,27 +50,12 @@ public abstract class VKeyConverter if (dbData == null) { return null; } - Class clazz = getAttributeClass(); - Key ofyKey; - if (!hasCompositeOfyKey()) { - // If this isn't a composite key, we can create the Ofy key from the SQL key. - ofyKey = - dbData instanceof String - ? Key.create(clazz, (String) dbData) - : Key.create(clazz, (Long) dbData); - return VKey.create(clazz, dbData, ofyKey); - } else { - // We don't know how to create the Ofy key and probably don't have everything necessary to do - // it anyway, so just create an asymmetric key - the containing object will have to convert it - // into a symmetric key. - return VKey.createSql(clazz, dbData); - } + return VKey.createSql(getEntityClass(), dbData); } - protected boolean hasCompositeOfyKey() { - return false; - } + /** Returns the class of the entity that the VKey represents. */ + protected abstract Class getEntityClass(); - /** Returns the class of the attribute. */ - protected abstract Class getAttributeClass(); + /** Returns the class of the key that the VKey holds. */ + protected abstract Class getKeyClass(); } diff --git a/core/src/test/java/google/registry/persistence/converter/LongVKeyConverterTest.java b/core/src/test/java/google/registry/persistence/converter/LongVKeyConverterTest.java deleted file mode 100644 index 090d4ca3f..000000000 --- a/core/src/test/java/google/registry/persistence/converter/LongVKeyConverterTest.java +++ /dev/null @@ -1,86 +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.persistence.converter; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.testing.DatabaseHelper.insertInDb; - -import google.registry.model.ImmutableObject; -import google.registry.persistence.VKey; -import google.registry.persistence.WithLongVKey; -import google.registry.testing.AppEngineExtension; -import javax.persistence.Entity; -import javax.persistence.Id; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -/** Test SQL persistence of VKey. */ -public class LongVKeyConverterTest { - - @RegisterExtension - public final AppEngineExtension appEngineExtension = - new AppEngineExtension.Builder() - .withCloudSql() - .withoutCannedData() - .withJpaUnitTestEntities( - TestLongEntity.class, - VKeyConverter_LongType.class, - VKeyConverter_CompositeLongType.class) - .withOfyTestEntities(TestLongEntity.class, CompositeKeyTestLongEntity.class) - .build(); - - @Test - void testRoundTrip() { - TestLongEntity original = - new TestLongEntity( - VKey.createSql(TestLongEntity.class, 10L), - VKey.createSql(CompositeKeyTestLongEntity.class, 20L)); - insertInDb(original); - - TestLongEntity retrieved = - jpaTm().transact(() -> jpaTm().getEntityManager().find(TestLongEntity.class, "id")); - assertThat(retrieved.number.getSqlKey()).isEqualTo(10L); - assertThat(retrieved.number.getOfyKey().getId()).isEqualTo(10L); - - assertThat(retrieved.composite.getSqlKey()).isEqualTo(20L); - assertThat(retrieved.composite.maybeGetOfyKey().isPresent()).isFalse(); - } - - @Entity(name = "TestLongEntity") - @com.googlecode.objectify.annotation.Entity - @WithLongVKey(classNameSuffix = "LongType") - static class TestLongEntity extends ImmutableObject { - @com.googlecode.objectify.annotation.Id @Id String id = "id"; - - VKey number; - VKey composite; - - TestLongEntity(VKey number, VKey composite) { - this.number = number; - this.composite = composite; - } - - /** Default constructor, needed for hibernate. */ - public TestLongEntity() {} - } - - @Entity(name = "CompositeKeyTestLongEntity") - @com.googlecode.objectify.annotation.Entity - @WithLongVKey(classNameSuffix = "CompositeLongType", compositeKey = true) - static class CompositeKeyTestLongEntity { - @com.googlecode.objectify.annotation.Id @Id String id = "id"; - } -} diff --git a/core/src/test/java/google/registry/persistence/converter/StringVKeyConverterTest.java b/core/src/test/java/google/registry/persistence/converter/StringVKeyConverterTest.java deleted file mode 100644 index e03f6b61f..000000000 --- a/core/src/test/java/google/registry/persistence/converter/StringVKeyConverterTest.java +++ /dev/null @@ -1,91 +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.persistence.converter; - -import static com.google.common.truth.Truth.assertThat; -import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; -import static google.registry.testing.DatabaseHelper.insertInDb; - -import google.registry.model.ImmutableObject; -import google.registry.persistence.VKey; -import google.registry.persistence.WithStringVKey; -import google.registry.testing.AppEngineExtension; -import javax.persistence.Entity; -import javax.persistence.Id; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -/** Test SQL persistence of VKey. */ -public class StringVKeyConverterTest { - - @RegisterExtension - public final AppEngineExtension appEngineExtension = - new AppEngineExtension.Builder() - .withCloudSql() - .withoutCannedData() - .withJpaUnitTestEntities( - TestStringEntity.class, - VKeyConverter_StringType.class, - VKeyConverter_CompositeStringType.class) - .withOfyTestEntities(TestStringEntity.class, CompositeKeyTestStringEntity.class) - .build(); - - @Test - void testRoundTrip() { - TestStringEntity original = - new TestStringEntity( - "TheRealSpartacus", - VKey.createSql(TestStringEntity.class, "ImSpartacus!"), - VKey.createSql(CompositeKeyTestStringEntity.class, "NoImSpartacus!")); - insertInDb(original); - - TestStringEntity retrieved = - jpaTm() - .transact( - () -> jpaTm().getEntityManager().find(TestStringEntity.class, "TheRealSpartacus")); - assertThat(retrieved.other.getSqlKey()).isEqualTo("ImSpartacus!"); - assertThat(retrieved.other.getOfyKey().getName()).isEqualTo("ImSpartacus!"); - - assertThat(retrieved.composite.getSqlKey()).isEqualTo("NoImSpartacus!"); - assertThat(retrieved.composite.maybeGetOfyKey().isPresent()).isFalse(); - } - - @Entity(name = "TestStringEntity") - @com.googlecode.objectify.annotation.Entity - @WithStringVKey(classNameSuffix = "StringType") - static class TestStringEntity extends ImmutableObject { - @com.googlecode.objectify.annotation.Id @Id String id; - - VKey other; - VKey composite; - - TestStringEntity( - String id, VKey other, VKey composite) { - this.id = id; - this.other = other; - this.composite = composite; - } - - /** Default constructor, needed for hibernate. */ - public TestStringEntity() {} - } - - @Entity(name = "CompositeKeyTestStringEntity") - @com.googlecode.objectify.annotation.Entity - @WithStringVKey(classNameSuffix = "CompositeStringType", compositeKey = true) - static class CompositeKeyTestStringEntity { - @com.googlecode.objectify.annotation.Id @Id String id = "id"; - } -} diff --git a/core/src/test/java/google/registry/persistence/converter/VKeyConverterTest.java b/core/src/test/java/google/registry/persistence/converter/VKeyConverterTest.java new file mode 100644 index 000000000..1a74212c2 --- /dev/null +++ b/core/src/test/java/google/registry/persistence/converter/VKeyConverterTest.java @@ -0,0 +1,103 @@ +// Copyright 2022 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.persistence.converter; + +import static com.google.common.truth.Truth.assertThat; +import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm; +import static google.registry.testing.DatabaseHelper.insertInDb; + +import google.registry.model.ImmutableObject; +import google.registry.persistence.VKey; +import google.registry.persistence.WithVKey; +import google.registry.persistence.transaction.JpaTestExtensions; +import google.registry.persistence.transaction.JpaTestExtensions.JpaUnitTestExtension; +import javax.persistence.Entity; +import javax.persistence.Id; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** Test SQL persistence of {@link VKey}. */ +public class VKeyConverterTest { + + @RegisterExtension + public final JpaUnitTestExtension jpa = + new JpaTestExtensions.Builder() + .withoutCannedData() + .withEntityClass( + TestEntity.class, + TestStringEntity.class, + TestLongEntity.class, + VKeyConverter_TestStringEntity.class, + VKeyConverter_TestLongEntity.class) + .buildUnitTestExtension(); + + @Test + void testRoundTrip() { + TestStringEntity stringEntity = new TestStringEntity("TheRealSpartacus"); + VKey stringKey = VKey.createSql(TestStringEntity.class, "TheRealSpartacus"); + TestLongEntity longEntity = new TestLongEntity(300L); + VKey longKey = VKey.createSql(TestLongEntity.class, 300L); + TestEntity original = new TestEntity(1984L, stringKey, longKey); + insertInDb(stringEntity, longEntity, original); + + TestEntity retrieved = + jpaTm().transact(() -> jpaTm().getEntityManager().find(TestEntity.class, 1984L)); + assertThat(retrieved.stringKey).isEqualTo(stringKey); + assertThat(retrieved.longKey).isEqualTo(longKey); + } + + @Entity(name = "TestStringEntity") + @WithVKey(String.class) + protected static class TestStringEntity extends ImmutableObject { + @Id String id; + + TestStringEntity(String id) { + this.id = id; + } + + /** Default constructor, needed for hibernate. */ + public TestStringEntity() {} + } + + @Entity(name = "TestLongEntity") + @WithVKey(Long.class) + protected static class TestLongEntity extends ImmutableObject { + @Id Long id; + + TestLongEntity(Long id) { + this.id = id; + } + + /** Default constructor, needed for hibernate. */ + public TestLongEntity() {} + } + + @Entity(name = "TestEntity") + @WithVKey(String.class) + protected static class TestEntity extends ImmutableObject { + @Id Long id; + VKey stringKey; + VKey longKey; + + TestEntity(Long id, VKey stringKey, VKey longKey) { + this.id = id; + this.stringKey = stringKey; + this.longKey = longKey; + } + + /** Default constructor, needed for hibernate. */ + public TestEntity() {} + } +} diff --git a/processor/src/main/java/google/registry/processors/AbstractVKeyProcessor.java b/processor/src/main/java/google/registry/processors/AbstractVKeyProcessor.java deleted file mode 100644 index a405fa3e3..000000000 --- a/processor/src/main/java/google/registry/processors/AbstractVKeyProcessor.java +++ /dev/null @@ -1,185 +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.processors; - -import static com.google.common.base.Preconditions.checkState; -import static com.google.common.collect.ImmutableList.toImmutableList; - -import com.squareup.javapoet.AnnotationSpec; -import com.squareup.javapoet.ClassName; -import com.squareup.javapoet.JavaFile; -import com.squareup.javapoet.MethodSpec; -import com.squareup.javapoet.ParameterizedTypeName; -import com.squareup.javapoet.TypeName; -import com.squareup.javapoet.TypeSpec; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.processing.AbstractProcessor; -import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.Modifier; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import javax.lang.model.util.Elements; -import javax.lang.model.util.Types; -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; - -/** Abstract processor to generate {@link AttributeConverter} for VKey type. */ -public abstract class AbstractVKeyProcessor extends AbstractProcessor { - - private static final String CONVERTER_CLASS_NAME_TEMP = "VKeyConverter_%s"; - // The method with same name should be defined in WithStringVKey and WithLongVKey - private static final String CLASS_NAME_SUFFIX_KEY = "classNameSuffix"; - - // Method in WithStringVKey and WithLongVKey to indicate that this is a composite key. - private static final String COMPOSITE_KEY_KEY = "compositeKey"; - - abstract Class getSqlColumnType(); - - abstract String getAnnotationSimpleName(); - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - annotations.forEach( - vKeyAnnotationType -> { - ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(vKeyAnnotationType)) - .forEach( - annotatedTypeElement -> { - DeclaredType entityType = getDeclaredType(annotatedTypeElement); - - List actualAnnotation = - annotatedTypeElement.getAnnotationMirrors().stream() - .filter( - annotationType -> - annotationType - .getAnnotationType() - .asElement() - .equals(vKeyAnnotationType)) - .collect(toImmutableList()); - checkState( - actualAnnotation.size() == 1, - String.format( - "type can have only 1 %s annotation", getAnnotationSimpleName())); - String converterClassNameSuffix = ""; - boolean hasCompositeOfyKey = false; - for (Map.Entry entry : - actualAnnotation.get(0).getElementValues().entrySet()) { - String keyName = entry.getKey().getSimpleName().toString(); - Object value = entry.getValue().getValue(); - if (keyName.equals(CLASS_NAME_SUFFIX_KEY)) { - converterClassNameSuffix = ((String) value).trim(); - } else if (keyName.equals(COMPOSITE_KEY_KEY)) { - hasCompositeOfyKey = (Boolean) value; - } - } - if (converterClassNameSuffix.isEmpty()) { - converterClassNameSuffix = - getTypeUtils().asElement(entityType).getSimpleName().toString(); - } - - try { - createJavaFile( - getPackageName(annotatedTypeElement), - String.format(CONVERTER_CLASS_NAME_TEMP, converterClassNameSuffix), - entityType, - hasCompositeOfyKey) - .writeTo(processingEnv.getFiler()); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - }); - return false; - } - - private JavaFile createJavaFile( - String packageName, - String converterClassName, - TypeMirror entityTypeMirror, - boolean hasCompositeOfyKey) { - TypeName entityType = ClassName.get(entityTypeMirror); - - ParameterizedTypeName attributeConverter = - ParameterizedTypeName.get( - ClassName.get("google.registry.persistence.converter", "VKeyConverter"), - entityType, - ClassName.get(getSqlColumnType())); - - MethodSpec getAttributeClass = - MethodSpec.methodBuilder("getAttributeClass") - .addAnnotation(Override.class) - .addModifiers(Modifier.PROTECTED) - .returns( - ParameterizedTypeName.get( - ClassName.get(Class.class), ClassName.get(entityTypeMirror))) - .addStatement("return $T.class", entityType) - .build(); - - TypeSpec.Builder classBuilder = - TypeSpec.classBuilder(converterClassName) - .addAnnotation( - AnnotationSpec.builder(ClassName.get(Converter.class)) - .addMember("autoApply", "true") - .build()) - .addModifiers(Modifier.FINAL) - .superclass(attributeConverter) - .addMethod(getAttributeClass); - - // If this is a converter for a composite vkey type, generate an override for the default - // {@link google.registry.persistence.VKeyConverter.hasCompositeOfyKey()} method, which returns - // false. - if (hasCompositeOfyKey) { - MethodSpec hasCompositeOfyKeyMethod = - MethodSpec.methodBuilder("hasCompositeOfyKey") - .addAnnotation(Override.class) - .addModifiers(Modifier.PROTECTED) - .returns(boolean.class) - .addStatement("return true", entityType) - .build(); - - classBuilder.addMethod(hasCompositeOfyKeyMethod); - } - - TypeSpec vKeyConverter = classBuilder.build(); - return JavaFile.builder(packageName, vKeyConverter).build(); - } - - private DeclaredType getDeclaredType(Element element) { - checkState(element.asType().getKind() == TypeKind.DECLARED, "element is not a DeclaredType"); - return (DeclaredType) element.asType(); - } - - private String getPackageName(Element element) { - return getElementUtils().getPackageOf(element).getQualifiedName().toString(); - } - - private Elements getElementUtils() { - return processingEnv.getElementUtils(); - } - - private Types getTypeUtils() { - return processingEnv.getTypeUtils(); - } -} diff --git a/processor/src/main/java/google/registry/processors/LongVKeyProcessor.java b/processor/src/main/java/google/registry/processors/LongVKeyProcessor.java deleted file mode 100644 index 52b904fdf..000000000 --- a/processor/src/main/java/google/registry/processors/LongVKeyProcessor.java +++ /dev/null @@ -1,37 +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.processors; - -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; - -/** Annotation processor for entity that is annotated with WithLongVKey. */ -@SupportedAnnotationTypes("google.registry.persistence.WithLongVKey") -@SupportedSourceVersion(SourceVersion.RELEASE_8) -public class LongVKeyProcessor extends AbstractVKeyProcessor { - - private static final String ANNOTATION_SIMPLE_NAME = "WithLongVKey"; - - @Override - Class getSqlColumnType() { - return Long.class; - } - - @Override - String getAnnotationSimpleName() { - return ANNOTATION_SIMPLE_NAME; - } -} diff --git a/processor/src/main/java/google/registry/processors/StringVKeyProcessor.java b/processor/src/main/java/google/registry/processors/StringVKeyProcessor.java deleted file mode 100644 index 0dba511f6..000000000 --- a/processor/src/main/java/google/registry/processors/StringVKeyProcessor.java +++ /dev/null @@ -1,37 +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.processors; - -import javax.annotation.processing.SupportedAnnotationTypes; -import javax.annotation.processing.SupportedSourceVersion; -import javax.lang.model.SourceVersion; - -/** Annotation processor for entity that is annotated with WithStringVKey. */ -@SupportedAnnotationTypes("google.registry.persistence.WithStringVKey") -@SupportedSourceVersion(SourceVersion.RELEASE_8) -public class StringVKeyProcessor extends AbstractVKeyProcessor { - - private static final String ANNOTATION_SIMPLE_NAME = "WithStringVKey"; - - @Override - Class getSqlColumnType() { - return String.class; - } - - @Override - String getAnnotationSimpleName() { - return ANNOTATION_SIMPLE_NAME; - } -} diff --git a/processor/src/main/java/google/registry/processors/VKeyProcessor.java b/processor/src/main/java/google/registry/processors/VKeyProcessor.java new file mode 100644 index 000000000..47818742a --- /dev/null +++ b/processor/src/main/java/google/registry/processors/VKeyProcessor.java @@ -0,0 +1,169 @@ +// 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.processors; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.toImmutableList; + +import com.squareup.javapoet.AnnotationSpec; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.AnnotationValue; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** Processor to generate {@link AttributeConverter} for {@code VKey} type. */ +@SupportedAnnotationTypes("google.registry.persistence.WithVKey") +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public class VKeyProcessor extends AbstractProcessor { + + private static final String CONVERTER_CLASS_NAME_TEMP = "VKeyConverter_%s"; + + private static final String VKEY_TYPE_METHOD_NAME = "value"; + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + annotations.forEach( + vKeyAnnotationType -> + ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(vKeyAnnotationType)) + .forEach( + annotatedTypeElement -> { + DeclaredType entityType = getDeclaredType(annotatedTypeElement); + String simpleTypeName = + getTypeUtils().asElement(entityType).getSimpleName().toString(); + List actualAnnotations = + annotatedTypeElement.getAnnotationMirrors().stream() + .filter( + annotationType -> + annotationType + .getAnnotationType() + .asElement() + .equals(vKeyAnnotationType)) + .collect(toImmutableList()); + checkState( + actualAnnotations.size() == 1, + "% can have only one @WithVKey annotation", + simpleTypeName); + TypeName keyType = null; + for (Map.Entry entry : + actualAnnotations.get(0).getElementValues().entrySet()) { + String keyName = entry.getKey().getSimpleName().toString(); + if (keyName.equals(VKEY_TYPE_METHOD_NAME)) { + try { + keyType = + TypeName.get(Class.forName(entry.getValue().getValue().toString())); + } catch (ClassNotFoundException e) { + throw new RuntimeException( + String.format( + "VKey key class %s is not valid", + entry.getValue().getValue().toString()), + e); + } + } + } + try { + createJavaFile( + getPackageName(annotatedTypeElement), + String.format(CONVERTER_CLASS_NAME_TEMP, simpleTypeName), + TypeName.get(entityType), + keyType) + .writeTo(processingEnv.getFiler()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + })); + return false; + } + + private static JavaFile createJavaFile( + String packageName, String converterClassName, TypeName entityType, TypeName keyType) { + ParameterizedTypeName attributeConverter = + ParameterizedTypeName.get( + ClassName.get("google.registry.persistence.converter", "VKeyConverter"), + entityType, + keyType); + + MethodSpec getEntityClass = + MethodSpec.methodBuilder("getEntityClass") + .addAnnotation(Override.class) + .addModifiers(Modifier.PROTECTED) + .returns(ParameterizedTypeName.get(ClassName.get(Class.class), entityType)) + .addStatement("return $T.class", entityType) + .build(); + + MethodSpec getKeyClass = + MethodSpec.methodBuilder("getKeyClass") + .addAnnotation(Override.class) + .addModifiers(Modifier.PROTECTED) + .returns(ParameterizedTypeName.get(ClassName.get(Class.class), keyType)) + .addStatement("return $T.class", keyType) + .build(); + + TypeSpec.Builder classBuilder = + TypeSpec.classBuilder(converterClassName) + .addAnnotation( + AnnotationSpec.builder(ClassName.get(Converter.class)) + .addMember("autoApply", "true") + .build()) + .addModifiers(Modifier.FINAL) + .superclass(attributeConverter) + .addMethod(getEntityClass) + .addMethod(getKeyClass); + + TypeSpec vKeyConverter = classBuilder.build(); + return JavaFile.builder(packageName, vKeyConverter).build(); + } + + private static DeclaredType getDeclaredType(Element element) { + checkState(element.asType().getKind() == TypeKind.DECLARED, "element is not a DeclaredType"); + return (DeclaredType) element.asType(); + } + + private String getPackageName(Element element) { + return getElementUtils().getPackageOf(element).getQualifiedName().toString(); + } + + private Elements getElementUtils() { + return processingEnv.getElementUtils(); + } + + private Types getTypeUtils() { + return processingEnv.getTypeUtils(); + } +} diff --git a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor index b3f272e52..de9f1f230 100644 --- a/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,2 +1 @@ -google.registry.processors.StringVKeyProcessor -google.registry.processors.LongVKeyProcessor \ No newline at end of file +google.registry.processors.VKeyProcessor \ No newline at end of file