Overview
By developing a Data Store plugin, you can add functionality to Fess for retrieving content from new data sources. A Data Store retrieves records from external systems such as databases, APIs, and files, converts them into index fields according to the mapping script configured in the admin console, and registers them into the Fess index.
Published connectors (fess-ds-*) for CSV, JSON, databases, Git, and various cloud services are all implemented using this mechanism. Because fess-ds-example is published as an implementation template, the easiest way to create a new connector is to copy it as a starting point.
Basic Structure
A Data Store plugin consists of the following three parts:
Create a class that extends
AbstractDataStoreImplement the two methods
getName()andstoreData()Register it as a component in
fess_ds++.xml
Minimum Implementation
Note
Both getName() and storeData() are protected abstract methods. Note that in Fess 15.x, the package for DataConfig is org.codelibs.fess.opensearch.config.exentity (the former org.codelibs.fess.es.config.exentity has been removed).
Component Registration
To make Fess recognize the Data Store you created, register the component in src/main/resources/fess_ds++.xml.
With <postConstruct name="register">, the register() method inherited from AbstractDataStore is automatically invoked after the component is created, registering itself with the DataStoreFactory. The name registered at this time is the return value of getName() (ExampleDataStore in the example above), and this becomes the “handler name” selected in the Data Store settings in the admin console.
AbstractDataStore
Main Methods
| Method | Category | Description |
|---|---|---|
getName() | Implementation (required) | Abstract method that returns the Data Store’s handler name. Convention is to return |
storeData() | Implementation (required) | Abstract method that performs data retrieval, conversion, and index registration |
register() | Inherited (normally no changes needed) | Automatically invoked from the |
store() | Inherited (called by the framework) | The entry point invoked by the framework. Prepares |
convertValue() | Inherited (helper) | Evaluates the value (template) in scriptMap using the script engine |
getScriptType() | Inherited (helper) | Retrieves the script_type parameter (default is Groovy) |
getReadInterval() | Inherited (helper) | Retrieves the readInterval parameter (in milliseconds) |
sleep() | Inherited (helper) | Sleeps for the specified number of milliseconds (used to wait between records) |
storeData Parameters
Parameters passed to the storeData() method:
| Parameter | Type | Description |
|---|---|---|
dataConfig | DataConfig | Data Store configuration (ID, handler name, parameters, scripts, etc.) |
callback | IndexUpdateCallback | Callback for registering generated documents to the index |
paramMap | DataStoreParams | Configuration values from the “Parameters” field in the admin console. Access them using |
scriptMap | Map<String, String> | Configuration from the “Scripts” field in the admin console. The key is the index field name, and the value is the script template to be evaluated |
defaultDataMap | Map<String, Object> | Default field values for each document (config ID, boost, role, mimetype, virtual host, etc.), prepared by the framework |
Warning
The type of paramMap is DataStoreParams, not Map<String, String>. Since DataStoreParams does not implement Map, use getAsString(), which returns a string, instead of get() to retrieve values.
Data Processing Flow
The implementation of storeData() processes data in the following flow.
Retrieve source records from the external system.
Merge the source record’s fields into
paramMap.asMap()to buildresultMap(scripts are evaluated against thisresultMap).Evaluate each entry in
scriptMapwithconvertValue(scriptType, template, resultMap)and store the result indataMap. The important point is that the mapping is not hardcoded in the code, but defined by the administrator in the “Scripts” field.Call
callback.store(paramMap, dataMap)to register the document to the index.
Implementation Examples
Simple Data Store
An example of retrieving records from an external API and registering them to the index.
fetchRecords() is a custom method that retrieves a list of records from the external system. The field names of each retrieved record (Map<String, Object>) become the names that can be referenced from the scripts in scriptMap. DataStoreException is a class in the org.codelibs.fess.exception package.
Pagination Support
When handling large volumes of data, process the data page by page while retrieving it. By extracting the per-record processing (building resultMap, evaluating scriptMap, and calling callback.store()) into a method such as processRecord(), you can separate it from the retrieval logic.
Authentication Implementation
Authentication with the external system is implemented on the connector side. The following is an example implementation using a common HTTP client library; it is not an API provided by Fess. Include the library you use as a dependency of the plugin.
OAuth 2.0
API Key Authentication
Error Handling
For fatal errors that should abort processing, throw DataStoreException.
Note
In actual connectors such as fess-ds-example, to avoid stopping the entire crawl due to an error in a single record, CrawlingAccessException is caught per record and the error URL is recorded in FailureUrlService. The interrupt flag of DataStoreCrawlingException is also used to control whether to abort the entire crawl. When implementing a robust connector, refer to the implementation of ExampleDataStore.
Testing
Unit Testing
Fess plugins are tested using UTFlute’s LastaDiTestCase. Tests are written in JUnit 5 (Jupiter). By replacing IndexUpdateCallback with an implementation that collects the registered dataMap, you can verify the mapping results without using a mock library.
Note
Since setUp is annotated with @BeforeEach in the base class, there is no need to re-annotate the lifecycle annotation on the overriding side. Add @Test (org.junit.jupiter.api.Test) to each test method.
Build and Installation
pom.xml
The plugin is built as a jar with fess-parent as the parent POM. Dependencies on fess and opensearch are set to provided because they are supplied by the Fess core at runtime.
JUnit 5 and org.dbflute.utflute:utflute-lastaflute are used for testing.
Build
fess-ds-example-15.8.0.jar is generated in the target/ directory.
Installation
Install the generated JAR into Fess and restart Fess. For details on the installation procedure, see Plugin. After installation, create a new configuration from “Crawler > Data Store” in the admin console, and specify the name returned by getName() (ExampleDataStore in this example) as the “handler name”.
Configuration Example
Example configuration in the admin console:
Parameters
In the “Parameters” field, describe the keys and values that the connector reads from paramMap.
Scripts
In the “Scripts” field, describe the mapping in the form left-hand side=right-hand side. The left-hand side is the index field name, and the right-hand side is a script (Groovy by default) that references a field of the source record. The following is an example where the source record has the url / title / content / updated_at / content_type fields.
Note
The field names that can be referenced on the right-hand side depend on the values that the connector stores in resultMap (the values of paramMap and the fields of the source record). Existing connectors such as CSV and JSON may use a custom prefix such as data.*, so refer to the documentation for each connector.
Reference
Plugin Architecture - Plugin Architecture
Plugin - Plugin Installation
Data Store Connector Overview - Data Store Connector Overview
fess-ds-example - Data Store plugin implementation template
GitHub: fess-ds-* - Examples of published connectors