How to Create Custom CTL Functions

Introduction

In addition to the prepared CTL functions, users can build their own CTL functions. To do that, they need to write their own code defining the custom CTL functions and specify its plugin.

Code of Custom CTL Functions

Each custom CTL function library must be derived/inherited from org.jetel.interpreter.extensions.TLFunctionLibrary class.
Each custom CTL function must be derived/inhereted from org.jetel.interpreter.extensions.TLFunctionPrototype class.
These classes have some standard operations defined and several abstract methods which need to be defined so as the custom functions may be used.
Within the custom functions code, an existing context must be used or some custom context must be defined. The context serves to store objects when function is to be executed repeatedly, in other words, on more records.
Example of such custom functions code follows:

Code example with comments

package org.mypackage.ctlfunctions;
 
 
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
 
import org.jetel.interpreter.TransformLangExecutorRuntimeException;
import org.jetel.interpreter.data.TLNullValue;
import org.jetel.interpreter.data.TLNumericValue;
import org.jetel.interpreter.data.TLStringValue;
import org.jetel.interpreter.data.TLValue;
import org.jetel.interpreter.data.TLValueType;
import org.jetel.interpreter.extensions.TLContext;
import org.jetel.interpreter.extensions.TLFunctionLibrary;
import org.jetel.interpreter.extensions.TLFunctionPrototype;
 
public class MyCTLFunctionsLib extends TLFunctionLibrary {
 
	private static final String LIBRARY_NAME = "MyCTLFunctions";
 
	enum Function {
        MY_FUNCTION1("my_function1_name"),
        MY_FUNCTION2("my_function2_name");
 
        public String name;
 
        private Function(String name) {
            this.name = name;
        }
 
        public static Function fromString(String s) {
            for(Function function : Function.values()) {
                if(s.equalsIgnoreCase(function.name) || s.equalsIgnoreCase(LIBRARY_NAME + "." + function.name)) {
                    return function;
                }
            }
            return null;
        }
    }
 
	public MyCTLFunctionsLib(){
		super();
	}
 
    public TLFunctionPrototype getFunction(String functionName) {
        switch(Function.fromString(functionName)) {
        case MY_FUNCTION1: return new MyFunction1();
        case MY_FUNCTION2: return new MyFunction2();
        default: return null;
       }
    }
 
    /* (non-Javadoc)
	 * @see org.jetel.interpreter.extensions.ITLFunctionLibrary#getAllFunctions()
	 */
	public Collection<TLFunctionPrototype> getAllFunctions() {
    	List<TLFunctionPrototype> ret = new ArrayList<TLFunctionPrototype>();
    	Function[] fun = Function.values();
    	for (Function function : fun) {
    		ret.add(getFunction(function.name));
		}
 
    	return ret;
	}
 
    class MyFunction1 extends TLFunctionPrototype {
 
        public MyFunction1() {
            super(LIBRARY_NAME, "my_function1_name", "Description of my function 1",
            		new TLValueType[] { TLValueType.DATE, TLValueType.STRING }, //MyFunction1 has 2 input parameters: 1st is date type,2nd is string type
                    TLValueType.STRING);//MyFunction1 returns string
        }
 
        @Override
        public TLValue execute(TLValue[] params, TLContext context) {//input parameters and context (the one that is created by createContext() method)
        	//because we use string context, context in context is TLStringValue
        	//we will fulfill the value by the result of our function
        	TLStringValue val = (TLStringValue)context.context;
            StringBuilder strBuf = (StringBuilder)val.getValue();
            strBuf.setLength(0);
 
            //checking input parameters' types
            if (params[0]==TLNullValue.getInstance() || params[1]==TLNullValue.getInstance()) {
                throw new TransformLangExecutorRuntimeException(params,
                        Function.MY_FUNCTION1.name()+" - NULL value not allowed");
            }
            if (params[0].type!=TLValueType.DATE || params[1].type!=TLValueType.STRING)
                throw new TransformLangExecutorRuntimeException(params,
                		Function.MY_FUNCTION1.name() + " - wrong type of literal");
 
            String result = "MyFunction1 result";
            //TODO fullfil the result
            strBuf.append(result);
            //val contains StringBuffer we have just fulfilled 
            return val;
        }
 
        @Override
        public TLContext createContext() {
        	//You can use one of pre-defined context or create your own context (see next example)
 
//        	return TLContext.createByteContext(); - contains TLByteArrayValue - with default initial size (INITIAL_BYTE_ARRAY_CAPACITY = 8)
//        	return TLContext.createDateContext(); - contains TLDateValue 
//        	return TLContext.createDoubleContext(); - contains TLNumericValue<CloverDouble> - used for storing double values
//        	return TLContext.createIntegerContext(); - contains TLNumericValue<CloverInteger> - used for storing integer values
//        	return TLContext.createListContext(); - contains TLListValue with empty list
//        	return TLContext.createLongContext(); - contains TLNumericValue<CloverLong> - used for storing long values
//        	return TLContext.createNullContext(); - empty context - can be used when there are no object for repeated usage
            return TLContext.createStringContext();//contains TLStringValue - StringBuffer can be re-used
        }
    }
 
    class MyFunction2 extends TLFunctionPrototype {
 
