Skip to content

Commit 0bf29bc

Browse files
committed
HHH-8493 - Implement ConstructorResults handling
1 parent 312283c commit 0bf29bc

File tree

9 files changed

+422
-14
lines changed

9 files changed

+422
-14
lines changed

hibernate-core/src/main/java/org/hibernate/cfg/annotations/ResultsetMappingSecondPass.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.Map;
3232
import java.util.Set;
3333
import javax.persistence.ColumnResult;
34+
import javax.persistence.ConstructorResult;
3435
import javax.persistence.EntityResult;
3536
import javax.persistence.FieldResult;
3637
import javax.persistence.SqlResultSetMapping;
@@ -43,6 +44,7 @@
4344
import org.hibernate.cfg.Mappings;
4445
import org.hibernate.cfg.QuerySecondPass;
4546
import org.hibernate.engine.ResultSetMappingDefinition;
47+
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
4648
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
4749
import org.hibernate.engine.query.spi.sql.NativeSQLQueryScalarReturn;
4850
import org.hibernate.internal.CoreMessageLogger;
@@ -191,6 +193,21 @@ public void doSecondPass(Map persistentClasses) throws MappingException {
191193
);
192194
}
193195

196+
for ( ConstructorResult constructorResult : ann.classes() ) {
197+
List<NativeSQLQueryScalarReturn> columnReturns = new ArrayList<NativeSQLQueryScalarReturn>();
198+
for ( ColumnResult columnResult : constructorResult.columns() ) {
199+
columnReturns.add(
200+
new NativeSQLQueryScalarReturn(
201+
mappings.getObjectNameNormalizer().normalizeIdentifierQuoting( columnResult.name() ),
202+
null
203+
)
204+
);
205+
}
206+
definition.addQueryReturn(
207+
new NativeSQLQueryConstructorReturn( constructorResult.targetClass(), columnReturns )
208+
);
209+
}
210+
194211
if ( isDefault ) {
195212
mappings.addDefaultResultSetMapping( definition );
196213
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* Copyright (c) 2013, Red Hat Inc. 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 Inc.
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+
package org.hibernate.engine.query.spi.sql;
25+
26+
import java.util.List;
27+
28+
/**
29+
* Describes a {@link javax.persistence.ConstructorResult}
30+
*
31+
* @author Steve Ebersole
32+
*/
33+
public class NativeSQLQueryConstructorReturn implements NativeSQLQueryReturn {
34+
private final Class targetClass;
35+
private final NativeSQLQueryScalarReturn[] columnReturns;
36+
37+
public NativeSQLQueryConstructorReturn(Class targetClass, List<NativeSQLQueryScalarReturn> columnReturns) {
38+
this.targetClass = targetClass;
39+
this.columnReturns = columnReturns.toArray( new NativeSQLQueryScalarReturn[ columnReturns.size() ] );
40+
}
41+
42+
public Class getTargetClass() {
43+
return targetClass;
44+
}
45+
46+
public NativeSQLQueryScalarReturn[] getColumnReturns() {
47+
return columnReturns;
48+
}
49+
}

hibernate-core/src/main/java/org/hibernate/internal/SQLQueryImpl.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import org.hibernate.ScrollableResults;
4343
import org.hibernate.engine.ResultSetMappingDefinition;
4444
import org.hibernate.engine.query.spi.ParameterMetadata;
45+
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
4546
import org.hibernate.engine.query.spi.sql.NativeSQLQueryJoinReturn;
4647
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
4748
import org.hibernate.engine.query.spi.sql.NativeSQLQueryRootReturn;
@@ -205,6 +206,10 @@ protected void verifyParameters() {
205206
break;
206207
}
207208
}
209+
else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) {
210+
autoDiscoverTypes = true;
211+
break;
212+
}
208213
}
209214
}
210215
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Hibernate, Relational Persistence for Idiomatic Java
3+
*
4+
* Copyright (c) 2013, Red Hat Inc. 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 Inc.
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+
package org.hibernate.loader.custom;
25+
26+
import java.lang.reflect.Constructor;
27+
28+
/**
29+
* @author Steve Ebersole
30+
*/
31+
public class ConstructorReturn implements Return {
32+
private final Class targetClass;
33+
private final ScalarReturn[] scalars;
34+
35+
// private final Constructor constructor;
36+
37+
public ConstructorReturn(Class targetClass, ScalarReturn[] scalars) {
38+
this.targetClass = targetClass;
39+
this.scalars = scalars;
40+
41+
// constructor = resolveConstructor( targetClass, scalars );
42+
}
43+
44+
private static Constructor resolveConstructor(Class targetClass, ScalarReturn[] scalars) {
45+
for ( Constructor constructor : targetClass.getConstructors() ) {
46+
final Class[] argumentTypes = constructor.getParameterTypes();
47+
if ( argumentTypes.length != scalars.length ) {
48+
continue;
49+
}
50+
51+
boolean allMatched = true;
52+
for ( int i = 0; i < argumentTypes.length; i++ ) {
53+
if ( areAssignmentCompatible( argumentTypes[i], scalars[i].getType().getReturnedClass() ) ) {
54+
allMatched = false;
55+
break;
56+
}
57+
}
58+
if ( !allMatched ) {
59+
continue;
60+
}
61+
62+
return constructor;
63+
}
64+
65+
throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() );
66+
}
67+
68+
@SuppressWarnings("unchecked")
69+
private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) {
70+
// todo : add handling for primitive/wrapper equivalents
71+
return argumentType.isAssignableFrom( typeReturnedClass );
72+
}
73+
74+
public Class getTargetClass() {
75+
return targetClass;
76+
}
77+
78+
public ScalarReturn[] getScalars() {
79+
return scalars;
80+
}
81+
82+
// public Constructor getConstructor() {
83+
// return constructor;
84+
// }
85+
}

