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 extends Serializable> 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 extends T> 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 extends TypeElement> 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 extends ExecutableElement, ? extends AnnotationValue> 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 extends TypeElement> 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 extends ExecutableElement, ? extends AnnotationValue> 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