Skip to content
DataSourceConfig.java 4.89 KiB
Newer Older
Sonia Zorba's avatar
Sonia Zorba committed
/*
 * This file is part of vospace-file-service
 * Copyright (C) 2021 Istituto Nazionale di Astrofisica
 * SPDX-License-Identifier: GPL-3.0-or-later
 */
Sonia Zorba's avatar
Sonia Zorba committed
package it.inaf.ia2.transfer.persistence;

import com.opentable.db.postgres.embedded.EmbeddedPostgres;
import com.opentable.db.postgres.embedded.PgBinaryResolver;
import com.opentable.db.postgres.embedded.UncompressBundleDirectoryResolver;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.sql.DataSource;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;

/**
 * Generates a DataSource that can be used for testing DAO classes. It loads an
 * embedded Postgres database and fills it using the data from
 * vospace-transfer-service repository (folder must exists; it location can be
 * configured using the init_database_scripts_path in test.properties).
 */
@TestConfiguration
public class DataSourceConfig {

    @Value("${init_database_scripts_path}")
    private String scriptPath;

    /**
     * Using the prototype scope we are generating a different database in each
     * test.
     */
    @Bean
    @Scope("prototype")
    @Primary
    public DataSource dataSource() throws Exception {
        DataSource embeddedPostgresDS = EmbeddedPostgres.builder()
                .setPgDirectoryResolver(new UncompressBundleDirectoryResolver(new CustomPostgresBinaryResolver()))
                .start().getPostgresDatabase();

        initDatabase(embeddedPostgresDS);

        return embeddedPostgresDS;
    }

    private class CustomPostgresBinaryResolver implements PgBinaryResolver {

        /**
         * Loads specific embedded Postgres version.
         */
        @Override
        public InputStream getPgBinary(String system, String architecture) throws IOException {
            ClassPathResource resource = new ClassPathResource(String.format("postgres-%s-%s.txz", system.toLowerCase(), architecture));
            return resource.getInputStream();
        }
    }

    /**
     * Loads SQL scripts for database initialization from
     * vospace-transfer-service repo directory.
     */
    private void initDatabase(DataSource dataSource) throws Exception {
        try ( Connection conn = dataSource.getConnection()) {

            File currentDir = new File(DataSourceConfig.class.getClassLoader().getResource(".").getFile());
            File scriptDir = currentDir.toPath().resolve(scriptPath).toFile().getCanonicalFile();

            assertTrue(scriptDir.exists(), "DAO tests require " + scriptDir.getAbsolutePath() + " to exists.\n"
                    + "Please clone the repository from https://www.ict.inaf.it/gitlab/vospace/vospace-file-catalog.git");

            File[] scripts = scriptDir.listFiles(f -> f.getName().endsWith(".sql"));
            Arrays.sort(scripts); // sort alphabetically

            for (File script : scripts) {
                ByteArrayResource scriptResource = replaceDollarQuoting(script.toPath());
                ScriptUtils.executeSqlScript(conn, scriptResource);
            }

            ScriptUtils.executeSqlScript(conn, new ClassPathResource("test-data.sql"));
        }
    }

    /**
     * It seems that dollar quoting (used in UDF) is broken in JDBC. Replacing
     * it with single quotes solves the problem. We replace the quoting here
     * instead of inside the original files because dollar quoting provides a
     * better visibility.
     */
    private ByteArrayResource replaceDollarQuoting(Path sqlScriptPath) throws Exception {

        String scriptContent = Files.readString(sqlScriptPath);

        if (scriptContent.contains("$func$")) {

            String func = extractFunctionDefinition(scriptContent);

            String originalFunction = "$func$" + func + "$func$";
            String newFunction = "'" + func.replaceAll("'", "''") + "'";

            scriptContent = scriptContent.replace(originalFunction, newFunction);
        }

        return new ByteArrayResource(scriptContent.getBytes());
    }

    private String extractFunctionDefinition(String scriptContent) {
        Pattern pattern = Pattern.compile("\\$func\\$(.*?)\\$func\\$", Pattern.DOTALL);
        Matcher matcher = pattern.matcher(scriptContent);
        if (matcher.find()) {
            return matcher.group(1);
        }
        throw new IllegalArgumentException(scriptContent + " doesn't contain $func$");
    }
}