        public MyFunction2() {
            super(LIBRARY_NAME, "my_function2_name", "Description of my function 2", 
            		new TLValueType[] { TLValueType.DECIMAL, TLValueType.STRING}, //1st input parameter is of type DECIMAL, 2nd (if present - see next line) is of type STRING
                    TLValueType.STRING, 2,1);//MyFunction1 returns string, it accepts 2 or 1 input parameters
        }
 
        @Override
        public TLValue execute(TLValue[] params, TLContext context) {//input parameters and context (the one that is created by createContext() method)
        	//in context we have TLValue (exactly TLStringValue - see Num2StrContext) and DecimalFormat 
        	//- both can be reused in function execution for different records
            TLValue val = ((Num2StrContext)context.getContext()).value;
            DecimalFormat format =((Num2StrContext)context.getContext()).format;
            TLValue input=params[0];
            StringBuilder strBuf = (StringBuilder)val.getValue();
            strBuf.setLength(0);
 
            //checking input parameters
            if (params[0]==TLNullValue.getInstance()) {
                throw new TransformLangExecutorRuntimeException(params,
                        Function.MY_FUNCTION2.name()+" - NULL value not allowed");
            }
            Integer radix = null;
            String formatString = null;
            //checking number of parameters too
            if (params.length>1) {
                if (params[1]==TLNullValue.getInstance()){
                    throw new TransformLangExecutorRuntimeException(params,
                        Function.MY_FUNCTION2.name()+" - wrong type of literals");
                }
                //if second parameter is numeric we use it as radix
                if (params[1].getType().isNumeric()) {
					radix = ((TLNumericValue) params[1]).getInt();
				//if second parameter is string we use it as format string
				}else if (params[1].getType() == TLValueType.STRING) {
					formatString = params[1].toString();
				}else {
                    throw new TransformLangExecutorRuntimeException(params,
                            Function.MY_FUNCTION2.name()+" - wrong type of literals");
				}
            }
 
            //body of the function
            if (radix == null && formatString == null) {
            	radix = 10;
            }
 
            if (radix != null) {
                if (radix == 10) {
                    strBuf.append(input.toString());
                } else {
                    switch(input.type) {
                    case INTEGER:
                        strBuf.append(Integer.toString(((TLNumericValue)input).getInt(),radix));
                        break;
                    case LONG:
                        strBuf.append(Long.toString(((TLNumericValue)input).getLong(),radix));
                        break;
                    case NUMBER:
                        strBuf.append(Double.toHexString(((TLNumericValue)input).getDouble()));
                        break;
                    default:
                    throw new TransformLangExecutorRuntimeException(params,
                            Function.MY_FUNCTION2.name()+" - can't convert number to string using specified radix");
                    }
                }
             } else {//radix == null --> formatString != null
            	 if (!format.toPattern().equals(formatString)) {
            		 format.applyPattern(formatString);
            	 }
            	 strBuf.append(format.format(((TLNumericValue)input).getDouble()));
            }
 
            //val contains StringBuffer that we have just fulfilled (in the body of the function)
            return val;
        }
 
        @Override
        public TLContext createContext() {
            return Num2StrContext.createContex();
        }
    }
}
 
 
/**
 * This class demonstrates how to create your own context.
 * In context you should store objects that can be reused in every execution of your function.
 * This saves time when executing the same function many times.
 * 
 * @author Agata Vackova (agata.vackova@javlinconsulting.cz)
 *         (c) Javlin Consulting (www.javlinconsulting.cz)
 *
 * @since Feb 25, 2009
 */
class Num2StrContext {
    TLValue value;
    DecimalFormat format;
 
    static TLContext createContex(){
        Num2StrContext con=new Num2StrContext();
        con.value=TLValue.create(TLValueType.STRING);
        con.format= new DecimalFormat();
 
        TLContext<Num2StrContext> context=new TLContext<Num2StrContext>();
        context.setContext(con);
 
        return context;        	
    }
}

Now the source code needs to be compiled:

javac MyCTLFunctionsLib.java
and received class files can be packed to jar file:

jar cf my_lib.jar *.class

Defining Plugin for Custom Function


Along with the custom functions code, you also need to define the custom functions plugin. The example follows:

<plugin
   id="org.mypackage.ctlfunctions"
   version="1">
 
   	<runtime>
		<library path="my_lib.jar"/>
	</runtime>
 
 
	<extension point-id="tlfunction">
		<parameter id="libraryName" value="MyCTLFunctions"/>
		<parameter id="className" value="org.mypackage.ctlfunctions.MyCTLFunctionsLib"/>
		<parameter id="function">
			<value>my_function1_name</value>
			<value>my_function2_name</value>
		</parameter>
	</extension>
 
</plugin>

Adding the plugin to CloverETL plugins directory

Now you need to put created files to CloverETL plugins directory. It is …\cloverETL\plugins\ for CloverETL Engine or ..\eclipse\plugins\com.cloveretl.gui_x.x.x\lib\plugins\ for CloverETL Designer. Just create new directory and put there plugin.xml and my_lib.jar. If your library requires any other libraries put them in nested lib directory and add them to the runtime tag in plugin.xml file.

function_building.txt · Last modified: 2010/05/19 12:47 by avackova
Back to top
chimeric.de = chi`s home Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0