Home | Table of Contents | CustomJavaComponent | CloverETL 4.6.1
--- | --- | ---
Prev | Others | Next

## CustomJavaComponent

# Available in Community Designer

Short Description
Ports
Metadata
CustomJavaComponent Attributes
Details
Public Clover API
Examples
Best Practices
Compatibility
See also

### Short Description

CustomJavaComponent executes user-defined Java code.

Component | Same input metadata | Sorted inputs | Inputs | Outputs | Each to all outputs | Java | CTL | Auto-propagated metadata
--- | --- | --- | --- | --- | --- | --- | --- | ---
CustomJavaComponent | - | - | 0-n | 0-n | - |  |  |

### Icon

### Ports

Number of ports depends on the Java code.

### Metadata

CustomJavaComponent does not propagate metadata.

CustomJavaComponent has no metadata templates.

Requirements on metadata depend on user-defined transformation.

### CustomJavaComponent Attributes

Attribute | Req | Description | Possible values
--- | --- | --- | ---
Basic
Algorithm | [1] | Runnable transformation in Java defined in the graph. |
Algorithm URL | [1] | External file defining the runnable transformation in Java. |
Algorithm class | [1] | External runnable transformation class. |
Algorithm source charset |  | Encoding of external file defining the transformation. The default encoding depends on DEFAULT_SOURCE_CODE_CHARSET in defaultProperties. | E.g. UTF-8
[1] One of these must be set. These transformation attributes must be specified.

### Details

CustomJavaComponent executes Java transformation.

There are specialized custom java components: CustomJavaReader, CustomJavaWriter and CustomJavaTransformer. These components differ just in provided java template.

You can use Public Clover API in this component. See Public Clover API.

#### External JAR Files

The default folder for external `.jar` files in a local project is `./lib`.

On a server, external `.jar` files can also be placed on the classpath of the application container.

You should add the `.jar` files to `classpath`. Open Project Properties dialog Project → Properties. Switch to Java Build Path → Libraries. Click Add JARs... and select the `.jar` files.

#### Running on Cluster

All `.java` and `.class` files should reside in shared sandbox.

#### Editing Code in Another Tab

If you click the Algorithm attribute value, dialog for editing of build-in java code opens. Use Switch to Java editor button to convert the transformation in Java to `.java` file. The file is opened as a new tab having java editor with syntax highlighting associated.

#### Java Interfaces for CustomJavaComponent

Transformation required by the component must extend `org.jetel.component.AbstractGenericTransform` class.

Following are the methods of the `AbstractGenericTransform` class:

- `ConfigurationStatus checkConfig(ConfigurationStatus status)`

  Use this method to check configuration of custom component: custom attributes and their values, ports and metadata.

- `void execute()`

  Define your transformation here. The method is called once when the component is started.

- `void init()`

  Initializes Java class/function. This method is called only once at the beginning of transformation process. Any object allocation/initialization should happen here.

- `void preExecute()`

  This is also initialization method, which is invoked before each separate graph run. Contrary the init() procedure here should be allocated only resources for this graph run. All here allocated resources should be released in the `postExecute()` method.

- `void postExecute()`

  This is de-initialization method for a single graph run. All resources allocated in the `preExecute()` method should be released here. It is guaranteed that this method is invoked after graph finish at the latest. For some graph elements, for instance components, is this method called immediately after phase finish.

- `File getFile(String fileUrl)`

  Returns file for given file URL.

- `InputStream getInputStream(String fileUrl)`

  Returns `InputStream` for given file URL.

- `OutputStream getOutputStream(String fileUrl, boolean append)`

  Returns `OutputStream` for given file URL.

### Public Clover API

Data Record
Data Field
Metadata
Dictionaries
Lookup Tables
Graph Parameters
Component Attributes
Sequences
Database Connections
Opening Streams
Logging

Public Clover API is a set of CloverETL Java classes you can use in transformations in CustomJavaComponent and other components using Java transformation.

Public clover API uses `@CloverPublicAPI` annotation. Classes annotated by `@CloverPublicAPI` are part of the API and can be used in your transformation. Details on particular classes are documented in javadoc. The following pieces of code serve to point to particular classes suitable for particular purpose.

You can use standard Java classes and classes provided by the API in your transformations. Do not use CloverETL Java classes not included in the API! Classes not included in the API may be changed in the next release or removed.

#### Data Record

One single record is represented by `DataRecord` class.

    DataRecord record;

To create data record not connected to any particular port use static method `newRecord()` of `DataRecordFactory`. It requires record metadata.

    String metadataId = getGraph().getDataRecordMetadataByName("recordName1");
    DataRecordMetadata metadata = getGraph().getDataRecordMetadata(metadataId);
    DataRecord record = DataRecordFactory.newRecord(metadata);

