Monday, April 28, 2014

PDI Plugin POJOs


I've been trying to figure out ways to make it dead-simple to create new plugins for Kettle / Pentaho Data Integration, and as a result I've got some GitHub projects using various approaches:

- pentaho-plugin-skeletons: Skeleton projects with the build artifacts and classes in-place, with heredoc explaining how to accomplish certain tasks. So far I only have the pdi-step-plugin-skeleton with a Gradle build and the heredoc is fairly lacking as I haven't had the time to fill in sample snippets. I also want to add a .travis.yml for folks that would like to use Travis for their CI needs.

- GroovyConsoleSpoonPlugin: This project serves three purposes: first, it can build a Spoon plugin that allows the user to bring up a Groovy Console within Spoon, which can access the entire environment and manipulate it in fairly cool ways (see my previous posts). Second, it can start a standalone Groovy Console that brings in Kettle dependencies in order to prototype features without a full running PDI environment. Third, it offers an internal DSL (based on Groovy of course) to make exploration of the PDI environment as simple as possible. This includes overloaded operators, additions to the Kettle API/SDK, and all sorts of helper methods/classes to make life easier. At some point I will likely move the DSL out of this project to a standalone project, but that won't be anytime soon I'm afraid.


- pdi-pojo: This project is the real subject of this blog post, and it aims to allow the PDI plugin developer to create a single class that extends some pdi-pojo class which provides all the boilerplate and "normal" handling of PDI plugin issues.


The pdi-pojo approach is to provide superclasses for common plugins (such as StepPluginPOJO) which provide delegates, default implementations, etc. for said plugins, thereby reducing the boilerplate code needed to get up and running with a new PDI plugin. This is accomplished by annotating fields in the subclass to indicate how they should be handled by the framework.


Here is an example (taken from the sample code in the project itself) for a StepPluginPOJO subclass that declares its fields as public members (you can also make them protected/private if there are bean getter/setter methods):

@Step( id = "TestStepPluginPOJO",
       image = "test-step-plugin-pojo.png",
       name = "StepPluginPOJO Test",
       description = "Tests the StepPluginPOJO",
       categoryDescription = "Experimental" )
public class TestStepPluginPOJO extends StepPluginPOJO {

@UI( label = "Enter value" )
public String testString;

@ExcludeMeta
public String testExcludeString; 


public int testInt;

public boolean testBool;

@UI( hint = "Checkbox" )
public boolean testBoolAsText;

@UI( label = "Cool bool", value = "true" )
public boolean testBoolWithLabel;

@UI( label = "Start date", hint = "Date" )
public Date startDate;

@UI( label = "End TOD", hint = "Time" )
public Date endTime;

@NewField
public String status;


The public member variables will be processed on initialization of the plugin to determine which are metadata fields, which need UI representation in the plugin's dialog, etc. By default, all public member variables are treated as metadata fields; to exclude a variable you use the ExcludeMeta annotation (see above).

Perhaps the most helpful of the annotations is the UI annotation, as this will indicate to the framework how to display and handle the graphical user element(s) associated with the member. If a UI annotation (or a hint as to how it should be displayed) is absent, the framework will choose a default representation. For example, a boolean member will be (by default) displayed as a checkbox, but a String is displayed as a text field (with Kettle variables allowed within).

For the members above, the following dialog is displayed:




Note how the "value" attribute of the UI annotation will set the default value (or a default is determined based on type).

These members are just for testing the various UI components and annotations; the only one I'm using in the code the is the "status" field, which is annotated as a NewField, which means the framework will add the metadata to the outgoing row, so all you have to do is find the index of that field by name using indexOf(), then storing your value into the output row at that index (see code below).

Although almost all of the boilerplate for a step plugin is taken care of by StepPluginPOJO, one method remains abstract: processRow(). This is ensure that your subclass is actually doing something :)

For the example, I'm basically creating a simplified version of the Add Constants step, by adding a field called "status" to the row and setting its value to the value specified in the dialog box.  Here is the body of the processRow() method, followed by a screen shot of the test transformation:

