DataProviderExtension

DataProviderExtension lets a plugin supply dropdown, lookup, and query data programmatically. Each provider registers under a globally unique provider key and returns a list of DataItem (value / label / metadata). When some lookup needs to compose data from multiple models, an external system, or a computation the DSL cannot express, a provider encapsulates that logic inside the plugin and the front end references it by key.

Concept

Each provider exposes a unique provider key in namespace:provider-name form (e.g. billing:currencies, hr:departments). The platform matches a DataProviderExtension by getProviderKey() through ExtensionRegistry.getDataProvider(providerKey) and calls fetchData(DataRequest) to retrieve rows.

DataRequest carries the context a provider needs:

  • tenantId — the current tenant
  • searchTerm — the user's typed search (for dropdown type-ahead)
  • filters — an additional filter map
  • offset / limit — pagination (defaults offset=0, limit=100)
  • pluginId / namespace / providerKey / settings — remaining context

A provider returns List<DataItem>; each DataItem is a value (submitted value), a label (display value), and optional metadata (extra info map). The shape is lookup / dropdown oriented — not an arbitrary field-code → value row set.

Interface signature

package com.auraboot.framework.plugin.extension;

import org.pf4j.ExtensionPoint;

import java.util.List;
import java.util.Map;

public interface DataProviderExtension extends ExtensionPoint {

    /** Unique provider key, format "namespace:provider-name". */
    String getProviderKey();

    /** Return data items for the request. */
    List<DataItem> fetchData(DataRequest request);

    /** Optional: total count matching the request, for pagination (default: size of fetchData). */
    default long getCount(DataRequest request) {
        return fetchData(request).size();
    }

    /** Optional: whether this provider handles the key (default compares getProviderKey()). */
    default boolean supports(String providerKey) {
        return getProviderKey().equals(providerKey);
    }

    /** Optional: whether results are cacheable (default true). */
    default boolean isCacheable() { return true; }

    /** Optional: cache TTL in seconds (default 300). */
    default int getCacheTtlSeconds() { return 300; }
}

DataItem and DataRequest are nested records on the interface:

record DataItem(String value, String label, Map<String, Object> metadata) {
    DataItem(String value, String label) { this(value, label, Map.of()); }
}

record DataRequest(
    Long tenantId, String pluginId, String namespace, String providerKey,
    String searchTerm, Map<String, Object> filters,
    int offset, int limit, Map<String, Object> settings
) {
    static Builder builder() { /* ... */ }
}

Implementation example

A lookup provider that returns a list of currencies:

package com.acme.billing.provider;

import com.auraboot.framework.plugin.extension.DataProviderExtension;
import org.pf4j.Extension;

import java.util.List;
import java.util.Map;

@Extension
public class CurrencyDataProvider implements DataProviderExtension {

    @Override
    public String getProviderKey() {
        return "billing:currencies";
    }

    @Override
    public List<DataItem> fetchData(DataRequest request) {
        return List.of(
            new DataItem("usd", "US Dollar", Map.of("symbol", "$")),
            new DataItem("eur", "Euro",      Map.of("symbol", "€")),
            new DataItem("cny", "Renminbi",  Map.of("symbol", "¥"))
        );
    }
}

For type-ahead, filter on request.searchTerm(); for large lists, paginate by request.offset() / request.limit() and override getCount().

Registration

@Extension annotation only — discovery via META-INF/extensions.idx, collected at startup by ExtensionRegistry into getAllDataProviders(). Provider keys are globally namespaced; always prefix with your own namespace to avoid collisions (billing:currencies is preferable to bare currencies).

Common pitfalls

  • Returning null items, or null value / label. Dropdowns render incorrectly; return an empty List when there's no data.
  • Ignoring getCount(). For large lookups, override it to return the real total and honour offset / limit in fetchData(), or pagination breaks.
  • Bypassing tenant scope. If you query external or shared data, scope explicitly by request.tenantId(). The platform does not filter your return for you.
  • Side effects in fetchData(). The method is called frequently and results may be cached (see isCacheable() / getCacheTtlSeconds()). Keep it read-only.

Related