FieldIndexer migration details

Overview

If your app uses a FieldIndexer via the customfield-searcher element, you will need to update its configuration to match the new search API. This will help prepare your app for future search tool updates that aren’t reliant on Lucene. For a refresher on customfield-searcher, visit the custom field searcher configuration guide.

To help you update your app, this page highlights the key modifications to indexers in the search API.

See the custom field sorting guide and the Search API upgrade guide for more information.

Indexers 

Indexers implemented using the Search API are platform agnostic. To support multiple platforms, the following field definition restrictions exist:

  • Fields must be pre-defined and registered as part of the index schema.
  • Field names must be unique.

As a result, starting from Jira Data Center 10.4, com.atlassian.jira.issue.index.indexers.FieldIndexer is deprecated in favor of com.atlassian.jira.search.issue.index.indexers.FieldIndexer. The corresponding method mappings are provided in the table below for clear reference and implementation guidance.

Legacy Lucene

@com.atlassian.jira.issue.index.indexers.FieldIndexer

Search API

@com.atlassian.jira.search.issue.index.indexers.FieldIndexer

Notes

String getId();

String getId();

Returns the id of the Jira field that this indexer is indexing. This must be unique for each FieldIndexer.

If the Indexer does not represent a System or Custom field in Jira, this must still return a unique string that describes the indexer.

String getDocumentFieldId();

Collection<Field> getFields();

Defines the fields on the index schema to configure how those fields will be stored and indexed on Lucene (and future search platforms).

void addIndex(Document doc, Issue issue)

void indexFields(FieldValueCollector collector, Issue issue, CustomFieldPrefetchedData prefetchedData);


void addIndex(Document doc, Issue issue, CustomFieldPrefetchedData prefetchedData)

void indexFields(FieldValueCollector collector, Issue issue, CustomFieldPrefetchedData prefetchedData);


boolean isFieldVisibleAndInScope(Issue issue);

boolean isFieldVisibleAndInScope(Issue issue);


Boolean skipsIndexingNull()

Boolean skipsIndexingNull()


To create a customized field indexer, make sure you implement the new FieldIndexer interface or extend one of its base classes. Also ensure that all fields have unique names and field IDs.

  1. Define a field (in this example, we’ve used 'currency').
  2. Add this field to the index schema.
  3. Index the field.

Example 1: How to implement a simple FieldIndexer

This example creates a simple field that will always be visible and cannot be hidden. For example, in Jira, this would apply the the issue key or summary.


Legacy LuceneSearch API
public class CurrencyFieldIndexer implements FieldIndexer {
    private final CustomField customField;
    
    public CurrencyFieldIndexer(CustomField customField) {
        this.customField = customField;
    }
    @Override
    public String getId() {
        return customField.getId();
    }
    @Override
    public String getDocumentFieldId() {
        return customField.getId();
    }
    @Override
    public void addIndex(Document doc, Issue issue, CustomFieldPrefetchedData prefetchedData) {
        final String currency = (String) prefetchedData.getData().orElseGet(() -> customField.getValue(issue));
        doc.add(new StringField(getDocumentFieldId(), currency, Field.Store.YES));
        doc.add(new SortedDocValuesField(getDocumentFieldId(), new BytesRef(currency)));
    }
    @Override
    public boolean isFieldVisibleAndInScope(Issue issue) {
       return true;
    }
}


public class CurrencyFieldIndexer extends VisibilityBaseFieldIndexer {
    private final CustomField customField;
    
    public CurrencyFieldIndexer(FieldVisibilityManager fieldVisibilityManager) {
        super(fieldVisibilityManager, 
              KeywordField.builder(customField.getId())
                               .indexed()
                               .docValues()
                               .stored()
                               .build());
        this.customField = customField;
    }
    @Override
    public String getId() {
        return customField.getId();
    }
    @Override
    public void indexFields(FieldValueColloector collector, Issue issue, CustomFieldPrefetchedData prefetchedData) {
        final String currency = (String) prefetchedData.getData().orElseGet(() -> customField.getValue(issue));
        indexField(collector, currency, issue);
    }
    @Override
    public boolean isFieldVisibleAndInScope(Issue issue) {
        /* 
         * This method only needs to be overridden if you need custom logic to determine
         * if the field should be visible. 
         * The base class VisibilityBaseFieldIndexer handles verifying the field is visible on search queries
         * based upon the project permission schema.
         */
    }
}



Example 2: How to implement a FieldIndexer for a field that might be hidden

This example creates a field that might be visible or hidden depending on a condition. For example, in Jira, this applies to the issue description or assignee.


Legacy LuceneSearch API
public class CurrencyFieldIndexer extends BaseFieldIndexer {
    private final CustomField customField;
    
    public CurrencyFieldIndexer(CustomField customField, FieldVisibilityManager fieldVisibilityManager) {
        super(fieldVisibilityManager);
        this.customField = customField;
    }
    @Override
    public String getId() {
        return customField.getId();
    }
    @Override
    public String getDocumentFieldId() {
        return customField.getId();
    }
    @Override
    public void addIndex(Document doc, Issue issue) {
        final String currency = (String) prefetchedData.getData().orElseGet(() -> customField.getValue(issue));
        if (isFieldVisibleAndInScope(issue)) {
            doc.add(new StringField(getDocumentFieldId(), currency, Field.Store.YES));
            doc.add(new SortedSetDocValuesField(getDocumentFieldId(), new BytesRef(currency)));
        } else {
            doc.add(new StoredField(getDocumentFieldId(), currency));
        }       
    }
    @Override
    public boolean isFieldVisibleAndInScope(Issue issue) {
        /* 
         * This method only needs to be overridden if you need custom logic to determine
         * if the field should be visible. 
         * The base class BaseFieldIndexer handles verifying the field is visible on search queries
         * based upon the project permission schema.
         */
    }
}


public class CurrencyFieldIndexer extends VisibilityBaseFieldIndexer {
    private final CustomField customField;
    
    public CurrencyFieldIndexer(FieldVisibilityManager fieldVisibilityManager) {
        super(fieldVisibilityManager, 
              KeywordField.builder(customField.getId())
                               .indexed()
                               .docValues()
                               .stored()
                               .build());
        this.customField = customField;
    }
    @Override
    public String getId() {
        return customField.getId();
    }
    @Override
    public void indexFields(FieldValueColloector collector, Issue issue, CustomFieldPrefetchedData prefetchedData) {
        final String currency = (String) prefetchedData.getData().orElseGet(() -> customField.getValue(issue));
        indexField(collector, currency, issue);
    }
    @Override
    public boolean isFieldVisibleAndInScope(Issue issue) {
        /* 
         * This method only needs to be overridden if you need custom logic to determine
         * if the field should be visible. 
         * The base class VisibilityBaseFieldIndexer handles verifying the field is visible on search queries
         * based upon the project permission schema.
         */
    }
}




Last modified on Jan 21, 2025

Was this helpful?

Yes
No
Provide feedback about this article
Powered by Confluence and Scroll Viewport.