HHH-17851 fix three bugs in Jakarta Data impl

- overloaded lifecycle methods
- handling of delimited compound names
- some missing imports
This commit is contained in:
Gavin King 2024-03-14 22:37:53 +01:00
parent f898de7e98
commit 3e8b1b37be
14 changed files with 255 additions and 31 deletions

View File

@ -7,7 +7,6 @@
package org.hibernate.annotations; package org.hibernate.annotations;
import org.hibernate.Remove; import org.hibernate.Remove;
import org.hibernate.binder.internal.CommentBinder;
import org.hibernate.binder.internal.CommentsBinder; import org.hibernate.binder.internal.CommentsBinder;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@ -42,12 +42,12 @@ public class QualifiedNameParser {
StringBuilder buff = new StringBuilder(); StringBuilder buff = new StringBuilder();
if ( catalogName != null ) { if ( catalogName != null ) {
buff.append( catalogName.toString() ).append( '.' ); buff.append( catalogName ).append( '.' );
} }
if ( schemaName != null ) { if ( schemaName != null ) {
buff.append( schemaName.toString() ).append( '.' ); buff.append( schemaName ).append( '.' );
} }
buff.append( objectName.toString() ); buff.append( objectName );
qualifiedText = buff.toString(); qualifiedText = buff.toString();
} }
@ -89,8 +89,8 @@ public class QualifiedNameParser {
NameParts that = (NameParts) o; NameParts that = (NameParts) o;
return Objects.equals( this.getCatalogName(), that.getCatalogName() ) return Objects.equals( this.getCatalogName(), that.getCatalogName() )
&& Objects.equals( this.getSchemaName(), that.getSchemaName() ) && Objects.equals( this.getSchemaName(), that.getSchemaName() )
&& Objects.equals( this.getObjectName(), that.getObjectName() ); && Objects.equals( this.getObjectName(), that.getObjectName() );
} }
@Override @Override

View File

@ -0,0 +1,6 @@
package org.hibernate.processor.test.data.eg;
import jakarta.persistence.Embeddable;
@Embeddable
public record Address(String street, String city, String postcode) {}

View File

@ -0,0 +1,23 @@
package org.hibernate.processor.test.data.eg;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import java.util.Set;
@Entity
public class Author {
@Id
String ssn;
@Basic(optional = false)
String name;
Address address;
@ManyToMany
Set<Book> books;
}

View File