To read a record from input port use `readRecordFromPort()` function. Index of input port (starting from 0) is specified in parameter of the function.

    record = readRecordFromPort(0);

The function returns `null` if no other records are available.

    while ((record = readRecordFromPort(0)) != null) {
     	// Do something
    }

To write a record to output port use `writeRecordToPort()` function. Parameters define index of output port and record to be written.

    writeRecordToPort(0, record);

If you create a component with variable number of input or output ports, use

    getComponent().getInputPortsMaxIndex()

or

    getComponent().getOutputPortsMaxIndex()

to get maximal index of input or output ports.

Note that first input port has index 0. A component with N input ports has N-1 as a maximal index of input port.

#### Data Field

Data field is represented by `DataField` class. You can get fields of data record using `getFields()` of `DataRecord`. You can get particular field using `getField()` taking field index or field name as a parameter.

    DataRecord record;
    DataField dataField1;
    dataField1 = record.getField(0);
    DataField dataField2;
    dataField2 = record.getField("field2");

Use `getValue()` and `setValue()` methods of `DataField` to work with field values.

    DataField field = record.getField(0);
    String value = field.getValue();
    field.setValue("some new value");

#### Metadata

There are two classes necessary to work with metadata: `DataRecordMetadata` and `DataFieldMetadata`. Class `DataRecordMetadata` represents metadata of the whole record whereas `DataFieldMetadata` represents metadata of particular field.

Use `getMetadata()` method of `DataRecord` to get access to metadata of a record.

    DataRecord record;
    record = ...
    DataRecordMetadata metadata = record.getMetadata();

Use `getMetadata()` method of `DataField` to get access to metadata of a field.

    DataField dataField;
    dataField = ...
    DataFieldMetadata fieldMetadata = dataField.getMetadata();

To use metadata depending on its name use `getDataRecordMetadataByName()` to get metadata id and subsequently use `getDataRecordMetadata()` to get metadata corresponding to the id.

    String metadataId = getGraph().getDataRecordMetadataByName("recordName1");
    DataRecordMetadata metadata = getGraph().getDataRecordMetadata(metadataId);

#### Dictionaries

To read value from dictionary use `getValue()` function, to write to from dictionary use `setValue()` function:

    Dictionary dictionary = getGraph().getDictionary();
    dictionary.setValue("mykey1", "NewValue");
    String s = dictionary.getValue("mykey2");

#### Lookup Tables

Interface `LookupTable` gives you an access to lookup tables. Use `put()` to insert data record into an existing lookup table. Note that `getLookupTable()` requires lookup table ID. The parameter is not lookup table name!

    LookupTable lookup = getGraph().getLookupTable("LookupTable0");
    DataRecord record = ...;
    lookup.put(record);

Use `createLookup()` to search for items matching the key.

    LookupTable lt;
    lt = ...

    DataRecord patternRecord = DataRecordFactory.newRecord(lt.getMetadata());
    patternRecord.getField(0).setValue("keyToBeSearchedPart1");
    patternRecord.getField(2).setValue("keyToBeSearchedPart2");
    String [] lookupFields = {"field1", "field3"};
    RecordKey recordKey = new RecordKey(lookupFields, lt.getMetadata());
    Lookup lookup;
    lookup = lt.createLookup(recordKey, patternRecord);
    lookup.seek();

    while (lookup.hasNext()){
     	DataRecord record = lookup.next();
     	// process the result found
     	writeRecordToPort(0, record);
    }

#### Graph Parameters

Graph parameters can be obtained from `TransformationGraph` using `getGraphParameters()`. To get particular parameter use `getGraphParameter()` with parameter name.

    TransformationGraph graph = getGraph();
    GraphParameters graphParameters = graph.getGraphParameters();
    GraphParameter graphParameter = graphParameters.getGraphParameter("MY_PARAMETER");

Use `getValue()` or `getValueResolved()` to get parameter value.

    String value = graphParameter.getValue();
    String valueResolved = graphParameter.getValueResolved(RefResFlag.REGULAR);

#### Component Attributes

You can get value of component attributes using `getProperty()` functions applied on `TypedProperties`.

    String myStringValue = getProperties().getStringProperty("myCustomPropertyName1");

    Integer myIntegerValue = getProperties().getIntProperty("myCustomPropertyName2");

#### Sequences

Sequence is accessible from `TransformationGraph` via `getSequence()` function with sequence ID as a parameter.

    Sequence seq = getGraph().getSequence("Sequence0");

