Basic Theory, By Example
Blackbird modules are managed objects. As such, they are classes that extend a base Blackbird class called Feather, and implement the functions they need to get their work done. Functions are callbacks, and their presence tells Blackbird what you expect to be doing.
To run through the theory, we will be examining the processes/email/sender.php sample. The class starts out simply enough:
require_once('Smarty.class.php');
class email_sender extends feather {
private $smarty;
public function __construct() {
parent::__construct();
$this->smarty = new Smarty();
$this->smarty->tempate_dir ='./configuration/templates';
$this->smarty->php_handling = SMARTY_PHP_ALLOW; // NOT SECURE, see below!
}
By extending feather, the class gains access to configuration handlers, default callbacks, and other utilities that prevents us from having to write a lot of code we don't actually care about. The constructor creates an instance of the Smarty class. We could do that each time a message is received, but it's more efficient this way.
public function onInit($config, $filename) {
parent::onInit($config, $filename);
$this->metrics['process.status'] = 'OK';
$this->bigfoot->mainmq->subscribe($this->config->topic);
}
public function onReconfigure($config, $filename) {
parent::onReconfigure($config, $filename);
$this->metrics['process.status'] = 'OK';
$this->bigfoot->mainmq->subscribe($this->config->topic);
}
onInit() is called once when the module is loaded. onReconfigure() is called if the configuration is changed on the fly. Many modules may not care about the difference, unless they use many external resources. In both cases here, we just call our parent, set a metric element so administrators can tell we're running fine, and subscribe to the topic we're given. Configuration data is automatically loaded and exposed under $this->config->option.
There are no hard and fast rules regarding metrics, but we like to break them into conceptual groups so we can display them in a tree, and we always implement process.status as a string status value, and transactions.count as an integer number of transactions processed. We would suggest that you at least implement these two values, as the management tools we'll be releasing will know to look for them, and will do clever things like highlight in green/yellow/red processes that are OK or not.
Moving on, we document the configuration parameters we expect to receive:
static public function docConfig(&$config) {
feather::docConfig($config);
bigfoot::docConfig('mainmq', 'STOMP', $config);
$config->addOption('required', 'from', 'Template file for FROM: address on message');
$config->addOption('required', 'to', 'Template file for TO: address on message');
$config->addOption('required', 'subject', 'Template file for SUBJECT: line on message');
$config->addOption('required', 'body', 'Template file for body of message');
}
Documentation allows Blackbird to ensure all required parameters are specified, and it allows management tools to create Web forms on the fly that administrators can use to make configuration files. You can add elements manually, as we do via $config->addOption(required/optional, fieldname, description), or by calling a module that provides functionality you require. In this case, we need to connect to a message topic to listen for messages, so we ask Bigfoot, the protocol handler, to add configuration lines for a STOMP connection called 'mainmq'.
Note that up to this point we've only implemented code that is relatively repetitive between modules. That means this code so far is largely cut/paste between modules, with minor tweaks for particular config options. In other words, we've written a bit of code, but not much, and it only took a minute to do.
Finally, to get real work done, we must implement a callback. There are many available, including on-timer and on-message events. We want to be notified every time a message arrives on the topic we're subscribed to. We implement STOMP_OnXMLMessage, which will parse the message's body into an XML object for us. (Note that we don't actually have to do the work to connect to and subscribe to the message topic. That was handled for us by the bigfoot connection handler when we asked it, in docConfig, to make our STOMP connection called 'mainmq'.)
public function STOMP_OnXMLMessage($conn, $msg, $xml) {
global $clog;
if ($xml == null) return;
$fields = xmltools::XMLToArray($xml);
$this->smarty->clear_all_assign();
$this->smarty->assign($fields);
$from = $this->smarty->fetch($this->config->format->from);
$to = $this->smarty->fetch($this->config->format->to);
$subject = $this->smarty->fetch($this->config->format->subject);
$body = $this->smarty->fetch($this->config->format->body);
if (!strstr($body, "!!!DONOTSEND!!!")) {
$clog->debug('Sending message to ' . $to . ', from ' . $from . ', subject ' . $subject . ', body ' . $body);
$hdr = "From: $from";
mail($to, $subject, $body, $hdr);
} else {
$clog->debug('Not sending message, DONOTSEND tag found');
}
$this->metrics['transactions.count']++;
}
First, we make sure we have a valid XML message. It's already been parsed, but we're called anyway even if that fails in case we want to see/log that event. That's especially useful when debugging. (The original message frame is in $msg.)
We then convert the XML message body to an array of fields, assign them to Smarty, and tell Smarty to create our to/from/subject/body fields. We template all four fields so you can do clever things in any field you like.
Note that in our constructor we told Smarty to allow PHP tags in the templates. Note above that we don't send the message if DONOTSEND is found in the body. We can use this to implement custom rules just for this template, rather than having to write a new email sender for each set of rules. However, this is a security risk, and should only be turned on if you understand (and are OK with) the concept that code in the template runs with the same permissions as Blackbird and its processes. If this is a problem, set up an isolated Blackbird instance to run your email senders.
Finally, after sending the message (or logging that we're refusing to), we increment the transaction counter metric. Management tools can use this value to draw nice line charts showing admins how much the component is being used over time.
That's it! Obviously, there are many callbacks you should know about, protocol handlers for various resources, and tricks to getting certain things running how you like them, but most Blackbird modules don't need to be any more complicated than the above. All together, this module is about 60 lines of code, but the unique functionality is less than 20.
Samples
A number of sample modules have been included to help you get started:
- email/sender.php - Sends an e-mail message in response to each bus message received. Supports Smarty for templating.
- file/receiver.php - For each bus message received, dump its contents to a file on disk
- file/sender.php - Monitor a directory, and send a bus message for each file found
- jabber/receive.php - Connect to a Jabber server and interact with other contacts
- message/bridge.php - Bridges two message topics, copying messages from one to the other
- message/transformer.php - Same as bridge, but can perform XSLT on a message before retransmitting it
- message/databank.php - Copies received messages to a database
Each module illustrates some key piece of functionality in Blackbird, such as connecting to more than one message queue, connecting to an external resource, or performing work on messages. They were also intended to be useful, not just samples.