/* * Hibernate, Relational Persistence for Idiomatic Java * * Copyright (c) 2010, Red Hat Inc. or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. All third-party contributions are * distributed under license by Red Hat Inc. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.hibernate.engine.jdbc.internal; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.hibernate.HibernateException; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.engine.jdbc.spi.InvalidatableWrapper; import org.hibernate.engine.jdbc.spi.JdbcResourceRegistry; import org.hibernate.engine.jdbc.spi.JdbcWrapper; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.jboss.logging.Logger; import org.jboss.logging.Logger.Level; /** * Standard implementation of the {@link org.hibernate.engine.jdbc.spi.JdbcResourceRegistry} contract * * @author Steve Ebersole */ public class JdbcResourceRegistryImpl implements JdbcResourceRegistry { private static final CoreMessageLogger LOG = Logger.getMessageLogger( CoreMessageLogger.class, JdbcResourceRegistryImpl.class.getName() ); private final HashMap<Statement,Set<ResultSet>> xref = new HashMap<Statement,Set<ResultSet>>(); private final Set<ResultSet> unassociatedResultSets = new HashSet<ResultSet>(); private final SqlExceptionHelper exceptionHelper; private Statement lastQuery; public JdbcResourceRegistryImpl(SqlExceptionHelper exceptionHelper) { this.exceptionHelper = exceptionHelper; } public void register(Statement statement) { LOG.tracev( "Registering statement [{0}]", statement ); if ( xref.containsKey( statement ) ) { throw new HibernateException( "statement already registered with JDBCContainer" ); } xref.put( statement, null ); } @SuppressWarnings({ "unchecked" }) public void registerLastQuery(Statement statement) { LOG.tracev( "Registering last query statement [{0}]", statement ); if ( statement instanceof JdbcWrapper ) { JdbcWrapper<Statement> wrapper = ( JdbcWrapper<Statement> ) statement; registerLastQuery( wrapper.getWrappedObject() ); return; } lastQuery = statement; } public void cancelLastQuery() { try { if (lastQuery != null) { lastQuery.cancel(); } } catch (SQLException sqle) { throw exceptionHelper.convert( sqle, "Cannot cancel query" ); } finally { lastQuery = null; } } public void release(Statement statement) { LOG.tracev( "Releasing statement [{0}]", statement ); Set<ResultSet> resultSets = xref.get( statement ); if ( resultSets != null ) { for ( ResultSet resultSet : resultSets ) { close( resultSet ); } resultSets.clear(); } xref.remove( statement ); close( statement ); } public void register(ResultSet resultSet) { LOG.tracev( "Registering result set [{0}]", resultSet ); Statement statement; try { statement = resultSet.getStatement(); } catch ( SQLException e ) { throw exceptionHelper.convert( e, "unable to access statement from resultset" ); } if ( statement != null ) { if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { LOG.unregisteredStatement(); } Set<ResultSet> resultSets = xref.get( statement ); if ( resultSets == null ) { resultSets = new HashSet<ResultSet>(); xref.put( statement, resultSets ); } resultSets.add( resultSet ); } else { unassociatedResultSets.add( resultSet ); } } public void release(ResultSet resultSet) { LOG.tracev( "Releasing result set [{0}]", resultSet ); Statement statement; try { statement = resultSet.getStatement(); } catch ( SQLException e ) { throw exceptionHelper.convert( e, "unable to access statement from resultset" ); } if ( statement != null ) { if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { LOG.unregisteredStatement(); } Set<ResultSet> resultSets = xref.get( statement ); if ( resultSets != null ) { resultSets.remove( resultSet ); if ( resultSets.isEmpty() ) { xref.remove( statement ); } } } else { boolean removed = unassociatedResultSets.remove( resultSet ); if (!removed) LOG.unregisteredResultSetWithoutStatement(); } close( resultSet ); } public boolean hasRegisteredResources() { return ! xref.isEmpty() || ! unassociatedResultSets.isEmpty(); } public void releaseResources() { LOG.tracev( "Releasing JDBC container resources [{0}]", this ); cleanup(); } private void cleanup() { for ( Map.Entry<Statement,Set<ResultSet>> entry : xref.entrySet() ) { if ( entry.getValue() != null ) { for ( ResultSet resultSet : entry.getValue() ) { close( resultSet ); } entry.getValue().clear(); } close( entry.getKey() ); } xref.clear(); for ( ResultSet resultSet : unassociatedResultSets ) { close( resultSet ); } unassociatedResultSets.clear(); // TODO: can ConcurrentModificationException still happen??? // Following is from old AbstractBatcher... /* Iterator iter = resultSetsToClose.iterator(); while ( iter.hasNext() ) { try { logCloseResults(); ( ( ResultSet ) iter.next() ).close(); } catch ( SQLException e ) { // no big deal log.warn( "Could not close a JDBC result set", e ); } catch ( ConcurrentModificationException e ) { // this has been shown to happen occasionally in rare cases // when using a transaction manager + transaction-timeout // where the timeout calls back through Hibernate's // registered transaction synchronization on a separate // "reaping" thread. In cases where that reaping thread // executes through this block at the same time the main // application thread does we can get into situations where // these CMEs occur. And though it is not "allowed" per-se, // the end result without handling it specifically is infinite // looping. So here, we simply break the loop log.info( "encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" ); break; } catch ( Throwable e ) { // sybase driver (jConnect) throwing NPE here in certain // cases, but we'll just handle the general "unexpected" case log.warn( "Could not close a JDBC result set", e ); } } resultSetsToClose.clear(); iter = statementsToClose.iterator(); while ( iter.hasNext() ) { try { closeQueryStatement( ( PreparedStatement ) iter.next() ); } catch ( ConcurrentModificationException e ) { // see explanation above... log.info( "encountered CME attempting to release batcher; assuming cause is tx-timeout scenario and ignoring" ); break; } catch ( SQLException e ) { // no big deal log.warn( "Could not close a JDBC statement", e ); } } statementsToClose.clear(); */ } public void close() { LOG.tracev( "Closing JDBC container [{0}]", this ); cleanup(); } @SuppressWarnings({ "unchecked" }) protected void close(Statement statement) { LOG.tracev( "Closing prepared statement [{0}]", statement ); if ( statement instanceof InvalidatableWrapper ) { InvalidatableWrapper<Statement> wrapper = ( InvalidatableWrapper<Statement> ) statement; close( wrapper.getWrappedObject() ); wrapper.invalidate(); return; } try { // if we are unable to "clean" the prepared statement, // we do not close it try { if ( statement.getMaxRows() != 0 ) { statement.setMaxRows( 0 ); } if ( statement.getQueryTimeout() != 0 ) { statement.setQueryTimeout( 0 ); } } catch( SQLException sqle ) { // there was a problem "cleaning" the prepared statement if ( LOG.isDebugEnabled() ) { LOG.debugf( "Exception clearing maxRows/queryTimeout [%s]", sqle.getMessage() ); } return; // EARLY EXIT!!! } statement.close(); if ( lastQuery == statement ) { lastQuery = null; } } catch( SQLException sqle ) { if ( LOG.isDebugEnabled() ) { LOG.debugf( "Unable to release statement [%s]", sqle.getMessage() ); } } } @SuppressWarnings({ "unchecked" }) protected void close(ResultSet resultSet) { LOG.tracev( "Closing result set [{0}]", resultSet ); if ( resultSet instanceof InvalidatableWrapper ) { InvalidatableWrapper<ResultSet> wrapper = (InvalidatableWrapper<ResultSet>) resultSet; close( wrapper.getWrappedObject() ); wrapper.invalidate(); } try { resultSet.close(); } catch( SQLException e ) { if ( LOG.isDebugEnabled() ) { LOG.debugf( "Unable to release result set [%s]", e.getMessage() ); } } } }