/* ================================================================== * DerbyDataSourceErrorProxy.java - 24/07/2016 2:52:57 PM * * Copyright 2007-2016 SolarNetwork.net Dev Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA * 02111-1307 USA * ================================================================== */ package net.solarnetwork.dao.jdbc; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; import java.util.logging.Logger; import javax.sql.ConnectionEvent; import javax.sql.ConnectionEventListener; import javax.sql.DataSource; import javax.sql.PooledConnection; import org.osgi.framework.BundleContext; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceReference; import org.slf4j.LoggerFactory; /** * A {@link DataSource} proxy that catches connection errors in order to handle * the exceptions from {@link SQLExceptionHandlers} registered with the system. * * @author matt * @version 1.0 */ public class SQLExceptionHandlerDataSourceProxy implements DataSource, ConnectionEventListener { private final DataSource delegate; private final BundleContext bundleContext; private final org.slf4j.Logger log = LoggerFactory.getLogger(getClass()); /** * Construct with values. * * @param delegate * The {@link DataSource} to delegate to. * @param bundleContext * The bundle context to use. May be <em>null</em>. */ public SQLExceptionHandlerDataSourceProxy(DataSource delegate, BundleContext bundleContext) { super(); this.delegate = delegate; this.bundleContext = bundleContext; } /** * Get the delegate DataSource. * * @return The delegate. */ public DataSource getDelegate() { return delegate; } @Override public Connection getConnection() throws SQLException { try { Connection conn = delegate.getConnection(); return getWrappedConnection(conn); } catch ( final SQLException e ) { handleSQLException(null, e); throw e; } } @Override public Connection getConnection(String username, String password) throws SQLException { try { Connection conn = delegate.getConnection(username, password); return getWrappedConnection(conn); } catch ( final SQLException e ) { handleSQLException(null, e); throw e; } } private Connection getWrappedConnection(Connection conn) { // if we have a PooledConnection we can tap into that... if ( conn instanceof PooledConnection ) { PooledConnection pooledConn = (PooledConnection) conn; pooledConn.addConnectionEventListener(this); } else { // not a PooledConnection, so use a Proxy to catch exceptions conn = wrapJdbcObjectWithProxy(conn); } return conn; } private void collectAllInterfaces(Class<?> clazz, Set<Class<?>> interfaces) { for ( Class<?> i : clazz.getInterfaces() ) { interfaces.add(i); } if ( clazz.getSuperclass() != null ) { collectAllInterfaces(clazz.getSuperclass(), interfaces); } } @SuppressWarnings("unchecked") private <T> T wrapJdbcObjectWithProxy(T delegate) { Set<Class<?>> allInterfaces = new LinkedHashSet<Class<?>>(); collectAllInterfaces(delegate.getClass(), allInterfaces); Class<?>[] interfaces = allInterfaces.toArray(new Class<?>[allInterfaces.size()]); Object proxy = Proxy.newProxyInstance(delegate.getClass().getClassLoader(), interfaces, new JDBCDelegatingHandler(delegate)); return (T) proxy; } /** * An {@link InvocationHandler} for JDBC objects that looks for thrown * {@link SQLException} exceptions to pass to the registered * {@link SQLExceptionHandler} instacnes. */ private class JDBCDelegatingHandler implements InvocationHandler { private final Object delegate; /** * Delegate all method calls to another JDBC object. * * @param delegate * the delegate */ public JDBCDelegatingHandler(Object delegate) { super(); this.delegate = delegate; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Method delegateMethod = delegate.getClass().getMethod(method.getName(), method.getParameterTypes()); try { Object res = delegateMethod.invoke(delegate, args); if ( res instanceof Statement ) { // to catch SQLExceptions thrown by statements, wrap those res = wrapJdbcObjectWithProxy(res); } return res; } catch ( InvocationTargetException e ) { Throwable t = e.getCause(); if ( t instanceof SQLException ) { Connection conn = null; if ( delegate instanceof Connection ) { conn = (Connection) delegate; } else if ( delegate instanceof Statement ) { conn = ((Statement) delegate).getConnection(); } handleSQLException(conn, (SQLException) t); } throw e.getCause(); } } } @Override public void connectionClosed(ConnectionEvent event) { // nothing to do } @Override public void connectionErrorOccurred(ConnectionEvent event) { SQLException ex = event.getSQLException(); try { handleSQLException((Connection) event.getSource(), ex); } catch ( SQLException e ) { log.warn("SQLException handling exception {}", ex, e); } } private void handleSQLException(final Connection conn, final SQLException e) throws SQLException { if ( e == null ) { return; } if ( bundleContext != null ) { doWithHandlers(new SQLExceptionHandlerCallback() { @Override public void doWithHandler(SQLExceptionHandler handler) throws Exception { if ( conn == null ) { handler.handleGetConnectionException(e); } else { handler.handleConnectionException(conn, e); } } }); } } static interface SQLExceptionHandlerCallback { void doWithHandler(SQLExceptionHandler handler) throws Exception; } private void doWithHandlers(SQLExceptionHandlerCallback callback) throws SQLException { Collection<ServiceReference<SQLExceptionHandler>> handlerRefs; try { handlerRefs = bundleContext.getServiceReferences(SQLExceptionHandler.class, null); } catch ( InvalidSyntaxException e ) { log.error("Exception getting SQLExceptionHandler references from bundle context", e); return; } for ( ServiceReference<SQLExceptionHandler> ref : handlerRefs ) { SQLExceptionHandler handler = bundleContext.getService(ref); if ( handler != null ) { try { callback.doWithHandler(handler); } catch ( SQLException e ) { throw e; } catch ( Exception e ) { log.error("SQLExceptionHandler threw exception", e); } } } } @Override public PrintWriter getLogWriter() throws SQLException { return delegate.getLogWriter(); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return delegate.unwrap(iface); } @Override public void setLogWriter(PrintWriter out) throws SQLException { delegate.setLogWriter(out); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return delegate.isWrapperFor(iface); } @Override public void setLoginTimeout(int seconds) throws SQLException { delegate.setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return delegate.getLoginTimeout(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return delegate.getParentLogger(); } }