Transform Plugins
Transforms are the fundamental building blocks of Hop pipelines. A transform reads rows from incoming hops, performs operations (transforming, filtering, enriching, validating, or writing), and emits rows to outgoing hops.
Core Architecture: The Four Classes
Every transform plugin is implemented as a set of four collaborating classes with a consistent naming convention:
| Class | Base Class / Interface | Responsibility |
|---|---|---|
|
|
Design-time metadata: Configuration settings, serialization ( |
|
|
Runtime execution: Processing rows ( |
|
|
Runtime state: Mutable variables, cached field indexes, temporary buffers. Separate from the transform to cleanly isolate multiple concurrent copies. |
|
|
User interface: Settings dialog in the Hop GUI. |
1. The Metadata Class (FooMeta)
Annotate the metadata class with @Transform and decorate each configurable field with @HopMetadataProperty:
import org.apache.hop.core.annotations.Transform;
import org.apache.hop.core.gui.plugin.GuiPlugin;
import org.apache.hop.metadata.api.HopMetadataProperty;
import org.apache.hop.pipeline.transform.BaseTransformMeta;
import lombok.Getter;
import lombok.Setter;
@Transform(
id = "CustomFilter",
name = "i18n::CustomFilter.Name",
description = "i18n::CustomFilter.Description",
image = "custom-filter.svg",
categoryDescription = "i18n:org.apache.hop.pipeline.transform:BaseTransform.Category.Transform",
documentationUrl = "/pipeline/transforms/custom-filter.html"
)
@GuiPlugin
@Getter
@Setter
public class CustomFilterMeta extends BaseTransformMeta<CustomFilter, CustomFilterData> {
@HopMetadataProperty(key = "target_field")
private String targetField;
@HopMetadataProperty(key = "threshold_value")
private int thresholdValue = 100;
@Override
public void setDefault() {
targetField = "";
thresholdValue = 100;
}
}
Crucial Methods on the Meta Class
-
getFields(IRowMeta inputRowMeta, String name, IRowMeta[] info, TransformMeta nextStep, IVariables variables, IHopMetadataProvider metadataProvider): Describes outgoing rows. If your transform adds, removes, or alters fields, modifyinputRowMetain this method. Downstream transforms rely on this method during design time. -
check(…): Validates configuration when a user clicks "Verify pipeline" in the GUI.
2. The Runtime Class (Foo)
The runtime class executes the data processing logic:
import org.apache.hop.core.exception.HopException;
import org.apache.hop.pipeline.Pipeline;
import org.apache.hop.pipeline.PipelineMeta;
import org.apache.hop.pipeline.transform.BaseTransform;
import org.apache.hop.pipeline.transform.TransformMeta;
public class CustomFilter extends BaseTransform<CustomFilterMeta, CustomFilterData> {
public CustomFilter(TransformMeta meta, CustomFilterMeta metaData,
CustomFilterData data, int copyNr,
PipelineMeta pipelineMeta, Pipeline pipeline) {
super(meta, metaData, data, copyNr, pipelineMeta, pipeline);
}
@Override
public boolean init() {
if (!super.init()) {
return false;
}
// Initialize external connections or resources here
return true;
}
@Override
public boolean processRow() throws HopException {
// 1. Fetch next incoming row
Object[] row = getRow();
// If row is null, input stream is exhausted
if (row == null) {
setOutputDone();
return false; // Tells the engine to stop calling processRow()
}
// 2. Handle first row setup (resolve field indexes once)
if (first) {
first = false;
data.outputRowMeta = getInputRowMeta().clone();
meta.getFields(data.outputRowMeta, getTransformName(), null, null, this, metadataProvider);
data.fieldIndex = getInputRowMeta().indexOfValue(meta.getTargetField());
if (data.fieldIndex < 0) {
throw new HopException("Field " + meta.getTargetField() + " not found in input stream!");
}
}
// 3. Process the row
Long value = getInputRowMeta().getInteger(row, data.fieldIndex);
if (value != null && value >= meta.getThresholdValue()) {
// Emit row to outgoing hops
putRow(data.outputRowMeta, row);
}
return true; // Keep processing subsequent rows
}
@Override
public void dispose() {
// Clean up streams, sockets, or file handles
super.dispose();
}
}
3. The Runtime Data Class (FooData)
import org.apache.hop.core.row.IRowMeta;
import org.apache.hop.pipeline.transform.BaseTransformData;
public class CustomFilterData extends BaseTransformData {
public IRowMeta outputRowMeta;
public int fieldIndex = -1;
public CustomFilterData() {
super();
}
}
4. The GUI Dialog (FooDialog)
The dialog extends BaseTransformDialog.
Annotate the metadata fields with @GuiWidgetElement and build them with GuiCompositeWidgets.
The open() recipe is Annotation derived widgets, under "Transform and action dialogs".
Concrete Codebase Examples
-
plugins/transforms/detectemptystream: Minimal transform example. -
plugins/transforms/coalesce: Modern transform demonstrating field mapping, annotations, and unit tests.