Skip to content

Commit

Permalink
Merge pull request #612 from DavideD/quickstart-refact
Browse files Browse the repository at this point in the history
Use Mutiny event-based API in the Hibernate Reactive quickstart
  • Loading branch information
gsmet authored Jul 15, 2020
2 parents 8a36c36 + f2abc7d commit 06ef27c
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@Entity
@Table(name = "known_fruits")
@NamedQuery(name = "Fruits.findAll", query = "SELECT f FROM Fruit f ORDER BY f.name", hints = @QueryHint(name = "org.hibernate.cacheable", value = "true"))
@NamedQuery(name = "Fruits.findAll", query = "SELECT f FROM Fruit f ORDER BY f.name")
public class Fruit {

@Id
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package org.acme.hibernate.reactive;

import java.util.List;
import java.util.function.Function;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
Expand All @@ -11,79 +15,134 @@
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.hibernate.reactive.mutiny.Mutiny;

import org.jboss.logging.Logger;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;

@Path("fruits")
@ApplicationScoped
@Produces("application/json")
@Consumes("application/json")
public class FruitMutinyResource {
private static final Logger LOGGER = Logger.getLogger(FruitMutinyResource.class.getName());

@Inject
Uni<Mutiny.Session> mutinySession;
Mutiny.Session mutinySession;

@GET
public Uni<Fruit[]> get() {
return mutinySession.flatMap( session -> session.createNamedQuery( "Fruits.findAll", Fruit.class ).getResultList() )
.map( fruits -> fruits.toArray( new Fruit[fruits.size()] ) );
public Multi<Fruit> get() {
return mutinySession
.createNamedQuery( "Fruits.findAll", Fruit.class ).getResults();
}

@GET
@Path("{id}")
public Uni<Fruit> getSingle(@PathParam Integer id) {
return mutinySession
.flatMap( session -> session.find( Fruit.class, id ) );
return mutinySession.find(Fruit.class, id);
}

@POST
public Uni<Response> create(Fruit fruit) {
if (fruit.getId() != null) {
if (fruit == null || fruit.getId() != null) {
throw new WebApplicationException("Id was invalidly set on request.", 422);
}

return mutinySession
.flatMap( session -> session.persist( fruit ) )
.flatMap( session -> session.flush() )
.map( ignore -> Response.ok( fruit ).status( 201 ).build() );
.persist(fruit)
.onItem().produceUni(session -> mutinySession.flush())
.onItem().apply(ignore -> Response.ok(fruit).status(201).build());
}

@PUT
@Path("{id}")
public Uni<Fruit> update(@PathParam Integer id, Fruit fruit) {
if (fruit.getName() == null) {
throw new WebApplicationException("Fruit Name was not set on request.", 422);
public Uni<Response> update(@PathParam Integer id, Fruit fruit) {
if (fruit == null || fruit.getName() == null) {
throw new WebApplicationException("Fruit name was not set on request.", 422);
}

// Update function (never returns null)
Function<Fruit, Uni<Response>> update = entity -> {
entity.setName(fruit.getName());
return mutinySession.flush()
.onItem().apply(ignore -> Response.ok(entity).build());
};

return mutinySession
.flatMap( session -> session.find( Fruit.class, id )
.flatMap( entity -> {
if (entity == null) {
throw new WebApplicationException("Fruit with id of " + id + " does not exist.", 404);
}
entity.setName( fruit.getName() );
return session.flush()
.map( ignore -> entity );
} )
);
.find( Fruit.class, id )
// If entity exists then
.onItem().ifNotNull()
.produceUni(update)
// else
.onItem().ifNull()
.continueWith(Response.ok().status(404).build());
}

@DELETE
@Path("{id}")
public Uni<Response> delete(@PathParam Integer id) {
// Delete function (never returns null)
Function<Fruit, Uni<Response>> delete = entity -> mutinySession.remove(entity)
.onItem().produceUni(ignore -> mutinySession.flush())
.onItem().apply(ignore -> Response.ok().status(204).build());

return mutinySession
.flatMap( session -> session.find( Fruit.class, id )
.flatMap( entity -> {
if ( entity == null ) {
throw new WebApplicationException( "Fruit with id of " + id + " does not exist.", 404 );
}
return session.remove( entity );
} )
)
.flatMap( session -> session.flush() )
.map( ignore -> Response.ok().status( 204 ).build() );
.find( Fruit.class, id )
// If entity exists then
.onItem().ifNotNull()
.produceUni(delete)
// else
.onItem().ifNull()
.continueWith(Response.ok().status(404).build());
}

/**
* Create a HTTP response from an exception.
*
* Response Example:
*
* <pre>
* HTTP/1.1 422 Unprocessable Entity
* Content-Length: 111
* Content-Type: application/json
*
* {
* "code": 422,
* "error": "Fruit name was not set on request.",
* "exceptionType": "javax.ws.rs.WebApplicationException"
* }
* </pre>
*/
@Provider
public static class ErrorMapper implements ExceptionMapper<Exception> {

@Override
public Response toResponse(Exception exception) {
LOGGER.error("Failed to handle request", exception);

int code = 500;
if (exception instanceof WebApplicationException) {
code = ((WebApplicationException) exception).getResponse().getStatus();
}

JsonObjectBuilder entityBuilder = Json.createObjectBuilder()
.add("exceptionType", exception.getClass().getName())
.add("code", code);

if (exception.getMessage() != null) {
entityBuilder.add("error", exception.getMessage());
}

return Response.status(code)
.entity(entityBuilder.build())
.build();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.text.IsEmptyString.emptyString;

@QuarkusTest
public class FruitsEndpointTest {
Expand All @@ -15,53 +16,116 @@ public class FruitsEndpointTest {
public void testListAllFruits() {
//List all, should have all 3 fruits the database has initially:
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
containsString("Cherry"),
containsString("Apple"),
containsString("Banana"));
.when()
.get("/fruits")
.then()
.statusCode(200)
.body(
containsString("Cherry"),
containsString("Apple"),
containsString("Banana"));

//Delete the Cherry:
// Update Cherry to Pineapple
given()
.when().delete("/fruits/1")
.then()
.statusCode(204);
.when()
.body("{\"name\" : \"Pineapple\"}")
.contentType("application/json")
.put("/fruits/1")
.then()
.statusCode(200)
.body(
containsString("\"id\":"),
containsString("\"name\":\"Pineapple\""));

//List all, cherry should be missing now:
//List all, Pineapple should've replaced Cherry:
given()
.when().get("/fruits")
.then()
.when()
.get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString( "Cherry" )),
containsString("Pineapple"),
containsString("Apple"),
containsString("Banana"));

//Delete Pineapple:
given()
.when()
.delete("/fruits/1")
.then()
.statusCode(204);

//List all, Pineapple should be missing now:
given()
.when()
.get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Cherry")),
containsString("Apple"),
containsString("Banana"));
not(containsString( "Pineapple")),
containsString("Apple"),
containsString("Banana"));

//Create the Pear:
given()
.when()
.body("{\"name\" : \"Pear\"}")
.contentType("application/json")
.post("/fruits")
.then()
.statusCode(201)
.body(
containsString("\"id\":"),
containsString("\"name\":\"Pear\""));
;
.when()
.body("{\"name\" : \"Pear\"}")
.contentType("application/json")
.post("/fruits")
.then()
.statusCode(201)
.body(
containsString("\"id\":"),
containsString("\"name\":\"Pear\""));

//List all, cherry should be missing now:
//List all, Pineapple should be still missing now:
given()
.when().get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Cherry")),
containsString("Apple"),
containsString("Banana"),
containsString("Pear"));
.when()
.get("/fruits")
.then()
.statusCode(200)
.body(
not(containsString("Pineapple")),
containsString("Apple"),
containsString("Banana"),
containsString("Pear"));
}

@Test
public void testEntityNotFoundForDelete() {
given()
.when()
.delete("/fruits/9236")
.then()
.statusCode(404)
.body(emptyString());
}

@Test
public void testEntityNotFoundForUpdate() {
given()
.when()
.body("{\"name\" : \"Watermelon\"}")
.contentType("application/json")
.put("/fruits/32432")
.then()
.statusCode(404)
.body(emptyString());
}

@Test
public void testMissingNameForUpdate() {
given()
.when()
.contentType("application/json")
.put("/fruits/3")
.then()
.statusCode(422)
.body(
containsString("\"code\":422"),
containsString("\"error\":\"Fruit name was not set on request.\""),
containsString("\"exceptionType\":\"javax.ws.rs.WebApplicationException\"")
);
}
}

0 comments on commit 06ef27c

Please sign in to comment.