package org.jetbrains.dekaf.jdbc; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.dekaf.Rdbms; import org.jetbrains.dekaf.core.ConnectionInfo; import org.jetbrains.dekaf.exceptions.DBException; import org.jetbrains.dekaf.intermediate.DBExceptionRecognizer; import org.jetbrains.dekaf.intermediate.IntegralIntermediateFacade; import org.jetbrains.dekaf.jdbc.pooling.ConnectionPool; import org.jetbrains.dekaf.jdbc.pooling.SimpleDataSource; import org.jetbrains.dekaf.util.Version; import javax.sql.DataSource; import java.io.PrintWriter; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.Driver; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Properties; import java.util.concurrent.LinkedBlockingQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.jetbrains.dekaf.util.Objects.castTo; /** * @author Leonid Bushuev from JetBrains */ public class JdbcIntermediateFacade implements IntegralIntermediateFacade { //// STATE \\\\ @NotNull protected final ConnectionPool myPool; @NotNull protected final DBExceptionRecognizer myExceptionRecognizer; private final LinkedBlockingQueue<JdbcIntermediateSession> mySessions = new LinkedBlockingQueue<JdbcIntermediateSession>(); //// CONSTRUCTORS \\\\ public JdbcIntermediateFacade(@NotNull final String connectionString, @Nullable final Properties connectionProperties, @NotNull final Driver driver, int connectionsLimit, @NotNull final DBExceptionRecognizer exceptionRecognizer) { this(prepareDataSource(connectionString, connectionProperties, driver), connectionsLimit, true, exceptionRecognizer); } public JdbcIntermediateFacade(@NotNull final DataSource dataSource, int connectionsLimit, boolean ownConnections, @NotNull final DBExceptionRecognizer exceptionRecognizer) { myPool = new ConnectionPool(dataSource, ownConnections); myPool.setConnectionsLimit(connectionsLimit); myExceptionRecognizer = exceptionRecognizer; } @NotNull private static DataSource prepareDataSource(final @NotNull String connectionString, final @Nullable Properties connectionProperties, final @NotNull Driver driver) { final SimpleDataSource dataSource = new SimpleDataSource(connectionString, connectionProperties, driver); dataSource.setLogWriter(new PrintWriter(System.out)); return dataSource; } //// IMPLEMENTATION \\\\ @NotNull @Override public Rdbms rdbms() { return UnknownDatabase.RDBMS; } @Override public synchronized void connect() { try { myPool.connect(); } catch (SQLException sqle) { throw myExceptionRecognizer.recognizeException(sqle, "connect"); } } @Override public synchronized void reconnect() { // TODO implement JdbcInterFacade.reconnect throw new RuntimeException("The JdbcInterFacade.reconnect has not been implemented yet."); } @Override public synchronized void disconnect() { try { while (!mySessions.isEmpty()) { Thread.sleep(10); //noinspection MismatchedQueryAndUpdateOfCollection Collection<JdbcIntermediateSession> sessionsToClose = new ArrayList<JdbcIntermediateSession>(10); mySessions.drainTo(sessionsToClose, 10); for (JdbcIntermediateSession sessionToClose : sessionsToClose) { sessionToClose.close(); } } } catch (InterruptedException ie) { // do nothing } myPool.disconnect(); } @Override public boolean isConnected() { return myPool.isReady(); } @NotNull @Override public ConnectionInfo getConnectionInfo() { ConnectionInfo info = null; try { info = obtainConnectionInfoNatively(); } catch (DBException e) { // TODO log the exception if in debug mode } if (info == null) { info = obtainConnectionInfoFromJdbc(); } return info; } @Nullable protected ConnectionInfo obtainConnectionInfoNatively() { return null; // inheritors should override this method } @NotNull protected ConnectionInfo obtainConnectionInfoFromJdbc() { try { Connection connection = myPool.borrow(); try { DatabaseMetaData md = connection.getMetaData(); String rdbmsName = md.getDatabaseProductName(); if (rdbmsName == null) rdbmsName = connection.getClass().getName(); String databaseName = connection.getCatalog(); String schemaName = getSchema(connection); String userName = getUserNameSafe(md); Version serverVersion = Version.of(md.getDatabaseMajorVersion(), md.getDatabaseMinorVersion()); Version driverVersion = Version.of(md.getDriverMajorVersion(), md.getDriverMinorVersion()); return new ConnectionInfo(rdbmsName, databaseName, schemaName, userName, serverVersion, driverVersion); } finally { myPool.release(connection); } } catch (SQLException sqle) { throw myExceptionRecognizer.recognizeException(sqle, "getting brief connection info using JDBC connection metadata"); } } private String getUserNameSafe(final DatabaseMetaData md) { try { return md.getUserName(); } catch (Exception ignored) { return null; } } /** * Try to call the "Connection#getSchema()" function * that appears in JRE 1.7. * * Some JDBC vendor can also ignore this method or throw exceptions. */ @Nullable private static String getSchema(final Connection connection) { String schemaName = null; try { final Class<? extends Connection> connectionClass = connection.getClass(); final Method getSchemaMethod = connectionClass.getMethod("getSchema"); if (getSchemaMethod != null) { schemaName = (String) getSchemaMethod.invoke(connection); } } catch (NoSuchMethodException nsm) { // no such method. sad } catch (Exception e) { // TODO log this somehow? } return schemaName; } @NotNull @Override public JdbcIntermediateSession openSession() { final Connection connection; try { connection = myPool.borrow(); } catch (SQLException sqle) { throw myExceptionRecognizer.recognizeException(sqle, "borrow a connection from the pool"); } final JdbcIntermediateSession session = instantiateSession(connection, false); mySessions.add(session); return session; } @NotNull protected JdbcIntermediateSession instantiateSession(@NotNull final Connection connection, final boolean ownConnection) { return new JdbcIntermediateSession(this, myExceptionRecognizer, connection, ownConnection); } @NotNull public DBExceptionRecognizer getExceptionRecognizer() { return myExceptionRecognizer; } void sessionIsClosed(@NotNull final JdbcIntermediateSession session, @NotNull final Connection connection) { mySessions.remove(session); myPool.release(connection); } @Nullable @Override public <I> I getSpecificService(@NotNull final Class<I> serviceClass, @NotNull final String serviceName) throws ClassCastException { if (serviceName.equalsIgnoreCase(Names.INTERMEDIATE_SERVICE)) { return castTo(serviceClass, this); } else { return myPool.getSpecificService(serviceClass, serviceName); } } @NotNull protected ConnectionInfo getConnectionInfoSmartly(final String envQuery, final Pattern serverVersionPattern, final int serverVersionGroupIndex, final Pattern driverVersionPattern, final int driverVersionGroupIndex) { String[] env; Version serverVersion, driverVersion; final JdbcIntermediateSession session = openSession(); try { // environment env = session.queryOneRow(envQuery, 3, String.class); if (env == null) env = new String[] {null,null,null}; assert env.length == 3 : "Session info should contain 3 components"; // versions String rdbmsName, serverVersionStr, driverVersionStr; try { DatabaseMetaData md = session.getConnection().getMetaData(); rdbmsName = md.getDatabaseProductName(); if (rdbmsName == null) rdbmsName = session.getConnection().getClass().getName(); serverVersionStr = md.getDatabaseProductVersion(); driverVersionStr = md.getDriverVersion(); } catch (SQLException sqle) { throw getExceptionRecognizer().recognizeException(sqle, "getting versions using JDBC metadata"); } serverVersion = extractVersion(serverVersionStr, serverVersionPattern, serverVersionGroupIndex); driverVersion = extractVersion(driverVersionStr, driverVersionPattern, driverVersionGroupIndex); // ok return new ConnectionInfo(rdbmsName, env[0], env[1], env[2], serverVersion, driverVersion); } finally { session.close(); } } protected static final Pattern SIMPLE_VERSION_PATTERN = Pattern.compile("(\\d{1,2}(\\.\\d{1,3}){1,5})"); @NotNull protected static Version extractVersion(@Nullable final String serverVersionStr, @NotNull final Pattern versionPattern, final int groupIndex) { if (serverVersionStr == null || serverVersionStr.isEmpty()) return Version.ZERO; Matcher m = versionPattern.matcher(serverVersionStr); if (m.find()) return Version.of(m.group(groupIndex)); else return Version.ZERO; } //// DIAGNOSTIC METHODS \\\\ public int countOpenedSessions() { return mySessions.size(); } public int countOpenedConnections() { return myPool.countAllConnections(); } public int countOpenedSeances() { int count = 0; for (JdbcIntermediateSession session : mySessions) count += session.countOpenedSeances(); return count; } public int countOpenedCursors() { int count = 0; for (JdbcIntermediateSession session : mySessions) count += session.countOpenedCursors(); return count; } }