ba00eb84be6ce2018d809f2e915969874f9d3570
[moon.git] /
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.h2.persistence;
9
10 import java.sql.Connection;
11 import java.sql.DatabaseMetaData;
12 import java.sql.PreparedStatement;
13 import java.sql.ResultSet;
14 import java.sql.SQLException;
15 import java.sql.Statement;
16 import java.util.ArrayList;
17 import java.util.List;
18
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * Base class for H2 stores.
24  */
25 abstract class AbstractStore<T> {
26     /**
27      * Logger.
28      */
29     private static final Logger LOG = LoggerFactory.getLogger(AbstractStore.class);
30
31     /**
32      * The name of the table used to represent this store.
33      */
34     private final String tableName;
35
36     /**
37      * Database connection, only used for tests.
38      */
39     Connection dbConnection = null;
40
41     /**
42      * Table types we're interested in (when checking tables' existence).
43      */
44     public static final String[] TABLE_TYPES = new String[] { "TABLE" };
45
46     /**
47      * Creates an instance.
48      *
49      * @param tableName The name of the table being managed.
50      */
51     protected AbstractStore(String tableName) {
52         this.tableName = tableName;
53     }
54
55     /**
56      * Returns a database connection. It is the caller's responsibility to close it. If the managed table does not
57      * exist, it will be created (using {@link #getTableCreationStatement()}).
58      *
59      * @return A database connection.
60      *
61      * @throws StoreException if an error occurs.
62      */
63     protected Connection dbConnect() throws StoreException {
64         Connection conn = H2Store.getConnection(dbConnection);
65         try {
66             // Ensure table check/creation is atomic
67             synchronized (this) {
68                 DatabaseMetaData dbm = conn.getMetaData();
69                 try (ResultSet rs = dbm.getTables(null, null, tableName, TABLE_TYPES)) {
70                     if (rs.next()) {
71                         LOG.debug("Table {} already exists", tableName);
72                     } else {
73                         LOG.info("Table {} does not exist, creating it", tableName);
74                         try (Statement stmt = conn.createStatement()) {
75                             stmt.executeUpdate(getTableCreationStatement());
76                         }
77                     }
78                 }
79             }
80         } catch (SQLException e) {
81             LOG.error("Error connecting to the H2 database", e);
82             throw new StoreException("Cannot connect to database server", e);
83         }
84         return conn;
85     }
86
87     /**
88      * Empties the store.
89      *
90      * @throws StoreException if a connection error occurs.
91      */
92     protected void dbClean() throws StoreException {
93         try (Connection c = dbConnect()) {
94             // The table name can't be a parameter in a prepared statement
95             String sql = "DELETE FROM " + tableName;
96             c.createStatement().execute(sql);
97         } catch (SQLException e) {
98             LOG.error("Error clearing table {}", tableName, e);
99             throw new StoreException("Error clearing table " + tableName, e);
100         }
101     }
102
103     /**
104      * Returns the SQL code required to create the managed table.
105      *
106      * @return The SQL table creation statement.
107      */
108     protected abstract String getTableCreationStatement();
109
110     /**
111      * Lists all the stored items.
112      *
113      * @return The stored item.
114      *
115      * @throws StoreException if an error occurs.
116      */
117     protected List<T> listAll() throws StoreException {
118         List<T> result = new ArrayList<>();
119         String query = "SELECT * FROM " + tableName;
120         try (Connection conn = dbConnect();
121              Statement stmt = conn.createStatement();
122              ResultSet rs = stmt.executeQuery(query)) {
123             while (rs.next()) {
124                 result.add(fromResultSet(rs));
125             }
126         } catch (SQLException e) {
127             LOG.error("Error listing all items from {}", tableName, e);
128             throw new StoreException(e);
129         }
130         return result;
131     }
132
133     /**
134      * Lists the stored items returned by the given statement.
135      *
136      * @param ps The statement (which must be ready for execution). It is the caller's reponsibility to close this.
137      *
138      * @return The stored items.
139      *
140      * @throws StoreException if an error occurs.
141      */
142     protected List<T> listFromStatement(PreparedStatement ps) throws StoreException {
143         List<T> result = new ArrayList<>();
144         try (ResultSet rs = ps.executeQuery()) {
145             while (rs.next()) {
146                 result.add(fromResultSet(rs));
147             }
148         } catch (SQLException e) {
149             LOG.error("Error listing matching items from {}", tableName, e);
150             throw new StoreException(e);
151         }
152         return result;
153     }
154
155     /**
156      * Extracts the first item returned by the given statement, if any.
157      *
158      * @param ps The statement (which must be ready for execution). It is the caller's reponsibility to close this.
159      *
160      * @return The first item, or {@code null} if none.
161      *
162      * @throws StoreException if an error occurs.
163      */
164     protected T firstFromStatement(PreparedStatement ps) throws StoreException {
165         try (ResultSet rs = ps.executeQuery()) {
166             if (rs.next()) {
167                 return fromResultSet(rs);
168             } else {
169                 return null;
170             }
171         } catch (SQLException e) {
172             LOG.error("Error listing first matching item from {}", tableName, e);
173             throw new StoreException(e);
174         }
175     }
176
177     /**
178      * Converts a single row in a result set to an instance of the managed type.
179      *
180      * @param rs The result set (which is ready for extraction; {@link ResultSet#next()} must <b>not</b> be called).
181      *
182      * @return The corresponding instance.
183      *
184      * @throws SQLException if an error occurs.
185      */
186     protected abstract T fromResultSet(ResultSet rs) throws SQLException;
187 }