RestApiExceptionMapperProperties.java
/*
* Copyright 2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bremersee.exception;
import java.util.List;
import org.immutables.value.Value;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
/**
* The rest api exception mapper properties.
*
* @author Christian Bremer
*/
@Value.Immutable
public interface RestApiExceptionMapperProperties {
/**
* Creates rest api exception mapper properties builder.
*
* @return the builder
*/
static ImmutableRestApiExceptionMapperProperties.Builder builder() {
return ImmutableRestApiExceptionMapperProperties.builder();
}
/**
* Gets default exception mapping.
*
* @return the default exception mapping
*/
@Value.Default
default ExceptionMapping getDefaultExceptionMapping() {
return ExceptionMapping.builder()
.message(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase())
.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.exceptionClassName("*")
.build();
}
/**
* Gets exception mappings.
*
* @return the exception mappings
*/
@Value.Default
default List<ExceptionMapping> getExceptionMappings() {
return List.of(
ExceptionMapping.builder()
.exceptionClassName(IllegalArgumentException.class.getName())
.status(HttpStatus.BAD_REQUEST.value())
.message(HttpStatus.BAD_REQUEST.getReasonPhrase())
.build(),
ExceptionMapping.builder()
.exceptionClassName("org.springframework.security.access.AccessDeniedException")
.status(HttpStatus.FORBIDDEN.value())
.message(HttpStatus.FORBIDDEN.getReasonPhrase())
.build(),
ExceptionMapping.builder()
.exceptionClassName("javax.persistence.EntityNotFoundException")
.status(HttpStatus.NOT_FOUND.value())
.message(HttpStatus.NOT_FOUND.getReasonPhrase())
.build()
);
}
/**
* Gets default exception mapping config.
*
* @return the default exception mapping config
*/
@Value.Default
default ExceptionMappingConfig getDefaultExceptionMappingConfig() {
return ExceptionMappingConfig.builder()
.exceptionClassName("*")
.build();
}
/**
* Gets exception mapping configs.
*
* @return the exception mapping configs
*/
@Value.Default
default List<ExceptionMappingConfig> getExceptionMappingConfigs() {
return List.of();
}
/**
* Find exception mapping.
*
* @param throwable the throwable
* @return the exception mapping
*/
default ExceptionMapping findExceptionMapping(Throwable throwable) {
return getExceptionMappings()
.stream()
.filter(exceptionMapping -> matches(
throwable, exceptionMapping.getExceptionClassName()))
.findFirst()
.orElseGet(this::getDefaultExceptionMapping);
}
/**
* Find exception mapping config.
*
* @param throwable the throwable
* @return the exception mapping config
*/
default ExceptionMappingConfig findExceptionMappingConfig(Throwable throwable) {
return getExceptionMappingConfigs()
.stream()
.filter(exceptionMappingConfig -> matches(
throwable, exceptionMappingConfig.getExceptionClassName()))
.findFirst()
.orElseGet(this::getDefaultExceptionMappingConfig);
}
private boolean matches(Throwable throwable, String exceptionClassName) {
if (throwable == null || exceptionClassName == null) {
return false;
}
if (throwable.getClass().getName().equals(exceptionClassName)) {
return true;
}
if (exceptionClassName.endsWith(".*")) {
String packagePrefix = exceptionClassName.substring(0, exceptionClassName.length() - 1);
if (throwable.getClass().getName().startsWith(packagePrefix)) {
return true;
}
}
if (matches(throwable.getCause(), exceptionClassName)) {
return true;
}
return matches(throwable.getClass().getSuperclass(), exceptionClassName);
}
private boolean matches(Class<?> exceptionClass, String exceptionClassName) {
if (exceptionClass == null || exceptionClassName == null) {
return false;
}
if (exceptionClass.getName().equals(exceptionClassName)) {
return true;
}
if (exceptionClassName.endsWith(".*")) {
String packagePrefix = exceptionClassName.substring(0, exceptionClassName.length() - 1);
if (exceptionClass.getName().startsWith(packagePrefix)) {
return true;
}
}
return matches(exceptionClass.getSuperclass(), exceptionClassName);
}
/**
* The exception mapping.
*/
@Value.Immutable
interface ExceptionMapping {
/**
* Builder immutable exception mapping . builder.
*
* @return the immutable exception mapping . builder
*/
static ImmutableExceptionMapping.Builder builder() {
return ImmutableExceptionMapping.builder();
}
/**
* Gets exception class name.
*
* @return the exception class name
*/
String getExceptionClassName();
/**
* Gets status.
*
* @return the status
*/
@Value.Default
default int getStatus() {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
/**
* Gets message.
*
* @return the message
*/
@Nullable
String getMessage();
/**
* Gets code.
*
* @return the code
*/
@Nullable
String getCode();
}
/**
* The exception mapping config.
*/
@Value.Immutable
interface ExceptionMappingConfig {
/**
* Creates builder.
*
* @return the builder
*/
static ImmutableExceptionMappingConfig.Builder builder() {
return ImmutableExceptionMappingConfig.builder();
}
/**
* Gets exception class name.
*
* @return the exception class name
*/
String getExceptionClassName();
/**
* Determines whether to include the message into the http output or not.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludeMessage() {
return true;
}
/**
* Is include exception class name.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludeException() {
return true;
}
/**
* Is include application name.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludeApplicationName() {
return true;
}
/**
* Is include path.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludePath() {
return true;
}
/**
* Is include handler.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludeHandler() {
return false;
}
/**
* Is include stack trace.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludeStackTrace() {
return false;
}
/**
* Is include cause.
*
* @return the boolean
*/
@Value.Default
default Boolean getIncludeCause() {
return false;
}
/**
* Is evaluate annotation first.
*
* @return the boolean
*/
@Value.Default
default Boolean getEvaluateAnnotationFirst() {
return false;
}
}
}