Component Java Definition

First, a Java class file with the component definition must be created. To do that, select FileNewJava Project from the main menu of the Eclipse Platform.

Creating a Java Project

Figure 1.1. Creating a Java Project


Type the name of the component Java project. The project can be named arbitrarily. Here, we have chosen org.company.components name.

Giving a Name to the Java Project

Figure 1.2. Giving a Name to the Java Project


Click Finish. The following window opens:

Java Project Has Been Created

Figure 1.3. Java Project Has Been Created


Right-click the org.company.components item and select NewPackage from the context menu.

Creating a New Java Package

Figure 1.4. Creating a New Java Package


After that, type the name of the package in the proper field. Here, we use the name of the project, i.e., org.company.components. However, you can choose any other name. Click Finish then.

Giving a Name to the Java Package

Figure 1.5. Giving a Name to the Java Package


The org.company.components package appears in the src subfolder in the Navigator pane:

Java Package Has Been Created

Figure 1.6. Java Package Has Been Created


Custom components in CloverDX extend CloverDX classes contained in CloverDX Designer plugin. For this reason, you must add corresponding CloverDX jar files to build path. To do that, right-click the project item and select Properties from the context menu or simply click Alt+Enter.

Opening the Properties Wizard

Figure 1.7. Opening the Properties Wizard


In the following window, select the Java Build Path item and switch to the Libraries tab.

Default Java Build Path

Figure 1.8. Default Java Build Path


Click the Add External JARs... button, locate the cloveretl.engine.jar file and click Open.

The cloveretl.engine.jar file is located in the following folder:

pathtoeclipse\eclipse\plugins\com.cloveretl.gui_3.0.0\lib\lib\.

If you want to use some other jars for defining connections, sequences, lookup tables, etc., add corresponding .jars too.

In older versions of Clover than 2.0.x, the same plugin was cz.opentech.cloveretl.plugin. From now it is com.cloveretl.gui.

Selecting Clover Jar Files

Figure 1.9. Selecting Clover Jar Files


After that, you will see the added .jars listed in the Referenced Libraries item in the Navigator pane as well as in the Libraries tab:

Adding Jars to the Java Build Path

Figure 1.10. Adding Jars to the Java Build Path


Now expand the project name, right-click the package name and select NewClass from the context menu.

Creating a Java Class File

Figure 1.11. Creating a Java Class File


You will be prompted to type the name of your new Java class file. We have chosen the name of the custom component, i.e., NewComponent was typed.

As every component has to be derived/inherited from org.jetel.graph.Node class, rewrite the prompted java.lang.Object superclass. This superclass defines some standard component operations and several abstract methods that must be implemented to enable executing the component.

Selecting Clover Superclass

Figure 1.12. Selecting Clover Superclass


Click Finish. A new basic Java file appears in your org.company.components package. You will see it opened in the editor at the same time too.

Basic Java File Content

Figure 1.13. Basic Java File Content


Now, write the component Java definition into this file. The component class name will be the following: NewComponent.class.

Your component must fulfil some necessary requirements. Here is an example of a Java code with comments. You should follow this structure. (the code is taken from the SimpleCopy component of the SIMPLE_COPY type).

We have replaced the SimpleCopy with NewComponent, and SIMPLE_COPY with NEW_COMPONENT

package org.company.components;

import java.nio.ByteBuffer;

import org.jetel.data.Defaults;
import org.jetel.exception.ComponentNotReadyException;
import org.jetel.exception.ConfigurationProblem;
import org.jetel.exception.ConfigurationStatus;
import org.jetel.exception.XMLConfigurationException;
import org.jetel.graph.InputPortDirect;
import org.jetel.graph.Node;
import org.jetel.graph.Result;
import org.jetel.graph.TransformationGraph;
import org.jetel.util.SynchronizeUtils;
import org.jetel.util.property.ComponentXMLAttributes;
import org.jetel.util.string.StringUtils;
import org.w3c.dom.Element;


public class NewComponent extends Node {

	/** Following line defines component type or identification name:*/
	
	public final static String COMPONENT_TYPE = "NEW_COMPONENT";
	
	/** This component expects all input data to be on port 0:*/
	
	private final static int READ_FROM_PORT = 0;

  /** 
   *  Everything is written to port 0. Well, this value is actually not used as we use different
   *  method which sends data to all connected output ports.
   */
	private final static int WRITE_TO_PORT = 0;

	private ByteBuffer recordBuffer;

	/** 
	 *  Simple constructor - everything is handled by super class
	 *  In case we need some additional initialization, it is done in init() method 
	 */
	public NewComponent(String id) {
		super(id);
	}

  /** Following method is mostly used by ComponentFactory when creating instance of this class */
	 