hibernate-core/src/main/java/org/hibernate/loader/custom/CustomLoader.java

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
package org.hibernate.loader.custom;
2525

2626
import java.io.Serializable;
27+
import java.lang.reflect.Constructor;
28+
import java.lang.reflect.InvocationTargetException;
2729
import java.sql.ResultSet;
2830
import java.sql.ResultSetMetaData;
2931
import java.sql.SQLException;
@@ -139,6 +141,25 @@ public CustomLoader(CustomQuery customQuery, SessionFactoryImplementor factory)
139141
includeInResultRowList.add( true );
140142
hasScalars = true;
141143
}
144+
else if ( ConstructorReturn.class.isInstance( rtn ) ) {
145+
final ConstructorReturn constructorReturn = (ConstructorReturn) rtn;
146+
resultTypes.add( null ); // this bit makes me nervous
147+
includeInResultRowList.add( true );
148+
hasScalars = true;
149+
150+
ScalarResultColumnProcessor[] scalarProcessors = new ScalarResultColumnProcessor[ constructorReturn.getScalars().length ];
151+
int i = 0;
152+
for ( ScalarReturn scalarReturn : constructorReturn.getScalars() ) {
153+
scalarProcessors[i++] = new ScalarResultColumnProcessor(
154+
StringHelper.unquote( scalarReturn.getColumnAlias(), factory.getDialect() ),
155+
scalarReturn.getType()
156+
);
157+
}
158+
159+
resultColumnProcessors.add(
160+
new ConstructorResultColumnProcessor( constructorReturn.getTargetClass(), scalarProcessors )
161+
);
162+
}
142163
else if ( rtn instanceof RootReturn ) {
143164
RootReturn rootRtn = ( RootReturn ) rtn;
144165
Queryable persister = ( Queryable ) factory.getEntityPersister( rootRtn.getEntityName() );
@@ -601,6 +622,90 @@ else if ( position < 0 ) {
601622
}
602623
}
603624