@ -0,0 +1,59 @@
package org.hibernate.processor.test.data.eg;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import org.hibernate.annotations.NaturalId;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.util.Set;
@Entity
public class Book {
@Id
String isbn;
@NaturalId
String title;
@NaturalId
LocalDate publicationDate;
String text;
@Enumerated(EnumType.STRING)
@Basic(optional = false)
Type type = Type.Book;
@ManyToOne(optional = false)
Publisher publisher;
@ManyToMany(mappedBy = "books")
Set<Author> authors;
@Basic(optional = false)
int pages ;
BigDecimal price;
BigInteger quantitySold;
public Book(String isbn, String title, String text) {
this.isbn = isbn;
this.title = title;
this.text = text;
}
protected Book() {}
@Override
public String toString() {
return isbn + " : " + title + " [" + type + "]";
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.processor.test.data.eg;
import org.hibernate.processor.test.util.CompilationTest;
import org.hibernate.processor.test.util.WithClasses;
import org.junit.Test;
import static org.hibernate.processor.test.util.TestUtil.assertMetamodelClassGeneratedFor;
import static org.hibernate.processor.test.util.TestUtil.getMetaModelSourceAsString;
/**
* @author Gavin King
*/
public class EgTest extends CompilationTest {
@Test
@WithClasses({ Publisher.class, Author.class, Book.class, Library.class })
public void test() {
System.out.println( getMetaModelSourceAsString( Author.class ) );
System.out.println( getMetaModelSourceAsString( Book.class ) );
System.out.println( getMetaModelSourceAsString( Author.class, true ) );
System.out.println( getMetaModelSourceAsString( Book.class, true ) );
System.out.println( getMetaModelSourceAsString( Library.class ) );
assertMetamodelClassGeneratedFor( Author.class, true );
assertMetamodelClassGeneratedFor( Book.class, true );
assertMetamodelClassGeneratedFor( Publisher.class, true );
assertMetamodelClassGeneratedFor( Author.class );
assertMetamodelClassGeneratedFor( Book.class );
assertMetamodelClassGeneratedFor( Publisher.class );
assertMetamodelClassGeneratedFor( Library.class );
}
}

View File

@ -0,0 +1,70 @@
package org.hibernate.processor.test.data.eg;
import jakarta.data.page.CursoredPage;
import jakarta.data.page.PageRequest;
import jakarta.data.repository.Delete;
import jakarta.data.repository.Find;
import jakarta.data.repository.Insert;
import jakarta.data.repository.OrderBy;
import jakarta.data.repository.Query;
import jakarta.data.repository.Repository;
import jakarta.data.repository.Save;
import jakarta.data.repository.Update;
import java.time.LocalDate;
import java.util.List;
@Repository
public interface Library {
@Find
Book book(String isbn);
@Find
Book books(String title, LocalDate publicationDate);
@Find
@OrderBy("title")
List<Book> booksByPublisher(String publisher_name);
@Query("where title like :titlePattern")
List<Book> booksByTitle(String titlePattern);
// not required by Jakarta Data
record BookWithAuthor(Book book, Author author) {}
@Query("select b, a from Book b join b.authors a")
List<BookWithAuthor> booksWithAuthors();
@Insert
void create(Book book);
@Update
void update(Book book);
@Delete
void delete(Book book);
@Update
void update(Book[] books);
@Save
void upsert(Book book);
@Find
Author author(String ssn);
@Insert
void create(Author author);
@Update
void update(Author author);
@Find
@OrderBy("isbn")
CursoredPage<Book> allBooks(PageRequest<Book> pageRequest);
@Find
@OrderBy("name")
@OrderBy("address.city") //not working currently
List<Author> authors();
}

View File

@ -0,0 +1,19 @@
package org.hibernate.processor.test.data.eg;
import jakarta.persistence.Basic;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import java.util.Set;
@Entity
public class Publisher {
@Id long id;
@Basic(optional = false)
String name;
@OneToMany(mappedBy = "publisher")
Set<Book> books;
}

View File

@ -0,0 +1,3 @@
package org.hibernate.processor.test.data.eg;
public enum Type { Book, Magazine, Journal }

View File

@ -111,8 +111,7 @@ public abstract class AbstractFinderMethod extends AbstractQueryMethod {
} }
} }
count++; count++;
final String path = paramNames.get(i) final String path = paramNames.get(i);
.replace('$', '.');
declaration declaration
.append("{@link ") .append("{@link ")
.append(annotationMetaEntity.importType(entity)) .append(annotationMetaEntity.importType(entity))
@ -141,7 +140,7 @@ public abstract class AbstractFinderMethod extends AbstractQueryMethod {
} }
String qualifier(String name) { String qualifier(String name) {
final int index = name.indexOf('$'); final int index = name.indexOf('.');
return index > 0 ? name.substring(0, index) : name; return index > 0 ? name.substring(0, index) : name;
} }

View File