Use `nextValueInt()`, `nextValueLong()` or `nextValueString()` to increment the sequence and return the incremented value. First call of any of the `nextValue*()` functions initializes the sequence to the initial sequence value and returns unincremented initial value.

    String sequenceValue = seq.nextValueString();
    int sequenceValueInt = seq.nextValueInt();
    long sequenceValueLong = seq.nextValueLong();

To get last value returned by functions above use `currentValueInt()` `currentValueLong()` or `currentValueString()`. If none of the `nextValue*()` functions have been called before, current value is the start value of the sequence.

    String sequenceValue = seq.currentValueString();
    int sequenceValueInt = seq.currentValueInt();
    long sequenceValueLong = seq.currentValueLong();

#### Database Connections

Database connections are accessible via `getConnection()` method of `TransformationGraph`. The method requires connection ID as a parameter.

`getConnection()` of `DBConnection` requires an identifier unique within the graph. You are suggested to use e.g. component ID.

    IConnection connection = getGraph().getConnection("JDBC0");
    if (connection instanceof DBConnection){
     	DBConnection dbconnection = (DBConnection) connection;
     	SqlConnection sqlconnection = dbconnection.getConnection("myUniqueID");
     	...
    }

`SqlConnection` extends `java.sql.Connection`. See documentation on `java.sql.Connection`.

#### Opening Streams

If you work with paths, use `getFile()` function to resolve the path correctly.

    String param = getProperties().getStringProperty("InputFile");
    File file = getFile(param);

You can access files via streams. Use `getOutputStream()` or `getInputStream();`

    String param = getProperties().getStringProperty("InputFile");
    InputStream is = getInputStream(param);

    String param = getProperties().getStringProperty("OutputFile");
    OutputStream os = getOutputStream(param, true);

#### Logging

Use `log()` function to log messages of important events of you Java-defined transformation.

    getLogger().log(Level.INFO, "Some message" );

### Examples

Remover of Empty Directories
Checking Configuration of a Custom Component

#### Remover of Empty Directories

Create component removing empty directories.

##### Solution

Add new attribute Directory to the component.

Use the following code.

    package jk;

    import java.io.File;

    import org.jetel.component.AbstractGenericTransform;

    /**
     * This is an example custom component. It shows how you can remove empty
     * directories.
     */
    public class CustomJavaComponentExample01 extends AbstractGenericTransform {

    	private void removeEmptyDirectories(File dir) {
    		if (!dir.isDirectory() || !dir.canRead() || !dir.canWrite()) {
    			return;
    		}

    		for (File f : dir.listFiles()) {
    			if (f.isDirectory()) {
    				removeEmptyDirectories(f);
    				if (f.listFiles().length == 0) {
    					f.delete();
    				}
    			}
    		}
    	}

    	@Override
    	public void execute() {
    		String directory = getProperties().getStringProperty("Directory");
    		File dir = getFile(directory);
    		removeEmptyDirectories(dir);
    	}

    }

#### Checking Configuration of a Custom Component

A component has to have one input port and one output port connected. Each port should have metadata assigned. The component has attribute Multiplier having integral value.

##### Solution

Use `checkConfig()` function of a component's template.

    @Override
    public ConfigurationStatus checkConfig(ConfigurationStatus status) {
     	super.checkConfig(status);

     	if (getComponent().getInPorts().size() != 1 || getComponent().getOutPorts().size() != 1) {
     	 	status.add("One input and one output port must be connected!", Severity.ERROR, getComponent(), Priority.NORMAL);
     	 	return status;
     	}

     	DataRecordMetadata inMetadata = getComponent().getInputPort(0).getMetadata();
     	DataRecordMetadata outMetadata = getComponent().getOutputPort(0).getMetadata();
     	if (inMetadata == null || outMetadata == null) {
     	 	status.add("Metadata on input or output port not specified!", Severity.ERROR, getComponent(), Priority.NORMAL);
     	}

     	if (!getProperties().containsKey("Multiplier")) {
     		status.add("Multiplier property is missing or is not set.", Severity.ERROR, getComponent(), Priority.NORMAL, "Multiplier");
     		return status;
     	}

     	try {
     		Integer.parseInt(getProperties().getStringProperty("Multiplier"));
    	} catch(Exception e){
     	 	status.add("Multiplier is not integer!", Severity.ERROR, getComponent(), Priority.NORMAL, "Multiplier");
     	}

    	 return status;
    }

### Best Practices

If the transformation is specified in an external file (with Algorithm URL), we recommend users to explicitly specify Algorithm source charset.

### Compatibility

CustomJavaComponent is available since CloverETL 4.1.0-M1. It replaced JavaExecute.

### See also

CustomJavaReader
CustomJavaTransformer
CustomJavaWriter
Common Properties of Components
Specific Attribute Types
Others Comparison

Prev | Up | Next
CheckForeignKey | Home | Table of Contents | DBExecute