Update RegistrarSettingsAction and RegistrarContact to SQL calls (#1042)

* Update RegistrarSettingsAction and RegistrarContact to SQL calls

Relevant potentially-unclear changes:
- Making sure the last update time is always correct and up to date in
the auto timestamp object
- Reloading the domain upon return when updating in a new transaction to
make sure that we use the properly-updated last update time (SQL returns
the correct result if retrieved within the same txn but DS does not)
This commit is contained in:
gbrodman
2021-03-30 16:41:26 -04:00
committed by GitHub
parent d30ab08f6d
commit c9980fcdec
8 changed files with 125 additions and 81 deletions
@@ -58,7 +58,8 @@ public class UpdateAutoTimestamp extends ImmutableObject {
@PreUpdate
void setTimestamp() {
if (autoUpdateEnabled() || lastUpdateTime == null) {
lastUpdateTime = DateTimeUtils.toZonedDateTime(jpaTm().getTransactionTime());
timestamp = jpaTm().getTransactionTime();
lastUpdateTime = DateTimeUtils.toZonedDateTime(timestamp);
}
}
@@ -16,8 +16,7 @@ package google.registry.model.common;
import static com.google.common.base.Preconditions.checkState;
import static google.registry.model.ofy.ObjectifyService.allocateId;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.persistence.transaction.TransactionManagerFactory.ofyTm;
import com.google.appengine.api.users.User;
import com.google.common.base.Splitter;
@@ -56,13 +55,22 @@ public class GaeUserIdConverter extends ImmutableObject {
try {
// Perform these operations in a transactionless context to avoid enlisting in some outer
// transaction (if any).
tm().doTransactionless(() -> ofy().saveWithoutBackup().entity(gaeUserIdConverter).now());
ofyTm()
.doTransactionless(
() -> {
ofyTm().putWithoutBackup(gaeUserIdConverter);
return null;
});
// The read must be done in its own transaction to avoid reading from the session cache.
return tm()
.transactNew(() -> ofy().load().entity(gaeUserIdConverter).safe().user.getUserId());
return ofyTm().transactNew(() -> ofyTm().loadByEntity(gaeUserIdConverter).user.getUserId());
} finally {
tm().doTransactionless(() -> ofy().deleteWithoutBackup().entity(gaeUserIdConverter).now());
ofyTm()
.doTransactionless(
() -> {
ofyTm().deleteWithoutBackup(gaeUserIdConverter);
return null;
});
}
}
}
@@ -23,6 +23,7 @@ import static com.google.common.io.BaseEncoding.base64;
import static google.registry.model.common.EntityGroupRoot.getCrossTldKey;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.model.registrar.Registrar.checkValidEmail;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
@@ -200,17 +201,34 @@ public class RegistrarContact extends ImmutableObject
* relevant Registrar entity with the {@link Registrar#contactsRequireSyncing} field set to true.
*/
public static void updateContacts(
final Registrar registrar, final Set<RegistrarContact> contacts) {
final Registrar registrar, final ImmutableSet<RegistrarContact> contacts) {
tm().transact(
() -> {
ofy()
.delete()
.keys(
difference(
ImmutableSet.copyOf(
ofy().load().type(RegistrarContact.class).ancestor(registrar).keys()),
contacts.stream().map(Key::create).collect(toImmutableSet())));
ofy().save().entities(contacts);
if (tm().isOfy()) {
ImmutableSet<Key<RegistrarContact>> existingKeys =
ImmutableSet.copyOf(
ofy().load().type(RegistrarContact.class).ancestor(registrar).keys());
tm().delete(
difference(
existingKeys,
contacts.stream().map(Key::create).collect(toImmutableSet()))
.stream()
.map(key -> VKey.createOfy(RegistrarContact.class, key))
.collect(toImmutableSet()));
} else {
ImmutableSet<String> emailAddressesToKeep =
contacts.stream()
.map(RegistrarContact::getEmailAddress)
.collect(toImmutableSet());
jpaTm()
.query(
"DELETE FROM RegistrarPoc WHERE registrarId = :registrarId AND "
+ "emailAddress NOT IN :emailAddressesToKeep")
.setParameter("registrarId", registrar.getClientId())
.setParameter("emailAddressesToKeep", emailAddressesToKeep)
.executeUpdate();
}
tm().putAll(contacts);
});
}
@@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static google.registry.model.common.GaeUserIdConverter.convertEmailAddressToGaeUserId;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.ui.server.SoyTemplateUtils.CSS_RENAMING_MAP_SUPPLIER;
import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
@@ -234,14 +233,13 @@ public final class ConsoleRegistrarCreatorAction extends HtmlAction {
.setEmailAddress(consoleUserEmail.get())
.setGaeUserId(gaeUserId)
.build();
tm()
.transact(
tm().transact(
() -> {
checkState(
!Registrar.loadByClientId(registrar.getClientId()).isPresent(),
"Registrar with client ID %s already exists",
registrar.getClientId());
ofy().save().entities(registrar, contact);
tm().putAll(registrar, contact);
});
data.put("password", password);
data.put("passcode", phonePasscode);
@@ -20,7 +20,7 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet;
import static com.google.common.collect.Sets.difference;
import static google.registry.config.RegistryEnvironment.PRODUCTION;
import static google.registry.export.sheet.SyncRegistrarsSheetAction.enqueueRegistrarSheetSync;
import static google.registry.model.ofy.ObjectifyService.ofy;
import static google.registry.persistence.transaction.TransactionManagerFactory.jpaTm;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.security.JsonResponseHelper.Status.ERROR;
import static google.registry.security.JsonResponseHelper.Status.SUCCESS;
@@ -170,24 +170,28 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
}
private RegistrarResult read(String clientId) {
return RegistrarResult.create("Success", loadRegistrarUnchecked(clientId));
}
private Registrar loadRegistrarUnchecked(String registrarId) {
try {
return RegistrarResult.create("Success", registrarAccessor.getRegistrar(clientId));
return registrarAccessor.getRegistrar(registrarId);
} catch (RegistrarAccessDeniedException e) {
throw new ForbiddenException(e.getMessage(), e);
}
}
private RegistrarResult update(final Map<String, ?> args, String clientId) {
return tm().transact(
tm().transact(
() -> {
// We load the registrar here rather than outside of the transaction - to make
// sure we have the latest version. This one is loaded inside the transaction, so it's
// guaranteed to not change before we update it.
Registrar registrar;
try {
registrar = registrarAccessor.getRegistrar(clientId);
} catch (RegistrarAccessDeniedException e) {
throw new ForbiddenException(e.getMessage(), e);
Registrar registrar = loadRegistrarUnchecked(clientId);
// Detach the registrar to avoid Hibernate object-updates, since we wish to email
// out the diffs between the existing and updated registrar objects
if (!tm().isOfy()) {
jpaTm().getEntityManager().detach(registrar);
}
// Verify that the registrar hasn't been changed.
// To do that - we find the latest update time (or null if the registrar has been
@@ -233,14 +237,15 @@ public class RegistrarSettingsAction implements Runnable, JsonActionRunner.JsonA
// Save the updated registrar
if (!updatedRegistrar.equals(registrar)) {
ofy().save().entity(updatedRegistrar);
tm().put(updatedRegistrar);
}
// Email and return update.
// Email the updates
sendExternalUpdatesIfNecessary(
registrar, contacts, updatedRegistrar, updatedContacts);
return RegistrarResult.create("Saved " + clientId, updatedRegistrar);
});
// Reload the result outside of the transaction to get the most recent version
return RegistrarResult.create("Saved " + clientId, loadRegistrarUnchecked(clientId));
}
private Map<String, Object> expandRegistrarWithContacts(