@Override
public boolean processRow( StepMetaInterface smi, StepDataInterface sdi ) throws KettleException {
  Object[] r = getRow(); // get row, set busy!
  // no more input to be expected...
  if ( r == null ) {
    setOutputDone();
    return false;
  }

  if ( first ) {
    first = false;
    outputRowMeta = getInputRowMeta().clone();
    getFields( outputRowMeta, getStepname(), null, null, this, repository, getMetaStore() );
  }


  // Allocate room on the row for the new fields
  Object[] newRow = RowDataUtil.resizeArray( r, r.length + getNewFields().size() );


  // Do processing here, add new field(s)
  int newFieldIndex = outputRowMeta.indexOfValue( "status" );
  if ( newFieldIndex == -1 ) {
    throw new KettleException( "Couldn't find field 'status' in output row!" );
  }
  newRow[newFieldIndex] = status;

  putRow( outputRowMeta, newRow ); // copy row to possible alternate rowset(s).

  if ( checkFeedback( getLinesRead() ) ) {
    if ( isBasic() ) {
      logBasic( "Lines read" + getLinesRead() );
    }
  }

  return true;
}




In this case I set the status variable to "Not sure", and my Generate Rows step created 10 rows with a String field named "x" containing the value "Hello". Here are the results, as expected:



I wondered what the performance impact would be for creating a step plugin with StepPluginPOJO versus writing the plugin by hand.  I tried to keep the processRow() as simple as possible while trying to match it up to an existing step (hence the Add Constants example).  I ran both the above transformation and its counterpart (with Add Constants replacing my test step) with a million rows, and for the most part I got the same performance. The biggest difference was a single run where the Add Constants version ran a million rows in 0.6 seconds and the TestStepPluginPOJO ran in 0.7 seconds.

Because most of the reflection is done at initialization and/or UI time, the performance should be close to writing the same plugin by hand.  Of course, if performance is your goal you should write the plugin by hand to fine-tune all aspects of its execution. This project is here for rapidly prototyping plugins.

Having said that, you can use pdi-pojo and incrementally move away from the provided code by overriding whichever methods you choose.  For example, if the auto-generated UI is too ugly or primitive for you, you can override the getDialogClassName() method and return one you implement by hand. If you want your own initialization code, override the init() method, and so on.

So there's lots more work to do, but I figure I have a good enough start to blog about this and to welcome any contributions to the project.  If you get a chance to try it out, please let me know how it works for you!  I will try to get the JAR onto Bintray or Sonatype or something like that shortly, but in the meantime just fork and clone my repo, run "gradle clean plugin", and then you can drop the JAR into whatever project you want (or publish it to your local Maven repo or whatever).

Cheers!

Thursday, March 6, 2014

Gradle Spoon Console Plugin

