Creating Actions, the lightweight alternative to Components

If you want to write some code that makes the Pentaho BI Platform talk to an external system, this may be what you are looking for, so read on...

What is an Action?

Actions are the lightweight alternative to Pentaho BI Components.  For an understanding of how and why they are different, let's take a step back and examine what a BI Component is and how it is used.  A BI Component is historically a Java class that does some work, yielding some output, when it is provided certain inputs and resources.  The work done as well as the inputs and resources required are entirely up to the component developer.  It's purpose is to perform some work in a sequential success-based workflow (action sequence).  A saavy BI Platform user with a bit of inclination for Java coding can implement his or her own custom component to do just about anything.  However, this is not an easy endeavour since developing your own BI Component requires intimate knowledge of internal Pentaho APIs.  When you are done writing your custom component, the code you have written will be tightly coupled to the Pentaho BI Platform, so much so, that it will be difficult to unit test and may be difficult to maintain.

These are some of the problems that the Action API seeks to solve.  As we mentioned earlier, Actions are the lightweight alternative to developing Components.  So, what do we mean by "lightweight"?  Well, we have taken the complex set of APIs required to implement a Component and reduced it to a handful of concise APIs, with IAction and thus the "execute()" API at the heart of it all.  With the new Action framework, we allow you to write a Java bean that performs the action you desire, and simply decorate it with the appropriate API for the desired interaction with the platform.  These platform interaction APIs will discussed in more detail later, but they are essentially:

  • IAction - advertises that your bean has something to do and gives the platform's solution engine a method to execute.  This is the API that identifies your bean an Action.
  • IStreamingAction - indicates that your bean accepts output streams managed by action sequence content outputs, such as a ServletResponse output stream.  You would implement this if you intended to write to such an output stream during execution.
  • ILoggingAction - if your bean implements this, it will be provided a logger instance to which it can write errors, warnings, and debug messages.
  • ISessionAwareAction - supplies your bean with an instance of the current Pentaho session
  • IVarArgsAction - Allows an Action to accept inputs from the action sequence that are unspecified by the Action itself
  • IDefinitionAwareAction - Makes an Action privy to certain details about the action definition that is responsible for executing it
  • IPreProcessingAction - Allows an Action to do some preliminary work prior to execution

Action Execution

As we mentioned earlier, the heart of the Action framework is it's ability to do some work, thus, the IAction.execute() API. The only interface your Action bean is required to implement is the IAction interface which specifies only a single method, "execute()". All the other interfaces listed in intro section above are just there to provide either your Action bean or the Action framework with additional context about this execution.

Action Validation

There are two stages in which we speak of validation. The first is during the action sequence validation stage which is prior to execution. The intent at this stage is to "check the wiring", i.e. to see that all expected inputs and outputs are declared in the action sequence. You can think of this as a static type of validation, vs runtime validation. In the IComponent framework, this kind of validation was performed by invoking a validate method on an IComponent. Given that part of the intent of the Action framework is to minimize the coupling of an Action bean with the Pentaho BI Platform, we provide explicit APIs to allow your Action bean to become "aware" of the context in which it is being used, namely, it can become aware that it is running in an action sequence. For more information, see the javadoc for IDefinitionAwareAction and IPreProcessingAction.

The second stage, runtime validation, is handled by you the Action bean writer. If you want runtime validation, then just implement it before you do anything else in your execute() method. It is highly recommended that you throw an execption when something goes wrong in execute(), this includes runtime validation failure. Throwing an exception will allow the Pentaho BI Platform to report meaningful information as to the cause of action sequence failure.

Action IO

Actions, as full-fledged Components, can participate in action sequences and can be provided inputs and resources by the typical means, as defined in xaction solution files.  There is an important distinction in the way that these parameters are obtained in Components versus in Actions.  While developing a Component, you will need to essentially go and mine out the parameters and values you need from within various internal APIs.  Actions are the inverse.  The Action framework will determine what inputs you desire and hand them right to you by way of a setter method (see Java bean specification).

The Action framework expects Action objects to be Java bean compliant with respect to setting inputs, setting resources, and getting outputs.  In other words, if your action needs takes a string input, the action definition in the xaction solution file will specify this string input, and the Action framework will cause that value to be set via a setter method on the Action object.  You do not see parameter Maps and such in the Action API for this reason.  All inputs, output, and resources IO will involve Java bean reflection on your Action object to find the appropriate IO methods.

See the Echo Plugin - a sample plugin for the BIServer project for examples of how to get data into your IAction and how to expose the output of your IAction so something else can do something with it (i.e. display the results of your IAction, pipe them to another step in an action sequence, or bind them to a session parameter).

Here is a quick reference for how input and output types map from xaction xml files to an Action bean.

Inputs

During an action sequence, the Action framework will attempt to pass all the information you intend for your Action bean.  The action-inputs section of your action definition is the contract which tells the Action framework all the things that your Action bean requires to execute successfully.  In the following table, we've listed the various XML that the Action framework might encounter along with the method that the Action framework will expect to find on your Action bean.  If an appropriate setter method is not found on your Action bean, the framework will report a warning and proceed to the next input.  If an appropriate setter method is found, but the value of the input cannot be converted to the type declared in your setter method, the framework will report an error and fail execution.

xaction input

Java type

expected Action bean method (Java)
(all methods start with 'public void')

<myInput type="string">

java.lang.String

