Skip to content

Commit

Permalink
Add mappedHandler Predicate to AbstractHandlerExceptionResolver
Browse files Browse the repository at this point in the history
Closes gh-26772
  • Loading branch information
rstoyanchev committed Dec 7, 2023
1 parent 7534090 commit dd23b1d
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.web.servlet.handler;

import java.util.Set;
import java.util.function.Predicate;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -53,6 +54,9 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti

private int order = Ordered.LOWEST_PRECEDENCE;

@Nullable
private Predicate<Object> mappedHandlerPredicate;

@Nullable
private Set<?> mappedHandlers;

Expand All @@ -74,33 +78,43 @@ public int getOrder() {
return this.order;
}

/**
* Use a {@code Predicate} to determine which handlers this exception
* resolver applies to, including when the request was not mapped in which
* case the handler is {@code null}.
* <p>If no handler predicate, nor handlers, nor handler classes are set,
* the exception resolver applies to all handlers.
* @since 6.1.2
*/
public void setMappedHandlerPredicate(Predicate<Object> predicate) {
this.mappedHandlerPredicate =
(this.mappedHandlerPredicate != null ? this.mappedHandlerPredicate.and(predicate) : predicate);
}

/**
* Specify the set of handlers that this exception resolver should apply to.
* <p>The exception mappings and the default error view will only apply to the specified handlers.
* <p>If no handlers or handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used
* as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
* ignored in this case.
* <p>If no handler predicate, nor handlers, nor handler classes are set,
* the exception resolver applies to all handlers.
* @see #setMappedHandlerPredicate(Predicate)
*/
public void setMappedHandlers(Set<?> mappedHandlers) {
this.mappedHandlers = mappedHandlers;
}

/**
* Specify the set of classes that this exception resolver should apply to.
* <p>The exception mappings and the default error view will only apply to handlers of the
* specified types; the specified types may be interfaces or superclasses of handlers as well.
* <p>If no handlers or handler classes are set, the exception mappings and the default error
* view will apply to all handlers. This means that a specified default error view will be used
* as a fallback for all exceptions; any further HandlerExceptionResolvers in the chain will be
* ignored in this case.
* The resolver will only apply to handlers of the specified types; the
* specified types may be interfaces or superclasses of handlers as well.
* <p>If no handler predicate, nor handlers, nor handler classes are set,
* the exception resolver applies to all handlers.
* @see #setMappedHandlerPredicate(Predicate)
*/
public void setMappedHandlerClasses(Class<?>... mappedHandlerClasses) {
this.mappedHandlerClasses = mappedHandlerClasses;
}

/**
* Add a mapped handler class.
* Alternative to {@link #setMappedHandlerClasses(Class[])}.
* @since 6.1
*/
public void addMappedHandlerClass(Class<?> mappedHandlerClass) {
Expand Down Expand Up @@ -177,6 +191,7 @@ public ModelAndView resolveException(
/**
* Check whether this resolver is supposed to apply to the given handler.
* <p>The default implementation checks against the configured
* {@linkplain #setMappedHandlerPredicate(Predicate) handlerPredicate}
* {@linkplain #setMappedHandlers handlers} and
* {@linkplain #setMappedHandlerClasses handler classes}, if any.
* @param request current HTTP request
Expand All @@ -188,6 +203,9 @@ public ModelAndView resolveException(
* @see #setMappedHandlerClasses
*/
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
if (this.mappedHandlerPredicate != null) {
return this.mappedHandlerPredicate.test(handler);
}
if (handler != null) {
if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
return true;
Expand All @@ -205,11 +223,13 @@ protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object han

/**
* Whether there are any handler mappings registered via
* {@link #setMappedHandlers(Set)} or {@link #setMappedHandlerClasses(Class[])}.
* {@link #setMappedHandlers(Set)}, {@link #setMappedHandlerClasses(Class[])}, or
* {@link #setMappedHandlerPredicate(Predicate)}.
* @since 5.3
*/
protected boolean hasHandlerMappings() {
return (this.mappedHandlers != null || this.mappedHandlerClasses != null);
return (this.mappedHandlers != null || this.mappedHandlerClasses != null ||
this.mappedHandlerPredicate != null);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,21 @@ void resolveExceptionViaMappedHandler() throws Exception {
assertExceptionHandledAsBody(mav, "DefaultTestExceptionResolver: IllegalStateException");
}

@Test // gh-26772
void resolveExceptionViaMappedHandlerPredicate() throws Exception {
Object handler = new Object();

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class);
this.resolver.setMappedHandlerPredicate(h -> h == handler);
this.resolver.setApplicationContext(ctx);
this.resolver.afterPropertiesSet();

ModelAndView mav = this.resolver.resolveException(
this.request, this.response, handler, new IllegalStateException());

assertExceptionHandledAsBody(mav, "DefaultTestExceptionResolver: IllegalStateException");
}


private void assertMethodProcessorCount(int resolverCount, int handlerCount) {
assertThat(this.resolver.getArgumentResolvers().getResolvers()).hasSize(resolverCount);
Expand Down

0 comments on commit dd23b1d

Please sign in to comment.