From 72a36e7b7bf5caf97d7dbdb0df069a9f3f32bb8e Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 25 Feb 2021 16:49:01 +0100 Subject: [PATCH] Qute - fix handling of Panache entity getters for boolean properties - resolves #15334 (cherry picked from commit 681fb4b0b5ffce38c3f340474f27638607de23e4) --- .../qute/deployment/QuteProcessor.java | 24 +++++++- .../generator/ValueResolverGenerator.java | 57 +++++++++++-------- integration-tests/qute/pom.xml | 51 ++++++++++++++++- .../main/java/io/quarkus/it/qute/Beer.java | 16 ++++++ .../java/io/quarkus/it/qute/BeerResource.java | 28 +++++++++ .../main/java/io/quarkus/it/qute/Brewery.java | 19 +++++++ .../src/main/resources/application.properties | 6 ++ .../templates/BeerResource/beer.html | 10 ++++ .../java/io/quarkus/it/qute/QuteTestCase.java | 3 +- .../io/quarkus/it/qute/TestResources.java | 8 +++ 10 files changed, 193 insertions(+), 29 deletions(-) create mode 100644 integration-tests/qute/src/main/java/io/quarkus/it/qute/Beer.java create mode 100644 integration-tests/qute/src/main/java/io/quarkus/it/qute/BeerResource.java create mode 100644 integration-tests/qute/src/main/java/io/quarkus/it/qute/Brewery.java create mode 100644 integration-tests/qute/src/main/resources/application.properties create mode 100644 integration-tests/qute/src/main/resources/templates/BeerResource/beer.html create mode 100644 integration-tests/qute/src/test/java/io/quarkus/it/qute/TestResources.java diff --git a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java index c26660d67b499..6d42477e95226 100644 --- a/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java +++ b/extensions/qute/deployment/src/main/java/io/quarkus/qute/deployment/QuteProcessor.java @@ -47,6 +47,7 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.PrimitiveType; +import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; import org.jboss.jandex.TypeVariable; import org.jboss.logging.Logger; @@ -136,6 +137,20 @@ public class QuteProcessor { private static final Logger LOGGER = Logger.getLogger(QuteProcessor.class); + private static final Function GETTER_FUN = new Function() { + @Override + public String apply(FieldInfo field) { + String prefix; + if (field.type().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE + && field.type().asPrimitiveType().primitive() == Primitive.BOOLEAN) { + prefix = ValueResolverGenerator.IS_PREFIX; + } else { + prefix = ValueResolverGenerator.GET_PREFIX; + } + return prefix + ValueResolverGenerator.capitalize(field.name()); + } + }; + @BuildStep FeatureBuildItem feature() { return new FeatureBuildItem(Feature.QUTE); @@ -873,10 +888,13 @@ public boolean test(String name) { for (PanacheEntityClassesBuildItem panaecheEntityClasses : panacheEntityClasses) { entityClasses.addAll(panaecheEntityClasses.getEntityClasses()); } - builder.setForceGettersPredicate(new Predicate() { + builder.setForceGettersFunction(new Function>() { @Override - public boolean test(ClassInfo clazz) { - return entityClasses.contains(clazz.name().toString()); + public Function apply(ClassInfo clazz) { + if (entityClasses.contains(clazz.name().toString())) { + return GETTER_FUN; + } + return null; } }); } diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java index 53d281ea516b8..99286d98c69f1 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java @@ -34,6 +34,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.BiConsumer; +import java.util.function.Function; import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -69,9 +70,9 @@ public static Builder builder() { private static final Logger LOGGER = Logger.getLogger(ValueResolverGenerator.class); - private static final String GET_PREFIX = "get"; - private static final String IS_PREFIX = "is"; - private static final String HAS_PREFIX = "has"; + public static final String GET_PREFIX = "get"; + public static final String IS_PREFIX = "is"; + public static final String HAS_PREFIX = "has"; public static final String TARGET = "target"; public static final String IGNORE_SUPERCLASSES = "ignoreSuperclasses"; @@ -85,16 +86,18 @@ public static Builder builder() { private final ClassOutput classOutput; private final Map nameToClass; private final Map nameToTemplateData; - private final Predicate forceGettersPredicate; + + private Function> forceGettersFunction; ValueResolverGenerator(IndexView index, ClassOutput classOutput, Map nameToClass, - Map nameToTemplateData, Predicate forceGettersPredicate) { + Map nameToTemplateData, + Function> forceGettersFunction) { this.generatedTypes = new HashSet<>(); this.classOutput = classOutput; this.index = index; this.nameToClass = new HashMap<>(nameToClass); this.nameToTemplateData = new HashMap<>(nameToTemplateData); - this.forceGettersPredicate = forceGettersPredicate; + this.forceGettersFunction = forceGettersFunction; } public Set getGeneratedTypes() { @@ -218,7 +221,7 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas ResultHandle name = resolve.invokeInterfaceMethod(Descriptors.GET_NAME, evalContext); ResultHandle params = resolve.invokeInterfaceMethod(Descriptors.GET_PARAMS, evalContext); ResultHandle paramsCount = resolve.invokeInterfaceMethod(Descriptors.COLLECTION_SIZE, params); - boolean forceGetters = forceGettersPredicate != null ? forceGettersPredicate.test(clazz) : false; + Function fieldToGetterFun = forceGettersFunction != null ? forceGettersFunction.apply(clazz) : null; // First collect and sort methods (getters must come before is/has properties, etc.) List methods = clazz.methods().stream().filter(filter::test).map(MethodKey::new).sorted() @@ -242,16 +245,8 @@ private void implementResolve(ClassCreator valueResolver, String clazzName, Clas if (!fields.isEmpty()) { BytecodeCreator zeroParamsBranch = resolve.ifNonZero(paramsCount).falseBranch(); for (FieldInfo field : fields) { - String getterName; - if ((field.type().kind() == org.jboss.jandex.Type.Kind.PRIMITIVE - && field.type().asPrimitiveType().equals(PrimitiveType.BOOLEAN)) - || (field.type().kind() == org.jboss.jandex.Type.Kind.CLASS - && field.type().name().equals(DotNames.BOOLEAN))) { - getterName = IS_PREFIX + capitalize(field.name()); - } else { - getterName = GET_PREFIX + capitalize(field.name()); - } - if (forceGetters && methods.stream().noneMatch(m -> m.name.equals(getterName))) { + String getterName = fieldToGetterFun != null ? fieldToGetterFun.apply(field) : null; + if (getterName != null && noneMethodMatches(methods, getterName)) { LOGGER.debugf("Forced getter added: %s", field); BytecodeCreator getterMatch = zeroParamsBranch.createScope(); // Match the getter name @@ -719,7 +714,7 @@ public static class Builder { private ClassOutput classOutput; private final Map nameToClass = new HashMap<>(); private final Map nameToTemplateData = new HashMap<>(); - private Predicate forceGettersPredicate; + private Function> forceGettersFunction; public Builder setIndex(IndexView index) { this.index = index; @@ -732,13 +727,18 @@ public Builder setClassOutput(ClassOutput classOutput) { } /** - * If a class for which a value resolver is generated matches the predicate then all fields are accessed via getters. + * The function returns: + *
    + *
  • a function that returns the getter name for a specific field or {@code null} if getter should not be forced for + * the given field
  • + *
  • {@code null} if getters are not forced for the given class
  • + *
* - * @param forceGettersPredicate + * @param forceGettersFunction * @return self */ - public Builder setForceGettersPredicate(Predicate forceGettersPredicate) { - this.forceGettersPredicate = forceGettersPredicate; + public Builder setForceGettersFunction(Function> forceGettersFunction) { + this.forceGettersFunction = forceGettersFunction; return this; } @@ -755,7 +755,7 @@ public Builder addClass(ClassInfo clazz, AnnotationInstance templateData) { } public ValueResolverGenerator build() { - return new ValueResolverGenerator(index, classOutput, nameToClass, nameToTemplateData, forceGettersPredicate); + return new ValueResolverGenerator(index, classOutput, nameToClass, nameToTemplateData, forceGettersFunction); } } @@ -864,7 +864,7 @@ static String decapitalize(String name) { return new String(chars); } - static String capitalize(String name) { + public static String capitalize(String name) { if (name == null || name.length() == 0) { return name; } @@ -933,6 +933,15 @@ static String generatedNameFromTarget(String targetPackage, String baseName, Str } } + private static boolean noneMethodMatches(List methods, String name) { + for (MethodKey method : methods) { + if (method.name.equals(name)) { + return false; + } + } + return true; + } + public static boolean hasCompletionStageInTypeClosure(ClassInfo classInfo, IndexView index) { return hasClassInTypeClosure(classInfo, DotNames.COMPLETION_STAGE, index); diff --git a/integration-tests/qute/pom.xml b/integration-tests/qute/pom.xml index efa279b7cc704..adb4055911d9f 100644 --- a/integration-tests/qute/pom.xml +++ b/integration-tests/qute/pom.xml @@ -8,7 +8,7 @@ 999-SNAPSHOT 4.0.0 - + quarkus-integration-test-qute Quarkus - Integration Tests - Qute Qute integration test module @@ -22,8 +22,21 @@ io.quarkus quarkus-vertx-web + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-jdbc-h2 + + + io.quarkus + quarkus-test-h2 + test + io.quarkus quarkus-junit5 @@ -62,10 +75,46 @@ + + io.quarkus + quarkus-hibernate-orm-panache-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-h2-deployment + ${project.version} + pom + test + + + * + * + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + 1.8 + 1.8 + true + + io.quarkus quarkus-maven-plugin diff --git a/integration-tests/qute/src/main/java/io/quarkus/it/qute/Beer.java b/integration-tests/qute/src/main/java/io/quarkus/it/qute/Beer.java new file mode 100644 index 0000000000000..9a3e5e267c754 --- /dev/null +++ b/integration-tests/qute/src/main/java/io/quarkus/it/qute/Beer.java @@ -0,0 +1,16 @@ +package io.quarkus.it.qute; + +import javax.persistence.Entity; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Beer extends PanacheEntity { + + public String name; + + public Boolean completed; + + public boolean done; + +} diff --git a/integration-tests/qute/src/main/java/io/quarkus/it/qute/BeerResource.java b/integration-tests/qute/src/main/java/io/quarkus/it/qute/BeerResource.java new file mode 100644 index 0000000000000..4a328dca72292 --- /dev/null +++ b/integration-tests/qute/src/main/java/io/quarkus/it/qute/BeerResource.java @@ -0,0 +1,28 @@ +package io.quarkus.it.qute; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.api.CheckedTemplate; + +@Path("/beer") +public class BeerResource { + + @CheckedTemplate + static class Templates { + + static native TemplateInstance beer(Beer beer); + + } + + @GET + @Produces(MediaType.TEXT_HTML) + public TemplateInstance get() { + Beer beer = Beer.find("name", "Pilsner").firstResult(); + return Templates.beer(beer); + } + +} diff --git a/integration-tests/qute/src/main/java/io/quarkus/it/qute/Brewery.java b/integration-tests/qute/src/main/java/io/quarkus/it/qute/Brewery.java new file mode 100644 index 0000000000000..0211649c419b4 --- /dev/null +++ b/integration-tests/qute/src/main/java/io/quarkus/it/qute/Brewery.java @@ -0,0 +1,19 @@ +package io.quarkus.it.qute; + +import javax.enterprise.event.Observes; +import javax.transaction.Transactional; + +import io.quarkus.runtime.StartupEvent; + +public class Brewery { + + @Transactional + void onStart(@Observes StartupEvent event) { + Beer myBeer = new Beer(); + myBeer.name = "Pilsner"; + myBeer.completed = true; + myBeer.done = true; + myBeer.persist(); + } + +} diff --git a/integration-tests/qute/src/main/resources/application.properties b/integration-tests/qute/src/main/resources/application.properties new file mode 100644 index 0000000000000..723225e17fee8 --- /dev/null +++ b/integration-tests/qute/src/main/resources/application.properties @@ -0,0 +1,6 @@ +quarkus.datasource.db-kind=h2 +quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test +quarkus.datasource.jdbc.max-size=8 + +quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect +quarkus.hibernate-orm.database.generation=drop-and-create \ No newline at end of file diff --git a/integration-tests/qute/src/main/resources/templates/BeerResource/beer.html b/integration-tests/qute/src/main/resources/templates/BeerResource/beer.html new file mode 100644 index 0000000000000..114e4e28b0261 --- /dev/null +++ b/integration-tests/qute/src/main/resources/templates/BeerResource/beer.html @@ -0,0 +1,10 @@ + + + + +Beer! + + +

Beer {beer.name}, completed: {beer.completed}, done: {beer.done}

+ + \ No newline at end of file diff --git a/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java b/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java index 1ca76db7acf95..ca4eb9f76bdb0 100644 --- a/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java +++ b/integration-tests/qute/src/test/java/io/quarkus/it/qute/QuteTestCase.java @@ -14,7 +14,7 @@ public class QuteTestCase { @Test - public void testTemplate() throws InterruptedException { + public void testTemplates() throws InterruptedException { RestAssured.when().get("/hello?name=Ciri").then().body(containsString("Hello Ciri!")); RestAssured.when().get("/hello").then().body(containsString("Hello world!")); RestAssured.given().accept(ContentType.HTML).when().get("/hello-route") @@ -24,6 +24,7 @@ public void testTemplate() throws InterruptedException { RestAssured.given().accept(ContentType.HTML).when().get("/hello-route?name=Ciri").then() .contentType(is(ContentType.HTML.toString())) .body(containsString("Hello Ciri!")); + RestAssured.when().get("/beer").then().body(containsString("Beer Pilsner, completed: true, done: true")); } @Test diff --git a/integration-tests/qute/src/test/java/io/quarkus/it/qute/TestResources.java b/integration-tests/qute/src/test/java/io/quarkus/it/qute/TestResources.java new file mode 100644 index 0000000000000..5dbac2cb4c232 --- /dev/null +++ b/integration-tests/qute/src/test/java/io/quarkus/it/qute/TestResources.java @@ -0,0 +1,8 @@ +package io.quarkus.it.qute; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +}