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. //FIXME: Rename without Test as these are not specifically test related.
050 */
051public class DatabaseTestUtils {
052
053    protected static final Logger log = LoggerFactory.getLogger(DatabaseTestUtils.class);
054
055    private static String dburi;
056
057    /**
058     * Get access to the database stored in the given file. This will start a new transaction that will be rolled back
059     * with dropDatabase. Only one connection can be taken at a time.
060     *
061     * @param resourcePath A file that contains a test database.
062     * @param dbCreationDir
063     * @return a connection to the database stored in the given file
064     */
065    public static void createDatabase(String resourcePath, String dbname, File dbCreationDir) throws Exception {
066        Settings.set(CommonSettings.DB_MACHINE, "");
067        Settings.set(CommonSettings.DB_PORT, "");
068        Settings.set(CommonSettings.DB_DIR, "");
069
070        FileUtils.removeRecursively(new File(dbCreationDir, dbname));
071
072        final String dbfile = dbCreationDir + "/" + dbname;
073
074        // FIXME: change for h2
075        dburi = "jdbc:derby:" + dbfile;
076
077        long startTime = System.currentTimeMillis();
078
079        try (Connection c = DriverManager.getConnection(dburi + ";create=true");){
080            c.setAutoCommit(false); // Do not commit individual .
081            // locate create script first, next to resource
082            File createFile = new File(new File(resourcePath).getParentFile(), "create.sql");
083            applyStatementsInInputStream(c, checkNotNull(new FileInputStream(createFile), "create.sql"));
084
085            // then populate it.
086            FileInputStream is = checkNotNull(new FileInputStream(resourcePath), resourcePath);
087            applyStatementsInInputStream(c, is);
088
089            c.commit();
090        }
091
092        log.debug("Populated {} in {}(ms)", dbfile, (System.currentTimeMillis() - startTime));
093    }
094
095    private static void applyStatementsInInputStream(Connection connection, InputStream is) throws SQLException,
096            IOException {
097        Statement statement = connection.createStatement();
098
099        LineNumberReader br = new LineNumberReader(new InputStreamReader(is));
100        String s = "";
101        long count = 0;
102        try {
103            while ((s = br.readLine()) != null) {
104                log.debug(br.getLineNumber() + ": " + s);
105                if (s.trim().startsWith("#")) {
106                    // skip comments
107                } else if (s.trim().length() == 0) {
108                    // skip empty lines
109                } else {
110                    count++;
111                    {
112                        // http://apache-database.10148.n7.nabble.com/Inserting-control-characters-in-SQL-td106944.html
113                        s = s.replace("\\n", "\n");
114                    }
115                    statement.execute(s);
116                }
117            }
118        } catch (SQLException e) {
119            throw new RuntimeException("Line " + br.getLineNumber() + ": " + s, e);
120        }
121        br.close();
122        statement.close();
123        if (count == 0) {
124            throw new RuntimeException("Executed " + count + " SQL commands.");
125        }
126    }
127
128    /**
129     * Get access to the database stored in the given file. This will start a new transaction that will be rolled back
130     * with dropDatabase. Only one connection can be taken at a time.
131     *
132     * @param resourcePath A file that contains a test database.
133     * @param dbCreationDir
134     * @return a connection to the database stored in the given file
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     * @return a connection to the given sample harvest definition database
147     */
148    public static void createHDDB(String resourcePath, String dbname, File dbCreationDir) throws Exception {
149        createDatabase(resourcePath, dbname, dbCreationDir);
150    }
151
152    /**
153     * Drop access to the database that's currently taken.
154     */
155    public static void dropDatabase() throws Exception {
156        try {
157            final String shutdownUri = dburi + ";shutdown=true";
158            DriverManager.getConnection(shutdownUri);
159            throw new IOFailure("Failed to shut down database");
160        } catch (SQLException e) {
161            log.warn("Expected SQL-exception when shutting down database:", e);
162        }
163        // connectionPool.clear();
164        // null field instance in DBSpecifics.
165
166        // inlined to break test dependency /tra 2014-05-19
167        // Field f = ReflectUtils.getPrivateField(DBSpecifics.class,
168        // "instance");
169        Field f = DBSpecifics.class.getDeclaredField("instance");
170        f.setAccessible(true);
171
172        f.set(null, null);
173        /*
174         * for (Thread t: connectionPool.keySet()) { final Connection connection = connectionPool.get(t); if
175         * (!(connection instanceof TestDBConnection)) { throw new UnknownID("Illegal connection " + connection); } try
176         * { if (savepoints.containsKey(t)) { connection.rollback(); // connection.rollback(savepoints.get(t));
177         * savepoints.remove(t); } } catch (SQLException e) { System.out.println("Can't rollback: " + e); }
178         * connection.close(); } connectionPool.clear();
179         */
180    }
181
182    /**
183     * Drop the connection to the harvest definition database.
184     */
185    public static void dropHDDB() throws Exception {
186        dropDatabase();
187        log.debug("dropHDDB() 1");
188        HarvestDBConnection.cleanup();
189    }
190}