Diagram exporters
A diagram exporter turns a subject (a pipeline, a workflow, or any other object that has a graph) into a format (SVG, Mermaid, PlantUML, Draw.io, PDF, …) and writes the result through Apache VFS.
Hop ships exporters for pipelines and workflows to SVG and Mermaid. Everything else — a data model canvas, an architecture map, PlantUML, Draw.io — is a plugin of this type.
The shared mechanics — discovery, annotation attributes, classloading, how a plugin gets into the distribution — are on the plugin types overview.
- Annotation
-
@DiagramExporter - Implement
-
IDiagramExporter<T>, in practice by extendingBaseDiagramExporter<T> - Plugin type
-
DiagramExporterPluginType, idDIAGRAM_EXPORTER - Examples
-
engine/src/main/java/org/apache/hop/diagram/exporter
Users hit exporters from two places:
-
Hop GUI, File → Export diagram, which opens
DiagramExportDialogfor the active editor. -
hop export(and thehop-exportwrapper scripts), which exports one file or a whole folder.
Subjects, formats and context
Three things are kept separate on purpose:
- Subject
-
What is being drawn. Pipelines (
PipelineMeta) and workflows (WorkflowMeta) are built in. A plugin can export any other object: the active editor’sIHopFileTypeHandler.getSubject()is what the GUI passes in. - Format
-
The target representation.
DiagramExportFormatknowsSVG,MERMAID,PDF,PLANTUMLandDRAWIO.DiagramExportFormat.parse("PLANTUML")(orof(…)) is enough for a format the enum does not list. - Context
-
Where the export is running: GUI, CLI, batch, web or tests.
IExportContextgives you variables, the metadata provider, a log channel, and theExportEnvironment.
DiagramExportService is the facade both the GUI and the CLI use: it finds exporters for a subject, picks one by format id or file extension, and calls export(…).
Writing an exporter
One class per subject-and-format pair.
Annotate it, extend BaseDiagramExporter<T>, implement export.
@DiagramExporter(
id = "pipeline-plantuml",
name = "Pipeline PlantUML Exporter",
description = "Exports a pipeline diagram to PlantUML",
format = "PLANTUML",
fileExtension = "puml",
fileFilterNames = {"PlantUML diagrams (*.puml)"},
supportedSubjectTypes = {PipelineMeta.class})
public class PipelinePlantUmlDiagramExporter extends BaseDiagramExporter<PipelineMeta> {
@Override
public DiagramExportResult export(
PipelineMeta pipelineMeta, DiagramExportOptions options, IExportContext context)
throws HopException {
if (pipelineMeta == null) {
throw new HopException("PipelineMeta is null");
}
String plantUml = render(pipelineMeta, options);
if (options != null && options.getTargetFilename() != null) {
writeToTarget(options.getTargetFilename(), plantUml, context);
}
return DiagramExportResult.success(
options != null ? options.getTargetFilename() : null, plantUml, "text/plain");
}
}
Copy the built-in PipelineSvgDiagramExporter / PipelineMermaidDiagramExporter (and the workflow pair) rather than inventing a new shape.
The annotation
| Attribute | Meaning |
|---|---|
|
Unique among diagram exporters.
Permanent: it is what |
|
Shown in the GUI format list (via the format name) and in |
|
Format id, matched case-insensitively by |
|
Default extension without a dot ( |
|
Labels for the save dialog ( |
|
The Java types this exporter accepts.
|
BaseDiagramExporter reads the annotation in its constructor and fills id, name, format, fileExtension and supportedSubjectTypes.
You do not re-implement those getters unless you have a reason.
export
Return a DiagramExportResult.
Always put the bytes or text on the result, even when you also write a file: Hop Web downloads result.getBytes(), and tests assert on result.getContent().
| Factory | When |
|---|---|
|
Text formats (Mermaid, PlantUML, SVG XML).
Also fills |
|
Binary formats (PDF).
Also fills |
|
The export failed. The GUI shows this in an error dialog; the CLI throws. |
Write files with writeToTarget(…) from the base class.
It resolves the filename through the context’s variables and opens the stream with HopVfs.getOutputStream(…).
Do not use java.io.File.
Honour the options you understand:
targetFilename-
where to write; may be
nullwhen the caller only wants the bytes (Hop Web). magnification-
scale for SVG and similar vector output.
includeNotes-
whether canvas notes belong in the diagram.
direction/theme/extraOptions-
optional; Mermaid uses
direction(LR/TD). Put plugin-specific keys inextraOptionsrather than subclassing the options bean unless you have to.
Classloading
IDiagramExporter, DiagramExportFormat and the rest of the API live in hop-core (org.apache.hop.core.diagram).
Plugin classloaders load org.apache.hop.core.* parent-first, so the IDiagramExporter your plugin implements is the same class the GUI and CLI already have.
Do not ship hop-core (or Jackson, or SLF4J) inside the plugin zip.
See Dependencies and classloading.
Custom subjects
Exporting a pipeline or workflow is the built-in case.
Exporting something else is two extra steps: the editor has to hand the object over, and hop export has to be able to load it from a file.
The GUI: getSubject()
HopGuiFileDelegate.exportDiagram() asks the active IHopFileTypeHandler for getSubject().
If that returns your model object, and your exporter’s supportedSubjectTypes includes that class, the dialog lists your formats.
A custom file type that returns null from getSubject(), or that returns a wrapper the exporter does not recognise, is invisible to File → Export diagram.
The dialog itself is a @GuiPlugin options bean (DiagramExportDialogModel) built with GuiCompositeWidgets.
It shows format, target file, magnification and include-notes for every exporter.
It does not currently instantiate getOptionsClass(); keep extra settings in DiagramExportOptions.extraOptions or accept the shared fields.
Hop Web still downloads SVG for the active pipeline or workflow rather than opening the dialog. A web-only exporter that only produces PlantUML will not be offered there yet.
The CLI: a subject loader
hop export -f and batch export do not know your file format.
They ask DiagramExportService.findSubjectLoader(filename) for an IDiagramSubjectLoader that claims the path, then call loadSubject(…).
Loaders are not annotation-discovered.
Register one after the environment is up, typically from HopEnvironmentAfterInit:
@ExtensionPoint(
id = "RegisterArchitectureMapLoader",
extensionPointId = "HopEnvironmentAfterInit",
description = "Register the architecture-map diagram subject loader")
public class RegisterArchitectureMapLoader implements IExtensionPoint<PluginRegistry> {
@Override
public void callExtensionPoint(
ILogChannel log, IVariables variables, PluginRegistry registry) {
DiagramExportService.getInstance().registerSubjectLoader(new ArchitectureMapSubjectLoader());
}
}
The loader itself is small:
public class ArchitectureMapSubjectLoader implements IDiagramSubjectLoader {
@Override
public boolean supportsFile(String filename) {
return filename != null && filename.toLowerCase().endsWith(".amap");
}
@Override
public Object loadSubject(
String filename, IHopMetadataProvider metadataProvider, IVariables variables)
throws HopException {
return new ArchitectureMapMeta(filename, metadataProvider, variables);
}
}
Match on the filename or URI, not on a java.io.File.
The CLI walks VFS folders, so supportsFile must accept a file:// URI as well as a plain path.
Built-in loaders: PipelineDiagramSubjectLoader (.hpl) and WorkflowDiagramSubjectLoader (.hwf), registered from HopEnvironment.init().
How the GUI and CLI pick an exporter
DiagramExportService.findExportersForSubject(subject) keeps every plugin (and any statically registered exporter) whose supportsSubject is true.
The dialog lists unique format names from that list.
findExporter(subject, formatId) matches, in order:
-
exporter.getFormat().getId()(for exampleMERMAID) -
exporter.getFileExtension()(for examplemmd)
So --format mermaid, --format MERMAID and an output file ending in .mmd all select the same plugin.
If nothing matches, the service falls back to the first exporter that supports the subject.
registerStaticExporter(…) exists for tests and for code that is not a plugin.
Production exporters should be @DiagramExporter classes so Jandex finds them.
hop export
The command is a @HopCommand(id = "export") (DiagramExportCommand in engine).
It also has wrapper scripts hop-export / hop-export.bat in the assembly.
# One file
hop export -f pipelines/process-orders.hpl --format mermaid -o diagrams/process-orders.mmd
# A folder
hop export -s pipelines/ -t diagrams/ --format svg --recursive
# What is installed
hop export --list-exporters
Useful flags: -f / --file, -o / --output-file, --format, -s / --source-folder, -t / --target-folder, -r / --recursive, -m / --magnification, -in / --include-notes.
Project and environment options (-j, -e) are mixed in from @ConfigPlugin beans with category = ConfigPlugin.CATEGORY_EXPORT.
The projects plugin already contributes ProjectsExportOptionPlugin on that category.
Add your own mixin the same way if the exporter needs project-scoped configuration; see Metadata and configuration.
Built-in exporters
| Id | Subject | Format | Extension | Notes |
|---|---|---|---|---|
|
|
SVG |
|
Delegates to |
|
|
MERMAID |
|
|
|
|
SVG |
|
Delegates to |
|
|
MERMAID |
|
|
PDF, PlantUML and Draw.io are recognised format ids. Hop does not ship exporters for them; that is what this plugin type is for.
Things that are easy to get wrong
-
Forgetting
supportedSubjectTypes.supportsSubjectthen returns false for everything and the exporter never appears. -
Returning a wrapper from
IHopFileTypeHandler.getSubject()that is not the type you declared. The GUI looks at the runtime object, not at the file extension. -
Writing with
java.io.Fileinstead ofwriteToTarget/HopVfs. CLI batch export and Hop Web both pass VFS URIs. -
Shipping a second
hop-corein the plugin zip. You will get aClassCastExceptiononIDiagramExporterthe first time the GUI tries to load you. -
Skipping
META-INF/jandex.idx. No index, no plugin — same as every other type. -
Registering a subject loader too early, or not at all. Without a loader,
hop export -f your-file.extfails with "No subject loader available" even though the GUI export works. -
Putting GUI-only code (SWT) on the exporter class. The CLI loads the same class. Keep painters that need SWT behind an engine helper the way
PipelineSvgPainteralready does, or keep the exporter in a module that both GUI and CLI already have on the classpath (hop-enginefor the built-ins).