Error Handling when using annotations - spring-statemachine

I'm following the documentation (State Machine Error Handling) to implement error handling. However, when an exception occurs it is propagated up rather than intercepted. I tried using the interceptor, the listener and the #OnStateMachineError without any success. Debugging the code, neither MethodInvokingStateMachineRuntimeProcessor.java:52 or any of its callers have any specific logic to handle errors.
Replicating the issue is simple, just create a state machine (I'm using the latest snapshot) and register the bean:
#WithStateMachine
public class ExceptionThrowingAction {
#OnTransition
public void throwError(#EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
throw new RuntimeException("test error");
}
}
Am I missing something or is it a genuine bug? If so, I'll raise as an issue

Yes, this is a bug. We've done a lot of changes in master to harden there user level hooks. None of those should break machine execution. Please raise an issue and we'll fix it.

Related

Spring-Boot removes message from ErrorAttributes in response

When throwing an exception from a spring-boot controller, the message in the server response is empty - but only if I don't run locally. That last part is what's confusing me the most. I mean, it would make perfect sense to be able to have spring-boot remove parts from the error response. Like the stacktrace for example, noone wants to send that out except when debugging.
But when I run the application locally, I get the full error response, message, stacktrace and all (even when not running in debug mode, which I first suspected might be the reason for this). A typical error might look something like this:
{
"timestamp": "2020-10-14T09:46:35.784+00:00",
"status": 400,
"error": "Bad Request",
"trace": "webcam.yellow.service.controller.error.BadRequestException: New password must be different from old password! at /*REDACTED*/",
"message": "New password must be different from old password!",
"path": "/users/9"
}
But when I produce the same error on a deployed server, all I get is this:
{
"timestamp": "2020-10-14T09:29:57.720+00:00",
"status": 400,
"error": "Bad Request",
"message": "",
"path": "/users/9"
}
I don't mind the stacktrace being removed at all (in fact I want it to be removed), but I would really like to receive that error message.
One thought I had was that it might be related to cross-origin access, but I get the same behaviour when producing the error through swagger instead of our frontend, and swagger is same-origin.
I would fully expect such behaviour to be configurable in spring-boot, that would be convenient. Trouble is, I'm not configuring it. I compared the configuration properties of the running server to my local ones and I don't see any property that might be responsible for that. Nor can I find any if I google it. According to all the tutorials I find, this should work just fine. Which it kind of does, except not on the running servers.
Does anybody know what in spring-boot is causing this behaviour and how to configure it? Using spring-boot 2.3.3 by the way.
Additional information:
After some fooling around, I managed to reproduce the problem locally. I get the shortened error response if I build the application, and then run it from the command line directly with java -jar. Running gradle bootRun results in the server returning the full error message.
I've tried to return my own error response through a ControllerAdvice:
#ControllerAdvice
class BadRequestHandler : ResponseEntityExceptionHandler() {
#ExceptionHandler(value = [BadRequestException::class])
protected fun handleBadRequest(ex: BadRequestException, request: WebRequest): ResponseEntity<Any> {
val message = ex.message
return ResponseEntity(message, HttpHeaders(), HttpStatus.BAD_REQUEST)
}
}
This was just intended to be a quick test to see if I could change the server response. Turns out I can't, the client still gets the same response, although the handler is executed. So whatever takes the information out of the request must come further down the chain.
Does anybody have any idea what's happening here??
This is intended behavior since Spring Boot 2.3 as explained here
Setting server.error.include-message=always in the application.properties resolves this issue.
The response can be configured by injecting a custom ErrorController, like for example this one:
#Controller
class ExampleErrorController(private val errorAttributes: ErrorAttributes) : ErrorController {
private val mapper = ObjectMapper()
#RequestMapping("/error")
#ResponseBody
fun handleError(request: HttpServletRequest): String {
val webRequest = ServletWebRequest(request)
val error = errorAttributes.getError(ServletWebRequest(request))
// if it's not a 500, include the error message in the response. If it's a 500, better not...
val errorAttributeOptions = if (error !is HttpServerErrorException.InternalServerError) {
ErrorAttributeOptions.defaults().including(ErrorAttributeOptions.Include.MESSAGE)
} else ErrorAttributeOptions.defaults()
val errorDetails = errorAttributes.getErrorAttributes(
webRequest, errorAttributeOptions)
return mapper.writeValueAsString(errorDetails)
}
override fun getErrorPath(): String = "/error"
}
Note that this takes ErrorAttributeOptions.defaults() as a baseline, then configures what goes in. It appears that this default object is the one used by the default ErrorController spring boot provides, and it is in fact this object that is different depending on whether I run this from gradle/intelij directly or build it into a jar. Why I couldn't find out, but I verified and confirmed the behaviour. I assume it is intended, albeit not widely documented.
Once I learned this, I wondered why it wasn't possible to just configure the default Options object globally for an application rather than providing an entire controller, which in many instances would be sufficient, but it does not look like that's possible at this point.

How to get a thrown exception from MailerClient of Playframework

I'm working with Play-Mailer inside of a Play application. I'm able to send emails to correct email addresses. So far so good. - But with none existing email addresses an exception is thrown from org.apache.commons.mailer but method mailerClient.send(email) itself throws nothing. - Is there a way to catch any exceptions form Play-Mailer?
EDIT:
I looked at the Play-Mailer plugin and saw that it is written in Scala. Scala does not have the notion of checked exceptions. Scala compiles directly to JVM bytecode and this is the "trick" how you can call Java code in Scala which throws checked exceptions but you do not have to rethrow them (the rules for checked exceptions are a bit different on JVM bytecode level).
You are free to use standard try-catch block in your Java code to handle the exceptions.
What kind of exceptions is the commons mailer throwing? Are you referring to: https://commons.apache.org/proper/commons-email/javadocs/api-release/org/apache/commons/mail/Email.html#send--
The send() methods throws an EmailException which is not a RuntimeException, so the exception would be propagated. If the Play-Mailer plugin is not catching it, you have to do it in your code:
try {
// try to send the mail
mailer.send(email);
} catch (EmailException e) {
// do something with the exception
}

What is the replacement for Spring's deprecated NoSuchRequestHandlingMethodException?

The NoSuchRequestHandlingMethodException was deprecated in Spring 4.3, in favor of annotation-driven handler methods. What does this mean? The exception is still listed in the documentation, without mentioning its deprecated status. If I understand correctly, this exception is thrown when there is no request mapper for a given request. It appears to be handled by the DefaultExceptionHandlerResolver, here, and the relevant method has been deprecated as well.
If this method is deprecated, can I assume Spring does not throw this exception anymore? How am I supposed to replace this functionality with annotation-driven exception handling? Which exception am I supposed to handle, if this one is deprecated?
Side note: I also noticed a newer NoHandlerFoundException, here. Is this a replacement? If so, why? It appears to do the same thing. And why are the exceptions related to other HTTP status codes not deprecated? It all doesn't make a lot of sense.
The NoSuchRequestHandlingMethodException exception is part of the multiaction package that has been deprecated in Spring:
https://docs.spring.io/spring-framework/docs/2.5.x/javadoc-api/org/springframework/web/servlet/mvc/multiaction/class-use/NoSuchRequestHandlingMethodException.html
If you don't use multiactions, you can safely get rid of that catch statement and/or stop trying to catch and handle that exception. For example, some "Exception-to-Response error handlers" sample code might look like this to try and catch cases when the dispatcher doesn't find a proper mapping:
#ControllerAdvice
public class RestErrorHandler {
#ExceptionHandler({FileNotFoundException.class, NoSuchRequestHandlingMethodException.class})
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorInfo process404(HttpServletRequest req, Exception ex) {
return handleException(req, HttpStatus.NOT_FOUND, ex);
}
}
But the latest dispatcher (non-multiaction) will never throw such an exception, so you could simply get rid of the NoSuchRequestHandlingMethodException and, instead, handle the NoHandlerFoundException (which is not thrown by default, but you can configure the spring dispatcher to throw it IF you really need to, because, by default, the dispatcher already returns a 404).

ServiceLocatorImplBase.cs not found

When my WebAPI controller is called from a client, I run into the following errors:
ServiceLocatorImplBase.cs not found error
An exception of type 'Microsoft.Practices.ServiceLocation.ActivationException' occurred in Microsoft.Practices.ServiceLocation.dll but was not handled in user code
The WebAPI controllers use constructor injection to inject a repository dependency which should be resolved by StructureMap IoC. Interestingly, the same code runs fine on my another development machine. Here is my stack trace. Thanks for your help.
System.ArgumentNullException was unhandled by user code
HResult=-2147467261
Message=Value cannot be null.
Parameter name: httpContext
Source=System.Web
ParamName=httpContext
StackTrace:
at System.Web.HttpContextWrapper..ctor(HttpContext httpContext)
at WebApi2.DependencyResolution.StructureMapDependencyScope.get_HttpContext() in c:.........\WebApi2\DependencyResolution\StructureMapDependencyScope.cs:line 69
at WebApi2.DependencyResolution.StructureMapDependencyScope.get_CurrentNestedContainer() in c:.........\WebApi2\DependencyResolution\StructureMapDependencyScope.cs:line 55
at WebApi2.DependencyResolution.StructureMapDependencyScope.DisposeNestedContainer() in c:.........\WebApi2\DependencyResolution\StructureMapDependencyScope.cs:line 90
at WebApi2.DependencyResolution.StructureMapDependencyScope.Dispose() in c:.........\WebApi2\DependencyResolution\StructureMapDependencyScope.cs:line 85
at WebApi2.App_Start.StructuremapMvc.End() in c:.........\WebApi2\App_Start\StructuremapMvc.cs:line 44
Thanks for your reply. Both machines are running integrated mode. The error is really misleading and threw me off to a wrong track. I spent hours trying to find where this ServiceLocatorImplBase.cs resides. I happened to look into the deeply nested inner exceptions, and found that the inner most exception (5th level) complains some entities generated by POCO generator have no identity key. This is because I manually added the foreign key relationship among some entities with
public virtual RelatedEntity1 {get;set;}
public virtual RelatedEntity2 {get;set;}
without setting [key] attributes in the related entities. I am not sure if this can be fixed but the exception message should not lead people to the wrong track.
The problem you are running into is because you are attempting to resolve HttpContext at the point in time that the application is composed (typically done in the Application_Start event of Global.asax). HttpContext is part of the application's runtime state. It is null at the point in time when the application is being composed.
The reason why it seems to work in your development environment is likely because your development environment's application pool is running in classic mode. Most likely the other environments are (correctly) running in integrated mode. So, this is a design issue, not a problem with deployment as you might expect.
The solution is to use an Abstract Factory so you can defer instantiating of the HttpContextWrapper until runtime. Then you can inject the abstract factory rather than HttpContextWrapper into your services.
public interface IHttpContextFactory
{
HttpContextBase Create();
}
public class HttpContextFactory
: IHttpContextFactory
{
public HttpContextBase Create()
{
return new HttpContextWrapper(HttpContext.Current);
}
}
See this answer and this answer for a complete examples including usage.

Pre-bound JDBC Connection found

We have an app that is using hibernate, spring, and DB2 in websphere 7. We have audit triggers and we need to set so the triggers can know the logged in user (we use generic logon to the database). We came up with a new scheme for setting this in a new app so that it can automatically join in new transactions. We overrode the transaction manager and did the work in the doBegin.
These scheme worked great in one app, and seemed to work great in a second app, but now, weeks later, and not consistently (behavior is intermittent and does not happen in local development), we are getting this Pre-bound JDBC Connection found error. Looking online most posts say this is when you use two transaction managers against one data source. That is now what we are doing.
I also read one post wondering if it was because he mixed annotation and AOP based transactions. This app does some of that. I don't really buy that theory, but thought I'd mention it.
Exception:
Caused by:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! HibernateTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single HibernateTransactionManager for all transactions on a single DataSource, no matter whether Hibernate or JDBC access.
at java.lang.Throwable.<init>(Throwable.java:67)
at org.springframework.core.NestedRuntimeException.<init>(NestedRuntimeException.java:54)
at org.springframework.transaction.TransactionException.<init>(TransactionException.java:34)
at org.springframework.orm.hibernate3.HibernateTransactionManager.doBegin(HibernateTransactionManager.java:475)
at gov.usdoj.afms.umc.utils.hibernate.AfmsHibernateTransactionManager.doBegin(AfmsHibernateTransactionManager.java:28)
Code (note that the exception comes from the super.doBegin()):
protected void doBegin(Object arg0, TransactionDefinition arg1) {
super.doBegin(arg0, arg1);
if (!Db2ClientInfo.exists()) {
clearDBProperty();
} else {
setDBProperty(Db2ClientInfo.getClientUserId(), Db2ClientInfo.getClientApplicationId());
}
}
private void setDBProperty(String uId, String appName) {
Session session = getSessionFactory().getCurrentSession();
Properties props = new Properties();
props.setProperty(WSConnection.CLIENT_ID, uId);
props.setProperty(WSConnection.CLIENT_APPLICATION_NAME, appName);
try {
Connection nativeConn = new SimpleNativeJdbcExtractor().getNativeConnection(session.connection());
if (nativeConn instanceof WSConnection) {
WSConnection wconn = (WSConnection) nativeConn;
wconn.setClientInformation(props);
} else {
logger.error("Connection was NOT an instance of WSConnection so client ID and app could not be set");
}
} catch (Exception e) {
throw new RuntimeException("Cannot set DB parameters!", e);
}
}
I just realized I never answered this. It turns out that the exception had nothing whatever to do with our Tx manager. It was the fact that this particular EAR has two apps in it, each pointing to the same data source. Evidently this confuses hibernate. We've plans to separate the apps some day, but creating an identical (except in name) data source and pointing the apps at them separately fixes the issue for now.
Instead of modifying the transaction manager it might be easier (better?) to create a wrapper around your datasource (extending DelegatingDataSource from spring) and override the 2 getConnection methods. For the cleanup you could wrap the connection in a proxy and intercept the close method.
That should be a safer (and easier I guess) way then trying to fiddle with the transaction manager and it works for every technology (JDBC, Hibernate, JPA etc.) as long as you use the wrapped datasource. (The registration could be done with a BeanPostProcessor which detects DataSource instances and simply wraps them in the delegate).
If that is to radical (as it means changing your current applications instead of updating a library). It could be a configuration problem, make sure that you are only loading your configuration (and thus DataSource and TransactionManager) only once, duplicating bean instances might lead to a similair behavior.
For posterity, I just got this problem and the answers here weren't very helpful. We resolved the problem by removing a double import of a core XML file which had the AOP transaction manager definition in it:
<tx:annotation-driven transaction-manager="..."
proxy-target-class="true" />
I'm thinking that it causes there to be 2 transaction managers overlapping the same namespace. To fix it, we moved the imports around so they were being done once.
Hope this helps someone else.

Resources