Showing posts with label Grails. Show all posts
Showing posts with label Grails. Show all posts

Tuesday, September 14, 2010

Pulling the configuration for the mail plugin for grails from the database

I have a grails application that sometimes needs to email users. I am using the mail plugin, but that expects the configuration (SMTP server, etc) configuration to be in the application's Config.groovy file. I wanted to make it possible for the site administrator to change the configuration. This way, they could create a specific email account with Google just to send email for the application.

After some research, I discovered that the configuration could be altered at runtime by getting the mailSender injected into my code, and setting its properties as needed.

Next, I needed a domain object to represent my configuration. I tried using the @Singleton annotation, but that does not play well with domain objects. I ended up writing getInstance() myself:


static long instanceId = 1l;
static EmailServerConfig instance;

static synchronized getInstance() {
if (!instance) {
instance = EmailServerConfig.get(instanceId);
if (!instance) {
instance = new EmailServerConfig()
instance.id = instanceId
instance.save()
}
}
return instance;
}
This can only work with
static mapping = {
id generator:'assigned'
}


and even with that, I was unable pass the id parameter to the constructor, that's why I set it separately after the EmailServerConfig object is created.

Then all I need is to define the GORM event handlers afterLoad, afterInsert and afterUpdate to apply the values from the database to the mailSender.

Finally, I made sure to encrypt the SMTP authentication password on its way into the database, and to decrypt it when retreiving it. Thanks to Geoff Lane for this blog post on how to handle that with codecs:
class EmailServerConfig {
String password
String passwordEncoded
//...
def afterLoad = {
password = passwordEncoded?.decodeSecure()
updateMailSender()
}
def beforeUpdate = {
passwordEncoded = password?.encodeAsSecure()
}
//...
}

Then all I had to do was write the controller to let site admins edit those values. The controller uses EmailServerConfig.getInstance(), since we're simulating a singleton.

Things to remember:
  • onLoad is called before the values are loaded into the object, so the values are null; use afterLoad instead
  • beforeInsert, beforeUpdate and beforeDelete are called only once the object is validated, so the constraints have to allow a null value for passwordEncoded
  • Grails tries to instantiate domain classes at startup, and @Singleton prevents that, that's why you can't have @Singleton on a domain class
  • If you don't create an EmailServerConfig in the Bootstrap, and you do not provide any configuration in Config.groovy, the mailSender will default to trying to send mail through localhost:25. This may work, depending on your setup.

Thursday, June 10, 2010

How to transform one type of object to another in Groovy/Grails (as long as it's not a domain object)

I've been working on a system that will be using remote calls to communicate between a client (browser, mobile phone, possitbly a GWT client) and the server. The client sends a request, and a grails controller returns a grails domain object encoded using JSON. Relatively straight-forward stuff, but I hit a few snags. I was thankful when I discovered http://blog.lourish.com/ which goes into some details into how to make it happen. Detailed post are here, here, and here.

I debated using the ObjectMarshaller to restrict the data sent (afterall, the client doesn't need to know the class name of my objects), but in the end, I decided to use Data Transfer Objects. I can see a future development where these objects will be used as commands, for example.

The problem that's been keeping me awake tonight, tho, is in the translation from domain object to DTO. Based on my reading, it looked like I could transform any kind of object into any other kind of object, as long as the initial object knew what to do.
class User {
// grails will contribute fields for id and version
String lastName
String firstName
Address workAddress //
Address homeAddress // The client does not need that info and SHOULD NOT ever see it
static hasMany [roles: Role, groups: Groups]  // etc

doThis() {
//..
}

doThat() {
//...
}
}

class UserDTO {
String lastName
String firstName
}
How do you take a User object and make a UserDTO out of it? Well, you should certainly have a look at Peter Ledbrook's DTO plugin. But for my needs, I thought I'd stick with something simpler. Just use the groovy "as" operator.
All you need to do something like
def dto = User as DTO
is to have User implement asType(Class clazz) and to handle (by hand) the case where clazz is DTO:
class User {
// same fields as before, etc
Object asType(Class clazz) {
if (clazz.isAssignableFrom(UserDTO)) {
return new UserDTO(lastName: lastName, firstName:firstName)
} else {
return super.asType(clazz)
}
}
}
All works well. Unit tests confirm, there's nothing to it.
void testUserAsUserDTO() {
String lastName = 'Lovelace'
String firstName = 'Ada'
User u = new User(lastName: lastName, firstName: firstName);
UserDTO dto = u as UserDTO;
assertEquals(UserDTO.class, dto.class)
assertEquals(lastName, dto.lastName);
assertEquals(firstName, dto.firstName);
}
Integration test. I want to make sure my controller sends the right data
The controller:
def whoAmI = {
def me = authenticateService.userDomain() // acegi plugin; this returns a User
if (me) {
def dto = me as UserDTO
render dto as JSON
} else {
render [error: "You are not logged in"] as JSON
}
}
The test:
class RpcWhoAmITest extends ControllerUnitTestCase {
void testWhoAmI() {
String lastName = 'Lovelace';
String firstName = 'Ada';
User u = new User(lastName: lastName, firstName: firstName)
mockDomain(User.class,[u])
mockLoginAs(u)
controller.whoAmI()
def returnedUser = JSON.parse(controller.response.contentAsString)
assertNotNull(returnedUser)
assertEquals(lastName, returnedUser.lastName)
assertEquals(firstName, returnedUser.firstName)
}
}
And that... fails! The message is
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'my.package.User : 1' with class 'my.package.User' to class 'my.package.rpc.UserDTO'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:348)
at ...

What went wrong? The call to mock my domain object is what went wrong. It replaces my asType(Class clazz) with its own. Fortunately, that's relatively easy to fix. I needed to override the method addConverters in grails.test.GrailsUnitTestCase to replace asType(Class) only if it didn't already exist (in my test class):
@Override
protected void addConverters(Class clazz) {
registerMetaClass(clazz)
if (!clazz.metaClass.asType) {
clazz.metaClass.asType = {Class asClass ->
if (ConverterUtil.isConverterClass(asClass)) {
return ConverterUtil.createConverter(asClass, delegate, applicationContext)
}
else {
return ConverterUtil.invokeOriginalAsTypeMethod(delegate, asClass)
}
}
}
}


Sadly, after all this work, I deploy, launch, and still get GroovyCastExceptions. It turns out that the instrumentation of domain class objects essentially throws out my "asType()" method. In the end, I switched to the DTO plugin (which post-instruments the domain object to do it's own stuff, something I considered doing, but at some point, the "quick, home-made solution" just isn't.