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.