setMyInput(String s)

<myInput type="long">

java.lang.Long

setMyInput(Long n)

<myInputMap type="property-map">

java.util.Map<java.lang.String, java.lang.String>

setMyInputMap(Map<String, String> m)

<myInputListMap type="property-map-list">

java.util.List<java.util.Map<java.lang.String, java.lang.String>>

setMyInputListMap(List<Map<String, String>> l)

<myInput type="string-list">

java.util.List<java.lang.String>

setMyInputList(List<String> l)

<myInput> (no type specified)

In this case, the Action framework does not know the type and will try to convert the data to the type specified in your Action's setter method. If the conversion fails, you will see a warning to this effect.

setMyInput(Object o),
or
setMyInput(SomeOtherType t) //of course this will only work if the data can be cast to a SomeOtherType type

VarArgs Inputs

Sometimes your Action bean will need to pass inputs through to another subsystem and not act on them directly. For these inputs, it is cumbersome and sometimes impossible to create setter methods for each possible input. If this is the case for you, then have your Action bean implement the IVarArgsAction interface. This will allow you to receive a map containing all the inputs that were specified in the action definition but had no setter method counterpart in your Action bean.

Collection Inputs

Your Action may need to operate on an array of similar items, such as sending an email with multiple attachments, like so:

<action-inputs>
<attachments_0>
<attachments_1>
<attachments_2>
</action-inputs>

Assuming these attachments are specified in your action definition inputs section, the possible options for getting those inputs to your bean are:

  1. you would need to code a setter method for each attachment declared in the action definition, e.g. setAttachments_0(Object o), setAttachments_1(Object o), and so on.
  2. have your Action bean implement IVarArgsAction and then loop through the inputs maps and find the attachment inputs
  3. use an beanutils "indexed" input in your Action bean

The names of the attachment inputs are significant here if we wish to use index properties to set them on our Action bean. The Action framework will understand the inputs to be indexable if the name contain "_n" where n is an integer. Once we have this, all you would need to do to your Action bean to get the attachments as a Collection type is specify an "indexed" setter like this (note the scalar getter is required for compliance with Java Bean spec, but is never called):

public class MyAction implements IAction {
public void setAttachments(int index, Object o)
{     //do something   }
public Object getAttachments(int index)
 { //will not be called }
public void execute() throws Exception {}
}

Alternatively, you can specify a getter method that returns a reference to the array to which the attachment object will be added, e.g.

public class MyAction implements IAction {
private List<Object> attachments = new ArrayList<Object>();
public List<Object> getAttachments()
{     return attachments;   }
public void execute() throws Exception {}
}

These two behaviors are employed by BeanUtils to implement what they call indexed properties. In summary,

  • if there is an indexed setter method bean utils will use that (note: a simple getter is required as well though it will not be invoked)
  • if there is an array-based getter like List<String> getNames(), bean utils will insert the new value into the array reference it gets from the array getter

Outputs

Normal Outputs

At the end of a step in an action sequence, the Action framework will attempt to retrieve data from your Action bean (per the action-outputs section of the action definition) by calling various getter methods. The framework will loop through the listed outputs and set them to the appropriate context. A discussion about the "appropriate" context is out of scope here, but suffice it to say the output data will be stored in a session-like structure and will be made accessible if you should chose to bind it to the input of a subsequent step, or bound in a way that will direct the data to the user for viewing or to a data sync such as a file.  You will notice that the type attribute is not required for action outputs, other than the special case "content" type which behaves very differently from other outputs.

xaction output

Java type

expected Action bean method (Java)

<myOutput> (no type specified)

user specified

public SomeType getMyOutput() { ...

Streaming Outputs

The content type output is treated like an input since the presence of a content type output implies that your Action bean intends to write to a pre-existing data stream. Prior to the invocation of your Action's execute method, this pre-existing stream is retrieved by the Action framework and passed to your Action bean as a java.io.OuputStream. If you intend your Action bean to be able to handle content type outputs, and thus writing to an output stream, then your Action bean is required to implement IStreamingAction. This API provides the Action framework with information about the type of data you are writing to the stream.

There is a special syntax for the setter of a streaming output. The Action framework will look for a setter with the word "Stream" is appended to the action definition output name. In the example below you see that the output name is "myOutput" yet the setter on the Action bean is "myOutputStream". This is intention and was done to help keep this special operation more clear. We didn't want to force action sequence writers to have to call their output any specific name, yet we wanted the setter to indicate what type it was accepting, since "setMyOuput", for example did not seem to be as clear as "setMyOutputStream". In any case, just note that your setter method name always need to end in "Stream".

xaction output

Java type

expected Action bean method (Java)

<myOutput type="content"/>

java.io.OutputStream

public void setMyOutputStream(OutputStream o)
{ ...

Resources

Resources are treated much like inputs in that they are processed prior to executing the Action bean. As illustrated below, when the Action framework finds a resource specified in the action-resources section of your action definition, it will get a handle to that resource in the form of an InputStream and hand that object to your Action bean.

xaction resource

Java type

expected Action bean method (Java)

<myResource type="resource"/>

java.io.InputStream

setMyResource(InputStream o) { ...

 

 


Plugging in Actions

Of course writing an Action does no good unless you can use it in an action sequence, right? Well the way you make the Pentaho BI Platform aware of your Action is to write a plugin. See the wiki page on developing plugins for more information.