diff --git a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java index 49d6ae16457a5..1f83fe5b593fb 100644 --- a/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java +++ b/extensions/panache/mongodb-panache/deployment/src/main/java/io/quarkus/mongodb/panache/deployment/PanacheResourceProcessor.java @@ -384,12 +384,9 @@ void buildMutiny(CombinedIndexBuildItem index, daoClasses.add(classInfo.name().toString()); } for (String daoClass : daoClasses) { - System.out.println("found " + daoClasses); transformers.produce(new BytecodeTransformerBuildItem(daoClass, daoEnhancer)); } - System.out.println("HERE WE ARE"); - MutinyPanacheMongoEntityEnhancer modelEnhancer = new MutinyPanacheMongoEntityEnhancer(index.getIndex()); Set modelClasses = new HashSet<>(); // Note that we do this in two passes because for some reason Jandex does not give us subtypes diff --git a/integration-tests/mongodb-panache/pom.xml b/integration-tests/mongodb-panache/pom.xml index 875eb656f078c..85585f48a28c1 100755 --- a/integration-tests/mongodb-panache/pom.xml +++ b/integration-tests/mongodb-panache/pom.xml @@ -27,6 +27,14 @@ io.quarkus quarkus-rest-client + + io.quarkus + quarkus-resteasy-mutiny + + + io.quarkus + quarkus-smallrye-context-propagation + io.quarkus diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java new file mode 100644 index 0000000000000..1f83216437968 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntity.java @@ -0,0 +1,81 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; + +import javax.json.bind.annotation.JsonbDateFormat; + +import org.bson.codecs.pojo.annotations.BsonIgnore; +import org.bson.codecs.pojo.annotations.BsonProperty; + +import io.quarkus.it.mongodb.panache.book.BookDetail; +import io.quarkus.mongodb.panache.MongoEntity; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntity; + +@MongoEntity(collection = "TheBookEntity") +public class ReactiveBookEntity extends ReactivePanacheMongoEntity { + @BsonProperty("bookTitle") + private String title; + private String author; + @BsonIgnore + private String transientDescription; + @JsonbDateFormat("yyyy-MM-dd") + private LocalDate creationDate; + + private List categories = new ArrayList<>(); + + private BookDetail details; + + public String getTitle() { + return title; + } + + public ReactiveBookEntity setTitle(String title) { + this.title = title; + return this; + } + + public String getAuthor() { + return author; + } + + public ReactiveBookEntity setAuthor(String author) { + this.author = author; + return this; + } + + public List getCategories() { + return categories; + } + + public ReactiveBookEntity setCategories(List categories) { + this.categories = categories; + return this; + } + + public BookDetail getDetails() { + return details; + } + + public ReactiveBookEntity setDetails(BookDetail details) { + this.details = details; + return this; + } + + public String getTransientDescription() { + return transientDescription; + } + + public void setTransientDescription(String transientDescription) { + this.transientDescription = transientDescription; + } + + public LocalDate getCreationDate() { + return creationDate; + } + + public void setCreationDate(LocalDate creationDate) { + this.creationDate = creationDate; + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.java new file mode 100644 index 0000000000000..cb4709d236117 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookEntityResource.java @@ -0,0 +1,135 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import java.net.URI; +import java.time.LocalDate; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.PATCH; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.bson.types.ObjectId; +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.SseElementType; +import org.reactivestreams.Publisher; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/books/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactiveBookEntityResource { + private static final Logger LOGGER = Logger.getLogger( + ReactiveBookEntityResource.class); + + @PostConstruct + void init() { + String databaseName = ReactiveBookEntity.mongoDatabase().getName(); + String collectionName = ReactiveBookEntity.mongoCollection().getNamespace().getCollectionName(); + LOGGER.infov("Using BookEntity[database={0}, collection={1}]", databaseName, collectionName); + } + + @GET + public Uni> getBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactiveBookEntity.listAll(Sort.ascending(sort)); + } + return ReactiveBookEntity.listAll(); + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + public Publisher streamBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactiveBookEntity.streamAll(Sort.ascending(sort)); + } + return ReactiveBookEntity.streamAll(); + } + + @POST + public Uni addBook(ReactiveBookEntity book) { + return book.persist().map(v -> { + //the ID is populated before sending it to the database + String id = book.id.toString(); + return Response.created(URI.create("/books/entity" + id)).build(); + }); + } + + @PUT + public Uni updateBook(ReactiveBookEntity book) { + return book.update().map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertBook(ReactiveBookEntity book) { + return book.persistOrUpdate().map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deleteBook(@PathParam("id") String id) { + return ReactiveBookEntity.findById(new ObjectId(id)).flatMap(ReactivePanacheMongoEntityBase::delete); + } + + @GET + @Path("/{id}") + public Uni getBook(@PathParam("id") String id) { + return ReactiveBookEntity.findById(new ObjectId(id)); + } + + @GET + @Path("/search/{author}") + public Uni> getBooksByAuthor(@PathParam("author") String author) { + return ReactiveBookEntity.list("author", author); + } + + @GET + @Path("/search") + public Uni search(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return ReactiveBookEntity.find("{'author': ?1,'bookTitle': ?2}", author, title).firstResult(); + } + + return ReactiveBookEntity + .find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult(); + } + + @GET + @Path("/search2") + public Uni search2(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return ReactiveBookEntity.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult(); + } + + return ReactiveBookEntity.find("{'creationDate': {$gte: :dateFrom}, 'creationDate': {$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))) + .firstResult(); + } + + @DELETE + public Uni deleteAll() { + return ReactiveBookEntity.deleteAll().map(l -> null); + } + +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java new file mode 100644 index 0000000000000..7cb283d7ca032 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.it.mongodb.panache.book.Book; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepository; + +@ApplicationScoped +public class ReactiveBookRepository implements ReactivePanacheMongoRepository { +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java new file mode 100644 index 0000000000000..f411ec6092985 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/book/ReactiveBookRepositoryResource.java @@ -0,0 +1,129 @@ +package io.quarkus.it.mongodb.panache.reactive.book; + +import java.net.URI; +import java.time.LocalDate; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.bson.types.ObjectId; +import org.jboss.logging.Logger; +import org.jboss.resteasy.annotations.SseElementType; +import org.reactivestreams.Publisher; + +import io.quarkus.it.mongodb.panache.book.Book; +import io.quarkus.panache.common.Parameters; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/books/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactiveBookRepositoryResource { + private static final Logger LOGGER = Logger.getLogger( + ReactiveBookRepositoryResource.class); + @Inject + ReactiveBookRepository reactiveBookRepository; + + @PostConstruct + void init() { + String databaseName = reactiveBookRepository.mongoDatabase().getName(); + String collectionName = reactiveBookRepository.mongoCollection().getNamespace().getCollectionName(); + LOGGER.infov("Using BookRepository[database={0}, collection={1}]", databaseName, collectionName); + } + + @GET + public Uni> getBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return reactiveBookRepository.listAll(Sort.ascending(sort)); + } + return reactiveBookRepository.listAll(); + } + + @GET + @Path("/stream") + @Produces(MediaType.SERVER_SENT_EVENTS) + @SseElementType(MediaType.APPLICATION_JSON) + public Publisher streamBooks(@QueryParam("sort") String sort) { + if (sort != null) { + return reactiveBookRepository.streamAll(Sort.ascending(sort)); + } + return reactiveBookRepository.streamAll(); + } + + @POST + public Uni addBook(Book book) { + return reactiveBookRepository.persist(book).map(v -> { + //the ID is populated before sending it to the database + String id = book.getId().toString(); + return Response.created(URI.create("/books/entity" + id)).build(); + }); + } + + @PUT + public Uni updateBook(Book book) { + return reactiveBookRepository.update(book).map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertBook(Book book) { + return reactiveBookRepository.persistOrUpdate(book).map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deleteBook(@PathParam("id") String id) { + return reactiveBookRepository.findById(new ObjectId(id)) + .flatMap(book -> reactiveBookRepository.delete(book)); + } + + @GET + @Path("/{id}") + public Uni getBook(@PathParam("id") String id) { + return reactiveBookRepository.findById(new ObjectId(id)); + } + + @GET + @Path("/search/{author}") + public Uni> getBooksByAuthor(@PathParam("author") String author) { + return reactiveBookRepository.list("author", author); + } + + @GET + @Path("/search") + public Uni search(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return reactiveBookRepository.find("{'author': ?1,'bookTitle': ?2}", author, title).firstResult(); + } + + return reactiveBookRepository + .find("{'creationDate': {$gte: ?1}, 'creationDate': {$lte: ?2}}", LocalDate.parse(dateFrom), + LocalDate.parse(dateTo)) + .firstResult(); + } + + @GET + @Path("/search2") + public Uni search2(@QueryParam("author") String author, @QueryParam("title") String title, + @QueryParam("dateFrom") String dateFrom, @QueryParam("dateTo") String dateTo) { + if (author != null) { + return reactiveBookRepository.find("{'author': :author,'bookTitle': :title}", + Parameters.with("author", author).and("title", title)).firstResult(); + } + + return reactiveBookRepository.find("{'creationDate': {$gte: :dateFrom}, 'creationDate': {$lte: :dateTo}}", + Parameters.with("dateFrom", LocalDate.parse(dateFrom)).and("dateTo", LocalDate.parse(dateTo))) + .firstResult(); + } + + @DELETE + public Uni deleteAll() { + return reactiveBookRepository.deleteAll().map(l -> null); + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java new file mode 100644 index 0000000000000..e389795a2de8e --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntity.java @@ -0,0 +1,12 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import org.bson.codecs.pojo.annotations.BsonId; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; + +public class ReactivePersonEntity extends ReactivePanacheMongoEntityBase { + @BsonId + public Long id; + public String firstname; + public String lastname; +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.java new file mode 100644 index 0000000000000..b482c71b7d5d5 --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonEntityResource.java @@ -0,0 +1,75 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import java.net.URI; +import java.util.List; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoEntityBase; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/persons/entity") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactivePersonEntityResource { + @GET + public Uni> getPersons(@QueryParam("sort") String sort) { + if (sort != null) { + return ReactivePersonEntity.listAll(Sort.ascending(sort)); + } + return ReactivePersonEntity.listAll(); + } + + @POST + public Uni addPerson(ReactivePersonEntity person) { + return person.persist().map(v -> { + //the ID is populated before sending it to the database + String id = person.id.toString(); + return Response.created(URI.create("/persons/entity" + id)).build(); + }); + } + + @POST + @Path("/multiple") + public Uni addPersons(List persons) { + return ReactivePersonEntity.persist(persons); + } + + @PUT + public Uni updatePerson(ReactivePersonEntity person) { + return person.update().map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertPerson(ReactivePersonEntity person) { + return person.persistOrUpdate().map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deletePerson(@PathParam("id") String id) { + return ReactivePersonEntity.findById(Long.parseLong(id)).flatMap(ReactivePanacheMongoEntityBase::delete); + } + + @GET + @Path("/{id}") + public Uni getPerson(@PathParam("id") String id) { + return ReactivePersonEntity.findById(Long.parseLong(id)); + } + + @GET + @Path("/count") + public Uni countAll() { + return ReactivePersonEntity.count(); + } + + @DELETE + public Uni deleteAll() { + return ReactivePersonEntity.deleteAll() + .onItem().ignore().andContinueWithNull(); + } +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java new file mode 100644 index 0000000000000..01054e26b910a --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepository.java @@ -0,0 +1,10 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import javax.enterprise.context.ApplicationScoped; + +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.mongodb.panache.reactive.ReactivePanacheMongoRepositoryBase; + +@ApplicationScoped +public class ReactivePersonRepository implements ReactivePanacheMongoRepositoryBase { +} diff --git a/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java new file mode 100644 index 0000000000000..00f767991c4ae --- /dev/null +++ b/integration-tests/mongodb-panache/src/main/java/io/quarkus/it/mongodb/panache/reactive/person/ReactivePersonRepositoryResource.java @@ -0,0 +1,81 @@ +package io.quarkus.it.mongodb.panache.reactive.person; + +import java.net.URI; +import java.util.List; + +import javax.inject.Inject; +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.panache.common.Sort; +import io.smallrye.mutiny.Uni; + +@Path("/reactive/persons/repository") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +public class ReactivePersonRepositoryResource { + + @Inject + ReactivePersonRepository reactivePersonRepository; + + @GET + public Uni> getPersons(@QueryParam("sort") String sort) { + if (sort != null) { + return reactivePersonRepository.listAll(Sort.ascending(sort)); + } + return reactivePersonRepository.listAll(); + } + + @POST + public Uni addPerson(Person person) { + return reactivePersonRepository.persist(person).map(v -> { + //the ID is populated before sending it to the database + String id = person.id.toString(); + return Response.created(URI.create("/persons/entity" + id)).build(); + }); + } + + @POST + @Path("/multiple") + public Uni addPersons(List persons) { + return reactivePersonRepository.persist(persons); + } + + @PUT + public Uni updatePerson(Person person) { + return reactivePersonRepository.update(person).map(v -> Response.accepted().build()); + } + + // PATCH is not correct here but it allows to test persistOrUpdate without a specific subpath + @PATCH + public Uni upsertPerson(Person person) { + return reactivePersonRepository.persistOrUpdate(person).map(v -> Response.accepted().build()); + } + + @DELETE + @Path("/{id}") + public Uni deletePerson(@PathParam("id") String id) { + return reactivePersonRepository.findById(Long.parseLong(id)) + .flatMap(person -> reactivePersonRepository.delete(person)); + } + + @GET + @Path("/{id}") + public Uni getPerson(@PathParam("id") String id) { + return reactivePersonRepository.findById(Long.parseLong(id)); + } + + @GET + @Path("/count") + public Uni countAll() { + return reactivePersonRepository.count(); + } + + @DELETE + public Uni deleteAll() { + return reactivePersonRepository.deleteAll() + .onItem().ignore().andContinueWithNull(); + } +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java new file mode 100644 index 0000000000000..729ca8be6771e --- /dev/null +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/NativeMongodbPanacheResourceIT.java @@ -0,0 +1,8 @@ +package io.quarkus.it.mongodb.panache.reactive; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +class NativeMongodbPanacheResourceIT extends ReactiveMongodbPanacheResourceTest { + +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java new file mode 100644 index 0000000000000..3a2bcbc443ded --- /dev/null +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/reactive/ReactiveMongodbPanacheResourceTest.java @@ -0,0 +1,334 @@ +package io.quarkus.it.mongodb.panache.reactive; + +import static io.restassured.RestAssured.get; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.sse.SseEventSource; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import io.quarkus.it.mongodb.panache.BookDTO; +import io.quarkus.it.mongodb.panache.MongoTestResource; +import io.quarkus.it.mongodb.panache.book.BookDetail; +import io.quarkus.it.mongodb.panache.person.Person; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import io.restassured.config.ObjectMapperConfig; +import io.restassured.parsing.Parser; +import io.restassured.response.Response; + +@QuarkusTest +@QuarkusTestResource(MongoTestResource.class) +class ReactiveMongodbPanacheResourceTest { + private static final Logger LOGGER = LoggerFactory.getLogger( + ReactiveMongodbPanacheResourceTest.class); + private static final TypeRef> LIST_OF_BOOK_TYPE_REF = new TypeRef>() { + }; + private static final TypeRef> LIST_OF_PERSON_TYPE_REF = new TypeRef>() { + }; + + @Test + public void testReactiveBookEntity() throws InterruptedException { + callReactiveBookEndpoint("/reactive/books/entity"); + } + + @Test + public void testReactiveBookRepository() throws InterruptedException { + callReactiveBookEndpoint("/reactive/books/repository"); + } + + @Test + public void testReactivePersonEntity() { + callReactivePersonEndpoint("/reactive/persons/entity"); + } + + @Test + public void testReactivePersonRepository() { + callReactivePersonEndpoint("/reactive/persons/repository"); + } + + private void callReactiveBookEndpoint(String endpoint) throws InterruptedException { + RestAssured.defaultParser = Parser.JSON; + ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + RestAssured.config + .objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory((type, s) -> objectMapper)); + + List list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(0, list.size()); + + BookDTO book1 = new BookDTO().setAuthor("Victor Hugo").setTitle("Les Misérables") + .setCreationDate(yearToDate(1886)) + .setCategories(Arrays.asList("long", "very long")) + .setDetails(new BookDetail().setRating(3).setSummary("A very long book")); + Response response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book1) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + Assertions.assertTrue(response.header("Location").length() > 20);//Assert that id has been populated + + BookDTO book2 = new BookDTO().setAuthor("Victor Hugo").setTitle("Notre-Dame de Paris") + .setCreationDate(yearToDate(1831)) + .setCategories(Arrays.asList("long", "quasimodo")) + .setDetails(new BookDetail().setRating(4).setSummary("quasimodo and esmeralda")); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book2) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + + list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(2, list.size()); + + BookDTO book3 = new BookDTO().setAuthor("Charles Baudelaire").setTitle("Les fleurs du mal") + .setCreationDate(yearToDate(1857)) + .setCategories(Collections.singletonList("poem")) + .setDetails(new BookDetail().setRating(2).setSummary("Les Fleurs du mal is a volume of poetry.")); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book3) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + + BookDTO book4 = new BookDTO().setAuthor("Charles Baudelaire").setTitle("Le Spleen de Paris") + .setCreationDate(yearToDate(1869)) + .setCategories(Collections.singletonList("poem")) + .setDetails(new BookDetail().setRating(2) + .setSummary("Le Spleen de Paris is a collection of 50 short prose poems.")); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book4) + .patch(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(4, list.size()); + + //with sort + list = get(endpoint + "?sort=author").as(LIST_OF_BOOK_TYPE_REF); + assertEquals(4, list.size()); + + // magic query find("author", author) + list = get(endpoint + "/search/Victor Hugo").as(LIST_OF_BOOK_TYPE_REF); + assertEquals(2, list.size()); + + // magic query find("{'author':?1,'title':?1}", author, title) + BookDTO book = get(endpoint + "/search?author=Victor Hugo&title=Notre-Dame de Paris").as(BookDTO.class); + assertNotNull(book); + + // date + book = get(endpoint + "/search?dateFrom=1885-01-01&dateTo=1887-01-01").as(BookDTO.class); + assertNotNull(book); + + book = get(endpoint + "/search2?dateFrom=1885-01-01&dateTo=1887-01-01").as(BookDTO.class); + assertNotNull(book); + + // magic query find("{'author'::author,'title'::title}", Parameters.with("author", author).and("title", title)) + book = get(endpoint + "/search2?author=Victor Hugo&title=Notre-Dame de Paris").as(BookDTO.class); + assertNotNull(book); + assertNotNull(book.getId()); + assertNotNull(book.getDetails()); + + //update a book + book.setTitle("Notre-Dame de Paris 2").setTransientDescription("should not be persisted"); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(book) + .put(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + //check that the title has been updated and the transient description ignored + book = get(endpoint + "/" + book.getId().toString()).as(BookDTO.class); + assertEquals("Notre-Dame de Paris 2", book.getTitle()); + Assertions.assertNull(book.getTransientDescription()); + + //delete a book + response = RestAssured + .given() + .delete(endpoint + "/" + book.getId().toString()) + .andReturn(); + assertEquals(204, response.statusCode()); + + list = get(endpoint).as(LIST_OF_BOOK_TYPE_REF); + assertEquals(3, list.size()); + + //test some special characters + list = get(endpoint + "/search/Victor'\\ Hugo").as(LIST_OF_BOOK_TYPE_REF); + assertEquals(0, list.size()); + + //test SSE : there is no JSON serialization for SSE so the test is not very elegant ... + Client client = ClientBuilder.newClient(); + WebTarget target = client.target("http://localhost:8081" + endpoint + "/stream"); + try (SseEventSource source = SseEventSource.target(target).build()) { + final IntegerAdder nbEvent = new IntegerAdder(); + source.register((inboundSseEvent) -> { + try { + BookDTO theBook = objectMapper.readValue(inboundSseEvent.readData(), BookDTO.class); + assertNotNull(theBook); + } catch (IOException e) { + throw new RuntimeException(e); + } + nbEvent.increment(); + }); + source.open(); + Thread.sleep(100);//wait a little for the events to comes in + assertEquals(3, nbEvent.count()); + } + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn(); + Assertions.assertEquals(204, response.statusCode()); + } + + private void callReactivePersonEndpoint(String endpoint) { + RestAssured.defaultParser = Parser.JSON; + RestAssured.config + .objectMapperConfig(new ObjectMapperConfig().jackson2ObjectMapperFactory((type, s) -> new ObjectMapper() + .registerModule(new Jdk8Module()) + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS))); + + List list = get(endpoint).as(LIST_OF_PERSON_TYPE_REF); + assertEquals(0, list.size()); + + Person person1 = new Person(); + person1.id = 1L; + person1.firstname = "John"; + person1.lastname = "Doe"; + Response response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person1) + .post(endpoint) + .andReturn(); + assertEquals(201, response.statusCode()); + + Person person2 = new Person(); + person2.id = 2L; + person2.firstname = "Jane"; + person2.lastname = "Doe"; + Person person3 = new Person(); + person3.id = 3L; + person3.firstname = "Victor"; + person3.lastname = "Hugo"; + List persons = new ArrayList<>(); + persons.add(person2); + persons.add(person3); + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(persons) + .post(endpoint + "/multiple") + .andReturn(); + assertEquals(204, response.statusCode()); + + Person person4 = new Person(); + person4.id = 4L; + person4.firstname = "Charles"; + person4.lastname = "Baudelaire"; + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person4) + .patch(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + list = get(endpoint).as(LIST_OF_PERSON_TYPE_REF); + assertEquals(4, list.size()); + + //with sort + list = get(endpoint + "?sort=firstname").as(LIST_OF_PERSON_TYPE_REF); + assertEquals(4, list.size()); + + //count + Long count = get(endpoint + "/count").as(Long.class); + assertEquals(4, count); + + //update a person + person3.lastname = "Webster"; + response = RestAssured + .given() + .header("Content-Type", "application/json") + .body(person3) + .put(endpoint) + .andReturn(); + assertEquals(202, response.statusCode()); + + //check that the title has been updated + person3 = get(endpoint + "/" + person3.id.toString()).as(Person.class); + assertEquals(3L, person3.id); + assertEquals("Webster", person3.lastname); + + //delete a person + response = RestAssured + .given() + .delete(endpoint + "/" + person3.id.toString()) + .andReturn(); + assertEquals(204, response.statusCode()); + + count = get(endpoint + "/count").as(Long.class); + assertEquals(3, count); + + //delete all + response = RestAssured + .given() + .delete(endpoint) + .andReturn(); + assertEquals(204, response.statusCode()); + + count = get(endpoint + "/count").as(Long.class); + assertEquals(0, count); + } + + private Date yearToDate(int year) { + Calendar cal = new GregorianCalendar(); + cal.set(year, 1, 1); + return cal.getTime(); + } + + private class IntegerAdder { + int cpt = 0; + + public void increment() { + cpt++; + } + + public int count() { + return cpt; + } + } +}