ExtendedExceptionMapperTest.java

/*
 * Copyright (c) 2013, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.glassfish.jersey.tests.e2e.server;

import java.util.logging.Level;
import java.util.logging.LogRecord;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;

import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.LocalizationMessages;
import org.glassfish.jersey.spi.ExtendedExceptionMapper;
import org.glassfish.jersey.test.JerseyTest;
import org.glassfish.jersey.test.TestProperties;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * @author Miroslav Fuksa
 *
 */
public class ExtendedExceptionMapperTest extends JerseyTest {

    @Override
    protected Application configure() {
        set(TestProperties.RECORD_LOG_LEVEL, Level.FINE.intValue());

        return new ResourceConfig(
                TestResource.class,
                ExceptionMapperA.class,
                ExceptionMapperB.class,
                ExceptionMapperC.class,
                ExceptionMapperD.class,
                ExceptionMapperE.class,
                ExceptionMapperX.class
        );
    }

    public static class ExceptionA extends RuntimeException {
        public ExceptionA(String message) {
            super(message);
        }
    }

    public static class ExceptionB extends ExceptionA {
        public ExceptionB(String message) {
            super(message);
        }
    }

    public static class ExceptionC extends ExceptionB {
        public ExceptionC(String message) {
            super(message);
        }
    }

    public static class ExceptionD extends ExceptionC {
        public ExceptionD(String message) {
            super(message);
        }
    }

    public static class ExceptionE extends ExceptionD {
        public ExceptionE(String message) {
            super(message);
        }
    }

    public static class ExceptionX extends RuntimeException {
        public ExceptionX(String message) {
            super(message);
        }
    }


    public static class ExceptionMapperA implements ExtendedExceptionMapper<ExceptionA> {

        @Override
        public boolean isMappable(ExceptionA exception) {
            return exception.getMessage().substring(2).contains("A");
        }

        @Override
        public Response toResponse(ExceptionA exception) {
            return Response.ok("A").build();
        }
    }

    public static class ExceptionMapperB implements ExtendedExceptionMapper<ExceptionB> {

        @Override
        public boolean isMappable(ExceptionB exception) {
            return exception.getMessage().substring(2).contains("B");
        }

        @Override
        public Response toResponse(ExceptionB exception) {
            return Response.ok("B").build();
        }
    }

    public static class ExceptionMapperC implements ExceptionMapper<ExceptionC> {

        @Override
        public Response toResponse(ExceptionC exception) {
            return Response.ok("C").build();
        }
    }

    public static class ExceptionMapperD implements ExtendedExceptionMapper<ExceptionD> {

        @Override
        public boolean isMappable(ExceptionD exception) {
            return exception.getMessage().substring(2).contains("D");
        }

        @Override
        public Response toResponse(ExceptionD exception) {
            return Response.ok("D").build();
        }
    }

    public static class ExceptionMapperE implements ExtendedExceptionMapper<ExceptionE> {

        @Override
        public boolean isMappable(ExceptionE exception) {
            return exception.getMessage().substring(2).contains("E");
        }

        @Override
        public Response toResponse(ExceptionE exception) {
            return Response.ok("E").build();
        }
    }

    public static class ExceptionMapperX implements ExtendedExceptionMapper<ExceptionX> {

        @Override
        public boolean isMappable(ExceptionX exception) {
            return exception.getMessage().substring(2).contains("X");
        }

        @Override
        public Response toResponse(ExceptionX exception) {
            return Response.ok("X").build();
        }
    }

    @Path("resource")
    public static class TestResource {
        @POST
        public String post(String e) {
            if (e.charAt(0) == 'A') {
                throw new ExceptionA(String.valueOf(e));
            } else if (e.charAt(0) == 'B') {
                throw new ExceptionB(String.valueOf(e));
            } else if (e.charAt(0) == 'C') {
                throw new ExceptionC(String.valueOf(e));
            } else if (e.charAt(0) == 'D') {
                throw new ExceptionD(String.valueOf(e));
            } else if (e.charAt(0) == 'E') {
                throw new ExceptionE(String.valueOf(e));
            } else if (e.charAt(0) == 'X') {
                throw new ExceptionX(String.valueOf(e));
            }
            return "get";
        }
    }

