/* * Copyright (C) 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package apps.provisioning.data; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; import org.h2.jdbcx.JdbcConnectionPool; import org.h2.tools.DeleteDbFiles; import apps.provisioning.server.account.data.UsernameDataSource; /** * Adds support for caching usernames from the Google Directory using a H2 database. * */ public class H2DataSource implements UsernameDataSource { private final String USERNAME_TABLE = "usernames"; private final String USERNAME_COLUMN_NAME = "user"; private final String USER_EXISTS_QUERY = "select 1 from " + USERNAME_TABLE + " where " + USERNAME_COLUMN_NAME + " = '%s'"; private final String INSERT_SINGLE_USER_QUERY = "insert into " + USERNAME_TABLE + "(" + USERNAME_COLUMN_NAME + ") VALUES ('%s')"; private final String INSERT_MULTIPLE_USERS_QUERY = "insert into " + USERNAME_TABLE + " (" + USERNAME_COLUMN_NAME + ") VALUES "; private final String INSERT_MULTIPLE_USERS_QUERY_PARAM = "('%s'), "; private final String MAYBE_CREATE_TABLE_QUERY = "create table if not exists " + USERNAME_TABLE + "(" + USERNAME_COLUMN_NAME + " varchar(64) primary key)"; private final Logger logger = Logger.getLogger(JdbcConnectionPool.class.getName()); // private JdbcConnectionPool connectionPool; private String databasePath; private String databaseName; private String databaseUrl; private JdbcConnectionPool connectionPool; public H2DataSource(String databasePath, String databaseName) throws SQLException, Exception { this.databasePath = databasePath; this.databaseName = databaseName; this.databaseUrl = "jdbc:h2:" + databasePath + databaseName; logger.log(Level.INFO, "H2 database URL: " + this.databaseUrl); this.setup(); } /** * Sets up the databse to be used to cache usersnames. * * @throws SQLException */ private void setup() throws SQLException { connectionPool = JdbcConnectionPool.create(databaseUrl, "", ""); maybeCreateUsernamesTable(); } /** * Creates the usernames table if none exists. * * @throws SQLException */ private void maybeCreateUsernamesTable() throws SQLException { executeQuery(MAYBE_CREATE_TABLE_QUERY); } public boolean exists(String username) throws SQLException, Exception { int resultCount = executeQuery(String.format(USER_EXISTS_QUERY, username)); switch (resultCount) { case 0: return false; case 1: return true; default: throw new Exception( "Inconsistent state. Only one username should exist in the database, but " + resultCount + " were found for: " + username); } } public void insert(String username) throws SQLException, Exception { int updateCount = executeQuery(String.format(INSERT_SINGLE_USER_QUERY, username)); if (updateCount != 1) { throw new Exception("User " + username + " could not be inserted."); } } /** * Inserts all the given usernames. * * @param usernames The usernames to be inserted. * @throws SQLException * @throws Exception when the number of inserted usernames does not match the given usernames. */ public void insertMultiple(ArrayList<String> usernames) throws SQLException, Exception { if (usernames.size() == 0) { return; } String query = INSERT_MULTIPLE_USERS_QUERY; for (String username : usernames) { query += String.format(INSERT_MULTIPLE_USERS_QUERY_PARAM, username); } int updateCount = executeQuery(query); if (updateCount != usernames.size()) { throw new Exception("Issue when inserting " + usernames.size() + " users. Only " + updateCount + " were inserted."); } } /** * Executes the given query. * * @param query The query to execute. * @return Returns the number of rows returned or the number of rows affected by the query. * @throws SQLException */ private int executeQuery(String query) throws SQLException { Connection connection = connectionPool.getConnection(); Statement statement; int resultCount = 0; try { statement = connection.createStatement(); logger.log(Level.INFO, "Running query: " + query); boolean hasResultSet = statement.execute(query); if (hasResultSet) { ResultSet resultSet = statement.getResultSet(); if (resultSet != null && resultSet.last()) { resultCount = resultSet.getRow(); } } else { resultCount = statement.getUpdateCount(); } } catch (SQLException e) { // Something went wrong. Close the connection. connection.close(); throw e; } if (statement != null) { statement.close(); } connection.close(); return resultCount; } /** * Deletes the current databse and creates a new one from scratch. Should be called with no active * connections. * * @throws SQLException * @throws Exception if active connections exist. */ public void reset() throws SQLException, Exception { dispose(); this.setup(); maybeCreateUsernamesTable(); } /** * Deletes the database files. Should be called with no active connections. * * @throws Exception if there are active connections. */ private void deleteDatabseFiles() throws Exception { if (connectionPool.getActiveConnections() != 0) { throw new Exception( "Should not try to delete database files while there are active connections."); } logger.log(Level.INFO, "Deleting database files at: " + databasePath + databaseName); DeleteDbFiles.execute(databasePath, databaseName, true); } /** * Disposes the connection pool and deletes all databse files. Should be called with no active * connections. * * @throws Exception if there are active connections. */ public void dispose() throws Exception { connectionPool.dispose(); deleteDatabseFiles(); } }