alanwilliamson

Open BlueDragon - Official PlugIn API Overview

Now that people are starting to get their hands dirty with Open BlueDragon and discovering the power, I want to go over the official plugin API that makes it very easy for people to add new tags and functions to the core engine without having to fiddle with any underlying code.

The plugin API is not dissimilar to the approach for creating custom tags written in Java, accept, it has much deeper integration, allowing you to do a wide variety of cool stuff. For example, the PlugInManager makes it very easy to call CFC's from within a Java class and be able to grab the output from any of its methods. In preparation of the release of the first Open BlueDragon Plugin PowerPack, I will go through the major parts of the PlugIn API that lets you build powerful additions and start expanding the CFML library.

PluginManager

Exists within the Open BlueDragon engine is a reference to the PluginManager. This is the main interface into the power of the underlying engine, abstracting away a lot of the heavy lifting of doing most things like registering new tags/functions, listeners and CFC calling.

The most important 2 methods within this class, are the methods to register a new tag or function. These take the name of the tag/function and the fully qualified class name of the class that will provide the functionality for that tag/function. We'll go into tag/function class later on.

public void registerTag(String tagName, String tagClass);
public void registerFunction(String functionName, String functionClass);

sample usage:
 PluginManager.getPlugInManager().registerTag("CFWIKI","com.bluedragon.extra.tag.cfWIKI");

The plugin manager runs after all the tags/functions have been registered within the system and this offers you the opportunity to override or change the processing of an inbuilt tag. For example, you may want to offer different functionality for CFFILE if you are running in a shared instance.

RequestListener

The plugin manager offers more hooks than simply tags and functions calling. You can also listen to events going on within the engine and react to them accordingly. You register a RequestListener, that will give you a hook into the main processing points for every request that comes into the system, passing you the necessary information accordingly.

/**
 * This is called when a request has just started.  This method may not
 * be called if it is an invalid request.
 */
public void requestStart( cfSession session );

/**
 * This is called at the end of *every* request.   All output has already
 * been sent to the client.
 */
public void requestEnd( cfSession session );

/**
 * This is called when the request file was not found.  Note the requestEnd()
 * will still be called after this method.
 */
public void requestBadFileException( cfmBadFileException bfException, cfSession session );

/**
 * This is called when an exception has been thrown.   Note the requestEnd()
 * will still be called after this method.
 */
public void requestRuntimeException( cfmRunTimeException cfException, cfSession session );

You can register as many listeners as you want through the PluginManager interface.

Invoking CFC's

Calling an existing CFC from within your Java code is a nice way to bridge the gap between the outside world and the CFML world. For example, within the Open BlueDragon Plugin PowerPack, there is a new tag CFSMTP that listens for incoming email on Port#25 and when it receives one, it will invoke a CFC method. This lets the CFML developer build truely reactive email applications without having to worry about the underlying logistics of email delivery. This tag utilises the plugin API and the following methods for interacting with CFC's.

from the PluginManager:
  public ObjectCFC createCFC( cfSession session, String cfcName ) throws Exception;

This method will take the name of a CFC and attempt to load it. It will look in all the normal places, including the current context directory if this method is invoked from inside a custom tag. The resulting ObjectCFC instance will be your handle to the CFC and allow you to interact with it. The code block below illustrates the methods available.

public interface ObjectCFC;

/**
 * If the method you wish to call requires parameters then you setup them up
 * using any of the addArgument(..) calls.
 */
public void addArgument(String name, String argVaue);

/**
 * If the method you wish to call requires parameters then you setup them up
 * using any of the addArgument(..) calls.  This method takes an array of
 * strings and converts them into a CFML Array object
 */
public void addArgument(String name, String argVaue[]);

/**
 * If the method you wish to call requires parameters then you setup them up
 * using any of the addArgument(..) calls.
 */
public void addArgument(String name, boolean argVaue);
	
/**
 * If the method you wish to call requires parameters then you setup them up
 * using any of the addArgument(..) calls.
 */
public void addArgument(String name, int argVaue);
	
/**
 * If the method you wish to call requires parameters then you setup them up
 * using any of the addArgument(..) calls.
 */
public void addArgument(String name, cfData argVaue);

/**
 * Clears down the current arguments
 */
public void clearArguments();

/**
 * Given the current arguments, will invoke the 'methodName' on this CFC based
 * on the current Session.
 */
public cfData runMethod( cfSession Session, String methodName ) throws Exception;
	
/**
 * Given the current arguments, will invoke the 'methodName' on this CFC based
 * on the current Session.
 */
public String runMethodReturnString( cfSession Session, String methodName ) throws Exception;
	
/**
 * Given the current arguments, will invoke the 'methodName' on this CFC based
 * on the current Session.
 */
public boolean runMethodReturnBoolean( cfSession Session, String methodName ) throws Exception;

As you can see, you can quickly setup parameters to the CFC, passing in a variety of different formats and then when you are ready you can use any of the runMethodXXX() methods to actually invoke the CFC method. You can reuse this object, calling a number of methods one after another with no problems.

Plugin

We are getting close now rounding out the overall plugin architecture. The main entry point for your set of tags and functions is a Plugin instance. You implement the Plugin interface (detailed below) that will manage the startup and shutdown of the server, allowing you to initilise accordingly.

