001/*
002 * #%L
003 * Netarchivesuite - harvester
004 * %%
005 * Copyright (C) 2005 - 2014 The Royal Danish Library, the Danish State and University Library,
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.harvester.datamodel;
025
026import static com.google.common.base.Preconditions.checkNotNull;
027
028import java.io.File;
029import java.io.FileInputStream;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.InputStreamReader;
033import java.io.LineNumberReader;
034import java.lang.reflect.Field;
035import java.sql.Connection;
036import java.sql.DriverManager;
037import java.sql.SQLException;
038import java.sql.Statement;
039
040import org.slf4j.Logger;
041import org.slf4j.LoggerFactory;
042
043import dk.netarkivet.common.CommonSettings;
044import dk.netarkivet.common.exceptions.IOFailure;
045import dk.netarkivet.common.utils.FileUtils;
046import dk.netarkivet.common.utils.Settings;
047
048/**
049 * Utilities to allow testing databases. 
050 * FIXME: Rename without Test as these are not specifically test related.
051 */
052public class DatabaseTestUtils {
053
054    protected static final Logger log = LoggerFactory.getLogger(DatabaseTestUtils.class);
055
056    private static String dburi;
057
058    /**
059     * Get access to the database stored in the given file. This will start a new transaction that will be rolled back
060     * with dropDatabase. Only one connection can be taken at a time.
061     *
062     * @param resourcePath A file that contains a test database.
063     * @param dbCreationDir
064     * 
065     */
066    public static void createDatabase(String resourcePath, String dbname, File dbCreationDir) throws Exception {
067        Settings.set(CommonSettings.DB_MACHINE, "");
068        Settings.set(CommonSettings.DB_PORT, "");
069        Settings.set(CommonSettings.DB_DIR, "");
070
071        FileUtils.removeRecursively(new File(dbCreationDir, dbname));
072
073        final String dbfile = dbCreationDir + "/" + dbname;
074
075        // FIXME: change for h2
076        dburi = "jdbc:derby:" + dbfile;
077
078        long startTime = System.currentTimeMillis();
079
080        try (Connection c = DriverManager.getConnection(dburi + ";create=true");){
081            c.setAutoCommit(false); // Do not commit individual .
082            // locate create script first, next to resource
083            File createFile = new File(new File(resourcePath).getParentFile(), "create.sql");
084            applyStatementsInInputStream(c, checkNotNull(new FileInputStream(createFile), "create.sql"));
085
086            // then populate it.
087            FileInputStream is = checkNotNull(new FileInputStream(resourcePath), resourcePath);
088            applyStatementsInInputStream(c, is);
089
090            c.commit();
091        }
092
093        log.debug("Populated {} in {}(ms)", dbfile, (System.currentTimeMillis() - startTime));
094    }
095
096    private static void applyStatementsInInputStream(Connection connection, InputStream is) throws SQLException,
097            IOException {
098        Statement statement = connection.createStatement();
099
100        LineNumberReader br = new LineNumberReader(new InputStreamReader(is));
101        String s = "";
102        long count = 0;
103        try {
104            while ((s = br.readLine()) != null) {
105                log.debug(br.getLineNumber() + ": " + s);
106                if (s.trim().startsWith("#")) {
107                    // skip comments
108                } else if (s.trim().length() == 0) {
109                    // skip empty lines
110                } else {
111                    count++;
112                    {
113                        // http://apache-database.10148.n7.nabble.com/Inserting-control-characters-in-SQL-td106944.html
114                        s = s.replace("\\n", "\n");
115                    }
116                    statement.execute(s);
117                }
118            }
119        } catch (SQLException e) {
120            throw new RuntimeException("Line " + br.getLineNumber() + ": " + s, e);
121        }
122        br.close();
123        statement.close();
124        if (count == 0) {
125            throw new RuntimeException("Executed " + count + " SQL commands.");
126        }
127    }
128
129    /**
130     * Get access to the database stored in the given file. This will start a new transaction that will be rolled back
131     * with dropDatabase. Only one connection can be taken at a time.
132     *
133     * @param resourcePath A file that contains a test database.
134     * @param dbCreationDir
135     */
136    public static void createDatabase(String resourcePath, File dbCreationDir) throws Exception {
137        createDatabase(resourcePath, "derivenamefromresource", dbCreationDir);
138    }
139
140    /**
141     * Get a connection to the given sample harvest definition database and fool the HD DB connect class into thinking
142     * it should use that one.
143     *
144     * @param resourcePath Location of the sql files to create and populate the test DB.
145     * @param dbCreationDir
146     */
147    public static void createHDDB(String resourcePath, String dbname, File dbCreationDir) throws Exception {
148        createDatabase(resourcePath, dbname, dbCreationDir);
149    }
150
151    /**
152     * Drop access to the database that's currently taken.
153     */
154    public static void dropDatabase() throws Exception {
155        try {
156            final String shutdownUri = dburi + ";shutdown=true";
157            DriverManager.getConnection(shutdownUri);
158            throw new IOFailure("Failed to shut down database");
159        } catch (SQLException e) {
160            log.warn("Expected SQL-exception when shutting down database:", e);
161        }
162        // connectionPool.clear();
163        // null field instance in DBSpecifics.
164
165        // inlined to break test dependency /tra 2014-05-19
166        // Field f = ReflectUtils.getPrivateField(DBSpecifics.class,
167        // "instance");
168        Field f = DBSpecifics.class.getDeclaredField("instance");
169        f.setAccessible(true);
170
171        f.set(null, null);
172        /*
173         * for (Thread t: connectionPool.keySet()) { final Connection connection = connectionPool.get(t); if
174         * (!(connection instanceof TestDBConnection)) { throw new UnknownID("Illegal connection " + connection); } try
175         * { if (savepoints.containsKey(t)) { connection.rollback(); // connection.rollback(savepoints.get(t));
176         * savepoints.remove(t); } } catch (SQLException e) { System.out.println("Can't rollback: " + e); }
177         * connection.close(); } connectionPool.clear();
178         */
179    }
180
181    /**
182     * Drop the connection to the harvest definition database.
183     */
184    public static void dropHDDB() throws Exception {
185        dropDatabase();
186        log.debug("dropHDDB() 1");
187        HarvestDBConnection.cleanup();
188    }
189}