diff --git a/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/GenericIdRef.java b/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/GenericIdRef.java new file mode 100644 index 000000000..9c19b292b --- /dev/null +++ b/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/GenericIdRef.java @@ -0,0 +1,60 @@ +package io.oasp.module.basic.common.api.reference; + +import java.util.Objects; + +/** + * Generic implementation of {@link Ref}. + * + * @param generic type of {@link #getId() ID}. + * @param generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}. + */ +public class GenericIdRef implements Ref { + + private static final long serialVersionUID = 1L; + + private final ID id; + + /** + * The constructor. + * + * @param id the {@link #getId() ID}. + */ + public GenericIdRef(ID id) { + + super(); + Objects.requireNonNull(id, "id"); + this.id = id; + } + + @Override + public ID getId() { + + return this.id; + } + + @Override + public int hashCode() { + + return this.id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + + if (this == obj) { + return true; + } + if ((obj == null) || (getClass() != obj.getClass())) { + return false; + } + GenericIdRef other = (GenericIdRef) obj; + return Objects.equals(this.id, other.id); + } + + @Override + public String toString() { + + return this.id.toString(); + } + +} diff --git a/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/IdRef.java b/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/IdRef.java new file mode 100644 index 000000000..62d7df7f4 --- /dev/null +++ b/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/IdRef.java @@ -0,0 +1,62 @@ +package io.oasp.module.basic.common.api.reference; + +import net.sf.mmm.util.entity.api.GenericEntity; + +/** + * A {@link Ref} using {@link Long} values as {@link #getId() ID}. + * + * @param generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}. + */ +public class IdRef extends GenericIdRef { + + private static final long serialVersionUID = 1L; + + /** + * The constructor. + * + * @param id the {@link #getId() ID}. + */ + public IdRef(Long id) { + + super(id); + } + + /** + * @param generic type of the referenced {@link GenericEntity}. + * @param entity the {@link GenericEntity} to reference. + * @return the {@link IdRef} pointing to the given {@link GenericEntity} or {@code null} if the {@link GenericEntity} + * or its {@link GenericEntity#getId() ID} is {@code null}. + */ + public static > IdRef of(E entity) { + + if (entity == null) { + return null; + } + return of(entity.getId()); + } + + /** + * @param generic type of the referenced {@link GenericEntity}. + * @param id the {@link #getId() ID} to wrap. + * @return the {@link IdRef} pointing to an entity with the specified {@link #getId() ID} or {@code null} if the given + * {@code ID} was {@code null}. + */ + public static IdRef of(Long id) { + + if (id == null) { + return null; + } + return new IdRef<>(id); + } + + /** + * @param generic type of the referenced {@link GenericEntity}. + * @param id the {@link #getId() ID} to wrap. + * @return the {@link IdRef} pointing to an entity with the specified {@link #getId() ID}. + */ + public static IdRef of(long id) { + + return new IdRef<>(Long.valueOf(id)); + } + +} diff --git a/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/Ref.java b/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/Ref.java new file mode 100644 index 000000000..71f2f82df --- /dev/null +++ b/modules/basic/src/main/java/io/oasp/module/basic/common/api/reference/Ref.java @@ -0,0 +1,22 @@ +package io.oasp.module.basic.common.api.reference; + +import net.sf.mmm.util.lang.api.Datatype; + +/** + * Interface for a reference to an {@link net.sf.mmm.util.entity.api.GenericEntity entity} via its {@link #getId() ID}. + * In most cases you want to use {@link IdRef}. + * + * @param generic type of {@link #getId() ID}. + * @param generic type of the referenced {@link net.sf.mmm.util.entity.api.Entity}. For flexibility not technically + * bound to {@link net.sf.mmm.util.entity.api.Entity} so it can also be used for an external entity not + * satisfying any requirements. + */ +public interface Ref extends Datatype { + + /** + * @return the ({@link net.sf.mmm.util.entity.api.GenericEntity#getId() ID} of the referenced + * {@link net.sf.mmm.util.entity.api.Entity}. + */ + ID getId(); + +} diff --git a/modules/basic/src/test/java/io/oasp/module/basic/common/api/reference/IdRefTest.java b/modules/basic/src/test/java/io/oasp/module/basic/common/api/reference/IdRefTest.java new file mode 100644 index 000000000..a3dab04af --- /dev/null +++ b/modules/basic/src/test/java/io/oasp/module/basic/common/api/reference/IdRefTest.java @@ -0,0 +1,79 @@ +package io.oasp.module.basic.common.api.reference; + +import net.sf.mmm.util.entity.api.MutableGenericEntity; + +import org.junit.Test; + +import io.oasp.module.basic.common.api.to.AbstractEto; +import io.oasp.module.test.common.base.ModuleTest; + +/** + * Test of {@link IdRef}. + */ +public class IdRefTest extends ModuleTest { + + /** Test of {@link IdRef#of(net.sf.mmm.util.entity.api.GenericEntity)} */ + @Test + public void testOfEntity() { + + // given + long id = 4711L; + FooEto foo = new FooEto(); + foo.setId(id); + + // when + IdRef fooId = IdRef. of(foo); // with Java8 type-inference the additional is not required + + // then + assertThat(fooId).isNotNull(); + assertThat(fooId.getId()).isEqualTo(foo.getId()).isEqualTo(id); + assertThat(fooId.toString()).isEqualTo(Long.toString(id)); + assertThat(IdRef.of((Foo) null)).isNull(); + + Bar bar = new Bar(); + bar.setFooId(fooId); // just a syntax/compilation check. + // bar.setBarId(fooId); // will produce compiler error what is desired in such case + IdRef barId = IdRef.of(1234L); + bar.setBarId(barId); // this again will compile + } + + /** Test of {@link IdRef#of(Long)} */ + @Test + public void testOfLong() { + + // given + long id = 4711L; + + // when + IdRef fooId = IdRef.of(id); // not type-safe but required in some cases + + // then + assertThat(fooId).isNotNull(); + assertThat(fooId.getId()).isEqualTo(id); + assertThat(fooId.toString()).isEqualTo(Long.toString(id)); + assertThat(IdRef.of(Long.valueOf(id))).isEqualTo(fooId).isNotSameAs(fooId); + assertThat(IdRef.of((Long) null)).isNull(); + } + + private interface Foo extends MutableGenericEntity { + + } + + private class FooEto extends AbstractEto implements Foo { + + private static final long serialVersionUID = 1L; + + } + + private class Bar { + + void setFooId(IdRef fooId) { + + } + + void setBarId(IdRef fooId) { + + } + } + +} diff --git a/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaEntityManagerAccess.java b/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaEntityManagerAccess.java new file mode 100644 index 000000000..49f3b4eb3 --- /dev/null +++ b/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaEntityManagerAccess.java @@ -0,0 +1,43 @@ +package io.oasp.module.jpa.dataaccess.api; + +import javax.persistence.EntityManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Internal access to {@link EntityManager}. + */ +class JpaEntityManagerAccess { + + private static final Logger LOG = LoggerFactory.getLogger(JpaEntityManagerAccess.class); + + private static EntityManager entityManager; + + static void setEntityManager(EntityManager entityManager, boolean check) { + + if ((JpaEntityManagerAccess.entityManager != null) && (JpaEntityManagerAccess.entityManager != entityManager)) { + if (check) { + throw new IllegalStateException("EntityManager has already been initialized!"); + } else { + LOG.debug("EntityManager conflict: {} has been replaced with {}. This may only happen during tests.", + JpaEntityManagerAccess.entityManager, entityManager); + } + } + JpaEntityManagerAccess.entityManager = entityManager; + } + + static boolean hasEntityManager() { + + return (entityManager != null); + } + + static EntityManager getEntityManager() { + + if (entityManager == null) { + throw new IllegalStateException("EntityManager has not yet been initialized!"); + } + return entityManager; + } + +} diff --git a/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaHelper.java b/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaHelper.java new file mode 100644 index 000000000..c70e436a2 --- /dev/null +++ b/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaHelper.java @@ -0,0 +1,78 @@ +package io.oasp.module.jpa.dataaccess.api; + +import java.util.Collection; + +import net.sf.mmm.util.entity.api.GenericEntity; + +import io.oasp.module.basic.common.api.reference.GenericIdRef; +import io.oasp.module.basic.common.api.reference.Ref; + +/** + * Helper class for generic handling of {@link net.sf.mmm.util.entity.api.PersistenceEntity persistence entities} (based + * on {@link javax.persistence.EntityManager}). In some cases it is required to access JPA features in a static way. + * E.g. a common case is a setter in your {@link net.sf.mmm.util.entity.api.PersistenceEntity} for a + * {@link io.oasp.module.basic.common.api.reference.Ref reference} from an + * {@link io.oasp.module.basic.common.api.to.AbstractEto ETO} that can be archieved via the following code: + * + *
+ * @Entity
+ * @Table("Foo")
+ * public class FooEntity extends ApplicationPersistenceEntity implements Foo {
+ *   @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
+ *   @JoinColumn(name = "bar")
+ *   private BarEntity bar;
+ *   ...
+ *   @Override
+ *   public void setBarId({@link io.oasp.module.basic.common.api.reference.IdRef}{@literal } barId) {
+ *     this.bar = {@link JpaHelper}.{@link JpaHelper#asEntity(Ref, Class) asEntity}(barId, BarEntity.class);
+ *   }
+ * }
+ * 
+ */ +public class JpaHelper { + + /** + * @param generic type of the {@link net.sf.mmm.util.entity.api.PersistenceEntity entity} + * @param reference the {@link Ref} or {@code null}. Typically an + * {@link io.oasp.module.basic.common.api.reference.IdRef}. + * @param entityClass the {@link net.sf.mmm.util.entity.api.PersistenceEntity entity} {@link Class}. + * @return the {@link net.sf.mmm.util.entity.api.PersistenceEntity entity} of the specified {@link Class} with the + * {@link Ref#getId() ID} from the given {@link GenericIdRef} or {@code null} if the given {@link Ref} is + * {@code null}. + */ + public static E asEntity(Ref reference, Class entityClass) { + + if (reference == null) { + return null; + } else { + return JpaEntityManagerAccess.getEntityManager().getReference(entityClass, reference.getId()); + } + } + + /** + * @param generic type of the input {@link GenericEntity entities} (most commonly the entity interface). + * @param

generic type of the output {@link net.sf.mmm.util.entity.api.PersistenceEntity persistence entities}. + * @param input the {@link Collection} of {@link GenericEntity entities} (e.g. + * {@link io.oasp.module.basic.common.api.to.AbstractEto ETOs}) to use as input. + * @param entityClass the {@link Class} reflecting the {@link net.sf.mmm.util.entity.api.PersistenceEntity}. + * @param output die {@link Collection} where to {@link Collection#add(Object) add} the + * {@link net.sf.mmm.util.entity.api.PersistenceEntity persistent entities} corresponding to the input + * {@link GenericEntity entities}. Most probably {@link Collection#isEmpty() empty} but may also already + * contain entities so this method will add additional entities. + */ + @SuppressWarnings("unchecked") + public static , P extends E> void asEntities(Collection input, + Class

entityClass, Collection

output) { + + for (E eto : input) { + P entity; + if (entityClass.isInstance(eto)) { + entity = (P) eto; + } else { + entity = JpaEntityManagerAccess.getEntityManager().getReference(entityClass, eto.getId()); + } + output.add(entity); + } + } + +} diff --git a/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaInitializer.java b/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaInitializer.java new file mode 100644 index 000000000..01d0c360b --- /dev/null +++ b/modules/jpa-basic/src/main/java/io/oasp/module/jpa/dataaccess/api/JpaInitializer.java @@ -0,0 +1,29 @@ +package io.oasp.module.jpa.dataaccess.api; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +/** + * Initializer bean for {@link EntityManager}. Will be auto configured via {@code oasp4j-starter-jpa}. + */ +public class JpaInitializer { + + /** + * @param entityManager the {@link EntityManager} to inject. + */ + @PersistenceContext + protected void setEntityManager(EntityManager entityManager) { + + JpaEntityManagerAccess.setEntityManager(entityManager, true); + } + + /** + * @param entityManager the {@link EntityManager} to set. + * @param check - {@code true} to check that the {@link EntityManager} does not change on-the-fly (desired in + * productive code), {@code false} otherwise (may be desired in test-code). + */ + protected void setEntityManager(EntityManager entityManager, boolean check) { + + JpaEntityManagerAccess.setEntityManager(entityManager, check); + } +} diff --git a/modules/jpa-dao/pom.xml b/modules/jpa-dao/pom.xml index 101ec7292..1a4b7b181 100644 --- a/modules/jpa-dao/pom.xml +++ b/modules/jpa-dao/pom.xml @@ -19,6 +19,11 @@ io.oasp.java.modules oasp4j-jpa-basic + + io.oasp.java.modules + oasp4j-beanmapping + test + org.hibernate hibernate-entitymanager diff --git a/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/api/GenericDao.java b/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/api/GenericDao.java index 1a37d2774..5b5e75924 100644 --- a/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/api/GenericDao.java +++ b/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/api/GenericDao.java @@ -7,6 +7,8 @@ import io.oasp.module.jpa.dataaccess.api.feature.FeatureForceIncrementModificationCounter; +import io.oasp.module.basic.common.api.reference.Ref; + /** * This is the interface for a Data Access Object (DAO). It acts as a manager responsible for the persistence * operations on a specific {@link PersistenceEntity entity} {@literal }.
@@ -65,6 +67,14 @@ public interface GenericDao> extends Feature */ E findOne(ID id) throws IllegalArgumentException; + /** + * @param reference the {@link Ref} to the {@link PersistenceEntity} to get. Typically an instance of + * {@link io.oasp.module.basic.common.api.reference.IdRef}. + * @return the {@link PersistenceEntity} as {@link javax.persistence.EntityManager#getReference(Class, Object) + * reference} for the given {@link Ref}. Will be {@code null} if the given {@link Ref} was {@code null}. + */ + E get(Ref reference); + /** * Returns whether an entity with the given id exists. * diff --git a/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDao.java b/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDao.java index 78b404b1b..e681946e8 100644 --- a/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDao.java +++ b/modules/jpa-dao/src/main/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDao.java @@ -20,6 +20,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.oasp.module.basic.common.api.reference.Ref; import io.oasp.module.jpa.dataaccess.api.GenericDao; import io.oasp.module.jpa.dataaccess.api.QueryHelper; @@ -136,6 +137,15 @@ public E find(ID id) throws ObjectNotFoundUserException { return entity; } + @Override + public E get(Ref reference) { + + if (reference == null) { + return null; + } + return getEntityManager().getReference(getEntityClass(), reference.getId()); + } + @Override public boolean exists(ID id) { diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/TestApplication.java b/modules/jpa-dao/src/test/java/io/oasp/example/TestApplication.java new file mode 100644 index 000000000..02e50f448 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/TestApplication.java @@ -0,0 +1,30 @@ +package io.oasp.example; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.Bean; + +import io.oasp.module.jpa.dataaccess.api.JpaInitializer; + +/** + * Spring-boot app for testing. + */ +@SpringBootApplication +@EntityScan +public class TestApplication { + + @Bean + public JpaInitializer jpaInitializer() { + + return new JpaInitializer(); + } + + /** + * @param args the command-line arguments + */ + public static void main(String[] args) { + + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/Bar.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/Bar.java new file mode 100644 index 000000000..15a83a84d --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/Bar.java @@ -0,0 +1,11 @@ +package io.oasp.example.component.common.api; + +import io.oasp.example.general.common.api.TestApplicationEntity; + +public interface Bar extends TestApplicationEntity { + + String getMessage(); + + void setMessage(String message); + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/Foo.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/Foo.java new file mode 100644 index 000000000..1ce449f45 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/Foo.java @@ -0,0 +1,16 @@ +package io.oasp.example.component.common.api; + +import io.oasp.example.general.common.api.TestApplicationEntity; +import io.oasp.module.basic.common.api.reference.IdRef; + +public interface Foo extends TestApplicationEntity { + + String getName(); + + void setName(String name); + + IdRef getBarId(); + + void setBarId(IdRef barId); + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/to/BarEto.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/to/BarEto.java new file mode 100644 index 000000000..dabdae7a6 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/to/BarEto.java @@ -0,0 +1,27 @@ +package io.oasp.example.component.common.api.to; + +import io.oasp.example.component.common.api.Bar; +import io.oasp.module.basic.common.api.to.AbstractEto; + +/** + * Implementation of {@link Bar} as {@link AbstractEto ETO}. + */ +public class BarEto extends AbstractEto implements Bar { + + private static final long serialVersionUID = 1L; + + private String message; + + @Override + public String getMessage() { + + return this.message; + } + + @Override + public void setMessage(String message) { + + this.message = message; + } + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/to/FooEto.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/to/FooEto.java new file mode 100644 index 000000000..0072c1ef8 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/common/api/to/FooEto.java @@ -0,0 +1,42 @@ +package io.oasp.example.component.common.api.to; + +import io.oasp.example.component.common.api.Bar; +import io.oasp.example.component.common.api.Foo; +import io.oasp.module.basic.common.api.reference.IdRef; +import io.oasp.module.basic.common.api.to.AbstractEto; + +/** + * Implementation of {@link Foo} as {@link AbstractEto ETO}. + */ +public class FooEto extends AbstractEto implements Foo { + private static final long serialVersionUID = 1L; + + private String name; + + private IdRef barId; + + @Override + public String getName() { + + return this.name; + } + + @Override + public void setName(String name) { + + this.name = name; + } + + @Override + public IdRef getBarId() { + + return this.barId; + } + + @Override + public void setBarId(IdRef barId) { + + this.barId = barId; + } + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/BarEntity.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/BarEntity.java new file mode 100644 index 000000000..bd171d27e --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/BarEntity.java @@ -0,0 +1,31 @@ +package io.oasp.example.component.dataaccess.api; + +import javax.persistence.Entity; +import javax.persistence.Table; + +import io.oasp.example.component.common.api.Bar; +import io.oasp.example.general.dataaccess.api.TestApplicationPersistenceEntity; + +/** + * Implementation of {@link Bar} as {@link TestApplicationPersistenceEntity persistence entity}. + */ +@Entity +@Table(name = "Bar") +public class BarEntity extends TestApplicationPersistenceEntity implements Bar { + private static final long serialVersionUID = 1L; + + private String message; + + @Override + public String getMessage() { + + return this.message; + } + + @Override + public void setMessage(String message) { + + this.message = message; + } + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/FooEntity.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/FooEntity.java new file mode 100644 index 000000000..9c8766d13 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/FooEntity.java @@ -0,0 +1,76 @@ +package io.oasp.example.component.dataaccess.api; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; +import javax.persistence.Transient; + +import io.oasp.example.component.common.api.Bar; +import io.oasp.example.component.common.api.Foo; +import io.oasp.example.general.dataaccess.api.TestApplicationPersistenceEntity; +import io.oasp.module.basic.common.api.reference.IdRef; +import io.oasp.module.jpa.dataaccess.api.JpaHelper; + +/** + * Implementation of {@link Foo} as {@link TestApplicationPersistenceEntity persistence entity}. + */ +@Entity +@Table(name = "Foo") +public class FooEntity extends TestApplicationPersistenceEntity implements Foo { + private static final long serialVersionUID = 1L; + + private String name; + + private BarEntity bar; + + @Override + public String getName() { + + return this.name; + } + + @Override + public void setName(String name) { + + this.name = name; + } + + /** + * @return the {@link BarEntity}. + */ + @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) + @JoinColumn(name = "bar") + public BarEntity getBar() { + + return this.bar; + } + + /** + * @param bar new value of {@link #getBar()}. + */ + public void setBar(BarEntity bar) { + + this.bar = bar; + } + + @Override + @Transient + public IdRef getBarId() { + + // you actually need Java8 type-inference to use this feature in a comfortable way as otherwise you need to qualify + // the generic on every use + + // return IdRef.of(this.bar); // Java 8+ + return IdRef. of(this.bar); // Java 5/6/7 + } + + @Override + public void setBarId(IdRef barId) { + + this.bar = JpaHelper.asEntity(barId, BarEntity.class); + } + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/dao/BarDao.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/dao/BarDao.java new file mode 100644 index 000000000..20675c240 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/api/dao/BarDao.java @@ -0,0 +1,11 @@ +package io.oasp.example.component.dataaccess.api.dao; + +import io.oasp.example.component.dataaccess.api.BarEntity; +import io.oasp.module.jpa.dataaccess.api.Dao; + +/** + * {@link Dao} for {@link BarEntity}. + */ +public interface BarDao extends Dao { + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/impl/BarDaoTxBean.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/impl/BarDaoTxBean.java new file mode 100644 index 000000000..907075d5c --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/impl/BarDaoTxBean.java @@ -0,0 +1,52 @@ +package io.oasp.example.component.dataaccess.impl; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.transaction.Transactional; + +import io.oasp.example.component.dataaccess.api.BarEntity; +import io.oasp.example.component.dataaccess.api.dao.BarDao; +import io.oasp.module.jpa.dataaccess.api.GenericDao; +import io.oasp.module.jpa.dataaccess.base.AbstractGenericDaoTest; + +/** + * This type provides methods in a transactional environment for the {@link AbstractGenericDaoTest}. All methods, + * annotated with the {@link Transactional} annotation, are executed in separate transaction, thus one test case can + * execute multiple transactions. Unfortunately this does not work when the transactional methods are directly in the + * top-level class of the test-case itself. + */ +@Named +public class BarDaoTxBean { + + @Inject + private BarDao genericDao; + + /** + * Creates a new {@link BarEntity}, persist it and surround everything with a transaction. + * + * @return entity the new {@link BarEntity}. + */ + @Transactional + public BarEntity create() { + + BarEntity entity = new BarEntity(); + this.genericDao.save(entity); + return entity; + } + + /** + * Loads the {@link BarEntity} with the given {@code id} and + * {@link GenericDao#forceIncrementModificationCounter(net.sf.mmm.util.entity.api.PersistenceEntity) increments the + * modification counter}. + * + * @param id of the {@link BarEntity} to load and increment. + * @return entity the updated {@link BarEntity}. + */ + @Transactional + public BarEntity incrementModificationCounter(long id) { + + BarEntity entity = this.genericDao.find(id); + this.genericDao.forceIncrementModificationCounter(entity); + return entity; + } +} \ No newline at end of file diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/impl/dao/BarDaoImpl.java b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/impl/dao/BarDaoImpl.java new file mode 100644 index 000000000..bcb195cdb --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/component/dataaccess/impl/dao/BarDaoImpl.java @@ -0,0 +1,21 @@ +package io.oasp.example.component.dataaccess.impl.dao; + +import javax.inject.Named; + +import io.oasp.example.component.dataaccess.api.BarEntity; +import io.oasp.example.component.dataaccess.api.dao.BarDao; +import io.oasp.module.jpa.dataaccess.base.AbstractDao; + +/** + * Implementation of {@link BarDao}. + */ +@Named +public class BarDaoImpl extends AbstractDao implements BarDao { + + @Override + protected Class getEntityClass() { + + return BarEntity.class; + } + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/general/common/api/TestApplicationEntity.java b/modules/jpa-dao/src/test/java/io/oasp/example/general/common/api/TestApplicationEntity.java new file mode 100644 index 000000000..5a35223c9 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/general/common/api/TestApplicationEntity.java @@ -0,0 +1,11 @@ +package io.oasp.example.general.common.api; + +import net.sf.mmm.util.entity.api.MutableGenericEntity; + +/** + * This is the abstract interface for a {@link MutableGenericEntity} of this application. We are using {@link Long} for + * all {@link #getId() primary keys}. + */ +public abstract interface TestApplicationEntity extends MutableGenericEntity { + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/general/common/api/config/BeansDozerConfig.java b/modules/jpa-dao/src/test/java/io/oasp/example/general/common/api/config/BeansDozerConfig.java new file mode 100644 index 000000000..c9f93ebe5 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/general/common/api/config/BeansDozerConfig.java @@ -0,0 +1,32 @@ +package io.oasp.example.general.common.api.config; + +import java.util.ArrayList; +import java.util.List; + +import org.dozer.DozerBeanMapper; +import org.dozer.Mapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * Java bean configuration for Dozer + */ +@Configuration +@ComponentScan(basePackages = { "io.oasp.module.beanmapping" }) +public class BeansDozerConfig { + + private static final String DOZER_MAPPING_XML = "config/app/common/dozer-mapping.xml"; + + /** + * @return the {@link DozerBeanMapper}. + */ + @Bean + public Mapper getDozer() { + + List beanMappings = new ArrayList<>(); + beanMappings.add(DOZER_MAPPING_XML); + return new DozerBeanMapper(beanMappings); + + } +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/example/general/dataaccess/api/TestApplicationPersistenceEntity.java b/modules/jpa-dao/src/test/java/io/oasp/example/general/dataaccess/api/TestApplicationPersistenceEntity.java new file mode 100644 index 000000000..f4abe4f09 --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/example/general/dataaccess/api/TestApplicationPersistenceEntity.java @@ -0,0 +1,108 @@ +package io.oasp.example.general.dataaccess.api; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; +import javax.persistence.Version; + +import io.oasp.example.general.common.api.TestApplicationEntity; +import io.oasp.module.jpa.dataaccess.api.MutablePersistenceEntity; + +/** + * Abstract Entity for all Entities with an id and a version field. + * + */ +@MappedSuperclass +public abstract class TestApplicationPersistenceEntity implements TestApplicationEntity, MutablePersistenceEntity { + + private static final long serialVersionUID = 1L; + + /** @see #getId() */ + private Long id; + + /** @see #getModificationCounter() */ + private int modificationCounter; + + /** @see #getRevision() */ + private Number revision; + + /** + * The constructor. + */ + public TestApplicationPersistenceEntity() { + + super(); + } + + @Override + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + public Long getId() { + + return this.id; + } + + /** + * {@inheritDoc} + */ + @Override + public void setId(Long id) { + + this.id = id; + } + + @Override + @Version + public int getModificationCounter() { + + return this.modificationCounter; + } + + @Override + public void setModificationCounter(int version) { + + this.modificationCounter = version; + } + + @Override + @Transient + public Number getRevision() { + + return this.revision; + } + + /** + * @param revision the revision to set + */ + @Override + public void setRevision(Number revision) { + + this.revision = revision; + } + + @Override + public String toString() { + + StringBuilder buffer = new StringBuilder(); + toString(buffer); + return buffer.toString(); + } + + /** + * Method to extend {@link #toString()} logic. + * + * @param buffer is the {@link StringBuilder} where to {@link StringBuilder#append(Object) append} the string + * representation. + */ + protected void toString(StringBuilder buffer) { + + buffer.append(getClass().getSimpleName()); + if (this.id != null) { + buffer.append("[id="); + buffer.append(this.id); + buffer.append("]"); + } + } +} \ No newline at end of file diff --git a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/api/JpaHelperTest.java b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/api/JpaHelperTest.java new file mode 100644 index 000000000..7c0c7181d --- /dev/null +++ b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/api/JpaHelperTest.java @@ -0,0 +1,60 @@ +package io.oasp.module.jpa.dataaccess.api; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.transaction.Transactional; + +import org.junit.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; + +import io.oasp.example.TestApplication; +import io.oasp.example.component.common.api.to.FooEto; +import io.oasp.example.component.dataaccess.api.BarEntity; +import io.oasp.example.component.dataaccess.api.FooEntity; +import io.oasp.module.beanmapping.common.api.BeanMapper; +import io.oasp.module.test.common.base.ComponentTest; + +/** + * Test of {@link JpaHelper}. + */ +@Transactional +@SpringBootTest(classes = { TestApplication.class }, webEnvironment = WebEnvironment.NONE) +public class JpaHelperTest extends ComponentTest { + + @PersistenceContext + private EntityManager entityManager; + + @Inject + private BeanMapper beanMapper; + + /** + * Test of {@link JpaHelper#asEntity(io.oasp.module.basic.common.api.reference.Ref, Class)} via real production-like + * scenario. + */ + @Test + public void testIdRefAsEntity() { + + // given + BarEntity barEntity = new BarEntity(); + barEntity.setMessage("Test message"); + FooEntity fooEntity = new FooEntity(); + fooEntity.setName("Test name"); + fooEntity.setBar(barEntity); + + // when + this.entityManager.persist(fooEntity); + FooEto fooEto = new FooEto(); + fooEto.setId(fooEntity.getId()); + fooEto.setModificationCounter(fooEntity.getModificationCounter()); + fooEto.setName(fooEntity.getName()); + fooEto.setBarId(fooEntity.getBarId()); + + FooEntity fooEntity2 = this.beanMapper.map(fooEto, FooEntity.class); + + // then + assertThat(fooEntity2.getBar()).isSameAs(barEntity); + } + +} diff --git a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDaoTest.java b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDaoTest.java index 6ee0c84f1..72534e992 100644 --- a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDaoTest.java +++ b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/AbstractGenericDaoTest.java @@ -1,27 +1,25 @@ package io.oasp.module.jpa.dataaccess.base; import javax.inject.Inject; -import javax.inject.Named; -import javax.transaction.Transactional; import org.junit.Test; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import io.oasp.example.TestApplication; +import io.oasp.example.component.dataaccess.api.BarEntity; +import io.oasp.example.component.dataaccess.impl.BarDaoTxBean; import io.oasp.module.jpa.dataaccess.api.GenericDao; import io.oasp.module.test.common.base.ComponentTest; /** * Test class to test the {@link GenericDao}. - * */ -@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class }) -@ContextConfiguration({ "classpath:config/app/dataaccess/beans-dataaccess.xml" }) +@SpringBootTest(classes = { TestApplication.class }, webEnvironment = WebEnvironment.NONE) public class AbstractGenericDaoTest extends ComponentTest { @Inject - private GenericDaoForceIncrementModificationTestBean testBean; + private BarDaoTxBean testBean; /** * Test of {@link GenericDao#forceIncrementModificationCounter(Object)}. Ensures that the modification counter is @@ -31,56 +29,15 @@ public class AbstractGenericDaoTest extends ComponentTest { public void testForceIncrementModificationCounter() { // given - TestEntity entity = this.testBean.create(); + BarEntity entity = this.testBean.create(); assertThat(entity.getId()).isNotNull(); assertThat(entity.getModificationCounter()).isEqualTo(0); // when - TestEntity updatedEntity = this.testBean.incrementModificationCounter(entity.getId()); + BarEntity updatedEntity = this.testBean.incrementModificationCounter(entity.getId()); // then assertThat(updatedEntity.getModificationCounter()).isEqualTo(1); } - /** - * This type provides methods in a transactional environment for the containing test class. All methods, annotated - * with the {@link Transactional} annotation, are executed in separate transaction, thus one test case can execute - * multiple transactions. Unfortunately this does not work when the transactional methods are directly in the - * top-level class of the test-case itself. - */ - @Named - public static class GenericDaoForceIncrementModificationTestBean { - - @Inject - private TestDao genericDao; - - /** - * Creates a new {@link TestEntity}, persist it and surround everything with a transaction. - * - * @return entity the new {@link TestEntity}. - */ - @Transactional - public TestEntity create() { - - TestEntity entity = new TestEntity(); - this.genericDao.save(entity); - return entity; - } - - /** - * Loads the {@link TestEntity} with the given {@code id} and - * {@link GenericDao#forceIncrementModificationCounter(Object) increments the modification counter}. - * - * @param id of the {@link TestEntity} to load and increment. - * @return entity the updated {@link TestEntity}. - */ - @Transactional - public TestEntity incrementModificationCounter(long id) { - - TestEntity entity = this.genericDao.find(id); - this.genericDao.forceIncrementModificationCounter(entity); - return entity; - } - } - }; diff --git a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestDao.java b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestDao.java deleted file mode 100644 index 357912971..000000000 --- a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestDao.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.oasp.module.jpa.dataaccess.base; - -import io.oasp.module.jpa.dataaccess.api.Dao; - -/** - * Test interface for {@link AbstractGenericDaoTest}. - * - */ -public interface TestDao extends Dao { - -} diff --git a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestDaoImpl.java b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestDaoImpl.java deleted file mode 100644 index 212bc52ef..000000000 --- a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestDaoImpl.java +++ /dev/null @@ -1,18 +0,0 @@ -package io.oasp.module.jpa.dataaccess.base; - -import javax.inject.Named; - -/** - * Implementation of {@link TestDao}. - * - */ -@Named -public class TestDaoImpl extends AbstractDao implements TestDao { - - @Override - protected Class getEntityClass() { - - return TestEntity.class; - } - -} diff --git a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestEntity.java b/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestEntity.java deleted file mode 100644 index ab5c27fdf..000000000 --- a/modules/jpa-dao/src/test/java/io/oasp/module/jpa/dataaccess/base/TestEntity.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.oasp.module.jpa.dataaccess.base; - -import javax.persistence.Entity; - -/** - * This is a test entity for {@link AbstractGenericDaoTest}. - * - */ -@Entity -public class TestEntity extends AbstractPersistenceEntity { - - private static final long serialVersionUID = 1L; - -} diff --git a/modules/jpa-dao/src/test/resources/config/app/application-default.properties b/modules/jpa-dao/src/test/resources/config/app/application-default.properties deleted file mode 100644 index 42b1d4ed5..000000000 --- a/modules/jpa-dao/src/test/resources/config/app/application-default.properties +++ /dev/null @@ -1,13 +0,0 @@ -# --------------------------------------------------------------------------- -# Persistence and database -# --------------------------------------------------------------------------- -database.user.login = sa -database.user.password = -database.url = jdbc:h2:./target/.sample;INIT=create schema if not exists public -database.hibernate.dialect = org.hibernate.dialect.H2Dialect -database.datasource = org.h2.jdbcx.JdbcDataSource -database.hibernate.hbm2ddl.auto = create -database.hibernate.show.sql = false - -# Activate spring profiles -#spring.profiles.active = integrationTest diff --git a/modules/jpa-dao/src/test/resources/config/app/common/beans-common.xml b/modules/jpa-dao/src/test/resources/config/app/common/beans-common.xml deleted file mode 100644 index d42fe16f0..000000000 --- a/modules/jpa-dao/src/test/resources/config/app/common/beans-common.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - classpath:/config/app/application-default.properties - - - - - - diff --git a/modules/jpa-dao/src/test/resources/config/app/common/dozer-mapping.xml b/modules/jpa-dao/src/test/resources/config/app/common/dozer-mapping.xml new file mode 100644 index 000000000..ecbcaf531 --- /dev/null +++ b/modules/jpa-dao/src/test/resources/config/app/common/dozer-mapping.xml @@ -0,0 +1,31 @@ + + + + + + true + + + java.lang.Long + java.lang.Integer + java.lang.Number + io.oasp.module.basic.common.api.reference.IdRef + + + + + + io.oasp.example.general.dataaccess.api.TestApplicationPersistenceEntity + io.oasp.module.basic.common.api.to.AbstractEto + + this + persistentEntity + + + diff --git a/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-dataaccess.xml b/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-dataaccess.xml deleted file mode 100644 index 2368bf945..000000000 --- a/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-dataaccess.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - diff --git a/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-db-plain.xml b/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-db-plain.xml deleted file mode 100644 index 72c85cf37..000000000 --- a/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-db-plain.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-jpa.xml b/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-jpa.xml deleted file mode 100644 index bb51ac146..000000000 --- a/modules/jpa-dao/src/test/resources/config/app/dataaccess/beans-jpa.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - io.oasp.module.jpa.dataaccess - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/modules/jpa/src/test/resources/config/app/common/dozer-mapping.xml b/modules/jpa/src/test/resources/config/app/common/dozer-mapping.xml new file mode 100644 index 000000000..ecbcaf531 --- /dev/null +++ b/modules/jpa/src/test/resources/config/app/common/dozer-mapping.xml @@ -0,0 +1,31 @@ + + + + + + true + + + java.lang.Long + java.lang.Integer + java.lang.Number + io.oasp.module.basic.common.api.reference.IdRef + + + + + + io.oasp.example.general.dataaccess.api.TestApplicationPersistenceEntity + io.oasp.module.basic.common.api.to.AbstractEto + + this + persistentEntity + + + diff --git a/modules/pom.xml b/modules/pom.xml index d6011e914..36fec2956 100644 --- a/modules/pom.xml +++ b/modules/pom.xml @@ -32,6 +32,7 @@ jpa-dao jpa-envers jpa-spring-data + test-jpa batch web basic diff --git a/modules/test-jpa/pom.xml b/modules/test-jpa/pom.xml new file mode 100644 index 000000000..6ec93588e --- /dev/null +++ b/modules/test-jpa/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + + io.oasp.java.dev + oasp4j-modules + dev-SNAPSHOT + + io.oasp.java.modules + oasp4j-test-jpa + ${oasp4j.version} + ${project.artifactId} + Module with code and configuration for JPA/DB tests of the Open Application Standard Platform for Java (OASP4J). + + + + io.oasp.java.modules + oasp4j-test + + + io.oasp.java.modules + oasp4j-jpa + + + org.flywaydb + flyway-core + + + \ No newline at end of file diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/ComponentDbTest.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/ComponentDbTest.java new file mode 100644 index 000000000..61dc44e15 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/ComponentDbTest.java @@ -0,0 +1,76 @@ +package io.oasp.module.test.common.base; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.transaction.TransactionalTestExecutionListener; + +import io.oasp.module.test.common.api.category.CategoryComponentTest; +import io.oasp.module.test.common.base.clean.TestCleaner; + +/** + * Combination of {@link DbTest} with {@link ComponentTest}. + */ +@RunWith(SpringRunner.class) +@TestExecutionListeners({ TransactionalTestExecutionListener.class, DependencyInjectionTestExecutionListener.class }) +@Category(CategoryComponentTest.class) +public abstract class ComponentDbTest extends DbTest { + + @PersistenceContext + private EntityManager entityManager; + + @Inject + private TestCleaner testUtility; + + @Override + protected void doSetUp() { + + super.doSetUp(); + if (isInitialSetup()) { + JpaTestInitializer.setJpaEntityManager(this.entityManager); + } + if (isPerformCleanup()) { + if (isAllowMultiCleanup() || isInitialSetup()) { + this.testUtility.cleanup(); + } + } + } + + /** + * @return {@code true} if {@link TestCleaner#cleanup()} should be enabled, {@code false} otherwise. Override to + * change behavior for your test. + */ + @Override + protected boolean isPerformCleanup() { + + return true; + } + + /** + * @return {@code true} to allow that {@link TestCleaner#cleanup()} is invoked for each test-method of your test + * (potentially multiple times), {@code false} to ensure that {@link TestCleaner#cleanup()} is never invoked + * multiple times per test class (e.g. to speed up read-only tests that will never have side-effects). Will be + * ignored if {@link #isPerformCleanup()} returns {@code false}. Override to change behavior for your test. + */ + @Override + protected boolean isAllowMultiCleanup() { + + return true; + } + + /** + * @return the instance of {@link TestCleaner}. + */ + @Override + public TestCleaner getTestUtility() { + + return this.testUtility; + } + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/DbTest.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/DbTest.java new file mode 100644 index 000000000..dfd30bc73 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/DbTest.java @@ -0,0 +1,68 @@ +package io.oasp.module.test.common.base; + +import javax.inject.Inject; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +import io.oasp.module.test.common.base.clean.TestCleaner; + +/** + * Extends {@link BaseTest} with the following features: + *

    + *
  • Automatically register the {@link EntityManager} of the current spring-test. See {@link JpaTestInitializer} for + * further details.
  • + *
  • Automatically performs {@link TestCleaner#cleanup() cleanup} to prevent side-effects. Can be configured by + * overriding {@link #isPerformCleanup()} and {@link #isAllowMultiCleanup()}.
  • + *
+ */ +public abstract class DbTest extends BaseTest { + + @PersistenceContext + private EntityManager entityManager; + + @Inject + private TestCleaner testUtility; + + @Override + protected void doSetUp() { + + super.doSetUp(); + if (isInitialSetup()) { + JpaTestInitializer.setJpaEntityManager(this.entityManager); + } + if (isPerformCleanup()) { + if (isAllowMultiCleanup() || isInitialSetup()) { + this.testUtility.cleanup(); + } + } + } + + /** + * @return {@code true} if {@link TestCleaner#cleanup()} should be enabled, {@code false} otherwise. Override to + * change behavior for your test. + */ + protected boolean isPerformCleanup() { + + return true; + } + + /** + * @return {@code true} to allow that {@link TestCleaner#cleanup()} is invoked for each test-method of your test + * (potentially multiple times), {@code false} to ensure that {@link TestCleaner#cleanup()} is never invoked + * multiple times per test class (e.g. to speed up read-only tests that will never have side-effects). Will be + * ignored if {@link #isPerformCleanup()} returns {@code false}. Override to change behavior for your test. + */ + protected boolean isAllowMultiCleanup() { + + return true; + } + + /** + * @return the instance of {@link TestCleaner}. + */ + public TestCleaner getTestUtility() { + + return this.testUtility; + } + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/JpaTestInitializer.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/JpaTestInitializer.java new file mode 100644 index 000000000..84f87b023 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/JpaTestInitializer.java @@ -0,0 +1,37 @@ +package io.oasp.module.test.common.base; + +import javax.persistence.EntityManager; + +import io.oasp.module.jpa.dataaccess.api.JpaInitializer; + +/** + * Helper class giving access to {@link #setJpaEntityManager(EntityManager) set} the {@link EntityManager} for + * tests.
+ * The {@code spring-test} infrastructure is very powerful and does a lot of magic for you. However, in some edge cases + * you need to understand what is going on behind the scenes to workaround some problems. On case is the regular + * {@link io.oasp.module.jpa.dataaccess.api.JpaInitializer} that initializes the {@link EntityManager} during the + * bootstrapping of the spring context and makes it internally available to static methods (e.g. + * {@link io.oasp.module.jpa.dataaccess.api.JpaHelper#asEntity(io.oasp.module.basic.common.api.reference.Ref, Class)}). + * However, {@code spring-test} internally reuses the spring context to boost performance if multiple spring tests are + * run using the same context. However, if then another spring test runs with a different context then that spring + * context will be setup overwriting the static instance of {@link EntityManager}. Still everything works as expected. + * But now if another spring-test is executed using a previous configuration that previous spring context will be + * magically reused by {@code spring-test}. In such case the static instance of {@link EntityManager} has to be set back + * to the {@link EntityManager} of the current spring context otherwise you will get strange errors in your tests. In + * order to archive this goal you need to inject the {@link EntityManager} into each of your spring tests and pass it + * into the {@link #setJpaEntityManager(EntityManager) method} offered here. To simplify your life you can simply derive + * from {@link SubsystemDbTest}, + */ +public class JpaTestInitializer extends JpaInitializer { + + private static final JpaTestInitializer INSTANCE = new JpaTestInitializer(); + + /** + * @param entityManager the {@link EntityManager} to set. + */ + public static final void setJpaEntityManager(EntityManager entityManager) { + + INSTANCE.setEntityManager(entityManager, false); + } + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/SubsystemDbTest.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/SubsystemDbTest.java new file mode 100644 index 000000000..16c389eeb --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/SubsystemDbTest.java @@ -0,0 +1,20 @@ +package io.oasp.module.test.common.base; + +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.transaction.TransactionalTestExecutionListener; + +import io.oasp.module.test.common.api.category.CategorySubsystemTest; + +/** + * Combination of {@link DbTest} with {@link SubsystemTest}. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@TestExecutionListeners({ TransactionalTestExecutionListener.class, DependencyInjectionTestExecutionListener.class }) +@Category(CategorySubsystemTest.class) +public abstract class SubsystemDbTest extends DbTest { + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/SystemDbTest.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/SystemDbTest.java new file mode 100644 index 000000000..1aa445a2f --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/SystemDbTest.java @@ -0,0 +1,13 @@ +package io.oasp.module.test.common.base; + +import org.junit.experimental.categories.Category; + +import io.oasp.module.test.common.api.category.CategorySystemTest; + +/** + * Combination of {@link DbTest} with {@link SystemTest}. + */ +@Category(CategorySystemTest.class) +public class SystemDbTest extends DbTest { + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/AbstractTestCleaner.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/AbstractTestCleaner.java new file mode 100644 index 000000000..67dd8bb41 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/AbstractTestCleaner.java @@ -0,0 +1,14 @@ +package io.oasp.module.test.common.base.clean; + +/** + * Interface providing ability to {@link #cleanup() cleanup}. + */ +public abstract interface AbstractTestCleaner { + + /** + * Performs a cleanup of contextual data. E.g. it may rest the database so according side-effects from previous tests + * are eliminated. + */ + void cleanup(); + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleaner.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleaner.java new file mode 100644 index 000000000..1534bf372 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleaner.java @@ -0,0 +1,8 @@ +package io.oasp.module.test.common.base.clean; + +/** + * Interface for the central component performing all {@link #cleanup() cleanups}. + */ +public interface TestCleaner extends AbstractTestCleaner { + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerImpl.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerImpl.java new file mode 100644 index 000000000..b8de3ec9e --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerImpl.java @@ -0,0 +1,31 @@ +package io.oasp.module.test.common.base.clean; + +import java.util.List; + +import javax.inject.Inject; + +/** + * Implementation of {@link TestCleaner}. Simply executes all {@link TestCleanerPlugin}s on {@link #cleanup()}. + */ +public class TestCleanerImpl implements TestCleaner { + + private List plugins; + + /** + * @param plugins the {@link List} of {@link TestCleanerPlugin}s to {@link Inject}. + */ + @Inject + public void setPlugins(List plugins) { + + this.plugins = plugins; + } + + @Override + public void cleanup() { + + for (TestCleanerPlugin plugin : this.plugins) { + plugin.cleanup(); + } + } + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPlugin.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPlugin.java new file mode 100644 index 000000000..e1bba5006 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPlugin.java @@ -0,0 +1,8 @@ +package io.oasp.module.test.common.base.clean; + +/** + * Interface for a single "plugin" of {@link TestCleanerImpl} performing all {@link #cleanup() cleanups}. + */ +public interface TestCleanerPlugin extends AbstractTestCleaner { + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPluginFlyway.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPluginFlyway.java new file mode 100644 index 000000000..ff099d760 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPluginFlyway.java @@ -0,0 +1,43 @@ +package io.oasp.module.test.common.base.clean; + +import javax.inject.Inject; + +import org.flywaydb.core.Flyway; + +/** + * Implementation of {@link TestCleanerPlugin} base on {@link Flyway}. It will {@link Flyway#clean() clean} and + * {@link Flyway#migrate() migrate} on {@link #cleanup()}. Therefore after {@link #cleanup()} only DDL and master-data + * will be left in the database. + */ +public class TestCleanerPluginFlyway implements TestCleanerPlugin { + + @Inject + private Flyway flyway; + + /** + * The constructor. + */ + public TestCleanerPluginFlyway() { + + super(); + } + + /** + * The constructor. + * + * @param flyway the {@link Flyway} instance. + */ + public TestCleanerPluginFlyway(Flyway flyway) { + + super(); + this.flyway = flyway; + } + + @Override + public void cleanup() { + + this.flyway.clean(); + this.flyway.migrate(); + } + +} diff --git a/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPluginNone.java b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPluginNone.java new file mode 100644 index 000000000..68d662988 --- /dev/null +++ b/modules/test-jpa/src/main/java/io/oasp/module/test/common/base/clean/TestCleanerPluginNone.java @@ -0,0 +1,16 @@ +package io.oasp.module.test.common.base.clean; + +/** + * Implementation of {@link TestCleanerPlugin} that simply does nothing. May be used to satisfy injections as spring + * would throw an exception if no {@link TestCleanerPlugin} is available at all for injection into + * {@link TestCleanerImpl}. + */ +public class TestCleanerPluginNone implements TestCleanerPlugin { + + @Override + public void cleanup() { + + // do nothing by design + } + +} diff --git a/modules/test/pom.xml b/modules/test/pom.xml index 8f9acd4b9..d6ba121df 100644 --- a/modules/test/pom.xml +++ b/modules/test/pom.xml @@ -17,7 +17,7 @@ junit junit - + org.springframework.boot spring-boot-starter-test diff --git a/modules/test/src/main/java/io/oasp/module/test/common/base/BaseTest.java b/modules/test/src/main/java/io/oasp/module/test/common/base/BaseTest.java index f9e8faf58..b59070752 100644 --- a/modules/test/src/main/java/io/oasp/module/test/common/base/BaseTest.java +++ b/modules/test/src/main/java/io/oasp/module/test/common/base/BaseTest.java @@ -31,6 +31,11 @@ * } * * + * Additional value is provided by {@link #isInitialSetup()} that may be helpful for specific implementations of + * {@link #doSetUp()} where you want to do some cleanup only once for the test-class rather than for every test method. + * Unlike {@link org.junit.BeforeClass} this can be used in non-static method code so you have access to injected + * dependencies. + * * @author shuber, jmolinar */ public abstract class BaseTest extends Assertions { diff --git a/modules/test/src/main/java/io/oasp/module/test/common/base/ComponentTest.java b/modules/test/src/main/java/io/oasp/module/test/common/base/ComponentTest.java index b0340030e..d4f9e019e 100644 --- a/modules/test/src/main/java/io/oasp/module/test/common/base/ComponentTest.java +++ b/modules/test/src/main/java/io/oasp/module/test/common/base/ComponentTest.java @@ -15,7 +15,6 @@ * extend this class. * * @see CategoryComponentTest - * */ @RunWith(SpringRunner.class) @TestExecutionListeners({ TransactionalTestExecutionListener.class, DependencyInjectionTestExecutionListener.class }) diff --git a/modules/test/src/main/java/io/oasp/module/test/common/base/SubsystemTest.java b/modules/test/src/main/java/io/oasp/module/test/common/base/SubsystemTest.java index 0e0a12897..893eeea57 100644 --- a/modules/test/src/main/java/io/oasp/module/test/common/base/SubsystemTest.java +++ b/modules/test/src/main/java/io/oasp/module/test/common/base/SubsystemTest.java @@ -10,12 +10,20 @@ import io.oasp.module.test.common.api.category.CategorySubsystemTest; /** - * This is the abstract base class for an integration test. You are free to create your integration tests as you like - * just by annotating {@link CategorySubsystemTest} using {@link Category}. However, in most cases it will be convenient - * just to extend this class. + * This is the abstract base class for an integrative test of a sub-system (e.g. your application backend). You are free + * to create your integration tests as you like just by annotating {@link CategorySubsystemTest} using {@link Category}. + * However, in most cases it will be convenient just to extend this class. Also we recommend to use the {@code spring} + * framework and utilize {@code spring-boot-test}. In such case create an abstract base-class for the + * {@link SubsystemTest}s of your application as following: * - * @see CategorySubsystemTest + *
+ * @{@link org.springframework.boot.test.context.SpringBootTest} (webEnvironment =
+ * {@link org.springframework.boot.test.context.SpringBootTest.WebEnvironment#RANDOM_PORT}, classes =
+ * MyApplication.class) public abstract class MyApplicationSubsystemTest {
+ * }
+ * 
* + * @see CategorySubsystemTest */ @RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners({ TransactionalTestExecutionListener.class, DependencyInjectionTestExecutionListener.class }) diff --git a/modules/test/src/main/java/io/oasp/module/test/common/base/SystemTest.java b/modules/test/src/main/java/io/oasp/module/test/common/base/SystemTest.java index eb523fabc..969a1c096 100644 --- a/modules/test/src/main/java/io/oasp/module/test/common/base/SystemTest.java +++ b/modules/test/src/main/java/io/oasp/module/test/common/base/SystemTest.java @@ -10,7 +10,6 @@ * extend this class. * * @see CategorySystemTest - * */ @Category(CategorySystemTest.class) public abstract class SystemTest extends BaseTest {