Rules and patterns for implementing an Elasticsearch DataSource module in Lutece 8. DataSource/DataObject interfaces, CDI auto-discovery, @ConfigProperty injection, batch processing, two-daemon indexing, incremental updates via CDI events. Based on the elasticdata-forms module pattern.
Instrucciones de origen · Vista previa de solo lectura
name
lutece-elasticdata
description
Rules and patterns for implementing an Elasticsearch DataSource module in Lutece 8. DataSource/DataObject interfaces, CDI auto-discovery, @ConfigProperty injection, batch processing, two-daemon indexing, incremental updates via CDI events. Based on the elasticdata-forms module pattern.
Lutece 8 ElasticData DataSource Module
Before implementing a DataSource, consult ~/.lutece-references/lutece-form-module-elasticdata-forms/ — the reference implementation. The framework plugin is at ~/.lutece-references/lutece-elk-plugin-elasticdata/ and the HTTP client library at ~/.lutece-references/lutece-elk-library-elastic/.
Architecture Overview
library-elastic (HTTP/JSON wrapper around Elasticsearch REST API)
↑ used by
plugin-elasticdata (framework — already deployed)
↓ auto-discovers via CDI
module-myentity-elasticdata (@ApplicationScoped DataSource)
↓ produces
MyEntityDataObject (extends AbstractDataObject)
↓ indexed by two daemons
FullIndexingDaemon (daily, bulk reindex)
IncrementalIndexingDaemon (every 3s, processes IndexerAction queue)
Incremental path:
Entity CRUD → CDI event fired
↓ observed by
MyEntityIndexerEventListener (@ObservesAsync)
↓ calls
DataSourceIncrementalService.addTask(dataSourceId, entityId, taskType)
↓ queues in DB → daemon processes
A module provides a DataSource implementation. The plugin-elasticdata framework handles Elasticsearch communication, daemon scheduling, batch processing, and action queue management.
CDI auto-discovery — any @ApplicationScoped class implementing DataSource is automatically registered:
IndexerAction.TASK_CREATE (1) — Index new document (also used for updates)
IndexerAction.TASK_MODIFY (2) — Partial update
IndexerAction.TASK_DELETE (3) — Delete by query
The IncrementalIndexingDaemon runs every 3 seconds and processes the queue. It handles conflict resolution automatically (e.g., CREATE followed by DELETE = task removed).
Step 5 — Elasticsearch Mappings
Provide custom mappings via @ConfigProperty or override getMappings():
TIMESTAMP_AND_LOCATION_MAPPINGS — timestamp + geo_point for spatial data
Use custom mappings when you need keyword fields for aggregations, specific analyzers, or nested objects.
Step 6 — Configuration Properties
# elasticdata-myentity.properties (MicroProfile Config)
# DataSource identity (injected via @ConfigProperty)
elasticdata-myentity.dataSource.id=MyEntityDataSource
elasticdata-myentity.dataSource.name=My Entity Data Source
elasticdata-myentity.dataSource.targetIndexName=myentity
elasticdata-myentity.dataSource.mappings={"mappings":{"properties":{"timestamp":{"type":"date","format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"}}}}
The Elasticsearch server connection is configured at the plugin-elasticdata level (not in your module):
# Already in plugin-elasticdata config (do NOT duplicate)
elasticdata.elastic_server.url=http://localhost:9200
elasticdata.elastic_server.login=
elasticdata.elastic_server.pwd=
elasticdata.bulk_batch_size=10000
Step 7 — plugin.xml
<plug-in><name>myentity-elasticdata</name><class>fr.paris.lutece.portal.service.plugin.PluginDefaultImplementation</class><version>1.0.0-SNAPSHOT</version><description>ElasticData module for MyEntity plugin</description><core-version-dependency><min-core-version>8.0.0</min-core-version></core-version-dependency><db-pool-required>0</db-pool-required></plug-in>
No daemons or admin features needed — the plugin-elasticdata framework provides them.
IDataSourceExternalAttributesProvider (Advanced)
Enrich data objects from other modules with additional attributes:
Process IndexerAction queue (create/update/delete)
Transaction safety: Incremental indexing wraps ES operations + DB task removal in a single transaction. If ES fails, the task remains in queue for retry.