/*
* Copyright 2001-2008 Geert Bevin <gbevin[remove] at uwyn dot com>
* Licensed under the Apache License, Version 2.0 (the "License")
* $Id: DbBeanFetcher.java 3936 2008-04-26 12:05:37Z gbevin $
*/
package com.uwyn.rife.database;
import com.uwyn.rife.database.exceptions.BeanException;
import com.uwyn.rife.database.exceptions.DatabaseException;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* This class allows a {@link ResultSet} to be easily processed into bean
* instance.
* <p>Multiple instances can be collected into a list when processing an
* entire {@link ResultSet}, or as a single bean instance can be retrieved for
* one row of a {@link ResultSet}. The default behavior is to not collect
* instances.
*
* @author JR Boyens (jboyens[remove] at uwyn dot com)
* @author Geert Bevin (gbevin[remove] at uwyn dot com)
* @version $Revision: 3936 $
* @since 1.0
*/
public class DbBeanFetcher<BeanType> extends DbRowProcessor
{
private Datasource mDatasource = null;
private Class<BeanType> mBeanClass = null;
private BeanType mLastBeanInstance = null;
private HashMap<String, PropertyDescriptor> mBeanProperties = new HashMap<String, PropertyDescriptor>();
private ArrayList<BeanType> mCollectedInstances = null;
/**
* Create a new DbBeanFetcher
*
* @param datasource the datasource to be used
* @param beanClass the type of bean that will be handled
* @exception BeanException thrown if there is an error getting
* information about the bean via the beanClass
* @since 1.0
*/
public DbBeanFetcher(Datasource datasource, Class<BeanType> beanClass)
throws BeanException
{
this(datasource, beanClass, false);
}
/**
* Create a new DbBeanFetcher
*
* @param datasource the datasource to be used
* @param beanClass the type of bean that will be handled
* @param collectInstances <code>true</code> if the fetcher should
* collected the bean instances; <code>false</code> if otherwise
* @exception BeanException thrown if there is an error getting
* information about the bean via the beanClass
* @since 1.0
*/
public DbBeanFetcher(Datasource datasource, Class<BeanType> beanClass, boolean collectInstances)
throws BeanException
{
if (null == datasource) throw new IllegalArgumentException("datasource can't be null.");
if (null == beanClass) throw new IllegalArgumentException("beanClass can't be null.");
BeanInfo bean_info = null;
mDatasource = datasource;
mBeanClass = beanClass;
try
{
bean_info = Introspector.getBeanInfo(beanClass);
}
catch (IntrospectionException e)
{
throw new BeanException("Couldn't introspect the bean with class '"+mBeanClass.getName()+"'.", beanClass, e);
}
PropertyDescriptor[] bean_properties = bean_info.getPropertyDescriptors();
for (PropertyDescriptor bean_property : bean_properties)
{
mBeanProperties.put(bean_property.getName().toLowerCase(), bean_property);
}
if (collectInstances)
{
mCollectedInstances = new ArrayList<BeanType>();
}
assert mDatasource != null;
assert mBeanClass != null;
assert null == mLastBeanInstance;
assert mBeanProperties != null;
}
/**
* Process a ResultSet row into a bean. Call this method on a {@link
* ResultSet} and the resulting bean will be stored and be accessible
* via {@link #getBeanInstance()}
*
* @param resultSet the {@link ResultSet} from which to process the
* row
* @exception SQLException thrown when there is a problem processing
* the row
* @return <code>true</code> if a bean instance was retrieved; or
* <p><code>false</code> if otherwise
*/
public boolean processRow(ResultSet resultSet)
throws SQLException
{
if (null == resultSet) throw new IllegalArgumentException("resultSet can't be null.");
BeanType instance = null;
try
{
instance = mBeanClass.newInstance();
}
catch (InstantiationException e)
{
SQLException e2 = new SQLException("Can't instantiate a bean with class '"+mBeanClass.getName()+"' : "+e.getMessage());
e2.initCause(e);
throw e2;
}
catch (IllegalAccessException e)
{
SQLException e2 = new SQLException("No permission to instantiate a bean with class '"+mBeanClass.getName()+"' : "+e.getMessage());
e2.initCause(e);
throw e2;
}
ResultSetMetaData meta = resultSet.getMetaData();
String column_name = null;
String column_label = null;
for (int i = 1; i <= meta.getColumnCount(); i++)
{
column_name = meta.getColumnName(i).toLowerCase();
column_label = meta.getColumnLabel(i).toLowerCase();
if (mBeanProperties.containsKey(column_name))
{
populateBeanProperty(instance, column_name, meta, resultSet, i);
}
else if (mBeanProperties.containsKey(column_label))
{
populateBeanProperty(instance, column_label, meta, resultSet, i);
}
}
assert instance != null;
mLastBeanInstance = instance;
if (mCollectedInstances != null)
{
mCollectedInstances.add(instance);
}
return gotBeanInstance(instance);
}
private void populateBeanProperty(BeanType instance, String propertyName, ResultSetMetaData meta, ResultSet resultSet, int columnIndex) throws SQLException
{
PropertyDescriptor property = mBeanProperties.get(propertyName);
Method write_method = property.getWriteMethod();
if (write_method != null)
{
try
{
int column_type = meta.getColumnType(columnIndex);
Object typed_object;
try
{
typed_object = mDatasource.getSqlConversion().getTypedObject(resultSet, columnIndex, column_type, property.getPropertyType());
}
catch (DatabaseException e)
{
SQLException e2 = new SQLException("Data conversion error while obtaining the typed object.");
e2.initCause(e);
throw e2;
}
// the sql conversion couldn't create a typed value
if (null == typed_object)
{
// check if the object returned by the resultset is of the same type hierarchy as the property type
Object column_value = resultSet.getObject(columnIndex);
if (column_value != null &&
property.getPropertyType().isAssignableFrom(column_value.getClass()))
{
typed_object = column_value;
}
// otherwise try to call the property type's constructor with a string argument
else
{
String column_stringvalue = resultSet.getString(columnIndex);
if (column_stringvalue != null)
{
try
{
Constructor constructor = property.getPropertyType().getConstructor(new Class[] {String.class});
if (constructor != null)
{
typed_object = constructor.newInstance((Object[])new String[] {column_stringvalue});
}
}
catch (SecurityException e)
{
instance = null;
SQLException e2 = new SQLException("No permission to obtain the String constructor of the property with name '"+property.getName()+"' and class '"+property.getPropertyType().getName()+"' of the bean with class '"+mBeanClass.getName()+"'.");
e2.initCause(e);
throw e2;
}
catch (NoSuchMethodException e)
{
instance = null;
SQLException e2 = new SQLException("Couldn't find a String constructor for the property with name '"+property.getName()+"' and class '"+property.getPropertyType().getName()+"' of the bean with class '"+mBeanClass.getName()+"'.");
e2.initCause(e);
throw e2;
}
catch (InstantiationException e)
{
instance = null;
SQLException e2 = new SQLException("Can't instantiate a new instance of the property with name '"+property.getName()+"' and class '"+property.getPropertyType().getName()+"' of the bean with class '"+mBeanClass.getName()+"'.");
e2.initCause(e);
throw e2;
}
}
}
}
// if the typed object isn't null, set the value
if (typed_object != null)
{
// stored the property type
write_method.invoke(instance, new Object[] {typed_object});
}
}
catch (IllegalAccessException e)
{
instance = null;
SQLException e2 = new SQLException("No permission to invoke the '"+write_method.getName()+"' method on the bean with class '"+mBeanClass.getName()+"'.");
e2.initCause(e);
throw e2;
}
catch (IllegalArgumentException e)
{
instance = null;
SQLException e2 = new SQLException("Invalid arguments while invoking the '"+write_method.getName()+"' method on the bean with class '"+mBeanClass.getName()+"'.");
e2.initCause(e);
throw e2;
}
catch (InvocationTargetException e)
{
instance = null;
SQLException e2 = new SQLException("The '"+write_method.getName()+"' method of the bean with class '"+mBeanClass.getName()+"' has thrown an exception");
e2.initCause(e);
throw e2;
}
catch (SQLException e)
{
instance = null;
SQLException e2 = new SQLException("SQLException while invoking the '"+write_method.getName()+"' method of the bean with class '"+mBeanClass.getName()+"'");
e2.initCause(e);
throw e2;
}
}
}
/**
* Hook method that can be overloaded to receive new bean instances as
* they are retrieved, without relying on the internal collection into
* a list.
*
* @param instance the received bean instance
* @return <code>true</code> if the bean fetcher should continue to
* retrieve the next bean; or
* <p><code>false</code> if the retrieval should stop after this bean
* @since 1.0
*/
public boolean gotBeanInstance(BeanType instance)
{
return true;
}
/**
* Get the last processed bean instance
*
* @return the last processed bean instance
* @since 1.0
*/
public BeanType getBeanInstance()
{
return mLastBeanInstance;
}
/**
* Get the collected bean instances
*
* @return the collected bean instances
* @since 1.0
*/
public List<BeanType> getCollectedInstances()
{
return mCollectedInstances;
}
}