My code is written in Kotlin. I have a config class defined in a file along 2 more classes as below:
#Configuration
class MultipartConfig(private val multipartProperties: MultipartProperties) {
#Bean
fun multipartResolver(): StandardServletMultipartResolver {
val multipartResolver = MultipartResolver(multipartProperties)
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily)
return multipartResolver
}
}
class MultipartResolver(private val multipartProperties: MultipartProperties) :
StandardServletMultipartResolver() {
override fun resolveMultipart(request: HttpServletRequest): MultipartHttpServletRequest {
return MultipartHttpServletRequest(multipartProperties, request)
}
}
class MultipartHttpServletRequest(
private val multipartProperties: MultipartProperties, request: HttpServletRequest
) : StandardMultipartHttpServletRequest(request, multipartProperties.isResolveLazily) {
override fun handleParseFailure(ex: Throwable) {
val msg = ex.message
if (msg != null && msg.contains("size") && msg.contains("exceed")) {
throw MaxUploadSizeExceededException(multipartProperties.maxFileSize.toMegabytes(), ex)
}
throw MultipartException("Failed to parse multipart servlet request", ex)
}
}
When I debug this code, in the class MultipartHttpServletRequest, constructor property multipartProperties is NOT null but the same property in the throw MaxUploadSizeExceededException(multipartProperties.maxFileSize.toMegabytes(), ex) is ALWAYS null. I cannot understand why this is happening.
Could someone please explain why this is happening?
I'm just answering my own question for clarity. I figured out the issue. It was related to the constructor of that class StandardMultipartHttpServletRequest. Below is the code of the constrcutor.
public StandardMultipartHttpServletRequest(HttpServletRequest request, boolean lazyParsing)
throws MultipartException {
super(request);
if (!lazyParsing) {
parseRequest(request);
}
}
Now, inside of parseRequest there is a catch block (for brevity I'm not posting the whole code of the method)
catch (Throwable ex) {
handleParseFailure(ex);
}
When the constructor of the super class is throwing exception the child's constructor will not get a chance to initialize.
Related
How to intercept and handle errors globally in WebFlux when they are being thrown from WebFilter chain?
It is clear how to handle errors thrown from controllers: #ControllerAdvice and #ExceptionHandler help great.
This approach does not work when an exception is thrown from WebFilter components.
In the following configuration GET /first and GET /second responses intentionally induce exceptions thrown. Although #ExceptionHandler methods handleFirst, handleSecond are similar, the handleSecond is never called. I suppose that is because MyWebFilter does not let a ServerWebExchange go to the stage where GlobalErrorHandlers methods could be applied.
Response for GET /first:
HTTP 500 "hello first" // expected
HTTP 500 "hello first" // actual
Response for GET /second:
HTTP 404 "hello second" // expected
HTTP 500 {"path": "/second", "status": 500, "error": "Internal Server Error" } // actual
#RestController
class MyController {
#GetMapping("/first")
String first(){
throw new FirstException("hello first");
}
}
#Component
class MyWebFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange swe, WebFilterChain wfc) {
var path = swe.getRequest().getURI().getPath();
if (path.contains("second")){
throw new SecondException("hello second")
}
}
}
#ControllerAdvice
class GlobalErrorHandlers {
#ExceptionHandler(FirstException::class)
ResponseEntity<String> handleFirst(FirstException ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.message)
}
#ExceptionHandler(SecondException::class)
ResponseEntity<String> handleSecond(SecondException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.message)
}
}
Three steps are required to get full control over all exceptions thrown from application endpoints handling code:
Implement org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
Annotate with #ControllerAdvice (or just #Component)
Set #Priority less than 1 to let the custom handler run before the default one (WebFluxResponseStatusExceptionHandler)
The tricky part is where we get an instance implementing
ServerResponse.Context for passing to
ServerResponse.writeTo(exchange, context). I did not find the final
answer, and comments are welcome. In the internal Spring code they always create a new instance of context for each writeTo invocation,
although in all cases (I've manged to find) the context instance is immutable.
That is why I ended up using the same ResponseContextInstance for all responses.
At the moment no problems detected with this approach.
#ControllerAdvice
#Priority(0) /* should go before WebFluxResponseStatusExceptionHandler */
class CustomWebExceptionHandler : ErrorWebExceptionHandler {
private val log = logger(CustomWebExceptionHandler::class)
override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
log.error("handled ${ex.javaClass.simpleName}", ex)
val sr = when (ex) {
is FirstException -> handleFirst(ex)
is SecondException -> handleSecond(ex)
else -> defaultException(ex)
}
return sr.flatMap { it.writeTo(exchange, ResponseContextInstance) }.then()
}
private fun handleFirst(ex: FirstException): Mono<ServerResponse> {
return ServerResponse
.status(HttpStatus.INTERNAL_SERVER_ERROR)
.bodyValue("first")
}
private fun handleSecond(ex: SecondException): Mono<ServerResponse> {
return ServerResponse.status(HttpStatus.BAD_REQUEST).bodyValue("second")
}
private object ResponseContextInstance : ServerResponse.Context {
val strategies: HandlerStrategies = HandlerStrategies.withDefaults()
override fun messageWriters(): List<HttpMessageWriter<*>> {
return strategies.messageWriters()
}
override fun viewResolvers(): List<ViewResolver> {
return strategies.viewResolvers()
}
}
}
I'm using spring-boot 2+ and created some custom annotation;
#Target({ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface MyCustomAnnotation{
}
When doing:
final AnnotatedType[] annotatedTypes = mostSpecificMethod.getAnnotatedParameterTypes();
//this will get the original class
//final Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
Class<?> annotatedMappedClass = null;
for (AnnotatedType annotatedType : annotatedTypes) {
if (annotatedType.isAnnotationPresent(MyCustomAnnotation.class)) {
annotatedMappedClass = TypeFactory.rawClass(annotatedType.getType());
}
}
it works when bean is not a proxy but when I add the #Transactional annotation it becomes a proxy and stops working. What is the Spring Util to find in the target class?
As far as I understood you'll need the bean. Using:
Method invocableMethod = AopUtils.selectInvocableMethod(mostSpecificMethod, bean.getClass());
seems to work.
Also a more complex one:
Method method = mostSpecificMethod;
if (AopUtils.isAopProxy(bean)) {
try {
Class<?> clazz = AopProxyUtils.ultimateTargetClass(bean);
method = clazz.getMethod(mostSpecificMethod.getName(), mostSpecificMethod.getParameterTypes());
}
catch (SecurityException ex) {
ReflectionUtils.handleReflectionException(ex);
}
catch (NoSuchMethodException ex) {
throw new IllegalStateException("...", ex);
}
}
I am new to String, SpringBoot.
Can we suppress thrown exception in a method annotated with #AfterThrowing?
I mean when an exception is thrown, it will suppress that and will return a default value on behalf of the invoking method?
Say, I have a controller -
#RestController
public class MyRestController implements IRestController{
#Override
#GetMapping("hello-throw")
public String mustThrowException(#RequestParam(value = "name")final String name) throws RuntimeException {
System.out.println("---> mustThrowException");
if("Bakasur".equals(name)) {
throw new RuntimeException("You are not welcome here!");
}
return name + " : Welcome to the club!!!";
}
}
I have created a #AspectJ, as follows -
#Aspect
#Component
public class MyAspect {
#Pointcut("execution(* com.crsardar.handson.java.springboot.controller.IRestController.*(..))")
public void executionPointcut(){
}
#AfterThrowing(pointcut="executionPointcut()",
throwing="th")
public String afterThrowing(JoinPoint joinPoint, Throwable th){
System.out.println("\n\n\tMyAspect : afterThrowing \n\n");
return "Exception handeled on behalf of you!";
}
}
If I run this & hit a ULR like - http://localhost:8080/hello-throw?name=Bakasur
I will get RuntimeException, but, I want to return a default message like - Exception handeled on behalf of you!, can we do it using #AfterThrowing?
I know it can be done using #Around, but around will be called on every hit of the url, that I do not want
What you want to do is Exception Handling on the controller. You don't need to build it yourself, Spring already supports you with some annotations like #ExceptionHandler and #ControllerAdvice. Best would be to follow this example: https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc#using-controlleradvice-classes
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
#ControllerAdvice
class GlobalDefaultExceptionHandler {
public static final String DEFAULT_ERROR_VIEW = "error";
#ExceptionHandler(value = Exception.class)
public ModelAndView
defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
// If the exception is annotated with #ResponseStatus rethrow it and let
// the framework handle it - like the OrderNotFoundException example
// at the start of this post.
// AnnotationUtils is a Spring Framework utility class.
if (AnnotationUtils.findAnnotation
(e.getClass(), ResponseStatus.class) != null)
throw e;
// Otherwise setup and send the user to a default error-view.
ModelAndView mav = new ModelAndView();
mav.addObject("exception", e);
mav.addObject("url", req.getRequestURL());
mav.setViewName(DEFAULT_ERROR_VIEW);
return mav;
}
}
You should use the fully qualified name of the class before method's name when you're referring to a pointcut. So, you should change #AfterThrowing something like this.
#AfterThrowing(pointcut="packageName.MyAspect.executionPointcut()",
throwing="th")
Please note that packageName is full package name of MyAspect.
method I am testing (the method setEventHubDataPayload throws JSONException and JsonProcessingException):
public class EventHubMapper {
//inits
public byte[] toEventDataJsonByteArray(UserRecord inbound) {
EventHubDto ehDto = new EventHubDto();
ehDto.setEventTypeVersion(inbound.getVersion());
ehDto.setEventId(inbound.getNotificationId());
JSONObject eventJson = new JSONObject(ehDto);
try {
eventJson.put("data", setEventHubDataPayload(ehDto, inbound));
} catch (JSONException e) {
analytics.trackError(AnalyticsConstants.EventHub.JSON_MAPPING_ERROR, e.toString());
} catch (JsonProcessingException e) {
analytics.trackError(AnalyticsConstants.EventHub.JSON_PROCESSING_ERROR, e.toString());
}
return eventJson.toString().getBytes();
}
}
unit test code:
#Test
public void toEventDataByteArray_JsonException() throws JSONException, JsonProcessingException {
EventHubMapper ehmMock = Mockito.spy(eventHubMapper);
doThrow(new JSONException("blah")).when(ehmMock).setEventHubDataPayload(any(), any());
eventHubMapper.toEventDataJsonByteArray(setUpMockUserRecord());
verify(analytics, times(1)).trackError( AnalyticsConstants.EventHub.JSON_MAPPING_ERROR, new JSONException("blah").toString());
}
I've tried using more specific matchers ... ex: any(EventHubDto.class) or any(UserRecord.class) and got the same result:
Wanted but not invoked:
analytics.trackError(
"EventHub_Publish_Error",
""
;
and also
Actually, there were zero interactions with this mock.
what is going on here?
I think you need to call like below while testing.
ehmMock.toEventDataJsonByteArray(setUpMockUserRecord());
When I go to /confirmation-account link, in tomcat console I can see that if and else block is also executed. I can see:
print from ColorConsoleHelper.getGreenLog("loginView") and from ColorConsoleHelper.getGreenLog("confirmationAccountView")
This is really strange behavior. Why?
#RequestMapping(value = "/confirmation-account", method = RequestMethod.GET)
#Transactional
public ModelAndView displayConfirmationAccountPage(ModelAndView modelAndView, #RequestParam Map<String, String> requestParams) {
final int ACTIVE_USER = 1;
// find the user associated with the confirmation token
UserEntity userEntity = userService.findUserByConfirmationToken(requestParams.get("token"));
// this should always be non-null but we check just in case
if (userEntity!=null) {
// set the confirmation token to null so it cannot be used again
userEntity.setConfirmationToken(null);
// set enabled user
userEntity.setEnabled(ACTIVE_USER);
// save data: (token to null and active user)
saveAll(userEntity.getTrainings());
/*
RedirectAttributes won't work with ModelAndView but returning a string from the redirecting handler method works.
*/
modelAndView.addObject("successMessage", "Konto zostało pomyślnie aktywowane!");
modelAndView.setViewName("loginView");
ColorConsoleHelper.getGreenLog("loginView");
} else {
ColorConsoleHelper.getGreenLog("confirmationAccountView");
modelAndView.addObject("errorMessage", "Link jest nieprawidłowy...");
modelAndView.setViewName("confirmationAccountView");
}
return modelAndView;
}
public void saveAll(List<TrainingUserEntity> trainingUserEntityList) {
for ( TrainingUserEntity trainingUserEntity : trainingUserEntityList) {
entityManagerService.mergeUsingPersistenceUnitB(trainingUserEntity);
}
}
public void mergeUsingPersistenceUnitB(Object object) {
EntityManager entityManager = getEntityManagerPersistenceUnitB();
EntityTransaction tx = null;
try {
tx = entityManager.getTransaction();
tx.begin();
entityManager.merge(object);
tx.commit();
}
catch (RuntimeException e) {
if ( tx != null && tx.isActive() ) tx.rollback();
throw e; // or display error message
}
finally {
entityManager.close();
}
}
Below solution & explanation:
Because of /confirmation-account link is invoke twice, what is caused by dynamic proxy and #Transactional method annotated in controller It is mandatory to check how many displayConfirmationAccountPage method is invoked. It is workaround.
What do you think it is good or not to annotated #Transactional controller method?