net.riezebos.thoth.configuration.persistence.ThothDB.java Source code

Java tutorial

Introduction

Here is the source code for net.riezebos.thoth.configuration.persistence.ThothDB.java

Source

/* Copyright (c) 2016 W.T.J. Riezebos
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.riezebos.thoth.configuration.persistence;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.riezebos.thoth.configuration.Configuration;
import net.riezebos.thoth.configuration.ThothEnvironment;
import net.riezebos.thoth.configuration.persistence.dbs.DDLExecuter;
import net.riezebos.thoth.configuration.persistence.dbs.DatabaseIdiom;
import net.riezebos.thoth.configuration.persistence.dbs.DatabaseIdiomFactory;
import net.riezebos.thoth.configuration.persistence.dbs.SqlStatement;
import net.riezebos.thoth.configuration.persistence.dbs.impl.ConnectionWrapper;
import net.riezebos.thoth.configuration.persistence.dbs.impl.DDLException;
import net.riezebos.thoth.exceptions.DatabaseException;
import net.riezebos.thoth.util.ThothUtil;

public class ThothDB {
    private static final String CREATE_SCRIPT = "net/riezebos/thoth/db/create_db.ddl";
    private static final String UPGRADE_SCRIPT_PREFIX = "net/riezebos/thoth/db/upgrade_to_";

    private static final Logger LOG = LoggerFactory.getLogger(ThothDB.class);

    private ThothEnvironment thothEnvironment;

    private Map<String, String> queries = null;
    private Map<String, String> drivers = null;

    public ThothDB(ThothEnvironment thothEnvironment) {
        this.thothEnvironment = thothEnvironment;
        setupDriverClass();
    }

    public void init() throws DatabaseException {

        try {
            String databaseType = getConfiguration().getDatabaseType();

            // The following is required to make sure the DriverManager can find the Derby Embedded driver on the classpath
            // even when using a Web Context classloader provided by (for instance) Tomcat
            for (Entry<String, String> entry : drivers.entrySet())
                if (databaseType.toLowerCase().indexOf(entry.getKey()) != -1)
                    registerDriver(entry.getValue());

            setupServer();
        } catch (Exception e) {
            throw new DatabaseException(e);
        }
    }

    protected void registerDriver(String driverClassName) throws DatabaseException {

        try {
            Class<?> driverClass = Thread.currentThread().getContextClassLoader().loadClass(driverClassName);
            DriverManager.registerDriver((Driver) driverClass.newInstance());
        } catch (Exception e) {
            throw new DatabaseException(e.getMessage(), e);
        }
    }

    protected void setupServer() throws SQLException, DDLException, IOException {
        try (Connection connection = getConnection()) {
            initializeSchema(connection);
        }
    }

    public Connection getConnection() throws SQLException {
        Properties properties = new Properties();
        String databaseUrl = getConfiguration().getDatabaseUrl();
        if (isEmbedded()) {
            properties.put("databaseName", databaseUrl);
            properties.put("create", "true");
            databaseUrl = "jdbc:derby:";
        }
        properties.put("user", getConfiguration().getDatabaseUser());
        properties.put("password", getConfiguration().getDatabasePassword());

        Connection connection = DriverManager.getConnection(databaseUrl, properties);
        connection.setAutoCommit(false);
        return new ConnectionWrapper(connection);
    }

    protected boolean isEmbedded() {
        return "embedded".equalsIgnoreCase(getConfiguration().getDatabaseType());
    }

    protected void initializeSchema(Connection connection) throws SQLException, DDLException, IOException {
        DatabaseIdiom idiom = DatabaseIdiomFactory.getDatabaseIdiom(connection);
        DDLExecuter executer = new DDLExecuter(connection, idiom);
        boolean tableExists = executer.tableExists("thoth_users", getConfiguration().getDatabaseUser());
        if (!tableExists) {
            executer.execute(CREATE_SCRIPT);
            connection.commit();
        }

        performUpgrades(connection);
    }

    protected void performUpgrades(Connection connection) throws SQLException, IOException, DDLException {

        int latestVersion = determineCurrentVersion();
        int currentVersion = latestVersion;

        try (SqlStatement commentStmt = new SqlStatement(connection, getQuery("get_schema_version")); //
                ResultSet rs = commentStmt.executeQuery()) {
            if (rs.next()) {
                currentVersion = rs.getInt(1);
            }
        }

        DatabaseIdiom idiom = DatabaseIdiomFactory.getDatabaseIdiom(connection);
        DDLExecuter executer = new DDLExecuter(connection, idiom);

        for (int i = currentVersion + 1; i <= latestVersion; i++) {
            LOG.info("Upgrading schema to version " + i);
            String upgradeScript = UPGRADE_SCRIPT_PREFIX + String.format("%03d", i) + ".ddl";
            executer.execute(upgradeScript);
            connection.commit();
        }
    }

    private int determineCurrentVersion() throws IOException {
        int result = 0;
        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(CREATE_SCRIPT);
        String script = ThothUtil.readInputStream(is);
        Pattern versionPattern = Pattern.compile("thoth_version.*values.*\\'thoth\\'\\,\\s*(\\d+)\\s*\\)",
                Pattern.MULTILINE);
        Matcher matcher = versionPattern.matcher(script);
        if (matcher.find()) {
            String group = matcher.group(1);
            result = Integer.parseInt(group);
        }
        return result;
    }

    public Configuration getConfiguration() {
        return thothEnvironment.getConfiguration();
    }

    public String getQuery(String queryName) {
        if (queries == null) {
            queries = loadQueries();
        }
        String query = queries.get(queryName.toLowerCase());
        if (query == null)
            throw new IllegalArgumentException("Query " + queryName + " not defined");
        return query;
    }

    protected Map<String, String> loadQueries() {
        try {
            Map<String, String> map = new HashMap<>();
            String body = ThothUtil.readInputStream(Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("net/riezebos/thoth/db/queries.txt"));
            for (String line : body.split(";")) {
                if (!StringUtils.isBlank(line)) {
                    int idx = line.indexOf('=');
                    if (idx == -1)
                        throw new IllegalArgumentException("Line " + line + " does not contain the '=' character");
                    String name = line.substring(0, idx).trim();
                    String query = line.substring(idx + 1).trim();
                    if (map.containsKey(name))
                        throw new IllegalArgumentException("Query named " + name + " not unique");
                    map.put(name.toLowerCase(), query);
                }
            }
            return map;
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
    }

    protected void setupDriverClass() {
        Map<String, String> result = new HashMap<>();
        result.put("embedded", "org.apache.derby.jdbc.EmbeddedDriver");
        result.put("derby", "org.apache.derby.jdbc.ClientDriver");
        result.put("oracle", "oracle.jdbc.OracleDriver");
        result.put("postgr", "org.postgresql.Driver");
        result.put("h2", "org.h2.Driver");
        result.put("hsql", "org.hsqldb.jdbc.JDBCDriver");
        result.put("mysql", "com.mysql.jdbc.Driver");
        drivers = result;
    }

    public void shutdown() {
        if (isEmbedded()) {
            try {
                DriverManager.getConnection("jdbc:derby:;shutdown=true");
            } catch (SQLException e) {
                // I don't like this at all; but by design shutting down Derby will throw an java.sql.SQLException stating "Derby system shutdown."
                // So here comes the dirty code to suppress logging this as an error (because it's not an error)
                if (e.getMessage() == null || e.getMessage().toLowerCase().indexOf("derby system shutdown") == -1)
                    LOG.error(e.getMessage(), e);
            }
        }
    }
}