This file is read by automated agents (security scanners, code analyzers, AI assistants) and human contributors working on this repository. Keep it concise — we will expand the sections below over time.
- Requirements: Java 21 (JDK) and Maven 3.6.3+ — or use the bundled wrapper
./mvnw(no local Maven needed). - Build + unit tests:
./mvnw clean install(build with Java 21, otherwise tests are skipped). On Linux wrap a desktop run with./tools/with-isolated-display.shso SWT UI tests do not steal the interactive session.-Pskip-uitestexcludes them;-Puitestruns only those tests.
- Formatting: code is formatted with Spotless. Run
./mvnw spotless:applybefore committing; CI runsspotless:check. - Imports: never use a star import, static or otherwise
(
import org.example.*,import static org.junit.jupiter.api.Assertions.*). CheckstyleAvoidStarImportfails the build. Import each type and each static method by name. Drop an import that nothing in the file calls. - License headers: every new source/text file needs the Apache License 2.0 header — Apache RAT fails the build otherwise.
- Contributing & PRs: see CONTRIBUTING.md.
Security model: SECURITY.md
Agents that scan this repository should consult SECURITY.md and the
threat model it links before reporting issues.
Unless there really is no other choice, files are read from or written to using Apache Commons VFS, HopVfs.getInputStream() or HopVfs.getOutputStream(). Use of java.io.File is reserved for rare and exceptional cases. The default is to use org.apache.commons.vfs2.FileObject instead.
When building the ui maven component make sure to do this with an isolated display to prevent instantiating focus-stealing UI artifacts on the developers display. Use tools/with-isolated-display.sh to do this, for example:
tools/with-isolated-display.sh mvn -T10 -pl ui clean install
or a full build of the Apache Hop assembly
tools/with-isolated-display.sh mvn -T10 clean install
We want to avoid building any functionality that is only expressed in a file and not in a GUI element.
When building or changing a transform, action, metadata, or run-configuration dialog in Apache Hop (core or a plugin), do not hand-layout FormAttachment rows on the shell first. Start with annotated metadata widgets and a grouped GuiCompositeWidgets container.
A flat createCompositeWidgets / addScrolledComposite form cannot simultaneously:
- keep the dialog resizable
- keep OK/Cancel pinned to the bottom of the shell
- keep those buttons strictly below the last widget
The fields and the button bar fight for the same vertical space, so the buttons overlap the lower widgets. Putting @GuiWidgetElement fields in a group makes GuiCompositeWidgets build a container that fills the area between the name/header line and the button bar, with a scrolled composite inside (tabs, boxes, or list). Resize works; buttons stay put; overflow scrolls.
- Annotate the Meta (or equivalent) class with
@GuiPluginand Lombok@Getter/@Setter. - Give the dialog a
GUI_PLUGIN_ELEMENT_PARENT_IDconstant. - Annotate every user-visible field with
@HopMetadataPropertyand@GuiWidgetElement. - Put every field in a group. Repeat
groupTypeon each field (the first non-NONEvalue wins; mixed types fall back to tabs). One options panel:groupType = GuiWidgetGroupType.BOXESand a singlegroup(seeDataSetOutputMeta). Several sections:GuiWidgetGroupType.TABSwith distinctgroupnames andgroupOrder. - In the dialog
open():createShell(...),buildButtonBar().ok(...).cancel(...).build(), thenGuiCompositeWidgets.addScrolledComposite(shell, variables, wTransformName, wOk, PARENT_ID, input). - On OK,
widgets.getWidgetsContents(input, PARENT_ID)and set the transform/action name. Do not copy fields by hand.
@GuiWidgetElement(
id = WIDGET_DATA_SET_NAME,
order = "0100",
type = GuiElementType.METADATA,
metadata = DataSet.class,
label = "i18n::DataSetOutputMeta.DataSetName.Label",
toolTip = "i18n::DataSetOutputMeta.DataSetName.Tooltip",
parentId = GUI_PLUGIN_ELEMENT_PARENT_ID,
groupType = GuiWidgetGroupType.BOXES,
group = "Data Set")
@HopMetadataProperty(hopMetadataPropertyType = HopMetadataPropertyType.PIPELINE_DATA_SET)
private String dataSetName;Use GuiElementType that matches the value: METADATA, FOLDER, FILENAME, TEXT, CHECKBOX, COMBO, and so on. Labels and tooltips live in the Meta's messages_en_US.properties; quote variable examples as '${HOP_DATASETS_FOLDER}'.
Hand-built SWT is for things annotations cannot express (canvas, custom painters). A TableView or similar extra composite belongs in the same grouped layout via GuiCompositeWidgets.registerExtraGroup(...), not as a free-floating control under the button bar.
Listeners (IGuiPluginCompositeWidgetsListener) are for enable/disable and setChanged(), not for creating the fields.
An enum that appears in a Hop GUI or Hop Web control (a combo, a table column, a set of options) implements org.apache.hop.metadata.api.IEnumHasCodeAndDescription.
getCode()is the stable value stored in metadata. For a new enum that is the constant name. Do not change a code that projects already store.getDescription()is the label a person sees. Load it withBaseMessagesfrom the enum'smessages_en_US.properties.GuiCompositeWidgetslists that description in a combo or table cell and writes the selection back by looking up the description, then the code, then the constant name.- Do not override
toString()to return the description.toString()stays the constant name.
Values defined in resource bundles (resource files in messages/messages\*.properties files) need to be properly escaped and quoted. This means that variables expressions like ${VARIABLE} really need to be surrounded with single quotes like this: '${VARIABLE}'.
Apache Hop and related projects should use Lombok for all classes to avoid cluttering classes with boilerplate getter/setter methods.
The key of @HopMetadata is the metadata plugin ID and the name of the folder its objects are stored in (metadata/<key>/<name>.json). It is also written into serialized metadata exports and matched against disabledGuiElements. Treat it as a public, persisted identifier.
- New metadata types: the key is lower-case and dash-separated (kebab-case) and names what the type is, for example
mail-server-connectionorpipeline-run-configuration. Never use PascalCase or the Java class name. Check this on every new or changed@HopMetadataannotation, including in reviews. - Renaming a key: never change an existing key without moving the old one to
legacyKeys, for examplekey = "mail-server-connection", legacyKeys = {"MailServerConnection"}. Objects in the old folder keep loading, and each one moves to the new folder the next time it is saved. WithoutlegacyKeys, existing projects silently lose those objects. - Code that works with type keys or metadata files must go through
HopMetadataUtil.getAllKeys()/matchesKey()orJsonMetadataSerializer.findFilename(), notannotation.key()alone. Otherwise objects still in a legacy folder are missed. rdbmspredates this convention and stays as it is.