HHH-16920 initial prototype support for Reactive in query/finder method generation

This commit is contained in:
Gavin King 2023-07-11 01:27:12 +02:00
parent 70a953e7a8
commit 59fdc46254
11 changed files with 235 additions and 82 deletions

View File

@ -8,7 +8,6 @@ package org.hibernate.jpamodelgen;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -29,6 +28,9 @@ import org.hibernate.jpamodelgen.util.AccessTypeInformation;
import org.checkerframework.checker.nullness.qual.Nullable;
import static java.lang.Boolean.parseBoolean;
import static java.util.Collections.emptyList;
/**
* @author Max Andersen
* @author Hardy Ferentschik
@ -77,12 +79,14 @@ public final class Context {
private final Collection<String> generatedModelClasses = new HashSet<>();
// keep track of which named queries have been checked
private Set<String> checkedNamedQueries = new HashSet<>();
private final Set<String> checkedNamedQueries = new HashSet<>();
public Context(ProcessingEnvironment processingEnvironment) {
this.processingEnvironment = processingEnvironment;
String persistenceXmlOption = processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.PERSISTENCE_XML_OPTION );
final Map<String, String> options = processingEnvironment.getOptions();
String persistenceXmlOption = options.get( JPAMetaModelEntityProcessor.PERSISTENCE_XML_OPTION );
if ( persistenceXmlOption != null ) {
if ( !persistenceXmlOption.startsWith("/") ) {
persistenceXmlOption = "/" + persistenceXmlOption;
@ -93,7 +97,7 @@ public final class Context {
persistenceXmlLocation = DEFAULT_PERSISTENCE_XML_LOCATION;
}
String ormXmlOption = processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.ORM_XML_OPTION );
String ormXmlOption = options.get( JPAMetaModelEntityProcessor.ORM_XML_OPTION );
if ( ormXmlOption != null ) {
ormXmlFiles = new ArrayList<>();
for ( String ormFile : ormXmlOption.split( "," ) ) {
@ -104,11 +108,11 @@ public final class Context {
}
}
else {
ormXmlFiles = Collections.emptyList();
ormXmlFiles = emptyList();
}
lazyXmlParsing = Boolean.parseBoolean( processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.LAZY_XML_PARSING ) );
logDebug = Boolean.parseBoolean( processingEnvironment.getOptions().get( JPAMetaModelEntityProcessor.DEBUG_OPTION ) );
lazyXmlParsing = parseBoolean( options.get( JPAMetaModelEntityProcessor.LAZY_XML_PARSING ) );
logDebug = parseBoolean( options.get( JPAMetaModelEntityProcessor.DEBUG_OPTION ) );
}
public ProcessingEnvironment getProcessingEnvironment() {

View File

@ -26,6 +26,7 @@ public abstract class AbstractFinderMethod implements MetaAttribute {
final boolean belongsToDao;
final String sessionType;
final boolean usingEntityManager;
final boolean reactive;
private final boolean addNonnullAnnotation;
final List<String> fetchProfiles;
@ -50,8 +51,9 @@ public abstract class AbstractFinderMethod implements MetaAttribute {
this.fetchProfiles = fetchProfiles;
this.paramNames = paramNames;
this.paramTypes = paramTypes;
this.usingEntityManager = Constants.ENTITY_MANAGER.equals(sessionType);
this.addNonnullAnnotation = addNonnullAnnotation;
this.usingEntityManager = Constants.ENTITY_MANAGER.equals(sessionType);
this.reactive = Constants.MUTINY_SESSION.equals(sessionType);
}
@Override
@ -203,8 +205,7 @@ public abstract class AbstractFinderMethod implements MetaAttribute {
void preamble(StringBuilder declaration) {
modifiers( declaration );
declaration
.append(annotationMetaEntity.importType(entity));
entityType( declaration );
declaration
.append(" ")
.append(methodName);
@ -214,6 +215,20 @@ public abstract class AbstractFinderMethod implements MetaAttribute {
.append("\n\treturn entityManager");
}
private void entityType(StringBuilder declaration) {
if ( reactive ) {
declaration
.append(annotationMetaEntity.importType(Constants.UNI))
.append('<');
}
declaration
.append(annotationMetaEntity.importType(entity));
if ( reactive ) {
declaration
.append('>');
}
}
void modifiers(StringBuilder declaration) {
declaration
.append(belongsToDao ? "@Override\npublic " : "public static ");

View File

@ -8,6 +8,7 @@ package org.hibernate.jpamodelgen.annotation;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.jpamodelgen.Context;
import org.hibernate.jpamodelgen.model.MetaAttribute;
import org.hibernate.jpamodelgen.model.Metamodel;
@ -108,7 +109,8 @@ public abstract class AnnotationMeta implements Metamodel {
this,
(SqmSelectStatement<?>) statement,
name.substring(1),
belongsToDao()
belongsToDao(),
getSessionType()
)
);
}
@ -160,6 +162,8 @@ public abstract class AnnotationMeta implements Metamodel {
abstract boolean belongsToDao();
abstract @Nullable String getSessionType();
abstract void putMember(String name, MetaAttribute nameMetaAttribute);
/**

View File

@ -219,10 +219,15 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
@Override
public boolean belongsToDao() {
boolean belongsToDao() {
return dao;
}
@Override
String getSessionType() {
return sessionType;
}
@Override
public boolean isInjectable() {
return dao;
@ -289,10 +294,10 @@ public class AnnotationMetaEntity extends AnnotationMeta {
* variable backing it, together with a constructor that initializes
* it.
*/
private String addDaoConstructor(ExecutableElement getterOrSetter) {
final String name = getterOrSetter.getSimpleName().toString();
private String addDaoConstructor(ExecutableElement method) {
final String name = method.getSimpleName().toString();
final String typeName = element.getSimpleName().toString() + '_';
final String sessionType = getterOrSetter.getReturnType().toString();
final String sessionType = method.getReturnType().toString();
putMember( name,
new DaoConstructor(
this,
@ -321,6 +326,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
final Name name = ((TypeElement) element).getQualifiedName();
return name.contentEquals(Constants.HIB_SESSION)
|| name.contentEquals(Constants.HIB_STATELESS_SESSION)
|| name.contentEquals(Constants.MUTINY_SESSION)
|| name.contentEquals(Constants.ENTITY_MANAGER);
}
}
@ -389,7 +395,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
private void addQueryMethod(ExecutableElement method) {
final TypeMirror returnType = method.getReturnType();
if ( returnType.getKind() == TypeKind.DECLARED ) {
final DeclaredType declaredType = (DeclaredType) returnType;
final DeclaredType declaredType = ununi((DeclaredType) returnType);
final TypeElement typeElement = (TypeElement) declaredType.asElement();
final List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
switch ( typeArguments.size() ) {
@ -426,6 +432,13 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
}
private static DeclaredType ununi(DeclaredType returnType) {
final TypeElement typeElement = (TypeElement) returnType.asElement();
return typeElement.getQualifiedName().contentEquals(Constants.UNI)
? (DeclaredType) returnType.getTypeArguments().get(0)
: returnType;
}
private static boolean isLegalRawResultType(String containerTypeName) {
return containerTypeName.equals(Constants.LIST)
|| containerTypeName.equals(Constants.QUERY)
@ -467,7 +480,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
Diagnostic.Kind.ERROR );
}
else {
final DeclaredType declaredType = (DeclaredType) returnType;
final DeclaredType declaredType = ununi( (DeclaredType) returnType );
final TypeElement entity = (TypeElement) declaredType.asElement();
if ( !containsAnnotation( entity, Constants.ENTITY ) ) {
context.message( method,
@ -588,42 +601,39 @@ public class AnnotationMetaEntity extends AnnotationMeta {
final FieldType fieldType = validateFinderParameter( entity, parameter );
if ( fieldType != null ) {
final String methodKey = methodName + "!";
switch ( fieldType ) {
final List<String> profiles = enabledFetchProfiles( method );
switch ( pickStrategy( fieldType, profiles ) ) {
case ID:
if ( !usingStatelessSession()
|| enabledFetchProfiles( method ).isEmpty() ) { // no byId() API for SS, only get()
putMember( methodKey,
new IdFinderMethod(
this,
methodName,
returnType.toString(),
parameter.getSimpleName().toString(),
parameter.asType().toString(),
dao,
sessionType,
enabledFetchProfiles( method ),
context.addNonnullAnnotation()
)
);
break;
}
putMember( methodKey,
new IdFinderMethod(
this,
methodName,
returnType.toString(),
parameter.getSimpleName().toString(),
parameter.asType().toString(),
dao,
sessionType,
profiles,
context.addNonnullAnnotation()
)
);
break;
case NATURAL_ID:
if ( !usingStatelessSession()) { // no byNaturalId() lookup API for SS
putMember( methodKey,
new NaturalIdFinderMethod(
this,
methodName,
returnType.toString(),
List.of( parameter.getSimpleName().toString() ),
List.of( parameter.asType().toString() ),
dao,
sessionType,
enabledFetchProfiles( method ),
context.addNonnullAnnotation()
)
);
break;
}
context.message( method, sessionType, Diagnostic.Kind.WARNING );
putMember( methodKey,
new NaturalIdFinderMethod(
this,
methodName,
returnType.toString(),
List.of( parameter.getSimpleName().toString() ),
List.of( parameter.asType().toString() ),
dao,
sessionType,
profiles,
context.addNonnullAnnotation()
)
);
break;
case BASIC:
putMember( methodKey,
new CriteriaFinderMethod(
@ -636,7 +646,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
fieldType == FieldType.ID,
dao,
sessionType,
enabledFetchProfiles( method ),
profiles,
context.addNonnullAnnotation()
)
);
@ -645,6 +655,22 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
}
private FieldType pickStrategy(FieldType fieldType, List<String> profiles) {
switch (fieldType) {
case ID:
// no byId() API for SS or M.S, only get()
return (usingStatelessSession() || usingReactiveSession()) && !profiles.isEmpty()
? FieldType.BASIC : FieldType.ID;
case NATURAL_ID:
// no byNaturalId() lookup API for SS
// no byNaturalId() in M.S, but we do have Identifier workaround
return usingStatelessSession() || (usingReactiveSession() && !profiles.isEmpty())
? FieldType.BASIC : FieldType.NATURAL_ID;
default:
return FieldType.BASIC;
}
}
private boolean matchesNaturalKey(ExecutableElement method, TypeElement entity) {
boolean result = true;
List<? extends VariableElement> parameters = method.getParameters();
@ -865,6 +891,10 @@ public class AnnotationMetaEntity extends AnnotationMeta {
&& !isOrderParam(type);
}
private boolean usingReactiveSession() {
return Constants.MUTINY_SESSION.equals(sessionType);
}
private boolean usingStatelessSession() {
return Constants.HIB_STATELESS_SESSION.equals(sessionType);
}

View File

@ -6,6 +6,7 @@
*/
package org.hibernate.jpamodelgen.annotation;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.jpamodelgen.Context;
import org.hibernate.jpamodelgen.ImportContextImpl;
import org.hibernate.jpamodelgen.model.ImportContext;
@ -139,6 +140,11 @@ public class AnnotationMetaPackage extends AnnotationMeta {
return false;
}
@Override
@Nullable String getSessionType() {
return null;
}
@Override
public boolean isInjectable() {
return false;

View File

@ -152,6 +152,11 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
private StringBuilder returnType() {
StringBuilder type = new StringBuilder();
boolean returnsUni = reactive
&& (containerType == null || Constants.LIST.equals(containerType));
if ( returnsUni ) {
type.append(annotationMetaEntity.importType(Constants.UNI)).append('<');
}
if ( containerType != null ) {
type.append(annotationMetaEntity.importType(containerType)).append('<');
}
@ -159,6 +164,9 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
if ( containerType != null ) {
type.append('>');
}
if ( returnsUni ) {
type.append('>');
}
return type;
}

View File

@ -43,28 +43,35 @@ public class IdFinderMethod extends AbstractFinderMethod {
final StringBuilder declaration = new StringBuilder();
comment( declaration );
preamble( declaration );
if ( usingStatelessSession || usingEntityManager && fetchProfiles.isEmpty() ) {
declaration
.append(usingStatelessSession ? ".get(" : ".find(")
.append(annotationMetaEntity.importType(entity))
.append(".class, ")
.append(paramName)
.append(");")
.append("\n}");
if ( fetchProfiles.isEmpty() ) {
findWithNoFetchProfiles( declaration );
}
else {
unwrapSession( declaration );
declaration
.append(".byId(")
.append(annotationMetaEntity.importType(entity))
.append(".class)");
enableFetchProfile( declaration );
declaration
.append("\n\t\t\t.load(")
.append(paramName)
.append(");\n}");
findWithFetchProfiles( declaration );
}
return declaration.toString();
}
private void findWithFetchProfiles(StringBuilder declaration) {
unwrapSession( declaration );
declaration
.append(".byId(")
.append(annotationMetaEntity.importType(entity))
.append(".class)");
enableFetchProfile(declaration);
declaration
.append("\n\t\t\t.load(")
.append(paramName)
.append(");\n}");
}
private void findWithNoFetchProfiles(StringBuilder declaration) {
declaration
.append(usingStatelessSession ? ".get(" : ".find(")
.append(annotationMetaEntity.importType(entity))
.append(".class, ")
.append(paramName)
.append(");")
.append("\n}");
}
}

View File

@ -32,12 +32,19 @@ class NamedQueryMethod implements MetaAttribute {
private final SqmSelectStatement<?> select;
private final String name;
private final boolean belongsToDao;
private final boolean reactive;
public NamedQueryMethod(AnnotationMeta annotationMeta, SqmSelectStatement<?> select, String name, boolean belongsToDao) {
public NamedQueryMethod(
AnnotationMeta annotationMeta,
SqmSelectStatement<?> select,
String name,
boolean belongsToDao,
@Nullable String sessionType) {
this.annotationMeta = annotationMeta;
this.select = select;
this.name = name;
this.belongsToDao = belongsToDao;
this.reactive = Constants.MUTINY_SESSION.equals(sessionType);
}
@Override
@ -54,11 +61,10 @@ class NamedQueryMethod implements MetaAttribute {
public String getAttributeDeclarationString() {
final TreeSet<SqmParameter<?>> sortedParameters =
new TreeSet<>( select.getSqmParameters() );
final String returnType = returnType();
StringBuilder declaration = new StringBuilder();
comment( declaration );
modifiers( declaration );
returnType( returnType, declaration );
returnType( declaration );
parameters( sortedParameters, declaration );
declaration
.append(" {")
@ -116,13 +122,22 @@ class NamedQueryMethod implements MetaAttribute {
.append(belongsToDao ? "public " : "public static ");
}
private void returnType(String returnType, StringBuilder declaration) {
private void returnType(StringBuilder declaration) {
if ( reactive ) {
declaration
.append(annotationMeta.importType(Constants.UNI))
.append('<');
}
declaration
.append(annotationMeta.importType(Constants.LIST))
.append('<')
.append(annotationMeta.importType(returnType))
.append(annotationMeta.importType(returnType()))
.append("> ")
.append(name);
if ( reactive ) {
declaration
.append('>');
}
}
private void parameters(TreeSet<SqmParameter<?>> sortedParameters, StringBuilder declaration) {

View File

@ -39,6 +39,17 @@ public class NaturalIdFinderMethod extends AbstractFinderMethod {
comment( declaration );
preamble( declaration );
unwrapSession( declaration );
if ( reactive ) {
findReactively( declaration );
}
else {
findBlockingly( declaration );
}
declaration.append(";\n}");
return declaration.toString();
}
private void findBlockingly(StringBuilder declaration) {
declaration
.append(".byNaturalId(")
.append(annotationMetaEntity.importType(entity))
@ -56,8 +67,49 @@ public class NaturalIdFinderMethod extends AbstractFinderMethod {
.append(")");
}
declaration
.append("\n\t\t\t.load();\n}");
return declaration.toString();
.append("\n\t\t\t.load()");
}
private void findReactively(StringBuilder declaration) {
boolean composite = paramNames.size() > 1;
declaration
.append(".find(");
if (composite) {
declaration.append("\n\t\t\t");
}
declaration
.append(annotationMetaEntity.importType(entity))
.append(".class, ");
if (composite) {
declaration
.append("\n\t\t\t")
.append(annotationMetaEntity.importType("org.hibernate.reactive.common.Identifier"))
.append(".composite(");
}
for ( int i = 0; i < paramNames.size(); i ++ ) {
if ( i>0 ) {
declaration
.append(", ");
}
if (composite) {
declaration
.append("\n\t\t\t\t");
}
final String paramName = paramNames.get(i);
declaration
.append(annotationMetaEntity.importType("org.hibernate.reactive.common.Identifier"))
.append(".id(")
.append(annotationMetaEntity.importType(entity + '_'))
.append('.')
.append(paramName)
.append(", ")
.append(paramName)
.append(")");
}
if (composite) {
declaration.append("\n\t\t\t)\n\t");
}
declaration.append(")");
}
}

View File

@ -32,7 +32,8 @@ public class QueryMethod implements MetaAttribute {
private final List<String> paramTypes;
private final boolean isNative;
private final boolean belongsToDao;
final boolean usingEntityManager;
private final boolean usingEntityManager;
private final boolean reactive;
private final boolean addNonnullAnnotation;
public QueryMethod(
@ -58,8 +59,9 @@ public class QueryMethod implements MetaAttribute {
this.paramTypes = paramTypes;
this.isNative = isNative;
this.belongsToDao = belongsToDao;
this.usingEntityManager = Constants.ENTITY_MANAGER.equals(sessionType);
this.addNonnullAnnotation = addNonnullAnnotation;
this.usingEntityManager = Constants.ENTITY_MANAGER.equals(sessionType);
this.reactive = Constants.MUTINY_SESSION.equals(sessionType);
}
@Override
@ -209,16 +211,23 @@ public class QueryMethod implements MetaAttribute {
private StringBuilder returnType() {
StringBuilder type = new StringBuilder();
boolean returnsUni = reactive
&& (containerTypeName == null || Constants.LIST.equals(containerTypeName));
if ( returnsUni ) {
type.append(annotationMetaEntity.importType(Constants.UNI)).append('<');
}
if ( containerTypeName != null ) {
type.append(annotationMetaEntity.importType(containerTypeName));
if ( returnTypeName != null ) {
type.append("<")
.append(annotationMetaEntity.importType(returnTypeName)).append(">");
type.append("<").append(annotationMetaEntity.importType(returnTypeName)).append(">");
}
}
else if ( returnTypeName != null ) {
type.append(annotationMetaEntity.importType(returnTypeName));
}
if ( returnsUni ) {
type.append('>');
}
return type;
}

View File

@ -70,6 +70,9 @@ public final class Constants {
public static final String HIB_SELECTION_QUERY = "org.hibernate.query.SelectionQuery";
public static final String HIB_SESSION = "org.hibernate.Session";
public static final String HIB_STATELESS_SESSION = "org.hibernate.StatelessSession";
public static final String MUTINY_SESSION = "org.hibernate.reactive.mutiny.Mutiny.Session";
public static final String UNI = "io.smallrye.mutiny.Uni";
public static final String SINGULAR_ATTRIBUTE = "jakarta.persistence.metamodel.SingularAttribute";
public static final String COLLECTION_ATTRIBUTE = "jakarta.persistence.metamodel.CollectionAttribute";