package sample;
import java.time.Clock;
import java.util.*;
import java.util.function.Supplier;
import javax.persistence.*;
import javax.sql.DataSource;
import org.junit.*;
import org.springframework.orm.jpa.*;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import sample.context.*;
import sample.context.Entity;
import sample.context.actor.ActorSession;
import sample.context.orm.*;
import sample.context.orm.DefaultRepository.DefaultDataSourceProperties;
import sample.model.*;
import sample.support.*;
/**
* Spring コンテナを用いない JPA のみに特化した検証用途。
* <p>model パッケージでのみ利用してください。
*/
public class EntityTestSupport {
protected Clock clock = Clock.systemDefaultZone();
protected Timestamper time;
protected BusinessDayHandler businessDay;
protected PasswordEncoder encoder;
protected ActorSession session;
protected MockDomainHelper dh;
protected EntityManagerFactory emf;
protected DefaultRepository rep;
protected PlatformTransactionManager txm;
protected DataFixtures fixtures;
/** テスト対象とするパッケージパス(通常はtargetEntitiesの定義を推奨) */
private String packageToScan = "sample";
/** テスト対象とするEntityクラス一覧 */
private List<Class<?>> targetEntities = new ArrayList<>();
@Before
public final void setup() {
setupPreset();
dh = new MockDomainHelper(clock);
time = dh.time();
session = dh.actorSession();
businessDay = new BusinessDayHandler();
businessDay.setTime(time);
encoder = new BCryptPasswordEncoder();
setupRepository();
setupDataFixtures();
before();
}
/** 設定事前処理。repインスタンス生成前 */
protected void setupPreset() {
// 各Entity検証で上書きしてください
}
/** 事前処理。repインスタンス生成後 */
protected void before() {
// 各Entity検証で上書きしてください
}
/**
* {@link #setupPreset()}内で対象Entityを指定してください。
* (targetEntitiesといずれかを設定する必要があります)
*/
protected void targetPackage(String packageToScan) {
this.packageToScan = packageToScan;
}
/**
* {@link #setupPreset()}内で対象Entityを指定してください。
* (targetPackageといずれかを設定する必要があります)
*/
protected void targetEntities(Class<?>... list) {
if (list != null) {
this.targetEntities = Arrays.asList(list);
}
}
/**
* {@link #setupPreset()}内で利用したいClockを指定してください。
*/
protected void clock(Clock clock) {
this.clock = clock;
}
/**
* {@link #before()}内でモック設定値を指定してください。
*/
protected void setting(String id, String value) {
dh.setting(id, value);
}
@After
public void cleanup() {
emf.close();
}
protected void setupRepository() {
setupEntityManagerFactory();
rep = new DefaultRepository();
rep.setDh(dh);
rep.setInterceptor(entityInterceptor());
rep.setEm(SharedEntityManagerCreator.createSharedEntityManager(emf));
}
protected void setupDataFixtures() {
fixtures = new DataFixtures();
fixtures.setTime(time);
fixtures.setBusinessDay(businessDay);
fixtures.setEncoder(encoder);
fixtures.setRep(rep);
fixtures.setTx(txm);
}
protected void setupEntityManagerFactory() {
DataSource ds = EntityTestFactory.dataSource();
DefaultDataSourceProperties props = new DefaultDataSourceProperties();
props.getJpa().setShowSql(true);
props.getJpa().getHibernate().setDdlAuto("create-drop");
if (targetEntities.isEmpty()) {
props.getJpa().setPackageToScan(packageToScan);
} else {
props.getJpa().setAnnotatedClasses(targetEntities.toArray(new Class[0]));
}
LocalContainerEntityManagerFactoryBean emfBean = props.entityManagerFactoryBean(ds);
emfBean.afterPropertiesSet();
emf = emfBean.getObject();
txm = props.transactionManager(emf);
}
private OrmInterceptor entityInterceptor() {
OrmInterceptor interceptor = new OrmInterceptor();
interceptor.setTime(time);
interceptor.setSession(session);
return interceptor;
}
/** トランザクション処理を行います。 */
protected <T> T tx(Supplier<T> callable) {
return new TransactionTemplate(txm).execute((status) -> {
T ret = callable.get();
if (ret instanceof Entity) {
ret.hashCode(); // for lazy loading
}
return ret;
});
}
protected void tx(Runnable command) {
tx(() -> {
command.run();
rep.flush();
return true;
});
}
// 簡易コンポーネントFactory
public static class EntityTestFactory {
private static Optional<DataSource> ds = Optional.empty();
static synchronized DataSource dataSource() {
return ds.orElseGet(() -> {
ds = Optional.of(createDataSource());
return ds.get();
});
}
private static DataSource createDataSource() {
OrmDataSourceProperties ds = new OrmDataSourceProperties();
ds.setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
ds.setUsername("");
ds.setPassword("");
return ds.dataSource();
}
}
}