@ -124,7 +124,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
declaration declaration
.append(annotationMetaEntity.importType(importReturnTypeArgument(paramType))) .append(annotationMetaEntity.importType(importReturnTypeArgument(paramType)))
.append(" ") .append(" ")
.append(paramNames.get(i)); .append(paramNames.get(i).replace('.', '$'));
} }
declaration declaration
.append(")"); .append(")");
@ -363,10 +363,10 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
} }
void makeKeyedPage(StringBuilder declaration, List<String> paramTypes) { void makeKeyedPage(StringBuilder declaration, List<String> paramTypes) {
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*"); annotationMetaEntity.staticImport(HIB_SORT_DIRECTION, "*");
annotationMetaEntity.staticImport(HIB_ORDER, "by");
annotationMetaEntity.staticImport(HIB_PAGE, "page");
annotationMetaEntity.staticImport("org.hibernate.query.KeyedPage.KeyInterpretation", "*"); annotationMetaEntity.staticImport("org.hibernate.query.KeyedPage.KeyInterpretation", "*");
annotationMetaEntity.staticImport("org.hibernate.query.Order", "by");
annotationMetaEntity.staticImport("org.hibernate.query.Page", "page");
annotationMetaEntity.staticImport(Collectors.class.getName(), "toList"); annotationMetaEntity.staticImport(Collectors.class.getName(), "toList");
if ( returnTypeName == null ) { if ( returnTypeName == null ) {
throw new AssertionFailure("entity class cannot be null"); throw new AssertionFailure("entity class cannot be null");
@ -489,6 +489,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
.append(">>();\n"); .append(">>();\n");
// static orders declared using @OrderBy must come first // static orders declared using @OrderBy must come first
for ( OrderBy orderBy : orderBys ) { for ( OrderBy orderBy : orderBys ) {
annotationMetaEntity.staticImport(HIB_SORT_DIRECTION, "*");
declaration declaration
.append("\t_orders.add(") .append("\t_orders.add(")
.append(annotationMetaEntity.staticImport(HIB_ORDER, "by")) .append(annotationMetaEntity.staticImport(HIB_ORDER, "by"))
@ -528,7 +529,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
} }
else if ( type.startsWith(JD_ORDER) else if ( type.startsWith(JD_ORDER)
|| type.startsWith(JD_PAGE_REQUEST) ) { || type.startsWith(JD_PAGE_REQUEST) ) {
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*"); annotationMetaEntity.staticImport(HIB_SORT_DIRECTION, "*");
declaration declaration
.append("\tfor (var _sort : ") .append("\tfor (var _sort : ")
.append(name) .append(name)
@ -546,7 +547,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
} }
else if ( type.startsWith(JD_SORT) && type.endsWith("...") ) { else if ( type.startsWith(JD_SORT) && type.endsWith("...") ) {
// almost identical // almost identical
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*"); annotationMetaEntity.staticImport(HIB_SORT_DIRECTION, "*");
declaration declaration
.append("\tfor (var _sort : ") .append("\tfor (var _sort : ")
.append(name) .append(name)
@ -563,7 +564,7 @@ public abstract class AbstractQueryMethod implements MetaAttribute {
.append("\t}\n"); .append("\t}\n");
} }
else if ( type.startsWith(JD_SORT) ) { else if ( type.startsWith(JD_SORT) ) {
annotationMetaEntity.staticImport("org.hibernate.query.SortDirection", "*"); annotationMetaEntity.staticImport(HIB_SORT_DIRECTION, "*");
declaration declaration
.append("\t_orders.add(") .append("\t_orders.add(")
.append(annotationMetaEntity.staticImport(HIB_ORDER, "by")) .append(annotationMetaEntity.staticImport(HIB_ORDER, "by"))

View File

@ -851,13 +851,14 @@ public class AnnotationMetaEntity extends AnnotationMeta {
Diagnostic.Kind.ERROR ); Diagnostic.Kind.ERROR );
} }
else { else {
final String entity = parameterType.toString();
final String methodName = method.getSimpleName().toString();
putMember( putMember(
method.getSimpleName().toString() methodName + '.' + entity,
+ '.' + operation,
new LifecycleMethod( new LifecycleMethod(
this, this,
parameterType.toString(), entity,
method.getSimpleName().toString(), methodName,
parameter.getSimpleName().toString(), parameter.getSimpleName().toString(),
getSessionVariableName(), getSessionVariableName(),
operation, operation,
@ -1099,13 +1100,16 @@ public class AnnotationMetaEntity extends AnnotationMeta {
final Boolean ignoreCaseOrNull = (Boolean) getAnnotationValue(orderBy, "ignoreCase"); final Boolean ignoreCaseOrNull = (Boolean) getAnnotationValue(orderBy, "ignoreCase");
final boolean descending = descendingOrNull != null && descendingOrNull; final boolean descending = descendingOrNull != null && descendingOrNull;
final boolean ignoreCase = ignoreCaseOrNull != null && ignoreCaseOrNull; final boolean ignoreCase = ignoreCaseOrNull != null && ignoreCaseOrNull;
if ( memberMatchingPath( entityType, fieldName ) == null ) { final String path = fieldName
.replace('$', '.')
.replace('_', '.'); //Jakarta Data allows _ here
if ( memberMatchingPath( entityType, path ) == null ) {
context.message( method, orderBy, context.message( method, orderBy,
"no matching field named '" + fieldName "no matching field named '" + fieldName
+ "' in entity class '" + entityType.getQualifiedName() + "'", + "' in entity class '" + entityType.getQualifiedName() + "'",
Diagnostic.Kind.ERROR ); Diagnostic.Kind.ERROR );
} }
return new OrderBy( fieldName, descending, ignoreCase ); return new OrderBy( path, descending, ignoreCase );
} }
private static @Nullable TypeMirror getTypeArgument(TypeMirror parameterType) { private static @Nullable TypeMirror getTypeArgument(TypeMirror parameterType) {
@ -1406,8 +1410,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
context.message( param, context.message( param,
"no matching field named '" "no matching field named '" + parameterName( param )
+ parameterName( param ).replace('$', '.')
+ "' in entity class '" + entityType + "'", + "' in entity class '" + entityType + "'",
Diagnostic.Kind.ERROR ); Diagnostic.Kind.ERROR );
return null; return null;
@ -1435,7 +1438,7 @@ public class AnnotationMetaEntity extends AnnotationMeta {
} }
private @Nullable Element memberMatchingPath(TypeElement entityType, String path) { private @Nullable Element memberMatchingPath(TypeElement entityType, String path) {
final StringTokenizer tokens = new StringTokenizer( path, "$" ); final StringTokenizer tokens = new StringTokenizer( path, "." );
return memberMatchingPath( entityType, tokens ); return memberMatchingPath( entityType, tokens );
} }
@ -1947,7 +1950,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
if ( name.contains("<error>") ) { if ( name.contains("<error>") ) {
throw new ProcessLaterException(); throw new ProcessLaterException();
} }
return name.replace('.', '$'); // since the rest of the code assumes $ as the path separator return name
.replace('$', '.')
.replace('_', '.');
} }
else if ( param != null ) { else if ( param != null ) {
final String name = (String) castNonNull(getAnnotationValue(param, "value")); final String name = (String) castNonNull(getAnnotationValue(param, "value"));
@ -1957,7 +1962,9 @@ public class AnnotationMetaEntity extends AnnotationMeta {
return name; return name;
} }
else { else {
return parameter.getSimpleName().toString(); return parameter.getSimpleName().toString()
.replace('$', '.')
.replace('_', '.');
} }
} }

View File

@ -143,7 +143,7 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
private static void nullCheck(StringBuilder declaration, String paramName) { private static void nullCheck(StringBuilder declaration, String paramName) {
declaration declaration
.append("\tif (") .append("\tif (")
.append(paramName) .append(paramName.replace('.', '$'))
.append(" == null) throw new IllegalArgumentException(\"Null ") .append(" == null) throw new IllegalArgumentException(\"Null ")
.append(paramName) .append(paramName)
.append("\");\n"); .append("\");\n");
@ -174,9 +174,10 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
private void parameter(StringBuilder declaration, int i, String paramName, String paramType) { private void parameter(StringBuilder declaration, int i, String paramName, String paramType) {
declaration declaration
.append("\n\t\t\t"); .append("\n\t\t\t");
final String parameterName = paramName.replace('.', '$');
if ( isNullable(i) && !isPrimitive(paramType) ) { if ( isNullable(i) && !isPrimitive(paramType) ) {
declaration declaration
.append(paramName) .append(parameterName)
.append("==null") .append("==null")
.append("\n\t\t\t\t? ") .append("\n\t\t\t\t? ")
.append("_entity"); .append("_entity");
@ -191,12 +192,12 @@ public class CriteriaFinderMethod extends AbstractFinderMethod {
declaration declaration
.append(", ") .append(", ")
//TODO: only safe if we are binding literals as parameters!!! //TODO: only safe if we are binding literals as parameters!!!
.append(paramName) .append(parameterName)
.append(')'); .append(')');
} }
private void path(StringBuilder declaration, String paramName) { private void path(StringBuilder declaration, String paramName) {
final StringTokenizer tokens = new StringTokenizer(paramName, "$"); final StringTokenizer tokens = new StringTokenizer(paramName, ".");
String typeName = entity; String typeName = entity;
while ( typeName!= null && tokens.hasMoreTokens() ) { while ( typeName!= null && tokens.hasMoreTokens() ) {
final String memberName = tokens.nextToken(); final String memberName = tokens.nextToken();

View File

@ -79,6 +79,7 @@ public final class Constants {
public static final String HIB_PAGE = "org.hibernate.query.Page"; public static final String HIB_PAGE = "org.hibernate.query.Page";
public static final String HIB_KEYED_PAGE = "org.hibernate.query.KeyedPage"; public static final String HIB_KEYED_PAGE = "org.hibernate.query.KeyedPage";
public static final String HIB_KEYED_RESULT_LIST = "org.hibernate.query.KeyedResultList"; public static final String HIB_KEYED_RESULT_LIST = "org.hibernate.query.KeyedResultList";
public static final String HIB_SORT_DIRECTION = "org.hibernate.query.SortDirection";
public static final String CHECK_HQL = "org.hibernate.annotations.processing.CheckHQL"; public static final String CHECK_HQL = "org.hibernate.annotations.processing.CheckHQL";