Skip to main content

Actions Configuration for Collections

This document explains how to configure actions for a collection in Hanami. Actions are small, declarative jobs that run automatically in response to lifecycle events on an item or when a user explicitly triggers a workflow action. Every collection may declare its own set of actions under

ext/collections/<collection‑id>/actions/

Each action is a single YAML file (extension: *.yml). The filename is arbitrary but should be meaningful (e.g. dataset-publish.yml).


1. Lifecycle events (on)

Actions are executed by the ActionsRunner service, which is invoked from three different code paths:

  1. Cron scheduler (onCron) – ActionsCronRunner runs once per the actions.cronExpression setting (Spring @Scheduled). A collection‑wide empty RDF Model is supplied together with collectionUri in the template arguments.

  2. Create / Update flow – the platform calls the runner inside the item service in this strict order:

    beforeSave → onSave → afterSave
    PhaseWhen it firesWhat happens if jobs exist?
    beforeSaveBefore any data is persisted.You may mutate itemModel. The platform reloads the model after all jobs run.
    onSaveImmediately after beforeSave.If at least one onSave job is defined, the default rdfStore.replaceGraph(...) step is skipped – your job is responsible for persisting the graph.
    afterSaveOnce data is safely stored (either by you or by the platform).Perfect for side‑effects such as notifications or extra writes; the model is reloaded so changes get indexed.
  3. Delete flow – called with the sequence:

    beforeDelete → onDelete → afterDelete

    onDelete follows the same override rule: if present, the platform does not call rdfStore.deleteGraph(...).

  4. Manual workflows (workflow) – kicked off by the user (UI button or API) by passing the action id to the runner.

Event summary

on valueTriggered byWhat happens if jobs exist?
onCronSchedulerJob is executed on its cron schedule.
beforeSaveCreate / UpdateStore will occur unless an onSave job overrides it.
onSaveCreate / UpdateJob is executed; it may skip or augment storage (the default replaceGraph is bypassed when an onSave job is present).
afterSaveCreate / UpdateJob can further modify the item model or trigger side-effects; the graph is already stored.
beforeDeleteDeleteDelete will occur unless an onDelete job overrides it.
onDeleteDeleteIf at least one job is found, you must handle graph removal (or an alternative). If none match, Hanami calls rdfStore.deleteGraph(graphUri) by default.
afterDeleteDeleteJob can post-process the deletion (e.g., cleanup, notifications).
workflowExplicit user actionJob executes whenever the user triggers the corresponding workflow action.

The runner first filters jobs by on and optional id. Each job runs serially; individual steps marked async: true are executed on the actionTaskExecutor thread‑pool via JobRunnerService.runJobAsync(...).

2. Top-level keys Top‑level keys

KeyTypeRequiredDescription
onstringOne of the values listed above.
idstringA globally unique identifier for the action. Use kebab case (e.g. dataset-publish-action).
asyncboolean✕ (default false)When true, the job is queued and processed in the background.
jobsobjectDeclarative job definition (see below).

Example skeleton:

on: afterSave          # lifecycle hook
id: sample-action # action id
async: false # run synchronously with the transaction

jobs:
steps:
- id: update-status
kind: itemUpdateQueryStep
with:
query: |
# SPARQL UPDATE goes here

3. jobs.steps

jobs holds a pipeline executed in order. Each element under steps has:

KeyRequiredPurpose
idStep identifier; must be unique within this file.
kindThe step implementation. Common kinds are: • itemUpdateQueryStep – SPARQL UPDATE scoped to the current item. • modelUpdateQueryStep – SPARQL UPDATE against the entire triplestore. • notifyStep – send an HTTP/webhook or platform notification. More kinds can be added by the platform.
withStep‑specific parameters (free structure).

3.1 Place‑holders available in SPARQL

Inside query strings you may reference templating variables wrapped with #{[ ... ]}:

VariableExpands to
itemUriAbsolute URI of the item being processed.
collectionUriURI of the collection that owns the item.
graphUriThe graph that stores the item (handy for updates).
userIdThe account that triggered the action.

3.2 Step‑kind catalog

Each kind corresponds to a Spring bean implementing app.hanami.actions.steps.Step. Below is the built‑in palette. You can add your own by wiring a bean with the same interface and referencing its name in YAML.

