
com.moparisthebest.jdbc.ResultSetMapper Maven / Gradle / Ivy
The newest version!
package com.moparisthebest.jdbc;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* $Header:$
*/
import java.lang.ref.SoftReference;
import java.lang.reflect.*;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.*;
/**
* Default ResultSetMapper implementation for Objects.
*
* This class is heavily modified from org.apache.beehive.controls.system.jdbc.DefaultObjectResultSetMapper
*
* This uses CustomRowToObjectMapper to perform the actual mapping to individual classes, so look to that
* implementation for details.
*
* This supports mapping to a single object, or collections of Objects, currently it supports:
* 1. arrays
* 2. anything implementing java.util.Collection with a public no-arg constructor
* 3. anything implementing java.util.Map with a public no-arg constructor
* a. The first column in the ResultSet will be the Map's key
* b. If there are only two columns, the second will be the Map's value
* c. If there are more than two columns, the value will be mapped to an object with the entire ResultSet in it,
* including the key, just like returning a Collection would do
* 4. Iterators returned by a Collection, or ListIterators returned by a List
*
* It has sensible default implementations to return if you specify an interface, look in Map 'interfaceToConcrete'
* for the default values returned for specific interfaces. If not specified in there, Map's default is HashMap,
* while Collection's default is ArrayList.
*
* Anything else must be a concrete class with a public no-arg constructor to instantiate it
*
* @author Travis Burtrum (modifications from beehive)
*/
public class ResultSetMapper {
public static final Map interfaceToConcrete = Collections.unmodifiableMap(new HashMap() {{
// Collection's
put(Collection.class, ArrayList.class);
// List
put(List.class, ArrayList.class);
// Set's
put(Set.class, HashSet.class);
// SortedSet's
put(SortedSet.class, TreeSet.class);
put(NavigableSet.class, ConcurrentSkipListSet.class);
// Queue's
put(Queue.class, LinkedList.class);
put(Deque.class, LinkedList.class);
// BlockingQueue's
put(BlockingQueue.class, LinkedBlockingQueue.class);
put(BlockingDeque.class, LinkedBlockingDeque.class);
// Map's
put(Map.class, HashMap.class);
// ConcurrentMap's
put(ConcurrentMap.class, ConcurrentHashMap.class);
put(ConcurrentNavigableMap.class, ConcurrentSkipListMap.class);
// SortedMap's
put(SortedMap.class, TreeMap.class);
put(NavigableMap.class, TreeMap.class);
}});
@SuppressWarnings({"unchecked"})
public static Class extends T> getConcreteClass(final Class returnType, final Class defaultConcreteClass) {
int classModifiers = returnType.getModifiers();
if (Modifier.isInterface(classModifiers) || Modifier.isAbstract(classModifiers)) {
Class extends T> concrete = (Class extends T>) interfaceToConcrete.get(returnType);
return concrete == null ? (Class extends T>) defaultConcreteClass : concrete;
}
return returnType;
}
public static T instantiateClass(final Class returnType, final Class defaultConcreteClass) {
try {
return getConcreteClass(returnType, defaultConcreteClass).newInstance();
} catch (Throwable e) {
throw new MapperException("Failed to instantiate class of type " + returnType.getName(), e);
}
}
private final Calendar cal;
private final int arrayMaxLength;
public ResultSetMapper(Calendar cal, int arrayMaxLength) {
this.cal = cal;
this.arrayMaxLength = arrayMaxLength;
}
public ResultSetMapper() {
this(-1);
}
public ResultSetMapper(int arrayMaxLength) {
this(null, arrayMaxLength);
}
protected T privToObject(ResultSet rs, Class componentType, Calendar cal, Class> mapValType) {
T ret = null;
try {
if (rs.next())
ret = getRowMapper(rs, componentType, cal, mapValType).mapRowToReturnType();
} catch (SQLException e) {
// ignore
}
tryClose(rs);
return ret == null ? RowToObjectMapper.fixNull(componentType) : ret;
}
protected , E> T privToCollection(ResultSet rs, final Class collectionType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToCollection(rs, instantiateClass(collectionType, ArrayList.class), componentType, arrayMaxLength, cal, mapValType);
}
/**
* Invoked when the return type of the method is an array type.
*
* @param rs ResultSet to process.
* @param componentType The class of object contained within the array
* @param arrayMaxLength The maximum size of array to create, a value of 0 indicates that the array
* size will be the same as the result set size (no limit).
* @param cal A calendar instance to use for date/time values
* @return An array of the specified class type
*/
protected , E> T privToCollection(ResultSet rs, T list, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
int numRows = 0;
// dereference list in an attempt to gracefully avoid OutOfMemoryError's
final SoftReference softList = new SoftReference(list);
list = null;
try {
// a value of less than 1 indicates that all rows from the ResultSet should be included.
final boolean unlimitedRows = arrayMaxLength < 1;
final RowToObjectMapper rowMapper = getRowMapper(rs, componentType, cal, mapValType);
for (; (unlimitedRows || numRows != arrayMaxLength) && rs.next(); ++numRows) {
E object = rowMapper.mapRowToReturnType();
list = softList.get();
if (list == null)
throw new OutOfMemoryError();
list.add(object);
list = null;
}
if (!unlimitedRows)
warnOnMaxLength(numRows, arrayMaxLength, rs);
list = softList.get();
if (list == null)
throw new OutOfMemoryError();
// if this list is Finishable, call finish()
if (list instanceof Finishable)
((Finishable) list).finish(rs);
tryClose(rs);
return list;
} catch(OutOfMemoryError e){
tryClose(rs);
throw new MapperException(String.format("Too many rows (processed %d, max %d), ran out of memory.", numRows, arrayMaxLength), e);
} catch(Throwable e) {
if(list == null)
list = softList.get();
String columnName = "UNKNOWN";
String columnType = "UNKNOWN";
String returnedType = "UNKNOWN";
int badColumn = 0;
try {
ResultSetMetaData md = rs.getMetaData();
badColumn = e.getCause() instanceof SQLExceptionColumnNum ? ((SQLExceptionColumnNum)e.getCause()).getColumnNum() : 1;
columnName = md.getColumnName(badColumn);
columnType = md.getColumnTypeName(badColumn);
returnedType = ((list != null && !list.isEmpty()) ? list.iterator().next() : rs.getObject(badColumn)).getClass().getName();
} catch (Throwable t) {
// ignore
}
tryClose(rs);
// assuming no errors in resultSetObject() this can only happen
// for single column result sets.
throw new MapperException("The declared Java type for "
+ String.format("%s<%s>"
, (list != null ? list.getClass() : "UnknownCollection")
, getComponentName(componentType, mapValType))
+ " is incompatible with the SQL format of column " + badColumn + " '" + columnName
+ "' (" + columnType + ") which returns objects of type " + returnedType,
e);
}
}
protected , K, E> T privToMap(ResultSet rs, final Class returnType, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToMap(rs, instantiateClass(returnType, HashMap.class), mapKeyType, componentType, arrayMaxLength, cal, mapValType);
}
/**
* Invoked when the return type of the method is a Map type.
*
* @param rs ResultSet to process.
* @param arrayMaxLength The maximum size of array to create, a value of 0 indicates that the array
* size will be the same as the result set size (no limit).
* @param cal A calendar instance to use for date/time values
* @return An array of the specified class type
*/
protected , K, E> T privToMap(ResultSet rs, T map, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
int numRows = 0;
// dereference map in an attempt to gracefully avoid OutOfMemoryError's
final SoftReference softMap = new SoftReference(map);
map = null;
try {
// a value of less than 1 indicates that all rows from the ResultSet should be included.
final boolean unlimitedRows = arrayMaxLength < 1;
final RowToObjectMapper rowMapper = getRowMapper(rs, componentType, cal, mapValType);
boolean onlyTwoColumns = rowMapper.getColumnCount() == 2;
for (; (unlimitedRows || numRows != arrayMaxLength) && rs.next(); ++numRows) {
K key = rowMapper.extractColumnValue(1, mapKeyType);
E value = onlyTwoColumns ? rowMapper.extractColumnValue(2, componentType) : rowMapper.mapRowToReturnType();
map = softMap.get();
if (map == null)
throw new OutOfMemoryError();
map.put(key, value);
map = null;
}
if (!unlimitedRows)
warnOnMaxLength(numRows, arrayMaxLength, rs);
map = softMap.get();
if (map == null)
throw new OutOfMemoryError();
// if this map is Finishable, call finish()
if (map instanceof Finishable)
((Finishable) map).finish(rs);
tryClose(rs);
return map;
} catch(OutOfMemoryError e){
tryClose(rs);
throw new MapperException(String.format("Too many rows (processed %d, max %d), ran out of memory.", numRows, arrayMaxLength), e);
}catch (Throwable e) {
if(map == null)
map = softMap.get();
String columnName = "UNKNOWN";
String columnType = "UNKNOWN";
String returnedType = "UNKNOWN";
int badColumn = 0;
try {
ResultSetMetaData md = rs.getMetaData();
badColumn = e.getCause() instanceof SQLExceptionColumnNum ? ((SQLExceptionColumnNum)e.getCause()).getColumnNum() : 1;
columnName = md.getColumnName(badColumn);
columnType = md.getColumnTypeName(badColumn);
returnedType = ((map != null && !map.isEmpty()) ? map.values().iterator().next() : rs.getObject(badColumn)).getClass().getName();
} catch (Throwable t) {
// ignore
}
tryClose(rs);
// assuming no errors in resultSetObject() this can only happen
// for single column result sets.
throw new MapperException("The declared Java type for "
+ String.format("%s<%s, %s>"
, (map != null ? map.getClass() : "UnknownMap")
, mapKeyType != null ? mapKeyType.getName() : "UNKNOWN"
, getComponentName(componentType, mapValType))
+ " is incompatible with the SQL format of column "+ badColumn + " '" + columnName
+ "' (" + columnType + ") which returns objects of type " + returnedType,
e);
}
}
protected , K, E extends Collection, C> T privToMapCollection(ResultSet rs, final Class returnType, Class mapKeyType, Class collectionType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToMapCollection(rs, instantiateClass(returnType, HashMap.class), mapKeyType, collectionType, componentType, arrayMaxLength, cal, mapValType);
}
/**
* Invoked when the return type of the method is a Map type with a list for a value.
*
* @param rs ResultSet to process.
* @param arrayMaxLength The maximum size of array to create, a value of 0 indicates that the array
* size will be the same as the result set size (no limit).
* @param cal A calendar instance to use for date/time values
* @return An array of the specified class type
*/
protected , K, E extends Collection, C> T privToMapCollection(ResultSet rs, T map, Class mapKeyType, Class collectionType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
int numRows = 0;
// dereference map in an attempt to gracefully avoid OutOfMemoryError's
final SoftReference softMap = new SoftReference(map);
map = null;
try {
// a value of less than 1 indicates that all rows from the ResultSet should be included.
final boolean unlimitedRows = arrayMaxLength < 1;
final RowToObjectMapper rowMapper = getRowMapper(rs, componentType, cal, mapValType);
boolean onlyTwoColumns = rowMapper.getColumnCount() == 2;
for (; (unlimitedRows || numRows != arrayMaxLength) && rs.next(); ++numRows) {
K key = rowMapper.extractColumnValue(1, mapKeyType);
C value = onlyTwoColumns ? rowMapper.extractColumnValue(2, componentType) : rowMapper.mapRowToReturnType();
map = softMap.get();
if (map == null)
throw new OutOfMemoryError();
E list = map.get(key);
if(list == null){
list = instantiateClass(collectionType, ArrayList.class);
map.put(key, list);
}
list.add(value);
map = null;
}
if (!unlimitedRows)
warnOnMaxLength(numRows, arrayMaxLength, rs);
map = softMap.get();
if (map == null)
throw new OutOfMemoryError();
// if this map is Finishable, call finish()
if (map instanceof Finishable)
((Finishable) map).finish(rs);
tryClose(rs);
return map;
} catch(OutOfMemoryError e){
tryClose(rs);
throw new MapperException(String.format("Too many rows (processed %d, max %d), ran out of memory.", numRows, arrayMaxLength), e);
}catch (Throwable e) {
if(map == null)
map = softMap.get();
String columnName = "UNKNOWN";
String columnType = "UNKNOWN";
String returnedType = "UNKNOWN";
int badColumn = 0;
try {
ResultSetMetaData md = rs.getMetaData();
badColumn = e.getCause() instanceof SQLExceptionColumnNum ? ((SQLExceptionColumnNum)e.getCause()).getColumnNum() : 1;
columnName = md.getColumnName(badColumn);
columnType = md.getColumnTypeName(badColumn);
returnedType = ((map != null && !map.isEmpty()) ? map.values().iterator().next() : rs.getObject(badColumn)).getClass().getName();
} catch (Throwable t) {
// ignore
}
tryClose(rs);
// assuming no errors in resultSetObject() this can only happen
// for single column result sets.
throw new MapperException("The declared Java type for "
+ String.format("%s<%s, %s>"
, (map != null ? map.getClass() : "UnknownMap")
, mapKeyType != null ? mapKeyType.getName() : "UNKNOWN"
, getComponentName(componentType, mapValType))
+ " is incompatible with the SQL format of column "+ badColumn + " '" + columnName
+ "' (" + columnType + ") which returns objects of type " + returnedType,
e);
}
}
// methods using toMapCollection to return different types
protected ListIterator privToListIterator(ResultSet rs, final Class type, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToList(rs, type, arrayMaxLength, cal, mapValType).listIterator();
}
protected Iterator privToIterator(ResultSet rs, final Class type, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToList(rs, type, arrayMaxLength, cal, mapValType).iterator();
}
@SuppressWarnings({"unchecked"})
protected T[] privToArray(ResultSet rs, final Class type, int arrayMaxLength, Calendar cal, Class> mapValType) {
List list = privToList(rs, type, arrayMaxLength, cal, mapValType);
return list.toArray((T[]) Array.newInstance(type, list.size()));
}
protected List privToList(ResultSet rs, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToCollection(rs, new ArrayList(), componentType, arrayMaxLength, cal, mapValType);
}
protected Map privToMap(ResultSet rs, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToMap(rs, new HashMap(), mapKeyType, componentType, arrayMaxLength, cal, mapValType);
}
protected , C> Map privToMapCollection(ResultSet rs, Class mapKeyType, Class collectionType, Class componentType, int arrayMaxLength, Calendar cal, Class> mapValType) {
return privToMapCollection(rs, new HashMap(), mapKeyType, collectionType, componentType, arrayMaxLength, cal, mapValType);
}
// fairly un-interesting methods below here
protected RowToObjectMapper getRowMapper(ResultSet resultSet, Class returnTypeClass, Calendar cal, Class> mapValType) {
return new RowToObjectMapper(resultSet, returnTypeClass, cal, mapValType);
}
protected void warnOnMaxLength(final int numRows, final int arrayMaxLength, final ResultSet rs) {
if (numRows < arrayMaxLength)
return;
int totalRows = numRows;
try {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
rs.last();
totalRows = rs.getRow();
} else {
totalRows = rs.getRow();
while (rs.next())
++totalRows;
}
if (totalRows == 0)
totalRows = numRows;
} catch (Throwable e) {
e.printStackTrace();
}
System.err.printf("This JdbcControl query returned %d rows, %s arrayMaxLength (%d), which you most likely never want to happen, investigate!!!!\n",
totalRows, totalRows == numRows ? "equaling" : "exceeding", arrayMaxLength);
Thread.dumpStack();
}
// overloaded helper methods
public T toObject(ResultSet rs, Class componentType, Calendar cal) {
return privToObject(rs, componentType, cal, null);
}
public , E> T toCollection(ResultSet rs, final Class collectionType, Class componentType, int arrayMaxLength, Calendar cal) {
return privToCollection(rs, collectionType, componentType, arrayMaxLength, cal, null);
}
public , E> T toCollection(ResultSet rs, T list, Class componentType, int arrayMaxLength, Calendar cal) {
return privToCollection(rs, list, componentType, arrayMaxLength, cal, null);
}
public , K, E> T toMap(ResultSet rs, final Class returnType, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal) {
return privToMap(rs, returnType, mapKeyType, componentType, arrayMaxLength, cal, null);
}
public , K, E> T toMap(ResultSet rs, T map, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal) {
return privToMap(rs, map, mapKeyType, componentType, arrayMaxLength, cal, null);
}
public , K, E extends Collection, C> T toMapCollection(ResultSet rs, final Class returnType, Class mapKeyType, Class collectionType, Class componentType, int arrayMaxLength, Calendar cal) {
return privToMapCollection(rs, returnType, mapKeyType, collectionType, componentType, arrayMaxLength, cal, null);
}
public , K, E extends Collection, C> T toMapCollection(ResultSet rs, T map, Class mapKeyType, Class collectionType, Class componentType, int arrayMaxLength, Calendar cal) {
return privToMapCollection(rs, map, mapKeyType, collectionType, componentType, arrayMaxLength, cal, null);
}
public ListIterator toListIterator(ResultSet rs, final Class type, int arrayMaxLength, Calendar cal) {
return privToListIterator(rs, type, arrayMaxLength, cal, null);
}
public Iterator toIterator(ResultSet rs, final Class type, int arrayMaxLength, Calendar cal) {
return privToIterator(rs, type, arrayMaxLength, cal, null);
}
public T[] toArray(ResultSet rs, final Class type, int arrayMaxLength, Calendar cal) {
return privToArray(rs, type, arrayMaxLength, cal, null);
}
/**
* Returns a simple List of componentType
*/
public List toList(ResultSet rs, Class componentType, int arrayMaxLength, Calendar cal) {
return privToList(rs, componentType, arrayMaxLength, cal, null);
}
/**
* Returns a simple Map of mapKeyType -> componentType
*/
public Map toMap(ResultSet rs, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal) {
return privToMap(rs, mapKeyType, componentType, arrayMaxLength, cal, null);
}
/**
* Returns a simple Map of mapKeyType -> List
*/
@SuppressWarnings({"unchecked"})
public , C> Map toMapList(ResultSet rs, Class mapKeyType, Class componentType, int arrayMaxLength, Calendar cal) {
return (Map) privToMapCollection(rs, mapKeyType, List.class, componentType, arrayMaxLength, cal, null);
}
// map methods
// the following 6 methods I can find no way to not require an unchecked cast on the object being returned
// please find a way and let me know, until then, I won't provide overloaded methods for them
public , E extends Map, V> T toCollectionMap(ResultSet rs, final Class collectionType, Class componentType, Class mapValType, int arrayMaxLength, Calendar cal) {
return privToCollection(rs, collectionType, componentType, arrayMaxLength, cal, mapValType);
}
public , E extends Map, V> T toCollectionMap(ResultSet rs, T list, Class componentType, Class mapValType, int arrayMaxLength, Calendar cal) {
return privToCollection(rs, list, componentType, arrayMaxLength, cal, mapValType);
}
public , K, E extends Map, V> T toMapMap(ResultSet rs, final Class returnType, Class mapKeyType, Class componentType, Class mapValType, int arrayMaxLength, Calendar cal) {
return privToMap(rs, returnType, mapKeyType, componentType, arrayMaxLength, cal, mapValType);
}
public , K, E extends Map, V> T toMapMap(ResultSet rs, T map, Class mapKeyType, Class componentType, Class mapValType, int arrayMaxLength, Calendar cal) {
return privToMap(rs, map, mapKeyType, componentType, arrayMaxLength, cal, mapValType);
}
public , K, C extends Collection, E extends Map, V> T toMapCollectionMap(ResultSet rs, final Class returnType, Class mapKeyType, Class collectionType, Class componentType, Class mapValType, int arrayMaxLength, Calendar cal) {
return privToMapCollection(rs, returnType, mapKeyType, collectionType, componentType, arrayMaxLength, cal, mapValType);
}
public , K, C extends Collection, E extends Map, V> T toMapCollectionMap(ResultSet rs, T map, Class mapKeyType, Class collectionType, Class componentType, Class mapValType, int arrayMaxLength, Calendar cal) {
return privToMapCollection(rs, map, mapKeyType, collectionType, componentType, arrayMaxLength, cal, mapValType);
}
public , V> Map toSingleMap(ResultSet rs, Class componentType, Class mapValType, Calendar cal) {
return privToObject(rs, componentType, cal, mapValType);
}
@SuppressWarnings({"unchecked"})
public , V> ListIterator