    @Test
    public void test() {
        // Format of first param: [exception thrown]-[exception mappers which will return true in isMappable]
        _test("A-A", "A");
        _test("A-AB", "A");
        _test("A-ABC", "A");
        _test("A-ABCD", "A");
        _test("A-ABCDE", "A");
        _test("A-ABCDEX", "A");
        _test("A-C", null);
        _test("A-D", null);
        _test("A-E", null);
        _test("A-D", null);
        _test("A-BCDEX", null);
        _test("A-00000", null);
        _test("A-X", null);


        _test("B-A", "A");
        _test("B-B", "B");
        _test("B-AB", "B");
        _test("B-ABC", "B");
        _test("B-ABCD", "B");
        _test("B-ABCDE", "B");
        _test("B-ABCDEX", "B");
        _test("B-C", null);
        _test("B-D", null);
        _test("B-E", null);
        _test("B-CDEX", null);
        _test("B-X", null);
        _test("B-000", null);

        // C is not an ExtendedExceptionMapper but just ExceptionMapper (always mappable)
        _test("C-C", "C");
        _test("C-A", "C");
        _test("C-AB", "C");
        _test("C-ABC", "C");
        _test("C-AEX", "C");
        _test("C-00000", "C");
        _test("C-ABCDEX", "C");
        _test("C-E", "C");
        _test("C-DE", "C");
        _test("C-D", "C");
        _test("C-X", "C");

        _test("D-000", "C");
        _test("D-A", "C");
        _test("D-B", "C");
        _test("D-C", "C");
        _test("D-D", "D");
        _test("D-E", "C");
        _test("D-ABC", "C");
        _test("D-ABCEX", "C");
        _test("D-ABCDEX", "D");
        _test("D-DE", "D");
        _test("D-ABEX", "C");
        _test("D-AEX", "C");
        _test("D-X", "C");

        _test("E-A", "C");
        _test("E-B", "C");
        _test("E-C", "C");
        _test("E-D", "D");
        _test("E-E", "E");
        _test("E-ABC", "C");
        _test("E-ABCD", "D");
        _test("E-ABCDE", "E");
        _test("E-ABCDEX", "E");
        _test("E-DE", "E");
        _test("E-X", "C");
        _test("E-000000", "C");

        _test("X-A", null);
        _test("X-ABCDE", null);
        _test("X-ABCDEX", "X");
        _test("X-X", "X");

        // Check logs. (??)
        for (final LogRecord logRecord : getLoggedRecords()) {

            // TODO: this test is fragile.
            if (logRecord.getLoggerName().contains("ClientExecutorProvidersConfigurator")) {
                continue;
            }

            for (final String message : new String[]{
                    LocalizationMessages.ERROR_EXCEPTION_MAPPING_ORIGINAL_EXCEPTION(),
                    LocalizationMessages.ERROR_EXCEPTION_MAPPING_THROWN_TO_CONTAINER()}) {

                if (logRecord.getMessage().contains(message) && logRecord.getLevel().intValue() > Level.FINE.intValue()) {
                    fail("Log message should be logged at lower (FINE) level: " + message);
                }
            }
        }
    }

    private void _test(String input, String expectedMapper) {
        final Response response = target("resource").request().post(Entity.entity(input, MediaType.TEXT_PLAIN_TYPE));
        if (expectedMapper == null) {
            Assertions.assertEquals(500, response.getStatus());
        } else {
            Assertions.assertEquals(200, response.getStatus());
            Assertions.assertEquals(expectedMapper, response.readEntity(String.class));
        }
    }


}