com.google.gerrit.server.schema.H2AccountPatchReviewStore.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gerrit.server.schema.H2AccountPatchReviewStore.java

Source

// Copyright (C) 2016 The Android Open Source Project
//
// 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 com.google.gerrit.server.schema;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.change.AccountPatchReviewStore;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.eclipse.jgit.lib.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Singleton
public class H2AccountPatchReviewStore implements AccountPatchReviewStore, LifecycleListener {
    private static final Logger log = LoggerFactory.getLogger(H2AccountPatchReviewStore.class);

    public static class Module extends LifecycleModule {
        @Override
        protected void configure() {
            DynamicItem.bind(binder(), AccountPatchReviewStore.class).to(H2AccountPatchReviewStore.class);
            listener().to(H2AccountPatchReviewStore.class);
        }
    }

    @VisibleForTesting
    public static class InMemoryModule extends LifecycleModule {
        @Override
        protected void configure() {
            H2AccountPatchReviewStore inMemoryStore = new H2AccountPatchReviewStore();
            DynamicItem.bind(binder(), AccountPatchReviewStore.class).toInstance(inMemoryStore);
            listener().toInstance(inMemoryStore);
        }
    }

    private final DataSource ds;

    @Inject
    H2AccountPatchReviewStore(@GerritServerConfig Config cfg, SitePaths sitePaths) {
        this.ds = createDataSource(H2.appendUrlOptions(cfg, getUrl(sitePaths)));
    }

    public static String getUrl(SitePaths sitePaths) {
        return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
    }

    /**
     * Creates an in-memory H2 database to store the reviewed flags. This should be used for tests
     * only.
     */
    @VisibleForTesting
    private H2AccountPatchReviewStore() {
        // DB_CLOSE_DELAY=-1: By default the content of an in-memory H2 database is
        // lost at the moment the last connection is closed. This option keeps the
        // content as long as the vm lives.
        this.ds = createDataSource("jdbc:h2:mem:account_patch_reviews;DB_CLOSE_DELAY=-1");
    }

    private static DataSource createDataSource(String url) {
        BasicDataSource datasource = new BasicDataSource();
        datasource.setDriverClassName("org.h2.Driver");
        datasource.setUrl(url);
        datasource.setMaxActive(50);
        datasource.setMinIdle(4);
        datasource.setMaxIdle(16);
        long evictIdleTimeMs = 1000 * 60;
        datasource.setMinEvictableIdleTimeMillis(evictIdleTimeMs);
        datasource.setTimeBetweenEvictionRunsMillis(evictIdleTimeMs / 2);
        return datasource;
    }

    @Override
    public void start() {
        try {
            createTableIfNotExists();
        } catch (OrmException e) {
            log.error("Failed to create table to store account patch reviews", e);
        }
    }

    public static void createTableIfNotExists(String url) throws OrmException {
        try (Connection con = DriverManager.getConnection(url); Statement stmt = con.createStatement()) {
            doCreateTable(stmt);
        } catch (SQLException e) {
            throw convertError("create", e);
        }
    }

    private void createTableIfNotExists() throws OrmException {
        try (Connection con = ds.getConnection(); Statement stmt = con.createStatement()) {
            doCreateTable(stmt);
        } catch (SQLException e) {
            throw convertError("create", e);
        }
    }

    private static void doCreateTable(Statement stmt) throws SQLException {
        stmt.executeUpdate("CREATE TABLE IF NOT EXISTS ACCOUNT_PATCH_REVIEWS ("
                + "ACCOUNT_ID INTEGER DEFAULT 0 NOT NULL, " + "CHANGE_ID INTEGER DEFAULT 0 NOT NULL, "
                + "PATCH_SET_ID INTEGER DEFAULT 0 NOT NULL, " + "FILE_NAME VARCHAR(255) DEFAULT '' NOT NULL, "
                + "CONSTRAINT PRIMARY_KEY_ACCOUNT_PATCH_REVIEWS "
                + "PRIMARY KEY (ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME)" + ")");
    }

    public static void dropTableIfExists(String url) throws OrmException {
        try (Connection con = DriverManager.getConnection(url); Statement stmt = con.createStatement()) {
            stmt.executeUpdate("DROP TABLE IF EXISTS ACCOUNT_PATCH_REVIEWS");
        } catch (SQLException e) {
            throw convertError("create", e);
        }
    }

    @Override
    public void stop() {
    }

