X-Git-Url: https://code.wpia.club/?p=gigi.git;a=blobdiff_plain;f=src%2Forg%2Fcacert%2Fgigi%2Fdatabase%2FDatabaseConnection.java;h=0be7becdf90093ee8dddfd0693a77256656a9377;hp=bccae86faebb950f1ea82da64aca0e90db8d58b2;hb=4b9842acd7dd0e79de8fc3f88cb8241f9b2f134e;hpb=a0232b6e40e7e09767f0444d24e18bf12dafc362 diff --git a/src/org/cacert/gigi/database/DatabaseConnection.java b/src/org/cacert/gigi/database/DatabaseConnection.java index bccae86f..0be7becd 100644 --- a/src/org/cacert/gigi/database/DatabaseConnection.java +++ b/src/org/cacert/gigi/database/DatabaseConnection.java @@ -14,6 +14,7 @@ import java.util.HashSet; import java.util.Map.Entry; import java.util.Properties; import java.util.StringJoiner; +import java.util.concurrent.LinkedBlockingDeque; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -21,15 +22,115 @@ import org.cacert.gigi.database.SQLFileManager.ImportType; public class DatabaseConnection { - public static final int CURRENT_SCHEMA_VERSION = 6; + public static class Link implements AutoCloseable { + + private DatabaseConnection target; + + protected Link(DatabaseConnection target) { + this.target = target; + } + + @Override + public void close() { + synchronized (DatabaseConnection.class) { + Link i = instances.get(Thread.currentThread()); + if (i != this) { + throw new Error(); + } + instances.remove(Thread.currentThread()); + pool.add(target); + } + } + + } + + public static final int MAX_CACHED_INSTANCES = 3; + + private static class StatementDescriptor { + + String query; + + boolean scrollable; + + int instance; + + PreparedStatement target; + + public StatementDescriptor(String query, boolean scrollable) { + this.query = query; + this.scrollable = scrollable; + this.instance = 0; + } + + public synchronized void instanciate(Connection c) throws SQLException { + if (scrollable) { + target = c.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); + } else { + target = c.prepareStatement(query, query.startsWith("SELECT ") ? Statement.NO_GENERATED_KEYS : Statement.RETURN_GENERATED_KEYS); + } + + } + + public synchronized PreparedStatement getTarget() { + return target; + } + + public synchronized void increase() { + if (target != null) { + throw new IllegalStateException(); + } + instance++; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + instance; + result = prime * result + ((query == null) ? 0 : query.hashCode()); + result = prime * result + (scrollable ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + StatementDescriptor other = (StatementDescriptor) obj; + if (instance != other.instance) { + return false; + } + if (query == null) { + if (other.query != null) { + return false; + } + } else if ( !query.equals(other.query)) { + return false; + } + if (scrollable != other.scrollable) { + return false; + } + return true; + } + + } + + public static final int CURRENT_SCHEMA_VERSION = 18; public static final int CONNECTION_TIMEOUT = 24 * 60 * 60; private Connection c; - private HashMap statements = new HashMap(); + private HashMap statements = new HashMap(); - HashSet underUse = new HashSet<>(); + private HashSet underUse = new HashSet<>(); private static Properties credentials; @@ -55,43 +156,37 @@ public class DatabaseConnection { } protected synchronized PreparedStatement prepareInternal(String query) throws SQLException { + return prepareInternal(query, false); + } + + protected synchronized PreparedStatement prepareInternal(String query, boolean scrollable) throws SQLException { + ensureOpen(); query = preprocessQuery(query); - PreparedStatement statement = statements.get(query); - if (statement != null) { - if (underUse.add(statement)) { - return statement; + StatementDescriptor searchHead = new StatementDescriptor(query, scrollable); + PreparedStatement statement = null; + while (statement == null) { + statement = statements.get(searchHead); + if (statement == null) { + searchHead.instanciate(c); + statement = searchHead.getTarget(); + if (searchHead.instance >= MAX_CACHED_INSTANCES) { + return statement; + } + underUse.add(statement); + statements.put(searchHead, statement); + } else if (underUse.contains(statement)) { + searchHead.increase(); + statement = null; } else { - throw new Error("Statement in Use"); + underUse.add(statement); } } - statement = c.prepareStatement(query, query.startsWith("SELECT ") ? Statement.NO_GENERATED_KEYS : Statement.RETURN_GENERATED_KEYS); - statements.put(query, statement); - if (underUse.add(statement)) { - return statement; - } else { - throw new Error("Statement in Use"); - } + return statement; } protected synchronized PreparedStatement prepareInternalScrollable(String query) throws SQLException { - ensureOpen(); - query = preprocessQuery(query); - PreparedStatement statement = statements.get("__SCROLLABLE__! " + query); - if (statement != null) { - if (underUse.add(statement)) { - return statement; - } else { - throw new Error("Statement in Use"); - } - } - statement = c.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); - statements.put("__SCROLLABLE__! " + query, statement); - if (underUse.add(statement)) { - return statement; - } else { - throw new Error("Statement in Use"); - } + return prepareInternal(query, true); } private long lastAction = System.currentTimeMillis(); @@ -111,17 +206,23 @@ public class DatabaseConnection { lastAction = System.currentTimeMillis(); } - private static DatabaseConnection instance; + private static HashMap instances = new HashMap<>(); - public static DatabaseConnection getInstance() { - if (instance == null) { - synchronized (DatabaseConnection.class) { - if (instance == null) { - instance = new DatabaseConnection(); - } - } + private static LinkedBlockingDeque pool = new LinkedBlockingDeque<>(); + + private static int connCount = 0; + + public static synchronized DatabaseConnection getInstance() { + Link l = instances.get(Thread.currentThread()); + if (l == null) { + throw new Error("No database connection allocated"); } - return instance; + return l.target; + } + + public static synchronized boolean hasInstance() { + Link l = instances.get(Thread.currentThread()); + return l != null; } public static boolean isInited() { @@ -133,24 +234,24 @@ public class DatabaseConnection { throw new Error("Re-initiaizing is forbidden."); } credentials = conf; - int version = 0; - try (GigiPreparedStatement gigiPreparedStatement = new GigiPreparedStatement("SELECT version FROM \"schemeVersion\" ORDER BY version DESC LIMIT 1;")) { - GigiResultSet rs = gigiPreparedStatement.executeQuery(); - if (rs.next()) { - version = rs.getInt(1); + try (Link i = newLink(false)) { + int version = 0; + try (GigiPreparedStatement gigiPreparedStatement = new GigiPreparedStatement("SELECT version FROM \"schemeVersion\" ORDER BY version DESC LIMIT 1;")) { + GigiResultSet rs = gigiPreparedStatement.executeQuery(); + if (rs.next()) { + version = rs.getInt(1); + } } + if (version == CURRENT_SCHEMA_VERSION) { + return; // Good to go + } + if (version > CURRENT_SCHEMA_VERSION) { + throw new Error("Invalid database version. Please fix this."); + } + upgrade(version); + } catch (InterruptedException e) { + throw new Error(e); } - if (version == CURRENT_SCHEMA_VERSION) { - return; // Good to go - } - if (version > CURRENT_SCHEMA_VERSION) { - throw new Error("Invalid database version. Please fix this."); - } - upgrade(version); - } - - public void beginTransaction() throws SQLException { - c.setAutoCommit(false); } private static void upgrade(int version) { @@ -158,12 +259,7 @@ public class DatabaseConnection { Statement s = getInstance().c.createStatement(); try { while (version < CURRENT_SCHEMA_VERSION) { - try (InputStream resourceAsStream = DatabaseConnection.class.getResourceAsStream("upgrade/from_" + version + ".sql")) { - if (resourceAsStream == null) { - throw new Error("Upgrade script from version " + version + " was not found."); - } - SQLFileManager.addFile(s, resourceAsStream, ImportType.PRODUCTION); - } + addUpgradeScript(Integer.toString(version), s); version++; } s.addBatch("UPDATE \"schemeVersion\" SET version='" + version + "'"); @@ -180,19 +276,12 @@ public class DatabaseConnection { } } - public void commitTransaction() throws SQLException { - c.commit(); - c.setAutoCommit(true); - } - - public void quitTransaction() { - try { - if ( !c.getAutoCommit()) { - c.rollback(); - c.setAutoCommit(true); + private static void addUpgradeScript(String version, Statement s) throws Error, IOException, SQLException { + try (InputStream resourceAsStream = DatabaseConnection.class.getResourceAsStream("upgrade/from_" + version + ".sql")) { + if (resourceAsStream == null) { + throw new Error("Upgrade script from version " + version + " was not found."); } - } catch (SQLException e) { - e.printStackTrace(); + SQLFileManager.addFile(s, resourceAsStream, ImportType.PRODUCTION); } } @@ -231,19 +320,46 @@ public class DatabaseConnection { return ident; } - protected synchronized void returnStatement(PreparedStatement target) { - underUse.remove(target); + protected synchronized void returnStatement(PreparedStatement target) throws SQLException { + if ( !underUse.remove(target)) { + target.close(); + } + } + + public synchronized int getNumberOfLockedStatements() { + return underUse.size(); } - public void lockedStatements(PrintWriter writer) { + public synchronized void lockedStatements(PrintWriter writer) { writer.println(underUse.size()); for (PreparedStatement ps : underUse) { - for (Entry e : statements.entrySet()) { + for (Entry e : statements.entrySet()) { if (e.getValue() == ps) { writer.println("
"); - writer.println(e.getKey()); + writer.println(e.getKey().instance + ":"); + + writer.println(e.getKey().query); } } } } + + public static synchronized Link newLink(boolean readOnly) throws InterruptedException { + if (instances.get(Thread.currentThread()) != null) { + throw new Error("There is already a connection allocated for this thread."); + } + if (pool.isEmpty() && connCount < 5) { + pool.addLast(new DatabaseConnection()); + connCount++; + } + DatabaseConnection conn = pool.takeFirst(); + try { + conn.c.setReadOnly(readOnly); + } catch (SQLException e) { + throw new Error(e); + } + Link l = new Link(conn); + instances.put(Thread.currentThread(), l); + return l; + } }