	public String getType() {
		return COMPONENT_TYPE;
	}


	/** 
	 *  This method is called prior to starting component. Any allocation and checking should be
	 *  done here. If anything goes wrong, it should throw ComponentNotReadyException.
	 */
	public void init() throws ComponentNotReadyException {
        if(isInitialized()) return;
		super.init();
		
		recordBuffer = ByteBuffer.allocateDirect(Defaults.Record.MAX_RECORD_SIZE);
		if (recordBuffer == null) {
			throw new ComponentNotReadyException("Can NOT allocate internal record buffer ! Required size:"
					+ Defaults.Record.MAX_RECORD_SIZE);
		}
	}

  /** 
   *  This is the main processing method of a component. Node and subsequently this component is 
   *  inherited from Thread class. By implementing run() method, we define what is the thread  
   *  going to do. After the graph is initialized (by calling init() methods of all registered  
   *  components), for every component in graph, there is a thread started and it executes run() 
   *  method.
   */
  	@Override
	public Result execute() throws Exception {
	  /** Bring in InputPort from which we expect to read data */  
		InputPortDirect inPort = (InputPortDirect) getInputPort(READ_FROM_PORT);
		
		/** 
		 *  Main processing loop starts here. The variable runIt is true unless stop() method of. Node 
		 *  class is called. This is nondestructive way of stopping component. The other possibility is
		 *  to call abort(), which kills the thread. immediately with all the consequences.
		 */
		while (inPort.readRecordDirect(recordBuffer) && runIt) {
		   /** 
		    *  We try to read in one data record from input port. If the method readRecord() returns 
		    *  false, it means that no data is available and we finish the execution loop. Otherwise, we 
		    *  use writeRecordBroadcas() method which sends data record to all connected output ports.
		  	*/
			writeRecordBroadcastDirect(recordBuffer);
			SynchronizeUtils.cloverYield();
		}
		    	/** 
		    	 * We have to determine whether component/main loop finished because of extrenal interrupt -
		    	 * stop() method call or because we used up all available input data.
		         */ 
        return runIt ? Result.FINISHED_OK : Result.ABORTED;
	}
	
	@Override
	public synchronized void reset() throws ComponentNotReadyException {
		super.reset();		
		//DO NOTHING
	}

	/**
	 *  This method converts component's configuration into XML, so it can be later read in.
	 *  No need to add anything here since NewComponent has no configuration parameters.
	 */
	public void toXML(Element xmlElement) {
		super.toXML(xmlElement);
	}

	/**
	 *  This method is responsible for getting all parameters needed for component constructor from 
	 *  XML Node/Tag attributes. This method is declared as static, because it is an other way how to 
	 *  created component instance aside calling directly constructor.
	 *  It is heavily used by ComponentFactory class and TransformationGraphXMLReaderWriter.
	 */
	   public static Node fromXML(TransformationGraph graph, Element xmlElement) 
	                                            throws XMLConfigurationException {
		ComponentXMLAttributes xattribs = new ComponentXMLAttributes(xmlElement, graph);

		try {
			return new NewComponent(xattribs.getString(XML_ID_ATTRIBUTE));
		} catch (Exception ex) {
	           throw new XMLConfigurationException(COMPONENT_TYPE + ":" 
	           + xattribs.getString(XML_ID_ATTRIBUTE," unknown ID ") + ":" + ex.getMessage(),ex);
		}
	}

		/**
		 *  Following method is used by TransformationGraph class when the graph is initialized.
		 *  This method is responsible for checking input output ports.      
	     */
    @Override
   public ConfigurationStatus checkConfig(ConfigurationStatus status) {
    		  super.checkConfig(status);
   		 
    		if(!checkInputPorts(status, 1, 1)
    				   || !checkOutputPorts(status, 1, Integer.MAX_VALUE)) {
    		   	return status;
    	   }
        checkMetadata(status, getInMetadata(), getOutMetadata(), false);

        try {
              init();
        } catch (ComponentNotReadyException e) {
              ConfigurationProblem problem = new ConfigurationProblem(e.getMessage(), 
                  ConfigurationStatus.Severity.ERROR, this, ConfigurationStatus.Priority.NORMAL);
              if(!StringUtils.isEmpty(e.getAttributeName())) {
                    problem.setAttributeName(e.getAttributeName());
              }
                status.add(problem);
          } finally {
            	free();
            }
            
            return status;
        }

	}
}

By default, Eclipse is in auto-build mode and takes care of compiling source files on-the-fly. Builds occur automatically in the background every time you change files in the workspace (for example saving an editor). The .java files are translated into .class file that will be written to the output folder. The default output folder is the bin folder that can be seen not in the Java perspective, but the CloverDX one, e.g.