    @Override
    public boolean markReviewed(PatchSet.Id psId, Account.Id accountId, String path) throws OrmException {
        try (Connection con = ds.getConnection();
                PreparedStatement stmt = con.prepareStatement("INSERT INTO ACCOUNT_PATCH_REVIEWS "
                        + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES " + "(?, ?, ?, ?)")) {
            stmt.setInt(1, accountId.get());
            stmt.setInt(2, psId.getParentKey().get());
            stmt.setInt(3, psId.get());
            stmt.setString(4, path);
            stmt.executeUpdate();
            return true;
        } catch (SQLException e) {
            OrmException ormException = convertError("insert", e);
            if (ormException instanceof OrmDuplicateKeyException) {
                return false;
            }
            throw ormException;
        }
    }

    @Override
    public void markReviewed(PatchSet.Id psId, Account.Id accountId, Collection<String> paths) throws OrmException {
        if (paths == null || paths.isEmpty()) {
            return;
        }

        try (Connection con = ds.getConnection();
                PreparedStatement stmt = con.prepareStatement("INSERT INTO ACCOUNT_PATCH_REVIEWS "
                        + "(ACCOUNT_ID, CHANGE_ID, PATCH_SET_ID, FILE_NAME) VALUES " + "(?, ?, ?, ?)")) {
            for (String path : paths) {
                stmt.setInt(1, accountId.get());
                stmt.setInt(2, psId.getParentKey().get());
                stmt.setInt(3, psId.get());
                stmt.setString(4, path);
                stmt.addBatch();
            }
            stmt.executeBatch();
        } catch (SQLException e) {
            OrmException ormException = convertError("insert", e);
            if (ormException instanceof OrmDuplicateKeyException) {
                return;
            }
            throw ormException;
        }
    }

    @Override
    public void clearReviewed(PatchSet.Id psId, Account.Id accountId, String path) throws OrmException {
        try (Connection con = ds.getConnection();
                PreparedStatement stmt = con.prepareStatement("DELETE FROM ACCOUNT_PATCH_REVIEWS "
                        + "WHERE ACCOUNT_ID = ? AND CHANGE_ID + ? AND " + "PATCH_SET_ID = ? AND FILE_NAME = ?")) {
            stmt.setInt(1, accountId.get());
            stmt.setInt(2, psId.getParentKey().get());
            stmt.setInt(3, psId.get());
            stmt.setString(4, path);
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw convertError("delete", e);
        }
    }

    @Override
    public void clearReviewed(PatchSet.Id psId) throws OrmException {
        try (Connection con = ds.getConnection();
                PreparedStatement stmt = con.prepareStatement(
                        "DELETE FROM ACCOUNT_PATCH_REVIEWS " + "WHERE CHANGE_ID + ? AND PATCH_SET_ID = ?")) {
            stmt.setInt(1, psId.getParentKey().get());
            stmt.setInt(2, psId.get());
            stmt.executeUpdate();
        } catch (SQLException e) {
            throw convertError("delete", e);
        }
    }

    @Override
    public Collection<String> findReviewed(PatchSet.Id psId, Account.Id accountId) throws OrmException {
        try (Connection con = ds.getConnection();
                PreparedStatement stmt = con.prepareStatement("SELECT FILE_NAME FROM ACCOUNT_PATCH_REVIEWS "
                        + "WHERE ACCOUNT_ID = ? AND CHANGE_ID = ? AND PATCH_SET_ID = ?")) {
            stmt.setInt(1, accountId.get());
            stmt.setInt(2, psId.getParentKey().get());
            stmt.setInt(3, psId.get());
            try (ResultSet rs = stmt.executeQuery()) {
                List<String> files = new ArrayList<>();
                while (rs.next()) {
                    files.add(rs.getString("FILE_NAME"));
                }
                return files;
            }
        } catch (SQLException e) {
            throw convertError("select", e);
        }
    }

    public static OrmException convertError(String op, SQLException err) {
        switch (getSQLStateInt(err)) {
        case 23001: // UNIQUE CONSTRAINT VIOLATION
        case 23505: // DUPLICATE_KEY_1
            return new OrmDuplicateKeyException("ACCOUNT_PATCH_REVIEWS", err);

        default:
            if (err.getCause() == null && err.getNextException() != null) {
                err.initCause(err.getNextException());
            }
            return new OrmException(op + " failure on ACCOUNT_PATCH_REVIEWS", err);
        }
    }

    private static String getSQLState(SQLException err) {
        String ec;
        SQLException next = err;
        do {
            ec = next.getSQLState();
            next = next.getNextException();
        } while (ec == null && next != null);
        return ec;
    }

    private static int getSQLStateInt(SQLException err) {
        String s = getSQLState(err);
        if (s != null) {
            Integer i = Ints.tryParse(s);
            return i != null ? i : -1;
        }
        return 0;
    }
}