Source code: org/objectstyle/cayenne/access/util/ResultDescriptor.java
1 /* ====================================================================
2 *
3 * The ObjectStyle Group Software License, Version 1.0
4 *
5 * Copyright (c) 2002-2003 The ObjectStyle Group
6 * and individual authors of the software. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution, if
21 * any, must include the following acknowlegement:
22 * "This product includes software developed by the
23 * ObjectStyle Group (http://objectstyle.org/)."
24 * Alternately, this acknowlegement may appear in the software itself,
25 * if and wherever such third-party acknowlegements normally appear.
26 *
27 * 4. The names "ObjectStyle Group" and "Cayenne"
28 * must not be used to endorse or promote products derived
29 * from this software without prior written permission. For written
30 * permission, please contact andrus@objectstyle.org.
31 *
32 * 5. Products derived from this software may not be called "ObjectStyle"
33 * nor may "ObjectStyle" appear in their names without prior written
34 * permission of the ObjectStyle Group.
35 *
36 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39 * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
40 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * SUCH DAMAGE.
48 * ====================================================================
49 *
50 * This software consists of voluntary contributions made by many
51 * individuals on behalf of the ObjectStyle Group. For more
52 * information on the ObjectStyle Group, please see
53 * <http://objectstyle.org/>.
54 *
55 */
56 package org.objectstyle.cayenne.access.util;
57
58 import java.sql.ResultSet;
59 import java.sql.ResultSetMetaData;
60 import java.sql.SQLException;
61 import java.util.ArrayList;
62 import java.util.Collection;
63 import java.util.Iterator;
64 import java.util.List;
65
66 import org.apache.log4j.Logger;
67 import org.objectstyle.cayenne.CayenneRuntimeException;
68 import org.objectstyle.cayenne.access.types.ExtendedType;
69 import org.objectstyle.cayenne.access.types.ExtendedTypeMap;
70 import org.objectstyle.cayenne.dba.TypesMapping;
71 import org.objectstyle.cayenne.map.DbAttribute;
72 import org.objectstyle.cayenne.map.ObjAttribute;
73 import org.objectstyle.cayenne.map.ObjEntity;
74 import org.objectstyle.cayenne.map.Procedure;
75 import org.objectstyle.cayenne.map.ProcedureParameter;
76
77 /**
78 * Contains information about the ResultSet used to process fetched rows.
79 * ResultDescriptor is initialized by calling various "add*" methods, after that
80 * it must be indexed by calling "index".
81 *
82 * @author Andrei Adamchik
83 */
84 public class ResultDescriptor {
85 private static Logger logObj = Logger.getLogger(ResultDescriptor.class);
86
87 private static final int[] emptyInt = new int[0];
88
89 // indexed data
90 protected String[] names;
91 protected int[] jdbcTypes;
92 protected ExtendedType[] converters;
93 protected int[] idIndexes;
94 protected int[] outParamIndexes;
95
96 // unindexed data
97 protected List dbAttributes = new ArrayList();
98 protected List javaTypes = new ArrayList();
99 protected ExtendedTypeMap typesMapping;
100 protected ObjEntity rootEntity;
101
102 /**
103 * Creates and returns a ResultDescritor based on ResultSet metadata.
104 */
105 public static ResultDescriptor createDescriptor(
106 ResultSet resultSet,
107 ExtendedTypeMap typeConverters) {
108 ResultDescriptor descriptor = new ResultDescriptor(typeConverters);
109 try {
110 ResultSetMetaData md = resultSet.getMetaData();
111 int len = md.getColumnCount();
112 if (len == 0) {
113 throw new CayenneRuntimeException("No columns in ResultSet.");
114 }
115
116 for (int i = 0; i < len; i++) {
117
118 // figure out column name
119 int pos = i + 1;
120 String name = md.getColumnLabel(pos);
121 int sqlType = md.getColumnType(pos);
122 int precision = md.getScale(pos);
123 int length = md.getColumnDisplaySize(pos);
124 if (name == null || name.length() == 0) {
125 name = md.getColumnName(i + 1);
126
127 if (name == null || name.length() == 0) {
128 name = "column_" + (i + 1);
129 }
130 }
131
132 DbAttribute desc = new DbAttribute();
133 desc.setName(name);
134 desc.setType(md.getColumnType(i + 1));
135
136 descriptor.addDbAttribute(desc);
137 descriptor.addJavaType(TypesMapping.getJavaBySqlType(sqlType, length, precision));
138 }
139 } catch (SQLException sqex) {
140 throw new CayenneRuntimeException("Error reading metadata.", sqex);
141 }
142
143 descriptor.index();
144 return descriptor;
145 }
146
147 /**
148 * Creates and returns a ResultDescriptor for the stored procedure parameters.
149 */
150 public static ResultDescriptor createDescriptor(
151 Procedure procedure,
152 ExtendedTypeMap typeConverters) {
153 ResultDescriptor descriptor = new ResultDescriptor(typeConverters);
154 Iterator it = procedure.getCallParameters().iterator();
155 while (it.hasNext()) {
156 descriptor.addDbAttribute(new ProcedureParameterWrapper((ProcedureParameter) it.next()));
157 }
158
159 descriptor.index();
160 return descriptor;
161 }
162
163 public ResultDescriptor(ExtendedTypeMap typesMapping) {
164 this(typesMapping, null);
165 }
166
167 public ResultDescriptor(
168 ExtendedTypeMap typesMapping,
169 ObjEntity rootEntity) {
170 this.typesMapping = typesMapping;
171 this.rootEntity = rootEntity;
172 }
173
174 public void addColumns(Collection dbAttributes) {
175 this.dbAttributes.addAll(dbAttributes);
176 }
177
178 public void addDbAttribute(DbAttribute attr) {
179 this.dbAttributes.add(attr);
180 }
181
182 public void addJavaTypes(Collection javaTypes) {
183 this.javaTypes.addAll(javaTypes);
184 }
185
186 public void addJavaType(String javaType) {
187 this.javaTypes.add(javaType);
188 }
189
190 public void index() {
191
192 // assert validity
193 if (javaTypes.size() > 0 && javaTypes.size() != dbAttributes.size()) {
194 throw new IllegalArgumentException("DbAttributes and Java type arrays must have the same size.");
195 }
196
197 // init various things
198 int resultWidth = dbAttributes.size();
199 int idWidth = 0;
200 int outWidth = 0;
201 this.names = new String[resultWidth];
202 this.jdbcTypes = new int[resultWidth];
203 for (int i = 0; i < resultWidth; i++) {
204 DbAttribute attr = (DbAttribute) dbAttributes.get(i);
205
206 // set type
207 jdbcTypes[i] = attr.getType();
208
209 // check if this is an ID
210 if (attr.isPrimaryKey()) {
211 idWidth++;
212 }
213
214 // check if this is a stored procedure OUT parameter
215 if (attr instanceof ProcedureParameterWrapper) {
216 if (((ProcedureParameterWrapper) attr).getParameter().isOutParam()) {
217 outWidth++;
218 }
219 }
220
221 // figure out name
222 String name = null;
223 if (rootEntity != null) {
224 ObjAttribute objAttr =
225 rootEntity.getAttributeForDbAttribute(attr);
226 if (objAttr != null) {
227 name = objAttr.getDbAttributePath();
228 }
229 }
230
231 if (name == null) {
232 name = attr.getName();
233 }
234
235 names[i] = name;
236 }
237
238 if (idWidth == 0) {
239 this.idIndexes = emptyInt;
240 } else {
241 this.idIndexes = new int[idWidth];
242 for (int i = 0, j = 0; i < resultWidth; i++) {
243 DbAttribute attr = (DbAttribute) dbAttributes.get(i);
244 jdbcTypes[i] = attr.getType();
245
246 if (attr.isPrimaryKey()) {
247 idIndexes[j++] = i;
248 }
249 }
250 }
251
252 if (outWidth == 0) {
253 this.outParamIndexes = emptyInt;
254 } else {
255 this.outParamIndexes = new int[outWidth];
256 for (int i = 0, j = 0; i < resultWidth; i++) {
257 DbAttribute attr = (DbAttribute) dbAttributes.get(i);
258 jdbcTypes[i] = attr.getType();
259
260 if (attr instanceof ProcedureParameterWrapper) {
261 if (((ProcedureParameterWrapper) attr).getParameter().isOutParam()) {
262 outParamIndexes[j++] = i;
263 }
264 }
265 }
266 }
267
268 // initialize type converters, must do after everything else,
269 // since this may depend on some of the indexed data
270 if (javaTypes.size() > 0) {
271 initConvertersFromJavaTypes();
272 } else if (rootEntity != null) {
273 initConvertersFromMapping();
274 } else {
275 initDefaultConverters();
276 }
277 }
278
279 protected void initConvertersFromJavaTypes() {
280 int resultWidth = dbAttributes.size();
281 this.converters = new ExtendedType[resultWidth];
282
283 for (int i = 0; i < resultWidth; i++) {
284 converters[i] =
285 typesMapping.getRegisteredType((String) javaTypes.get(i));
286 }
287 }
288
289 protected void initDefaultConverters() {
290 int resultWidth = dbAttributes.size();
291 this.converters = new ExtendedType[resultWidth];
292
293 for (int i = 0; i < resultWidth; i++) {
294 String javaType = TypesMapping.getJavaBySqlType(jdbcTypes[i]);
295 converters[i] = typesMapping.getRegisteredType(javaType);
296 }
297 }
298
299 protected void initConvertersFromMapping() {
300
301 // assert that we have all the data
302 if (dbAttributes.size() == 0) {
303 throw new IllegalArgumentException("DbAttributes list is empty.");
304 }
305
306 if (rootEntity == null) {
307 throw new IllegalArgumentException("Root ObjEntity is null.");
308 }
309
310 int resultWidth = dbAttributes.size();
311 this.converters = new ExtendedType[resultWidth];
312
313 for (int i = 0; i < resultWidth; i++) {
314 String javaType = null;
315 DbAttribute attr = (DbAttribute) dbAttributes.get(i);
316 ObjAttribute objAttr = rootEntity.getAttributeForDbAttribute(attr);
317 if (objAttr != null) {
318 javaType = objAttr.getType();
319 } else {
320 javaType = TypesMapping.getJavaBySqlType(attr.getType());
321 }
322
323 converters[i] = typesMapping.getRegisteredType(javaType);
324 }
325 }
326
327 public ExtendedType[] getConverters() {
328 return converters;
329 }
330
331 public int[] getIdIndexes() {
332 return idIndexes;
333 }
334
335 public int[] getJdbcTypes() {
336 return jdbcTypes;
337 }
338
339 public String[] getNames() {
340 return names;
341 }
342
343 /**
344 * Returns a count of columns in the result.
345 */
346 public int getResultWidth() {
347 return dbAttributes.size();
348 }
349
350 public int[] getOutParamIndexes() {
351 return outParamIndexes;
352 }
353
354 // [UGLY HACK AHEAD] wrapper to make a ProcedureParameter
355 // look like a DbAttribute. A better implementation would
356 // probably be a common interface for both.
357 static class ProcedureParameterWrapper extends DbAttribute {
358 ProcedureParameter parameter;
359
360 ProcedureParameterWrapper(ProcedureParameter parameter) {
361 this.parameter = parameter;
362 }
363
364 public int getMaxLength() {
365 return parameter.getMaxLength();
366 }
367
368 public int getPrecision() {
369 return parameter.getPrecision();
370 }
371
372 public int getType() {
373 return parameter.getType();
374 }
375
376 public String getName() {
377 return parameter.getName();
378 }
379
380 public Object getParent() {
381 return parameter.getParent();
382 }
383
384 public ProcedureParameter getParameter() {
385 return parameter;
386 }
387
388 }
389 }