Where should the separation of the UI message elements be if a grails service throws an exception? Should the message get loaded by the service and passed to the controller via the exception, or should the controller load the message based on the type of exception thrown? This assumes the message will have some parameter values that need to be filled in.
Here is an exception:
class CustomException extends RuntimeException {
String message
}
Loading the message source from the controller after catching the exception:
class MyService {
void doSomething() {
...
if (somethingBad) {
String value = 'Mary Smith'
throw new CustomException(value)
}
...
}
}
class MyController {
def myService
void processRequest() {
try {
myService.doSomething()
}
catch (CustomException e) {
flash.error = g.message(code:'user.input.error', args:'[${e.value}]')
render view:'some_gsp'
}
...
}
}
Loading error from message source in the service where the controller pulls the message string the from the exception:
class MyService {
def messageSource
void doSomething() {
...
if (somethingBad) {
String value = 'Mary Smith'
throw new CustomException(messageSource.getMessage('thread.inactive.user', [value]))
}
...
}
}
class MyController {
def myService
void processRequest() {
try {
myService.doSomething()
}
catch (CustomException e) {
flash.error = e.message
render view:'some_gsp'
}
...
}
}
Frankly speaking, neither of those two places do you need the translations. :)
Separation Of Concern
Controller should only worry about HTTP methods and its delegation.
Services should take care of transactions and underlying business logic.
Declarative Error Handling
For 2.0.* and above, Grails provides you a sweet spot for handling errors. Guess what? Declarative Error Handling
All exception related code moves to a separate controller (in house) where they are handled properly, keeping your business controllers and services clean and abstracted from boiler plate codes.
For Grails 2.3.*, an added feature was to handle exception in the controller itself but most of the boiler plate (try catch stuff) is abstracted from the controller implementation.
Conclusion
If you are using v2.0.* and above then your controllers would look something like:
class MyController {
def myService
def processRequest() {
myService.doSomething()
...
}
}
//URL Mapping
static mappings = {
"500"(controller: "errors", action: "customException",
exception: CustomException)
}
//Error Controller
class ErrorsController {
def customException() {
def exception = request.exception
// perform desired processing to handle the exception
}
}
You can move the logic of error handling to a separate plugin if required in order to handle variety of errors/exception and unhappy paths. It becomes elegant to separate this concern.
If you are using v2.3.* then your controller would look something like:
class MyController {
def myService
def processRequest() {
myService.doSomething()
...
}
def handleCustomException(CustomException e) {
//Move this translation to src/groovy/utility if feasible
flash.error = g.message(code:'user.input.error', args:'[${e.value}]')
render view:'some_gsp'
}
}
In this case no handling required from services as well, you just need to throe that exception.
I suppose you would get more input from various sources as well if you look around and are interested to use this pattern.
Related
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.
I would like to mock a service method in an integration test for one test, however I don't know how to get a reference to the service as it's added to the controller via dependency injection. To further complicate things the service is in a webflow, but I know it's not stored in the flow as the service is not serialized.
Ideal mocking scenario:
Get reference to the service
Mock the method via the metaClass
Main test
Set the metaClass to null so it's replaced with the original
Methods like mockFor so far don't seem to effect the service.
Example of the setup:
Controller:
package is.webflow.bad
import is.webflow.bad.service.FakeService
class FakeController
{
def index = {
redirect(action: 'fake')
}
def fakeFlow = {
start {
action {
flow.result = fakeService.fakeCall()
test()
}
on('test').to('study')
}
study {
on('done').to('done')
}
done {
System.out.println('done')
}
}
}
Service:
package is.webflow.bad.service
class FakeService
{
def fakeCall()
{
return 'failure'
}
}
Test:
package is.webflow.bad
import static org.junit.Assert.*
import grails.test.WebFlowTestCase
import is.webflow.bad.service.FakeService
import org.junit.*
class FakeControllerFlowIntegrationTests extends WebFlowTestCase
{
def controller = new FakeController()
def getFlow() { controller.fakeFlow }
String getFlowId() { "fake" }
#Before
void setUp() {
// Setup logic here
super.setUp()
}
#Test
void testBasic()
{
startFlow()
assertCurrentStateEquals 'study'
assertEquals 'failure', getFlowScope().result
}
#Test
void testServiceMetaClassChange()
{
// want to modify the metaClass here to return success
startFlow()
assertCurrentStateEquals 'study'
assertEquals 'success', getFlowScope().result
}
}
You can inject the service into your Integration test using "#AutoWired" or using application context you get reference. Am i missing something?
#Autowired
private YourService yourservice;
or
#Autowired
private ApplicationContext appContext;
YourService yourService = (YourService)appContext.getBean("yourService");
Here you go:
void "test something"() {
given: "Mocked service"
someController.someInjectedService = [someMethod: { args ->
// Mocked code
return "some data"
] as SomeService
when: "Controller code is tested"
// test condition
then: "mocked service method will be called"
// assert
}
My application works on grails 2.3.1. Some days ago I noticed strange behaviour of error controller. Grails calls error controller but request.exception is null. It seems this happened after application was updated from grails 2.2.x to 2.3.x and we start using async controllers.
My controller:
package grailsapp
import static grails.async.Promises.task
class MyController {
def controllerAction(ActionCommand command) {
task {
// Controller code
}
}
}
Moreover, it happens only if controller code contains difficult calculations, which takes a long time(about 30 sec).
I've tried to run code without grails async and it works perfect.
Also tried wrap code within task with try\catch – nothing changed, catch section haven't caught anything.
package grailsapp
import static grails.async.Promises.task
class MyController {
def controllerAction(ActionCommand command) {
task {
try{
// Controller code
} catch (Exception e){
e.printStackTrace()
// Nothing caught here
}
}
}
}
Tried wrap whole action method body – the same result.
package grailsapp
import static grails.async.Promises.task
class MyController {
def controllerAction(ActionCommand command) {
try{
task {
// Controller code
}
} catch (Exception e){
e.printStackTrace()
// Nothing caught here
}
}
}
Could someone help and explain why it happens?
I needed to display some validation messages received from the service each in its proper place, and I solved it putting the messages in an exception:
class InvalidInputException extends RuntimeException {
def errors
InvalidInputException(String s) {
super(s)
}
InvalidInputException(String s, errors) {
super(s)
this.errors = errors
}
}
That way, I could throw the exception sending the errors:
if (errors) {
throw new InvalidInputException("There were some errors", errors)
}
.. and then I deal with the errors later in the controller, after catching the exception:
...
catch (InvalidInputException e) {
if (e.errors) {
// set them up to display appropriately
}
// render the view
}
Now, I've read somewhere that Groovy's exceptions can cost too much, so... is this too bad?
What problems may I encounter putting additional data in an Exception?
It's much easier than fiddling with returned error messages, and the code is much shorter.
If you are concerned about the performance of exceptions in Java, I suggest you to look at this other question.
If you not create a exception, the other possibility is to make your service return an object that represents the result of this flow. Something like:
class MyServiceResult {
List<String> errorCodes = [] //codes for i18n
void addErrorCode(String errorCode) {
errorCodes << errorCode //add error to list
}
boolean isValid() {
return (!(errorCodes.size() > 0)) //if we have errors then isn't valid.
}
List<String> getErrorCodes() {
return errorCodes.asImmutable()
}
}
And just use it in your service method
class MyService {
MyServiceResult someMethod() {
MyServiceResult result = new MyServiceResult()
...
result.addErrorCode('some.key.here')
...
return result
}
}
class MyController {
def myService
def action() {
MyServiceResult result = myService.someMethod()
if(!result.isValid()) {
//handle errors
}
}
}
But it's important to say that it can be 2x slower than creating an exception. You can check all details in this post.
I want to call a service inside my grails domain objects beforeDelete() event. Unfortunately it always crashes reproducibly when the event is fired. I built an example that reproduces the problem. The domain object:
class Gallery {
def myService
def beforeDelete() {
// System.out.println(myService); // not even this works, same error!
System.out.println(myService.say());
}
}
The service:
class MyService {
String say() {
"hello"
}
}
The test controller:
class DeleteController {
def index() {
Gallery.list().each {
it.delete()
}
}
def create() {
def gallery = new Gallery()
gallery.save()
}
}
If I start the application and call create followed by index I get:
Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [testbeforedelete.Gallery#1]
What I want to accomplish is to call my service, which is a bit more complicated than this example. I cannot explain this behavior and I don't know how cope with this. I know that the Hibernate events need special care yet I'm stuck.
The beforeDelete actually makes a change to your domainclass. I agree that you would not expect this behaviour. Hibernate thinks that you are modifying the instance. You could use the follow code to get around your problem
def beforeDelete() {
Gallery.withNewSession {
System.out.println(myService.say());
}
}