From 8c76e39060f6b288eaaeeb28bc477b33a972a40f Mon Sep 17 00:00:00 2001 From: Gytis Trikleris Date: Tue, 23 Feb 2021 18:00:29 +0100 Subject: [PATCH] Wrap REST Data Panache exceptions for handling --- .../HibernateOrmPanacheRestProcessor.java | 7 +++ .../deployment/ResourceImplementor.java | 4 ++ .../deployment/AbstractPostMethodTest.java | 10 ++++ .../RestDataPanacheExceptionMapper.java | 48 +++++++++++++++ .../methods/AddMethodImplementor.java | 38 +++++++----- .../methods/DeleteMethodImplementor.java | 35 +++++++---- .../methods/GetMethodImplementor.java | 35 +++++++---- .../methods/ListMethodImplementor.java | 46 +++++++++------ .../methods/StandardMethodImplementor.java | 13 ++++- .../methods/UpdateMethodImplementor.java | 40 +++++++------ .../methods/hal/AddHalMethodImplementor.java | 41 +++++++------ .../methods/hal/GetHalMethodImplementor.java | 36 ++++++++---- .../methods/hal/ListHalMethodImplementor.java | 58 +++++++++++-------- .../hal/UpdateHalMethodImplementor.java | 45 +++++++------- .../deployment/utils/ResponseImplementor.java | 4 ++ .../panache/RestDataPanacheException.java | 8 +++ 16 files changed, 318 insertions(+), 150 deletions(-) create mode 100644 extensions/panache/hibernate-orm-rest-data-panache/runtime/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/runtime/RestDataPanacheExceptionMapper.java create mode 100644 extensions/panache/rest-data-panache/runtime/src/main/java/io/quarkus/rest/data/panache/RestDataPanacheException.java diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/HibernateOrmPanacheRestProcessor.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/HibernateOrmPanacheRestProcessor.java index ba521aa3238f2..d258aa19ace3e 100644 --- a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/HibernateOrmPanacheRestProcessor.java +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/HibernateOrmPanacheRestProcessor.java @@ -20,8 +20,10 @@ import io.quarkus.gizmo.ClassOutput; import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource; import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource; +import io.quarkus.hibernate.orm.rest.data.panache.runtime.RestDataPanacheExceptionMapper; import io.quarkus.rest.data.panache.deployment.ResourceMetadata; import io.quarkus.rest.data.panache.deployment.RestDataResourceBuildItem; +import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; class HibernateOrmPanacheRestProcessor { @@ -36,6 +38,11 @@ FeatureBuildItem feature() { return new FeatureBuildItem(HIBERNATE_ORM_REST_DATA_PANACHE); } + @BuildStep + ResteasyJaxrsProviderBuildItem registerRestDataPanacheExceptionMapper() { + return new ResteasyJaxrsProviderBuildItem(RestDataPanacheExceptionMapper.class.getName()); + } + /** * Find Panache entity resources and generate their implementations. */ diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/ResourceImplementor.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/ResourceImplementor.java index 2eaff72f3d8fd..f86b8c7533432 100644 --- a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/ResourceImplementor.java +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/ResourceImplementor.java @@ -3,6 +3,7 @@ import java.util.List; import javax.enterprise.context.ApplicationScoped; +import javax.transaction.Transactional; import org.jboss.jandex.FieldInfo; import org.jboss.logging.Logger; @@ -89,6 +90,7 @@ private void implementGet(ClassCreator classCreator, DataAccessImplementor dataA private void implementAdd(ClassCreator classCreator, DataAccessImplementor dataAccessImplementor) { MethodCreator methodCreator = classCreator.getMethodCreator("add", Object.class, Object.class); + methodCreator.addAnnotation(Transactional.class); ResultHandle entity = methodCreator.getMethodParam(0); methodCreator.returnValue(dataAccessImplementor.persist(methodCreator, entity)); methodCreator.close(); @@ -97,6 +99,7 @@ private void implementAdd(ClassCreator classCreator, DataAccessImplementor dataA private void implementUpdate(ClassCreator classCreator, DataAccessImplementor dataAccessImplementor, String entityType) { MethodCreator methodCreator = classCreator.getMethodCreator("update", Object.class, Object.class, Object.class); + methodCreator.addAnnotation(Transactional.class); ResultHandle id = methodCreator.getMethodParam(0); ResultHandle entity = methodCreator.getMethodParam(1); // Set entity ID before executing an update to make sure that a requested object ID matches a given entity ID. @@ -107,6 +110,7 @@ private void implementUpdate(ClassCreator classCreator, DataAccessImplementor da private void implementDelete(ClassCreator classCreator, DataAccessImplementor dataAccessImplementor) { MethodCreator methodCreator = classCreator.getMethodCreator("delete", boolean.class, Object.class); + methodCreator.addAnnotation(Transactional.class); ResultHandle id = methodCreator.getMethodParam(0); methodCreator.returnValue(dataAccessImplementor.deleteById(methodCreator, id)); methodCreator.close(); diff --git a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/AbstractPostMethodTest.java b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/AbstractPostMethodTest.java index e618644eecf7e..d1ff22c9bb891 100644 --- a/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/AbstractPostMethodTest.java +++ b/extensions/panache/hibernate-orm-rest-data-panache/deployment/src/test/java/io/quarkus/hibernate/orm/rest/data/panache/deployment/AbstractPostMethodTest.java @@ -60,6 +60,11 @@ void shouldCreateComplexObjects() { .and().body("id", is(equalTo("test-complex"))) .and().body("name", is(equalTo("test collection"))) .and().body("items", is(empty())); + given().accept("application/json") + .and().contentType("application/json") + .and().body("{\"id\": \"test-complex\", \"name\": \"test collection\"}") + .when().post("/collections") + .then().statusCode(409); } @Test @@ -78,5 +83,10 @@ void shouldCreateComplexHalObjects() { .and().body("_links.self.href", endsWith("/collections/test-complex-hal")) .and().body("_links.update.href", endsWith("/collections/test-complex-hal")) .and().body("_links.remove.href", endsWith("/collections/test-complex-hal")); + given().accept("application/hal+json") + .and().contentType("application/json") + .and().body("{\"id\": \"test-complex-hal\", \"name\": \"test collection\"}") + .when().post("/collections") + .then().statusCode(409); } } diff --git a/extensions/panache/hibernate-orm-rest-data-panache/runtime/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/runtime/RestDataPanacheExceptionMapper.java b/extensions/panache/hibernate-orm-rest-data-panache/runtime/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/runtime/RestDataPanacheExceptionMapper.java new file mode 100644 index 0000000000000..41cdd1bb49e74 --- /dev/null +++ b/extensions/panache/hibernate-orm-rest-data-panache/runtime/src/main/java/io/quarkus/hibernate/orm/rest/data/panache/runtime/RestDataPanacheExceptionMapper.java @@ -0,0 +1,48 @@ +package io.quarkus.hibernate.orm.rest.data.panache.runtime; + +import javax.persistence.PersistenceException; +import javax.transaction.RollbackException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; + +import org.hibernate.exception.ConstraintViolationException; + +import io.quarkus.arc.ArcUndeclaredThrowableException; +import io.quarkus.rest.data.panache.RestDataPanacheException; + +public class RestDataPanacheExceptionMapper implements ExceptionMapper { + @Override + public Response toResponse(RestDataPanacheException exception) { + exception.printStackTrace(); + + if (exception.getCause() instanceof ArcUndeclaredThrowableException) { + return toResponse((ArcUndeclaredThrowableException) exception.getCause(), exception.getMessage()); + } + return Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), exception.getMessage()).build(); + } + + private Response toResponse(ArcUndeclaredThrowableException exception, String message) { + if (exception.getCause() instanceof RollbackException) { + return toResponse((RollbackException) exception.getCause(), message); + } + return Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), message).build(); + } + + private Response toResponse(RollbackException exception, String message) { + if (exception.getCause() instanceof PersistenceException) { + return toResponse((PersistenceException) exception.getCause(), message); + } + return Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), message).build(); + } + + private Response toResponse(PersistenceException exception, String message) { + if (exception.getCause() instanceof ConstraintViolationException) { + return toResponse((ConstraintViolationException) exception.getCause(), message); + } + return Response.status(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), message).build(); + } + + private Response toResponse(ConstraintViolationException exception, String message) { + return Response.status(Response.Status.CONFLICT.getStatusCode(), message).build(); + } +} diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java index e7157de74ae6b..e41d2e373f9ba 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/AddMethodImplementor.java @@ -8,6 +8,7 @@ import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import io.quarkus.rest.data.panache.RestDataResource; import io.quarkus.rest.data.panache.deployment.ResourceMetadata; import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; @@ -27,7 +28,6 @@ public final class AddMethodImplementor extends StandardMethodImplementor { * *
      * {@code
-     *     @Transactional
      *     @POST
      *     @Path("")
      *     @Consumes({"application/json"})
@@ -37,15 +37,19 @@ public final class AddMethodImplementor extends StandardMethodImplementor {
      *         entityClassName = "com.example.Entity"
      *     )
      *     public Response add(Entity entityToSave) {
-     *         Entity entity = restDataResource.add(entityToSave);
-     *         String location = new ResourceLinksProvider().getSelfLink(entity);
-     *         if (location != null) {
-     *             ResponseBuilder responseBuilder = Response.status(201);
-     *             responseBuilder.entity(entity);
-     *             responseBuilder.location(URI.create(location));
-     *             return responseBuilder.build();
-     *         } else {
-     *             throw new RuntimeException("Could not extract a new entity URL")
+     *         try {
+     *             Entity entity = restDataResource.add(entityToSave);
+     *             String location = new ResourceLinksProvider().getSelfLink(entity);
+     *             if (location != null) {
+     *                 ResponseBuilder responseBuilder = Response.status(201);
+     *                 responseBuilder.entity(entity);
+     *                 responseBuilder.location(URI.create(location));
+     *                 return responseBuilder.build();
+     *             } else {
+     *                 throw new RuntimeException("Could not extract a new entity URL")
+     *             }
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
      *         }
      *     }
      * }
@@ -54,26 +58,28 @@ public final class AddMethodImplementor extends StandardMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class.getName(),
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getEntityType());
 
         // Add method annotations
         addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
-        addTransactionalAnnotation(methodCreator);
         addPostAnnotation(methodCreator);
         addConsumesAnnotation(methodCreator, APPLICATION_JSON);
         addProducesAnnotation(methodCreator, APPLICATION_JSON);
         addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle entityToSave = methodCreator.getMethodParam(0);
-        ResultHandle entity = methodCreator.invokeVirtualMethod(
+
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to add an entity");
+        ResultHandle entity = tryBlock.invokeVirtualMethod(
                 ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, Object.class, Object.class),
                 resource, entityToSave);
-
         // Return response
-        methodCreator.returnValue(ResponseImplementor.created(methodCreator, entity));
+        tryBlock.returnValue(ResponseImplementor.created(tryBlock, entity));
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java
index 507a73b897419..59222f0d6d818 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/DeleteMethodImplementor.java
@@ -2,11 +2,14 @@
 
 import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
 
+import javax.ws.rs.core.Response;
+
 import io.quarkus.gizmo.BranchResult;
 import io.quarkus.gizmo.ClassCreator;
 import io.quarkus.gizmo.FieldDescriptor;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.gizmo.TryBlock;
 import io.quarkus.rest.data.panache.RestDataResource;
 import io.quarkus.rest.data.panache.deployment.ResourceMetadata;
 import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties;
@@ -26,16 +29,22 @@ public final class DeleteMethodImplementor extends StandardMethodImplementor {
      *
      * 
      * {@code
-     *     @Transactional
      *     @DELETE
      *     @Path("{id}")
      *     @LinkResource(
      *         rel = "remove",
      *         entityClassName = "com.example.Entity"
      *     )
-     *     public void delete(@PathParam("id") ID id) {
-     *         if (!restDataResource.delete(id)) {
-     *             throw new WebApplicationException(404);
+     *     public Response delete(@PathParam("id") ID id) {
+     *         try {
+     *             boolean deleted = restDataResource.delete(id);
+     *             if (deleted) {
+     *                 return Response.noContent().build();
+     *             } else {
+     *                 return Response.status(404).build();
+     *             }
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
      *         }
      *     }
      * }
@@ -44,28 +53,30 @@ public final class DeleteMethodImplementor extends StandardMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, void.class.getName(),
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getIdType());
 
         // Add method annotations
         addPathAnnotation(methodCreator, appendToPath(resourceProperties.getPath(RESOURCE_METHOD_NAME), "{id}"));
-        addTransactionalAnnotation(methodCreator);
         addDeleteAnnotation(methodCreator);
         addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
         addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle id = methodCreator.getMethodParam(0);
-        ResultHandle result = methodCreator.invokeVirtualMethod(
+
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to delete an entity");
+        ResultHandle deleted = tryBlock.invokeVirtualMethod(
                 ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, boolean.class, Object.class),
                 resource, id);
-        BranchResult entityWasDeleted = methodCreator.ifNonZero(result);
 
         // Return response
-        entityWasDeleted.trueBranch().returnValue(null);
-        entityWasDeleted.falseBranch()
-                .throwException(ResponseImplementor.notFoundException(entityWasDeleted.falseBranch()));
+        BranchResult entityWasDeleted = tryBlock.ifNonZero(deleted);
+        entityWasDeleted.trueBranch().returnValue(ResponseImplementor.noContent(entityWasDeleted.trueBranch()));
+        entityWasDeleted.falseBranch().returnValue(ResponseImplementor.notFound(entityWasDeleted.falseBranch()));
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java
index da0c7b29cb6f3..563acd7e2f981 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/GetMethodImplementor.java
@@ -2,11 +2,14 @@
 
 import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
 
+import javax.ws.rs.core.Response;
+
 import io.quarkus.gizmo.BranchResult;
 import io.quarkus.gizmo.ClassCreator;
 import io.quarkus.gizmo.FieldDescriptor;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.gizmo.TryBlock;
 import io.quarkus.rest.data.panache.RestDataResource;
 import io.quarkus.rest.data.panache.deployment.ResourceMetadata;
 import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties;
@@ -33,12 +36,16 @@ public final class GetMethodImplementor extends StandardMethodImplementor {
      *         rel = "self",
      *         entityClassName = "com.example.Entity"
      *     )
-     *     public Entity get(@PathParam("id") ID id) {
-     *         Entity entity = restDataResource.get(id);
-     *         if (entity != null) {
-     *             return entity;
-     *         } else {
-     *             throw new WebApplicationException(404);
+     *     public Response get(@PathParam("id") ID id) {
+     *         try {
+     *             Entity entity = restDataResource.get(id);
+     *             if (entity != null) {
+     *                 return entity;
+     *             } else {
+     *                 return Response.status(404).build();
+     *             }
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
      *         }
      *     }
      * }
@@ -47,7 +54,7 @@ public final class GetMethodImplementor extends StandardMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, resourceMetadata.getEntityType(),
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getIdType());
 
         // Add method annotations
@@ -57,17 +64,21 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
         addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
         addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle id = methodCreator.getMethodParam(0);
-        ResultHandle entity = methodCreator.invokeVirtualMethod(
+
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to get an entity");
+        ResultHandle entity = tryBlock.invokeVirtualMethod(
                 ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, Object.class, Object.class),
                 resource, id);
-        BranchResult entityNotFound = methodCreator.ifNull(entity);
 
         // Return response
-        entityNotFound.trueBranch().throwException(ResponseImplementor.notFoundException(entityNotFound.trueBranch()));
-        entityNotFound.falseBranch().returnValue(entity);
+        BranchResult wasNotFound = tryBlock.ifNull(entity);
+        wasNotFound.trueBranch().returnValue(ResponseImplementor.notFound(wasNotFound.trueBranch()));
+        wasNotFound.falseBranch().returnValue(ResponseImplementor.ok(wasNotFound.falseBranch(), entity));
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java
index b06d5a4afcd9a..c6332488bb75d 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/ListMethodImplementor.java
@@ -13,6 +13,7 @@
 import io.quarkus.gizmo.FieldDescriptor;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.gizmo.TryBlock;
 import io.quarkus.panache.common.Page;
 import io.quarkus.panache.common.Sort;
 import io.quarkus.rest.data.panache.RestDataResource;
@@ -54,12 +55,16 @@ public final class ListMethodImplementor extends StandardMethodImplementor {
      *             @QueryParam("sort") String sortQuery) {
      *         Page page = Page.of(pageIndex, pageSize);
      *         Sort sort = ...; // Build a sort instance by parsing a query param
-     *         List entities = resource.getAll(page, sort);
-     *         // Get the page count, and build first, last, next, previous page instances
-     *         Response.ResponseBuilder responseBuilder = Response.status(200);
-     *         responseBuilder.entity(entities);
-     *         // Add headers with first, last, next and previous page URIs if they exist
-     *         return responseBuilder.build();
+     *         try {
+     *             List entities = resource.getAll(page, sort);
+     *             // Get the page count, and build first, last, next, previous page instances
+     *             Response.ResponseBuilder responseBuilder = Response.status(200);
+     *             responseBuilder.entity(entities);
+     *             // Add headers with first, last, next and previous page URIs if they exist
+     *             return responseBuilder.build();
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
+     *         }
      *     }
      * }
      * 
@@ -99,25 +104,28 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource addContextAnnotation(methodCreator.getParameterAnnotations(3)); ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis()); - - // Invoke resource methods ResultHandle sortQuery = methodCreator.getMethodParam(0); ResultHandle sort = sortImplementor.getSort(methodCreator, sortQuery); ResultHandle pageIndex = methodCreator.getMethodParam(1); ResultHandle pageSize = methodCreator.getMethodParam(2); ResultHandle page = paginationImplementor.getPage(methodCreator, pageIndex, pageSize); - ResultHandle pageCount = methodCreator.invokeVirtualMethod( + ResultHandle uriInfo = methodCreator.getMethodParam(3); + + // Invoke resource methods + TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to list the entities"); + ResultHandle pageCount = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), Constants.PAGE_COUNT_METHOD_PREFIX + RESOURCE_METHOD_NAME, int.class, Page.class), resource, page); - ResultHandle uriInfo = methodCreator.getMethodParam(3); - ResultHandle links = paginationImplementor.getLinks(methodCreator, uriInfo, page, pageCount); - ResultHandle entities = methodCreator.invokeVirtualMethod( + ResultHandle links = paginationImplementor.getLinks(tryBlock, uriInfo, page, pageCount); + ResultHandle entities = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, List.class, Page.class, Sort.class), resource, page, sort); // Return response - methodCreator.returnValue(ResponseImplementor.ok(methodCreator, entities, links)); + tryBlock.returnValue(ResponseImplementor.ok(tryBlock, entities, links)); + + tryBlock.close(); methodCreator.close(); } @@ -132,16 +140,20 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL); addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort"); - // Invoke resource methods ResultHandle sortQuery = methodCreator.getMethodParam(0); ResultHandle sort = sortImplementor.getSort(methodCreator, sortQuery); ResultHandle resource = methodCreator.readInstanceField(resourceFieldDescriptor, methodCreator.getThis()); - ResultHandle entities = methodCreator.invokeVirtualMethod( + + // Invoke resource methods + TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to list the entities"); + ResultHandle entities = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, List.class, Page.class, Sort.class), - resource, methodCreator.loadNull(), sort); + resource, tryBlock.loadNull(), sort); // Return response - methodCreator.returnValue(ResponseImplementor.ok(methodCreator, entities)); + tryBlock.returnValue(ResponseImplementor.ok(tryBlock, entities)); + + tryBlock.close(); methodCreator.close(); } } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java index 3b880c23d53dc..bc2c937f65c19 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/StandardMethodImplementor.java @@ -1,6 +1,5 @@ package io.quarkus.rest.data.panache.deployment.methods; -import javax.transaction.Transactional; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; @@ -17,8 +16,12 @@ import io.quarkus.gizmo.AnnotatedElement; import io.quarkus.gizmo.AnnotationCreator; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.CatchBlockCreator; import io.quarkus.gizmo.ClassCreator; import io.quarkus.gizmo.FieldDescriptor; +import io.quarkus.gizmo.TryBlock; +import io.quarkus.rest.data.panache.RestDataPanacheException; import io.quarkus.rest.data.panache.deployment.ResourceMetadata; import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; import io.quarkus.rest.data.panache.runtime.sort.SortQueryParamValidator; @@ -50,8 +53,12 @@ protected abstract void implementInternal(ClassCreator classCreator, ResourceMet */ protected abstract String getResourceMethodName(); - protected void addTransactionalAnnotation(AnnotatedElement element) { - element.addAnnotation(Transactional.class); + protected TryBlock implementTryBlock(BytecodeCreator bytecodeCreator, String message) { + TryBlock tryBlock = bytecodeCreator.tryBlock(); + CatchBlockCreator catchBlock = tryBlock.addCatch(Throwable.class); + catchBlock.throwException(RestDataPanacheException.class, message, catchBlock.getCaughtException()); + catchBlock.close(); + return tryBlock; } protected void addGetAnnotation(AnnotatedElement element) { diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java index ddecbb9813165..d5ffb4cd0c896 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/UpdateMethodImplementor.java @@ -10,6 +10,7 @@ import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import io.quarkus.rest.data.panache.RestDataResource; import io.quarkus.rest.data.panache.deployment.ResourceMetadata; import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; @@ -32,7 +33,6 @@ public final class UpdateMethodImplementor extends StandardMethodImplementor { * *
      * {@code
-     *     @Transactional
      *     @PUT
      *     @Path("{id}")
      *     @Consumes({"application/json"})
@@ -42,19 +42,23 @@ public final class UpdateMethodImplementor extends StandardMethodImplementor {
      *         entityClassName = "com.example.Entity"
      *     )
      *     public Response update(@PathParam("id") ID id, Entity entityToSave) {
-     *         if (resource.get(id) != null) {
-     *             resource.update(id, entityToSave);
-     *             return Response.status(204).build();
-     *         } else {
-     *             Entity entity = resource.update(id, entityToSave);
-     *             String location = new ResourceLinksProvider().getSelfLink(entity);
-     *             if (location != null) {
-     *                 ResponseBuilder responseBuilder = Response.status(201);
-     *                 responseBuilder.entity(entity);
-     *                 responseBuilder.location(URI.create(location));
-     *                 return responseBuilder.build();
+     *         try {
+     *             if (resource.get(id) != null) {
+     *                 resource.update(id, entityToSave);
+     *                 return Response.status(204).build();
      *             } else {
-     *                 throw new RuntimeException("Could not extract a new entity URL")
+     *                 Entity entity = resource.update(id, entityToSave);
+     *                 String location = new ResourceLinksProvider().getSelfLink(entity);
+     *                 if (location != null) {
+     *                     ResponseBuilder responseBuilder = Response.status(201);
+     *                     responseBuilder.entity(entity);
+     *                     responseBuilder.location(URI.create(location));
+     *                     return responseBuilder.build();
+     *                 } else {
+     *                     throw new RuntimeException("Could not extract a new entity URL")
+     *                 }
+     *             } catch (Throwable t) {
+     *                 throw new RestDataPanacheException(t);
      *             }
      *         }
      *     }
@@ -64,27 +68,29 @@ public final class UpdateMethodImplementor extends StandardMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class.getName(),
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getIdType(), resourceMetadata.getEntityType());
 
         // Add method annotations
         addPathAnnotation(methodCreator,
                 appendToPath(resourceProperties.getPath(RESOURCE_UPDATE_METHOD_NAME), "{id}"));
-        addTransactionalAnnotation(methodCreator);
         addPutAnnotation(methodCreator);
         addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
         addConsumesAnnotation(methodCreator, APPLICATION_JSON);
         addProducesAnnotation(methodCreator, APPLICATION_JSON);
         addLinksAnnotation(methodCreator, resourceMetadata.getEntityType(), REL);
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle id = methodCreator.getMethodParam(0);
         ResultHandle entityToSave = methodCreator.getMethodParam(1);
 
-        BranchResult entityExists = doesEntityExist(methodCreator, resourceMetadata.getResourceClass(), resource, id);
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to update an entity");
+        BranchResult entityExists = doesEntityExist(tryBlock, resourceMetadata.getResourceClass(), resource, id);
         updateAndReturn(entityExists.trueBranch(), resourceMetadata.getResourceClass(), resource, id, entityToSave);
         createAndReturn(entityExists.falseBranch(), resourceMetadata.getResourceClass(), resource, id, entityToSave);
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/AddHalMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/AddHalMethodImplementor.java
index 1e4aac9db311b..fce46c7207827 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/AddHalMethodImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/AddHalMethodImplementor.java
@@ -8,6 +8,7 @@
 import io.quarkus.gizmo.FieldDescriptor;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.gizmo.TryBlock;
 import io.quarkus.rest.data.panache.RestDataResource;
 import io.quarkus.rest.data.panache.deployment.ResourceMetadata;
 import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties;
@@ -25,22 +26,25 @@ public final class AddHalMethodImplementor extends HalMethodImplementor {
      *
      * 
      * {@code
-     *     @Transactional
      *     @POST
      *     @Path("")
      *     @Consumes({"application/json"})
      *     @Produces({"application/hal+json"})
      *     public Response addHal(Entity entityToSave) {
-     *         Entity entity = resource.add(entityToSave);
-     *         HalEntityWrapper wrapper = new HalEntityWrapper(entity);
-     *         String location = new ResourceLinksProvider().getSelfLink(entity);
-     *         if (location != null) {
-     *             ResponseBuilder responseBuilder = Response.status(201);
-     *             responseBuilder.entity(wrapper);
-     *             responseBuilder.location(URI.create(location));
-     *             return responseBuilder.build();
-     *         } else {
-     *             throw new RuntimeException("Could not extract a new entity URL")
+     *         try {
+     *             Entity entity = resource.add(entityToSave);
+     *             HalEntityWrapper wrapper = new HalEntityWrapper(entity);
+     *             String location = new ResourceLinksProvider().getSelfLink(entity);
+     *             if (location != null) {
+     *                 ResponseBuilder responseBuilder = Response.status(201);
+     *                 responseBuilder.entity(wrapper);
+     *                 responseBuilder.location(URI.create(location));
+     *                 return responseBuilder.build();
+     *             } else {
+     *                 throw new RuntimeException("Could not extract a new entity URL");
+     *             }
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
      *         }
      *     }
      * }
@@ -49,26 +53,29 @@ public final class AddHalMethodImplementor extends HalMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class.getName(),
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getEntityType());
 
         // Add method annotations
         addPathAnnotation(methodCreator, resourceProperties.getPath(RESOURCE_METHOD_NAME));
-        addTransactionalAnnotation(methodCreator);
         addPostAnnotation(methodCreator);
         addConsumesAnnotation(methodCreator, APPLICATION_JSON);
         addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON);
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle entityToSave = methodCreator.getMethodParam(0);
-        ResultHandle entity = methodCreator.invokeVirtualMethod(
+
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to add an entity");
+        ResultHandle entity = tryBlock.invokeVirtualMethod(
                 ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, Object.class, Object.class),
                 resource, entityToSave);
 
         // Wrap and return response
-        methodCreator.returnValue(ResponseImplementor.created(methodCreator, wrapHalEntity(methodCreator, entity),
-                ResponseImplementor.getEntityUrl(methodCreator, entity)));
+        tryBlock.returnValue(ResponseImplementor.created(tryBlock, wrapHalEntity(tryBlock, entity),
+                ResponseImplementor.getEntityUrl(tryBlock, entity)));
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/GetHalMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/GetHalMethodImplementor.java
index 671d8f7d212ee..8d457e5d49d44 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/GetHalMethodImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/GetHalMethodImplementor.java
@@ -2,11 +2,14 @@
 
 import static io.quarkus.gizmo.MethodDescriptor.ofMethod;
 
+import javax.ws.rs.core.Response;
+
 import io.quarkus.gizmo.BranchResult;
 import io.quarkus.gizmo.ClassCreator;
 import io.quarkus.gizmo.FieldDescriptor;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.gizmo.TryBlock;
 import io.quarkus.rest.data.panache.RestDataResource;
 import io.quarkus.rest.data.panache.deployment.ResourceMetadata;
 import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties;
@@ -28,12 +31,16 @@ public final class GetHalMethodImplementor extends HalMethodImplementor {
      *     @GET
      *     @Produces({"application/hal+json"})
      *     @Path("{id}")
-     *     public HalEntityWrapper getHal(@PathParam("id") ID id) {
-     *         Entity entity = resource.get(id);
-     *         if (entity != null) {
-     *             return new HalEntityWrapper(entity);
-     *         } else {
-     *             throw new WebApplicationException(404);
+     *     public Response getHal(@PathParam("id") ID id) {
+     *         try {
+     *             Entity entity = resource.get(id);
+     *             if (entity != null) {
+     *                 return Response.ok(new HalEntityWrapper(entity)).build();
+     *             } else {
+     *                 return Response.status(404).build();
+     *             }
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
      *         }
      *     }
      * }
@@ -42,7 +49,7 @@ public final class GetHalMethodImplementor extends HalMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, HalEntityWrapper.class,
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getIdType());
 
         // Add method annotations
@@ -51,17 +58,22 @@ protected void implementInternal(ClassCreator classCreator, ResourceMetadata res
         addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON);
         addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle id = methodCreator.getMethodParam(0);
-        ResultHandle entity = methodCreator.invokeVirtualMethod(
+
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to get an entity");
+        ResultHandle entity = tryBlock.invokeVirtualMethod(
                 ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, Object.class, Object.class),
                 resource, id);
-        BranchResult entityNotFound = methodCreator.ifNull(entity);
 
         // Wrap and return response
-        entityNotFound.trueBranch().throwException(ResponseImplementor.notFoundException(entityNotFound.trueBranch()));
-        entityNotFound.falseBranch().returnValue(wrapHalEntity(entityNotFound.falseBranch(), entity));
+        BranchResult wasNotFound = tryBlock.ifNull(entity);
+        wasNotFound.trueBranch().returnValue(ResponseImplementor.notFound(wasNotFound.trueBranch()));
+        wasNotFound.falseBranch().returnValue(
+                ResponseImplementor.ok(wasNotFound.falseBranch(), wrapHalEntity(wasNotFound.falseBranch(), entity)));
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java
index 18455972f33bf..6f15a3d6b7860 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/ListHalMethodImplementor.java
@@ -14,6 +14,7 @@
 import io.quarkus.gizmo.FieldDescriptor;
 import io.quarkus.gizmo.MethodCreator;
 import io.quarkus.gizmo.ResultHandle;
+import io.quarkus.gizmo.TryBlock;
 import io.quarkus.panache.common.Page;
 import io.quarkus.panache.common.Sort;
 import io.quarkus.rest.data.panache.RestDataResource;
@@ -50,14 +51,18 @@ public final class ListHalMethodImplementor extends HalMethodImplementor {
      *             @QueryParam("sort") String sortQuery) {
      *         Page page = Page.of(pageIndex, pageSize);
      *         Sort sort = ...; // Build a sort instance by parsing a query param
-     *         List entities = resource.getAll(page, sort);
-     *         // Get the page count, and build first, last, next, previous page instances
-     *         HalCollectionWrapper wrapper = new HalCollectionWrapper(entities, Entity.class, "entities");
-     *         // Add first, last, next and previous page URIs to the wrapper if they exist
-     *         Response.ResponseBuilder responseBuilder = Response.status(200);
-     *         responseBuilder.entity(wrapper);
-     *         // Add headers with first, last, next and previous page URIs if they exist
-     *         return responseBuilder.build();
+     *         try {
+     *             List entities = resource.getAll(page, sort);
+     *             // Get the page count, and build first, last, next, previous page instances
+     *             HalCollectionWrapper wrapper = new HalCollectionWrapper(entities, Entity.class, "entities");
+     *             // Add first, last, next and previous page URIs to the wrapper if they exist
+     *             Response.ResponseBuilder responseBuilder = Response.status(200);
+     *             responseBuilder.entity(wrapper);
+     *             // Add headers with first, last, next and previous page URIs if they exist
+     *             return responseBuilder.build();
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
+     *         }
      *    }
      * }
      * 
@@ -80,7 +85,6 @@ protected String getResourceMethodName() { private void implementPaged(ClassCreator classCreator, ResourceMetadata resourceMetadata, ResourceProperties resourceProperties, FieldDescriptor resourceField) { // Method parameters: sort strings, page index, page size, uri info - // TODO could list be an Iterator? MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class, List.class, int.class, int.class, UriInfo.class); @@ -96,36 +100,38 @@ private void implementPaged(ClassCreator classCreator, ResourceMetadata resource addDefaultValueAnnotation(methodCreator.getParameterAnnotations(2), Integer.toString(DEFAULT_PAGE_SIZE)); addContextAnnotation(methodCreator.getParameterAnnotations(3)); - // Invoke resource methods ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis()); - ResultHandle sortQuery = methodCreator.getMethodParam(0); ResultHandle sort = sortImplementor.getSort(methodCreator, sortQuery); ResultHandle pageIndex = methodCreator.getMethodParam(1); ResultHandle pageSize = methodCreator.getMethodParam(2); ResultHandle page = paginationImplementor.getPage(methodCreator, pageIndex, pageSize); - ResultHandle pageCount = methodCreator.invokeVirtualMethod( + ResultHandle uriInfo = methodCreator.getMethodParam(3); + + // Invoke resource methods + TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to list the entities"); + ResultHandle pageCount = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), Constants.PAGE_COUNT_METHOD_PREFIX + RESOURCE_METHOD_NAME, int.class, Page.class), resource, page); - ResultHandle uriInfo = methodCreator.getMethodParam(3); - ResultHandle links = paginationImplementor.getLinks(methodCreator, uriInfo, page, pageCount); - ResultHandle entities = methodCreator.invokeVirtualMethod( + ResultHandle links = paginationImplementor.getLinks(tryBlock, uriInfo, page, pageCount); + ResultHandle entities = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, List.class, Page.class, Sort.class), resource, page, sort); // Wrap and return response - ResultHandle wrapper = wrapHalEntities(methodCreator, entities, resourceMetadata.getEntityType(), + ResultHandle wrapper = wrapHalEntities(tryBlock, entities, resourceMetadata.getEntityType(), resourceProperties.getHalCollectionName()); - methodCreator.invokeVirtualMethod( + tryBlock.invokeVirtualMethod( ofMethod(HalCollectionWrapper.class, "addLinks", void.class, Link[].class), wrapper, links); - methodCreator.returnValue(ResponseImplementor.ok(methodCreator, wrapper, links)); + tryBlock.returnValue(ResponseImplementor.ok(tryBlock, wrapper, links)); + + tryBlock.close(); methodCreator.close(); } private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resourceMetadata, ResourceProperties resourceProperties, FieldDescriptor resourceFieldDescriptor) { - // TODO could list be an Iterator? MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class, List.class); // Add method annotations @@ -134,18 +140,22 @@ private void implementNotPaged(ClassCreator classCreator, ResourceMetadata resou addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON); addQueryParamAnnotation(methodCreator.getParameterAnnotations(0), "sort"); - // Invoke resource methods ResultHandle sortQuery = methodCreator.getMethodParam(0); ResultHandle sort = sortImplementor.getSort(methodCreator, sortQuery); ResultHandle resource = methodCreator.readInstanceField(resourceFieldDescriptor, methodCreator.getThis()); - ResultHandle entities = methodCreator.invokeVirtualMethod( + + // Invoke resource methods + TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to list the entities"); + ResultHandle entities = tryBlock.invokeVirtualMethod( ofMethod(resourceMetadata.getResourceClass(), RESOURCE_METHOD_NAME, List.class, Page.class, Sort.class), - resource, methodCreator.loadNull(), sort); + resource, tryBlock.loadNull(), sort); // Wrap and return response - ResultHandle wrapper = wrapHalEntities(methodCreator, entities, resourceMetadata.getEntityType(), + ResultHandle wrapper = wrapHalEntities(tryBlock, entities, resourceMetadata.getEntityType(), resourceProperties.getHalCollectionName()); - methodCreator.returnValue(ResponseImplementor.ok(methodCreator, wrapper)); + tryBlock.returnValue(ResponseImplementor.ok(tryBlock, wrapper)); + + tryBlock.close(); methodCreator.close(); } } diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/UpdateHalMethodImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/UpdateHalMethodImplementor.java index ab508c867b00f..ee55679932b81 100644 --- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/UpdateHalMethodImplementor.java +++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/methods/hal/UpdateHalMethodImplementor.java @@ -10,6 +10,7 @@ import io.quarkus.gizmo.FieldDescriptor; import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.gizmo.TryBlock; import io.quarkus.rest.data.panache.RestDataResource; import io.quarkus.rest.data.panache.deployment.ResourceMetadata; import io.quarkus.rest.data.panache.deployment.properties.ResourceProperties; @@ -29,27 +30,30 @@ public final class UpdateHalMethodImplementor extends HalMethodImplementor { * *
      * {@code
-     *     @Transactional
      *     @PUT
      *     @Path("{id}")
      *     @Consumes({"application/json"})
      *     @Produces({"application/hal+json"})
      *     public Response updateHal(@PathParam("id") ID id, Entity entityToSave) {
-     *         if (resource.get(id) != null) {
-     *             resource.update(id, entityToSave);
-     *             return Response.status(204).build();
-     *         } else {
-     *             Entity entity = resource.update(id, entityToSave);
-     *             HalEntityWrapper wrapper = new HalEntityWrapper(entity);
-     *             String location = new ResourceLinksProvider().getSelfLink(entity);
-     *             if (location != null) {
-     *                 ResponseBuilder responseBuilder = Response.status(201);
-     *                 responseBuilder.entity(wrapper);
-     *                 responseBuilder.location(URI.create(location));
-     *                 return responseBuilder.build();
+     *         try {
+     *             if (resource.get(id) != null) {
+     *                 resource.update(id, entityToSave);
+     *                 return Response.status(204).build();
      *             } else {
-     *                 throw new RuntimeException("Could not extract a new entity URL")
-     *             }
+     *                 Entity entity = resource.update(id, entityToSave);
+     *                 HalEntityWrapper wrapper = new HalEntityWrapper(entity);
+     *                 String location = new ResourceLinksProvider().getSelfLink(entity);
+     *                 if (location != null) {
+     *                     ResponseBuilder responseBuilder = Response.status(201);
+     *                     responseBuilder.entity(wrapper);
+     *                     responseBuilder.location(URI.create(location));
+     *                     return responseBuilder.build();
+     *                 } else {
+     *                     throw new RuntimeException("Could not extract a new entity URL")
+     *                 }
+     *              }
+     *         } catch (Throwable t) {
+     *             throw new RestDataPanacheException(t);
      *         }
      *     }
      * }
@@ -58,27 +62,28 @@ public final class UpdateHalMethodImplementor extends HalMethodImplementor {
     @Override
     protected void implementInternal(ClassCreator classCreator, ResourceMetadata resourceMetadata,
             ResourceProperties resourceProperties, FieldDescriptor resourceField) {
-        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class.getName(),
+        MethodCreator methodCreator = classCreator.getMethodCreator(METHOD_NAME, Response.class,
                 resourceMetadata.getIdType(), resourceMetadata.getEntityType());
 
         // Add method annotations
         addPathAnnotation(methodCreator,
                 appendToPath(resourceProperties.getPath(RESOURCE_UPDATE_METHOD_NAME), "{id}"));
-        addTransactionalAnnotation(methodCreator);
         addPutAnnotation(methodCreator);
         addPathParamAnnotation(methodCreator.getParameterAnnotations(0), "id");
         addConsumesAnnotation(methodCreator, APPLICATION_JSON);
         addProducesAnnotation(methodCreator, APPLICATION_HAL_JSON);
 
-        // Invoke resource methods
         ResultHandle resource = methodCreator.readInstanceField(resourceField, methodCreator.getThis());
         ResultHandle id = methodCreator.getMethodParam(0);
         ResultHandle entityToSave = methodCreator.getMethodParam(1);
 
-        // Wrap and return response
-        BranchResult entityExists = doesEntityExist(methodCreator, resourceMetadata.getResourceClass(), resource, id);
+        // Invoke resource methods
+        TryBlock tryBlock = implementTryBlock(methodCreator, "Failed to update an entity");
+        BranchResult entityExists = doesEntityExist(tryBlock, resourceMetadata.getResourceClass(), resource, id);
         updateAndReturn(entityExists.trueBranch(), resourceMetadata.getResourceClass(), resource, id, entityToSave);
         createAndReturn(entityExists.falseBranch(), resourceMetadata.getResourceClass(), resource, id, entityToSave);
+
+        tryBlock.close();
         methodCreator.close();
     }
 
diff --git a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java
index 0c1d072511c08..a3cd37068c1f0 100644
--- a/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java
+++ b/extensions/panache/rest-data-panache/deployment/src/main/java/io/quarkus/rest/data/panache/deployment/utils/ResponseImplementor.java
@@ -55,6 +55,10 @@ public static ResultHandle noContent(BytecodeCreator creator) {
         return status(creator, Response.Status.NO_CONTENT.getStatusCode());
     }
 
+    public static ResultHandle notFound(BytecodeCreator creator) {
+        return status(creator, Response.Status.NOT_FOUND.getStatusCode());
+    }
+
     public static ResultHandle notFoundException(BytecodeCreator creator) {
         return creator.newInstance(MethodDescriptor.ofConstructor(WebApplicationException.class, int.class),
                 creator.load(Response.Status.NOT_FOUND.getStatusCode()));
diff --git a/extensions/panache/rest-data-panache/runtime/src/main/java/io/quarkus/rest/data/panache/RestDataPanacheException.java b/extensions/panache/rest-data-panache/runtime/src/main/java/io/quarkus/rest/data/panache/RestDataPanacheException.java
new file mode 100644
index 0000000000000..454f6bf50a5aa
--- /dev/null
+++ b/extensions/panache/rest-data-panache/runtime/src/main/java/io/quarkus/rest/data/panache/RestDataPanacheException.java
@@ -0,0 +1,8 @@
+package io.quarkus.rest.data.panache;
+
+public class RestDataPanacheException extends RuntimeException {
+
+    public RestDataPanacheException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}