kindDescription & typical inputsOutputs
declareConstantStepInjects a literal into the template context. with: value: SpEL‑templated string.steps.<id>.value
failOnConditionStepAborts the whole job when a boolean SpEL expression evaluates to true. with: condition: SpEL returning boolean message: Error text– (throws)
httpRequestStepMakes an HTTP call. Fields support templating: method, url, headers, parameters, body, connectTimeout, readTimeout.steps.<id>.response.body (and headers)
sparqlSelectQueryStepRuns a SELECT over the in‑memory itemModel and expects exactly one binding. with.query: SPARQL (templated).steps.<id>.result
modelToStringStepSerialises itemModel to text/turtle or application/rdf+xml. with.format: MIME typesteps.<id>.modelString
jsonPathStepExtracts a value from arbitrary JSON using a JSON Pointer. with: json, jsonPointer, [defaultValue]steps.<id>.result
modelUpdateQueryStep⚙️ Hook‑time helper. Executes an UPDATE on itemModel, then overwrites the graph in the store. Use this within beforeSave / afterSave etc.
itemUpdateQueryStep🏷 Workflow‑time helper. Performs an UPDATE on itemModel, then calls ItemService.updateJsonLd(...) which re‑enters the full create‑update pipeline (including triggering its own actions!). Ideal for publish/retract flows.

Model vs. Item update
modelUpdateQueryStep is surgical: it tweaks the current model and writes it back once. No extra hooks fire.
itemUpdateQueryStep is recursive: after modifying the model it invokes the high‑level item service, so all beforeSave/onSave/afterSave actions of the same collection will run again. Use it when you want the normal validation/indexing lifecycle to happen.


4. End‑to‑end examples

4.1 Workflow: Publish a dataset

This action is wired to a button (or API call) and flips the catalog record to adms:current.

on: workflow
id: dataset-publish-action
async: false

jobs:
steps:
- id: update
kind: itemUpdateQueryStep
with:
query: |
PREFIX adms: <http://www.w3.org/ns/adms#>
PREFIX dcat: <http://www.w3.org/ns/dcat#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX status: <https://data.in.ep.europa.eu/def/record-status/>

DELETE {
?record adms:status ?oldStatus .
}
INSERT {
?record adms:status status:current .
}
WHERE {
<#{[itemUri]}> a dcat:Dataset .
?record a dcat:CatalogRecord ;
foaf:primaryTopic <#{[itemUri]}> ;
adms:status ?oldStatus .
}

4.2 Workflow: Revert to draft

on: workflow
id: dataset-back-to-draft-action
async: false

jobs:
steps:
- id: update
kind: itemUpdateQueryStep
with:
query: |
PREFIX adms: <http://www.w3.org/ns/adms#>
PREFIX dcat: <http://www.w3.org/ns/dcat#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
DELETE {
?catalogRecord adms:status ?oldStatus .
}
INSERT {
?catalogRecord adms:status <https://data.in.ep.europa.eu/def/record-status/draft> .
}
WHERE {
?catalogRecord a dcat:CatalogRecord ;
foaf:primaryTopic <#{[itemUri]}> ;
adms:status ?oldStatus .
}

4.3 Hook: Reset status automatically after each save

on: afterSave
id: dataset-reset-onsave
async: false

jobs:
steps:
- id: update
kind: modelUpdateQueryStep
with:
query: |
PREFIX adms: <http://www.w3.org/ns/adms#>
PREFIX dcat: <http://www.w3.org/ns/dcat#>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
DELETE {
?catalogRecord adms:status ?oldStatus .
}
INSERT {
?catalogRecord adms:status <https://data.in.ep.europa.eu/def/record-status/draft> .
}
WHERE {
?catalogRecord a dcat:CatalogRecord ;
foaf:primaryTopic <#{[itemUri]}> ;
adms:status ?oldStatus .
}

5. Synchronous vs. asynchronous actions

SettingBehaviourWhen to choose
async: false (default)The action runs within the same transaction as the triggering event. If it fails, the whole operation rolls back.Fast updates, critical integrity checks.
async: trueThe action is queued. Users do not wait for completion. Failures are logged but do not affect the primary operation.Long‑running SPARQL updates, calls to external APIs, heavy indexing.

6. Feature flags & cron schedule

Actions are globally toggled via Spring configuration properties:

hanami:
features:
actions:
enabled: true # master switch for ALL hooks (save / delete / cron)
wf-enabled: true # allow manual `workflow` jobs even when hooks are disabled
cron-expression: "0 0 * * * *" # Spring cron syntax (here: every hour)
  • enabled – if false, Hanami ignores every automatic hook (on*) but still allows workflow jobs provided wf-enabled is true.
  • wf-enabled – controls whether manual workflow actions are available.
  • cron-expression – schedule consumed by ActionsCronRunner.

The guard inside ActionsRunner requires at least one of enabled or wf-enabled to be true; otherwise runJobs(...) is a no‑op.