/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2013, 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.result.internal;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.hibernate.JDBCException;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.custom.CustomLoader;
import org.hibernate.loader.custom.CustomQuery;
import org.hibernate.loader.custom.sql.SQLQueryReturnProcessor;
import org.hibernate.loader.spi.AfterLoadAction;
import org.hibernate.result.NoMoreReturnsException;
import org.hibernate.result.Output;
import org.hibernate.result.Outputs;
import org.hibernate.result.spi.ResultContext;

import org.jboss.logging.Logger;

Author:Steve Ebersole
/** * @author Steve Ebersole */
public class OutputsImpl implements Outputs { private static final Logger log = CoreLogging.logger( OutputsImpl.class ); private final ResultContext context; private final PreparedStatement jdbcStatement; private final CustomLoaderExtension loader; private CurrentReturnState currentReturnState; public OutputsImpl(ResultContext context, PreparedStatement jdbcStatement) { this.context = context; this.jdbcStatement = jdbcStatement; // For now... but see the LoadPlan work; eventually this should just be a ResultSetProcessor. this.loader = buildSpecializedCustomLoader( context ); try { final boolean isResultSet = jdbcStatement.execute(); currentReturnState = buildCurrentReturnState( isResultSet ); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getMoreResults" ); } } private CurrentReturnState buildCurrentReturnState(boolean isResultSet) { int updateCount = -1; if ( ! isResultSet ) { try { updateCount = jdbcStatement.getUpdateCount(); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getUpdateCount" ); } } return buildCurrentReturnState( isResultSet, updateCount ); } protected CurrentReturnState buildCurrentReturnState(boolean isResultSet, int updateCount) { return new CurrentReturnState( isResultSet, updateCount ); } protected JDBCException convert(SQLException e, String message) { return context.getSession().getFactory().getSQLExceptionHelper().convert( e, message, context.getSql() ); } @Override public Output getCurrent() { if ( currentReturnState == null ) { return null; } return currentReturnState.getOutput(); } @Override public boolean goToNext() { if ( currentReturnState == null ) { return false; } if ( currentReturnState.indicatesMoreOutputs() ) // prepare the next return state try { final boolean isResultSet = jdbcStatement.getMoreResults(); currentReturnState = buildCurrentReturnState( isResultSet ); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getMoreResults" ); } // and return return currentReturnState != null && currentReturnState.indicatesMoreOutputs(); } @Override public void release() { try { jdbcStatement.close(); } catch (SQLException e) { log.debug( "Unable to close PreparedStatement", e ); } } private List extractCurrentResults() { try { return extractResults( jdbcStatement.getResultSet() ); } catch (SQLException e) { throw convert( e, "Error calling CallableStatement.getResultSet" ); } } protected List extractResults(ResultSet resultSet) { try { return loader.processResultSet( resultSet ); } catch (SQLException e) { throw convert( e, "Error extracting results from CallableStatement" ); } }
Encapsulates the information needed to interpret the current return within a result
/** * Encapsulates the information needed to interpret the current return within a result */
protected class CurrentReturnState { private final boolean isResultSet; private final int updateCount; private Output rtn; protected CurrentReturnState(boolean isResultSet, int updateCount) { this.isResultSet = isResultSet; this.updateCount = updateCount; } public boolean indicatesMoreOutputs() { return isResultSet() || getUpdateCount() >= 0; } public boolean isResultSet() { return isResultSet; } public int getUpdateCount() { return updateCount; } public Output getOutput() { if ( rtn == null ) { rtn = buildOutput(); } return rtn; } protected Output buildOutput() { if ( log.isDebugEnabled() ) { log.debugf( "Building Return [isResultSet=%s, updateCount=%s, extendedReturn=%s", isResultSet(), getUpdateCount(), hasExtendedReturns() ); } if ( isResultSet() ) { return buildResultSetOutput( extractCurrentResults() ); } else if ( getUpdateCount() >= 0 ) { return buildUpdateCountOutput( updateCount ); } else if ( hasExtendedReturns() ) { return buildExtendedReturn(); } throw new NoMoreReturnsException(); } // hooks for stored procedure (out param) processing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ protected Output buildResultSetOutput(List list) { return new ResultSetOutputImpl( list ); } protected Output buildUpdateCountOutput(int updateCount) { return new UpdateCountOutputImpl( updateCount ); } protected boolean hasExtendedReturns() { return false; } protected Output buildExtendedReturn() { throw new IllegalStateException( "State does not define extended returns" ); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Hooks into Hibernate's Loader hierarchy for ResultSet -> Object mapping private static CustomLoaderExtension buildSpecializedCustomLoader(final ResultContext context) { // might be better to just manually construct the Return(s).. SQLQueryReturnProcessor does a lot of // work that is really unnecessary here. final SQLQueryReturnProcessor processor = new SQLQueryReturnProcessor( context.getQueryReturns(), context.getSession().getFactory() ); processor.process(); final List<org.hibernate.loader.custom.Return> customReturns = processor.generateCustomReturns( false ); CustomQuery customQuery = new CustomQuery() { @Override public String getSQL() { return context.getSql(); } @Override public Set<String> getQuerySpaces() { return context.getSynchronizedQuerySpaces(); } @Override public Map getNamedParameterBindPoints() { // no named parameters in terms of embedded in the SQL string return null; } @Override public List<org.hibernate.loader.custom.Return> getCustomQueryReturns() { return customReturns; } }; return new CustomLoaderExtension( customQuery, context.getQueryParameters(), context.getSession() ); } private static class CustomLoaderExtension extends CustomLoader { private QueryParameters queryParameters; private SessionImplementor session; private boolean needsDiscovery = true; public CustomLoaderExtension( CustomQuery customQuery, QueryParameters queryParameters, SessionImplementor session) { super( customQuery, session.getFactory() ); this.queryParameters = queryParameters; this.session = session; } // todo : this would be a great way to add locking to stored procedure support (at least where returning entities). public List processResultSet(ResultSet resultSet) throws SQLException { if ( needsDiscovery ) { super.autoDiscoverTypes( resultSet ); // todo : EntityAliases discovery needsDiscovery = false; } return super.processResultSet( resultSet, queryParameters, session, true, null, Integer.MAX_VALUE, Collections.<AfterLoadAction>emptyList() ); } } }