public interface Plugin

/**
 * This is called when the plugin manager has created your plugin.  This lets you register 
 * tags and functions and set up your functionality
 */
public void pluginStart( PluginManagerInterface manager, xmlCFML systemParameters );

/**
 * When the engine is being shutdown in an orderly fashion this method will be called. 
 * Do not assume it will always be called, as the engine can be stopped in many different 
 * ways that would result in everything disappearing (eg power off, operating system kill)
 */
public void pluginStop( PluginManagerInterface manager );

/**
 * @return short name for your plugin
 */
public String getPluginName();

/**
 * @return the description of your pluing
 */
public String getPluginDescription();

/**
 * @return version of the plugin
 */
public String getPluginVersion();

You implement this interface, with the resulting class being used as the hook for the underlying engine. You register your plugin using the bluedragon.xml file, by passing in the fully qualified path into the

  <server>
    <system>
     <plugin>com.bluedragon.extra.ExtraPackPlugIn</plugin>
    </system>
  </server>

You can specify multiple plugins by comma-separating the list of main entry points.

Sample Plugin

So let us put some of the above into an example that you can see working. I will use one of the custom functions that ships as part of the Open BlueDragon Plugin PowerPack, queryToArray(), that takes in a standard Query object and converts it to an array.

The first thing is our function implementation, that actually provides the real work. Writing a new function is a very simple process, of overriding the functionBase class. The main entry point is the execute(..) method that is invoked when the function has been called by the CFML page or CFC. This passes in the current cfSession and the list of parameters (in reverse order) that were passed into the function.

You can specify the minimum and maximum number of parameters your function will accept by overriding the class values min/max in the constructor. If the caller attempts to pass more or less, then it will throw a page error.

public class queryToArray extends com.naryx.tagfusion.expression.function.functionBase {
  private static final long serialVersionUID = 1;
  
  public queryToArray(){  min = 1; max = 1; }
  
  public cfData execute( cfSession _session, List parameters ) throws cfmRunTimeException {
    cfData el = parameters.get(0);
    if ( el.getDataType() != cfData.CFQUERYRESULTDATA ){
      throwException( _session, "parameter must be an query" );
    }

    cfQueryResultData	queryData = (cfQueryResultData)el;
    cfArrayData	aData	= cfArrayData.createArray(1);
    
    int totalRows	= queryData.getNoRows();
    List	row;
    String columns[]	= queryData.getColumnList();
    
    for ( int r=0; r < totalRows; r++ ){
      row = queryData.getRow(r);
      
      cfStructData rowData = new cfStructData();
      for ( int c=0; c < columns.length; c++){
        rowData.setData( columns[c], row.get(c) );
      }
      
      aData.addElement( rowData );
    }
    return aData;
  }
}

So now that we have a our class providing the functionality for our queryToArray() function, let us create a plugin wrapper to allow us to register this class. I will utilise the main plugin that will be shipping so you can see it with a number of tags and functions being registered at once. You will notice the reference to our above queryToArray() function in the pluginStart() method.

public class ExtraPackPlugIn implements Plugin {

public void pluginStart(PluginManagerInterface manager, xmlCFML systemParameters) {
 manager.registerTag("CFWIKI",     "com.bluedragon.extra.tag.cfWIKI");
 manager.registerTag("CFHTMLTIDY", "com.bluedragon.extra.tag.cfHTMLTIDY");
 manager.registerTag("CFMESSAGE",  "com.bluedragon.extra.jms.cfMESSAGE");
 manager.registerTag("CFSMTP",     "com.bluedragon.extra.smtp.cfSMTP");
 manager.registerTag("CFONLINE",   "com.bluedragon.extra.tag.cfONLINE");
    
 manager.registerFunction("dateageformat", "com.bluedragon.extra.function.dateAgeFormat");
 manager.registerFunction("querytoarray",  "com.bluedragon.extra.function.queryToArray");
}

public void pluginStop(PluginManagerInterface manager) {}

public String getPluginDescription() {
 return "Open BlueDragon ExtraPack - <CFMESSAGE> <CFHTMLTIDY> " + 
    "<CFWIKI> <CFSMTP> <CFONLINE> dateAgeFormat() QueryToArray()";
}

public String getPluginName() {
 return "Open BlueDragon ExtraPack";
}

public String getPluginVersion() {
 return "1.0";
}
}

We then package this up within its own JAR and drop it (with any supporting JAR files that it may require) into the main /WEB-INF/lib/ folder of the Open BlueDragon server, register the plugin with the bluedragon.xml and restart.

That's it, your plugin is now ready to play within the bigger Open BlueDragon pond.

Summary

I went through the plugin architecture here for adding functionality to Open BlueDragon without having to touch any of the underlying code base. The vast majority of new functionality can be added using this technique.

You will be able to package up your plugin, and offer it to others as a completely separate package they can add to their existing installation. And who knows, if your plugin proves to be popular you may want to offer it to the core branch for inclusion with the core installation.

Plugins allows for the infinite expansion of the CFML language without worrying about the underlying logistics of the core engine. Watch out for the release of the Plugin Pack any day now complete with an overview of all the new functions/tags alluded to here.


 

Recent Cloud posts

Recent JAVA posts

Latest CFML posts


 
Site Links