HHH-17772 initial, rudimentary support for Jakarta Data annotations

This commit is contained in:
Gavin King 2024-02-23 10:10:20 +01:00
parent 0163fceed9
commit 5be9463364
6 changed files with 282 additions and 32 deletions

View File

@ -41,13 +41,10 @@ import static javax.lang.model.util.ElementFilter.fieldsIn;
import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.hibernate.jpamodelgen.util.Constants.FIND;
import static org.hibernate.jpamodelgen.util.Constants.HQL;
import static org.hibernate.jpamodelgen.util.Constants.JD_REPOSITORY;
import static org.hibernate.jpamodelgen.util.Constants.SQL;
import static org.hibernate.jpamodelgen.util.StringUtil.isProperty;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
import static org.hibernate.jpamodelgen.util.TypeUtils.getCollectionElementType;
import static org.hibernate.jpamodelgen.util.TypeUtils.isAnnotationMirrorOfType;
import static org.hibernate.jpamodelgen.util.TypeUtils.isClassOrRecordType;
import static org.hibernate.jpamodelgen.util.TypeUtils.toTypeString;
import static org.hibernate.jpamodelgen.util.TypeUtils.*;
/**
* Main annotation processor.
@ -70,7 +67,9 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.toTypeString;
Constants.HIB_FETCH_PROFILE,
Constants.HIB_FILTER_DEF,
Constants.HIB_NAMED_QUERY,
Constants.HIB_NAMED_NATIVE_QUERY
Constants.HIB_NAMED_NATIVE_QUERY,
// do not need to list other Jakarta Data annotations here
Constants.JD_REPOSITORY
})
@SupportedOptions({
JPAMetaModelEntityProcessor.DEBUG_OPTION,
@ -246,8 +245,20 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
}
else if ( element instanceof TypeElement ) {
final TypeElement typeElement = (TypeElement) element;
final AnnotationMirror repository = getAnnotationMirror( element, JD_REPOSITORY );
if ( repository != null ) {
final String provider = (String) getAnnotationValue( repository, "provider" );
if ( provider == null || provider.isEmpty()
|| provider.equalsIgnoreCase("hibernate") ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing repository class '" + element + "'" );
final AnnotationMetaEntity metaEntity =
AnnotationMetaEntity.create( typeElement, context, false, false );
context.addMetaAuxiliary( metaEntity.getQualifiedName(), metaEntity );
}
}
else {
for ( Element member : typeElement.getEnclosedElements() ) {
if ( containsAnnotation( member, HQL, SQL, FIND ) ) {
if ( hasAnnotation( member, HQL, SQL, FIND ) ) {
context.logMessage( Diagnostic.Kind.OTHER, "Processing annotated class '" + element + "'" );
final AnnotationMetaEntity metaEntity =
AnnotationMetaEntity.create( typeElement, context, false, false );
@ -255,6 +266,7 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
break;
}
}
}
}
}
catch ( ProcessLaterException processLaterException ) {

View File

@ -32,6 +32,7 @@ import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hibernate.AssertionFailure;
import org.hibernate.jpamodelgen.Context;
import org.hibernate.jpamodelgen.ImportContextImpl;
import org.hibernate.jpamodelgen.ProcessLaterException;
@ -64,10 +65,19 @@ import static javax.lang.model.util.ElementFilter.methodsIn;
import static org.hibernate.internal.util.StringHelper.qualify;
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isOrderParam;
import static org.hibernate.jpamodelgen.annotation.QueryMethod.isPageParam;
import static org.hibernate.jpamodelgen.util.Constants.FIND;
import static org.hibernate.jpamodelgen.util.Constants.HIB_SESSION;
import static org.hibernate.jpamodelgen.util.Constants.HIB_STATELESS_SESSION;
import static org.hibernate.jpamodelgen.util.Constants.HQL;
import static org.hibernate.jpamodelgen.util.Constants.JD_DELETE;
import static org.hibernate.jpamodelgen.util.Constants.JD_FIND;
import static org.hibernate.jpamodelgen.util.Constants.JD_INSERT;
import static org.hibernate.jpamodelgen.util.Constants.JD_QUERY;
import static org.hibernate.jpamodelgen.util.Constants.JD_REPOSITORY;
import static org.hibernate.jpamodelgen.util.Constants.JD_UPDATE;
import static org.hibernate.jpamodelgen.util.Constants.MUTINY_SESSION;
import static org.hibernate.jpamodelgen.util.Constants.SESSION_TYPES;
import static org.hibernate.jpamodelgen.util.Constants.SQL;
import static org.hibernate.jpamodelgen.util.NullnessUtil.castNonNull;
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
import static org.hibernate.jpamodelgen.util.TypeUtils.determineAccessTypeForHierarchy;
@ -75,6 +85,7 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecif
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror;
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValue;
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValueRef;
import static org.hibernate.jpamodelgen.util.TypeUtils.hasAnnotation;
/**
* Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass).
@ -284,16 +295,25 @@ public class AnnotationMetaEntity extends AnnotationMeta {
final List<ExecutableElement> methodsOfClass = methodsIn( element.getEnclosedElements() );
final List<ExecutableElement> gettersAndSettersOfClass = new ArrayList<>();
final List<ExecutableElement> queryMethods = new ArrayList<>();
final List<ExecutableElement> lifecycleMethods = new ArrayList<>();
for ( ExecutableElement method: methodsOfClass ) {
if ( isGetterOrSetter( method ) ) {
gettersAndSettersOfClass.add( method );
}
else if ( containsAnnotation( method, Constants.HQL, Constants.SQL, Constants.FIND ) ) {
else if ( containsAnnotation( method, HQL, SQL, JD_QUERY, FIND, JD_FIND ) ) {
queryMethods.add( method );
}
else if ( containsAnnotation( method, JD_INSERT, JD_UPDATE, JD_DELETE ) ) {
lifecycleMethods.add( method );
}
}
findSessionGetter( element );
if ( !dao && hasAnnotation( element, JD_REPOSITORY ) ) {
dao = true;
sessionType = HIB_STATELESS_SESSION;
addDaoConstructor( null );
}
if ( managed ) {
putMember( "class", new AnnotationMetaType(this) );
@ -306,15 +326,17 @@ public class AnnotationMetaEntity extends AnnotationMeta {
checkNamedQueries();
addLifecycleMethods( lifecycleMethods );
addQueryMethods( queryMethods );
initialized = true;
}
private void findSessionGetter(TypeElement type) {
if ( !containsAnnotation( type, Constants.ENTITY )
&& !containsAnnotation( type, Constants.MAPPED_SUPERCLASS )
&& !containsAnnotation( type, Constants.EMBEDDABLE ) ) {
if ( !hasAnnotation( type, Constants.ENTITY )
&& !hasAnnotation( type, Constants.MAPPED_SUPERCLASS )
&& !hasAnnotation( type, Constants.EMBEDDABLE ) ) {
for ( ExecutableElement method : methodsIn( type.getEnclosedElements() ) ) {
if ( isSessionGetter( method ) ) {
dao = true;
@ -342,19 +364,21 @@ public class AnnotationMetaEntity extends AnnotationMeta {
* variable backing it, together with a constructor that initializes
* it.
*/
private String addDaoConstructor(ExecutableElement method) {
final String name = method.getSimpleName().toString();
private String addDaoConstructor(@Nullable ExecutableElement method) {
final String sessionType = method == null ? this.sessionType : method.getReturnType().toString();
final String sessionVariableName = getSessionVariableName( sessionType );
final String name = method == null ? sessionVariableName : method.getSimpleName().toString();
final String typeName = element.getSimpleName().toString() + '_';
final String sessionType = method.getReturnType().toString();
putMember( name,
new DaoConstructor(
this,
typeName,
name,
sessionType,
getSessionVariableName(sessionType),
sessionVariableName,
context.addInjectAnnotation(),
context.addNonnullAnnotation()
context.addNonnullAnnotation(),
method != null
)
);
return sessionType;
@ -445,6 +469,16 @@ public class AnnotationMetaEntity extends AnnotationMeta {
&& isSessionGetter( (ExecutableElement) memberOfClass ) );
}
private void addLifecycleMethods(List<ExecutableElement> queryMethods) {
for ( ExecutableElement method : queryMethods) {
if ( method.getModifiers().contains(Modifier.ABSTRACT) ) {
if ( hasAnnotation( method, JD_INSERT, JD_UPDATE, JD_DELETE ) ) {
addLifecycleMethod( method );
}
}
}
}
private void addQueryMethods(List<ExecutableElement> queryMethods) {
for ( ExecutableElement method : queryMethods) {
if ( method.getModifiers().contains(Modifier.ABSTRACT) ) {
@ -521,20 +555,91 @@ public class AnnotationMetaEntity extends AnnotationMeta {
ExecutableElement method,
@Nullable TypeMirror returnType,
@Nullable TypeElement containerType) {
final AnnotationMirror hql = getAnnotationMirror( method, Constants.HQL );
final AnnotationMirror hql = getAnnotationMirror( method, HQL );
if ( hql != null ) {
addQueryMethod( method, returnType, containerType, hql, false );
}
final AnnotationMirror sql = getAnnotationMirror( method, Constants.SQL );
final AnnotationMirror sql = getAnnotationMirror( method, SQL );
if ( sql != null ) {
addQueryMethod( method, returnType, containerType, sql, true );
}
final AnnotationMirror find = getAnnotationMirror( method, Constants.FIND );
if ( find != null ) {
final AnnotationMirror jdql = getAnnotationMirror( method, JD_QUERY );
if ( jdql != null ) {
addQueryMethod( method, returnType, containerType, jdql, false );
}
if ( hasAnnotation( method, FIND, JD_FIND ) ) {
addFinderMethod( method, returnType, containerType );
}
}
private void addLifecycleMethod(ExecutableElement method) {
final TypeMirror returnType = method.getReturnType();
if ( !HIB_STATELESS_SESSION.equals(sessionType) ) {
context.message( method,
"repository must be backed by a 'StatelessSession'",
Diagnostic.Kind.ERROR );
}
else if ( method.getParameters().size() != 1 ) {
context.message( method,
"must have exactly one parameter",
Diagnostic.Kind.ERROR );
}
else if ( returnType == null || returnType.getKind() != TypeKind.VOID ) {
context.message( method,
"must be declared 'void'",
Diagnostic.Kind.ERROR );
}
else {
final String operation = lifecycleOperation( method );
final VariableElement parameter = method.getParameters().get(0);
final TypeMirror parameterType = parameter.asType();
if ( parameterType.getKind() != TypeKind.DECLARED ) {
context.message( parameter,
"incorrect parameter type '" + parameterType + "' is not an entity type",
Diagnostic.Kind.ERROR );
}
else {
final DeclaredType declaredType = (DeclaredType) parameterType;
if ( !containsAnnotation( declaredType.asElement(), Constants.ENTITY ) ) {
context.message( parameter,
"incorrect parameter type '" + parameterType + "' is not annotated '@Entity'",
Diagnostic.Kind.ERROR );
}
else {
putMember(
method.getSimpleName().toString()
+ '.' + operation,
new LifecycleMethod(
this,
parameterType.toString(),
method.getSimpleName().toString(),
parameter.getSimpleName().toString(),
getSessionVariableName(),
operation,
context.addNonnullAnnotation()
)
);
}
}
}
}
private static String lifecycleOperation(ExecutableElement method) {
if ( hasAnnotation(method, JD_INSERT ) ) {
return "insert";
}
else if ( hasAnnotation(method, JD_UPDATE ) ) {
return "update";
}
else if ( hasAnnotation(method, JD_DELETE ) ) {
return "delete";
}
else {
throw new AssertionFailure("Unrecognized lifecycle operation");
}
}
private void addFinderMethod(
ExecutableElement method,
@Nullable TypeMirror returnType,
@ -700,20 +805,25 @@ public class AnnotationMetaEntity extends AnnotationMeta {
}
private static List<String> enabledFetchProfiles(ExecutableElement method) {
final Object enabledFetchProfiles =
getAnnotationValue( castNonNull( getAnnotationMirror( method, Constants.FIND ) ),
"enabledFetchProfiles" );
if ( enabledFetchProfiles == null ) {
final AnnotationMirror findAnnotation = getAnnotationMirror( method, FIND );
if ( findAnnotation == null ) {
return emptyList();
}
else {
@SuppressWarnings("unchecked")
final List<AnnotationValue> annotationValues = (List<AnnotationValue>) enabledFetchProfiles;
final List<String> result = annotationValues.stream().map(AnnotationValue::toString).collect(toList());
if ( result.stream().anyMatch("<error>"::equals) ) {
throw new ProcessLaterException();
final Object enabledFetchProfiles =
getAnnotationValue( findAnnotation, "enabledFetchProfiles" );
if ( enabledFetchProfiles == null ) {
return emptyList();
}
else {
@SuppressWarnings("unchecked")
final List<AnnotationValue> annotationValues = (List<AnnotationValue>) enabledFetchProfiles;
final List<String> result = annotationValues.stream().map(AnnotationValue::toString).collect(toList());
if ( result.stream().anyMatch("<error>"::equals) ) {
throw new ProcessLaterException();
}
return result;
}
return result;
}
}

View File

@ -21,6 +21,7 @@ public class DaoConstructor implements MetaAttribute {
private final String sessionVariableName;
private final boolean addInjectAnnotation;
private final boolean addNonnullAnnotation;
private final boolean addOverrideAnnotation;
public DaoConstructor(
Metamodel annotationMetaEntity,
@ -29,7 +30,8 @@ public class DaoConstructor implements MetaAttribute {
String sessionTypeName,
String sessionVariableName,
boolean addInjectAnnotation,
boolean addNonnullAnnotation) {
boolean addNonnullAnnotation,
boolean addOverrideAnnotation) {
this.annotationMetaEntity = annotationMetaEntity;
this.constructorName = constructorName;
this.methodName = methodName;
@ -37,6 +39,7 @@ public class DaoConstructor implements MetaAttribute {
this.sessionVariableName = sessionVariableName;
this.addInjectAnnotation = addInjectAnnotation;
this.addNonnullAnnotation = addNonnullAnnotation;
this.addOverrideAnnotation = addOverrideAnnotation;
}
@Override
@ -78,7 +81,11 @@ public class DaoConstructor implements MetaAttribute {
.append(sessionVariableName)
.append(";")
.append("\n}")
.append("\n\n")
.append("\n\n");
if (addOverrideAnnotation) {
declaration.append("@Override\n");
}
declaration
.append("public ");
notNull( declaration );
declaration

View File

@ -0,0 +1,105 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpamodelgen.annotation;
import org.hibernate.jpamodelgen.model.MetaAttribute;
import org.hibernate.jpamodelgen.model.Metamodel;
public class LifecycleMethod implements MetaAttribute {
private final AnnotationMetaEntity annotationMetaEntity;
private final String entity;
private final String methodName;
private final String parameterName;
private final String sessionName;
private final String operationName;
private final boolean addNonnullAnnotation;
public LifecycleMethod(
AnnotationMetaEntity annotationMetaEntity,
String entity,
String methodName,
String parameterName,
String sessionName,
String operationName,
boolean addNonnullAnnotation) {
this.annotationMetaEntity = annotationMetaEntity;
this.entity = entity;
this.methodName = methodName;
this.parameterName = parameterName;
this.sessionName = sessionName;
this.operationName = operationName;
this.addNonnullAnnotation = addNonnullAnnotation;
}
@Override
public boolean hasTypedAttribute() {
return true;
}
@Override
public boolean hasStringAttribute() {
return false;
}
@Override
public String getAttributeDeclarationString() {
StringBuilder declaration = new StringBuilder()
.append("\n@Override\npublic void ")
.append(methodName)
.append('(');
notNull( declaration );
declaration
.append(annotationMetaEntity.importType(entity))
.append(' ')
.append(parameterName)
.append(')')
.append(" {\n")
.append("\t")
.append(sessionName)
.append('.')
.append(operationName)
.append('(')
.append(parameterName)
.append(')')
.append(";\n}");
return declaration.toString();
}
private void notNull(StringBuilder declaration) {
if ( addNonnullAnnotation ) {
declaration
.append('@')
.append(annotationMetaEntity.importType("jakarta.annotation.Nonnull"))
.append(' ');
}
}
@Override
public String getAttributeNameDeclarationString() {
throw new UnsupportedOperationException();
}
@Override
public String getMetaType() {
throw new UnsupportedOperationException();
}
@Override
public String getPropertyName() {
return methodName;
}
@Override
public String getTypeDeclaration() {
return entity;
}
@Override
public Metamodel getHostingEntity() {
return annotationMetaEntity;
}
}

View File

@ -61,6 +61,13 @@ public final class Constants {
public static final String SQL = "org.hibernate.annotations.processing.SQL";
public static final String FIND = "org.hibernate.annotations.processing.Find";
public static final String JD_REPOSITORY = "jakarta.data.repository.Repository";
public static final String JD_QUERY = "jakarta.data.repository.Query";
public static final String JD_FIND = "jakarta.data.repository.Find";
public static final String JD_INSERT = "jakarta.data.repository.Insert";
public static final String JD_UPDATE = "jakarta.data.repository.Update";
public static final String JD_DELETE = "jakarta.data.repository.Delete";
public static final String CHECK_HQL = "org.hibernate.annotations.processing.CheckHQL";
public static final String ENTITY_MANAGER = "jakarta.persistence.EntityManager";

View File

@ -231,6 +231,15 @@ public final class TypeUtils {
return getAnnotationMirror( element, qualifiedName ) != null;
}
public static boolean hasAnnotation(Element element, String... qualifiedNames) {
for ( String qualifiedName : qualifiedNames ) {
if ( hasAnnotation( element, qualifiedName ) ) {
return true;
}
}
return false;
}
public static @Nullable Object getAnnotationValue(AnnotationMirror annotationMirror, String parameterValue) {
assert annotationMirror != null;
assert parameterValue != null;