Skip to content

Commit

Permalink
Merge pull request #41 from vnobo/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
vnobo authored Dec 18, 2024
2 parents 764ad73 + 6ecb7d9 commit 73692e6
Show file tree
Hide file tree
Showing 18 changed files with 141 additions and 249 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/gradle-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ env:
GITHUB_REGISTRY: ghcr.io/${{ github.actor }}
DOCKER_REGISTRY: docker.io/alexbob
tags: |
type=semver,pattern={{version}},value=v0.0.2
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{version}},value=v0.0.3
type=semver,pattern={{major}}.{{minor}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=semver,pattern={{major}},enable=${{ !startsWith(github.ref, 'refs/tags/v0.') }}
type=ref,event=tag
jobs:
Expand Down
1 change: 0 additions & 1 deletion boot/platform/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ dependencies {

implementation("org.springframework.boot:spring-boot-starter-data-r2dbc")
implementation("org.postgresql:r2dbc-postgresql")
implementation("io.r2dbc:r2dbc-spi")

runtimeOnly ('io.netty.incubator:netty-incubator-codec-http3:0.0.28.Final')
implementation('io.netty:netty-tcnative-boringssl-static')
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
package com.plate.boot.commons;

import com.google.common.collect.Lists;
import com.plate.boot.commons.exception.RestServerException;
import io.r2dbc.spi.R2dbcException;
import lombok.extern.log4j.Log4j2;
import lombok.NonNull;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.r2dbc.BadSqlGrammarException;
import org.springframework.validation.ObjectError;
import org.springframework.http.*;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.result.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;

import java.util.List;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import reactor.core.publisher.Mono;

/**
* GlobalExceptionHandler is a centralized exception handler that intercepts and processes
Expand All @@ -27,85 +20,18 @@
* and informative error messages are returned to the client.
* <p>
* Key Features:
* - Handles {@link ServerWebInputException} to manage binding errors, including validation failures.
* - Handles {@link ResponseEntityExceptionHandler} to manage binding errors, including validation failures.
* - Manages data access exceptions such as {@link DataAccessException} and {@link R2dbcException}.
* - Custom exception handling for {@link RestServerException}, designed for application-specific errors.
* - Generic exception handling for uncaught {@link Exception}s to maintain robustness.
* <p>
* Each exception handler method transforms the exception into a standardized {@link ErrorResponse}
* format before returning it in the body of a {@link ResponseEntity} with the appropriate HTTP status code.
*/
@Log4j2
@ControllerAdvice
public class GlobalExceptionHandler {

/**
* Handles exceptions related to binding issues by creating an appropriate error response.
* This method is specifically designed to process {@link ServerWebInputException} and its subclasses,
* extracting error details to construct an {@link ErrorResponse} instance, which is then returned
* as part of a {@link ResponseEntity} with a status of {@link HttpStatus#EXPECTATION_FAILED}.
*
* @param exchange The current server web exchange containing request/response details.
* @param ex The exception that has been thrown during the binding process, providing insights into the failure.
* @return A {@link ResponseEntity} containing an {@link ErrorResponse} which encapsulates
* the error details including the request ID, request path, HTTP status code, reason for the failure,
* and a list of error messages detailing the fields in error and their respective messages.
*/
@ExceptionHandler(ServerWebInputException.class)
public ResponseEntity<ErrorResponse> handleBindException(ServerWebExchange exchange, ServerWebInputException ex) {
List<String> errors = Lists.newArrayList(ex.getReason());
if (ex instanceof WebExchangeBindException bindException) {
for (ObjectError objectError : bindException.getBindingResult().getAllErrors()) {
errors.add("Error field: %s, msg: %s.".formatted(objectError.getObjectName(),
objectError.getDefaultMessage()));
}
} else {
errors.add("Exception reason %s".formatted(ex.getReason()));
errors.add("Cause message %s.".formatted(ex.getCause().getMessage()));
}
if (log.isDebugEnabled()) {
log.error(ex.getReason(), ex);
}
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.BAD_REQUEST.value(),
exchange.getRequest().getPath().value(), ex.getReason(), errors));
}
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {


/**
* Handles specific types of failure exceptions by creating an appropriate error response.
* This method is designed to catch and process {@link DataAccessException} and {@link R2dbcException},
* extracting useful information for logging and client response.
*
* @param exchange The current server web exchange containing the request and response details.
* @param ex The runtime exception that has been caught, which should be either a
* {@link DataAccessException} or an {@link R2dbcException}.
* @return A {@link ResponseEntity} containing an {@link ErrorResponse} with details about the failure,
* including an HTTP status of 507 (INSUFFICIENT_STORAGE), application/json content type,
* and specifics of the error extracted from the exception and exchange.
*/
@ExceptionHandler({DataAccessException.class, R2dbcException.class})
public ResponseEntity<ErrorResponse> handleFailureException(ServerWebExchange exchange, RuntimeException ex) {
List<String> errors = Lists.newArrayList("Database exec exception!");
if (ex instanceof R2dbcException r2dbcException) {
errors.add(r2dbcException.getSql());
errors.add(r2dbcException.getSqlState());
errors.add(r2dbcException.getMessage());
} else if (ex instanceof BadSqlGrammarException grammarException) {
errors.add(grammarException.getMessage());
errors.add(grammarException.getSql());
} else {
errors.add(ex.getLocalizedMessage());
}
if (log.isDebugEnabled()) {
log.error(ex.getLocalizedMessage(), ex);
log.error(ex.getCause().getMessage(), ex.getCause());
}
return ResponseEntity.status(HttpStatus.INSUFFICIENT_STORAGE).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.INSUFFICIENT_STORAGE.value(),
exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), errors));
}

