zodi91
zodi91

Reputation: 150

Exception Handling/Mapping for a particular class

I have resource class which itself's talks with a internal service. This resource acts a rest API for the service. The service layer can throw unexpected exceptions, thus the resource should handle those handled unexpected exceptions and log it. I am using dropwizard framework which in turns use jersey. It goes like this.

@PATH(/user)
@GET
public Response getUser(@QueryParam("id") String userId) {
   assertNotNull(userId);
   try {
      User user = service.getUser(userId);
      return Response.ok(user).build();
   }
   catch (MyOwnException moe) { //basically 400's
     return Response.status(400).entity(moe.getMsg()).build();
   }
   catch (Exception e) {   //unexpected exceptions

      logger.debug(e.getMessage);
      return Response.status(500).entity(moe.getMsg()).build();
   }
}

The problem here is that i have to do this exact same exception handling for each REST api endpoint. Can i do some kind of exception mapping for this particular resource so that i can put all the handling logic and logging there? I know i can build a mapper for an particular exception in jersey, but that is for the whole module not a single class.

Upvotes: 2

Views: 2813

Answers (2)

zyexal
zyexal

Reputation: 1579

Afaig you can't register an ExceptionMapper to a resource method. I've tried this by implementing a DynamicFeature which was looking for a custom Annotation and then tried to register a custom ExceptionMapper with the FeatureContext.

The result was disillusioning:
WARNING: The given contract (interface javax.ws.rs.ext.ExceptionMapper) of class path.to.CustomExceptionMapper provider cannot be bound to a resource method.

Might not work:
But...

For a resource class this is in fact easy. Just register your ExceptionMapper for your resource class within your ResourceConfig. For me it looks like:

@ApplicationPath("/")
public class ApplicationResourceConfig extends ResourceConfig {
    public ApplicationResourceConfig() {
        // [...]
        register(YourExceptionMapper.class, YourResource.class);
        // [...]
    }
}

So if you are okay with having this on resource class level, do it like this.

Otherwise you might need to use Aspects (but I don't see any reasons to do so). Example:

Aspect

@Aspect
public class ResourceAspect {

    Logger logger = [...]

    private static final String RESOURCE = "execution(public !static javax.ws.rs.core.Response path.to.resources..*(..)) && @annotation(path.to.HandleMyOwnException)";

    @Around(RESOURCE)
    public Object translateRuntimeException(ProceedingJoinPoint p) throws Throwable {

        try {
            return p.proceed();
        } catch (MyOwnException moe) {
            return Response.status(400).entity(moe.getMsg()).build();
        } catch (Exception e) {   //unexpected exceptions
            logger.debug(e.getMessage);
            return Response.status(500).entity(e.getMessage()).build();
        }
    }

}

Please notice, the RESOURCE config. Here it works for none static methods under path.to.resources which returning Response and are anntotated with the HandleMyOwnException annotation.

HandleMyOwnException

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HandleMyOwnException {}

ResourceMethod

@GET
@PATH("/user")
@HandleMyOwnException
public Response getUser(@QueryParam("id") String userId) {
   assertNotNull(userId);
   return Response.ok(service.getUser(userId)).build();
}

pom.xml

<!-- deps -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.2</version> <!-- or newer version -->
</dependency>

<!-- build plugins -->
<plugins>
    <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>aspectj-maven-plugin</artifactId>
        <version>1.7</version>
        <configuration>
            <complianceLevel>1.8</complianceLevel>
            <showWeaveInfo>true</showWeaveInfo>
        </configuration>
        <executions>
            <execution>
                <goals>
                    <goal>compile</goal>
                </goals>
            </execution>
        </executions>
    </plugin>
</plugins>
<pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.eclipse.m2e</groupId>
            <artifactId>lifecycle-mapping</artifactId>
            <version>1.0.0</version>
            <configuration>
                <lifecycleMappingMetadata>
                    <pluginExecutions>
                        <pluginExecution>
                            <pluginExecutionFilter>
                                <groupId>org.codehaus.mojo</groupId>
                                <artifactId>aspectj-maven-plugin</artifactId>
                                <versionRange>[1.7,)</versionRange>
                                <goals>
                                    <goal>compile</goal>
                                </goals>
                            </pluginExecutionFilter>
                            <action>
                                <ignore></ignore>
                            </action>
                        </pluginExecution>
                    </pluginExecutions>
                </lifecycleMappingMetadata>
            </configuration>
        </plugin>
    <plugins>
<pluginManagement>

Have a nice day!

EDITED

~ Added more complete pom.xml config
~ Corrected missing path for Annotation in ResourceAspect

Upvotes: 4

JB Nizet
JB Nizet

Reputation: 691943

Why not just factor out the exception handling into a private method?

@PATH(/user)
@GET
public Response getUser(@QueryParam("id") String userId) {
    assertNotNull(userId);
    return handleExceptions(() -> {
        User user = service.getUser(userId);
        return Response.ok(user).build();
    });
}

private Response handleExceptions(Callable<Response> callable) {
    try {
        return callable.call();
    }
    catch (MyOwnException moe) { //basically 400's
        return Response.status(400).entity(moe.getMsg()).build();
    }
    catch (Exception e) {   //unexpected exceptions
        logger.debug(e.getMessage);
        return Response.status(500).entity(e.getMessage()).build();
    }
}

Upvotes: 3

Related Questions