As my blog followers know, I've been trying to get a Groovy Console into Pentaho Data Integration's (PDI's) Spoon UI for quite a while now.  I haven't put it into the Marketplace as we're wrestling with Groovy versions and I wanted to offer something that worked out-of-the-box versus something that needed extra setup/config.  For PDI 5.1 this should be a moot point...

...then I realized, I've been trying to bring the Groovy Console to PDI.  Why not bring PDI to the Groovy Console?  This idea was borne from a blog post by Mike Hugo with a tip of the hat to carlosgsouza who created a Gradle plugin from it.

The idea of the post (and thus mine) is that you can leverage Gradle to fetch your dependencies and keep track of your needed classpath, then start a Groovy Console with that classpath passed in. Thus you can access your project's dependencies (APIs, etc.) via the console, allowing for prototyping, rapid development, etc. of features.

For PDI, my GroovyConsoleSpoonPlugin project already had the necessary requirements: it had PDI/Kettle dependencies, it leveraged the Groovy Console, and it used Groovy to provide an internal Domain-Specific Language (DSL) for interacting with the PDI environment. After fixing a few bugs in my project related to working with the Groovy Console outside of Spoon, I'm happy to announce that the first version of the Gradle Console for PDI is ready to roll :)

To use, get Gradle and then just clone my GitHub project and run "gradle console" (or if you don't want/have Gradle, go to your clone and use "gradlew console"). You will be presented with a Groovy Console with PDI core dependencies, steps, etc. available, as well as my first attempt at an internal DSL for building transformations and jobs (see earlier blog posts). To change the PDI version, just edit the build.gradle file looking for the "project.ext.kettle_dependency_revision" property (yes, I know that's deprecated ;)

Here's an example of creating and running a (VERY!) basic transformation. I hope to implement a Builder soon to fill in metadata, etc. :


I'm hoping to make this DSL (and the whole experience) as easy yet powerful as possible, please let me know your ideas!

PDI memcached plugins

I've definitely been neglecting this blog :) but I've recently put a couple of plugins into the PDI marketplace to read and write from memcached instances. I hope this leads to more key/value store support (I'm working on Hazelcast plugins right now) for PDI.

Anyway the Memcached Input/Output plugins allow for the reading and writing of key/value pairs to memcached instances.  The Input step requires incoming key names, and the type of the values that will come out:





The Output step requires and value field names (it interrogates the types from the row metadata):



This is just an initial offering, eventually I'd like to add other features (I'm always open to suggestions for improvement!). But I figured I should get a foot in the door for key/value store support :)  I also have been working on a Map (key/value) type for PDI, that should be out in 5.1 or before if I can get some supporting changes into the 5.0.x code line.

Please feel free to install this from the PDI Marketplace and let me know how it works for you and/or how it could improve :) I only tested this on PDI 5.0, so I'm not sure it will work on PDI 4.x but I don't see any reason it shouldn't.

Cheers!



Saturday, March 1, 2014

Groovy Memcached "client"

Ok so this post is not PDI related (yet, stay tuned :) but in my search for easy memcached client UIs I came up fairly shorthanded unless I wanted to buy something, write a Java client, or install a package.  All I needed to do for my test was to be able to set and get a couple of values and I didn't really want to start a new project, build a Java app, etc.

There's a very easy way to do this with Groovy Grapes and the spymemcached client (and the Groovy Console as a "UI"). Just @Grab the spymemcached JAR, connect a client, and off you go:


The script is as simple as this:

@Grab('net.spy:spymemcached:2.10.5')
import net.spy.memcached.MemcachedClient

def memcachedClient = new MemcachedClient( new InetSocketAddress('127.0.0.1', 11211 ) );

memcachedClient.set('myKey',3600, "Hello world!")
memcachedClient.set('intKey',3600, 45)

println "myKey = ${memcachedClient.get('myKey')}"
println "intKey = ${memcachedClient.get('intKey')}"


It's pretty easy to change this to a command-line interface (CLI) to take parameters for keys, expiration times, etc. Note I'm using the synchronous gets/sets but this is just for testing.

Anyway this came about while writing my Memcached Input/Output step plugins for PDI, keep your eye on the Marketplace, I hope to have them released this week. Cheers!


Thursday, March 14, 2013

Content Metadata UDJC step (using Apache Tika)

I recently stumbled across the Apache Tika project, which is a content analysis toolkit that offers great capabilities such as extracting metadata from various documents.  Depending on the document type, various kinds of metadata are available.  Some examples of metadata include MIME type, last modified date, author, publisher, etc.

I think (more) content analysis would be a great capability to add the Pentaho suite (especially Pentaho Data Integration), so I set out to write a quick UDJC step using Tika, followed by a sample transformation to extract document metadata using that step:



The first thing I noticed when I started writing the UDJC code to interface with Tika is that most of the useful code for recognizing document types and outputting to various formats is buried as private in a class called TikaCLI.  It appears the current state of Tika is such that it is meant to be used as a command-line tool that can be extended by adding your own content types, output formats, etc.  However, for this test I just wanted to be able to use Tika programmatically from my code.  Since Tika is licensed under Apache 2.0, I simply grabbed the sections of code I needed from TikaCLI and pasted them into my UDJC step.

The actual UDJC step processing basically does the following:

  1. Reads in a filename or URL and converts it (if necessary) to a URL
  2. Calls Tika methods to extract metadata from the document at the URL
  3. For each metadata item (a property/value pair), create a new row and add the property and value
  4. If Tika throws any errors and the step is connected to a error-handling step, send the error row(s)
I ran the sample transformation on my Downloads directory, here is a snippet of the output:


If you know ahead of time which metadata properties you want, you can use a Row Denormaliser step to have the properties become field names, and their values be the values in those fields.  This helps reduce the amount of output, since the denormalizer will output one row per document, whereas the UDJC step outputs one row per metadata property per document.  For my example transformation (see above), I chose the "Content-Type" property to denormalise.  Here is the output snippet corresponding to the same run as above:


Tika does a lot more than just metadata extraction, it can extract text from document formats such as Microsoft Word, PDF, etc. and it can even guess the language (English, French, e.g.) of the content.  Adding these features to PDI would be a great thing, and if I ever get the time, I will create a proper "Analyze Content" step, using as many of Tika's features as I can pack in :)  We could even integrate the Automatic Documentation Output functionality by adding content recognizers and such for PDI artifacts like jobs and transformations.

The sample transformation is on GitHub here.  As always, I welcome your questions, comments, and suggestions. If you try this out, let me know how it works for you. Cheers!

Friday, March 8, 2013

Pentaho Data Integration 4.4 and Hadoop 1.0.3

While working with a few new Hadoop-based technologies (blog posts to come later), the need arose to get Pentaho Data Integration (PDI) and its Big Data plugin (source available on GitHub) working with an Apache Hadoop 1.0.3 cluster.  Currently, PDI 4.4 only supports the following distributions (and any distributions compatible with them):


  • Apache Hadoop 0.20.x (hadoop-20)
  • Cloudera CDH3u4 (cdh3u4)
  • Cloudera CDH4 (cdh4)
  • MapR (mapr)


The values in parentheses in the list above are the folder names under the Big Data plugin's "hadoop-configurations", each of which contains JARs and other resources needed to run PDI against a particular distribution.  To select a distribution for PDI to use, you edit the plugin.properties file in the Big Data plugin's root folder and set the "active.hadoop.configuration" property to one of the folder names above.  The default setting is for Apache Hadoop 0.20.x:

active.hadoop.configuration=hadoop-20

Apache Hadoop 1.0.3 is not compatible with the Apache Hadoop 0.20.x line, and thus PDI doesn't work with 1.0.3 out-of-the-box.  So I set out to find a way to make that happen.

First, I simply copied the hadoop-20 folder to a "hadoop-103" folder in the same directory (pentaho-big-data-plugin/hadoop-configurations/).  Then I replaced the following JARs in the client/ subfolder with the versions from the Apache Hadoop 1.0.3 distribution:

commons-codec-<version>.jar
hadoop-core-<version>.jar

and I added the following JAR from the Hadoop 1.0.3 distribution to the client/ subfolder as well:

commons-configuration-<version>.jar

Then I changed the property in plugins.properties to point to my new folder:

active.hadoop.configuration=hadoop-103

Then I started PDI and was able to use steps like Hadoop Copy Files and Pentaho MapReduce (see the Wiki for How-Tos).

NOTE: I didn't try to get all functionality working or tested.  Specifically, I didn't try anything related to Hive, HBase, Sqoop, or Oozie.  For Hive, I'm hoping the PDI client will work against any Hive server running on an Apache Hadoop 0.20.x cluster, or any compatible configuration.  If I test any of these Hadoop technologies, I will update this blog post.

If you try this procedure (for 1.0.3, 1.0.x, or any other Hadoop distribution), let me know if it works for you, especially if you had to do anything I haven't listed here :)  Cheers!



Sunday, January 13, 2013

New PDI/Kettle project structure

In case you haven't heard, the Kettle project in Subversion has been restructured to be cleaner and to use Apache Ivy for dependency management.  This has been a long time coming, and PDI/Kettle is now more consistent with other Pentaho projects.  The "cut-over" from the old project to the new is scheduled to occur on Monday, January 14, 2013.

If you currently have changes in a working copy of Kettle trunk, you should not commit them into the new structure as it has changed.  For example, all the Kettle modules' source code used to reside in folders named src-<module_name>, such as src-core or src-db.  The modules have been reorganized such that you can check out and build individual modules if you choose.  So now each module has its own folder under the root, such as core/ and db/.  Inside these folders are src/ folders, which contain the same files and package structure as the old Kettle project.  So the files that used to be in src-core/ are now in core/src.

Other structural changes may impact your working copy as well. For example, the old "ui/" folder is now located at "assembly/package-res/", because "ui" is a Kettle module so the "ui" folder now contains the contents of "src-ui/".  The Ant build scripts have been updated to reflect this, and there is now a "create-dot-classpath" Ant target that will generate a ".classpath" and "<project_name>.launch" file to get you up and going in Eclipse.

For more information, consult the README.txt file in the root folder, as well as the readme.txt file in the plugins/ folder.

I wrote a quick Groovy script to try and provide a mapping of any changed files you may have in your current working copy, so you will know where to commit the changes. I tried to extend it to create diff files to be used as patches, but I could never get it to work very well, probably because I'm using Cygwin on Windows with its Subversion command-line client.  It shouldn't be too hard for Linux users familiar with Groovy to extend the following script to try and automate the patch creation process.

The script is very simple and looks like this:

def module_map = ['src-core':'core/src',
                  'src-db':'db/src',
                  'src-dbdialog':'dbdialog/src',
                  'src':'engine/src',
                  'src-jdbc':'jdbc/src',
                  'src-ui':'ui/src',
                  'src-plugins':'plugins',
                  'libext':'lib',
                  'ui':'assembly/package-res/ui']


'svn status'.execute().text.eachLine {line ->
    def svn_op = line.charAt(0)
    def old_fname = line.substring(1).trim()
    def path_segments = old_fname.split('/')
    def old_module = path_segments[0]
    def new_module = module_map.get(old_module)
    def file_mapping = new_module ? "$old_fname -> ${new_module}/${path_segments[1..-1].join('/')}" : old_fname
    println "${svn_op}\t${file_mapping}"
}

I also put the script on Gist here.

The module-map is the key to the location of the restructured files, the script simply calls "svn status" and for each changed file in your working copy, it will use the module-map to show the new location of the file.  Note that some files will not be mapped to a new location; this is either because the location hasn't changed, or because I forgot a mapping :-P

To use it, simply create a script called merge_helper.groovy (or whatever you want to call it) with the above contents and place it in your working copy of the old project structure.  Run the script with the command "groovy merge_helper.groovy" and it should show you output that will look something like this:

M       .classpath
M       src-plugins/market/src/org/pentaho/di/core/market/Market.java -> plugins/market/src/org/pentaho/di/core/market/Market.java
M       src-plugins/market/src/org/pentaho/di/core/market/messages/messages_en_US.properties -> plugins/market/src/org/pentaho/di/core/market/messages/messages_en_US.properties
M       src-plugins/market/src/org/pentaho/di/ui/spoon/dialog/MarketplaceDialog.java -> plugins/market/src/org/pentaho/di/ui/spoon/dialog/MarketplaceDialog.java
M       src-ui/org/pentaho/di/ui/spoon/job/JobGraph.java -> ui/src/org/pentaho/di/ui/spoon/job/JobGraph.java

If you have changes to ".classpath", be warned that there is no version-controlled file called ".classpath" any longer.  If you have new JARs or source folders to commit, please consult the README files for guidance on how to update the appropriate files. Also you can comment on this blog post with any questions about the migration process.

We hope you will find the new project structure easier to use, and the use of Apache Ivy will allow us to avoid many of the headaches that come with upgrading third-party JARs, especially to ensure that Pentaho products (which depend on each other) are using the same (or compatible) versions of their dependencies.

Cheers!