001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: DbBeanFetcher.java 3701 2007-03-18 12:24:23Z gbevin $
007: */
008: package com.uwyn.rife.database;
009:
010: import com.uwyn.rife.database.exceptions.BeanException;
011: import com.uwyn.rife.database.exceptions.DatabaseException;
012: import java.beans.BeanInfo;
013: import java.beans.IntrospectionException;
014: import java.beans.Introspector;
015: import java.beans.PropertyDescriptor;
016: import java.lang.reflect.Constructor;
017: import java.lang.reflect.InvocationTargetException;
018: import java.lang.reflect.Method;
019: import java.sql.ResultSet;
020: import java.sql.ResultSetMetaData;
021: import java.sql.SQLException;
022: import java.sql.Types;
023: import java.util.ArrayList;
024: import java.util.HashMap;
025: import java.util.List;
026:
027: /**
028: * This class allows a {@link ResultSet} to be easily processed into bean
029: * instance.
030: * <p>Multiple instances can be collected into a list when processing an
031: * entire {@link ResultSet}, or as a single bean instance can be retrieved for
032: * one row of a {@link ResultSet}. The default behavior is to not collect
033: * instances.
034: *
035: * @author JR Boyens (jboyens[remove] at uwyn dot com)
036: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
037: * @version $Revision: 3701 $
038: * @since 1.0
039: */
040: public class DbBeanFetcher<BeanType> extends DbRowProcessor {
041: private Datasource mDatasource = null;
042: private Class<BeanType> mBeanClass = null;
043: private BeanType mLastBeanInstance = null;
044: private HashMap<String, PropertyDescriptor> mBeanProperties = new HashMap<String, PropertyDescriptor>();
045: private ArrayList<BeanType> mCollectedInstances = null;
046:
047: /**
048: * Create a new DbBeanFetcher
049: *
050: * @param datasource the datasource to be used
051: * @param beanClass the type of bean that will be handled
052: * @exception BeanException thrown if there is an error getting
053: * information about the bean via the beanClass
054: * @since 1.0
055: */
056: public DbBeanFetcher(Datasource datasource,
057: Class<BeanType> beanClass) throws BeanException {
058: this (datasource, beanClass, false);
059: }
060:
061: /**
062: * Create a new DbBeanFetcher
063: *
064: * @param datasource the datasource to be used
065: * @param beanClass the type of bean that will be handled
066: * @param collectInstances <code>true</code> if the fetcher should
067: * collected the bean instances; <code>false</code> if otherwise
068: * @exception BeanException thrown if there is an error getting
069: * information about the bean via the beanClass
070: * @since 1.0
071: */
072:
073: public DbBeanFetcher(Datasource datasource,
074: Class<BeanType> beanClass, boolean collectInstances)
075: throws BeanException {
076: if (null == datasource)
077: throw new IllegalArgumentException(
078: "datasource can't be null.");
079: if (null == beanClass)
080: throw new IllegalArgumentException(
081: "beanClass can't be null.");
082:
083: BeanInfo bean_info = null;
084:
085: mDatasource = datasource;
086: mBeanClass = beanClass;
087: try {
088: bean_info = Introspector.getBeanInfo(beanClass);
089: } catch (IntrospectionException e) {
090: throw new BeanException(
091: "Couldn't introspect the bean with class '"
092: + mBeanClass.getName() + "'.", beanClass, e);
093: }
094: PropertyDescriptor[] bean_properties = bean_info
095: .getPropertyDescriptors();
096: for (PropertyDescriptor bean_property : bean_properties) {
097: mBeanProperties.put(bean_property.getName().toLowerCase(),
098: bean_property);
099: }
100:
101: if (collectInstances) {
102: mCollectedInstances = new ArrayList<BeanType>();
103: }
104:
105: assert mDatasource != null;
106: assert mBeanClass != null;
107: assert null == mLastBeanInstance;
108: assert mBeanProperties != null;
109: }
110:
111: /**
112: * Process a ResultSet row into a bean. Call this method on a {@link
113: * ResultSet} and the resulting bean will be stored and be accessible
114: * via {@link #getBeanInstance()}
115: *
116: * @param resultSet the {@link ResultSet} from which to process the
117: * row
118: * @exception SQLException thrown when there is a problem processing
119: * the row
120: * @return <code>true</code> if a bean instance was retrieved; or
121: * <p><code>false</code> if otherwise
122: */
123: public boolean processRow(ResultSet resultSet) throws SQLException {
124: if (null == resultSet)
125: throw new IllegalArgumentException(
126: "resultSet can't be null.");
127:
128: BeanType instance = null;
129: try {
130: instance = mBeanClass.newInstance();
131: } catch (InstantiationException e) {
132: SQLException e2 = new SQLException(
133: "Can't instantiate a bean with class '"
134: + mBeanClass.getName() + "' : "
135: + e.getMessage());
136: e2.initCause(e);
137: throw e2;
138: } catch (IllegalAccessException e) {
139: SQLException e2 = new SQLException(
140: "No permission to instantiate a bean with class '"
141: + mBeanClass.getName() + "' : "
142: + e.getMessage());
143: e2.initCause(e);
144: throw e2;
145: }
146:
147: ResultSetMetaData meta = resultSet.getMetaData();
148: String column_name = null;
149: PropertyDescriptor property = null;
150: Method write_method = null;
151: int column_type = Types.OTHER;
152: Object typed_object = null;
153:
154: for (int i = 1; i <= meta.getColumnCount(); i++) {
155: column_name = meta.getColumnName(i).toLowerCase();
156: if (mBeanProperties.containsKey(column_name)) {
157: property = mBeanProperties.get(column_name);
158:
159: write_method = property.getWriteMethod();
160: if (write_method != null) {
161: try {
162: column_type = meta.getColumnType(i);
163: try {
164: typed_object = mDatasource
165: .getSqlConversion().getTypedObject(
166: resultSet, i, column_type,
167: property.getPropertyType());
168: } catch (DatabaseException e) {
169: SQLException e2 = new SQLException(
170: "Data conversion error while obtaining the typed object.");
171: e2.initCause(e);
172: throw e2;
173: }
174:
175: // the sql conversion couldn't create a typed value
176: if (null == typed_object) {
177: // check if the object returned by the resultset is of the same type hierarchy as the property type
178: Object column_value = resultSet
179: .getObject(i);
180: if (column_value != null
181: && property
182: .getPropertyType()
183: .isAssignableFrom(
184: column_value
185: .getClass())) {
186: typed_object = column_value;
187: }
188: // otherwise try to call the property type's constructor with a string argument
189: else {
190: String column_stringvalue = resultSet
191: .getString(i);
192: if (column_stringvalue != null) {
193: try {
194: Constructor constructor = property
195: .getPropertyType()
196: .getConstructor(
197: new Class[] { String.class });
198: if (constructor != null) {
199: typed_object = constructor
200: .newInstance((Object[]) new String[] { column_stringvalue });
201: }
202: } catch (SecurityException e) {
203: instance = null;
204: SQLException e2 = new SQLException(
205: "No permission to obtain the String constructor of the property with name '"
206: + property
207: .getName()
208: + "' and class '"
209: + property
210: .getPropertyType()
211: .getName()
212: + "' of the bean with class '"
213: + mBeanClass
214: .getName()
215: + "'.");
216: e2.initCause(e);
217: throw e2;
218: } catch (NoSuchMethodException e) {
219: instance = null;
220: SQLException e2 = new SQLException(
221: "Couldn't find a String constructor for the property with name '"
222: + property
223: .getName()
224: + "' and class '"
225: + property
226: .getPropertyType()
227: .getName()
228: + "' of the bean with class '"
229: + mBeanClass
230: .getName()
231: + "'.");
232: e2.initCause(e);
233: throw e2;
234: } catch (InstantiationException e) {
235: instance = null;
236: SQLException e2 = new SQLException(
237: "Can't instantiate a new instance of the property with name '"
238: + property
239: .getName()
240: + "' and class '"
241: + property
242: .getPropertyType()
243: .getName()
244: + "' of the bean with class '"
245: + mBeanClass
246: .getName()
247: + "'.");
248: e2.initCause(e);
249: throw e2;
250: }
251: }
252: }
253: }
254:
255: // if the typed object isn't null, set the value
256: if (typed_object != null) {
257: // stored the property type
258: write_method.invoke(instance,
259: new Object[] { typed_object });
260: }
261: } catch (IllegalAccessException e) {
262: instance = null;
263: SQLException e2 = new SQLException(
264: "No permission to invoke the '"
265: + write_method.getName()
266: + "' method on the bean with class '"
267: + mBeanClass.getName() + "'.");
268: e2.initCause(e);
269: throw e2;
270: } catch (IllegalArgumentException e) {
271: instance = null;
272: SQLException e2 = new SQLException(
273: "Invalid arguments while invoking the '"
274: + write_method.getName()
275: + "' method on the bean with class '"
276: + mBeanClass.getName() + "'.");
277: e2.initCause(e);
278: throw e2;
279: } catch (InvocationTargetException e) {
280: instance = null;
281: SQLException e2 = new SQLException("The '"
282: + write_method.getName()
283: + "' method of the bean with class '"
284: + mBeanClass.getName()
285: + "' has thrown an exception");
286: e2.initCause(e);
287: throw e2;
288: } catch (SQLException e) {
289: instance = null;
290: SQLException e2 = new SQLException(
291: "SQLException while invoking the '"
292: + write_method.getName()
293: + "' method of the bean with class '"
294: + mBeanClass.getName() + "'");
295: e2.initCause(e);
296: throw e2;
297: }
298: }
299: }
300: }
301:
302: assert instance != null;
303:
304: mLastBeanInstance = instance;
305:
306: if (mCollectedInstances != null) {
307: mCollectedInstances.add(instance);
308: }
309:
310: return gotBeanInstance(instance);
311:
312: }
313:
314: /**
315: * Hook method that can be overloaded to receive new bean instances as
316: * they are retrieved, without relying on the internal collection into
317: * a list.
318: *
319: * @param instance the received bean instance
320: * @return <code>true</code> if the bean fetcher should continue to
321: * retrieve the next bean; or
322: * <p><code>false</code> if the retrieval should stop after this bean
323: * @since 1.0
324: */
325: public boolean gotBeanInstance(BeanType instance) {
326: return true;
327: }
328:
329: /**
330: * Get the last processed bean instance
331: *
332: * @return the last processed bean instance
333: * @since 1.0
334: */
335: public BeanType getBeanInstance() {
336: return mLastBeanInstance;
337: }
338:
339: /**
340: * Get the collected bean instances
341: *
342: * @return the collected bean instances
343: * @since 1.0
344: */
345: public List<BeanType> getCollectedInstances() {
346: return mCollectedInstances;
347: }
348: }
|