mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 12:07:51 +02:00
Refactor VKeyConverter (#1794)
Remove the redundant composite key boolean and simply the annotation structure a bit.
This commit is contained in:
parent
916f746af9
commit
fd635628ee
18 changed files with 324 additions and 539 deletions
|
@ -38,7 +38,7 @@ import google.registry.model.domain.rgp.GracePeriodStatus;
|
||||||
import google.registry.model.domain.token.AllocationToken;
|
import google.registry.model.domain.token.AllocationToken;
|
||||||
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
import google.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithLongVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import google.registry.persistence.converter.JodaMoneyType;
|
import google.registry.persistence.converter.JodaMoneyType;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -295,7 +295,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
@Index(columnList = "cancellation_matching_billing_recurrence_id")
|
@Index(columnList = "cancellation_matching_billing_recurrence_id")
|
||||||
})
|
})
|
||||||
@AttributeOverride(name = "id", column = @Column(name = "billing_event_id"))
|
@AttributeOverride(name = "id", column = @Column(name = "billing_event_id"))
|
||||||
@WithLongVKey(compositeKey = true)
|
@WithVKey(Long.class)
|
||||||
public static class OneTime extends BillingEvent {
|
public static class OneTime extends BillingEvent {
|
||||||
|
|
||||||
/** The billable value. */
|
/** The billable value. */
|
||||||
|
@ -473,7 +473,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
@Index(columnList = "recurrence_time_of_year")
|
@Index(columnList = "recurrence_time_of_year")
|
||||||
})
|
})
|
||||||
@AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id"))
|
@AttributeOverride(name = "id", column = @Column(name = "billing_recurrence_id"))
|
||||||
@WithLongVKey(compositeKey = true)
|
@WithVKey(Long.class)
|
||||||
public static class Recurring extends BillingEvent {
|
public static class Recurring extends BillingEvent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -606,7 +606,7 @@ public abstract class BillingEvent extends ImmutableObject
|
||||||
@Index(columnList = "billing_recurrence_id")
|
@Index(columnList = "billing_recurrence_id")
|
||||||
})
|
})
|
||||||
@AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id"))
|
@AttributeOverride(name = "id", column = @Column(name = "billing_cancellation_id"))
|
||||||
@WithLongVKey(compositeKey = true)
|
@WithVKey(Long.class)
|
||||||
public static class Cancellation extends BillingEvent {
|
public static class Cancellation extends BillingEvent {
|
||||||
|
|
||||||
/** The billing time of the charge that is being cancelled. */
|
/** The billing time of the charge that is being cancelled. */
|
||||||
|
|
|
@ -17,7 +17,7 @@ package google.registry.model.bulkquery;
|
||||||
import google.registry.model.domain.Domain;
|
import google.registry.model.domain.Domain;
|
||||||
import google.registry.model.domain.DomainBase;
|
import google.registry.model.domain.DomainBase;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithStringVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
@ -31,7 +31,7 @@ import javax.persistence.Entity;
|
||||||
* <p>Please refer to {@link BulkQueryEntities} for more information.
|
* <p>Please refer to {@link BulkQueryEntities} for more information.
|
||||||
*/
|
*/
|
||||||
@Entity(name = "Domain")
|
@Entity(name = "Domain")
|
||||||
@WithStringVKey
|
@WithVKey(String.class)
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
public class DomainLite extends DomainBase {
|
public class DomainLite extends DomainBase {
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||||
import google.registry.model.annotations.ExternalMessagingName;
|
import google.registry.model.annotations.ExternalMessagingName;
|
||||||
import google.registry.model.annotations.ReportedOn;
|
import google.registry.model.annotations.ReportedOn;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithStringVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
@ -45,7 +45,7 @@ import org.joda.time.DateTime;
|
||||||
@Index(columnList = "searchName")
|
@Index(columnList = "searchName")
|
||||||
})
|
})
|
||||||
@ExternalMessagingName("contact")
|
@ExternalMessagingName("contact")
|
||||||
@WithStringVKey(compositeKey = true)
|
@WithVKey(String.class)
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
public class Contact extends ContactBase implements ForeignKeyedEppResource {
|
public class Contact extends ContactBase implements ForeignKeyedEppResource {
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import google.registry.model.annotations.ReportedOn;
|
||||||
import google.registry.model.domain.secdns.DomainDsData;
|
import google.registry.model.domain.secdns.DomainDsData;
|
||||||
import google.registry.model.host.Host;
|
import google.registry.model.host.Host;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithStringVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
|
@ -66,7 +66,7 @@ import org.joda.time.DateTime;
|
||||||
@Index(columnList = "transfer_billing_event_id"),
|
@Index(columnList = "transfer_billing_event_id"),
|
||||||
@Index(columnList = "transfer_billing_recurrence_id")
|
@Index(columnList = "transfer_billing_recurrence_id")
|
||||||
})
|
})
|
||||||
@WithStringVKey(compositeKey = true)
|
@WithVKey(String.class)
|
||||||
@ExternalMessagingName("domain")
|
@ExternalMessagingName("domain")
|
||||||
@Access(AccessType.FIELD)
|
@Access(AccessType.FIELD)
|
||||||
public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
public class Domain extends DomainBase implements ForeignKeyedEppResource {
|
||||||
|
|
|
@ -41,7 +41,7 @@ import google.registry.model.common.TimedTransitionProperty;
|
||||||
import google.registry.model.reporting.HistoryEntry;
|
import google.registry.model.reporting.HistoryEntry;
|
||||||
import google.registry.persistence.DomainHistoryVKey;
|
import google.registry.persistence.DomainHistoryVKey;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithStringVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -58,7 +58,7 @@ import org.joda.time.DateTime;
|
||||||
|
|
||||||
/** An entity representing an allocation token. */
|
/** An entity representing an allocation token. */
|
||||||
@Entity
|
@Entity
|
||||||
@WithStringVKey(compositeKey = true)
|
@WithVKey(String.class)
|
||||||
@Table(
|
@Table(
|
||||||
indexes = {
|
indexes = {
|
||||||
@Index(columnList = "token", name = "allocation_token_token_idx", unique = true),
|
@Index(columnList = "token", name = "allocation_token_token_idx", unique = true),
|
||||||
|
|
|
@ -19,7 +19,7 @@ import google.registry.model.EppResource.ForeignKeyedEppResource;
|
||||||
import google.registry.model.annotations.ExternalMessagingName;
|
import google.registry.model.annotations.ExternalMessagingName;
|
||||||
import google.registry.model.annotations.ReportedOn;
|
import google.registry.model.annotations.ReportedOn;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithStringVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import javax.persistence.Access;
|
import javax.persistence.Access;
|
||||||
import javax.persistence.AccessType;
|
import javax.persistence.AccessType;
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ import javax.persistence.AccessType;
|
||||||
@javax.persistence.Index(columnList = "currentSponsorRegistrarId")
|
@javax.persistence.Index(columnList = "currentSponsorRegistrarId")
|
||||||
})
|
})
|
||||||
@ExternalMessagingName("host")
|
@ExternalMessagingName("host")
|
||||||
@WithStringVKey(compositeKey = true)
|
@WithVKey(String.class)
|
||||||
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
|
@Access(AccessType.FIELD) // otherwise it'll use the default if the repoId (property)
|
||||||
public class Host extends HostBase implements ForeignKeyedEppResource {
|
public class Host extends HostBase implements ForeignKeyedEppResource {
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ import google.registry.model.transfer.TransferResponse;
|
||||||
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
import google.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||||
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
import google.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import google.registry.persistence.WithLongVKey;
|
import google.registry.persistence.WithVKey;
|
||||||
import google.registry.util.NullIgnoringCollectionBuilder;
|
import google.registry.util.NullIgnoringCollectionBuilder;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.persistence.AttributeOverride;
|
import javax.persistence.AttributeOverride;
|
||||||
|
@ -342,7 +342,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@DiscriminatorValue("ONE_TIME")
|
@DiscriminatorValue("ONE_TIME")
|
||||||
@WithLongVKey(compositeKey = true)
|
@WithVKey(Long.class)
|
||||||
public static class OneTime extends PollMessage {
|
public static class OneTime extends PollMessage {
|
||||||
|
|
||||||
@Embedded
|
@Embedded
|
||||||
|
@ -544,7 +544,7 @@ public abstract class PollMessage extends ImmutableObject
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
@DiscriminatorValue("AUTORENEW")
|
@DiscriminatorValue("AUTORENEW")
|
||||||
@WithLongVKey(compositeKey = true)
|
@WithVKey(Long.class)
|
||||||
public static class Autorenew extends PollMessage {
|
public static class Autorenew extends PollMessage {
|
||||||
|
|
||||||
/** The target id of the autorenew event. */
|
/** The target id of the autorenew event. */
|
||||||
|
|
|
@ -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.
|
|
||||||
*
|
|
||||||
* <p>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;
|
|
||||||
}
|
|
|
@ -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");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
|
@ -14,29 +14,22 @@
|
||||||
|
|
||||||
package google.registry.persistence;
|
package google.registry.persistence;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import javax.persistence.AttributeConverter;
|
import javax.persistence.AttributeConverter;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Annotation for {@link Entity} which id is long type and needs an {@link AttributeConverter} for
|
* Annotation for {@link Entity} that can be saved as a foreign key in the form of a {@link VKey} in
|
||||||
* its VKey.
|
* another table.
|
||||||
|
*
|
||||||
|
* <p>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})
|
@Target({ElementType.TYPE})
|
||||||
public @interface WithLongVKey {
|
public @interface WithVKey {
|
||||||
/**
|
/** The type of the SQL primary ID of the entity that is saved in the {@link VKey} */
|
||||||
* Sets the suffix of the class name for the {@link AttributeConverter} generated by
|
Class<? extends Serializable> value();
|
||||||
* 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.
|
|
||||||
*
|
|
||||||
* <p>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;
|
|
||||||
}
|
}
|
|
@ -14,20 +14,34 @@
|
||||||
|
|
||||||
package google.registry.persistence.converter;
|
package google.registry.persistence.converter;
|
||||||
|
|
||||||
import com.googlecode.objectify.Key;
|
|
||||||
import google.registry.persistence.VKey;
|
import google.registry.persistence.VKey;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.persistence.AttributeConverter;
|
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.
|
||||||
|
*
|
||||||
|
* <p>Typically the converted type is {@link String} or {@link Long}.
|
||||||
|
*/
|
||||||
public abstract class VKeyConverter<T, C extends Serializable>
|
public abstract class VKeyConverter<T, C extends Serializable>
|
||||||
implements AttributeConverter<VKey<? extends T>, C> {
|
implements AttributeConverter<VKey<? extends T>, C> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public C convertToDatabaseColumn(@Nullable VKey<? extends T> attribute) {
|
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
|
@Override
|
||||||
|
@ -36,27 +50,12 @@ public abstract class VKeyConverter<T, C extends Serializable>
|
||||||
if (dbData == null) {
|
if (dbData == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
Class<T> clazz = getAttributeClass();
|
return VKey.createSql(getEntityClass(), dbData);
|
||||||
Key<T> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasCompositeOfyKey() {
|
/** Returns the class of the entity that the VKey represents. */
|
||||||
return false;
|
protected abstract Class<T> getEntityClass();
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the class of the attribute. */
|
/** Returns the class of the key that the VKey holds. */
|
||||||
protected abstract Class<T> getAttributeClass();
|
protected abstract Class<C> getKeyClass();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<TestLongEntity> number;
|
|
||||||
VKey<CompositeKeyTestLongEntity> composite;
|
|
||||||
|
|
||||||
TestLongEntity(VKey<TestLongEntity> number, VKey<CompositeKeyTestLongEntity> 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";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<TestStringEntity> other;
|
|
||||||
VKey<CompositeKeyTestStringEntity> composite;
|
|
||||||
|
|
||||||
TestStringEntity(
|
|
||||||
String id, VKey<TestStringEntity> other, VKey<CompositeKeyTestStringEntity> 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";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<TestStringEntity> stringKey = VKey.createSql(TestStringEntity.class, "TheRealSpartacus");
|
||||||
|
TestLongEntity longEntity = new TestLongEntity(300L);
|
||||||
|
VKey<TestLongEntity> 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<TestStringEntity> stringKey;
|
||||||
|
VKey<TestLongEntity> longKey;
|
||||||
|
|
||||||
|
TestEntity(Long id, VKey<TestStringEntity> stringKey, VKey<TestLongEntity> longKey) {
|
||||||
|
this.id = id;
|
||||||
|
this.stringKey = stringKey;
|
||||||
|
this.longKey = longKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Default constructor, needed for hibernate. */
|
||||||
|
public TestEntity() {}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<AnnotationMirror> 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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<AnnotationMirror> 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1 @@
|
||||||
google.registry.processors.StringVKeyProcessor
|
google.registry.processors.VKeyProcessor
|
||||||
google.registry.processors.LongVKeyProcessor
|
|
Loading…
Add table
Reference in a new issue