The examples/ directory in the source repository contains two Cortex example projects.
cortex-custom-node
A minimal Maven project that shows how to build a custom processing node.
HelloWorldNode
/**
* Custom Cortex node that reads file size and word count from each media asset.
*/
public class HelloWorldNode extends AbstractMediaNode<HelloWorldNodeOptions> {
public static final String OUTPUT_FILE_SIZE = "file_size";
public static final String OUTPUT_WORD_COUNT = "word_count";
@Inject
public HelloWorldNode(@Nullable LoomClient client,
CortexOptions cortexOption,
HelloWorldNodeOptions options) {
super(client, cortexOption, options);
}
@Override
public String name() {
return "hello-world";
}
@Override
protected boolean isProcessable(NodeContext<LoomMedia> ctx) {
return true; // accepts all media types
}
@Override
protected NodeResult compute(NodeContext<LoomMedia> ctx, AssetResponse asset) throws Exception {
LoomMedia media = ctx.media();
// Read an output published by an upstream node (e.g. sha256)
String upstreamHash = ctx.upstreamOutput("sha256", "sha256");
// Compute outputs
long fileSize = media.size();
long wordCount = countWords(media.file());
// Publish outputs for downstream nodes
ctx.output(OUTPUT_FILE_SIZE, fileSize);
ctx.output(OUTPUT_WORD_COUNT, wordCount);
ctx.info("file_size=" + fileSize + ", word_count=" + wordCount);
return ctx.origin(COMPUTED).next();
}
}
HelloWorldNodeOptions
Options control per-node CLI flags and are injected via Dagger:
@Singleton
public class HelloWorldNodeOptions extends AbstractNodeOptions {
@Option(names = "--hw-enabled", description = "Enable HelloWorldNode")
private boolean enabled = true;
@Override
public boolean isEnabled() { return enabled; }
}
Dagger Module
Register the node so the pipeline runner discovers it:
@Module
public abstract class HelloWorldNodeModule {
@Binds @IntoSet
public abstract MediaNode bindHelloWorldNode(HelloWorldNode node);
}
cortex-custom-cli
The cortex-custom-cli example shows how to assemble a complete Cortex CLI binary that includes your custom node alongside the built-in nodes.
NodeCollectionModule
@Module(includes = {
HelloWorldNodeModule.class,
// ... add other built-in node modules here
})
public interface NodeCollectionModule {}
CortexComponent
Wire the Dagger component with the custom module:
@Singleton
@Component(modules = {
NodeCollectionModule.class,
LoomStorageModule.class,
PicoCLIModule.class
})
public interface CortexComponent {
CommandLine cli();
// ...
}
Running the Custom CLI
# Build the fat JAR
mvn package
# Process a folder with the custom node enabled
java -jar target/cortex-custom-cli.jar process run --actions hello-world /media/inbox
Unit Testing a Node
class HelloWorldNodeTest {
@Test
void testCompute() {
// Arrange
HelloWorldNode node = new HelloWorldNode(null, new CortexOptions(),
new HelloWorldNodeOptions());
NodeContext<LoomMedia> ctx = MockNodeContext.forFile(Path.of("src/test/resources/sample.txt"));
// Act
NodeResult result = node.compute(ctx, null);
// Assert
assertEquals(COMPUTED, result.origin());
assertNotNull(ctx.output(HelloWorldNode.OUTPUT_FILE_SIZE));
}
}