alanwilliamson

Open BlueDragon - Developing a tag with the Plugin API

Earlier I introduced the Open BlueDragon Plugin API that lets you integrate expressions and tags into the core engine without having to touch the underlying engine. In this article, I am going to go through the basics of a tag and what you need to do and what you can do when creating your own tag. Subsequent articles will delve a little deeper into more advanced topics.

Extending the cfTag class

Every tag within the system extends the com.naryx.tagfusion.cfm.tag.cfTag class. The main body of work for the tag is the render( cfSession _Session ) method, which is called whenever the tag is executed. It this method you override to provide the implementation details.

Tag Start up

When the engine starts up, all the supported tags are initialised. If your tag needs to do something special on startup, you can hook into this startup, but providing a static init() method in your class. The core engine will invoke this method, passing in the reference to the bluedragon.xml file, so you can read XML parameters. The following code snippet illustrates this one time startup call, pulling in the element that is within the <server> - <mytag> - <username> section.

public class cfMyTag extends cfTag {
 ...
 private static String username;

 public static void init( xmlCFML config ) {
   username = config.getString( "server.mytag.username", "" );
 }
 ...
}

This gives your tag an opportunity to startup any servers it may require, for example, the CFMAIL tag uses this to startup its outgoing mail spooler. This lets your tag stand on its own two feet. The vast majority of tags however do not need to do this and this method is left out.

Tag Loading

When a page is first loaded, the engine scans the page looking for all the tags and creates a new instance for each one encountered. How this is arranged in memory isn't of any importance at this level, but its enough to know that the tag class is loaded through its default constructor and then defaultParameters( String _tag ) method is called passing in all the attributes, in a non-parsed form, to the tag.

This is where you get an opportunity to specify the attributes for the tag, defaulting attributes and throwing any errors to the user detailing any problems. The underlying tag class manages the attribute parsing for you, but you need to tell it the attributes you really need or want.

This is a 3 step process; specify your default attributes, call to parse the attributes that came in and then do any post-processing error checking, throwing any problems. Once a problem has been thrown, the page is deemed invalid and all further processing stopped.

The following code example illustrates this in action. We default the attribute TO, but require the developer to specify at least the FROM attribute.

public class cfMyTag extends cfTag {
 ...
 protected void defaultParameters( String _tag ) throws cfmBadFileException {
   // We need this attribute to run this tag; so lets default it
   defaultAttribute( "TO", 10 );

   // Parse them
   parseTagHeader( _tag );

   // Now let us do some checking
   if ( !containsAttribute("FROM") )
     throw newBadFileException( "Missing FROM", "Must contain at least a FROM field" );	
 }
 ...
}

Now remember, we are merely checking for the existence of this attribute, not necessarily its value. Some attributes may be fixed in its value, in other words you cannot pass it a dynamic variable, where as some attribute values will only be known when the tag is executed within its context. The current trend in tag development is that pretty much every attribute can be dynamic. So at this stage you can only check for its existence.

Now tags can be of one of two formats; standalone, or with an ending tag. The default state of a tag is assumed to have no ending tag, but you can change this by overriding the method getEndMarker() tag that specifies the end tag delimiter. Here you return the closing tag. If the CFML developer does not provide this tag, then an error is thrown.

public class cfMyTag extends cfTag {
 ...
 public String getEndMarker(){ return "</CFMYTAG>"; }
 ...
}

So now that we have all the necessary hooks for loading up of the tag, we now turn our attention towards the main execution method of the tag.

Tag Execution

The main method you need to override to give your tag something to do is the render( cfSession _Session ) method. This method accepts the current cfSession context, which has everything you need to manage the request coming in.

For every page, a single tag instance is created for everytime it appears in the page. That one instance is then shared amongest all requests when that tag is being asked to execute. Therefore, it is very likely that many threads will be executing through the main execution method, render( cfSession _Session ), at the same time.

There is a lot of control you can excert on how your tag interacts with the request it is processing. All this is performed by overriding the render( cfSession _Session ) and utilising the methods from the underlying cfTag and cfSession classes.

public class cfMyTag extends cfTag {
 ...
 public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException {
 
   return cfTagReturnType.NORMAL;
 }
 ...
}

The first thing you will notice that the render(.) method requires a return value. If you think of some of the tags within the CFML world you will realise that some of them have specific behaviour that results in the page not executing in a linear manner. For example, the CFABORT tag, stops all processing of the page, where as the CFBREAK tag breaks out of the current loop. You need to communicate to the rendering engine on how your tag behaves when it completes.

The majority of tags will simply execute and move onto the next one, and we communicate this by returning the cfTagReturnType.NORMAL value. There are a number of others to choose from cfTagReturnType.TYPE_BREAK, cfTagReturnType.TYPE_CONTINUE, cfTagReturnType.TYPE_EXIT, cfTagReturnType.TYPE_RETURN, with each one effecting the post page processing differently.