625+
public class ConstructorResultColumnProcessor implements ResultColumnProcessor {
626+
private final Class targetClass;
627+
private final ScalarResultColumnProcessor[] scalarProcessors;
628+
629+
private Constructor constructor;
630+
631+
public ConstructorResultColumnProcessor(Class targetClass, ScalarResultColumnProcessor[] scalarProcessors) {
632+
this.targetClass = targetClass;
633+
this.scalarProcessors = scalarProcessors;
634+
}
635+
636+
@Override
637+
public Object extract(Object[] data, ResultSet resultSet, SessionImplementor session)
638+
throws SQLException, HibernateException {
639+
if ( constructor == null ) {
640+
throw new IllegalStateException( "Constructor to call was null" );
641+
}
642+
643+
final Object[] args = new Object[ scalarProcessors.length ];
644+
for ( int i = 0; i < scalarProcessors.length; i++ ) {
645+
args[i] = scalarProcessors[i].extract( data, resultSet, session );
646+
}
647+
648+
try {
649+
return constructor.newInstance( args );
650+
}
651+
catch (InvocationTargetException e) {
652+
throw new HibernateException(
653+
String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ),
654+
e
655+
);
656+
}
657+
catch (Exception e) {
658+
throw new HibernateException(
659+
String.format( "Unable to call %s constructor", constructor.getDeclaringClass() ),
660+
e
661+
);
662+
}
663+
}
664+
665+
@Override
666+
public void performDiscovery(Metadata metadata, List<Type> types, List<String> aliases) throws SQLException {
667+
final List<Type> localTypes = new ArrayList<Type>();
668+
for ( ScalarResultColumnProcessor scalar : scalarProcessors ) {
669+
scalar.performDiscovery( metadata, localTypes, aliases );
670+
}
671+
672+
types.addAll( localTypes );
673+
674+
constructor = resolveConstructor( targetClass, localTypes );
675+
}
676+
677+
}
678+
679+
private static Constructor resolveConstructor(Class targetClass, List<Type> types) {
680+
for ( Constructor constructor : targetClass.getConstructors() ) {
681+
final Class[] argumentTypes = constructor.getParameterTypes();
682+
if ( argumentTypes.length != types.size() ) {
683+
continue;
684+
}
685+
686+
boolean allMatched = true;
687+
for ( int i = 0; i < argumentTypes.length; i++ ) {
688+
if ( ! areAssignmentCompatible( argumentTypes[i], types.get( i ).getReturnedClass() ) ) {
689+
allMatched = false;
690+
break;
691+
}
692+
}
693+
if ( !allMatched ) {
694+
continue;
695+
}
696+
697+
return constructor;
698+
}
699+
700+
throw new IllegalArgumentException( "Could not locate appropriate constructor on class : " + targetClass.getName() );
701+
}
702+
703+
@SuppressWarnings("unchecked")
704+
private static boolean areAssignmentCompatible(Class argumentType, Class typeReturnedClass) {
705+
// todo : add handling for primitive/wrapper equivalents
706+
return argumentType.isAssignableFrom( typeReturnedClass );
707+
}
708+
604709
@Override
605710
protected void autoDiscoverTypes(ResultSet rs) {
606711
try {

hibernate-core/src/main/java/org/hibernate/loader/custom/sql/SQLQueryReturnProcessor.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.hibernate.HibernateException;
3838
import org.hibernate.MappingException;
3939
import org.hibernate.engine.query.spi.sql.NativeSQLQueryCollectionReturn;
40+
import org.hibernate.engine.query.spi.sql.NativeSQLQueryConstructorReturn;
4041
import org.hibernate.engine.query.spi.sql.NativeSQLQueryJoinReturn;
4142
import org.hibernate.engine.query.spi.sql.NativeSQLQueryNonScalarReturn;
4243
import org.hibernate.engine.query.spi.sql.NativeSQLQueryReturn;
@@ -53,6 +54,7 @@
5354
import org.hibernate.loader.custom.CollectionFetchReturn;
5455
import org.hibernate.loader.custom.CollectionReturn;
5556
import org.hibernate.loader.custom.ColumnCollectionAliases;
57+
import org.hibernate.loader.custom.ConstructorReturn;
5658
import org.hibernate.loader.custom.EntityFetchReturn;
5759
import org.hibernate.loader.custom.FetchReturn;
5860
import org.hibernate.loader.custom.NonScalarReturn;
@@ -353,6 +355,20 @@ else if ( queryReturn instanceof NativeSQLQueryJoinReturn ) {
353355
customReturns.add( customReturn );
354356
customReturnsByAlias.put( alias, customReturn );
355357
}
358+
else if ( NativeSQLQueryConstructorReturn.class.isInstance( queryReturn ) ) {
359+
final NativeSQLQueryConstructorReturn constructorReturn = (NativeSQLQueryConstructorReturn) queryReturn;
360+
final ScalarReturn[] scalars = new ScalarReturn[ constructorReturn.getColumnReturns().length ];
361+
int i = 0;
362+
for ( NativeSQLQueryScalarReturn scalarReturn : constructorReturn.getColumnReturns() ) {
363+
scalars[i++] = new ScalarReturn( scalarReturn.getType(), scalarReturn.getColumnAlias() );
364+
}
365+
customReturns.add( new ConstructorReturn( constructorReturn.getTargetClass(), scalars ) );
366+
}
367+
else {
368+
throw new IllegalStateException(
369+
"Unrecognized NativeSQLQueryReturn concrete type : " + queryReturn
370+
);
371+
}
356372
}
357373
return customReturns;
358374
}
@@ -381,11 +397,23 @@ else if ( rtn instanceof NativeSQLQueryRootReturn ) {
381397
processRootReturn( ( NativeSQLQueryRootReturn ) rtn );
382398
}
383399
else if ( rtn instanceof NativeSQLQueryCollectionReturn ) {
384-
processCollectionReturn( ( NativeSQLQueryCollectionReturn ) rtn );
400+
processCollectionReturn( (NativeSQLQueryCollectionReturn) rtn );
385401
}
386-
else {
402+
else if ( NativeSQLQueryJoinReturn.class.isInstance( rtn ) ) {
387403
processJoinReturn( ( NativeSQLQueryJoinReturn ) rtn );
388404
}
405+
else if ( NativeSQLQueryConstructorReturn.class.isInstance( rtn ) ) {
406+
processConstructorReturn( (NativeSQLQueryConstructorReturn) rtn );
407+
}
408+
else {
409+
throw new IllegalStateException(
410+
"Unrecognized NativeSQLQueryReturn concrete type encountered : " + rtn
411+
);
412+
}
413+
}
414+
415+
private void processConstructorReturn(NativeSQLQueryConstructorReturn rtn) {
416+
//To change body of created methods use File | Settings | File Templates.
389417
}
390418

391419
private void processScalarReturn(NativeSQLQueryScalarReturn typeReturn) {

0 commit comments

Comments
 (0)