Java Configuration with Jersey @Provider ExceptionMapper Not Working in REST Plugin?

Howdy,

I’m trying to centralize error-handling in my Jira plugin so that I can present a custom error object (rather than throwing a full stack trace to the user).

I can get this working in the ref-app with @Provider on my ExceptionMapper class, however I’m not able to get this registered in my Java-configuration-based app. (for Jira Server 8.1.3)

I’ve tried AutoWiring the application context and also have tried @InjectParam in the constructor directly…but it isn’t working.

AppConfig.java

@Configuration
public class AppConfig {
    ////

    // Makes no difference
    @Bean
    public ExceptionMapper<Throwable> exceptionMapper() {
        return new ExceptionHandler();
    }

    @Bean
    FactoryBean<ServiceRegistration> exportExceptionMapper() {
        ExportOptions serviceProperties = ExportOptions.as(ExceptionMapper.class);
        return exportOsgiService(exceptionMapper(), serviceProperties);
    }

    ////
}

atlassian-plugin.xml

<atlassian-plugin key="${atlassian.plugin.key}" name="${project.name}" plugins-version="2">
    <plugin-info>
        <name>${project.name}</name>
        <description>${project.description}</description>
        <version>${project.version}</version>
        <vendor name="${project.organization.name}" url="${project.organization.url}" />
    </plugin-info>

    <rest key="${atlassian.plugin.key}-1"
          path="/myplugin"
          version="1"
    >
        <description>Provides REST services for this plugin</description>
    </rest>

</atlassian-plugin>

SomeResource.java

@Path("/foo")
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class SomeResource {

    @GET
    @Path("/bar")
    public Response bar() {
        throw new RunTimeException("oh no");
    }

}

ExceptionHandler.java

@Provider
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public class ExceptionHandler implements ExceptionMapper<Throwable> {

    public Response toResponse(Throwable throwable) {
        return Response
                 .status(Response.Status.BAD_REQUEST)
                 .entity("You've been stick-bugged, lol")
                 .build();
    }
}

Not entirely sure how the REST module is supposed to work with Java Configuration anyway? I mean, I don’t even have to register my @Path annotated classes as Beans. (whether I do makes no difference)

Anybody else having this problem?
Anyone found a solution that works for them?

So far have found https://community.atlassian.com/t5/Answers-Developer-Questions/How-can-I-add-a-custom-filter-for-my-rest-resources-requests/qaq-p/554620 helpful.

Declaring a (Jersey, not javax.servlet) filter and annotating my resource with it worked perfectly!

RestErrorFilter.java

import com.sun.jersey.spi.container.*;

public class RestErrorFilter implements ResourceFilter, ContainerRequestFilter, ContainerResponseFilter {

    private final RestExceptionHandler restExceptionHandler = new AppConfig().restExceptionHandler();

    @Override
    public ContainerRequest filter(ContainerRequest request) {
        return request;
    }

    @Override
    public ContainerResponse filter(ContainerRequest request, ContainerResponse containerResponse) {
        Throwable t = containerResponse.getMappedThrowable();

        if(t != null) {
            Response response = new DefaultExceptionMapper().toResponse(t);
            if(t instanceof SomeException) response = new SomeExceptionMapper().toResponse((SomeException) t);

            // some other exceptions here

            containerResponse.setResponse(response);                // Set the response
            containerResponse.setStatus(response.getStatus()); // Set the status
        }
        return containerResponse;
    }

    @Override
    public ContainerRequestFilter getRequestFilter() {
        return this;
    }

    @Override
    public ContainerResponseFilter getResponseFilter() {
        return this;
    }
}

And then in my resource…
SomeResource.java

@Path("...")
@ResourceFilters(RestErrorFilter.class)
@Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
public class DependencyResource {
    //
}

Only thing worth noting is DI still seems a bit wonky.
I actually have a RestExceptionHandler class that I unit-test handles X, Y, Z exceptions, and has the ExceptionMapper instances injected into it.

This would be all fine-and-dandy, but trying to either Autowire it into the RestErrorFilter or declare RestErrorFilter as a @Bean in my @Configuration class causes problems.

So, in my case it just made sense to cut out that class entirely and test the filter directly. :woman_shrugging:

2 Likes