package de.superx; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.JDBCType; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.sql.DataSource; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.junit.Ignore; import org.pentaho.di.core.KettleEnvironment; import org.pentaho.di.core.logging.KettleLogStore; import org.postgresql.ds.common.PGObjectFactory; import org.postgresql.util.PGobject; import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; import org.springframework.core.convert.converter.Converter; import org.springframework.core.io.ClassPathResource; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.jdbc.core.convert.JdbcCustomConversions; import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; import org.springframework.data.relational.core.dialect.Dialect; import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.init.ScriptUtils; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.zaxxer.hikari.HikariDataSource; import de.superx.bianalysis.metadata.Identifier; import de.superx.servlet.LogInit; import de.superx.servlet.SuperXManager; import de.superx.servlet.SxPools; import de.superx.spring.TestUserServiceImpl; import de.superx.spring.batch.His1DataSources; import de.superx.spring.service.UserService; import de.superx.util.PathAndFileUtils; @Ignore @Configuration @EnableJdbcRepositories(basePackages = {"de.superx.jdbc.repository","de.superx.bianalysis.repository"} ) @ComponentScan(basePackages = "de.superx.spring.service,de.superx.rest,de.superx.bianalysis.service,de.superx.bianalysis.rest") @Import(TestAppConfig.class) public class TestApplicationConfigH2 extends AbstractJdbcConfiguration implements InitializingBean, DisposableBean{ protected static Logger logger; public static final String TEST_MANDANTEN_ID = "test"; private static String dbPath = String.join(File.separator, "test", "resources", "db") + File.separator; private static String DB_SUFFIX = ".mv.db"; private static String DB_TRACE_SUFFIX = ".trace.db"; private static String DB_TEMPLATE = "head_eduetl"; private long startThreadId; @Bean public UserService userService() { return new TestUserServiceImpl(); } @Bean DataSource dataSource() throws Exception { HikariDataSource ds = new HikariDataSource(); ds.setJdbcUrl("jdbc:h2:./test/resources/db/" + getDbName() + ";MODE=POSTGRESQL;DATABASE_TO_LOWER=TRUE;NON_KEYWORDS=KEY,VALUE,YEAR;BUILTIN_ALIAS_OVERRIDE=TRUE"); ds.setUsername("sa"); ds.setPassword(""); if (!batchSchemaExists(ds)) { createBatchSchema(ds); } return ds; } @Bean His1DataSources dataSources() throws Exception { Map dataSources = new HashMap<>(); dataSources.put("eduetl", dataSource()); His1DataSources h1d = new His1DataSources(dataSources); return h1d; } @Bean NamedParameterJdbcOperations operations() throws Exception { return new NamedParameterJdbcTemplate(dataSource()); } @Bean PlatformTransactionManager transactionManager() throws Exception { return new DataSourceTransactionManager(dataSource()); } @Override @Bean public Dialect jdbcDialect(NamedParameterJdbcOperations operations) { return PostgresDialect.INSTANCE; } @Override public JdbcCustomConversions jdbcCustomConversions() { return new JdbcCustomConversions(Arrays.asList(IntegerToBooleanConverter.INSTANCE, BooleanToIntegerConverter.INSTANCE, ShortToBooleanConverter.INSTANCE, BooleanToShortConverter.INSTANCE, IdentifierToString.INSTANCE, StringToIdentifier.INSTANCE, ArrayNodeToPGobjectConverter.INSTANCE, PGobjectToArrayNodeConverter.INSTANCE )); } @ReadingConverter enum IntegerToBooleanConverter implements Converter { INSTANCE; @Override public Boolean convert(Integer source) { if (source.equals(Integer.valueOf(1))) { return Boolean.TRUE; } return Boolean.FALSE; } } @WritingConverter public enum BooleanToIntegerConverter implements Converter { INSTANCE; @Override public Integer convert(Boolean source) { Integer value =source != null && source.booleanValue() ? Integer.valueOf(1) : Integer.valueOf(0); return value; } } @ReadingConverter enum ShortToBooleanConverter implements Converter { INSTANCE; @Override public Boolean convert(Short source) { if (source.equals(Short.valueOf("1"))) { return Boolean.TRUE; } return Boolean.FALSE; } } @WritingConverter public enum BooleanToShortConverter implements Converter { INSTANCE; @Override public Short convert(Boolean source) { return (source != null && source.booleanValue()) ? Short.valueOf((short)1) : Short.valueOf((short)0); } } @WritingConverter enum IdentifierToString implements Converter { INSTANCE; @Override public String convert(Identifier id) { return id.composedId; } } @ReadingConverter enum StringToIdentifier implements Converter { INSTANCE; @Override public Identifier convert(String source) { return new Identifier(source); } } protected static boolean batchSchemaExists(DataSource dataSource) { boolean exists = false; try(Connection con = dataSource.getConnection()) { DatabaseMetaData meta = con.getMetaData(); ResultSet resultSet = meta.getTables(null, null, "batch_job_execution" , new String[] {"TABLE"}); exists = resultSet.next(); } catch (SQLException e) { throw new RuntimeException("Couldn't check for table batch_job_execution", e); } return exists; } protected static void createBatchSchema(DataSource dataSource) { try (Connection con = dataSource.getConnection()){ ScriptUtils.executeSqlScript(con, new ClassPathResource("/org/springframework/batch/core/schema-postgresql.sql")); } catch (Exception e) { e.printStackTrace(); } } protected void setupTestDb() throws IOException { FileUtils.deleteQuietly(new File(dbPath + getDbName() + DB_TRACE_SUFFIX)); FileUtils.deleteQuietly(new File(dbPath + getDbName() + DB_SUFFIX)); Path from = FileSystems.getDefault().getPath(dbPath, DB_TEMPLATE + DB_SUFFIX); Path to = FileSystems.getDefault().getPath(dbPath, getDbName() + DB_SUFFIX); Files.copy(from, to); } private String getDbName() { return DB_TEMPLATE + startThreadId; } @Override public void afterPropertiesSet() throws Exception { this.startThreadId = Thread.currentThread().getId(); System.setProperty("SX_LOG_TO_TMP", "true"); File webInfPath = new File(String.join(File.separator, "superx", "WEB-INF")); File modulePath = new File(String.join(File.separator, "superx", "WEB-INF") + File.separator + PathAndFileUtils.MODULE_PATH); SuperXManager.setWEB_INFPfad(webInfPath.getAbsolutePath()); SuperXManager.setModuleDir(modulePath.getAbsolutePath()); System.setProperty("log4j.configuration", "log4j_unittests.properties"); System.setProperty("log4j.debug", "true"); initLogging(); setupTestDb(); DataSource eduetl = dataSource(); SxPools.initTesting(eduetl); initKettleEnv(); } @Override public void destroy() throws Exception { logger.debug("Context Stop Event received."); SxPools.closeAll(); FileUtils.deleteQuietly(new File(dbPath + getDbName() + DB_TRACE_SUFFIX)); FileUtils.deleteQuietly(new File(dbPath + getDbName() + DB_SUFFIX)); System.setProperty("SX_LOG_TO_TMP", "false"); if (KettleEnvironment.isInitialized()) { KettleEnvironment.shutdown(); } } private final static void initKettleEnv() { // init kettle environment try { System.setProperty("BI_KETTLE_PLUGIN_BASE_FOLDERS", SuperXManager.getWEB_INFPfad() + File.separator + "kettle-plugins"); KettleEnvironment.init(); // init KettleLogStore KettleLogStore.init(); logger.debug("Kettle environment sucessfully initialized."); } catch (Exception e) { logger.error("Couldn't initialize KettleEnvironment!", e); } } private static void initLogging() { File propertiesFile = new File("test/conf/log4j_unittests.properties"); LogInit.initLog4J(propertiesFile, System.getProperty("java.io.tmpdir")); logger = Logger.getLogger(TestApplicationConfigH2.class); logger.info("Log4J Init complete"); } @ReadingConverter enum PGobjectToArrayNodeConverter implements Converter { INSTANCE; ObjectMapper mapper = new ObjectMapper(); @Override public ArrayNode convert(PGobject source) { if (source != null) { PGobject pgObject = source; String value = pgObject.getValue(); if (value == null || value.trim().isEmpty()) { return mapper.createArrayNode(); // Empty array instead of null } try { JsonNode node = mapper.readTree(value); // Ensure it's always an ArrayNode return node.isArray() ? (ArrayNode) node : mapper.createArrayNode(); } catch (IOException e) { throw new RuntimeException("Failed to parse JSON", e); } } return mapper.createArrayNode(); // Default empty array } } @WritingConverter enum ArrayNodeToPGobjectConverter implements Converter { INSTANCE; @Override public PGobject convert(ArrayNode source) { if (source == null) return null; PGobject pgObject = new PGobject(); pgObject.setType("jsonb"); try { pgObject.setValue(new ObjectMapper().writeValueAsString(source)); } catch (JsonProcessingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return pgObject; } } }