HHH-16633 generate query methods from @NamedQuery annotations
This commit is contained in:
parent
16b433ebf1
commit
db4d529f60
|
@ -50,7 +50,7 @@ public interface SqmExpressible<J> extends BindableType<J> {
|
|||
default String getTypeName() {
|
||||
// default impl to handle the general case returning the Java type name
|
||||
JavaType<J> expressibleJavaType = getExpressibleJavaType();
|
||||
return expressibleJavaType == null ? "unknown" : expressibleJavaType.getJavaType().getTypeName();
|
||||
return expressibleJavaType == null ? "unknown" : expressibleJavaType.getTypeName();
|
||||
}
|
||||
|
||||
DomainType<J> getSqmType();
|
||||
|
|
|
@ -86,6 +86,13 @@ public interface JavaType<T> extends Serializable {
|
|||
return ReflectHelper.getClass( getJavaType() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the Java type.
|
||||
*/
|
||||
default String getTypeName() {
|
||||
return getJavaType().getTypeName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the given value an instance of the described type?
|
||||
* <p>
|
||||
|
|
|
@ -16,6 +16,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.util.Elements;
|
||||
|
@ -73,6 +74,9 @@ public final class Context {
|
|||
// keep track of all classes for which model have been generated
|
||||
private final Collection<String> generatedModelClasses = new HashSet<>();
|
||||
|
||||
// keep track of which named queries have been checked
|
||||
private Set<String> checkedNamedQueries = new HashSet<>();
|
||||
|
||||
public Context(ProcessingEnvironment processingEnvironment) {
|
||||
this.processingEnvironment = processingEnvironment;
|
||||
|
||||
|
@ -266,13 +270,15 @@ public final class Context {
|
|||
getProcessingEnvironment().getMessager()
|
||||
.printMessage( severity, message, method );
|
||||
}
|
||||
|
||||
public void message(Element method, AnnotationMirror mirror, AnnotationValue value, String message, Diagnostic.Kind severity) {
|
||||
getProcessingEnvironment().getMessager()
|
||||
.printMessage( severity, message, method, mirror, value );
|
||||
}
|
||||
|
||||
public void message(Element method, AnnotationMirror mirror, String message, Diagnostic.Kind severity) {
|
||||
getProcessingEnvironment().getMessager()
|
||||
.printMessage( severity, message, method, mirror,
|
||||
mirror.getElementValues().entrySet().stream()
|
||||
.filter( entry -> entry.getKey().getSimpleName().contentEquals("query")
|
||||
|| entry.getKey().getSimpleName().contentEquals("value") )
|
||||
.map(Map.Entry::getValue).findAny().orElseThrow() );
|
||||
.printMessage( severity, message, method, mirror );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -288,4 +294,8 @@ public final class Context {
|
|||
sb.append( '}' );
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public boolean checkNamedQuery(String name) {
|
||||
return checkedNamedQueries.add(name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,12 +164,13 @@ public class JPAMetaModelEntityProcessor extends AbstractProcessor {
|
|||
);
|
||||
}
|
||||
else {
|
||||
context.logMessage( Diagnostic.Kind.OTHER, "Starting new round" );
|
||||
try {
|
||||
processClasses( roundEnvironment );
|
||||
createMetaModelClasses();
|
||||
}
|
||||
catch (Exception e) {
|
||||
context.logMessage( Diagnostic.Kind.ERROR, "Error generating JPA metamodel:" + e.getMessage() );
|
||||
context.logMessage( Diagnostic.Kind.ERROR, "Error generating JPA metamodel: " + e.getMessage() );
|
||||
}
|
||||
}
|
||||
return ALLOW_OTHER_PROCESSORS_TO_CLAIM_ANNOTATIONS;
|
||||
|
|
|
@ -6,18 +6,22 @@
|
|||
*/
|
||||
package org.hibernate.jpamodelgen.annotation;
|
||||
|
||||
import org.antlr.v4.runtime.RecognitionException;
|
||||
import org.antlr.v4.runtime.Recognizer;
|
||||
import org.hibernate.jpamodelgen.model.MetaAttribute;
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
import org.hibernate.jpamodelgen.validation.ProcessorSessionFactory;
|
||||
import org.hibernate.jpamodelgen.validation.Validation;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.emptySet;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.containsAnnotation;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.*;
|
||||
|
||||
public abstract class AnnotationMeta implements Metamodel {
|
||||
|
||||
|
@ -44,61 +48,90 @@ public abstract class AnnotationMeta implements Metamodel {
|
|||
void checkNamedQueries() {
|
||||
boolean checkHql = containsAnnotation( getElement(), Constants.CHECK_HQL )
|
||||
|| containsAnnotation( getElement().getEnclosingElement(), Constants.CHECK_HQL );
|
||||
checkNamedQueriesForAnnotation( Constants.NAMED_QUERY, checkHql );
|
||||
checkNamedQueriesForRepeatableAnnotation( Constants.NAMED_QUERIES, checkHql );
|
||||
checkNamedQueriesForAnnotation( Constants.HIB_NAMED_QUERY, checkHql );
|
||||
checkNamedQueriesForRepeatableAnnotation( Constants.HIB_NAMED_QUERIES, checkHql );
|
||||
handleNamedQueryAnnotation( Constants.NAMED_QUERY, checkHql );
|
||||
handleNamedQueryRepeatableAnnotation( Constants.NAMED_QUERIES, checkHql );
|
||||
handleNamedQueryAnnotation( Constants.HIB_NAMED_QUERY, checkHql );
|
||||
handleNamedQueryRepeatableAnnotation( Constants.HIB_NAMED_QUERIES, checkHql );
|
||||
}
|
||||
|
||||
private void checkNamedQueriesForAnnotation(String annotationName, boolean checkHql) {
|
||||
private void handleNamedQueryAnnotation(String annotationName, boolean checkHql) {
|
||||
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
|
||||
if ( mirror != null ) {
|
||||
checkNamedQueriesForMirror( mirror, checkHql );
|
||||
handleNamedQuery( mirror, checkHql );
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNamedQueriesForRepeatableAnnotation(String annotationName, boolean checkHql) {
|
||||
private void handleNamedQueryRepeatableAnnotation(String annotationName, boolean checkHql) {
|
||||
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
|
||||
if ( mirror != null ) {
|
||||
mirror.getElementValues().forEach((key, value) -> {
|
||||
if ( key.getSimpleName().contentEquals("value") ) {
|
||||
List<? extends AnnotationMirror> values =
|
||||
(List<? extends AnnotationMirror>) value.getValue();
|
||||
final Object value = getAnnotationValue( mirror, "value" );
|
||||
if ( value instanceof List ) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<? extends AnnotationMirror> values =
|
||||
(List<? extends AnnotationMirror>) value;
|
||||
for ( AnnotationMirror annotationMirror : values ) {
|
||||
checkNamedQueriesForMirror( annotationMirror, checkHql );
|
||||
handleNamedQuery( annotationMirror, checkHql );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNamedQueriesForMirror(AnnotationMirror mirror, boolean checkHql) {
|
||||
mirror.getElementValues().forEach((key, value) -> {
|
||||
if ( key.getSimpleName().contentEquals("query") ) {
|
||||
final String hql = value.getValue().toString();
|
||||
private void handleNamedQuery(AnnotationMirror mirror, boolean checkHql) {
|
||||
final Object nameValue = getAnnotationValue( mirror, "name" );
|
||||
if ( nameValue instanceof String ) {
|
||||
final String name = nameValue.toString();
|
||||
final boolean reportErrors = getContext().checkNamedQuery( name );
|
||||
final AnnotationValue value = getAnnotationValueRef( mirror, "query" );
|
||||
if ( value != null ) {
|
||||
final Object query = value.getValue();
|
||||
if ( query instanceof String ) {
|
||||
final String hql = (String) query;
|
||||
final SqmStatement<?> statement =
|
||||
Validation.validate(
|
||||
hql,
|
||||
false, checkHql,
|
||||
false, true,
|
||||
emptySet(), emptySet(),
|
||||
new ErrorHandler( getElement(), mirror, hql, getContext() ),
|
||||
// If we are in the scope of @CheckHQL, semantic errors in the
|
||||
// query result in compilation errors. Otherwise, they only
|
||||
// result in warnings, so we don't break working code.
|
||||
new WarningErrorHandler( mirror, value, hql, reportErrors, checkHql ),
|
||||
ProcessorSessionFactory.create( getContext().getProcessingEnvironment() )
|
||||
);
|
||||
if ( statement instanceof SqmSelectStatement
|
||||
&& isQueryMethodName( name ) ) {
|
||||
putMember( name,
|
||||
new NamedQueryMethod(
|
||||
this,
|
||||
(SqmSelectStatement<?>) statement,
|
||||
name.substring(1),
|
||||
belongsToDao()
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isQueryMethodName(String name) {
|
||||
return name.length() >= 2
|
||||
&& name.charAt(0) == '#'
|
||||
&& Character.isJavaIdentifierStart( name.charAt(1) )
|
||||
&& name.substring(2).chars().allMatch(Character::isJavaIdentifierPart);
|
||||
}
|
||||
|
||||
private void addAuxiliaryMembersForRepeatableAnnotation(String annotationName, String prefix) {
|
||||
final AnnotationMirror mirror = getAnnotationMirror( getElement(), annotationName );
|
||||
if ( mirror != null ) {
|
||||
mirror.getElementValues().forEach((key, value) -> {
|
||||
if ( key.getSimpleName().contentEquals("value") ) {
|
||||
List<? extends AnnotationMirror> values =
|
||||
(List<? extends AnnotationMirror>) value.getValue();
|
||||
final Object value = getAnnotationValue( mirror, "value" );
|
||||
if ( value instanceof List ) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final List<? extends AnnotationMirror> values =
|
||||
(List<? extends AnnotationMirror>) value;
|
||||
for ( AnnotationMirror annotationMirror : values ) {
|
||||
addAuxiliaryMembersForMirror( annotationMirror, prefix );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -113,11 +146,52 @@ public abstract class AnnotationMeta implements Metamodel {
|
|||
mirror.getElementValues().forEach((key, value) -> {
|
||||
if ( key.getSimpleName().contentEquals("name") ) {
|
||||
final String name = value.getValue().toString();
|
||||
if ( !name.isEmpty() ) {
|
||||
putMember( prefix + name,
|
||||
new NameMetaAttribute( this, name, prefix ) );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
abstract boolean belongsToDao();
|
||||
|
||||
abstract void putMember(String name, MetaAttribute nameMetaAttribute);
|
||||
|
||||
private class WarningErrorHandler extends ErrorHandler {
|
||||
private final boolean reportErrors;
|
||||
private final boolean checkHql;
|
||||
|
||||
public WarningErrorHandler(AnnotationMirror mirror, AnnotationValue value, String hql, boolean reportErrors, boolean checkHql) {
|
||||
super( AnnotationMeta.this.getElement(), mirror, value, hql, AnnotationMeta.this.getContext() );
|
||||
this.reportErrors = reportErrors;
|
||||
this.checkHql = checkHql;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int start, int end, String message) {
|
||||
if (reportErrors) {
|
||||
if (checkHql) {
|
||||
super.error( start, end, message );
|
||||
}
|
||||
else {
|
||||
super.warn( start, end, message );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(int start, int end, String message) {
|
||||
if (reportErrors) {
|
||||
super.warn( start, end, message );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String message, RecognitionException e) {
|
||||
if (reportErrors) {
|
||||
super.syntaxError( recognizer, offendingSymbol, line, charPositionInLine, message, e );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ import static org.hibernate.jpamodelgen.util.TypeUtils.determineAccessTypeForHie
|
|||
import static org.hibernate.jpamodelgen.util.TypeUtils.determineAnnotationSpecifiedAccessType;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationMirror;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValue;
|
||||
import static org.hibernate.jpamodelgen.util.TypeUtils.getAnnotationValueRef;
|
||||
|
||||
/**
|
||||
* Class used to collect meta information about an annotated type (entity, embeddable or mapped superclass).
|
||||
|
@ -201,6 +202,11 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
members.put( name, nameMetaAttribute );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean belongsToDao() {
|
||||
return dao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringBuilder()
|
||||
|
@ -642,11 +648,13 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
@Nullable TypeElement containerType,
|
||||
AnnotationMirror mirror,
|
||||
boolean isNative) {
|
||||
final Object queryString = getAnnotationValue( mirror, "value" );
|
||||
if ( queryString instanceof String ) {
|
||||
final AnnotationValue value = getAnnotationValueRef( mirror, "value" );
|
||||
if ( value != null ) {
|
||||
final Object query = value.getValue();
|
||||
if ( query instanceof String ) {
|
||||
final String hql = (String) query;
|
||||
final List<String> paramNames = parameterNames( method );
|
||||
final List<String> paramTypes = parameterTypes( method );
|
||||
final String hql = (String) queryString;
|
||||
final QueryMethod attribute =
|
||||
new QueryMethod(
|
||||
this,
|
||||
|
@ -662,19 +670,20 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
);
|
||||
putMember( attribute.getPropertyName() + paramTypes, attribute );
|
||||
|
||||
checkParameters( method, paramNames, paramTypes, mirror, hql );
|
||||
checkParameters( method, paramNames, paramTypes, mirror, value, hql );
|
||||
if ( !isNative ) {
|
||||
// checkHqlSyntax( method, mirror, hql );
|
||||
// checkHqlSyntax( method, mirror, hql );
|
||||
Validation.validate(
|
||||
hql,
|
||||
false, true,
|
||||
emptySet(), emptySet(),
|
||||
new ErrorHandler( method, mirror, hql, context ),
|
||||
new ErrorHandler( method, mirror, value, hql, context ),
|
||||
ProcessorSessionFactory.create( context.getProcessingEnvironment() )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> parameterTypes(ExecutableElement method) {
|
||||
return method.getParameters().stream()
|
||||
|
@ -688,13 +697,20 @@ public class AnnotationMetaEntity extends AnnotationMeta {
|
|||
.collect(toList());
|
||||
}
|
||||
|
||||
private void checkParameters(ExecutableElement method, List<String> paramNames, List<String> paramTypes, AnnotationMirror mirror, String hql) {
|
||||
private void checkParameters(
|
||||
ExecutableElement method,
|
||||
List<String> paramNames, List<String> paramTypes,
|
||||
AnnotationMirror mirror,
|
||||
AnnotationValue value,
|
||||
String hql) {
|
||||
for (int i = 1; i <= paramNames.size(); i++) {
|
||||
final String param = paramNames.get(i-1);
|
||||
final String type = paramTypes.get(i-1);
|
||||
if ( parameterIsMissing( hql, i, param, type ) ) {
|
||||
context.message( method, mirror, "missing query parameter for '" + param
|
||||
+ "' (no parameter named :" + param + " or ?" + i + ")", Diagnostic.Kind.ERROR );
|
||||
context.message( method, mirror, value,
|
||||
"missing query parameter for '" + param
|
||||
+ "' (no parameter named :" + param + " or ?" + i + ")",
|
||||
Diagnostic.Kind.ERROR );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,4 +133,9 @@ public class AnnotationMetaPackage extends AnnotationMeta {
|
|||
void putMember(String name, MetaAttribute nameMetaAttribute) {
|
||||
members.put( name, nameMetaAttribute );
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean belongsToDao() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.hibernate.jpamodelgen.Context;
|
|||
import org.hibernate.jpamodelgen.validation.Validation;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.util.BitSet;
|
||||
|
@ -27,13 +28,15 @@ import static org.hibernate.query.hql.internal.StandardHqlTranslator.prettifyAnt
|
|||
class ErrorHandler implements Validation.Handler {
|
||||
private final Element element;
|
||||
private final AnnotationMirror mirror;
|
||||
private final AnnotationValue value;
|
||||
private final String queryString;
|
||||
private final Context context;
|
||||
private int errorCount;
|
||||
|
||||
public ErrorHandler(Element element, AnnotationMirror mirror, String queryString, Context context) {
|
||||
public ErrorHandler(Element element, AnnotationMirror mirror, AnnotationValue value, String queryString, Context context) {
|
||||
this.element = element;
|
||||
this.mirror = mirror;
|
||||
this.value = value;
|
||||
this.queryString = queryString;
|
||||
this.context = context;
|
||||
}
|
||||
|
@ -46,11 +49,12 @@ class ErrorHandler implements Validation.Handler {
|
|||
@Override
|
||||
public void error(int start, int end, String message) {
|
||||
errorCount++;
|
||||
context.message( element, mirror, message, Diagnostic.Kind.ERROR );
|
||||
context.message( element, mirror, value, message, Diagnostic.Kind.ERROR );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warn(int start, int end, String message) {
|
||||
context.message( element, mirror, value, message, Diagnostic.Kind.WARNING );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -58,7 +62,7 @@ class ErrorHandler implements Validation.Handler {
|
|||
errorCount++;
|
||||
String prettyMessage = "illegal HQL syntax - "
|
||||
+ prettifyAntlrError( offendingSymbol, line, charPositionInLine, message, e, queryString, false );
|
||||
context.message( element, mirror, prettyMessage, Diagnostic.Kind.ERROR );
|
||||
context.message( element, mirror, value, prettyMessage, Diagnostic.Kind.ERROR );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,7 +8,8 @@ package org.hibernate.jpamodelgen.annotation;
|
|||
|
||||
import org.hibernate.jpamodelgen.model.MetaAttribute;
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.StringUtil;
|
||||
|
||||
import static org.hibernate.jpamodelgen.util.StringUtil.nameToFieldName;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
|
@ -46,7 +47,7 @@ class NameMetaAttribute implements MetaAttribute {
|
|||
.append(annotationMetaEntity.importType(String.class.getName()))
|
||||
.append(" ")
|
||||
.append(prefix)
|
||||
.append(StringUtil.nameToFieldName(name))
|
||||
.append(fieldName())
|
||||
.append(" = ")
|
||||
.append("\"")
|
||||
.append(name)
|
||||
|
@ -55,6 +56,10 @@ class NameMetaAttribute implements MetaAttribute {
|
|||
.toString();
|
||||
}
|
||||
|
||||
private String fieldName() {
|
||||
return nameToFieldName(name.charAt(0) == '#' ? name.substring(1) : name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetaType() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* 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.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.hibernate.jpamodelgen.model.MetaAttribute;
|
||||
import org.hibernate.jpamodelgen.model.Metamodel;
|
||||
import org.hibernate.jpamodelgen.util.Constants;
|
||||
import org.hibernate.query.sqm.SqmExpressible;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectableNode;
|
||||
import org.hibernate.type.descriptor.java.JavaType;
|
||||
|
||||
import javax.lang.model.element.ModuleElement;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static org.hibernate.jpamodelgen.util.StringUtil.nameToFieldName;
|
||||
import static org.hibernate.jpamodelgen.validation.ProcessorSessionFactory.findEntityByUnqualifiedName;
|
||||
|
||||
/**
|
||||
* @author Gavin King
|
||||
*/
|
||||
class NamedQueryMethod implements MetaAttribute {
|
||||
private final AnnotationMeta annotationMeta;
|
||||
private final SqmSelectStatement<?> select;
|
||||
private final String name;
|
||||
private final boolean belongsToDao;
|
||||
|
||||
public NamedQueryMethod(AnnotationMeta annotationMeta, SqmSelectStatement<?> select, String name, boolean belongsToDao) {
|
||||
this.annotationMeta = annotationMeta;
|
||||
this.select = select;
|
||||
this.name = name;
|
||||
this.belongsToDao = belongsToDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTypedAttribute() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasStringAttribute() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
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 );
|
||||
parameters( sortedParameters, declaration );
|
||||
declaration
|
||||
.append(" {")
|
||||
.append("\n\treturn entityManager.createNamedQuery(")
|
||||
.append(fieldName())
|
||||
.append(")");
|
||||
for ( SqmParameter<?> param : sortedParameters ) {
|
||||
declaration
|
||||
.append("\n\t\t\t.setParameter(")
|
||||
.append(param.getName() == null ? param.getPosition() : '"' + param.getName() + '"')
|
||||
.append(", ")
|
||||
.append(param.getName() == null ? "parameter" + param.getPosition() : param.getName())
|
||||
.append(')');
|
||||
}
|
||||
declaration
|
||||
.append("\n\t\t\t.getResultList();\n}");
|
||||
return declaration.toString();
|
||||
}
|
||||
|
||||
private String fieldName() {
|
||||
return "QUERY_" + nameToFieldName(name);
|
||||
}
|
||||
|
||||
private String returnType() {
|
||||
final JavaType<?> javaType = select.getSelection().getJavaTypeDescriptor();
|
||||
if ( javaType != null ) {
|
||||
return javaType.getTypeName();
|
||||
}
|
||||
else {
|
||||
final List<SqmSelectableNode<?>> items =
|
||||
select.getQuerySpec().getSelectClause().getSelectionItems();
|
||||
if ( items.size() == 1 ) {
|
||||
final String typeName = items.get(0).getExpressible().getTypeName();
|
||||
final TypeElement entityType = entityType( typeName );
|
||||
return entityType == null ? typeName : entityType.getQualifiedName().toString();
|
||||
|
||||
}
|
||||
else {
|
||||
return "Object[]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void comment(StringBuilder declaration) {
|
||||
declaration
|
||||
.append("\n/**\n * Executes named query {@value #")
|
||||
.append(fieldName())
|
||||
.append("} defined by annotation of {@link ")
|
||||
.append(annotationMeta.getSimpleName())
|
||||
.append("}.\n **/\n");
|
||||
}
|
||||
|
||||
private void modifiers(StringBuilder declaration) {
|
||||
declaration
|
||||
.append(belongsToDao ? "public " : "public static ");
|
||||
}
|
||||
|
||||
private void returnType(String returnType, StringBuilder declaration) {
|
||||
declaration
|
||||
.append(annotationMeta.importType(Constants.LIST))
|
||||
.append('<')
|
||||
.append(annotationMeta.importType(returnType))
|
||||
.append("> ")
|
||||
.append(name);
|
||||
}
|
||||
|
||||
private void parameters(TreeSet<SqmParameter<?>> sortedParameters, StringBuilder declaration) {
|
||||
declaration
|
||||
.append('(');
|
||||
if ( !belongsToDao ) {
|
||||
declaration
|
||||
.append(annotationMeta.importType(Constants.ENTITY_MANAGER))
|
||||
.append(" entityManager");
|
||||
}
|
||||
int i = 0;
|
||||
for ( SqmParameter<?> param : sortedParameters) {
|
||||
if ( 0 < i++ || !belongsToDao ) {
|
||||
declaration
|
||||
.append(", ");
|
||||
}
|
||||
declaration
|
||||
.append(parameterType(param))
|
||||
.append(" ")
|
||||
.append(parameterName(param));
|
||||
}
|
||||
declaration
|
||||
.append(')');
|
||||
}
|
||||
|
||||
private static String parameterName(SqmParameter<?> param) {
|
||||
return param.getName() == null ? "parameter" + param.getPosition() : param.getName();
|
||||
}
|
||||
|
||||
private String parameterType(SqmParameter<?> param) {
|
||||
final SqmExpressible<?> expressible = param.getExpressible();
|
||||
final String paramType = expressible == null ? "unknown" : expressible.getTypeName(); //getTypeName() can return "unknown"
|
||||
return "unknown".equals(paramType) ? "Object" : annotationMeta.importType(paramType);
|
||||
}
|
||||
|
||||
private @Nullable TypeElement entityType(String entityName) {
|
||||
TypeElement symbol =
|
||||
findEntityByUnqualifiedName( entityName,
|
||||
annotationMeta.getContext().getElementUtils().getModuleElement("") );
|
||||
if ( symbol != null ) {
|
||||
return symbol;
|
||||
}
|
||||
for ( ModuleElement module : annotationMeta.getContext().getElementUtils().getAllModuleElements() ) {
|
||||
symbol = findEntityByUnqualifiedName( entityName, module );
|
||||
if ( symbol != null ) {
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAttributeNameDeclarationString() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetaType() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPropertyName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeDeclaration() {
|
||||
return Constants.LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Metamodel getHostingEntity() {
|
||||
return annotationMeta;
|
||||
}
|
||||
}
|
|
@ -221,6 +221,18 @@ public final class TypeUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable AnnotationValue getAnnotationValueRef(AnnotationMirror annotationMirror, String parameterValue) {
|
||||
assert annotationMirror != null;
|
||||
assert parameterValue != null;
|
||||
for ( Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry
|
||||
: annotationMirror.getElementValues().entrySet() ) {
|
||||
if ( entry.getKey().getSimpleName().contentEquals( parameterValue ) ) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void determineAccessTypeForHierarchy(TypeElement searchedElement, Context context) {
|
||||
final String fqcn = searchedElement.getQualifiedName().toString();
|
||||
context.logMessage( Diagnostic.Kind.OTHER, "Determining access type for " + fqcn );
|
||||
|
|
|
@ -142,13 +142,12 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
|
|||
}
|
||||
|
||||
static Type propertyType(Element member, String entityName, String path, AccessType defaultAccessType) {
|
||||
TypeMirror memberType = memberType(member);
|
||||
final TypeMirror memberType = memberType(member);
|
||||
if (isEmbeddedProperty(member)) {
|
||||
return component.make(asElement(memberType), entityName, path, defaultAccessType);
|
||||
}
|
||||
else if (isToOneAssociation(member)) {
|
||||
String targetEntity = getToOneTargetEntity(member);
|
||||
return new ManyToOneType(typeConfiguration, targetEntity);
|
||||
return new ManyToOneType(typeConfiguration, getToOneTargetEntity(member));
|
||||
}
|
||||
else if (isToManyAssociation(member)) {
|
||||
return collectionType(memberType, qualify(entityName, path));
|
||||
|
@ -157,7 +156,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
|
|||
return collectionType(memberType, qualify(entityName, path));
|
||||
}
|
||||
else if (isEnumProperty(member)) {
|
||||
return new BasicTypeImpl(new EnumJavaType(Enum.class), enumJdbcType(member));
|
||||
return enumType( member, memberType );
|
||||
}
|
||||
else {
|
||||
return typeConfiguration.getBasicTypeRegistry()
|
||||
|
@ -165,6 +164,27 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
private static BasicType<?> enumType(Element member, TypeMirror memberType) {
|
||||
final Class<Enum> enumClass = Enum.class; // because we can't load the real enum class!
|
||||
return enumType( member, qualifiedName( memberType ), enumClass );
|
||||
}
|
||||
|
||||
private static <T extends Enum<T>> BasicType<T> enumType(Element member, String typeName, Class<T> enumClass) {
|
||||
final EnumJavaType<T> javaType = new EnumJavaType<>( enumClass ) {
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return typeName;
|
||||
}
|
||||
};
|
||||
return new BasicTypeImpl<>( javaType, enumJdbcType(member) ) {
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return typeName;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static JdbcType enumJdbcType(Element member) {
|
||||
VariableElement mapping = (VariableElement)
|
||||
getAnnotationMember(getAnnotation(member,"Enumerated"), "value");
|
||||
|
@ -395,7 +415,7 @@ public abstract class ProcessorSessionFactory extends MockSessionFactory {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static TypeElement findEntityByUnqualifiedName(String entityName, ModuleElement module) {
|
||||
public static TypeElement findEntityByUnqualifiedName(String entityName, ModuleElement module) {
|
||||
for (Element element: module.getEnclosedElements()) {
|
||||
if (element.getKind() == ElementKind.PACKAGE) {
|
||||
PackageElement pack = (PackageElement) element;
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.antlr.v4.runtime.DefaultErrorStrategy;
|
|||
import org.antlr.v4.runtime.Token;
|
||||
import org.antlr.v4.runtime.atn.PredictionMode;
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.hibernate.PropertyNotFoundException;
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
|
@ -22,6 +23,7 @@ import org.hibernate.query.hql.internal.SemanticQueryBuilder;
|
|||
import org.hibernate.query.sqm.EntityTypeException;
|
||||
import org.hibernate.query.sqm.PathElementException;
|
||||
import org.hibernate.query.sqm.TerminalPathException;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.type.descriptor.java.spi.JdbcTypeRecommendationException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
@ -46,17 +48,17 @@ public class Validation {
|
|||
int getErrorCount();
|
||||
}
|
||||
|
||||
public static void validate(
|
||||
public static @Nullable SqmStatement<?> validate(
|
||||
String hql,
|
||||
boolean checkParams, boolean checkTyping,
|
||||
Set<Integer> setParameterLabels,
|
||||
Set<String> setParameterNames,
|
||||
Handler handler,
|
||||
SessionFactoryImplementor factory) {
|
||||
validate( hql, checkParams, checkTyping, setParameterLabels, setParameterNames, handler, factory, 0 );
|
||||
return validate( hql, checkParams, checkTyping, setParameterLabels, setParameterNames, handler, factory, 0 );
|
||||
}
|
||||
|
||||
public static void validate(
|
||||
public static @Nullable SqmStatement<?> validate(
|
||||
String hql,
|
||||
boolean checkParams, boolean checkTyping,
|
||||
Set<Integer> setParameterLabels,
|
||||
|
@ -69,25 +71,28 @@ public class Validation {
|
|||
try {
|
||||
final HqlParser.StatementContext statementContext = parseAndCheckSyntax( hql, handler );
|
||||
if ( checkTyping && handler.getErrorCount() == 0 ) {
|
||||
final SqmStatement<?> statement =
|
||||
checkTyping( hql, handler, factory, errorOffset, statementContext );
|
||||
}
|
||||
if ( checkParams ) {
|
||||
checkParameterBinding( hql, setParameterLabels, setParameterNames, handler, errorOffset );
|
||||
}
|
||||
return statement;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void checkTyping(
|
||||
private static @Nullable SqmStatement<?> checkTyping(
|
||||
String hql,
|
||||
Handler handler,
|
||||
SessionFactoryImplementor factory,
|
||||
int errorOffset,
|
||||
HqlParser.StatementContext statementContext) {
|
||||
try {
|
||||
new SemanticQueryBuilder<>( Object[].class, () -> false, factory )
|
||||
return new SemanticQueryBuilder<>( Object[].class, () -> false, factory )
|
||||
.visitStatement( statementContext );
|
||||
}
|
||||
catch ( JdbcTypeRecommendationException ignored ) {
|
||||
|
@ -95,11 +100,12 @@ public class Validation {
|
|||
}
|
||||
catch ( QueryException | PathElementException | TerminalPathException | EntityTypeException
|
||||
| PropertyNotFoundException se ) { //TODO is this one really thrown by core? It should not be!
|
||||
String message = se.getMessage();
|
||||
final String message = se.getMessage();
|
||||
if ( message != null ) {
|
||||
handler.error( -errorOffset +1, -errorOffset + hql.length(), message );
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HqlParser.StatementContext parseAndCheckSyntax(String hql, Handler handler) {
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
*/
|
||||
package org.hibernate.jpamodelgen.test.namedquery;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.hibernate.jpamodelgen.test.util.CompilationTest;
|
||||
import org.hibernate.jpamodelgen.test.util.TestUtil;
|
||||
import org.hibernate.jpamodelgen.test.util.WithClasses;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertMetamodelClassGeneratedFor;
|
||||
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfMethodInMetamodelFor;
|
||||
import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfNameFieldInMetamodelFor;
|
||||
|
||||
/**
|
||||
|
@ -20,8 +22,9 @@ import static org.hibernate.jpamodelgen.test.util.TestUtil.assertPresenceOfNameF
|
|||
public class AuxiliaryTest extends CompilationTest {
|
||||
@Test
|
||||
@WithClasses({ Book.class, Main.class })
|
||||
public void testGeneratedAnnotationNotGenerated() {
|
||||
public void test() {
|
||||
System.out.println( TestUtil.getMetaModelSourceAsString( Main.class ) );
|
||||
System.out.println( TestUtil.getMetaModelSourceAsString( Book.class ) );
|
||||
assertMetamodelClassGeneratedFor( Book.class );
|
||||
assertMetamodelClassGeneratedFor( Main.class );
|
||||
assertPresenceOfNameFieldInMetamodelFor(
|
||||
|
@ -74,10 +77,59 @@ public class AuxiliaryTest extends CompilationTest {
|
|||
"QUERY__SYSDATE_",
|
||||
"Missing fetch profile attribute."
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Main.class,
|
||||
"bookByIsbn",
|
||||
EntityManager.class,
|
||||
String.class
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Main.class,
|
||||
"bookByTitle",
|
||||
EntityManager.class,
|
||||
String.class
|
||||
);
|
||||
|
||||
assertPresenceOfNameFieldInMetamodelFor(
|
||||
Book.class,
|
||||
"GRAPH_ENTITY_GRAPH",
|
||||
"Missing fetch profile attribute."
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Book.class,
|
||||
"findByTitle",
|
||||
EntityManager.class,
|
||||
String.class
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Book.class,
|
||||
"findByTitleAndType",
|
||||
EntityManager.class,
|
||||
String.class,
|
||||
Type.class
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Book.class,
|
||||
"getTitles",
|
||||
EntityManager.class
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Book.class,
|
||||
"getUpperLowerTitles",
|
||||
EntityManager.class
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Book.class,
|
||||
"typeOfBook",
|
||||
EntityManager.class,
|
||||
String.class
|
||||
);
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
Book.class,
|
||||
"crazy",
|
||||
EntityManager.class,
|
||||
Object.class,
|
||||
Object.class
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,11 +3,25 @@ package org.hibernate.jpamodelgen.test.namedquery;
|
|||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.NamedEntityGraph;
|
||||
import jakarta.persistence.NamedQuery;
|
||||
|
||||
@Entity
|
||||
@NamedEntityGraph(name = "entityGraph")
|
||||
@NamedQuery(name = "#findByTitle",
|
||||
query = "from Book where title like :titlePattern")
|
||||
@NamedQuery(name = "#findByTitleAndType",
|
||||
query = "select book from Book book where book.title like :titlePattern and book.type = :type")
|
||||
@NamedQuery(name = "#getTitles",
|
||||
query = "select title from Book")
|
||||
@NamedQuery(name = "#getUpperLowerTitles",
|
||||
query = "select upper(title), lower(title), length(title) from Book")
|
||||
@NamedQuery(name = "#typeOfBook",
|
||||
query = "select type from Book where isbn = :isbn")
|
||||
@NamedQuery(name = "#crazy",
|
||||
query = "select 1 where :x = :y")
|
||||
public class Book {
|
||||
@Id String isbn;
|
||||
String title;
|
||||
String text;
|
||||
Type type = Type.Book;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ import jakarta.persistence.SqlResultSetMappings;
|
|||
import org.hibernate.annotations.FetchProfile;
|
||||
import org.hibernate.annotations.FetchProfiles;
|
||||
|
||||
@NamedQueries(@NamedQuery(name = "bookByIsbn", query = "from Book where isbn = :isbn"))
|
||||
@NamedQuery(name = "bookByTitle", query = "from Book where title = :title")
|
||||
@NamedQueries(@NamedQuery(name = "#bookByIsbn", query = "from Book where isbn = :isbn"))
|
||||
@NamedQuery(name = "#bookByTitle", query = "from Book where title = :title")
|
||||
@FetchProfile(name = "dummy-fetch")
|
||||
@FetchProfiles({@FetchProfile(name = "fetch.one"), @FetchProfile(name = "fetch#two")})
|
||||
@NamedNativeQuery(name = "bookNativeQuery", query = "select * from Book")
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package org.hibernate.jpamodelgen.test.namedquery;
|
||||
|
||||
enum Type {Book, Magazine, Journal}
|
|
@ -13,6 +13,7 @@ import java.io.FileReader;
|
|||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URL;
|
||||
|
@ -78,10 +79,23 @@ public class TestUtil {
|
|||
);
|
||||
}
|
||||
|
||||
public static void assertPresenceOfMethodInMetamodelFor(Class<?> clazz, String methodName, Class<?>... params) {
|
||||
assertPresenceOfMethodInMetamodelFor(
|
||||
clazz,
|
||||
methodName,
|
||||
"'" + methodName + "' should appear in metamodel class",
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
public static void assertPresenceOfFieldInMetamodelFor(Class<?> clazz, String fieldName, String errorString) {
|
||||
assertTrue( buildErrorString( errorString, clazz ), hasFieldInMetamodelFor( clazz, fieldName ) );
|
||||
}
|
||||
|
||||
public static void assertPresenceOfMethodInMetamodelFor(Class<?> clazz, String fieldName, String errorString, Class<?>... params) {
|
||||
assertTrue( buildErrorString( errorString, clazz ), hasMethodInMetamodelFor( clazz, fieldName, params ) );
|
||||
}
|
||||
|
||||
public static void assertPresenceOfNameFieldInMetamodelFor(Class<?> clazz, String fieldName, String errorString) {
|
||||
assertTrue( buildErrorString( errorString, clazz ), hasFieldInMetamodelFor( clazz, fieldName ) );
|
||||
assertEquals(buildErrorString(errorString, clazz), getFieldFromMetamodelFor(clazz, fieldName).getType(), String.class);
|
||||
|
@ -254,14 +268,22 @@ public class TestUtil {
|
|||
|
||||
public static Field getFieldFromMetamodelFor(Class<?> entityClass, String fieldName) {
|
||||
Class<?> metaModelClass = getMetamodelClassFor( entityClass );
|
||||
Field field;
|
||||
try {
|
||||
field = metaModelClass.getDeclaredField( fieldName );
|
||||
return metaModelClass.getDeclaredField( fieldName );
|
||||
}
|
||||
catch ( NoSuchFieldException e ) {
|
||||
field = null;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Method getMethodFromMetamodelFor(Class<?> entityClass, String methodName, Class<?>... params) {
|
||||
Class<?> metaModelClass = getMetamodelClassFor( entityClass );
|
||||
try {
|
||||
return metaModelClass.getDeclaredMethod( methodName, params );
|
||||
}
|
||||
catch ( NoSuchMethodException e ) {
|
||||
return null;
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
public static String fcnToPath(String fcn) {
|
||||
|
@ -272,6 +294,10 @@ public class TestUtil {
|
|||
return getFieldFromMetamodelFor( clazz, fieldName ) != null;
|
||||
}
|
||||
|
||||
private static boolean hasMethodInMetamodelFor(Class<?> clazz, String fieldName, Class<?>... params) {
|
||||
return getMethodFromMetamodelFor( clazz, fieldName, params ) != null;
|
||||
}
|
||||
|
||||
private static String buildErrorString(String baseError, Class<?> clazz) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append( baseError );
|
||||
|
|
Loading…
Reference in New Issue