Tuesday, June 11, 2013

Smart Setters in Talend Open Studio Custom Components

Smart Setters in Talend Open Studio Custom Components

A Talend Custom Component allows you to integrate your custom code into Talend Open Studio.  This done by creating several files based on JET (Java Emitter Template): begin, main, end.  However, working with JET in complex examples is difficult because the readability is poor. Looping and branching between the JET (Java) code at generation time becomes confused with the Java code used at runtime.

This code creates a bean of type 'ScriptRulesBean' and sets a property on the bean describing the input row.

// begin.javajet
<%
    List< ? extends IConnection > in_conns = node.getIncomingConnections();
    String inputRowName = null;  
    if(in_conns!=null && in_conns.size()>0){
       IConnection in_conn = in_conns.get(0);
       inputRowName = in_conn.getName();
    }
%>

com.bekwam.talend.component.scriptrules.ScriptRulesBean rulesBean_<%= cid %> =
new com.bekwam.talend.component.scriptrules.ScriptRulesBean();
 
<%
     if( inputRowName != null ) {
%>
       rulesBean_<%= cid %>.setInputRowClass( <%= inputRowName %>.getClass() );
<%
     }
%>

The JET code is bracketed by <% %> and is evaluated when the code behind a Talend Open Studio job is generated.  The code in brackets is not present at runtime.  From the Code View of Talend Open Studio, all that remains when the job is created and ready to run are the lines referencing 'rulesBean'.

// code in tScriptRulesTestJob.java
com.bekwam.talend.component.scriptrules.ScriptRulesBean rulesBean_tScriptRules_1 = new com.bekwam.talend.component.scriptrules.ScriptRulesBean();

rulesBean_tScriptRules_1.setInputRowClass(row1.getClass());


<%= cid %> has been substituted with a component's name to distinguish this usage from other components in the job.  The block containing List is used to set a variable 'inputRowName' and not present in the runtime version.  <%= inputRowName %> is first checked for null and then substituted for the actual object variable.  If <%= inputRowName %> evaluated to null, then the line containing setInputRowClass() would be omitted from the generated class.

The setter used in rulesBean only sets a property.

// ScriptRulesBean.java

private Class inputRowClass;

public void setInputRowClass(Class inputRowClass) {

this.inputRowClass = inputRowClass;
}


Strategy

 As I'm coding a Version 2 of a custom component 'tScriptRules', I'm following a strategy of removing as much difficult-to-read JET code and pushing it into a Java class that's packaged in a JAR. This has the additional benefit of getting more code into my Java build environment where more can be tested using solid tools like JUnit and Cobertura.


Smart Setter

To act on this strategy, I made the setter on ScriptRulesBean 'smarter' by having it check for a null argument and doing the getClass() call only on a non-null object.   I overload the method to take an object.  I keep around the previous version which matches the type of the property.  It's now called by the object parameter version.

// ScriptRulesBean.java
public void setInputRowClass(Object row) {
   if( row == null ) return;
   setInputRowClass( row.getClass() );
}

And the begin.javajet file is reduced

// begin.javajet
<%
   List< ? extends IConnection > in_conns = node.getIncomingConnections();


   String inputRowName = null;  
   if(in_conns!=null && in_conns.size()>0){
      IConnection in_conn = in_conns.get(0);
      inputRowName = in_conn.getName();
   }
%>

com.bekwam.talend.component.scriptrules.ScriptRulesBean rulesBean_<%= cid %> =
new com.bekwam.talend.component.scriptrules.ScriptRulesBean();
 
rulesBean_<%= cid %>.setInputRowClass( <%= inputRowName %> );

Most setter methods I see simply set a Java Bean property.  'Smart Setter' refers to a setter that does more than than a single assignment.

Result

The second version removes the inputRowName != null check from JET and the closing bracket.  Also, the getClass() is no longer appended to <%= inputRowName %>.  More problems are caught when I compile the class for a supporting JAR and run a unit test.

This savings is actually three-fold, because in the real version, I'm repeating this code for the filter and reject connections.

JET is based on Java, but it can be tough to read because it needs to be surrounded by brackets.  The substitutions aren't always clear if there is a lot of looping within JET and the runtime Java code.  (This is especially true in the main.javajets.)  Where possible, push as much code into a Java Bean that is carried through the component's API (begin/end/main).

Epilogue

Removed.  I'm not using a third setter form.

No comments: