From 366d49b50e4487280d7e0baa94e8906c3f2a09f5 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Mon, 6 Sep 2021 14:10:37 +0200 Subject: [PATCH] GraphQL - make sure the panache entities are updated in the index Signed-off-by: Phillip Kruger --- .../smallrye-graphql/deployment/pom.xml | 1 - .../graphql/deployment/OverridableIndex.java | 250 ++++++++++++++++++ .../SmallRyeGraphQLIndexBuildItem.java | 18 ++ .../deployment/SmallRyeGraphQLProcessor.java | 37 ++- .../graphql/deployment/HotReloadTest.java | 77 ++++++ .../smallrye/graphql/deployment/TestPojo.java | 2 + .../hibernate-orm-graphql-panache/pom.xml | 122 +++++++++ .../orm/graphql/panache/Author.java | 14 + .../hibertnate/orm/graphql/panache/Book.java | 41 +++ .../orm/graphql/panache/BookRepository.java | 9 + .../orm/graphql/panache/GraphQLResource.java | 32 +++ .../src/main/resources/application.properties | 9 + .../src/main/resources/import.sql | 6 + .../panache/HibernateOrmGraphQLPanacheIT.java | 7 + .../HibernateOrmGraphQLPanacheTest.java | 69 +++++ .../orm/graphql/panache/PayloadCreator.java | 31 +++ .../orm/graphql/panache/TestResources.java | 8 + integration-tests/pom.xml | 1 + 18 files changed, 730 insertions(+), 4 deletions(-) create mode 100644 extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java create mode 100644 extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLIndexBuildItem.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/pom.xml create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Author.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Book.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/BookRepository.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/GraphQLResource.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/main/resources/application.properties create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/main/resources/import.sql create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheIT.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheTest.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/PayloadCreator.java create mode 100644 integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/TestResources.java diff --git a/extensions/smallrye-graphql/deployment/pom.xml b/extensions/smallrye-graphql/deployment/pom.xml index cf6474ef19815..cd7dd722d20ea 100644 --- a/extensions/smallrye-graphql/deployment/pom.xml +++ b/extensions/smallrye-graphql/deployment/pom.xml @@ -18,7 +18,6 @@ io.quarkus quarkus-smallrye-graphql - ${project.version} diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java new file mode 100644 index 0000000000000..6b87a8d21ee6e --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java @@ -0,0 +1,250 @@ +package io.quarkus.smallrye.graphql.deployment; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.FieldInfo; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.MethodParameterInfo; +import org.jboss.jandex.ModuleInfo; +import org.jboss.jandex.RecordComponentInfo; +import org.jboss.jandex.Type; + +public class OverridableIndex implements IndexView { + + private final IndexView original; + private final IndexView override; + + private OverridableIndex(IndexView original, IndexView override) { + this.original = original; + this.override = override; + } + + public static OverridableIndex create(IndexView original, IndexView override) { + return new OverridableIndex(original, override); + } + + @Override + public Collection getKnownClasses() { + return overrideCollection(original.getKnownClasses(), override.getKnownClasses(), classInfoComparator); + } + + @Override + public ClassInfo getClassByName(DotName dn) { + return overrideObject(original.getClassByName(dn), override.getClassByName(dn)); + } + + @Override + public Collection getKnownDirectSubclasses(DotName dn) { + return overrideCollection(original.getKnownDirectSubclasses(dn), override.getKnownDirectSubclasses(dn), + classInfoComparator); + } + + @Override + public Collection getAllKnownSubclasses(DotName dn) { + return overrideCollection(original.getAllKnownSubclasses(dn), override.getAllKnownSubclasses(dn), classInfoComparator); + } + + @Override + public Collection getKnownDirectImplementors(DotName dn) { + return overrideCollection(original.getKnownDirectImplementors(dn), override.getKnownDirectImplementors(dn), + classInfoComparator); + } + + @Override + public Collection getAllKnownImplementors(DotName dn) { + return overrideCollection(original.getAllKnownImplementors(dn), override.getAllKnownImplementors(dn), + classInfoComparator); + } + + @Override + public Collection getAnnotations(DotName dn) { + return overrideCollection(original.getAnnotations(dn), override.getAnnotations(dn), annotationInstanceComparator); + } + + @Override + public Collection getAnnotationsWithRepeatable(DotName dn, IndexView iv) { + return overrideCollection(original.getAnnotationsWithRepeatable(dn, iv), override.getAnnotationsWithRepeatable(dn, iv), + annotationInstanceComparator); + } + + @Override + public Collection getKnownModules() { + return overrideCollection(original.getKnownModules(), override.getKnownModules(), moduleInfoComparator); + } + + @Override + public ModuleInfo getModuleByName(DotName dn) { + return overrideObject(original.getModuleByName(dn), override.getModuleByName(dn)); + } + + @Override + public Collection getKnownUsers(DotName dn) { + return overrideCollection(original.getKnownUsers(dn), override.getKnownUsers(dn), classInfoComparator); + } + + private Comparator classInfoComparator = new Comparator() { + @Override + public int compare(ClassInfo t, ClassInfo t1) { + return t.name().toString().compareTo(t1.name().toString()); + } + }; + + private Comparator typeComparator = new Comparator() { + @Override + public int compare(Type t, Type t1) { + return t.name().toString().compareTo(t1.name().toString()); + } + }; + + private Comparator moduleInfoComparator = new Comparator() { + @Override + public int compare(ModuleInfo t, ModuleInfo t1) { + return t.name().toString().compareTo(t1.name().toString()); + } + }; + + private Comparator fieldInfoComparator = new Comparator() { + @Override + public int compare(FieldInfo t, FieldInfo t1) { + if (classInfoComparator.compare(t.declaringClass(), t1.declaringClass()) == 0) { // Same class + return t.name().toString().compareTo(t1.name().toString()); + } + return -1; + } + }; + + private Comparator recordComponentInfoComparator = new Comparator() { + @Override + public int compare(RecordComponentInfo t, RecordComponentInfo t1) { + if (classInfoComparator.compare(t.declaringClass(), t1.declaringClass()) == 0) { // Same class + return t.name().toString().compareTo(t1.name().toString()); + } + return -1; + } + }; + + private Comparator methodInfoComparator = new Comparator() { + @Override + public int compare(MethodInfo t, MethodInfo t1) { + if (classInfoComparator.compare(t.declaringClass(), t1.declaringClass()) == 0) { // Same class + if (t.name().toString().compareTo(t1.name().toString()) == 0) { // Same method name + if (t.parameters().size() == t1.parameters().size()) { // Same number of parameters + for (int i = 0; i < t.parameters().size(); i++) { + int typeTheSame = typeComparator.compare(t.parameters().get(i), t1.parameters().get(i)); + if (typeTheSame != 0) { + return typeTheSame; + } + } + // All parameter type are the same + return 0; + } + } + } + return -1; + } + }; + + private Comparator methodParameterInfoComparator = new Comparator() { + @Override + public int compare(MethodParameterInfo t, MethodParameterInfo t1) { + if (methodInfoComparator.compare(t.method(), t1.method()) == 0 && // Same method + t.kind().equals(t1.kind()) && // Same kind + t.name().equals(t1.name()) && // Same name + t.position() == t1.position()) { // Same position + return 0; + } + return -1; + } + }; + + private Comparator annotationInstanceComparator = new Comparator() { + @Override + public int compare(AnnotationInstance t, AnnotationInstance t1) { + if (t.name().equals(t1.name())) { + // Class Info + if (t.target().kind().equals(AnnotationTarget.Kind.CLASS) + && t1.target().kind().equals(AnnotationTarget.Kind.CLASS)) { + return classInfoComparator.compare(t.target().asClass(), t1.target().asClass()); + } + + // Field Info + if (t.target().kind().equals(AnnotationTarget.Kind.FIELD) + && t1.target().kind().equals(AnnotationTarget.Kind.FIELD)) { + return fieldInfoComparator.compare(t.target().asField(), t1.target().asField()); + } + + // Type + if (t.target().kind().equals(AnnotationTarget.Kind.TYPE) + && t1.target().kind().equals(AnnotationTarget.Kind.TYPE)) { + return typeComparator.compare(t.target().asType().target(), t1.target().asType().target()); + } + + // Method Info + if (t.target().kind().equals(AnnotationTarget.Kind.METHOD) + && t1.target().kind().equals(AnnotationTarget.Kind.METHOD)) { + return methodInfoComparator.compare(t.target().asMethod(), t1.target().asMethod()); + } + + // Method Parameter + if (t.target().kind().equals(AnnotationTarget.Kind.METHOD_PARAMETER) + && t1.target().kind().equals(AnnotationTarget.Kind.METHOD_PARAMETER)) { + return methodParameterInfoComparator.compare(t.target().asMethodParameter(), + t1.target().asMethodParameter()); + } + + // Record + if (t.target().kind().equals(AnnotationTarget.Kind.RECORD_COMPONENT) + && t1.target().kind().equals(AnnotationTarget.Kind.RECORD_COMPONENT)) { + return recordComponentInfoComparator.compare(t.target().asRecordComponent(), + t1.target().asRecordComponent()); + } + } + return -1; + } + }; + + private Collection overrideCollection(Collection originalCollection, Collection overrideCollection, + Comparator comparator) { + if (originalCollection == null && overrideCollection == null) { + return null; + } + + if (originalCollection == null) { + return overrideCollection; + } + if (overrideCollection == null) { + return originalCollection; + } + + if (originalCollection.isEmpty() && overrideCollection.isEmpty()) { + return originalCollection; + } + + if (originalCollection.isEmpty()) { + return overrideCollection; + } + if (overrideCollection.isEmpty()) { + return originalCollection; + } + + Set newCollection = new TreeSet<>(comparator); + newCollection.addAll(overrideCollection); + newCollection.addAll(originalCollection); // Won't add if it's already there. + return newCollection; + } + + private T overrideObject(T originalObject, T overrideObject) { + if (overrideObject != null) { + return overrideObject; + } + return originalObject; + } +} diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLIndexBuildItem.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLIndexBuildItem.java new file mode 100644 index 0000000000000..c6c3c0e3bea1d --- /dev/null +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLIndexBuildItem.java @@ -0,0 +1,18 @@ +package io.quarkus.smallrye.graphql.deployment; + +import java.util.Map; + +import io.quarkus.builder.item.SimpleBuildItem; + +final class SmallRyeGraphQLIndexBuildItem extends SimpleBuildItem { + + private final Map modifiedClases; + + public SmallRyeGraphQLIndexBuildItem(Map modifiedClases) { + this.modifiedClases = modifiedClases; + } + + public Map getModifiedClases() { + return modifiedClases; + } +} diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java index 52ac9f9278bfc..22de82bc20aad 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/SmallRyeGraphQLProcessor.java @@ -1,9 +1,11 @@ package io.quarkus.smallrye.graphql.deployment; +import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -13,7 +15,7 @@ import java.util.stream.Stream; import org.eclipse.microprofile.config.ConfigProvider; -import org.jboss.jandex.IndexView; +import org.jboss.jandex.Indexer; import org.jboss.logging.Logger; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; @@ -38,6 +40,7 @@ import io.quarkus.deployment.builditem.LiveReloadBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; import io.quarkus.deployment.builditem.SystemPropertyBuildItem; +import io.quarkus.deployment.builditem.TransformedClassesBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem; @@ -158,6 +161,22 @@ void registerNativeImageResources(BuildProducer servic .produce(ServiceProviderBuildItem.allProvidersFromClassPath(SmallRyeGraphQLConfigMapping.class.getName())); } + @BuildStep + SmallRyeGraphQLIndexBuildItem createIndex(TransformedClassesBuildItem transformedClassesBuildItem) { + Map modifiedClasses = new HashMap<>(); + Map> transformedClassesByJar = transformedClassesBuildItem + .getTransformedClassesByJar(); + for (Map.Entry> transformedClassesByJarEntrySet : transformedClassesByJar + .entrySet()) { + + Set transformedClasses = transformedClassesByJarEntrySet.getValue(); + for (TransformedClassesBuildItem.TransformedClass transformedClass : transformedClasses) { + modifiedClasses.put(transformedClass.getClassName(), transformedClass.getData()); + } + } + return new SmallRyeGraphQLIndexBuildItem(modifiedClasses); + } + @Record(ExecutionTime.STATIC_INIT) @BuildStep void buildExecutionService( @@ -165,13 +184,25 @@ void buildExecutionService( BuildProducer reflectiveHierarchyProducer, BuildProducer graphQLInitializedProducer, SmallRyeGraphQLRecorder recorder, + SmallRyeGraphQLIndexBuildItem graphQLIndexBuildItem, BeanContainerBuildItem beanContainer, CombinedIndexBuildItem combinedIndex, SmallRyeGraphQLConfig graphQLConfig) { - IndexView index = combinedIndex.getIndex(); + Indexer indexer = new Indexer(); + Map modifiedClases = graphQLIndexBuildItem.getModifiedClases(); + + for (Map.Entry kv : modifiedClases.entrySet()) { + try (ByteArrayInputStream bais = new ByteArrayInputStream(kv.getValue())) { + indexer.index(bais); + } catch (IOException ex) { + LOG.warn("Could not index [" + kv.getKey() + "] - " + ex.getMessage()); + } + } + + OverridableIndex overridableIndex = OverridableIndex.create(combinedIndex.getIndex(), indexer.complete()); - Schema schema = SchemaBuilder.build(index, graphQLConfig.autoNameStrategy); + Schema schema = SchemaBuilder.build(overridableIndex, graphQLConfig.autoNameStrategy); RuntimeValue initialized = recorder.createExecutionService(beanContainer.getValue(), schema); graphQLInitializedProducer.produce(new SmallRyeGraphQLInitializedBuildItem(initialized)); diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/HotReloadTest.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/HotReloadTest.java index 5976f6ffed18d..8b25292acd725 100644 --- a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/HotReloadTest.java +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/HotReloadTest.java @@ -1,5 +1,8 @@ package io.quarkus.smallrye.graphql.deployment; +import static io.quarkus.smallrye.graphql.deployment.AbstractGraphQLTest.MEDIATYPE_JSON; + +import org.hamcrest.CoreMatchers; import org.jboss.logging.Logger; import org.jboss.shrinkwrap.api.ShrinkWrap; import org.jboss.shrinkwrap.api.asset.EmptyAsset; @@ -9,6 +12,7 @@ import org.junit.jupiter.api.extension.RegisterExtension; import io.quarkus.test.QuarkusDevModeTest; +import io.restassured.RestAssured; /** * Test Hot reload after a code change @@ -25,6 +29,79 @@ public class HotReloadTest extends AbstractGraphQLTest { .addAsResource(new StringAsset(getPropertyAsString()), "application.properties") .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); + @Test + public void testAddAndRemoveFieldChange() { + + String fooRequest = getPayload("{\n" + + " foo {\n" + + " message\n" + + " randomNumber{\n" + + " value\n" + + " }\n" + + " foo\n" + + " list\n" + + " }\n" + + "}"); + + // Do a request + RestAssured.given().when() + .accept(MEDIATYPE_JSON) + .contentType(MEDIATYPE_JSON) + .body(fooRequest) + .post("/graphql") + .then() + .assertThat() + .statusCode(200) + .and() + .body(CoreMatchers.containsString( + "{\"errors\":[{\"message\":\"Validation error of type FieldUndefined: Field 'foo' in type 'TestPojo' is undefined @ 'foo/foo'\",\"locations\":[{\"line\":7,\"column\":5}],\"extensions\":{\"classification\":\"ValidationError\"}}],\"data\":null}")); + LOG.info("Initial request done"); + + // Make a code change (add a field) + TEST.modifySourceFile("TestPojo.java", s -> s.replace("// ", + "private String foo = \"bar\";\n" + + " public String getFoo(){\n" + + " return foo;\n" + + " }")); + LOG.info("Code change done - field added"); + + // Do the request again + RestAssured.given().when() + .accept(MEDIATYPE_JSON) + .contentType(MEDIATYPE_JSON) + .body(fooRequest) + .post("/graphql") + .then() + .assertThat() + .statusCode(200) + .and() + .body(CoreMatchers.containsString( + "{\"data\":{\"foo\":{\"message\":\"bar\",\"randomNumber\":{\"value\":123.0},\"foo\":\"bar\",\"list\":[\"a\",\"b\",\"c\"]}}}")); + LOG.info("Hot reload done"); + + // Make a code change again (remove) + TEST.modifySourceFile("TestPojo.java", s -> s.replace("private String foo = \"bar\";\n" + + " public String getFoo(){\n" + + " return foo;\n" + + " }", "// ")); + + // Do the request yet again + RestAssured.given().when() + .accept(MEDIATYPE_JSON) + .contentType(MEDIATYPE_JSON) + .body(fooRequest) + .post("/graphql") + .then() + .assertThat() + .statusCode(200) + .and() + .body(CoreMatchers.containsString( + "{\"errors\":[{\"message\":\"Validation error of type FieldUndefined: Field 'foo' in type 'TestPojo' is undefined @ 'foo/foo'\",\"locations\":[{\"line\":7,\"column\":5}],\"extensions\":{\"classification\":\"ValidationError\"}}],\"data\":null}")); + + LOG.info("Code change done - field removed"); + + } + @Test public void testCodeChange() { // Do a request diff --git a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/TestPojo.java b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/TestPojo.java index 65ff1aea16548..ad707a3149bfd 100644 --- a/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/TestPojo.java +++ b/extensions/smallrye-graphql/deployment/src/test/java/io/quarkus/smallrye/graphql/deployment/TestPojo.java @@ -45,6 +45,8 @@ public void setNumber(Number number) { this.number = number; } + // + @Override public String toString() { return "TestPojo{" + "message=" + message + ", list=" + list + ", number=" + number + '}'; diff --git a/integration-tests/hibernate-orm-graphql-panache/pom.xml b/integration-tests/hibernate-orm-graphql-panache/pom.xml new file mode 100644 index 0000000000000..983efc7595d19 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + quarkus-integration-tests-parent + io.quarkus + 999-SNAPSHOT + + + quarkus-integration-test-hibernate-orm-graphql-panache + Quarkus - Integration Tests - GraphQL and Hibernate ORM with Panache + + + + io.quarkus + quarkus-smallrye-graphql + + + io.quarkus + quarkus-jdbc-h2 + + + io.quarkus + quarkus-hibernate-orm-panache + + + io.quarkus + quarkus-hibernate-validator + + + io.quarkus + quarkus-junit5 + test + + + io.quarkus + quarkus-test-h2 + test + + + org.assertj + assertj-core + test + + + io.rest-assured + rest-assured + test + + + + + io.quarkus + quarkus-hibernate-orm-panache-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-smallrye-graphql-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-jdbc-h2-deployment + ${project.version} + pom + test + + + * + * + + + + + io.quarkus + quarkus-hibernate-validator-deployment + ${project.version} + pom + test + + + * + * + + + + + + + + + io.quarkus + quarkus-maven-plugin + + + + build + + + + + + + + \ No newline at end of file diff --git a/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Author.java b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Author.java new file mode 100644 index 0000000000000..952f7062fd7c5 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Author.java @@ -0,0 +1,14 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import java.time.LocalDate; + +import javax.persistence.Entity; + +import io.quarkus.hibernate.orm.panache.PanacheEntity; + +@Entity +public class Author extends PanacheEntity { + + public String name; + public LocalDate dob; +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Book.java b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Book.java new file mode 100644 index 0000000000000..1a636496400c1 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/Book.java @@ -0,0 +1,41 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.validation.constraints.NotBlank; + +@Entity +public class Book { + + @Id + @GeneratedValue + private Long id; + + @NotBlank + private String title; + + @ManyToOne + private Author author; + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public Author getAuthor() { + return author; + } + + public void setAuthor(Author author) { + this.author = author; + } +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/BookRepository.java b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/BookRepository.java new file mode 100644 index 0000000000000..35052b9b0976a --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/BookRepository.java @@ -0,0 +1,9 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.hibernate.orm.panache.PanacheRepository; + +@ApplicationScoped +public class BookRepository implements PanacheRepository { +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/GraphQLResource.java b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/GraphQLResource.java new file mode 100644 index 0000000000000..a3e8b1d1a1e31 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/main/java/io/quarkus/it/hibertnate/orm/graphql/panache/GraphQLResource.java @@ -0,0 +1,32 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import java.util.List; + +import javax.inject.Inject; + +import org.eclipse.microprofile.graphql.Description; +import org.eclipse.microprofile.graphql.GraphQLApi; +import org.eclipse.microprofile.graphql.Query; + +import io.quarkus.logging.Log; + +@GraphQLApi +public class GraphQLResource { + + @Inject + BookRepository bookRepository; + + @Query("authors") + @Description("Retrieve the stored authors") + public List getAuthors() { + Log.info("Getting all authors"); + return Author.listAll(); + } + + @Query("books") + @Description("Retrieve the stored books") + public List getBooks() { + Log.info("Getting all books"); + return bookRepository.listAll(); + } +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/main/resources/application.properties b/integration-tests/hibernate-orm-graphql-panache/src/main/resources/application.properties new file mode 100644 index 0000000000000..bbcff456cb710 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/main/resources/application.properties @@ -0,0 +1,9 @@ +quarkus.datasource.db-kind=h2 +quarkus.datasource.jdbc.url=jdbc:h2:tcp://localhost/mem:test +quarkus.datasource.jdbc.max-size=8 +quarkus.hibernate-orm.database.generation=drop-and-create +#quarkus.hibernate-orm.log.sql=true +quarkus.hibernate-orm.sql-load-script=import.sql + +quarkus.default-locale=en_US +quarkus.locales=en_US \ No newline at end of file diff --git a/integration-tests/hibernate-orm-graphql-panache/src/main/resources/import.sql b/integration-tests/hibernate-orm-graphql-panache/src/main/resources/import.sql new file mode 100644 index 0000000000000..91854e2f738d8 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/main/resources/import.sql @@ -0,0 +1,6 @@ +insert into author(id, name, dob) values (nextval('hibernate_sequence'), 'Fyodor Dostoevsky', '1821-11-11'); + +insert into book(id, title, author_id) values (nextval('hibernate_sequence'), 'Crime and Punishment', 1); +insert into book(id, title, author_id) values (nextval('hibernate_sequence'), 'Idiot', 1); +insert into book(id, title, author_id) values (nextval('hibernate_sequence'), 'Demons', 1); +insert into book(id, title, author_id) values (nextval('hibernate_sequence'), 'The adolescent', 1); diff --git a/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheIT.java b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheIT.java new file mode 100644 index 0000000000000..f9ac8e27659c9 --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheIT.java @@ -0,0 +1,7 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class HibernateOrmGraphQLPanacheIT extends HibernateOrmGraphQLPanacheTest { +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheTest.java b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheTest.java new file mode 100644 index 0000000000000..2f9224fdf81ca --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/HibernateOrmGraphQLPanacheTest.java @@ -0,0 +1,69 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.contains; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +class HibernateOrmGraphQLPanacheTest { + + private static final int DOSTOEVSKY_ID = 1; + + private static final String DOSTOEVSKY_NAME = "Fyodor Dostoevsky"; + + private static final String DOSTOEVSKY_DOB = "1821-11-11"; + + private static final int CRIME_AND_PUNISHMENT_ID = 2; + + private static final String CRIME_AND_PUNISHMENT_TITLE = "Crime and Punishment"; + + private static final int IDIOT_ID = 3; + + private static final String IDIOT_TITLE = "Idiot"; + + private static final int DEMONS_ID = 4; + + private static final String DEMONS_TITLE = "Demons"; + + private static final int THE_ADOLESCENT_ID = 5; + + private static final String THE_ADOLESCENT_TITLE = "The adolescent"; + + @Test + void testEndpoint() { + + String bookAndAuthorRequest = PayloadCreator.getPayload("{\n" + + " authors {\n" + + " id\n" + + " name\n" + + " dob\n" + + " }\n" + + " books {\n" + + " id\n" + + " title\n" + + " author {\n" + + " name\n" + + " }\n" + + " }\n" + + "}"); + + given() + .when() + .accept("application/json") + .contentType("application/json") + .body(bookAndAuthorRequest) + .post("/graphql") + .then() + .statusCode(200) + .and().body("data.authors.id", contains(DOSTOEVSKY_ID)) + .and().body("data.authors.name", contains(DOSTOEVSKY_NAME)) + .and().body("data.authors.dob", contains(DOSTOEVSKY_DOB)) + .and().body("data.books.id", contains(CRIME_AND_PUNISHMENT_ID, IDIOT_ID, DEMONS_ID, THE_ADOLESCENT_ID)) + .and().body("data.books.title", + contains(CRIME_AND_PUNISHMENT_TITLE, IDIOT_TITLE, DEMONS_TITLE, THE_ADOLESCENT_TITLE)); + } + +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/PayloadCreator.java b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/PayloadCreator.java new file mode 100644 index 0000000000000..47682edf351ac --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/PayloadCreator.java @@ -0,0 +1,31 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import javax.json.Json; +import javax.json.JsonObject; + +public class PayloadCreator { + + private PayloadCreator() { + } + + public static String getPayload(String query) { + JsonObject jsonObject = createRequestBody(query); + return jsonObject.toString(); + } + + private static JsonObject createRequestBody(String graphQL) { + return createRequestBody(graphQL, null); + } + + private static JsonObject createRequestBody(String graphQL, JsonObject variables) { + // Create the request + if (variables == null || variables.isEmpty()) { + variables = Json.createObjectBuilder().build(); + } + return Json.createObjectBuilder().add(QUERY, graphQL).add(VARIABLES, variables).build(); + } + + public static final String MEDIATYPE_JSON = "application/json"; + private static final String QUERY = "query"; + private static final String VARIABLES = "variables"; +} diff --git a/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/TestResources.java b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/TestResources.java new file mode 100644 index 0000000000000..87980f0eb266d --- /dev/null +++ b/integration-tests/hibernate-orm-graphql-panache/src/test/java/io/quarkus/it/hibertnate/orm/graphql/panache/TestResources.java @@ -0,0 +1,8 @@ +package io.quarkus.it.hibertnate.orm.graphql.panache; + +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.h2.H2DatabaseTestResource; + +@QuarkusTestResource(H2DatabaseTestResource.class) +public class TestResources { +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index c7cadd1fe67ba..e8c89c8a26527 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -153,6 +153,7 @@ jpa-oracle hibernate-orm-panache hibernate-orm-rest-data-panache + hibernate-orm-graphql-panache hibernate-orm-panache-kotlin hibernate-reactive-db2 hibernate-reactive-mysql