mirror of
https://github.com/google/nomulus.git
synced 2025-04-30 03:57:51 +02:00
Add a DAO for RegistryLock objects (#290)
* Add a DAO for RegistryLock objects * Add an index on verification code and remove old file * Move to v4 * Use camelCase in index names * Javadoc fixes * Allow alteration of RegistryLock objects in-place * save, load-modify, read in separate transactions * Change the creation timestamp to be a CreateAutoTimestamp
This commit is contained in:
parent
b061d763df
commit
0855040bd7
5 changed files with 193 additions and 32 deletions
|
@ -0,0 +1,52 @@
|
||||||
|
// 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.model.registry;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
|
||||||
|
import google.registry.schema.domain.RegistryLock;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
/** Data access object for {@link google.registry.schema.domain.RegistryLock}. */
|
||||||
|
public final class RegistryLockDao {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the most recent version of the {@link RegistryLock} referred to by the verification
|
||||||
|
* code (there may be two instances of the same code in the database--one after lock object
|
||||||
|
* creation and one after verification.
|
||||||
|
*/
|
||||||
|
public static RegistryLock getByVerificationCode(String verificationCode) {
|
||||||
|
return jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
EntityManager em = jpaTm().getEntityManager();
|
||||||
|
Long revisionId =
|
||||||
|
em.createQuery(
|
||||||
|
"SELECT MAX(revisionId) FROM RegistryLock WHERE verificationCode ="
|
||||||
|
+ " :verificationCode",
|
||||||
|
Long.class)
|
||||||
|
.setParameter("verificationCode", verificationCode)
|
||||||
|
.getSingleResult();
|
||||||
|
checkNotNull(revisionId, "No registry lock with this code");
|
||||||
|
return em.find(RegistryLock.class, revisionId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save(RegistryLock registryLock) {
|
||||||
|
checkNotNull(registryLock, "Null registry lock cannot be saved");
|
||||||
|
jpaTm().transact(() -> jpaTm().getEntityManager().persist(registryLock));
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,16 +15,18 @@
|
||||||
package google.registry.schema.domain;
|
package google.registry.schema.domain;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static google.registry.util.DateTimeUtils.toJodaDateTime;
|
|
||||||
import static google.registry.util.DateTimeUtils.toZonedDateTime;
|
import static google.registry.util.DateTimeUtils.toZonedDateTime;
|
||||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||||
|
|
||||||
import google.registry.model.Buildable;
|
import google.registry.model.Buildable;
|
||||||
|
import google.registry.model.CreateAutoTimestamp;
|
||||||
import google.registry.model.ImmutableObject;
|
import google.registry.model.ImmutableObject;
|
||||||
|
import google.registry.persistence.CreateAutoTimestampConverter;
|
||||||
import google.registry.util.DateTimeUtils;
|
import google.registry.util.DateTimeUtils;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.persistence.Column;
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Convert;
|
||||||
import javax.persistence.Entity;
|
import javax.persistence.Entity;
|
||||||
import javax.persistence.EnumType;
|
import javax.persistence.EnumType;
|
||||||
import javax.persistence.Enumerated;
|
import javax.persistence.Enumerated;
|
||||||
|
@ -56,14 +58,17 @@ import org.joda.time.DateTime;
|
||||||
* Unique constraint to get around Hibernate's failure to handle auto-increment field in
|
* Unique constraint to get around Hibernate's failure to handle auto-increment field in
|
||||||
* composite primary key.
|
* composite primary key.
|
||||||
*
|
*
|
||||||
* <p>Note: because of this index, physical columns must be declared in the {@link Column}
|
* <p>Note: indexes use the camelCase version of the field names because the {@link
|
||||||
* annotations for {@link RegistryLock#revisionId} and {@link RegistryLock#repoId} fields.
|
* google.registry.persistence.NomulusNamingStrategy} does not translate the field name into the
|
||||||
|
* snake_case column name until the write itself.
|
||||||
*/
|
*/
|
||||||
indexes =
|
indexes = {
|
||||||
@Index(
|
@Index(
|
||||||
name = "idx_registry_lock_repo_id_revision_id",
|
name = "idx_registry_lock_repo_id_revision_id",
|
||||||
columnList = "repo_id, revision_id",
|
columnList = "repoId, revisionId",
|
||||||
unique = true))
|
unique = true),
|
||||||
|
@Index(name = "idx_registry_lock_verification_code", columnList = "verificationCode")
|
||||||
|
})
|
||||||
public final class RegistryLock extends ImmutableObject implements Buildable {
|
public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
|
|
||||||
/** Describes the action taken by the user. */
|
/** Describes the action taken by the user. */
|
||||||
|
@ -74,11 +79,11 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
@Column(name = "revision_id", nullable = false)
|
@Column(nullable = false)
|
||||||
private Long revisionId;
|
private Long revisionId;
|
||||||
|
|
||||||
/** EPP repo ID of the domain in question. */
|
/** EPP repo ID of the domain in question. */
|
||||||
@Column(name = "repo_id", nullable = false)
|
@Column(nullable = false)
|
||||||
private String repoId;
|
private String repoId;
|
||||||
|
|
||||||
// TODO (b/140568328): remove this when everything is in Cloud SQL and we can join on "domain"
|
// TODO (b/140568328): remove this when everything is in Cloud SQL and we can join on "domain"
|
||||||
|
@ -104,7 +109,8 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
|
|
||||||
/** Creation timestamp is when the lock/unlock is first requested. */
|
/** Creation timestamp is when the lock/unlock is first requested. */
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private ZonedDateTime creationTimestamp;
|
@Convert(converter = CreateAutoTimestampConverter.class)
|
||||||
|
private CreateAutoTimestamp creationTimestamp = CreateAutoTimestamp.create(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Completion timestamp is when the user has verified the lock/unlock, when this object de facto
|
* Completion timestamp is when the user has verified the lock/unlock, when this object de facto
|
||||||
|
@ -148,7 +154,7 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DateTime getCreationTimestamp() {
|
public DateTime getCreationTimestamp() {
|
||||||
return toJodaDateTime(creationTimestamp);
|
return creationTimestamp.getTimestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the completion timestamp, or empty if this lock has not been completed yet. */
|
/** Returns the completion timestamp, or empty if this lock has not been completed yet. */
|
||||||
|
@ -168,9 +174,16 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
return revisionId;
|
return revisionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCompletionTimestamp(DateTime dateTime) {
|
||||||
|
this.completionTimestamp = toZonedDateTime(dateTime);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Builder asBuilder() {
|
public Builder asBuilder() {
|
||||||
return new Builder(clone(this));
|
RegistryLock clone = clone(this);
|
||||||
|
// Revision ID should be different for every object
|
||||||
|
clone.revisionId = null;
|
||||||
|
return new Builder(clone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Builder for {@link google.registry.schema.domain.RegistryLock}. */
|
/** Builder for {@link google.registry.schema.domain.RegistryLock}. */
|
||||||
|
@ -187,7 +200,6 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
checkArgumentNotNull(getInstance().domainName, "Domain name cannot be null");
|
checkArgumentNotNull(getInstance().domainName, "Domain name cannot be null");
|
||||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
checkArgumentNotNull(getInstance().registrarId, "Registrar ID cannot be null");
|
||||||
checkArgumentNotNull(getInstance().action, "Action cannot be null");
|
checkArgumentNotNull(getInstance().action, "Action cannot be null");
|
||||||
checkArgumentNotNull(getInstance().creationTimestamp, "Creation timestamp cannot be null");
|
|
||||||
checkArgumentNotNull(getInstance().verificationCode, "Verification codecannot be null");
|
checkArgumentNotNull(getInstance().verificationCode, "Verification codecannot be null");
|
||||||
checkArgument(
|
checkArgument(
|
||||||
getInstance().registrarPocId != null || getInstance().isSuperuser,
|
getInstance().registrarPocId != null || getInstance().isSuperuser,
|
||||||
|
@ -220,8 +232,8 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setCreationTimestamp(DateTime creationTimestamp) {
|
public Builder setCreationTimestamp(CreateAutoTimestamp creationTimestamp) {
|
||||||
getInstance().creationTimestamp = toZonedDateTime(creationTimestamp);
|
getInstance().creationTimestamp = creationTimestamp;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
// 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.model.registry;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static google.registry.model.transaction.TransactionManagerFactory.jpaTm;
|
||||||
|
import static google.registry.testing.JUnitBackports.assertThrows;
|
||||||
|
|
||||||
|
import google.registry.model.transaction.JpaTransactionManagerRule;
|
||||||
|
import google.registry.schema.domain.RegistryLock;
|
||||||
|
import google.registry.schema.domain.RegistryLock.Action;
|
||||||
|
import google.registry.testing.AppEngineRule;
|
||||||
|
import java.util.UUID;
|
||||||
|
import javax.persistence.PersistenceException;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Unit tests for {@link RegistryLockDao}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public final class RegistryLockDaoTest {
|
||||||
|
|
||||||
|
@Rule public final AppEngineRule appEngine = AppEngineRule.builder().withDatastore().build();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final JpaTransactionManagerRule jpaTmRule =
|
||||||
|
new JpaTransactionManagerRule.Builder().withEntityClass(RegistryLock.class).build();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveAndLoad_success() {
|
||||||
|
RegistryLock lock = createLock();
|
||||||
|
RegistryLockDao.save(lock);
|
||||||
|
RegistryLock fromDatabase = RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
|
||||||
|
assertThat(fromDatabase.getDomainName()).isEqualTo(lock.getDomainName());
|
||||||
|
assertThat(fromDatabase.getVerificationCode()).isEqualTo(lock.getVerificationCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveAndLoad_failure_differentCode() {
|
||||||
|
RegistryLock lock = createLock();
|
||||||
|
RegistryLockDao.save(lock);
|
||||||
|
PersistenceException exception =
|
||||||
|
assertThrows(
|
||||||
|
PersistenceException.class,
|
||||||
|
() -> RegistryLockDao.getByVerificationCode(UUID.randomUUID().toString()));
|
||||||
|
assertThat(exception)
|
||||||
|
.hasCauseThat()
|
||||||
|
.hasMessageThat()
|
||||||
|
.isEqualTo("No registry lock with this code");
|
||||||
|
assertThat(exception).hasCauseThat().isInstanceOf(NullPointerException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSaveTwiceAndLoad_returnsLatest() {
|
||||||
|
RegistryLock lock = createLock();
|
||||||
|
jpaTm().transact(() -> RegistryLockDao.save(lock));
|
||||||
|
jpaTmRule.getTxnClock().advanceOneMilli();
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
RegistryLock secondLock =
|
||||||
|
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
|
||||||
|
secondLock.setCompletionTimestamp(jpaTmRule.getTxnClock().nowUtc());
|
||||||
|
RegistryLockDao.save(secondLock);
|
||||||
|
});
|
||||||
|
jpaTm()
|
||||||
|
.transact(
|
||||||
|
() -> {
|
||||||
|
RegistryLock fromDatabase =
|
||||||
|
RegistryLockDao.getByVerificationCode(lock.getVerificationCode());
|
||||||
|
assertThat(fromDatabase.getCompletionTimestamp().get())
|
||||||
|
.isEqualTo(jpaTmRule.getTxnClock().nowUtc());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailure_saveNull() {
|
||||||
|
assertThrows(NullPointerException.class, () -> RegistryLockDao.save(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private RegistryLock createLock() {
|
||||||
|
return new RegistryLock.Builder()
|
||||||
|
.setRepoId("repoId")
|
||||||
|
.setDomainName("example.test")
|
||||||
|
.setRegistrarId("TheRegistrar")
|
||||||
|
.setAction(Action.LOCK)
|
||||||
|
.setVerificationCode(UUID.randomUUID().toString())
|
||||||
|
.isSuperuser(true)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,19 +12,5 @@
|
||||||
-- See the License for the specific language governing permissions and
|
-- See the License for the specific language governing permissions and
|
||||||
-- limitations under the License.
|
-- limitations under the License.
|
||||||
|
|
||||||
CREATE TABLE "RegistryLock" (
|
create index if not exists idx_registry_lock_verification_code ON "RegistryLock"
|
||||||
revision_id BIGSERIAL NOT NULL,
|
using btree (verification_code);
|
||||||
action TEXT NOT NULL,
|
|
||||||
completion_timestamp TIMESTAMPTZ,
|
|
||||||
creation_timestamp TIMESTAMPTZ NOT NULL,
|
|
||||||
domain_name TEXT NOT NULL,
|
|
||||||
is_superuser BOOLEAN NOT NULL,
|
|
||||||
registrar_id TEXT NOT NULL,
|
|
||||||
registrar_poc_id TEXT,
|
|
||||||
repo_id TEXT NOT NULL,
|
|
||||||
verification_code TEXT NOT NULL,
|
|
||||||
PRIMARY KEY (revision_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE IF EXISTS "RegistryLock"
|
|
||||||
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
|
|
|
@ -220,6 +220,13 @@ ALTER TABLE ONLY public."RegistryLock"
|
||||||
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
|
ADD CONSTRAINT idx_registry_lock_repo_id_revision_id UNIQUE (repo_id, revision_id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: idx_registry_lock_verification_code; Type: INDEX; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE INDEX idx_registry_lock_verification_code ON public."RegistryLock" USING btree (verification_code);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
|
-- Name: ClaimsEntry fk6sc6at5hedffc0nhdcab6ivuq; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
|
Loading…
Add table
Reference in a new issue