Skip to content

Commit

Permalink
Make disableDefaultExceptionMapperRegistration public in StandardExce…
Browse files Browse the repository at this point in the history
…ptionMappers (#484)

* Make disableDefaultExceptionMapperRegistration public
* Directly call setter method if argument is an AbsractServerFactory
* Enhance exception methods when reflective invocation fails
* Add/update javadocs

Closes #483
  • Loading branch information
sleberknight authored Apr 30, 2024
1 parent 2b6e2ae commit 54332b4
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.kiwiproject.base.KiwiStrings.format;

import com.google.common.annotations.VisibleForTesting;
import io.dropwizard.core.server.AbstractServerFactory;
import io.dropwizard.core.server.ServerFactory;
import io.dropwizard.core.setup.Environment;
import lombok.experimental.UtilityClass;
Expand All @@ -29,9 +30,13 @@ public class StandardExceptionMappers {

/**
* Registers the "standard" set of exception mappers.
* <p>
* This uses {@link #disableDefaultExceptionMapperRegistration(ServerFactory)} to prevent
* Dropwizard from registering any of its exception mappers.
*
* @param serverFactory the serverFactory so that the default exception mappers can be disabled
* @param environment the Dropwizard environment
* @see #disableDefaultExceptionMapperRegistration(ServerFactory)
*/
public static void register(ServerFactory serverFactory, Environment environment) {
var jersey = environment.jersey();
Expand All @@ -56,10 +61,37 @@ public static void register(ServerFactory serverFactory, Environment environment
jersey.register(new JerseyViolationExceptionMapper());
}

private static void disableDefaultExceptionMapperRegistration(ServerFactory serverFactory) {
/**
* Disable registration of Dropwizard exception mappers if the {@link ServerFactory} supports it
* via a {@code setRegisterDefaultExceptionMappers} method which accepts a {@link Boolean} (the
* wrapper type, not a primitive {@code boolean}).
* <p>
* Both Dropwizard implementations, {@link io.dropwizard.core.server.DefaultServerFactory DefaultServerFactory}
* and {@link io.dropwizard.core.server.SimpleServerFactory SimpleServerFactory}, support this option
* since they extend {@link io.dropwizard.core.server.AbstractServerFactory AbstractServerFactory}.
* <p>
* This should only be used if you do not want any of Dropwizard's default exception mappers to be
* registered. The {@link io.dropwizard.core.setup.ExceptionMapperBinder ExceptionMapperBinder} registers
* Dropwizard's default set of exception mappers.
* <p>
* Also see
* <a href="https://www.dropwizard.io/en/stable/manual/core.html#overriding-default-exception-mappers">
* Overriding Default Exception Mappers
* </a>
* in the Dropwizard reference manual.
*
* @param serverFactory
* @see io.dropwizard.core.setup.ExceptionMapperBinder
* @see io.dropwizard.core.server.AbstractServerFactory#setRegisterDefaultExceptionMappers(Boolean)
*/
public static void disableDefaultExceptionMapperRegistration(ServerFactory serverFactory) {
LOG.info("Disabling Dropwizard registration of default exception mappers");
var methodHandle = findRegistrationSetter(serverFactory);
invokeRegistrationSetter(methodHandle, serverFactory);
if (serverFactory instanceof AbstractServerFactory baseFactory) {
baseFactory.setRegisterDefaultExceptionMappers(false);
} else {
var methodHandle = findRegistrationSetter(serverFactory);
invokeRegistrationSetter(methodHandle, serverFactory);
}
}

@VisibleForTesting
Expand All @@ -69,7 +101,7 @@ static MethodHandle findRegistrationSetter(ServerFactory serverFactory) {
REGISTER_DEFAULT_EXCEPTION_MAPPERS_SETTER, methodType(Void.TYPE, Boolean.class));
} catch (NoSuchMethodException | IllegalAccessException ex) {
throw new IllegalStateException(
format("ServerFactory class ({}) must respond to '{}' to disable default exception mapper registration!",
format("ServerFactory class ({}) must respond to '{}(Boolean)' to disable default exception mapper registration!",
serverFactory.getClass(), REGISTER_DEFAULT_EXCEPTION_MAPPERS_SETTER),
ex);
}
Expand All @@ -81,7 +113,7 @@ static void invokeRegistrationSetter(MethodHandle methodHandle, ServerFactory se
methodHandle.invoke(serverFactory, Boolean.FALSE);
} catch (Throwable throwable) {
throw new IllegalStateException(
format("Unable to invoke '{}' using handle {} on {}. Cannot disable default exception mapper registration!",
format("Unable to invoke '{}(Boolean.FALSE)' using handle {} on {}. Cannot disable default exception mapper registration!",
REGISTER_DEFAULT_EXCEPTION_MAPPERS_SETTER, methodHandle, serverFactory),
throwable);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package org.kiwiproject.dropwizard.util.exception;

import static java.util.Objects.nonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import io.dropwizard.core.server.AbstractServerFactory;
import io.dropwizard.core.server.DefaultServerFactory;
import io.dropwizard.core.server.ServerFactory;
import io.dropwizard.core.server.SimpleServerFactory;
import io.dropwizard.core.setup.Environment;
import org.eclipse.jetty.server.Server;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -62,6 +67,117 @@ public void setRegisterDefaultExceptionMappers(Boolean registerDefaultExceptionM
}
}

@Nested
class DisableDefaultExceptionMapperRegistration {

@Test
void shouldDisableForDefaultServerFactory() {
var serverFactory = checkAbstractServerFactoryPrecondition(new DefaultServerFactory());

StandardExceptionMappers.disableDefaultExceptionMapperRegistration(serverFactory);

assertThat(serverFactory.getRegisterDefaultExceptionMappers()).isFalse();
}

@Test
void shouldDisableForSimpleServerFactory() {
var serverFactory = checkAbstractServerFactoryPrecondition(new SimpleServerFactory());

StandardExceptionMappers.disableDefaultExceptionMapperRegistration(serverFactory);

assertThat(serverFactory.getRegisterDefaultExceptionMappers()).isFalse();
}

private static AbstractServerFactory checkAbstractServerFactoryPrecondition(AbstractServerFactory serverFactory) {
assertThat(serverFactory.getRegisterDefaultExceptionMappers())
.describedAs("precondition failed: expected registerDefaultExceptionMappers=true")
.isTrue();

return serverFactory;
}

@Test
void shouldDisableForCustomServerFactory_WhichSupportsDisabling() {
var serverFactory = new SupportedCustomServerFactory();

StandardExceptionMappers.disableDefaultExceptionMapperRegistration(serverFactory);

assertAll(
() -> assertThat(serverFactory.registerDefaultExceptionMappersCalled).isTrue(),
() -> assertThat(serverFactory.argumentHadCorrectValue).isTrue()
);
}

@Test
void shouldThrowIllegalState_IfServerFactoryDoesNotSupportDisabling() {
var serverFactory = new UnsupportedCustomServerFactory();

assertThatIllegalStateException()
.isThrownBy(() -> StandardExceptionMappers.disableDefaultExceptionMapperRegistration(serverFactory));
}

@Test
void shouldThrowIllegalState_IfServerFactoryHasCorrectlyNamedMethodThatAcceptsPrimitiveBoolean() {
var serverFactory = new UnsupportedPrimitiveBooleanCustomServerFactory();

assertThatIllegalStateException()
.isThrownBy(() -> StandardExceptionMappers.disableDefaultExceptionMapperRegistration(serverFactory));
}
}

public static class SupportedCustomServerFactory implements ServerFactory {

boolean registerDefaultExceptionMappersCalled;
boolean argumentHadCorrectValue;

public void setRegisterDefaultExceptionMappers(Boolean registerDefaultExceptionMappers) {
registerDefaultExceptionMappersCalled = true;
argumentHadCorrectValue = nonNull(registerDefaultExceptionMappers) && !registerDefaultExceptionMappers;
}

@Override
public Server build(Environment environment) {
throw new UnsupportedOperationException("Should never be called by tests");
}

@Override
public void configure(Environment environment) {
throw new UnsupportedOperationException("Should never be called by tests");
}
}

public static class UnsupportedCustomServerFactory implements ServerFactory {

@Override
public Server build(Environment environment) {
throw new UnsupportedOperationException("Should never be called by tests");
}

@Override
public void configure(Environment environment) {
throw new UnsupportedOperationException("Should never be called by tests");
}
}

public static class UnsupportedPrimitiveBooleanCustomServerFactory implements ServerFactory {

boolean registerDefaultExceptionMappersCalled;

public void setRegisterDefaultExceptionMappers(boolean registerDefaultExceptionMappers) {
registerDefaultExceptionMappersCalled = true;
}

@Override
public Server build(Environment environment) {
throw new UnsupportedOperationException("Should never be called by tests");
}

@Override
public void configure(Environment environment) {
throw new UnsupportedOperationException("Should never be called by tests");
}
}

@Nested
class FindRegistrationSetter {

Expand All @@ -81,7 +197,7 @@ public void configure(Environment environment) {
assertThatThrownBy(() -> StandardExceptionMappers.findRegistrationSetter(serverFactory))
.isExactlyInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("ServerFactory class")
.hasMessageEndingWith("must respond to 'setRegisterDefaultExceptionMappers' to disable default exception mapper registration!");
.hasMessageEndingWith("must respond to 'setRegisterDefaultExceptionMappers(Boolean)' to disable default exception mapper registration!");
}

@Test
Expand Down Expand Up @@ -113,7 +229,7 @@ public void setRegisterDefaultExceptionMappers(Boolean registerDefaultExceptionM
MethodHandle registrationSetter = StandardExceptionMappers.findRegistrationSetter(serverFactory);
assertThatThrownBy(() -> StandardExceptionMappers.invokeRegistrationSetter(registrationSetter, serverFactory))
.isExactlyInstanceOf(IllegalStateException.class)
.hasMessageStartingWith("Unable to invoke 'setRegisterDefaultExceptionMappers' using handle")
.hasMessageStartingWith("Unable to invoke 'setRegisterDefaultExceptionMappers(Boolean.FALSE)' using handle")
.hasMessageEndingWith("Cannot disable default exception mapper registration!");
}

Expand Down

0 comments on commit 54332b4

Please sign in to comment.