KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > jodd > db > orm > mapper > DefaultResultSetMapper


1 // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
2

3 package jodd.db.orm.mapper;
4
5 import jodd.bean.BeanUtil;
6 import jodd.typeconverter.TypeConverterManager;
7 import jodd.db.orm.DbOrm;
8 import jodd.db.orm.DbOrmException;
9 import jodd.db.orm.DbEntityDescriptor;
10 import jodd.util.collection.HashBag;
11 import jodd.util.collection.Bag;
12
13 import java.sql.ResultSet JavaDoc;
14 import java.sql.ResultSetMetaData JavaDoc;
15 import java.sql.SQLException JavaDoc;
16 import java.util.LinkedHashMap JavaDoc;
17 import java.util.Map JavaDoc;
18
19 /**
20  * Maps all columns of database result set (RS) row to objects.
21  * It does it in two steps: preparation (reading table and column names)
22  * and parsing (parsing one result set row to resulting objects).
23  *
24  * <p>
25  * <b>Preparation</b><br>
26  * Default mapper reads RS column and table names from RS meta-data and external maps, if provided.
27  * Since column name is always availiable in RS meta-data, it may be used to hold table name information.
28  * Column names may contain table code separator ({@link jodd.db.orm.DbOrm#getColumnAliasSeparator()} that
29  * divides column name to: table reference and column name. Here, table reference may be either table name or
30  * table alias. When it is table alias, external alias-to-name map must be provided.
31  * Hence, this defines the table name, and there is no need to read it from RS meta-data.
32  *
33  * <p>
34  * When column name doesn't contain a separator, it may be either a real column name, or a column code.
35  * For column codes, both table and colum name is lookuped from external map. If column name is a real column name,
36  * table information is read from the RS meta data. Unfortunately, some DBs (such Oracle) doesn't implements
37  * this simple JDBC feature. Therefore, it must be expected that column table name is not availiable.
38  *
39  * <p>
40  * Table name is also not availiable for columns which are not directly table columns:
41  * e.g. some calculations etc.
42  *
43  * <p>
44  * <b>Parsing</b><br>
45  * Parser takes types array and tries to populate their instances in best possible way. It assumes that provided
46  * types list matches selected columns. That is very important, and yet very easy and natural to follow.
47  * So, parser will try to inject columns value into the one result instance. Now, there are two types of instances:
48  * simple types (numbers and strings) and entities (pojo objects). Simple types are always mapped to
49  * one and only one column. Entities will be mapped to all possible columns that can be matched starting from
50  * current column. So, simple types are not column-hungry, entity types are column-hungry:)
51  *
52  * <p>
53  * A column can be injected in one entities property only once. If one column is already mapped to current result,
54  * RS mapper will assume that current result is finished with mapping and will proceed to the next one.
55  * Similarly, if property name is not found for a column, RS mapper will proceed to the next result.
56  * Therefore, entity types are column precize and hungry;) - all listed columns must be mapped somewhere.
57  *
58  * <p>
59  * Results that are not used during parsing will be set to <code>null</code>.
60  */