/**
* Handles exceptions of type {@link RestServerException} by creating an appropriate error response.
* This method is designed to be used within a Spring MVC controller advice context to manage
Expand All @@ -121,35 +47,28 @@ public ResponseEntity<ErrorResponse> handleFailureException(ServerWebExchange ex
* The error response includes the request ID, the request path, the error code,
* the localized error message, and any custom message provided by the exception.
*/
@ExceptionHandler(RestServerException.class)
public ResponseEntity<ErrorResponse> handleRestServerException(ServerWebExchange exchange, RestServerException ex) {
if (log.isDebugEnabled()) {
log.error(ex.getLocalizedMessage(), ex);
@Override
protected @NonNull Mono<ResponseEntity<Object>> handleServerErrorException(@NonNull ServerErrorException ex,
@NonNull HttpHeaders headers,
@NonNull HttpStatusCode status,
@NonNull ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
logger.error(ex.getLocalizedMessage(), ex);
}
return ResponseEntity.status(INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), ex.getStatusCode().value(),
exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), ex.getErrors()));
return handleExceptionInternal(ex, null, headers, status, exchange);
}

/**
* Handles exceptions of type {@link IllegalArgumentException} by creating an appropriate error response.
* This method is designed to be used within a Spring MVC controller advice context to manage
* exceptions that occur during the execution of RESTful server operations.
*
* @param exchange The current server web exchange containing request and response information.
* This is used to extract details necessary for constructing the
* error response.
*/
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArgumentException(ServerWebExchange exchange,
IllegalArgumentException ex) {
if (log.isDebugEnabled()) {
log.error(ex.getLocalizedMessage(), ex);
@ExceptionHandler(DataAccessException.class)
public Mono<ResponseEntity<Object>> handleDataAccessException(DataAccessException ex, ServerWebExchange exchange) {
if (logger.isDebugEnabled()) {
logger.error(ex.getLocalizedMessage(), ex);
}
return ResponseEntity.status(INTERNAL_SERVER_ERROR).contentType(MediaType.APPLICATION_JSON)
.body(ErrorResponse.of(exchange.getRequest().getId(), HttpStatus.BAD_REQUEST.value(),
exchange.getRequest().getPath().value(), ex.getLocalizedMessage(), ex.getStackTrace()));
ProblemDetail problemDetail = ProblemDetail
.forStatusAndDetail(HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage());
problemDetail.setTitle("Bad Sql Grammar Data Access Exception");
problemDetail.setType(exchange.getRequest().getURI());
return handleExceptionInternal(ex, problemDetail, exchange.getRequest().getHeaders(),
HttpStatus.INSUFFICIENT_STORAGE, exchange);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.plate.boot.security.core.UserAuditor;
import lombok.Data;
import org.springframework.data.annotation.*;
import org.springframework.data.relational.core.mapping.InsertOnlyProperty;

import java.time.LocalDateTime;
import java.util.Map;
Expand Down Expand Up @@ -56,6 +57,7 @@ public abstract class AbstractEntity<T> implements BaseEntity<T> {
* use User.class code property
*/
@CreatedBy
@InsertOnlyProperty
protected UserAuditor creator;

/**
Expand All @@ -75,6 +77,7 @@ public abstract class AbstractEntity<T> implements BaseEntity<T> {
* Data entity create time, timestamp column
*/
@CreatedDate
@InsertOnlyProperty
protected LocalDateTime createdTime;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ public Collection<?> convert(@NonNull Json source) {
return ContextUtils.OBJECT_MAPPER.readValue(source.asString(), new TypeReference<>() {
});
} catch (JsonProcessingException e) {
throw JsonException.withMsg("Json converter to collection error",
"Object Json converter to Collection error, message: " + e.getMessage());
throw JsonException.withMsg("Object Json converter to Collection error", e);
}
}
}
Expand All @@ -120,7 +119,7 @@ public static class CollectionWriteConverter implements Converter<Collection<?>,

/**
* Converts a given collection into its JSON representation.
*
* <p>
* This method takes a collection of any type and attempts to serialize it into a JSON object
* using the Jackson {@link ObjectMapper}. If the serialization process encounters a
* {@link JsonProcessingException}, it is caught and rethrown as a {@link JsonException} with a
Expand All @@ -135,8 +134,7 @@ public Json convert(@NonNull Collection<?> source) {
try {
return Json.of(ContextUtils.OBJECT_MAPPER.writeValueAsBytes(source));
} catch (JsonProcessingException e) {
throw JsonException.withMsg("Collection converter to Json error",
"Collection converter to Json error, message: " + e.getMessage());
throw JsonException.withMsg("Collection converter to Json error", e);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.plate.boot.commons.exception;

import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import lombok.Getter;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerErrorException;

import java.io.Serializable;
import java.lang.reflect.Method;

/**
* Represents a custom exception class for handling REST server errors, which includes an error message, a status code,
Expand All @@ -22,29 +24,37 @@
* <li>Convenience static factory methods for instantiation.</li>
* </ul>
*/
@Data
@Getter
@EqualsAndHashCode(callSuper = true)
public class RestServerException extends RuntimeException implements Serializable {
public class RestServerException extends ServerErrorException {

protected final HttpStatusCode statusCode;
protected final Object errors;

public RestServerException(HttpStatusCode code, String message, Object msg) {
super(message);
this.statusCode = code;
this.errors = msg;
public RestServerException(String reason, Throwable cause) {
super(reason, cause);
}

public RestServerException(String message, Throwable throwable) {
this(HttpStatus.INTERNAL_SERVER_ERROR, message, throwable.fillInStackTrace());
public RestServerException(String reason, Method handlerMethod, Throwable cause) {
super(reason, handlerMethod, cause);
}

public static RestServerException withMsg(String message, Object errors) {
return withMsg(HttpStatusCode.valueOf(500), message, errors);
public RestServerException(String reason, MethodParameter parameter, Throwable cause) {
super(reason, parameter, cause);
}

public static RestServerException withMsg(HttpStatusCode code, String message, Object errors) {
return new RestServerException(code, message, errors);
public static RestServerException withMsg(String reason, Throwable cause) {
var ex = new RestServerException(reason, cause);
ex.setTitle(reason);
ex.setDetail(cause.getMessage());
ex.setStackTrace(cause.getStackTrace());
return ex;
}

public static RestServerException withMsg(String reason, Method handlerMethod, @Nullable Throwable cause) {
return new RestServerException(reason, handlerMethod, cause);
}

public static RestServerException withMsg(String reason, MethodParameter parameter, Throwable cause) {
return new RestServerException(reason, parameter, cause);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,19 @@ public final class BeanUtils implements InitializingBean {
* or if there is an issue converting the JsonNode to the target class.
*/
public static <T> T jsonPathToBean(JsonNode json, String path, Class<T> clazz) {

String[] paths = StringUtils.commaDelimitedListToStringArray(path);
StringJoiner pathJoiner = new StringJoiner("/");
for (String p : paths) {
pathJoiner.add(p);
}
JsonPointer jsonPointer = JsonPointer.valueOf(pathJoiner.toString());
JsonNode valueNode = json.at(jsonPointer);
if (valueNode.isMissingNode()) {
throw JsonPointerException.withError("Json pointer path error, path: {}" + pathJoiner,
new IllegalArgumentException(pathJoiner + " is not found in the json [" + json + "]"));
}
try {
String[] paths = StringUtils.commaDelimitedListToStringArray(path);
StringJoiner pathJoiner = new StringJoiner("/");
for (String p : paths) {
pathJoiner.add(p);
}
JsonPointer jsonPointer = JsonPointer.valueOf(pathJoiner.toString());
JsonNode valueNode = json.at(jsonPointer);
if (valueNode.isMissingNode()) {
throw JsonPointerException.withMsg("Json pointer path error",
"JsonPointer path is not exist!");
}
return ContextUtils.OBJECT_MAPPER.convertValue(valueNode, clazz);
} catch (IllegalArgumentException e) {
throw JsonPointerException.withError("Json pointer covert error", e);
Expand Down
Loading

0 comments on commit 73692e6

Please sign in to comment.