Initial Contribution

Signed-off-by: Jan Supol <jan.supol@oracle.com>
diff --git a/examples/exception-mapping/README.MD b/examples/exception-mapping/README.MD
new file mode 100644
index 0000000..613624d
--- /dev/null
+++ b/examples/exception-mapping/README.MD
@@ -0,0 +1,49 @@
+[//]: # " Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved. "
+[//]: # " "
+[//]: # " This program and the accompanying materials are made available under the "
+[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at "
+[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. "
+[//]: # " "
+[//]: # " SPDX-License-Identifier: BSD-3-Clause "
+
+Exception Mapping Example
+=========================
+
+This example demonstrates how to create a custom exception mapping. There are described several common use-cases such as
+`WebApplicationException`, custom exceptions and an exception mapper inheritance.
+
+The full description how to handle exception and map them to Response object can be found in Jersey User Guide, chapter
+[WebApplicationException and Mapping Exceptions to Responses](https://jersey.java.net/documentation/latest/representations.html#d0e6567).
+
+Contents
+--------
+
+The mapping of the URI path space is presented in the following table:
+
+URI path                                   | HTTP methods  | Allowed Values                              | Description
+-----------------------------------------  | ------------- |-------------------------------------------- | ----------------
+**_/exception_**                           | GET           | ---                                         | returns "ping!"
+**_/exception/webapplication_entity_**     | POST          | two numbers delimited by colons e.g. 1:201  | not handled by `WebApplicationExceptionMapper` (already has an entity in the exception)
+**_/exception/webapplication_noentity_**   | POST          | e.g. 1:201                                  | handled by `WebApplicationExceptionMapper`
+**_/exception/my_**                        | POST          | e.g. 1:201                                  | handled by `MyExceptionMapper`
+**_/exception/mysub_**                     | POST          | e.g. 1:201                                  | handled by `MySubExceptionMapper`
+**_/exception/mysubsub_**                  | POST          | e.g. 1:201                                  | handled by `MySubExceptionMapper`
+**_/exception/request_exception_**         | POST          | String "Request Exception"                  | not reached a resource method, processed by `ContainerRequestFilter`
+**_/exception/response_exception_**        | GET           | ---                                         | response handled and changed by `ContainerResponseFilter`
+
+
+Application is based on Grizzly container (see `App`). Everything needed
+(resources/providers) is registered in `ExceptionResource` and custom exceptions
+along with mappers can be found in `Exceptions` class.
+
+Running the Example
+-------------------
+
+Run the example as follows:
+
+>     mvn clean compile exec:java
+
+This deploys the example using [Grizzly](http://grizzly.java.net/) container.
+
+-   <http://localhost:8080/base/exception>
+-   <http://localhost:8080/base/exception/response_exception>
diff --git a/examples/exception-mapping/pom.xml b/examples/exception-mapping/pom.xml
new file mode 100644
index 0000000..68f418d
--- /dev/null
+++ b/examples/exception-mapping/pom.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.glassfish.jersey.examples</groupId>
+        <artifactId>project</artifactId>
+        <version>2.28-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>exception-mapping</artifactId>
+    <packaging>jar</packaging>
+    <name>jersey-examples-exception-mapping</name>
+
+    <description>Jersey example showing exception mappers in action.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.glassfish.jersey.containers</groupId>
+            <artifactId>jersey-container-grizzly2-http</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.inject</groupId>
+            <artifactId>jersey-hk2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jersey.test-framework.providers</groupId>
+            <artifactId>jersey-test-framework-provider-bundle</artifactId>
+            <type>pom</type>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.codehaus.mojo</groupId>
+                <artifactId>exec-maven-plugin</artifactId>
+                <configuration>
+                    <mainClass>org.glassfish.jersey.examples.exception.App</mainClass>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <profiles>
+        <profile>
+            <id>release</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-assembly-plugin</artifactId>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+
+</project>
diff --git a/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/App.java b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/App.java
new file mode 100644
index 0000000..b520eda
--- /dev/null
+++ b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/App.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
+import org.glassfish.jersey.server.ResourceConfig;
+
+import org.glassfish.grizzly.http.server.HttpServer;
+
+/**
+ * Hello world!
+ */
+public class App {
+
+    private static final URI BASE_URI = URI.create("http://localhost:8080/base/");
+    public static final String ROOT_PATH = "exception";
+
+    public static void main(String[] args) {
+        try {
+            System.out.println("\"Exception Mapping\" Jersey Example App");
+
+            final ResourceConfig resourceConfig = new ResourceConfig(
+                    ExceptionResource.class,
+                    ExceptionResource.MyResponseFilter.class,
+                    ExceptionResource.WebApplicationExceptionFilter.class,
+                    Exceptions.MyExceptionMapper.class,
+                    Exceptions.MySubExceptionMapper.class,
+                    Exceptions.WebApplicationExceptionMapper.class);
+
+            final HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false);
+            Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    server.shutdownNow();
+                }
+            }));
+            server.start();
+
+            System.out.println(String.format(
+                    "Application started.%n"
+                    + "Try out %s%s%n"
+                    + "Stop the application using CTRL+C",
+                    BASE_URI, ROOT_PATH));
+
+            Thread.currentThread().join();
+        } catch (IOException | InterruptedException ex) {
+            Logger.getLogger(App.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+    }
+}
diff --git a/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/ExceptionResource.java b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/ExceptionResource.java
new file mode 100644
index 0000000..e4cfe74
--- /dev/null
+++ b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/ExceptionResource.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import java.io.IOException;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.container.ContainerResponseContext;
+import javax.ws.rs.container.ContainerResponseFilter;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+
+import org.glassfish.jersey.examples.exception.Exceptions.MyException;
+import org.glassfish.jersey.examples.exception.Exceptions.MySubException;
+import org.glassfish.jersey.examples.exception.Exceptions.MySubSubException;
+import org.glassfish.jersey.server.ContainerRequest;
+
+/**
+ * ExceptionResource class.
+ *
+ * @author Santiago.PericasGeertsen at oracle.com
+ */
+@Path("exception")
+@Consumes("text/plain")
+@Produces("text/plain")
+public class ExceptionResource {
+
+    @Provider
+    static class MyResponseFilter implements ContainerResponseFilter {
+
+        @Override
+        public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+            System.out.println("MyResponseFilter.postFilter() enter");
+            responseContext.setEntity(
+                    responseContext.getEntity() + ":" + getClass().getSimpleName(), null, MediaType.TEXT_PLAIN_TYPE);
+            System.out.println("MyResponseFilter.postFilter() exit");
+        }
+    }
+
+    @Provider
+    static class WebApplicationExceptionFilter implements ContainerRequestFilter, ContainerResponseFilter {
+
+        @Override
+        public void filter(ContainerRequestContext context) throws IOException {
+            System.out.println("WebApplicationExceptionFilter.preFilter() enter");
+
+            String path = ((ContainerRequest) context).getRequestUri().getPath();
+            if (path.endsWith("request_exception") && context.hasEntity() && ((ContainerRequest) context)
+                    .readEntity(String.class).equals("Request Exception")) {
+                throw new WebApplicationException(Response.Status.OK);
+            }
+            System.out.println("WebApplicationExceptionFilter.preFilter() exit");
+        }
+
+        @Override
+        public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
+            System.out.println("WebApplicationExceptionFilter.postFilter() enter");
+            if (responseContext.hasEntity() && responseContext.getEntity().equals("Response Exception")) {
+                throw new WebApplicationException(Response.Status.OK);
+            }
+            System.out.println("WebApplicationExceptionFilter.postFilter() exit");
+        }
+    }
+
+    @GET
+    public String pingMe() {
+        return "ping!";
+    }
+
+    @POST
+    @Path("webapplication_entity")
+    public String testWebApplicationExceptionEntity(String s) {
+        String[] tokens = s.split(":");
+        assert tokens.length == 2;
+        int statusCode = Integer.valueOf(tokens[1]);
+        Response r = Response.status(statusCode).entity(s).build();
+        throw new WebApplicationException(r);
+    }
+
+    @POST
+    @Path("webapplication_noentity")
+    public String testWebApplicationExceptionNoEntity(String s) {
+        String[] tokens = s.split(":");
+        assert tokens.length == 2;
+        int statusCode = Integer.valueOf(tokens[1]);
+        Response r = Response.status(statusCode).build();
+        throw new WebApplicationException(r);
+    }
+
+    @POST
+    @Path("my")
+    public String testMyException(String s) {
+        String[] tokens = s.split(":");
+        assert tokens.length == 2;
+        int statusCode = Integer.valueOf(tokens[1]);
+        Response r = Response.status(statusCode).build();
+        throw new MyException(r);
+    }
+
+    @POST
+    @Path("mysub")
+    public String testMySubException(String s) {
+        String[] tokens = s.split(":");
+        assert tokens.length == 2;
+        int statusCode = Integer.valueOf(tokens[1]);
+        Response r = Response.status(statusCode).build();
+        throw new MySubException(r);
+    }
+
+    @POST
+    @Path("mysubsub")
+    public String testMySubSubException(String s) {
+        String[] tokens = s.split(":");
+        assert tokens.length == 2;
+        int statusCode = Integer.valueOf(tokens[1]);
+        Response r = Response.status(statusCode).build();
+        throw new MySubSubException(r);
+    }
+
+    @POST
+    @Path("request_exception")
+    public String exceptionInRequestFilter() {
+        throw new InternalServerErrorException();        // should not reach here
+    }
+
+    @GET
+    @Path("response_exception")
+    public String exceptionInResponseFilter() {
+        return "Response Exception";
+    }
+}
diff --git a/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/Exceptions.java b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/Exceptions.java
new file mode 100644
index 0000000..670bde8
--- /dev/null
+++ b/examples/exception-mapping/src/main/java/org/glassfish/jersey/examples/exception/Exceptions.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Exceptions class.
+ *
+ * @author Santiago.Pericas-Geertsen at oracle.com
+ */
+public class Exceptions {
+
+    // -- Exceptions
+    public static class MyException extends RuntimeException {
+
+        private Response response;
+
+        public MyException(Response response) {
+            this.response = response;
+        }
+
+        public Response getResponse() {
+            return response;
+        }
+    }
+
+    public static class MySubException extends MyException {
+
+        public MySubException(Response response) {
+            super(response);
+        }
+    }
+
+    public static class MySubSubException extends MySubException {
+
+        public MySubSubException(Response response) {
+            super(response);
+        }
+    }
+
+    // -- Exception Mappers
+    @Provider
+    public static class MyExceptionMapper implements ExceptionMapper<MyException> {
+
+        @Override
+        public Response toResponse(MyException exception) {
+            Response r = exception.getResponse();
+            return Response.status(r.getStatus()).entity(
+                    "Code:" + r.getStatus() + ":" + getClass().getSimpleName()).build();
+        }
+    }
+
+    @Provider
+    public static class MySubExceptionMapper implements ExceptionMapper<MySubException> {
+
+        @Override
+        public Response toResponse(MySubException exception) {
+            Response r = exception.getResponse();
+            return Response.status(r.getStatus()).entity(
+                    "Code:" + r.getStatus() + ":" + getClass().getSimpleName()).build();
+        }
+    }
+
+    @Provider
+    public static class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> {
+
+        @Override
+        public Response toResponse(WebApplicationException exception) {
+            Response r = exception.getResponse();
+            return Response.status(r.getStatus()).entity("Code:" + r.getStatus() + ":"
+                    + getClass().getSimpleName()).build();
+        }
+    }
+}
diff --git a/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingFilterTest.java b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingFilterTest.java
new file mode 100644
index 0000000..d509934
--- /dev/null
+++ b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingFilterTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.glassfish.jersey.examples.exception.ExceptionResource.MyResponseFilter;
+import static org.glassfish.jersey.examples.exception.Exceptions.MyExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.MySubExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.WebApplicationExceptionMapper;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * ExceptionMappingFilterTest class.
+ *
+ * @author Santiago.PericasGeertsen at oracle.com
+ */
+public class ExceptionMappingFilterTest extends JerseyTest {
+
+    @Override
+    protected ResourceConfig configure() {
+        // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+        // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+
+        final ResourceConfig resourceConfig = new ResourceConfig(
+                ExceptionResource.class,
+                MyResponseFilter.class,
+                ExceptionResource.WebApplicationExceptionFilter.class,
+                MyExceptionMapper.class,
+                MySubExceptionMapper.class,
+                WebApplicationExceptionMapper.class);
+
+        return resourceConfig;
+    }
+
+    /**
+     * Instructs request filter to throw a WebApplicationException which must be mapped
+     * to a response and processed through response pipeline.
+     */
+    @Test
+    public void testWebApplicationExceptionInRequestFilter() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("request_exception").build());
+        Response r = t.request("text/plain").post(Entity.text("Request Exception"));
+        assertEquals(200, r.getStatus());
+        final String entity = r.readEntity(String.class);
+        System.out.println("entity = " + entity);
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    @Test
+    public void testWebApplicationExceptionInResponseFilter() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("response_exception").build());
+        Response r = t.request("text/plain").get();
+        assertEquals(200, r.getStatus());
+        final String entity = r.readEntity(String.class);
+        System.out.println("entity = " + entity);
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+}
diff --git a/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingTest.java b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingTest.java
new file mode 100644
index 0000000..3166312
--- /dev/null
+++ b/examples/exception-mapping/src/test/java/org/glassfish/jersey/examples/exception/ExceptionMappingTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.exception;
+
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+
+import org.junit.Test;
+import static org.glassfish.jersey.examples.exception.ExceptionResource.MyResponseFilter;
+import static org.glassfish.jersey.examples.exception.Exceptions.MyExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.MySubExceptionMapper;
+import static org.glassfish.jersey.examples.exception.Exceptions.MySubSubException;
+import static org.glassfish.jersey.examples.exception.Exceptions.WebApplicationExceptionMapper;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * ExceptionMappingTest class.
+ *
+ * @author Santiago.PericasGeertsen at oracle.com
+ */
+public class ExceptionMappingTest extends JerseyTest {
+
+    @Override
+    protected ResourceConfig configure() {
+        // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.inmemory.InMemoryTestContainerFactory
+        // mvn test -Djersey.test.containerFactory=org.glassfish.jersey.test.grizzly.GrizzlyTestContainerFactory
+        return new ResourceConfig(
+                ExceptionResource.class,
+                MyResponseFilter.class,
+                MyExceptionMapper.class,
+                MySubExceptionMapper.class,
+                MySubSubException.class,
+                WebApplicationExceptionMapper.class);
+    }
+
+    /**
+     * Ensure we can access resource with response filter installed.
+     */
+    @Test
+    public void testPingAndFilter() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).build());
+        Response r = t.request("text/plain").get();
+        assertEquals(200, r.getStatus());
+        assertTrue(r.readEntity(String.class).contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    /**
+     * No mapper should be used if WebApplicationException already contains a
+     * Response with a non-empty entity.
+     */
+    @Test
+    public void testWebApplicationExceptionWithEntity() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("webapplication_entity").build());
+        Response r = t.request("text/plain").post(Entity.text("Code:200"));
+        assertEquals(200, r.getStatus());
+        final String entity = r.readEntity(String.class);
+        assertTrue(entity.contains("Code:200"));
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    /**
+     * No mapper should be used if WebApplicationException already contains a
+     * Response with a non-empty entity. Same as last test but using 400 code.
+     */
+    @Test
+    public void testWebApplicationExceptionWithEntity400() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("webapplication_entity").build());
+        Response r = t.request("text/plain").post(Entity.text("Code:400"));
+        assertEquals(400, r.getStatus());
+        final String entity = r.readEntity(String.class);
+        assertTrue(entity.contains("Code:400"));
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    /**
+     * WebApplicationExceptionMapper should be used if WebApplicationException contains
+     * empty entity.
+     */
+    @Test
+    public void testWebApplicationExceptionUsingMapper() {
+        WebTarget t = client()
+                .target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("webapplication_noentity").build());
+        Response r = t.request("text/plain").post(Entity.text("Code:200"));
+        assertEquals(200, r.getStatus());
+        String entity = r.readEntity(String.class);
+        assertTrue(entity.contains("Code:200"));
+        assertTrue(entity.contains(WebApplicationExceptionMapper.class.getSimpleName()));
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    /**
+     * MyExceptionMapper should be used if MyException is thrown.
+     */
+    @Test
+    public void testMyException() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("my").build());
+        Response r = t.request("text/plain").post(Entity.text("Code:200"));
+        assertEquals(200, r.getStatus());
+        String entity = r.readEntity(String.class);
+        assertTrue(entity.contains("Code:200"));
+        assertTrue(entity.contains(MyExceptionMapper.class.getSimpleName()));
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    /**
+     * MySubExceptionMapper should be used if MySubException is thrown.
+     */
+    @Test
+    public void testMySubException() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("mysub").build());
+        Response r = t.request("text/plain").post(Entity.text("Code:200"));
+        assertEquals(200, r.getStatus());
+        String entity = r.readEntity(String.class);
+        assertTrue(entity.contains("Code:200"));
+        assertTrue(entity.contains(MySubExceptionMapper.class.getSimpleName()));
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+
+    /**
+     * MySubExceptionMapper should be used if MySubSubException is thrown, given that
+     * there is no mapper for MySubSubException and MySubException is the nearest
+     * super type.
+     */
+    @Test
+    public void testMySubSubException() {
+        WebTarget t = client().target(UriBuilder.fromUri(getBaseUri()).path(App.ROOT_PATH).path("mysub").build());
+        Response r = t.request("text/plain").post(Entity.text("Code:200"));
+        assertEquals(200, r.getStatus());
+        String entity = r.readEntity(String.class);
+        assertTrue(entity.contains("Code:200"));
+        assertTrue(entity.contains(MySubExceptionMapper.class.getSimpleName()));
+        assertTrue(entity.contains(MyResponseFilter.class.getSimpleName()));
+    }
+}