Coverage Report - org.owasp.dependencycheck.data.nvdcve.ConnectionFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
ConnectionFactory
44%
42/95
34%
9/26
5.286
 
 1  
 /*
 2  
  * This file is part of dependency-check-core.
 3  
  *
 4  
  * Dependency-check-core is free software: you can redistribute it and/or modify it
 5  
  * under the terms of the GNU General Public License as published by the Free
 6  
  * Software Foundation, either version 3 of the License, or (at your option) any
 7  
  * later version.
 8  
  *
 9  
  * Dependency-check-core is distributed in the hope that it will be useful, but
 10  
  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  
  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 12  
  * details.
 13  
  *
 14  
  * You should have received a copy of the GNU General Public License along with
 15  
  * dependency-check-core. If not, see http://www.gnu.org/licenses/.
 16  
  *
 17  
  * Copyright (c) 2014 Jeremy Long. All Rights Reserved.
 18  
  */
 19  
 package org.owasp.dependencycheck.data.nvdcve;
 20  
 
 21  
 import java.io.BufferedReader;
 22  
 import java.io.File;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.io.InputStreamReader;
 26  
 import java.sql.CallableStatement;
 27  
 import java.sql.Connection;
 28  
 import java.sql.DriverManager;
 29  
 import java.sql.ResultSet;
 30  
 import java.sql.SQLException;
 31  
 import java.sql.Statement;
 32  
 import java.util.logging.Level;
 33  
 import java.util.logging.Logger;
 34  
 import org.owasp.dependencycheck.utils.DBUtils;
 35  
 import org.owasp.dependencycheck.utils.Settings;
 36  
 
 37  
 /**
 38  
  * Loads the configured database driver and returns the database connection. If the embedded H2 database is used
 39  
  * obtaining a connection will ensure the database file exists and that the appropriate table structure has been
 40  
  * created.
 41  
  *
 42  
  * @author Jeremy Long <jeremy.long@owasp.org>
 43  
  */
 44  
 public final class ConnectionFactory {
 45  
 
 46  
     /**
 47  
      * The version of the current DB Schema.
 48  
      */
 49  
     public static final String DB_SCHEMA_VERSION = "2.8";
 50  
     /**
 51  
      * Resource location for SQL file used to create the database schema.
 52  
      */
 53  
     public static final String DB_STRUCTURE_RESOURCE = "data/initialize.sql";
 54  
 
 55  
     /**
 56  
      * Private constructor for this factory class; no instance is ever needed.
 57  
      */
 58  0
     private ConnectionFactory() {
 59  0
     }
 60  
 
 61  
     /**
 62  
      * Constructs a new database connection object per the database configuration. This will load the appropriate
 63  
      * database driver, via the DriverManager, if configured.
 64  
      *
 65  
      * @return a database connection object
 66  
      * @throws DatabaseException thrown if there is an exception loading the database connection
 67  
      */
 68  
     public static Connection getConnection() throws DatabaseException {
 69  48
         Connection conn = null;
 70  
         try {
 71  48
             Logger.getLogger(CveDB.class.getName()).log(Level.FINE, "Loading database connection");
 72  
 
 73  48
             final String connStr = getConnectionString();
 74  48
             final String user = Settings.getString(Settings.KEYS.DB_USER, "dcuser");
 75  
             //yes, yes - hard-coded password - only if there isn't one in the properties file.
 76  48
             final String pass = Settings.getString(Settings.KEYS.DB_PASSWORD, "DC-Pass1337!");
 77  48
             Logger.getLogger(CveDB.class.getName()).log(Level.FINE, "Connection String: {0}", connStr);
 78  48
             Logger.getLogger(CveDB.class.getName()).log(Level.FINE, "Database User: {0}", user);
 79  48
             boolean createTables = false;
 80  48
             if (connStr.startsWith("jdbc:h2:file:")) { //H2
 81  48
                 createTables = needToCreateDatabaseStructure();
 82  48
                 Logger.getLogger(CveDB.class.getName()).log(Level.FINE, "Need to create DB Structure: {0}", createTables);
 83  
             }
 84  48
             final String driverName = Settings.getString(Settings.KEYS.DB_DRIVER_NAME, "");
 85  48
             if (!driverName.isEmpty()) { //likely need to load the correct driver
 86  0
                 Logger.getLogger(CveDB.class.getName()).log(Level.FINE, "Loading driver: {0}", driverName);
 87  0
                 final String driverPath = Settings.getString(Settings.KEYS.DB_DRIVER_PATH, "");
 88  0
                 if (!driverPath.isEmpty()) { //ugh, driver is not on classpath?
 89  0
                     Logger.getLogger(CveDB.class.getName()).log(Level.FINE, "Loading driver from: {0}", driverPath);
 90  0
                     DriverLoader.load(driverName, driverPath);
 91  
                 } else {
 92  0
                     DriverLoader.load(driverName);
 93  
                 }
 94  
             }
 95  
 
 96  
             //JDBC4 drivers don't need this call.
 97  
             //Class.forName("org.h2.Driver");
 98  48
             conn = DriverManager.getConnection(connStr, user, pass);
 99  48
             if (createTables) {
 100  0
                 createTables(conn);
 101  
             } else {
 102  48
                 ensureSchemaVersion(conn);
 103  
             }
 104  0
         } catch (IOException ex) {
 105  0
             Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, null, ex);
 106  0
             throw new DatabaseException("Unable to load database");
 107  0
         } catch (DriverLoadException ex) {
 108  0
             Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, null, ex);
 109  0
             throw new DatabaseException("Unable to load database driver");
 110  0
         } catch (SQLException ex) {
 111  0
             Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, null, ex);
 112  0
             throw new DatabaseException("Unable to connect to the database");
 113  0
         } catch (DatabaseException ex) {
 114  0
             Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, null, ex);
 115  0
             throw new DatabaseException("Unable to create the database structure");
 116  48
         }
 117  48
         return conn;
 118  
     }
 119  
 
 120  
     /**
 121  
      * Returns the configured connection string. If using the embedded H2 database this function will also ensure the
 122  
      * data directory exists and if not create it.
 123  
      *
 124  
      * @return the connection string
 125  
      * @throws IOException thrown the data directory cannot be created
 126  
      */
 127  
     private static String getConnectionString() throws IOException {
 128  48
         final String connStr = Settings.getString(Settings.KEYS.DB_CONNECTION_STRING, "jdbc:h2:file:%s;AUTO_SERVER=TRUE");
 129  48
         if (connStr.contains("%s")) {
 130  48
             final String fileName = getDataDirectory().getCanonicalPath();
 131  48
             final File file = new File(fileName, "cve." + DB_SCHEMA_VERSION);
 132  48
             return String.format(connStr, file.getAbsolutePath());
 133  
         }
 134  0
         return connStr;
 135  
     }
 136  
 
 137  
     /**
 138  
      * Retrieves the directory that the JAR file exists in so that we can ensure we always use a common data directory
 139  
      * for the embedded H2 database. This is public solely for some unit tests; otherwise this should be private.
 140  
      *
 141  
      * @return the data directory to store data files
 142  
      * @throws IOException is thrown if an IOException occurs of course...
 143  
      */
 144  
     public static File getDataDirectory() throws IOException {
 145  96
         final File path = Settings.getDataFile(Settings.KEYS.DATA_DIRECTORY);
 146  96
         if (!path.exists()) {
 147  0
             if (!path.mkdirs()) {
 148  0
                 throw new IOException("Unable to create NVD CVE Data directory");
 149  
             }
 150  
         }
 151  96
         return path;
 152  
     }
 153  
 
 154  
     /**
 155  
      * Determines if the H2 database file exists. If it does not exist then the data structure will need to be created.
 156  
      *
 157  
      * @return true if the H2 database file does not exist; otherwise false
 158  
      * @throws IOException thrown if the data directory does not exist and cannot be created
 159  
      */
 160  
     private static boolean needToCreateDatabaseStructure() throws IOException {
 161  48
         final File dir = getDataDirectory();
 162  48
         final String name = String.format("cve.%s.h2.db", DB_SCHEMA_VERSION);
 163  48
         final File file = new File(dir, name);
 164  48
         return !file.exists();
 165  
     }
 166  
 
 167  
     /**
 168  
      * Creates the database structure (tables and indexes) to store the CVE data.
 169  
      *
 170  
      * @param conn the database connection
 171  
      * @throws DatabaseException thrown if there is a Database Exception
 172  
      */
 173  
     private static void createTables(Connection conn) throws DatabaseException {
 174  0
         Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, "Creating database structure");
 175  
         InputStream is;
 176  
         InputStreamReader reader;
 177  0
         BufferedReader in = null;
 178  
         try {
 179  0
             is = ConnectionFactory.class.getClassLoader().getResourceAsStream(DB_STRUCTURE_RESOURCE);
 180  0
             reader = new InputStreamReader(is, "UTF-8");
 181  0
             in = new BufferedReader(reader);
 182  0
             final StringBuilder sb = new StringBuilder(2110);
 183  
             String tmp;
 184  0
             while ((tmp = in.readLine()) != null) {
 185  0
                 sb.append(tmp);
 186  
             }
 187  0
             Statement statement = null;
 188  
             try {
 189  0
                 statement = conn.createStatement();
 190  0
                 statement.execute(sb.toString());
 191  0
             } catch (SQLException ex) {
 192  0
                 Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, null, ex);
 193  0
                 throw new DatabaseException("Unable to create database statement", ex);
 194  
             } finally {
 195  0
                 DBUtils.closeStatement(statement);
 196  0
             }
 197  0
         } catch (IOException ex) {
 198  0
             throw new DatabaseException("Unable to create database schema", ex);
 199  
         } finally {
 200  0
             if (in != null) {
 201  
                 try {
 202  0
                     in.close();
 203  0
                 } catch (IOException ex) {
 204  0
                     Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINEST, null, ex);
 205  0
                 }
 206  
             }
 207  
         }
 208  0
     }
 209  
 
 210  
     /**
 211  
      * Uses the provided connection to check the specified schema version within the database.
 212  
      *
 213  
      * @param conn the database connection object
 214  
      * @throws DatabaseException thrown if the schema version is not compatible with this version of dependency-check
 215  
      */
 216  
     private static void ensureSchemaVersion(Connection conn) throws DatabaseException {
 217  48
         ResultSet rs = null;
 218  48
         CallableStatement cs = null;
 219  
         try {
 220  48
             cs = conn.prepareCall("SELECT value FROM properties WHERE id = 'version'");
 221  48
             rs = cs.executeQuery();
 222  48
             if (rs.next()) {
 223  48
                 final boolean isWrongSchema = !DB_SCHEMA_VERSION.equals(rs.getString(1));
 224  48
                 if (isWrongSchema) {
 225  0
                     throw new DatabaseException("Incorrect database schema; unable to continue");
 226  
                 }
 227  48
             } else {
 228  0
                 throw new DatabaseException("Database schema is missing");
 229  
             }
 230  0
         } catch (SQLException ex) {
 231  0
             Logger.getLogger(ConnectionFactory.class.getName()).log(Level.FINE, null, ex);
 232  0
             throw new DatabaseException("Unable to check the database schema version");
 233  
         } finally {
 234  48
             DBUtils.closeResultSet(rs);
 235  48
             DBUtils.closeStatement(cs);
 236  48
         }
 237  48
     }
 238  
 }