mirror of
https://github.com/google/nomulus
synced 2026-09-18 22:14:23 +00:00
Embed a ZonedDateTime as the UpdateAutoTimestamp in SQL (#1033)
* Embed a ZonedDateTime as the UpdateAutoTimestamp in SQL This means we can get rid of the converter and more importantly, means that reading the object from SQL does not affect the last-read time (the test added to UpdateAutoTimestampTest failed prior to the production code change). For now we keep both time fields in UpdateAutoTimestamp however post-migration, we can remove the joda-time field if we wish. Note: I'm not sure why <now> is the time that we started getting LazyInitializationExceptions in the LegacyHistoryObject and ReplayExtension tests but we can solve that by just examining / initializing the object within the transaction.
This commit is contained in:
@@ -16,6 +16,8 @@ package google.registry.model;
|
||||
|
||||
import javax.persistence.Access;
|
||||
import javax.persistence.AccessType;
|
||||
import javax.persistence.AttributeOverride;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import javax.xml.bind.annotation.XmlTransient;
|
||||
|
||||
@@ -40,6 +42,7 @@ public abstract class BackupGroupRoot extends ImmutableObject {
|
||||
// Prevents subclasses from unexpectedly accessing as property (e.g., HostResource), which would
|
||||
// require an unnecessary non-private setter method.
|
||||
@Access(AccessType.FIELD)
|
||||
@AttributeOverride(name = "lastUpdateTime", column = @Column(name = "updateTimestamp"))
|
||||
UpdateAutoTimestamp updateTimestamp = UpdateAutoTimestamp.create(null);
|
||||
|
||||
/** Get the {@link UpdateAutoTimestamp} for this entity. */
|
||||
|
||||
@@ -14,11 +14,22 @@
|
||||
|
||||
package google.registry.model;
|
||||
|
||||
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
import static google.registry.util.DateTimeUtils.START_OF_TIME;
|
||||
|
||||
import com.googlecode.objectify.annotation.Ignore;
|
||||
import com.googlecode.objectify.annotation.OnLoad;
|
||||
import google.registry.model.translators.UpdateAutoTimestampTranslatorFactory;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.PostLoad;
|
||||
import javax.persistence.PrePersist;
|
||||
import javax.persistence.PreUpdate;
|
||||
import javax.persistence.Transient;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
@@ -26,14 +37,44 @@ import org.joda.time.DateTime;
|
||||
*
|
||||
* @see UpdateAutoTimestampTranslatorFactory
|
||||
*/
|
||||
@Embeddable
|
||||
public class UpdateAutoTimestamp extends ImmutableObject {
|
||||
|
||||
// When set to true, database converters/translators should do tha auto update. When set to
|
||||
// When set to true, database converters/translators should do the auto update. When set to
|
||||
// false, auto update should be suspended (this exists to allow us to preserve the original value
|
||||
// during a replay).
|
||||
private static ThreadLocal<Boolean> autoUpdateEnabled = ThreadLocal.withInitial(() -> true);
|
||||
|
||||
DateTime timestamp;
|
||||
@Transient DateTime timestamp;
|
||||
|
||||
@Ignore
|
||||
@Column(nullable = false)
|
||||
ZonedDateTime lastUpdateTime;
|
||||
|
||||
// Unfortunately, we cannot use the @UpdateTimestamp annotation on "lastUpdateTime" in this class
|
||||
// because Hibernate does not allow it to be used on @Embeddable classes, see
|
||||
// https://hibernate.atlassian.net/browse/HHH-13235. This is a workaround.
|
||||
@PrePersist
|
||||
@PreUpdate
|
||||
void setTimestamp() {
|
||||
if (autoUpdateEnabled() || lastUpdateTime == null) {
|
||||
lastUpdateTime = DateTimeUtils.toZonedDateTime(jpaTm().getTransactionTime());
|
||||
}
|
||||
}
|
||||
|
||||
@OnLoad
|
||||
void onLoad() {
|
||||
if (timestamp != null) {
|
||||
lastUpdateTime = DateTimeUtils.toZonedDateTime(timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@PostLoad
|
||||
void postLoad() {
|
||||
if (lastUpdateTime != null) {
|
||||
timestamp = DateTimeUtils.toJodaDateTime(lastUpdateTime);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the timestamp, or {@code START_OF_TIME} if it's null. */
|
||||
public DateTime getTimestamp() {
|
||||
@@ -43,6 +84,7 @@ public class UpdateAutoTimestamp extends ImmutableObject {
|
||||
public static UpdateAutoTimestamp create(@Nullable DateTime timestamp) {
|
||||
UpdateAutoTimestamp instance = new UpdateAutoTimestamp();
|
||||
instance.timestamp = timestamp;
|
||||
instance.lastUpdateTime = timestamp == null ? null : DateTimeUtils.toZonedDateTime(timestamp);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,10 +51,8 @@ public final class RegistryLockDao {
|
||||
return ImmutableList.copyOf(
|
||||
jpaTm()
|
||||
.query(
|
||||
"SELECT lock FROM RegistryLock lock"
|
||||
+ " WHERE lock.registrarId = :registrarId"
|
||||
+ " AND lock.unlockCompletionTimestamp IS NULL"
|
||||
+ " ORDER BY lock.domainName ASC",
|
||||
"SELECT lock FROM RegistryLock lock WHERE lock.registrarId = :registrarId"
|
||||
+ " AND lock.unlockCompletionTime IS NULL ORDER BY lock.domainName ASC",
|
||||
RegistryLock.class)
|
||||
.setParameter("registrarId", registrarId)
|
||||
.getResultList());
|
||||
@@ -89,9 +87,8 @@ public final class RegistryLockDao {
|
||||
return jpaTm()
|
||||
.query(
|
||||
"SELECT lock FROM RegistryLock lock WHERE lock.repoId = :repoId AND"
|
||||
+ " lock.lockCompletionTimestamp IS NOT NULL AND"
|
||||
+ " lock.unlockCompletionTimestamp IS NULL ORDER BY lock.revisionId"
|
||||
+ " DESC",
|
||||
+ " lock.lockCompletionTime IS NOT NULL AND lock.unlockCompletionTime IS NULL"
|
||||
+ " ORDER BY lock.revisionId DESC",
|
||||
RegistryLock.class)
|
||||
.setParameter("repoId", repoId)
|
||||
.setMaxResults(1)
|
||||
@@ -110,8 +107,7 @@ public final class RegistryLockDao {
|
||||
return jpaTm()
|
||||
.query(
|
||||
"SELECT lock FROM RegistryLock lock WHERE lock.repoId = :repoId AND"
|
||||
+ " lock.unlockCompletionTimestamp IS NOT NULL ORDER BY lock.revisionId"
|
||||
+ " DESC",
|
||||
+ " lock.unlockCompletionTime IS NOT NULL ORDER BY lock.revisionId DESC",
|
||||
RegistryLock.class)
|
||||
.setParameter("repoId", repoId)
|
||||
.setMaxResults(1)
|
||||
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
// Copyright 2019 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 google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
|
||||
|
||||
import google.registry.model.UpdateAutoTimestamp;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.sql.Timestamp;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Converter;
|
||||
|
||||
/** JPA converter for storing/retrieving UpdateAutoTimestamp objects. */
|
||||
@Converter(autoApply = true)
|
||||
public class UpdateAutoTimestampConverter
|
||||
implements AttributeConverter<UpdateAutoTimestamp, Timestamp> {
|
||||
|
||||
@Override
|
||||
public Timestamp convertToDatabaseColumn(UpdateAutoTimestamp entity) {
|
||||
return Timestamp.from(
|
||||
DateTimeUtils.toZonedDateTime(
|
||||
UpdateAutoTimestamp.autoUpdateEnabled()
|
||||
|| entity == null
|
||||
|| entity.getTimestamp() == null
|
||||
? jpaTm().getTransactionTime()
|
||||
: entity.getTimestamp())
|
||||
.toInstant());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public UpdateAutoTimestamp convertToEntityAttribute(@Nullable Timestamp columnValue) {
|
||||
if (columnValue == null) {
|
||||
return null;
|
||||
}
|
||||
ZonedDateTime zdt = ZonedDateTime.ofInstant(columnValue.toInstant(), ZoneOffset.UTC);
|
||||
return UpdateAutoTimestamp.create(DateTimeUtils.toJodaDateTime(zdt));
|
||||
}
|
||||
}
|
||||
@@ -102,22 +102,22 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
|
||||
/** When the lock is first requested. */
|
||||
@Column(nullable = false)
|
||||
private CreateAutoTimestamp lockRequestTimestamp = CreateAutoTimestamp.create(null);
|
||||
private CreateAutoTimestamp lockRequestTime = CreateAutoTimestamp.create(null);
|
||||
|
||||
/** When the unlock is first requested. */
|
||||
private ZonedDateTime unlockRequestTimestamp;
|
||||
private ZonedDateTime unlockRequestTime;
|
||||
|
||||
/**
|
||||
* When the user has verified the lock. If this field is null, it means the lock has not been
|
||||
* verified yet (and thus not been put into effect).
|
||||
*/
|
||||
private ZonedDateTime lockCompletionTimestamp;
|
||||
private ZonedDateTime lockCompletionTime;
|
||||
|
||||
/**
|
||||
* When the user has verified the unlock of this lock. If this field is null, it means the unlock
|
||||
* action has not been verified yet (and has not been put into effect).
|
||||
*/
|
||||
private ZonedDateTime unlockCompletionTimestamp;
|
||||
private ZonedDateTime unlockCompletionTime;
|
||||
|
||||
/** The user must provide the random verification code in order to complete the action. */
|
||||
@Column(nullable = false)
|
||||
@@ -140,7 +140,7 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
private Duration relockDuration;
|
||||
|
||||
/** Time that this entity was last updated. */
|
||||
private UpdateAutoTimestamp lastUpdateTimestamp;
|
||||
private UpdateAutoTimestamp lastUpdateTime = UpdateAutoTimestamp.create(null);
|
||||
|
||||
public String getRepoId() {
|
||||
return repoId;
|
||||
@@ -158,25 +158,25 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
return registrarPocId;
|
||||
}
|
||||
|
||||
public DateTime getLockRequestTimestamp() {
|
||||
return lockRequestTimestamp.getTimestamp();
|
||||
public DateTime getLockRequestTime() {
|
||||
return lockRequestTime.getTimestamp();
|
||||
}
|
||||
|
||||
/** Returns the unlock request timestamp or null if an unlock has not been requested yet. */
|
||||
public Optional<DateTime> getUnlockRequestTimestamp() {
|
||||
return Optional.ofNullable(unlockRequestTimestamp).map(DateTimeUtils::toJodaDateTime);
|
||||
public Optional<DateTime> getUnlockRequestTime() {
|
||||
return Optional.ofNullable(unlockRequestTime).map(DateTimeUtils::toJodaDateTime);
|
||||
}
|
||||
|
||||
/** Returns the completion timestamp, or empty if this lock has not been completed yet. */
|
||||
public Optional<DateTime> getLockCompletionTimestamp() {
|
||||
return Optional.ofNullable(lockCompletionTimestamp).map(DateTimeUtils::toJodaDateTime);
|
||||
public Optional<DateTime> getLockCompletionTime() {
|
||||
return Optional.ofNullable(lockCompletionTime).map(DateTimeUtils::toJodaDateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unlock completion timestamp, or empty if this unlock has not been completed yet.
|
||||
*/
|
||||
public Optional<DateTime> getUnlockCompletionTimestamp() {
|
||||
return Optional.ofNullable(unlockCompletionTimestamp).map(DateTimeUtils::toJodaDateTime);
|
||||
public Optional<DateTime> getUnlockCompletionTime() {
|
||||
return Optional.ofNullable(unlockCompletionTime).map(DateTimeUtils::toJodaDateTime);
|
||||
}
|
||||
|
||||
public String getVerificationCode() {
|
||||
@@ -187,8 +187,8 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
return isSuperuser;
|
||||
}
|
||||
|
||||
public DateTime getLastUpdateTimestamp() {
|
||||
return lastUpdateTimestamp.getTimestamp();
|
||||
public DateTime getLastUpdateTime() {
|
||||
return lastUpdateTime.getTimestamp();
|
||||
}
|
||||
|
||||
public Long getRevisionId() {
|
||||
@@ -211,20 +211,20 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return lockCompletionTimestamp != null && unlockCompletionTimestamp == null;
|
||||
return lockCompletionTime != null && unlockCompletionTime == null;
|
||||
}
|
||||
|
||||
/** Returns true iff the lock was requested >= 1 hour ago and has not been verified. */
|
||||
public boolean isLockRequestExpired(DateTime now) {
|
||||
return !getLockCompletionTimestamp().isPresent()
|
||||
&& isBeforeOrAt(getLockRequestTimestamp(), now.minusHours(1));
|
||||
return !getLockCompletionTime().isPresent()
|
||||
&& isBeforeOrAt(getLockRequestTime(), now.minusHours(1));
|
||||
}
|
||||
|
||||
/** Returns true iff the unlock was requested >= 1 hour ago and has not been verified. */
|
||||
public boolean isUnlockRequestExpired(DateTime now) {
|
||||
Optional<DateTime> unlockRequestTimestamp = getUnlockRequestTimestamp();
|
||||
Optional<DateTime> unlockRequestTimestamp = getUnlockRequestTime();
|
||||
return unlockRequestTimestamp.isPresent()
|
||||
&& !getUnlockCompletionTimestamp().isPresent()
|
||||
&& !getUnlockCompletionTime().isPresent()
|
||||
&& isBeforeOrAt(unlockRequestTimestamp.get(), now.minusHours(1));
|
||||
}
|
||||
|
||||
@@ -278,18 +278,18 @@ public final class RegistryLock extends ImmutableObject implements Buildable, Sq
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnlockRequestTimestamp(DateTime unlockRequestTimestamp) {
|
||||
getInstance().unlockRequestTimestamp = toZonedDateTime(unlockRequestTimestamp);
|
||||
public Builder setUnlockRequestTime(DateTime unlockRequestTime) {
|
||||
getInstance().unlockRequestTime = toZonedDateTime(unlockRequestTime);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLockCompletionTimestamp(DateTime lockCompletionTimestamp) {
|
||||
getInstance().lockCompletionTimestamp = toZonedDateTime(lockCompletionTimestamp);
|
||||
public Builder setLockCompletionTime(DateTime lockCompletionTime) {
|
||||
getInstance().lockCompletionTime = toZonedDateTime(lockCompletionTime);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnlockCompletionTimestamp(DateTime unlockCompletionTimestamp) {
|
||||
getInstance().unlockCompletionTimestamp = toZonedDateTime(unlockCompletionTimestamp);
|
||||
public Builder setUnlockCompletionTime(DateTime unlockCompletionTime) {
|
||||
getInstance().unlockCompletionTime = toZonedDateTime(unlockCompletionTime);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ public final class DomainLockUtils {
|
||||
RegistryLock lock = getByVerificationCode(verificationCode);
|
||||
|
||||
checkArgument(
|
||||
!lock.getLockCompletionTimestamp().isPresent(),
|
||||
!lock.getLockCompletionTime().isPresent(),
|
||||
"Domain %s is already locked",
|
||||
lock.getDomainName());
|
||||
|
||||
@@ -115,7 +115,7 @@ public final class DomainLockUtils {
|
||||
!lock.isSuperuser() || isAdmin, "Non-admin user cannot complete admin lock");
|
||||
|
||||
RegistryLock newLock =
|
||||
RegistryLockDao.save(lock.asBuilder().setLockCompletionTimestamp(now).build());
|
||||
RegistryLockDao.save(lock.asBuilder().setLockCompletionTime(now).build());
|
||||
setAsRelock(newLock);
|
||||
tm().transact(() -> applyLockStatuses(newLock, now, isAdmin));
|
||||
return newLock;
|
||||
@@ -131,7 +131,7 @@ public final class DomainLockUtils {
|
||||
DateTime now = jpaTm().getTransactionTime();
|
||||
RegistryLock previousLock = getByVerificationCode(verificationCode);
|
||||
checkArgument(
|
||||
!previousLock.getUnlockCompletionTimestamp().isPresent(),
|
||||
!previousLock.getUnlockCompletionTime().isPresent(),
|
||||
"Domain %s is already unlocked",
|
||||
previousLock.getDomainName());
|
||||
|
||||
@@ -145,7 +145,7 @@ public final class DomainLockUtils {
|
||||
|
||||
RegistryLock newLock =
|
||||
RegistryLockDao.save(
|
||||
previousLock.asBuilder().setUnlockCompletionTimestamp(now).build());
|
||||
previousLock.asBuilder().setUnlockCompletionTime(now).build());
|
||||
tm().transact(() -> removeLockStatuses(newLock, isAdmin, now));
|
||||
return newLock;
|
||||
});
|
||||
@@ -169,7 +169,7 @@ public final class DomainLockUtils {
|
||||
RegistryLock newLock =
|
||||
RegistryLockDao.save(
|
||||
createLockBuilder(domainName, registrarId, registrarPocId, isAdmin)
|
||||
.setLockCompletionTimestamp(now)
|
||||
.setLockCompletionTime(now)
|
||||
.build());
|
||||
tm().transact(() -> applyLockStatuses(newLock, now, isAdmin));
|
||||
setAsRelock(newLock);
|
||||
@@ -192,7 +192,7 @@ public final class DomainLockUtils {
|
||||
RegistryLock result =
|
||||
RegistryLockDao.save(
|
||||
createUnlockBuilder(domainName, registrarId, isAdmin, relockDuration)
|
||||
.setUnlockCompletionTimestamp(now)
|
||||
.setUnlockCompletionTime(now)
|
||||
.build());
|
||||
tm().transact(() -> removeLockStatuses(result, isAdmin, now));
|
||||
return result;
|
||||
@@ -230,7 +230,7 @@ public final class DomainLockUtils {
|
||||
previousLock ->
|
||||
checkArgument(
|
||||
previousLock.isLockRequestExpired(now)
|
||||
|| previousLock.getUnlockCompletionTimestamp().isPresent()
|
||||
|| previousLock.getUnlockCompletionTime().isPresent()
|
||||
|| isAdmin,
|
||||
"A pending or completed lock action already exists for %s",
|
||||
previousLock.getDomainName()));
|
||||
@@ -264,7 +264,7 @@ public final class DomainLockUtils {
|
||||
new RegistryLock.Builder()
|
||||
.setRepoId(domainBase.getRepoId())
|
||||
.setDomainName(domainName)
|
||||
.setLockCompletionTimestamp(now)
|
||||
.setLockCompletionTime(now)
|
||||
.setRegistrarId(registrarId));
|
||||
} else {
|
||||
RegistryLock lock =
|
||||
@@ -275,7 +275,7 @@ public final class DomainLockUtils {
|
||||
checkArgument(
|
||||
lock.isLocked(), "Lock object for domain %s is not currently locked", domainName);
|
||||
checkArgument(
|
||||
!lock.getUnlockRequestTimestamp().isPresent() || lock.isUnlockRequestExpired(now),
|
||||
!lock.getUnlockRequestTime().isPresent() || lock.isUnlockRequestExpired(now),
|
||||
"A pending unlock action already exists for %s",
|
||||
domainName);
|
||||
checkArgument(
|
||||
@@ -290,7 +290,7 @@ public final class DomainLockUtils {
|
||||
return newLockBuilder
|
||||
.setVerificationCode(stringGenerator.createString(VERIFICATION_CODE_LENGTH))
|
||||
.isSuperuser(isAdmin)
|
||||
.setUnlockRequestTimestamp(now)
|
||||
.setUnlockRequestTime(now)
|
||||
.setRegistrarId(registrarId);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ public class BackfillRegistryLocksCommand extends ConfirmingCommand
|
||||
.setRegistrarId(registryAdminClientId)
|
||||
.setRepoId(domainBase.getRepoId())
|
||||
.setDomainName(domainBase.getDomainName())
|
||||
.setLockCompletionTimestamp(
|
||||
.setLockCompletionTime(
|
||||
getLockCompletionTimestamp(domainBase, jpaTm().getTransactionTime()))
|
||||
.setVerificationCode(
|
||||
stringGenerator.createString(VERIFICATION_CODE_LENGTH))
|
||||
|
||||
@@ -191,14 +191,13 @@ public final class RegistryLockGetAction implements JsonGetAction {
|
||||
DateTime now = jpaTm().getTransactionTime();
|
||||
return new ImmutableMap.Builder<String, Object>()
|
||||
.put(DOMAIN_NAME_PARAM, lock.getDomainName())
|
||||
.put(
|
||||
LOCKED_TIME_PARAM, lock.getLockCompletionTimestamp().map(DateTime::toString).orElse(""))
|
||||
.put(LOCKED_TIME_PARAM, lock.getLockCompletionTime().map(DateTime::toString).orElse(""))
|
||||
.put(LOCKED_BY_PARAM, lock.isSuperuser() ? "admin" : lock.getRegistrarPocId())
|
||||
.put(IS_LOCK_PENDING_PARAM, !lock.getLockCompletionTimestamp().isPresent())
|
||||
.put(IS_LOCK_PENDING_PARAM, !lock.getLockCompletionTime().isPresent())
|
||||
.put(
|
||||
IS_UNLOCK_PENDING_PARAM,
|
||||
lock.getUnlockRequestTimestamp().isPresent()
|
||||
&& !lock.getUnlockCompletionTimestamp().isPresent()
|
||||
lock.getUnlockRequestTime().isPresent()
|
||||
&& !lock.getUnlockCompletionTime().isPresent()
|
||||
&& !lock.isUnlockRequestExpired(now))
|
||||
.put(USER_CAN_UNLOCK_PARAM, isAdmin || !lock.isSuperuser())
|
||||
.build();
|
||||
|
||||
@@ -98,7 +98,6 @@
|
||||
<class>google.registry.persistence.converter.StringSetConverter</class>
|
||||
<class>google.registry.persistence.converter.TldStateTransitionConverter</class>
|
||||
<class>google.registry.persistence.converter.TransferServerApproveEntitySetConverter</class>
|
||||
<class>google.registry.persistence.converter.UpdateAutoTimestampConverter</class>
|
||||
<class>google.registry.persistence.converter.ZonedDateTimeConverter</class>
|
||||
|
||||
<!-- Generated converters for VKey -->
|
||||
|
||||
Reference in New Issue
Block a user