Grails - Dynamic Plugins for your applications

The new functionality offers from Groovy/Grails make you able to write a very dynamical application and the possibility to add some new features to your web application at run-time!! Naturally we are exiting a "little bit" from JEE standards where our library/scritps/class and so on, must be in your war or provided from application server.

What I think about this, is that this way to see the development is a little bit old and could bring java to death! I'm dramatic, I know, but there is lot of confusion inside java, java standard and all java project.

It's time to change!! :)



Anyway...



What I'll explain is the way that we are using to add plugins (some functions) to OpenSymbolic, after application installation and, if you want, after webserver startup (I don't know what JBoss thinks about this... I'll do some test when all will be ready).



Our goal was to obtain a way to add some schedulable functions (Quartz Job) to our application leaving to user the decision about which he needs and which not (in a future article we will see the real usage in Symbolic).



First step

Question: How can I add a Job Dynamically to my scheduler?

Answer: at the moment I can't! :(



Solution: I called Sergey Nebolsin, the Quartz plugin developer, exposing my problem, and in ONE NIGHT (underline well ONE), he send me the implementation.

Lesson learned: If I need something for the following day... I will have to call Sergey!! :P (Really thanks again for your work and help Sergey!!)



Second step

Writing a bit of code to see if I can really do what I'm thinking.



Here what I have.



All my plugins will be contained in a specific folder on my machine (configured in configuration file of my application).

/etc/symbolic/plugins

-> /nagios_plugin

-> /msn_plugin

-> /dont_know_plugin



Each of these folders contains the file I need: in my test a configuration file and one script file.

So... with a simple script I could try to scan these folders look for what I need.

import org.codehaus.groovy.grails.commons.ConfigurationHolder



class PluginService {



boolean transactional = false



static CONFIG_FILE_EXT = 'conf'

static SCRIPT_FILE_EXT = 'groovy'

static LIB_FOLDER = 'lib'



public void init() {

//Scan Plugins Folder

def pluginsFolder = ConfigurationHolder.config.plugin.folder

log.debug "Scanning plugin folder: ${pluginsFolder}"

if (pluginsFolder) {

new File(pluginsFolder).eachDir {dir ->

log.debug "Directory found: ${dir}"

def dataMap = [:]



//Read Plugin File and configuring it

dir.eachFile {file ->

if (file.isFile()) {

if (file.name.contains(CONFIG_FILE_EXT)) {

log.debug "File ${file.name} is the configuration file"

def pluginConfiguration = readConfigFile(file)

dataMap['pluginName'] = pluginConfiguration.get("job.name")

dataMap['cronString'] = pluginConfiguration.get("job.cron")

} else if (file.name.contains(SCRIPT_FILE_EXT)) {

log.debug "File ${file.name} is the script file"

dataMap['scriptFile'] = file

} else {

log.debug "File ${file.name} will be ignored!"

}

} else {

if (file.name.equals(LIB_FOLDER)) {

log.debug "Lib folder found... adding jars to classpath."

}

}

}

DefaultPluginJob.schedule(dataMap['cronString'], dataMap)

}

}



else {

logger.info "No plugins folder set. Nothing to load!"

}



}





def readConfigFile = {file ->

Properties prop = new Properties()

if (file) {

prop.load (new FileInputStream(file))

}

prop

}

}



It's just a simple test... there are many improvements to do! ;)



Class "DefaultPluginJob" is a simple Quartz Job, that you can create in a standard grails way, and, with a new plugin release made by Sergey, it has some static methods that you can use to add your job to your quartz scheduler!



Here is the code of Job:

import org.quartz.JobDataMap

import org.quartz.JobExecutionContext



class DefaultPluginJob {



static triggers = { }



def execute(context) {



String instName = context.getJobDetail().getName();

String instGroup = context.getJobDetail().getGroup();

def file = context.mergedJobDataMap.get("scriptFile")



Binding binding = new Binding();

GroovyShell shell = new GroovyShell(binding);



def scriptResult = shell.evaluate(file.text);



}

}



It seems very simple, no? :)



Third step

To create a real extension of your application, you may need to add also some libraries used by your script: you cannot add all existing java libraries to your application because someone would create a plugin that will use that libraries! ;)



A very simple way we have found to solve this problem is adding a lib sub-folder to your plugin folder, where you can put all your libraries.

The plugin server, scanning folders, will add your jars to root class loader in this way:

this.class.classLoader.rootLoader.addURL(new URL("${file}"))



By now you can use all classes contained in your added jar files! :D



Future...

What I need to solve now, is a way to prevent jar conflict. Add all to root class loader, in fact, could make some problems to your application or simply to your other plugins.

I think that a solution could be write something inside your job, that will add your library only to your job instances during (before) execution of the script!

If anyone has any ideas about that... is welcome! :P