mirror of
https://github.com/google/nomulus.git
synced 2025-04-29 19:47: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;
|
||||
|
||||
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.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import google.registry.model.Buildable;
|
||||
import google.registry.model.CreateAutoTimestamp;
|
||||
import google.registry.model.ImmutableObject;
|
||||
import google.registry.persistence.CreateAutoTimestampConverter;
|
||||
import google.registry.util.DateTimeUtils;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Convert;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
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
|
||||
* composite primary key.
|
||||
*
|
||||
* <p>Note: because of this index, physical columns must be declared in the {@link Column}
|
||||
* annotations for {@link RegistryLock#revisionId} and {@link RegistryLock#repoId} fields.
|
||||
* <p>Note: indexes use the camelCase version of the field names because the {@link
|
||||
* google.registry.persistence.NomulusNamingStrategy} does not translate the field name into the
|
||||
* snake_case column name until the write itself.
|
||||
*/
|
||||
indexes =
|
||||
@Index(
|
||||
name = "idx_registry_lock_repo_id_revision_id",
|
||||
columnList = "repo_id, revision_id",
|
||||
unique = true))
|
||||
indexes = {
|
||||
@Index(
|
||||
name = "idx_registry_lock_repo_id_revision_id",
|
||||
columnList = "repoId, revisionId",
|
||||
unique = true),
|
||||
@Index(name = "idx_registry_lock_verification_code", columnList = "verificationCode")
|
||||
})
|
||||
public final class RegistryLock extends ImmutableObject implements Buildable {
|
||||
|
||||
/** Describes the action taken by the user. */
|
||||
|
@ -74,11 +79,11 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
|||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "revision_id", nullable = false)
|
||||
@Column(nullable = false)
|
||||
private Long revisionId;
|
||||
|
||||
/** EPP repo ID of the domain in question. */
|
||||
@Column(name = "repo_id", nullable = false)
|
||||
@Column(nullable = false)
|
||||
private String repoId;
|
||||
|
||||
// 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. */
|
||||
@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
|
||||
|
@ -148,7 +154,7 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
|||
}
|
||||
|
||||
public DateTime getCreationTimestamp() {
|
||||
return toJodaDateTime(creationTimestamp);
|
||||
return creationTimestamp.getTimestamp();
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
public void setCompletionTimestamp(DateTime dateTime) {
|
||||
this.completionTimestamp = toZonedDateTime(dateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
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}. */
|
||||
|
@ -187,7 +200,6 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
|||
checkArgumentNotNull(getInstance().domainName, "Domain name cannot be null");
|
||||
checkArgumentNotNull(getInstance().registrarId, "Registrar ID 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");
|
||||
checkArgument(
|
||||
getInstance().registrarPocId != null || getInstance().isSuperuser,
|
||||
|
@ -220,8 +232,8 @@ public final class RegistryLock extends ImmutableObject implements Buildable {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Builder setCreationTimestamp(DateTime creationTimestamp) {
|
||||
getInstance().creationTimestamp = toZonedDateTime(creationTimestamp);
|
||||
public Builder setCreationTimestamp(CreateAutoTimestamp creationTimestamp) {
|
||||
getInstance().creationTimestamp = creationTimestamp;
|
||||
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
|
||||
-- limitations under the License.
|
||||
|
||||
CREATE TABLE "RegistryLock" (
|
||||
revision_id BIGSERIAL NOT NULL,
|
||||
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);
|
||||
create index if not exists idx_registry_lock_verification_code ON "RegistryLock"
|
||||
using btree (verification_code);
|
|
@ -220,6 +220,13 @@ ALTER TABLE ONLY public."RegistryLock"
|
|||
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: -
|
||||
--
|
||||
|
|
Loading…
Add table
Reference in a new issue