Skip to content

Instantly share code, notes, and snippets.

@Sam-Kruglov
Last active November 22, 2019 08:11
Show Gist options
  • Save Sam-Kruglov/cf46c96392b7a2c4cddf5450174b6715 to your computer and use it in GitHub Desktop.
Save Sam-Kruglov/cf46c96392b7a2c4cddf5450174b6715 to your computer and use it in GitHub Desktop.
Validate persistent collection on insert. https://vladmihalcea.com/hibernate-facts-favoring-sets-vs-bags/
package mypackage;
import mypackage.Identifiable;
import java.util.Objects;
import java.util.Set;
public final class EntitySetUtil {
private EntitySetUtil() {
// not meant to be initialized
}
/**
* Adds an entity to the set specified in the following {@link AddDetachedBuilder#to to} method.
*
* @param item an {@code @Entity} that is not null, which id is null (detached), and which does not yet exists in the set.
*/
public static <T extends Identifiable> AddDetachedBuilder<T> addDetachedEntity(T item) {
return new AddDetachedBuilder<>(item);
}
/**
* Adds an entity to the set specified in the following {@link AddManagedBuilder#to to} method.
*
* @param item an {@code @Entity} that is not null, which id is not null (managed), and which does not yet exists
* in the set.
*/
public static <T extends Identifiable> AddManagedBuilder<T> addManagedEntity(T item) {
return new AddManagedBuilder<>(item);
}
/**
* Removes an entity from the set specified in the following {@link RemoveManagedBuilder#from from} method.
*
* @param item an {@code @Entity} that is not null, which id is not null (managed), and which already exists in the
* set.
*/
public static <T extends Identifiable> RemoveManagedBuilder<T> removeManagedEntity(T item) {
return new RemoveManagedBuilder<>(item);
}
public static class AddDetachedBuilder<T extends Identifiable> extends BaseWithItemAndExtraCode<T> {
private AddDetachedBuilder(final T item) {
super(item);
}
@Override
public AddDetachedBuilder<T> withCode(Runnable check) {
setExtraCode(check);
return this;
}
/**
* Adds the entity assigned in {@link #addDetachedEntity} to the specified set.
*
* @throws IllegalArgumentException id is not null (managed) or already exists in the set.
* @throws NullPointerException is null.
*/
public void to(Set<T> items) {
assertItemIsNotNull();
assertTrue(item.getId() == null,
"Trying to persist an already managed " + item.getClass().getSimpleName() + ".");
runExtraCodeIfPresent();
addItemTo(items);
}
void addItemTo(Set<T> items) {
assertTrue(items.add(item), item.getClass().getSimpleName() + " already exists.");
}
}
public static class AddManagedBuilder<T extends Identifiable> extends AddDetachedBuilder<T> {
private AddManagedBuilder(final T item) {
super(item);
}
/**
* Adds the entity assigned in {@link #addManagedEntity} to the specified set.
*
* @throws IllegalArgumentException id is null (detached) or already exists in the set.
* @throws NullPointerException is null.
*/
@Override
public void to(Set<T> items) {
assertItemIsNotNull();
assertTrue(item.getId() != null,
"Trying to add a detached " + item.getClass().getSimpleName() + ".");
runExtraCodeIfPresent();
addItemTo(items);
}
}
public static class RemoveManagedBuilder<T extends Identifiable> extends BaseWithItemAndExtraCode<T> {
RemoveManagedBuilder(final T item) {
super(item);
}
/**
* Executes the supplied code after removing from the set if entity is present there.
*/
@Override
public RemoveManagedBuilder<T> withCode(Runnable check) {
setExtraCode(check);
return this;
}
/**
* Removes the entity assigned in the {@link #removeManagedEntity} from the specified set.
*
* @return true if this set contained the specified entity.
* @throws IllegalArgumentException id is null (detached).
* @throws NullPointerException is null.
*/
public boolean from(Set<T> items) {
assertItemIsNotNull();
assertTrue(item.getId() != null,
"Trying to remove detached " + item.getClass().getSimpleName() + ".");
boolean removed = removeItemFrom(items);
if (removed) {
runExtraCodeIfPresent();
}
return removed;
}
boolean removeItemFrom(final Set<T> items) {
return items.remove(item);
}
}
private abstract static class BaseWithItemAndExtraCode<T extends Identifiable> {
final T item;
private Runnable extraCode;
private BaseWithItemAndExtraCode(final T item) {
this.item = item;
}
/**
* Executes the supplied code after validation of the entity.
*/
public abstract BaseWithItemAndExtraCode<T> withCode(Runnable check);
void setExtraCode(Runnable code) {
extraCode = code;
}
void runExtraCodeIfPresent() {
Optional.ofNullable(extraCode).ifPresent(Runnable::run);
}
void assertItemIsNotNull() {
Objects.requireNonNull(item, "Persistent collection cannot work with null elements.");
}
}
private static void assertTrue(Boolean expression, String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}
}
package mypackage;
import mypackage.Identifiable;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import java.util.HashSet;
import java.util.Set;
import static package.EntitySetUtil.addDetachedEntity;
import static package.EntitySetUtil.addManagedEntity;
import static package.EntitySetUtil.removeManagedEntity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
import static org.assertj.core.api.Assertions.catchThrowable;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
@DisplayName("Set of entities")
@TestInstance(PER_CLASS)
class EntitySetUtilTest {
final Set<Identifiable> entities = new HashSet<>();
@AfterEach
void clearSet() {
entities.clear();
}
@DisplayName("supplying managed entity")
@Nested
class ManagedEntity implements TestSingleton {
final Identifiable managedEntity;
ManagedEntity() {
managedEntity = mock(Identifiable.class);
int id = 1;
when(managedEntity.getId()).thenReturn(id);
}
@DisplayName("addDetachedEntity throws 'managed'")
@Test
void addDetachedEntity_exception() {
//act & assert
assertThatIllegalArgumentException()
.isThrownBy(() -> addDetachedEntity(managedEntity).to(entities))
.withMessageContaining("managed");
}
@DisplayName("addManagedEntity adds")
@Test
void addManagedEntity_added() {
//act
addManagedEntity(managedEntity).to(entities);
//assert
assertThat(entities).containsExactly(managedEntity);
}
@DisplayName("removeManagedEntity returns false")
@Test
void removeManagedEntity_false() {
//act & assert
assertThat(removeManagedEntity(managedEntity).from(entities)).isFalse();
}
@DisplayName("supplying extra code")
@Nested
class ExtraCode extends BaseExtraCode {
@DisplayName("addManagedEntity executes")
@Test
void addManagedEntity_executed() {
//act
addManagedEntity(managedEntity).withCode(extraCode).to(entities);
//assert
verify(extraCode).run();
}
@DisplayName("addDetachedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void addDetachedEntity_exception() {
//act
catchThrowable(() -> addDetachedEntity(managedEntity).withCode(extraCode).to(entities));
//assert
verify(extraCode, never()).run();
}
@DisplayName("removeManagedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void removeManagedEntity_exception() {
//entity does not exist so we do not execute the code because it may influence hashcode & equals
//act
catchThrowable(() -> removeManagedEntity(managedEntity).withCode(extraCode).from(entities));
//act & assert
verify(extraCode, never()).run();
}
}
@DisplayName("the entity is already inside the set")
@Nested
class InsideSet implements TestSingleton {
@BeforeEach
void addEntityToSet() {
entities.add(managedEntity);
}
@Test
@DisplayName("addManagedEntity throws 'exists'")
void addManagedEntity_exception() {
//act & assert
assertThatIllegalArgumentException()
.isThrownBy(() -> addManagedEntity(managedEntity).to(entities))
.withMessageContaining("exists");
}
@Test
@DisplayName("removeManagedEntity removes")
void removeManagedEntity_removed() {
//act
removeManagedEntity(managedEntity).from(entities);
//assert
assertThat(entities).isEmpty();
}
@DisplayName("supplying extra code")
@Nested
class ExtraCode extends BaseExtraCode {
@Test
@DisplayName("removeManagedEntity executes")
void removeManagedEntity_executed() {
//act
removeManagedEntity(managedEntity).withCode(extraCode).from(entities);
//assert
verify(extraCode).run();
}
@DisplayName("addManagedEntity executes")
@Test
@SuppressWarnings("ThrowableNotThrown")
void addManagedEntity_exception() {
//entity exists but we still execute the code because it may influence hashcode & equals
//act
catchThrowable(() -> addManagedEntity(managedEntity).withCode(extraCode).to(entities));
//assert
verify(extraCode).run();
}
}
}
}
@DisplayName("supplying detached entity")
@Nested
class DetachedEntity implements TestSingleton {
final Identifiable detachedEntity;
DetachedEntity() {
detachedEntity = mock(Identifiable.class);
when(detachedEntity.getId()).thenReturn(null);
}
@DisplayName("addDetachedEntity adds")
@Test
void addDetachedEntity_added() {
//act
addDetachedEntity(detachedEntity).to(entities);
//assert
assertThat(entities).containsExactly(detachedEntity);
}
@DisplayName("addManagedEntity throws 'detached'")
@Test
void addManagedEntity_exception() {
//act & assert
assertThatIllegalArgumentException()
.isThrownBy(() -> addManagedEntity(detachedEntity).to(entities))
.withMessageContaining("detached");
}
@DisplayName("removeManagedEntity throws 'detached'")
@Test
void removeManagedEntity_exception() {
//act & assert
assertThatIllegalArgumentException()
.isThrownBy(() -> removeManagedEntity(detachedEntity).from(entities))
.withMessageContaining("detached");
}
@DisplayName("supplying extra code")
@Nested
class ExtraCode extends BaseExtraCode {
@DisplayName("addDetachedEntity executes")
@Test
void addDetachedEntity_executed() {
//act
addDetachedEntity(detachedEntity).withCode(extraCode).to(entities);
//assert
verify(extraCode).run();
}
@DisplayName("addManagedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void addManagedEntity_exception() {
//act
catchThrowable(() -> addManagedEntity(detachedEntity).withCode(extraCode).to(entities));
//assert
verify(extraCode, never()).run();
}
@DisplayName("removeManagedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void removeManagedEntity_exception() {
//act
catchThrowable(() -> removeManagedEntity(detachedEntity).withCode(extraCode).from(entities));
//assert
verify(extraCode, never()).run();
}
}
@DisplayName("the entity is already inside the set")
@Nested
class InsideSet implements TestSingleton {
@BeforeEach
void addEntityToSet() {
entities.add(detachedEntity);
}
@DisplayName("addDetachedEntity throws 'exists'")
@Test
void addDetachedEntity_exception() {
//act & assert
assertThatIllegalArgumentException()
.isThrownBy(() -> addDetachedEntity(detachedEntity).to(entities))
.withMessageContaining("exists");
}
@DisplayName("supplying extra code")
@Nested
class ExtraCode extends BaseExtraCode {
@DisplayName("addDetachedEntity executes")
@Test
@SuppressWarnings("ThrowableNotThrown")
void addDetachedEntity_executedBeforeException() {
//entity exists but we still execute the code because it may influence hashcode & equals
//act
catchThrowable(() -> addDetachedEntity(detachedEntity).withCode(extraCode).to(entities));
//assert
verify(extraCode).run();
}
}
}
}
@DisplayName("supplying null entity")
@Nested
class NullEntity implements TestSingleton {
final Identifiable nullEntity = null;
@DisplayName("addDetachedEntity throws 'null'")
@Test
void addDetachedEntity_exception() {
//act & assert
assertThatNullPointerException()
.isThrownBy(() -> addDetachedEntity(nullEntity).to(entities))
.withMessageContaining("null");
}
@DisplayName("addManagedEntity throws 'null'")
@Test
void addManagedEntity_exception() {
//act & assert
assertThatNullPointerException()
.isThrownBy(() -> addManagedEntity(nullEntity).to(entities))
.withMessageContaining("null");
}
@DisplayName("removeManagedEntity throws 'null'")
@Test
void removeManagedEntity_exception() {
//act & assert
assertThatNullPointerException()
.isThrownBy(() -> removeManagedEntity(nullEntity).from(entities))
.withMessageContaining("null");
}
@DisplayName("supplying extra code")
@Nested
class ExtraCode extends BaseExtraCode {
@DisplayName("addDetachedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void addDetachedEntity_exception() {
//act
catchThrowable(() -> addDetachedEntity(nullEntity).withCode(extraCode).to(entities));
//assert
verify(extraCode, never()).run();
}
@DisplayName("addManagedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void addManagedEntity_exception() {
//act
catchThrowable(() -> addManagedEntity(nullEntity).withCode(extraCode).to(entities));
//assert
verify(extraCode, never()).run();
}
@DisplayName("removeManagedEntity does not execute")
@Test
@SuppressWarnings("ThrowableNotThrown")
void removeManagedEntity_exception() {
//act
catchThrowable(() -> removeManagedEntity(nullEntity).withCode(extraCode).from(entities));
//assert
verify(extraCode, never()).run();
}
}
}
class BaseExtraCode implements TestSingleton {
final Runnable extraCode = spy(Runnable.class);
@AfterEach
void clearSpy() {
clearInvocations(extraCode);
}
}
@TestInstance(PER_CLASS)
interface TestSingleton {
}
}
package mypackage;
import java.io.Serializable;
public interface Identifiable<T extends Serializable> {
T getId();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment