aboutsummaryrefslogtreecommitdiffstats
path: root/odl-aaa-moon/aaa/aaa-h2-store/src/main/java/org/opendaylight/aaa/h2/persistence/AbstractStore.java
blob: ba00eb84be6ce2018d809f2e915969874f9d3570 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
/*
 * Copyright © 2016 Red Hat, Inc. and others.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */
package org.opendaylight.aaa.h2.persistence;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base class for H2 stores.
 */
abstract class AbstractStore<T> {
    /**
     * Logger.
     */
    private static final Logger LOG = LoggerFactory.getLogger(AbstractStore.class);

    /**
     * The name of the table used to represent this store.
     */
    private final String tableName;

    /**
     * Database connection, only used for tests.
     */
    Connection dbConnection = null;

    /**
     * Table types we're interested in (when checking tables' existence).
     */
    public static final String[] TABLE_TYPES = new String[] { "TABLE" };

    /**
     * Creates an instance.
     *
     * @param tableName The name of the table being managed.
     */
    protected AbstractStore(String tableName) {
        this.tableName = tableName;
    }

    /**
     * Returns a database connection. It is the caller's responsibility to close it. If the managed table does not
     * exist, it will be created (using {@link #getTableCreationStatement()}).
     *
     * @return A database connection.
     *
     * @throws StoreException if an error occurs.
     */
    protected Connection dbConnect() throws StoreException {
        Connection conn = H2Store.getConnection(dbConnection);
        try {
            // Ensure table check/creation is atomic
            synchronized (this) {
                DatabaseMetaData dbm = conn.getMetaData();
                try (ResultSet rs = dbm.getTables(null, null, tableName, TABLE_TYPES)) {
                    if (rs.next()) {
                        LOG.debug("Table {} already exists", tableName);
                    } else {
                        LOG.info("Table {} does not exist, creating it", tableName);
                        try (Statement stmt = conn.createStatement()) {
                            stmt.executeUpdate(getTableCreationStatement());
                        }
                    }
                }
            }
        } catch (SQLException e) {
            LOG.error("Error connecting to the H2 database", e);
            throw new StoreException("Cannot connect to database server", e);
        }
        return conn;
    }

    /**
     * Empties the store.
     *
     * @throws StoreException if a connection error occurs.
     */
    protected void dbClean() throws StoreException {
        try (Connection c = dbConnect()) {
            // The table name can't be a parameter in a prepared statement
            String sql = "DELETE FROM " + tableName;
            c.createStatement().execute(sql);
        } catch (SQLException e) {
            LOG.error("Error clearing table {}", tableName, e);
            throw new StoreException("Error clearing table " + tableName, e);
        }
    }

    /**
     * Returns the SQL code required to create the managed table.
     *
     * @return The SQL table creation statement.
     */
    protected abstract String getTableCreationStatement();

    /**
     * Lists all the stored items.
     *
     * @return The stored item.
     *
     * @throws StoreException if an error occurs.
     */
    protected List<T> listAll() throws StoreException {
        List<T> result = new ArrayList<>();
        String query = "SELECT * FROM " + tableName;
        try (Connection conn = dbConnect();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(query)) {
            while (rs.next()) {
                result.add(fromResultSet(rs));
            }
        } catch (SQLException e) {
            LOG.error("Error listing all items from {}", tableName, e);
            throw new StoreException(e);
        }
        return result;
    }

    /**
     * Lists the stored items returned by the given statement.
     *
     * @param ps The statement (which must be ready for execution). It is the caller's reponsibility to close this.
     *
     * @return The stored items.
     *
     * @throws StoreException if an error occurs.
     */
    protected List<T> listFromStatement(PreparedStatement ps) throws StoreException {
        List<T> result = new ArrayList<>();
        try (ResultSet rs = ps.executeQuery()) {
            while (rs.next()) {
                result.add(fromResultSet(rs));
            }
        } catch (SQLException e) {
            LOG.error("Error listing matching items from {}", tableName, e);
            throw new StoreException(e);
        }
        return result;
    }

    /**
     * Extracts the first item returned by the given statement, if any.
     *
     * @param ps The statement (which must be ready for execution). It is the caller's reponsibility to close this.
     *
     * @return The first item, or {@code null} if none.
     *
     * @throws StoreException if an error occurs.
     */
    protected T firstFromStatement(PreparedStatement ps) throws StoreException {
        try (ResultSet rs = ps.executeQuery()) {
            if (rs.next()) {
                return fromResultSet(rs);
            } else {
                return null;
            }
        } catch (SQLException e) {
            LOG.error("Error listing first matching item from {}", tableName, e);
            throw new StoreException(e);
        }
    }

    /**
     * Converts a single row in a result set to an instance of the managed type.
     *
     * @param rs The result set (which is ready for extraction; {@link ResultSet#next()} must <b>not</b> be called).
     *
     * @return The corresponding instance.
     *
     * @throws SQLException if an error occurs.
     */
    protected abstract T fromResultSet(ResultSet rs) throws SQLException;
}