61 public class DefaultResultSetMapper implements ResultSetMapper {
62
63     protected DbOrm dbOrm;
64     protected ResultSet JavaDoc rs;
65
66     protected int totalColumns; // total number of columns
67
protected String JavaDoc[] columnNames; // list of all column names
68
protected String JavaDoc[] tableNames; // list of table names for each column, table name may be null
69

70     private Bag resultColums; // internal columns per entity cache
71

72     // ---------------------------------------------------------------- ctor
73

74     public DefaultResultSetMapper(ResultSet JavaDoc rs) {
75         this(rs, null, DbOrm.getInstance());
76     }
77
78     public DefaultResultSetMapper(ResultSet JavaDoc rs, DbOrm orm) {
79         this(rs, null, orm);
80     }
81
82     public DefaultResultSetMapper(ResultSet JavaDoc rs, Map JavaDoc<String JavaDoc, String JavaDoc[]> columnAliases) {
83         this(rs, columnAliases, DbOrm.getInstance());
84     }
85
86     /**
87      * Reads RS meta-data for column and table names.
88      */

89     public DefaultResultSetMapper(ResultSet JavaDoc rs, Map JavaDoc<String JavaDoc, String JavaDoc[]> columnAliases, DbOrm orm) {
90         this.dbOrm = orm;
91         this.rs = rs;
92         this.resultColums = new HashBag();
93         try {
94             ResultSetMetaData JavaDoc rsMetaData = rs.getMetaData();
95             if (rsMetaData == null) {
96                 throw new DbOrmException("JDBC driver doesn't provide meta-data.");
97             }
98             totalColumns = rsMetaData.getColumnCount();
99             columnNames = new String JavaDoc[totalColumns];
100             tableNames = new String JavaDoc[totalColumns];
101
102             for (int i = 0; i < totalColumns; i++) {
103                 String JavaDoc columnName = rsMetaData.getColumnName(i + 1);
104                 String JavaDoc tableName = null;
105
106                 // resolve column and table name
107
int sepNdx = columnName.indexOf(dbOrm.getColumnAliasSeparator());
108                 if (sepNdx != -1) {
109                     // column alias exist, result set is ignored and columnAliases contains table data.
110
tableName = columnName.substring(0, sepNdx);
111                     if (columnAliases != null) {
112                         String JavaDoc[] tableData = columnAliases.get(tableName);
113                         if (tableData != null) {
114                             tableName = tableData[0];
115                         }
116                     }
117                     columnName = columnName.substring(sepNdx + 1);
118                 } else {
119                     // column alias doesn't exist, table name is readed from columnAliases and result set (if availiable).
120
if (columnAliases != null) {
121                         String JavaDoc[] columnData = columnAliases.get(columnName.toLowerCase());
122                         if (columnData != null) {
123                             tableName = columnData[0];
124                             columnName = columnData[1];
125                         }
126                     }
127                     if (tableName == null) {
128                         try {
129                             tableName = rsMetaData.getTableName(i + 1);
130                         } catch (SQLException JavaDoc sex) {
131                             // ignore
132
}
133                         if ((tableName != null) && (tableName.length() == 0)) {
134                             tableName = null;
135                         }
136                     }
137                 }
138
139                 columnName = columnName.trim();
140                 if (columnName.length() == 0) {
141                     columnName = null;
142                 }
143                 columnNames[i] = columnName;
144                 if (tableName != null) {
145                     tableName = tableName.trim();
146                 }
147                 tableNames[i] = tableName;
148             }
149         } catch (SQLException JavaDoc sex) {
150             throw new DbOrmException("Unable to read ResultSet meta-data.", sex);
151         }
152     }
153
154     // ---------------------------------------------------------------- delegates
155

156     /**
157      * Moves the cursor down one row from its current position.
158      */

159     public boolean next() {
160         try {
161             return rs.next();
162         } catch (SQLException JavaDoc sex) {
163             throw new DbOrmException("Unable to move ResultSet cursor to next position.", sex);
164         }
165     }
166
167     /**
168      * Releases this ResultSet object's database and JDBC resources immediately instead of
169      * waiting for this to happen when it is automatically closed.
170      */

171     public void close() {
172         try {
173             rs.close();
174         } catch (SQLException JavaDoc sex) {
175             // ignore
176
}
177     }
178
179     /**
180      * Return JDBC result set.
181      */

182     public ResultSet JavaDoc getResultSet() {
183         return rs;
184     }
185
186
187     // ---------------------------------------------------------------- parse objects
188

189     /**
190      * Creates new instances of a types.
191      */

192     protected Object JavaDoc newInstance(Class JavaDoc types) {
193         try {
194             return types.newInstance();
195         } catch (Exception JavaDoc ex) {
196             throw new DbOrmException("Unable to create new entity instance for type '" + types + "'.", ex);
197         }
198     }
199
200
201     protected Class JavaDoc[] cachedUsedTypes;
202     protected String JavaDoc[] cachedTypesTableNames;
203
204     /**
205      * Creates table names for all specified types.
206      * Since this is usually done once per result set, these names are cached.
207      * Type name will be <code>null</code> for simple names, i.e. for all those
208      * types that returns <code>null</code> when used by {@link DbOrm#lookup(Class)}.
209      */

210     protected String JavaDoc[] createTypesTableNames(Class JavaDoc[] types) {
211         if (types != cachedUsedTypes) {
212             cachedTypesTableNames = new String JavaDoc[types.length];
213             for (int i = 0; i < types.length; i++) {
214                 if (types[i] == null) {
215                     cachedTypesTableNames[i] = null;
216                     continue;
217                 }
218                 DbEntityDescriptor ded = dbOrm.lookup(types[i]);
219                 if (ded != null) {
220                     cachedTypesTableNames[i] = ded.getTableName();
221                 }
222             }
223             cachedUsedTypes = types;
224         }
225         return cachedTypesTableNames;
226     }
227
228
229     protected int cachedColumnNdx;
230     protected Object JavaDoc cachedColumnValue;
231
232     /**
233      * Reads column value from result set. Since this method may be called more then once for
234      * the same column, it caches column value.
235      */

236     protected Object JavaDoc readColumnValue(int colNdx) {
237         if (colNdx != cachedColumnNdx) {
238             try {
239                 cachedColumnValue = rs.getObject(colNdx + 1);
240             } catch (SQLException JavaDoc sex) {
241                 throw new DbOrmException("Unable to read value for column #" + (colNdx + 1) + '.');
242             }
243             cachedColumnNdx = colNdx;
244         }
245         return cachedColumnValue;
246     }
247
248
249     public Object JavaDoc[] parseObjects(Class JavaDoc... types) {
250         int totalTypes = types.length;
251         Object JavaDoc[] result = new Object JavaDoc[totalTypes];
252         boolean[] resultUsage = new boolean[totalTypes];
253         String JavaDoc[] typesTableNames = createTypesTableNames(types);
254
255         int currentResult = 0;
256         cachedColumnNdx = -1;
257         int colNdx = 0;
258         while (colNdx < totalColumns) {
259
260             // no more types for mapping?
261
if (currentResult >= totalTypes) {
262                 break;
263             }
264
265             // skip columns that doesn't map
266
Class JavaDoc currentType = types[currentResult];
267             if (currentType == null) {
268                 colNdx++;
269                 currentResult++; resultColums.clear();
270                 continue;
271             }
272
273             Object JavaDoc value = readColumnValue(colNdx);
274             String JavaDoc columnName = columnNames[colNdx];
275             String JavaDoc tableName = tableNames[colNdx];
276             String JavaDoc resultTableName = typesTableNames[currentResult];
277
278             if (resultTableName == null) {
279                 // match: simple type
280
result[currentResult] = TypeConverterManager.convert(value, currentType);
281                 resultUsage[currentResult] = true;
282                 colNdx++;
283                 currentResult++; resultColums.clear();
284                 continue;
285             }
286             if ((tableName == null) || (resultTableName.equals(tableName) == true)) {
287                 if (resultColums.contains(columnName) == false) {
288                     String JavaDoc propertyName = dbOrm.lookup(currentType).getPropertyName(columnName);
289                     if (propertyName != null) {
290                         if (result[currentResult] == null) {
291                             result[currentResult] = newInstance(currentType);
292                         }
293 /*
294                         boolean success =
295                                 value != null ?
296                                         BeanUtil.setDeclaredPropertySilent(result[currentResult], propertyName, value)
297                                         :
298                                         BeanUtil.hasDeclaredProperty(result[currentResult], propertyName);
299 */

300                         if (BeanUtil.hasDeclaredProperty(result[currentResult], propertyName) == true) {
301                             // match: entity
302
if (value != null) {
303                                 BeanUtil.setDeclaredProperty(result[currentResult], propertyName, value);
304                                 resultUsage[currentResult] = true;
305                             }
306                             colNdx++;
307                             resultColums.add(columnName);
308                             continue;
309                         }
310                     }
311                 }
312             }
313             // got to next type, i.e. result
314
currentResult++; resultColums.clear();
315         }
316
317         resultColums.clear();
318         for (int i = 0; i < resultUsage.length; i++) {
319             if (resultUsage[i] == false) {
320                 result[i] = null;
321             }
322         }
323         return result;
324     }
325
326     public Object JavaDoc parseOneObject(Class JavaDoc... types) {
327         return parseObjects(types)[0];
328     }
329
330     // ---------------------------------------------------------------- utilities
331

332     public Object JavaDoc[] parseObjects() {
333         Map JavaDoc<String JavaDoc, Class JavaDoc> classesMap = new LinkedHashMap JavaDoc<String JavaDoc, Class JavaDoc>();
334         for (String JavaDoc tableName : tableNames) {
335             if (tableName == null) {
336                 throw new DbOrmException("Table name for one or more columns is not availiable. " +
337                         "It may be due JDBC driver that doesn't provide table names in ResultSet meta-data.");
338             }
339             if (classesMap.get(tableName) == null) {
340                 Class JavaDoc type = dbOrm.getMappedEntityType(tableName);
341                 if (type == null) {
342                     throw new DbOrmException("Unable to find entity type for table '" + tableName + "'.");
343                 }
344                 classesMap.put(tableName, type);
345             }
346         }
347
348         Class JavaDoc[] results = classesMap.values().toArray(new Class JavaDoc[classesMap.size()]);
349         return parseObjects(results);
350     }
351
352
353     public Object JavaDoc parseOneObject() {
354         return parseObjects()[0];
355     }
356
357 }
358
Popular Tags