1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.loader.custom;
26
27 import java.sql.ResultSet;
28 import java.sql.ResultSetMetaData;
29 import java.sql.SQLException;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Set;
33 import java.util.ArrayList;
34 import java.util.Iterator;
35 import java.util.HashSet;
36
37 import org.hibernate.HibernateException;
38 import org.hibernate.LockMode;
39 import org.hibernate.QueryException;
40 import org.hibernate.ScrollableResults;
41 import org.hibernate.engine.QueryParameters;
42 import org.hibernate.engine.SessionFactoryImplementor;
43 import org.hibernate.engine.SessionImplementor;
44 import org.hibernate.hql.HolderInstantiator;
45 import org.hibernate.loader.CollectionAliases;
46 import org.hibernate.loader.EntityAliases;
47 import org.hibernate.loader.Loader;
48 import org.hibernate.persister.collection.CollectionPersister;
49 import org.hibernate.persister.collection.QueryableCollection;
50 import org.hibernate.persister.entity.Loadable;
51 import org.hibernate.persister.entity.Queryable;
52 import org.hibernate.transform.ResultTransformer;
53 import org.hibernate.type.Type;
54 import org.hibernate.type.TypeFactory;
55 import org.hibernate.type.EntityType;
56 import org.hibernate.type.CollectionType;
57 import org.hibernate.util.ArrayHelper;
58
59 /**
60 * Extension point for loaders which use a SQL result set with "unexpected" column aliases.
61 *
62 * @author Gavin King
63 * @author Steve Ebersole
64 */
65 public class CustomLoader extends Loader {
66
67 // Currently *not* cachable if autodiscover types is in effect (e.g. "select * ...")
68
69 private final String sql;
70 private final Set querySpaces = new HashSet();
71 private final Map namedParameterBindPoints;
72
73 private final Queryable[] entityPersisters;
74 private final int[] entiytOwners;
75 private final EntityAliases[] entityAliases;
76
77 private final QueryableCollection[] collectionPersisters;
78 private final int[] collectionOwners;
79 private final CollectionAliases[] collectionAliases;
80
81 private final LockMode[] lockModes;
82 // private final String[] sqlAliases;
83 // private final String[] sqlAliasSuffixes;
84 private final ResultRowProcessor rowProcessor;
85
86 // this is only needed (afaict) for processing results from the query cache;
87 // however, this cannot possibly work in the case of discovered types...
88 private Type[] resultTypes;
89
90 // this is only needed (afaict) for ResultTransformer processing...
91 private String[] transformerAliases;
92
93
94 public CustomLoader(CustomQuery customQuery, SessionFactoryImplementor factory) {
95 super( factory );
96
97 this.sql = customQuery.getSQL();
98 this.querySpaces.addAll( customQuery.getQuerySpaces() );
99 this.namedParameterBindPoints = customQuery.getNamedParameterBindPoints();
100
101 List entityPersisters = new ArrayList();
102 List entityOwners = new ArrayList();
103 List entityAliases = new ArrayList();
104
105 List collectionPersisters = new ArrayList();
106 List collectionOwners = new ArrayList();
107 List collectionAliases = new ArrayList();
108
109 List lockModes = new ArrayList();
110 List resultColumnProcessors = new ArrayList();
111 List nonScalarReturnList = new ArrayList();
112 List resultTypes = new ArrayList();
113 List specifiedAliases = new ArrayList();
114 int returnableCounter = 0;
115 boolean hasScalars = false;
116
117 Iterator itr = customQuery.getCustomQueryReturns().iterator();
118 while ( itr.hasNext() ) {
119 final Return rtn = ( Return ) itr.next();
120 if ( rtn instanceof ScalarReturn ) {
121 ScalarReturn scalarRtn = ( ScalarReturn ) rtn;
122 resultTypes.add( scalarRtn.getType() );
123 specifiedAliases.add( scalarRtn.getColumnAlias() );
124 resultColumnProcessors.add(
125 new ScalarResultColumnProcessor(
126 scalarRtn.getColumnAlias(),
127 scalarRtn.getType()
128 )
129 );
130 hasScalars = true;
131 }
132 else if ( rtn instanceof RootReturn ) {
133 RootReturn rootRtn = ( RootReturn ) rtn;
134 Queryable persister = ( Queryable ) factory.getEntityPersister( rootRtn.getEntityName() );
135 entityPersisters.add( persister );
136 lockModes.add( rootRtn.getLockMode() );
137 resultColumnProcessors.add( new NonScalarResultColumnProcessor( returnableCounter++ ) );
138 nonScalarReturnList.add( rtn );
139 entityOwners.add( new Integer( -1 ) );
140 resultTypes.add( persister.getType() );
141 specifiedAliases.add( rootRtn.getAlias() );
142 entityAliases.add( rootRtn.getEntityAliases() );
143 ArrayHelper.addAll( querySpaces, persister.getQuerySpaces() );
144 }
145 else if ( rtn instanceof CollectionReturn ) {
146 CollectionReturn collRtn = ( CollectionReturn ) rtn;
147 String role = collRtn.getOwnerEntityName() + "." + collRtn.getOwnerProperty();
148 QueryableCollection persister = ( QueryableCollection ) factory.getCollectionPersister( role );
149 collectionPersisters.add( persister );
150 lockModes.add( collRtn.getLockMode() );
151 resultColumnProcessors.add( new NonScalarResultColumnProcessor( returnableCounter++ ) );
152 nonScalarReturnList.add( rtn );
153 collectionOwners.add( new Integer( -1 ) );
154 resultTypes.add( persister.getType() );
155 specifiedAliases.add( collRtn.getAlias() );
156 collectionAliases.add( collRtn.getCollectionAliases() );
157 // determine if the collection elements are entities...
158 Type elementType = persister.getElementType();
159 if ( elementType.isEntityType() ) {
160 Queryable elementPersister = ( Queryable ) ( ( EntityType ) elementType ).getAssociatedJoinable( factory );
161 entityPersisters.add( elementPersister );
162 entityOwners.add( new Integer( -1 ) );
163 entityAliases.add( collRtn.getElementEntityAliases() );
164 ArrayHelper.addAll( querySpaces, elementPersister.getQuerySpaces() );
165 }
166 }
167 else if ( rtn instanceof EntityFetchReturn ) {
168 EntityFetchReturn fetchRtn = ( EntityFetchReturn ) rtn;
169 NonScalarReturn ownerDescriptor = fetchRtn.getOwner();
170 int ownerIndex = nonScalarReturnList.indexOf( ownerDescriptor );
171 entityOwners.add( new Integer( ownerIndex ) );
172 lockModes.add( fetchRtn.getLockMode() );
173 Queryable ownerPersister = determineAppropriateOwnerPersister( ownerDescriptor );
174 EntityType fetchedType = ( EntityType ) ownerPersister.getPropertyType( fetchRtn.getOwnerProperty() );
175 String entityName = fetchedType.getAssociatedEntityName( getFactory() );
176 Queryable persister = ( Queryable ) factory.getEntityPersister( entityName );
177 entityPersisters.add( persister );
178 nonScalarReturnList.add( rtn );
179 specifiedAliases.add( fetchRtn.getAlias() );
180 entityAliases.add( fetchRtn.getEntityAliases() );
181 ArrayHelper.addAll( querySpaces, persister.getQuerySpaces() );
182 }
183 else if ( rtn instanceof CollectionFetchReturn ) {
184 CollectionFetchReturn fetchRtn = ( CollectionFetchReturn ) rtn;
185 NonScalarReturn ownerDescriptor = fetchRtn.getOwner();
186 int ownerIndex = nonScalarReturnList.indexOf( ownerDescriptor );
187 collectionOwners.add( new Integer( ownerIndex ) );
188 lockModes.add( fetchRtn.getLockMode() );
189 Queryable ownerPersister = determineAppropriateOwnerPersister( ownerDescriptor );
190 String role = ownerPersister.getEntityName() + '.' + fetchRtn.getOwnerProperty();
191 QueryableCollection persister = ( QueryableCollection ) factory.getCollectionPersister( role );
192 collectionPersisters.add( persister );
193 nonScalarReturnList.add( rtn );
194 specifiedAliases.add( fetchRtn.getAlias() );
195 collectionAliases.add( fetchRtn.getCollectionAliases() );
196 // determine if the collection elements are entities...
197 Type elementType = persister.getElementType();
198 if ( elementType.isEntityType() ) {
199 Queryable elementPersister = ( Queryable ) ( ( EntityType ) elementType ).getAssociatedJoinable( factory );
200 entityPersisters.add( elementPersister );
201 entityOwners.add( new Integer( ownerIndex ) );
202 entityAliases.add( fetchRtn.getElementEntityAliases() );
203 ArrayHelper.addAll( querySpaces, elementPersister.getQuerySpaces() );
204 }
205 }
206 else {
207 throw new HibernateException( "unexpected custom query return type : " + rtn.getClass().getName() );
208 }
209 }
210
211 this.entityPersisters = new Queryable[ entityPersisters.size() ];
212 for ( int i = 0; i < entityPersisters.size(); i++ ) {
213 this.entityPersisters[i] = ( Queryable ) entityPersisters.get( i );
214 }
215 this.entiytOwners = ArrayHelper.toIntArray( entityOwners );
216 this.entityAliases = new EntityAliases[ entityAliases.size() ];
217 for ( int i = 0; i < entityAliases.size(); i++ ) {
218 this.entityAliases[i] = ( EntityAliases ) entityAliases.get( i );
219 }
220
221 this.collectionPersisters = new QueryableCollection[ collectionPersisters.size() ];
222 for ( int i = 0; i < collectionPersisters.size(); i++ ) {
223 this.collectionPersisters[i] = ( QueryableCollection ) collectionPersisters.get( i );
224 }
225 this.collectionOwners = ArrayHelper.toIntArray( collectionOwners );
226 this.collectionAliases = new CollectionAliases[ collectionAliases.size() ];
227 for ( int i = 0; i < collectionAliases.size(); i++ ) {
228 this.collectionAliases[i] = ( CollectionAliases ) collectionAliases.get( i );
229 }
230
231 this.lockModes = new LockMode[ lockModes.size() ];
232 for ( int i = 0; i < lockModes.size(); i++ ) {
233 this.lockModes[i] = ( LockMode ) lockModes.get( i );
234 }
235
236 this.resultTypes = ArrayHelper.toTypeArray( resultTypes );
237 this.transformerAliases = ArrayHelper.toStringArray( specifiedAliases );
238
239 this.rowProcessor = new ResultRowProcessor(
240 hasScalars,
241 ( ResultColumnProcessor[] ) resultColumnProcessors.toArray( new ResultColumnProcessor[ resultColumnProcessors.size() ] )
242 );
243 }
244
245 private Queryable determineAppropriateOwnerPersister(NonScalarReturn ownerDescriptor) {
246 String entityName = null;
247 if ( ownerDescriptor instanceof RootReturn ) {
248 entityName = ( ( RootReturn ) ownerDescriptor ).getEntityName();
249 }
250 else if ( ownerDescriptor instanceof CollectionReturn ) {
251 CollectionReturn collRtn = ( CollectionReturn ) ownerDescriptor;
252 String role = collRtn.getOwnerEntityName() + "." + collRtn.getOwnerProperty();
253 CollectionPersister persister = getFactory().getCollectionPersister( role );
254 EntityType ownerType = ( EntityType ) persister.getElementType();
255 entityName = ownerType.getAssociatedEntityName( getFactory() );
256 }
257 else if ( ownerDescriptor instanceof FetchReturn ) {
258 FetchReturn fetchRtn = ( FetchReturn ) ownerDescriptor;
259 Queryable persister = determineAppropriateOwnerPersister( fetchRtn.getOwner() );
260 Type ownerType = persister.getPropertyType( fetchRtn.getOwnerProperty() );
261 if ( ownerType.isEntityType() ) {
262 entityName = ( ( EntityType ) ownerType ).getAssociatedEntityName( getFactory() );
263 }
264 else if ( ownerType.isCollectionType() ) {
265 Type ownerCollectionElementType = ( ( CollectionType ) ownerType ).getElementType( getFactory() );
266 if ( ownerCollectionElementType.isEntityType() ) {
267 entityName = ( ( EntityType ) ownerCollectionElementType ).getAssociatedEntityName( getFactory() );
268 }
269 }
270 }
271
272 if ( entityName == null ) {
273 throw new HibernateException( "Could not determine fetch owner : " + ownerDescriptor );
274 }
275
276 return ( Queryable ) getFactory().getEntityPersister( entityName );
277 }
278
279 protected String getQueryIdentifier() {
280 return sql;
281 }
282
283 protected String getSQLString() {
284 return sql;
285 }
286
287 public Set getQuerySpaces() {
288 return querySpaces;
289 }
290
291 protected LockMode[] getLockModes(Map lockModesMap) {
292 return lockModes;
293 }
294
295 protected Loadable[] getEntityPersisters() {
296 return entityPersisters;
297 }
298
299 protected CollectionPersister[] getCollectionPersisters() {
300 return collectionPersisters;
301 }
302
303 protected int[] getCollectionOwners() {
304 return collectionOwners;
305 }
306
307 protected int[] getOwners() {
308 return entiytOwners;
309 }
310
311 public List list(SessionImplementor session, QueryParameters queryParameters) throws HibernateException {
312 return list( session, queryParameters, querySpaces, resultTypes );
313 }
314
315 public ScrollableResults scroll(
316 final QueryParameters queryParameters,
317 final SessionImplementor session) throws HibernateException {
318 return scroll(
319 queryParameters,
320 resultTypes,
321 getHolderInstantiator( queryParameters.getResultTransformer(), getReturnAliasesForTransformer() ),
322 session
323 );
324 }
325
326 static private HolderInstantiator getHolderInstantiator(ResultTransformer resultTransformer, String[] queryReturnAliases) {
327 if ( resultTransformer != null ) {
328 return HolderInstantiator.NOOP_INSTANTIATOR;
329 }
330 else {
331 return new HolderInstantiator(resultTransformer, queryReturnAliases);
332 }
333 }
334
335 protected Object getResultColumnOrRow(
336 Object[] row,
337 ResultTransformer transformer,
338 ResultSet rs,
339 SessionImplementor session) throws SQLException, HibernateException {
340 return rowProcessor.buildResultRow( row, rs, transformer != null, session );
341 }
342
343 protected List getResultList(List results, ResultTransformer resultTransformer) throws QueryException {
344 // meant to handle dynamic instantiation queries...(Copy from QueryLoader)
345 HolderInstantiator holderInstantiator = HolderInstantiator.getHolderInstantiator(
346 null,
347 resultTransformer,
348 getReturnAliasesForTransformer()
349 );
350 if ( holderInstantiator.isRequired() ) {
351 for ( int i = 0; i < results.size(); i++ ) {
352 Object[] row = ( Object[] ) results.get( i );
353 Object result = holderInstantiator.instantiate(row);
354 results.set( i, result );
355 }
356
357 return resultTransformer.transformList(results);
358 }
359 else {
360 return results;
361 }
362 }
363
364 private String[] getReturnAliasesForTransformer() {
365 return transformerAliases;
366 }
367
368 protected EntityAliases[] getEntityAliases() {
369 return entityAliases;
370 }
371
372 protected CollectionAliases[] getCollectionAliases() {
373 return collectionAliases;
374 }
375
376 public int[] getNamedParameterLocs(String name) throws QueryException {
377 Object loc = namedParameterBindPoints.get( name );
378 if ( loc == null ) {
379 throw new QueryException(
380 "Named parameter does not appear in Query: " + name,
381 sql
382 );
383 }
384 if ( loc instanceof Integer ) {
385 return new int[] { ( ( Integer ) loc ).intValue() };
386 }
387 else {
388 return ArrayHelper.toIntArray( ( List ) loc );
389 }
390 }
391
392
393 public class ResultRowProcessor {
394 private final boolean hasScalars;
395 private ResultColumnProcessor[] columnProcessors;
396
397 public ResultRowProcessor(boolean hasScalars, ResultColumnProcessor[] columnProcessors) {
398 this.hasScalars = hasScalars || ( columnProcessors == null || columnProcessors.length == 0 );
399 this.columnProcessors = columnProcessors;
400 }
401
402 public void prepareForAutoDiscovery(Metadata metadata) throws SQLException {
403 if ( columnProcessors == null || columnProcessors.length == 0 ) {
404 int columns = metadata.getColumnCount();
405 columnProcessors = new ResultColumnProcessor[ columns ];
406 for ( int i = 1; i <= columns; i++ ) {
407 columnProcessors[ i - 1 ] = new ScalarResultColumnProcessor( i );
408 }
409
410 }
411 }
412
413 /**
414 * Build a logical result row.
415 * <p/>
416 * At this point, Loader has already processed all non-scalar result data. We
417 * just need to account for scalar result data here...
418 *
419 * @param data Entity data defined as "root returns" and already handled by the
420 * normal Loader mechanism.
421 * @param resultSet The JDBC result set (positioned at the row currently being processed).
422 * @param hasTransformer Does this query have an associated {@link ResultTransformer}
423 * @param session The session from which the query request originated.
424 * @return The logical result row
425 * @throws SQLException
426 * @throws HibernateException
427 */
428 public Object buildResultRow(
429 Object[] data,
430 ResultSet resultSet,
431 boolean hasTransformer,
432 SessionImplementor session) throws SQLException, HibernateException {
433 Object[] resultRow;
434 if ( !hasScalars ) {
435 resultRow = data;
436 }
437 else {
438 // build an array with indices equal to the total number
439 // of actual returns in the result Hibernate will return
440 // for this query (scalars + non-scalars)
441 resultRow = new Object[ columnProcessors.length ];
442 for ( int i = 0; i < columnProcessors.length; i++ ) {
443 resultRow[i] = columnProcessors[i].extract( data, resultSet, session );
444 }
445 }
446
447 return ( hasTransformer )
448 ? resultRow
449 : ( resultRow.length == 1 )
450 ? resultRow[0]
451 : resultRow;
452 }
453 }
454
455 private static interface ResultColumnProcessor {
456 public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session) throws SQLException, HibernateException;
457 public void performDiscovery(Metadata metadata, List types, List aliases) throws SQLException, HibernateException;
458 }
459
460 public class NonScalarResultColumnProcessor implements ResultColumnProcessor {
461 private final int position;
462
463 public NonScalarResultColumnProcessor(int position) {
464 this.position = position;
465 }
466
467 public Object extract(
468 Object[] data,
469 ResultSet resultSet,
470 SessionImplementor session) throws SQLException, HibernateException {
471 return data[ position ];
472 }
473
474 public void performDiscovery(Metadata metadata, List types, List aliases) {
475 }
476
477 }
478
479 public class ScalarResultColumnProcessor implements ResultColumnProcessor {
480 private int position = -1;
481 private String alias;
482 private Type type;
483
484 public ScalarResultColumnProcessor(int position) {
485 this.position = position;
486 }
487
488 public ScalarResultColumnProcessor(String alias, Type type) {
489 this.alias = alias;
490 this.type = type;
491 }
492
493 public Object extract(
494 Object[] data,
495 ResultSet resultSet,
496 SessionImplementor session) throws SQLException, HibernateException {
497 return type.nullSafeGet( resultSet, alias, session, null );
498 }
499
500 public void performDiscovery(Metadata metadata, List types, List aliases) throws SQLException {
501 if ( alias == null ) {
502 alias = metadata.getColumnName( position );
503 }
504 else if ( position < 0 ) {
505 position = metadata.resolveColumnPosition( alias );
506 }
507 if ( type == null ) {
508 type = metadata.getHibernateType( position );
509 }
510 types.add( type );
511 aliases.add( alias );
512 }
513 }
514
515 protected void autoDiscoverTypes(ResultSet rs) {
516 try {
517 Metadata metadata = new Metadata( getFactory(), rs );
518 List aliases = new ArrayList();
519 List types = new ArrayList();
520
521 rowProcessor.prepareForAutoDiscovery( metadata );
522
523 for ( int i = 0; i < rowProcessor.columnProcessors.length; i++ ) {
524 rowProcessor.columnProcessors[i].performDiscovery( metadata, types, aliases );
525 }
526
527 resultTypes = ArrayHelper.toTypeArray( types );
528 transformerAliases = ArrayHelper.toStringArray( aliases );
529 }
530 catch ( SQLException e ) {
531 throw new HibernateException( "Exception while trying to autodiscover types.", e );
532 }
533 }
534
535 private static class Metadata {
536 private final SessionFactoryImplementor factory;
537 private final ResultSet resultSet;
538 private final ResultSetMetaData resultSetMetaData;
539
540 public Metadata(SessionFactoryImplementor factory, ResultSet resultSet) throws HibernateException {
541 try {
542 this.factory = factory;
543 this.resultSet = resultSet;
544 this.resultSetMetaData = resultSet.getMetaData();
545 }
546 catch( SQLException e ) {
547 throw new HibernateException( "Could not extract result set metadata", e );
548 }
549 }
550
551 public int getColumnCount() throws HibernateException {
552 try {
553 return resultSetMetaData.getColumnCount();
554 }
555 catch( SQLException e ) {
556 throw new HibernateException( "Could not determine result set column count", e );
557 }
558 }
559
560 public int resolveColumnPosition(String columnName) throws HibernateException {
561 try {
562 return resultSet.findColumn( columnName );
563 }
564 catch( SQLException e ) {
565 throw new HibernateException( "Could not resolve column name in result set [" + columnName + "]", e );
566 }
567 }
568
569 public String getColumnName(int position) throws HibernateException {
570 try {
571 return resultSetMetaData.getColumnName( position );
572 }
573 catch( SQLException e ) {
574 throw new HibernateException( "Could not resolve column name [" + position + "]", e );
575 }
576 }
577
578 public Type getHibernateType(int columnPos) throws SQLException {
579 int columnType = resultSetMetaData.getColumnType( columnPos );
580 int scale = resultSetMetaData.getScale( columnPos );
581 int precision = resultSetMetaData.getPrecision( columnPos );
582 return TypeFactory.heuristicType(
583 factory.getDialect().getHibernateTypeName(
584 columnType,
585 precision,
586 precision,
587 scale
588 )
589 );
590 }
591 }
592 }