Accessing Attributes

One of the first things you probably need to do, is to fathom out which attributes were passed into your tag, and then determine their runtime value. For example, <cfmytag FROM="#form.value#">, we would want the FROM attribute to be fully expanded before we use it.

The following code snippet illustrates how we check for the existence of the FROM attribute, and then determine its value. You check for its existence using the containsAttribute(.) that will return true/false if it exists. This merely detects the attribute was successfully specified and legal at the tag loading. It does not yet know what the value of this attribute is.

You determine the value of the attribute by invoking the getDynamic(.) method, passing in the current cfSession context. Now as a CFML developer, you will know that many different data types exist within the CFML world ranging from simple data types (numbers, strings, booleans) right through to complex data structures (structures, arrays, queries, cfcs).

In the BlueDragon world, every data type has the base class of cfData. The getDynamic(.) returns a cfData type that can then be used in the manner or checked for the data type you want. I will go into this world in a later article because this is powerful API that is available to you to manipulate data types.

The code below illustrates an example attribute check, optionally throwing an error if the attribute is not available. Also note that if the attribute turns out NOT to be a simple number, then an exception will be thrown automatically. You can of course trap for this, using try/catch, and take action accordingly.

public class cfMyTag extends cfTag {
 ...
 public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException {
   int from;
   if ( containsAttribute( "FROM") ){ 
     from = getDynamic( _Session, "FROM" ).getInt(); 
   }else
     throw newRunTimeException( "Missing Attribute FROM" );

   return cfTagReturnType.NORMAL;
 }
 ...
}

Output to the page

One of the most common tasks a tag will do is to output something. This would normally be a page request with the resulting value making its way to the users browser. This done through methods within the cfSession object. The most common being the write(.) method that accepts a String object, byte array, or a char array.

public class cfMyTag extends cfTag {
 ...
 public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException {
   int from;
   if ( containsAttribute( "FROM") ){ 
     from = getDynamic( _Session, "FROM" ).getInt(); 
   }else
     throw newRunTimeException( "Missing Attribute FROM" );

   _Session.write( "<p>MyTagOutput: " + from + "</p>" );

   return cfTagReturnType.NORMAL;
 }
 ...
}

Embedded Tags

If your tag has an ending tag, then you may elect to permit the CFML developer to further embed tags within your tag. This is very common in the CFML world, particularly within the likes of CFSAVECONTENT, CFQUERY to name but 2.

When the engine comes to execute your tag, it is up to you determine whether or not the embedded tags will be executed. You invoked this execution manually within your render(.) by calling the renderToString(.) method, passing the result text back to you as a single String.

public class cfMyTag extends cfTag {
 ...
 public cfTagReturnType render( cfSession _Session ) throws cfmRunTimeException {
   ...

   // Execute the body of your tag
   String resultTxt = renderToString( _Session, cfTag.HONOR_CF_SETTING ).getOutput();

   return cfTagReturnType.NORMAL;
 }
 ...
}

You can then pass this text on out using the Session.write(.), simply discard it or process it for some other purpose. But this call will take care of all the heavy lifting of tag processing and give you back the result.

Embedded Tags: Passing data between tags

By this point you will be getting a feel for how easy it is to create tags and have them play within the bigger tag pond. Each tag is its own little world, operating within a larger context. You do not directly interact with the tags, but instead you let the natural execution flow deal with all communication. But how do you communicate between tags?

For example, CFQUERYPARAM is an embedded tag that sits inside CFQUERY tag collecting SQL parameters for the later execution of that SQL statement. How does the CFQUERYPARAM tag talk to the CFQUERY tag?

It does it by leaving messages for CFQUERY! Within the cfSession object is a general data bin, that you can place objects for later use within. The code snippet below shows the 3 methods available for interacting with this data store.

public class cfSession {
 ...
 public Object getDataBin( String key );
 public void setDataBin( String key, Object value );
 public void deleteDataBin( String key );
 ...
}

This data store is only valid for the duration of the request, and is only available to that particular request. The CFQUERY tag can check to see its not itself been embedded (CFQUERY inside a CFQUERY) by checking for the existence of a special object witin the data bin, and throwing an error accordingly.

This is a very powerful data layer that your tags can utilise to provide sophisicated functionality. For example the CFFORM and all its child tags utilise this method to build up the necessary data for the form data.

Summary

Within this piece I went over the basics of creating a tag for use within the Open BlueDragon Plugin API. As you can see there is a lot you can do even with this understanding, including building up sophisicated embedded tags. Later on, I will delve a little deeper into the power of the cfSession object and how you can utilise more advanced data types.


 

Recent Cloud posts

Recent JAVA posts

Latest CFML posts


 
Site Links