Skip to content

Commit

Permalink
Merge pull request quarkusio#15278 from gytis/rest-data-panache-excep…
Browse files Browse the repository at this point in the history
…tion-mapping

Wrap REST Data Panache exceptions for handling
  • Loading branch information
geoand authored Feb 23, 2021
2 parents 53bef6c + 8c76e39 commit 2d1b990
Show file tree
Hide file tree
Showing 16 changed files with 318 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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.
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<RestDataPanacheException> {
@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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,7 +28,6 @@ public final class AddMethodImplementor extends StandardMethodImplementor {
*
* <pre>
* {@code
* &#64;Transactional
* &#64;POST
* &#64;Path("")
* &#64;Consumes({"application/json"})
Expand All @@ -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);
* }
* }
* }
Expand All @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,16 +29,22 @@ public final class DeleteMethodImplementor extends StandardMethodImplementor {
*
* <pre>
* {@code
* &#64;Transactional
* &#64;DELETE
* &#64;Path("{id}")
* &#64;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);
* }
* }
* }
Expand All @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
* }
* }
* }
Expand All @@ -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
Expand All @@ -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();
}

Expand Down
Loading

0 comments on commit 2d1b990

Please sign in to comment.