2 * Copyright © 2016 Red Hat, Inc. and others.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.aaa.datastore.h2;
10 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
11 import java.sql.Connection;
12 import java.sql.DatabaseMetaData;
13 import java.sql.PreparedStatement;
14 import java.sql.ResultSet;
15 import java.sql.SQLException;
16 import java.sql.Statement;
17 import java.util.ArrayList;
18 import java.util.List;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
23 * Base class for H2 stores.
25 // "Nonconstant string passed to execute or addBatch method on an SQL statement...Consider using a prepared statement
26 // instead. It is more efficient and less vulnerable to SQL injection attacks.". Possible TODO - is it worth it here to
27 // use prepared statements?
28 @SuppressFBWarnings("SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE")
29 abstract class AbstractStore<T> {
31 private static final Logger LOG = LoggerFactory.getLogger(AbstractStore.class);
34 * The name of the table used to represent this store.
36 private final String tableName;
39 * Database connection factory.
41 private final ConnectionProvider dbConnectionFactory;
44 * Table types we're interested in (when checking tables' existence).
46 public static final String[] TABLE_TYPES = new String[] { "TABLE" };
49 * Creates an instance.
51 * @param dbConnectionFactory factory to obtain JDBC Connections from
52 * @param tableName The name of the table being managed.
54 protected AbstractStore(ConnectionProvider dbConnectionFactory, String tableName) {
55 this.dbConnectionFactory = dbConnectionFactory;
56 this.tableName = tableName;
60 * Returns a database connection. It is the caller's responsibility to close it. If the managed table does not
61 * exist, it will be created (using {@link #getTableCreationStatement()}).
63 * @return A database connection.
65 * @throws StoreException if an error occurs.
67 protected Connection dbConnect() throws StoreException {
68 Connection conn = dbConnectionFactory.getConnection();
70 // Ensure table check/creation is atomic
72 DatabaseMetaData dbm = conn.getMetaData();
73 try (ResultSet rs = dbm.getTables(null, null, tableName, TABLE_TYPES)) {
75 LOG.debug("Table {} already exists", tableName);
77 LOG.info("Table {} does not exist, creating it", tableName);
78 try (Statement stmt = conn.createStatement()) {
79 stmt.executeUpdate(getTableCreationStatement());
84 } catch (SQLException e) {
85 LOG.error("Error connecting to the H2 database", e);
86 throw new StoreException("Cannot connect to database server", e);
94 * @throws StoreException if a connection error occurs.
96 public void dbClean() throws StoreException {
97 try (Connection c = dbConnect()) {
98 // The table name can't be a parameter in a prepared statement
99 String sql = "DELETE FROM " + tableName;
100 try (Statement statement = c.createStatement()) {
101 statement.execute(sql);
103 } catch (SQLException e) {
104 LOG.error("Error clearing table {}", tableName, e);
105 throw new StoreException("Error clearing table " + tableName, e);
110 * Returns the SQL code required to create the managed table.
112 * @return The SQL table creation statement.
114 protected abstract String getTableCreationStatement();
117 * Lists all the stored items.
119 * @return The stored item.
121 * @throws StoreException if an error occurs.
123 protected List<T> listAll() throws StoreException {
124 List<T> result = new ArrayList<>();
125 String query = "SELECT * FROM " + tableName;
126 try (Connection conn = dbConnect();
127 Statement stmt = conn.createStatement();
128 ResultSet rs = stmt.executeQuery(query)) {
130 result.add(fromResultSet(rs));
132 } catch (SQLException e) {
133 LOG.error("Error listing all items from {}", tableName, e);
134 throw new StoreException(e);
140 * Lists the stored items returned by the given statement.
142 * @param ps The statement (which must be ready for execution). It is the caller's responsibility to close this.
144 * @return The stored items.
146 * @throws StoreException if an error occurs.
148 protected List<T> listFromStatement(PreparedStatement ps) throws StoreException {
149 List<T> result = new ArrayList<>();
150 try (ResultSet rs = ps.executeQuery()) {
152 result.add(fromResultSet(rs));
154 } catch (SQLException e) {
155 LOG.error("Error listing matching items from {}", tableName, e);
156 throw new StoreException(e);
162 * Extracts the first item returned by the given statement, if any.
164 * @param ps The statement (which must be ready for execution). It is the caller's responsibility to close this.
166 * @return The first item, or {@code null} if none.
168 * @throws StoreException if an error occurs.
170 protected T firstFromStatement(PreparedStatement ps) throws StoreException {
171 try (ResultSet rs = ps.executeQuery()) {
173 return fromResultSet(rs);
177 } catch (SQLException e) {
178 LOG.error("Error listing first matching item from {}", tableName, e);
179 throw new StoreException(e);
184 * Converts a single row in a result set to an instance of the managed type.
186 * @param rs The result set (which is ready for extraction; {@link ResultSet#next()} must <b>not</b> be called).
188 * @return The corresponding instance.
190 * @throws SQLException if an error occurs.
192 protected abstract T fromResultSet(ResultSet rs) throws SQLException;