In my Grails app, I have defined the following (simplified) web flow
def registerFlow = {
start {
action {RegistrationCommand cmd ->
try {
memberService.validateRegistrationCommandDTO(cmd)
} catch (MemberException ex) {
flow.regErrorCode = ex.errorCode
throw ex
}
}
on("success").to "survey" // The 'survey' state has been omitted
on(MemberException).to "handleRegMemberException"
on(Exception).to "handleUnexpectedException"
}
handleRegMemberException {
action {
// Implementation omitted
}
}
handleUnexpectedException {
redirect(controller:'error', action:'serverError')
}
}
If a MemberException is thrown by the "start" state, execution should proceed to the "handleRegMemberException" state, but it doesn't. Is there something wrong with my flow definition, or my understanding of how this should work?
Thanks,
Don
I am still pretty new to Groovy and Grails, but I have a suggestion. Perhaps the issue has something to do with the difference in the way the Grails framework (and Groovy for that matter) handles checked vs. unchecked exceptions.
If MemberException is a checked exception (extends Exception), perhaps the 'throw' that is inside the closure behaves to bring execution the whole way out of the webflow. You and I both could do some RTFM'ing on this one... I see the Groovy book on my bookshelf from here. As a quick answer, I would say change MemberException to an unchecked exception (extends RuntimeException) and see if you get the same results. Or, you could wrap MemberException in a RuntimeException...
throw new RuntimeException(ex)
The flow should behave as you expect.
Something might be wrong with your flow, for example some other error in service, but it is not clear from your question what is actually going on. You say how you expect flow to behave and then you say it doesn't behave the way you expected, but you don't say how it actually behaves.
I suggest adding some traces to your flow to see what is actually going on.
By the way, there are some known bugs with different versions of grails and webflows are broken in grails 1.2-M3:
http://jira.codehaus.org/browse/GRAILS-5185
Here is my flow similar to what you have programmed:
class SomeController {
def index = {
redirect(action:'someProcess')
}
def someProcessFlow = {
start{
action{
dosome ->
println "-> inside start action closure"
try{
println "-> throwing IllegalArgumentException"
throw new IllegalArgumentException()
}catch(IllegalArgumentException ex){
println "-> inside catch"
throw ex
}
throw new Exception()
"success"
}
on("success").to "helloPage"
on(IllegalArgumentException).to "illegal"
on(Exception).to "handleException"
}
illegal{
action{
println "-> illegal handled"
"success"
}
on("success").to "helloPage"
}
handleException{
action{
println "-> generic exception handled"
"success"
}
on("success").to "helloPage"
}
helloPage()
}
}
It behave as you expect and the output is:
-> inside start action closure
-> throwing IllegalArgumentException
-> inside catch
2009-11-03 11:55:00,364 [http-8080-1] ERROR builder.ClosureInvokingAction
- Exception occured invoking flow action: null
java.lang.IllegalArgumentException
at SomeController$_closure2_closure3_closure6.doCall(SomeController:18)
at java.lang.Thread.run(Thread.java:619)
-> illegal handled
Related
In my Production code, I am getting errors in my logs when a Mono times out.
I have managed to recreate these errors with the following code:
#Test
public void testScheduler() {
Mono<String> callableMethod1 = callableMethod();
callableMethod1.block();
Mono<String> callableMethod2 = callableMethod();
callableMethod2.block();
}
private Mono<String> callableMethod() {
return Mono.fromCallable(() -> {
Thread.sleep(60);
return "Success";
})
.subscribeOn(Schedulers.elastic())
.timeout(Duration.ofMillis(50))
.onErrorResume(throwable -> Mono.just("Timeout"));
}
In the Mono.fromCallable I am making a blocking call using a third-party library. When this call times out, I get errors similar to
reactor.core.publisher.Operators - Operator called default onErrorDropped
reactor.core.publisher.Operators - Scheduler worker in group main failed with an uncaught exception
These errors also seem to be intermittent, sometimes when I run the code provided I get no errors at all. However when I repeat the call in a loop of say 10, I consistently get them.
Question: Why does this error happen?
Answer:
When the duration given to the timeout() operator has passed, it throws a TimeoutException. That results in the following outcomes:
An onError signal is sent to the main reactive chain. As a result, the main execution is resumed and the process moves on (i.e., onErrorResume() is executed).
Shortly after outcome #1, the async task defined within fromCallable() is interrupted, which triggers a 2nd exception (InterruptedException). The main reactive chain can no longer handle this InterruptedException because the TimeoutException happened first and already caused the main reactive chain to resume (Note: this behavior of not generating a 2nd onError signal conforms with the Reactive Stream Specification -> Publisher #7).
Since the 2nd exception (InterruptedException) can't be handled gracefully by the main chain, Reactor logs it at error level to let us know an unexpected exception occurred.
Question: How do I get rid of them?
Short Answer: Use Hooks.onErrorDropped() to change the log level:
Logger logger = Logger.getLogger(this.getClass().getName());
#Test
public void test() {
Hooks.onErrorDropped(error -> {
logger.log(Level.WARNING, "Exception happened:", error);
});
Mono.fromCallable(() -> {
Thread.sleep(60);
return "Success";
})
.subscribeOn(Schedulers.elastic())
.timeout(Duration.ofMillis(50))
.onErrorResume(throwable -> Mono.just("Timeout"))
.doOnSuccess(result -> logger.info("Result: " + result))
.block();
}
Long Answer: If your use-case allows, you could handle the exception happening within fromCallable() so that the only exception affecting the main chain is the TimeoutException. In that case, the onErrorDropped() wouldn't happen in the first place.
#Test
public void test() {
Mono.fromCallable(() -> {
try {
Thread.sleep(60);
} catch (InterruptedException ex) {
//release resources, rollback actions, etc
logger.log(Level.WARNING, "Something went wrong...", ex);
}
return "Success";
})
.subscribeOn(Schedulers.elastic())
.timeout(Duration.ofMillis(50))
.onErrorResume(throwable -> Mono.just("Timeout"))
.doOnSuccess(result -> logger.info("Result: " + result))
.block();
}
Extra References:
https://tacogrammer.com/onerrordropped-explained/
https://medium.com/#kalpads/configuring-timeouts-in-spring-reactive-webclient-4bc5faf56411
Extract from class CUT under test:
def compileOutputLines( TopDocs topDocs ) {
println "gubbins"
}
Test code:
def "my feature"(){
given:
CUT stubCut = Stub( CUT ){
compileOutputLines(_) >> { TopDocs mockTD ->
// NB no Exception is thrown
// try {
println "babbles"
callRealMethod()
println "bubbles"
// }catch( Exception e ) {
// println "exception $e"
// }
}
}
CUT spyCut = Spy( CUT ){
compileOutputLines(_) >> { TopDocs mockTD ->
println "babbles 2"
callRealMethod()
println "bubbles 2"
}
}
when:
stubCut.compileOutputLines( Mock( TopDocs ))
spyCut.compileOutputLines( Mock( TopDocs ))
then:
true
}
Output to stdout:
babbles
bubbles
babbles 2
gubbins
bubbles 2
I tried to find a link online to the full Spock Framework Javadoc... but I couldn't find it... the "non-framework" Javadoc is here, but you won't find the method callRealMethod in the index.
From the Javadoc API I have generated locally from the source, I can indeed find this method: it is a method of org.spockframework.mock.IMockInvocation. It says:
java.lang.Object callRealMethod()
Delegates this method invocation to the real object underlying this
mock object, including any method arguments. If this mock object has
no underlying real object, a CannotInvokeRealMethodException is
thrown.
Returns:
the return value of the method to which this invocation was delegated
My understanding (such as it is) is that a Stub should cause this Exception to be thrown. But it doesn't appear to be. Any comment from a passing expert?
Preface
This is an interesting question. In theory my answer would be:
callRealMethod() is only available for spies, not for mocks or stubs. It is also only mentioned in the chapter about spies, did you notice?
Think about it: A spy wraps a real object, so there you have a reference to a real method you can call. The same is not true for mocks and stubs which are just no-op subclasses. If you could call a real method for a stub, it would be a spy.
The puzzle
In reality I am seeing a different, even weirder behaviour from yours in my own test with Spock 1.1 (Groovy 2.4): No matter if I use a mock, stub or spy, callRealMethod() always calls the real method. This is really a surprise. So, yes, the behaviour is different from what I would have expected. Looking through the interface implementation's source code while debugging, I also cannot see any checks for the type of mock object (is it a spy or not?). The real method is just identified and called.
The solution
Looking at class DynamicProxyMockInterceptorAdapter I found the explanation for this behaviour: The exception mentioned in the IMockInvocation Javadoc is only thrown when trying to call the real method for an interface type mock, never for mocks or class type objects:
public Object invoke(Object target, Method method, Object[] arguments) throws Throwable {
IResponseGenerator realMethodInvoker = (ReflectionUtil.isDefault(method) || ReflectionUtil.isObjectMethod(method))
? new DefaultMethodInvoker(target, method, arguments)
: new FailingRealMethodInvoker("Cannot invoke real method '" + method.getName() + "' on interface based mock object");
return interceptor.intercept(target, method, arguments, realMethodInvoker);
}
So the sentence "if this mock object has no underlying real object, a (...)Exception is thrown" is in essence correct, but ambiguous because it does not explain what "underlying real object" means. Your assumption was just wrong, so was mine. Lesson learned for both of us.
Now when would you see the described behaviour?
package de.scrum_master.stackoverflow;
public interface MyInterface {
void doSomething();
}
package de.scrum_master.stackoverflow
import org.spockframework.mock.CannotInvokeRealMethodException
import spock.lang.Specification
class MyInterfaceTest extends Specification {
def "Try to call real method on interface mock"() {
given:
MyInterface myInterface = Mock() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
def "Try to call real method on interface stub"() {
given:
MyInterface myInterface = Stub() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
def "Try to call real method on interface spy"() {
given:
MyInterface myInterface = Spy() {
doSomething() >> { callRealMethod() }
}
when:
myInterface.doSomething()
then:
thrown(CannotInvokeRealMethodException)
}
}
Update: I have just created issue #830 requesting improvements in Spock's documentation.
I'm using grails 2.1.1 and I getting the message "No signature of method: ClassA.save() is applicable for argument types: () values: []" when trying to save an object in production environment.
This is the code:
def method(campId, userId){
ClassA cbl = new ClassA()
cbl.userId = userId
cbl.campId = campId
cbl.save(flush:true)
}
This code works fine in development but when I execute the code in production I have this problem.
A couple of things to look out for
You say it works in dev but not in production so the first starting point of any investigation is what is the difference between both. Meaning do a show create table class_a on both product/development environments.
It maybe something has changed and it has some required value that is no longer provided
Step 2:
It is obviously not saving so you need to see if any errors are thrown
def method(campId, userId){
if (campId && userId) {
try {
ClassA cbl = new ClassA()
cbl.userId = userId
cbl.campId = campId
if (!cbl.save(flush:true)) {
//println cbl.errors
//log.info "${cbl.errors}"
}
}catch (Exception e) {
//println "E really happended $e"
}
}else {
println "oh my ... a certain value has not been provided"
}
}
You see in above code an if statement to ensure both values provided since you are setting without checking, the try catch was last alternative to try but the if it saves println error is the next thing to try
Finally there are many ways to save a class just in case as Anton suggest you have some other inbuild functions in ClassA when it comes to saving and new ClassA() may cause an issue maybe since you are missing
ClassA() {
super()
}
or something then I would lastly try this as a test
ClassA cbl = new ClassA(userId:userId,campId:campId)?.save(flush:true)
Same logic applied differently
I am purposely causing a cat instance to fail. The following test passes.
void testSomething() {
Cat.metaClass.save = {
throw new Exception("Asdasd")
}
shouldFail(Exception){
Cat cat = new Cat(name: "asd")
cat.save()
}
GroovySystem.metaClassRegistry.removeMetaClass(Cat.class)
}
But, when i set the failOnError property for the save method then this fails. How can i alter the save using metaClass in order to make the save(failOnError:true) throw an exception? I appreciate any help! Thanks!
void testSomething() {
Cat.metaClass.save = {
throw new Exception("Asdasd")
}
shouldFail(Exception){
Cat cat = new Cat(name: "asd")
cat.save(failOnError: true)
}
GroovySystem.metaClassRegistry.removeMetaClass(Cat.class)
}
One alternative to doing the same test is to pass in invalid parameters to the domain instance so that the validation fails and exception is thrown but this will not work in all cases because in some cases the domain instance doesn't require any parameters given by the user. So, in order to simulate the failure of domain save() in this case we will need a way to mock the save failure. So, i appreciate if anyone has answer to how to mock save with or without save params like save(flush:true), save(failOnError:true). Thanks!
Your first instance of metaClassing the save() is fine.
When trying to metaClass the save(failOnError: true) version, you have to alter your metaClassing statement to match the signature of the actual employed method. A "save()" invocation is not the same as a "save(failOnError:true)" invocation. Try this (I suspect the parameter is strictly typed, so I'm using Map. :
Cat.metaClass.save = { Map map ->
throw new Exception("failOnError is true")
}
This question is connected with another.
I'd like to add properties to constructor and overwrite getLocalisedMessage() function to get proper translated message with error. First I want to overload constructor to set properties, but when I add:
GroovyCastException.metaClass.constructor = { Object objectToCast, Class classToCastTo ->
def constructor = GroovyCastException.class.getConstructor(Object, Class)
def instance = constructor.newInstance(objectToCast, classToCastTo)
// ... do some further stuff with the instance ...
println "Created ${instance} and executed!"
instance
}
and then get thrown GroovyCastException I don't get println in console.
Why?
How to overload constructor, set properties (objectToCast, classToCastTo) and then overload getLocalizedMessage?
I tried also:
def originalMapConstructor = GroovyCastException.metaClass.retrieveConstructor(Map)
GroovyCastException.metaClass.constructor = { Map m ->
// do work before creation
print "boot do work before creation "
m.each{
print it
}
print "boot do work before creation 2"
def instance = originalMapConstructor.newInstance(m)
// do work after creation
print "boot do work after creation"
instance
}
I 've put it in controller (right before catching exception) and in Bootstrap.groovy. Unfortunatelly there is no printlns in console output.
You're better off not using meta-programming to do internationalization. In grails, you should do it in the view layer with the <g:message> tag if possible. If not, the next best choice is the controller layer.
If you just want to display localized messages on an error page when an exception occurs, the best practice is to have a "500" URL mapping, and render the exception with a <g:renderException> in the view.
If you want to intercept the exception, you can change the "500" URL mapping to a controller and wrap it there before passing it to the view. Example:
// UrlMappings.groovy
class UrlMappings {
static mappings = {
...
"500"(controller:"error", method: "serverError")
}
}
// ErrorController.groovy
class ErrorController {
def serverError() {
def exception = request.exception.cause
if (exception instanceof GroovyCastException) {
exception = new LocalizedGroovyCastException(exception)
}
[exception: exception]
}
}
And then do your localization in a new class LocalizedGroovyCastException.