Bump versions to 0.14.2-SNAPSHOT
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / datastore / h2 / AbstractStore.java
1 /*
2  * Copyright © 2016 Red Hat, Inc. and others.
3  *
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
7  */
8 package org.opendaylight.aaa.datastore.h2;
9
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;
21
22 /**
23  * Base class for H2 stores.
24  */
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> {
30
31     private static final Logger LOG = LoggerFactory.getLogger(AbstractStore.class);
32
33     /**
34      * The name of the table used to represent this store.
35      */
36     private final String tableName;
37
38     /**
39      * Database connection factory.
40      */
41     private final ConnectionProvider dbConnectionFactory;
42
43     /**
44      * Table types we're interested in (when checking tables' existence).
45      */
46     public static final String[] TABLE_TYPES = new String[] { "TABLE" };
47
48     /**
49      * Creates an instance.
50      *
51      * @param dbConnectionFactory factory to obtain JDBC Connections from
52      * @param tableName The name of the table being managed.
53      */
54     protected AbstractStore(ConnectionProvider dbConnectionFactory, String tableName) {
55         this.dbConnectionFactory = dbConnectionFactory;
56         this.tableName = tableName;
57     }
58
59     /**
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()}).
62      *
63      * @return A database connection.
64      *
65      * @throws StoreException if an error occurs.
66      */
67     protected Connection dbConnect() throws StoreException {
68         Connection conn = dbConnectionFactory.getConnection();
69         try {
70             // Ensure table check/creation is atomic
71             synchronized (this) {
72                 DatabaseMetaData dbm = conn.getMetaData();
73                 try (ResultSet rs = dbm.getTables(null, null, tableName, TABLE_TYPES)) {
74                     if (rs.next()) {
75                         LOG.debug("Table {} already exists", tableName);
76                     } else {
77                         LOG.info("Table {} does not exist, creating it", tableName);
78                         try (Statement stmt = conn.createStatement()) {
79                             stmt.executeUpdate(getTableCreationStatement());
80                         }
81                     }
82                 }
83             }
84         } catch (SQLException e) {
85             LOG.error("Error connecting to the H2 database", e);
86             throw new StoreException("Cannot connect to database server", e);
87         }
88         return conn;
89     }
90
91     /**
92      * Empties the store.
93      *
94      * @throws StoreException if a connection error occurs.
95      */
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);
102             }
103         } catch (SQLException e) {
104             LOG.error("Error clearing table {}", tableName, e);
105             throw new StoreException("Error clearing table " + tableName, e);
106         }
107     }
108
109     /**
110      * Returns the SQL code required to create the managed table.
111      *
112      * @return The SQL table creation statement.
113      */
114     protected abstract String getTableCreationStatement();
115
116     /**
117      * Lists all the stored items.
118      *
119      * @return The stored item.
120      *
121      * @throws StoreException if an error occurs.
122      */
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)) {
129             while (rs.next()) {
130                 result.add(fromResultSet(rs));
131             }
132         } catch (SQLException e) {
133             LOG.error("Error listing all items from {}", tableName, e);
134             throw new StoreException(e);
135         }
136         return result;
137     }
138
139     /**
140      * Lists the stored items returned by the given statement.
141      *
142      * @param ps The statement (which must be ready for execution). It is the caller's responsibility to close this.
143      *
144      * @return The stored items.
145      *
146      * @throws StoreException if an error occurs.
147      */
148     protected List<T> listFromStatement(PreparedStatement ps) throws StoreException {
149         List<T> result = new ArrayList<>();
150         try (ResultSet rs = ps.executeQuery()) {
151             while (rs.next()) {
152                 result.add(fromResultSet(rs));
153             }
154         } catch (SQLException e) {
155             LOG.error("Error listing matching items from {}", tableName, e);
156             throw new StoreException(e);
157         }
158         return result;
159     }
160
161     /**
162      * Extracts the first item returned by the given statement, if any.
163      *
164      * @param ps The statement (which must be ready for execution). It is the caller's responsibility to close this.
165      *
166      * @return The first item, or {@code null} if none.
167      *
168      * @throws StoreException if an error occurs.
169      */
170     protected T firstFromStatement(PreparedStatement ps) throws StoreException {
171         try (ResultSet rs = ps.executeQuery()) {
172             if (rs.next()) {
173                 return fromResultSet(rs);
174             } else {
175                 return null;
176             }
177         } catch (SQLException e) {
178             LOG.error("Error listing first matching item from {}", tableName, e);
179             throw new StoreException(e);
180         }
181     }
182
183     /**
184      * Converts a single row in a result set to an instance of the managed type.
185      *
186      * @param rs The result set (which is ready for extraction; {@link ResultSet#next()} must <b>not</b> be called).
187      *
188      * @return The corresponding instance.
189      *
190      * @throws SQLException if an error occurs.
191      */
192     protected abstract T fromResultSet(ResultSet rs) throws SQLException;
193 }