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
-> /nagiosplugin
-> /msn
plugin
-> /dontknowplugin

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 CONFIGFILEEXT = 'conf'
static SCRIPTFILEEXT = 'groovy'
static LIBFOLDER = '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
FILEEXT)) {
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
FILEEXT)) {
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