001/*
002 * Copyright 2002-2015 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.jdbc.core;
018
019import java.sql.ResultSet;
020import java.sql.SQLException;
021import javax.sql.rowset.CachedRowSet;
022import javax.sql.rowset.RowSetFactory;
023import javax.sql.rowset.RowSetProvider;
024
025import org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet;
026import org.springframework.jdbc.support.rowset.SqlRowSet;
027import org.springframework.lang.UsesJava7;
028import org.springframework.util.ClassUtils;
029
030/**
031 * {@link ResultSetExtractor} implementation that returns a Spring {@link SqlRowSet}
032 * representation for each given {@link ResultSet}.
033 *
034 * <p>The default implementation uses a standard JDBC CachedRowSet underneath.
035 * This means that JDBC RowSet support needs to be available at runtime:
036 * by default, Sun's {@code com.sun.rowset.CachedRowSetImpl} class on Java 6,
037 * or the {@code javax.sql.rowset.RowSetProvider} mechanism on Java 7+ / JDBC 4.1+.
038 *
039 * @author Juergen Hoeller
040 * @since 1.2
041 * @see #newCachedRowSet
042 * @see org.springframework.jdbc.support.rowset.SqlRowSet
043 * @see JdbcTemplate#queryForRowSet(String)
044 * @see javax.sql.rowset.CachedRowSet
045 */
046public class SqlRowSetResultSetExtractor implements ResultSetExtractor<SqlRowSet> {
047
048        private static final CachedRowSetFactory cachedRowSetFactory;
049
050        static {
051                if (ClassUtils.isPresent("javax.sql.rowset.RowSetProvider",
052                                SqlRowSetResultSetExtractor.class.getClassLoader())) {
053                        // using JDBC 4.1 RowSetProvider, available on JDK 7+
054                        cachedRowSetFactory = new StandardCachedRowSetFactory();
055                }
056                else {
057                        // JDBC 4.1 API not available - fall back to Sun CachedRowSetImpl on JDK 6
058                        cachedRowSetFactory = new SunCachedRowSetFactory();
059                }
060        }
061
062
063        @Override
064        public SqlRowSet extractData(ResultSet rs) throws SQLException {
065                return createSqlRowSet(rs);
066        }
067
068        /**
069         * Create a SqlRowSet that wraps the given ResultSet,
070         * representing its data in a disconnected fashion.
071         * <p>This implementation creates a Spring ResultSetWrappingSqlRowSet
072         * instance that wraps a standard JDBC CachedRowSet instance.
073         * Can be overridden to use a different implementation.
074         * @param rs the original ResultSet (connected)
075         * @return the disconnected SqlRowSet
076         * @throws SQLException if thrown by JDBC methods
077         * @see #newCachedRowSet
078         * @see org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet
079         */
080        protected SqlRowSet createSqlRowSet(ResultSet rs) throws SQLException {
081                CachedRowSet rowSet = newCachedRowSet();
082                rowSet.populate(rs);
083                return new ResultSetWrappingSqlRowSet(rowSet);
084        }
085
086        /**
087         * Create a new CachedRowSet instance, to be populated by
088         * the {@code createSqlRowSet} implementation.
089         * <p>The default implementation uses JDBC 4.1's RowSetProvider
090         * when running on JDK 7 or higher, falling back to Sun's
091         * {@code com.sun.rowset.CachedRowSetImpl} class on older JDKs.
092         * @return a new CachedRowSet instance
093         * @throws SQLException if thrown by JDBC methods
094         * @see #createSqlRowSet
095         */
096        protected CachedRowSet newCachedRowSet() throws SQLException {
097                return cachedRowSetFactory.createCachedRowSet();
098        }
099
100
101        /**
102         * Internal strategy interface for the creation of CachedRowSet instances.
103         */
104        private interface CachedRowSetFactory {
105
106                CachedRowSet createCachedRowSet() throws SQLException;
107        }
108
109
110        /**
111         * Inner class to avoid a hard dependency on JDBC 4.1 RowSetProvider class.
112         */
113        @UsesJava7
114        private static class StandardCachedRowSetFactory implements CachedRowSetFactory {
115
116                private final RowSetFactory rowSetFactory;
117
118                public StandardCachedRowSetFactory() {
119                        try {
120                                this.rowSetFactory = RowSetProvider.newFactory();
121                        }
122                        catch (SQLException ex) {
123                                throw new IllegalStateException("Cannot create RowSetFactory through RowSetProvider", ex);
124                        }
125                }
126
127                @Override
128                public CachedRowSet createCachedRowSet() throws SQLException {
129                        return this.rowSetFactory.createCachedRowSet();
130                }
131        }
132
133
134        /**
135         * Inner class to avoid a hard dependency on Sun's CachedRowSetImpl class.
136         */
137        private static class SunCachedRowSetFactory implements CachedRowSetFactory {
138
139                private static final Class<?> implementationClass;
140
141                static {
142                        try {
143                                implementationClass = ClassUtils.forName("com.sun.rowset.CachedRowSetImpl",
144                                                SqlRowSetResultSetExtractor.class.getClassLoader());
145                        }
146                        catch (Throwable ex) {
147                                throw new IllegalStateException(ex);
148                        }
149                }
150
151                @Override
152                public CachedRowSet createCachedRowSet() throws SQLException {
153                        try {
154                                return (CachedRowSet) implementationClass.newInstance();
155                        }
156                        catch (Throwable ex) {
157                                throw new IllegalStateException(ex);
158                        }
159                }
160        }
161
162}