How to fix broken Extractors

Problem

Your plugin exhibits the following stack trace when starting up in Confluence version 5.2 and later. 

com.atlassian.plugin.PluginParseException: Error retrieving dependency of class: com.atlassian.confluence.plugins.MyExtractor. Missing class: org/apache/lucene/document/Fieldable
	at com.atlassian.plugin.module.LegacyModuleFactory.getModuleClass(LegacyModuleFactory.java:50)
	...
Caused by: java.lang.NoClassDefFoundError: org/apache/lucene/document/Fieldable
	at java.lang.Class.getDeclaredConstructors0(Native Method)
	...
Caused by: java.lang.ClassNotFoundException: org.apache.lucene.document.Fieldable
	at java.lang.ClassLoader.findClass(ClassLoader.java:358)
	... 

Solution

You have two options: 

  1. Find the class in your plugin that implements Extractor and update it to implement Extractor2 OR
  2. Maintain separate branches of development for your plugin. One for Confluence versions => 5.2 and the other for < 5.2. 

Option 1 - Implementing Extractor2

The benefit of this option is that you can continue to maintain a single branch of your plugin that will work for Confluence versions 3.5 to 5.2+. 

The first step is add the following dependency to your plugin's pom.xml

        <dependency>
            <groupId>com.atlassian.confluence.plugins</groupId>
            <artifactId>confluence-extractor-api-plugin</artifactId>
            <version>${confluence-extractor-api-plugin.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.labs</groupId>
            <artifactId>lucene-compat-plugin</artifactId>
            <version>${lucene-compat-plugin.version}</version>
            <scope>provided</scope>
        </dependency>

and

    <properties>
        <confluence-extractor-api-plugin.version>1.0.1</confluence-extractor-api-plugin.version>
        <lucene-compat-plugin.version>1.1.1</lucene-compat-plugin.version>
    </properties>

This will make the new Extractor2 interface available for you to use. 

Next, update your Extractor implementation. Here's some sample code demonstrating how to add an index field in Extractor2:

import com.atlassian.confluence.plugins.index.api.Extractor2;
import com.atlassian.confluence.plugins.index.api.FieldDescriptor;
 
public class MyExtractor implements Extractor2
{
    @Override
    public Collection<FieldDescriptor> extractFields(Object searchable)
    {
        return Collections.singleton(new FieldDescriptor("fieldName", "fieldValue", FieldDescriptor.Store.NO, FieldDescriptor.Index.ANALYZED));
    }

    @Override
    public StringBuilder extractText(Object searchable)
    {
        return new StringBuilder(0);
    }
}

Instead of calling lucene's document.add(new Field(...)), you now return an instance of the generic FieldDescriptor class. We will then translate this into the appropriate version of lucene for you. 

Next, update your atlassian-plugin.xml. Remove:

<extractor name="My Extractor" key="myExtractor" class="com.atlassian.confluence.plugins.MyExtractor"/>

And in it's place, add: 

    <component name="My Extractor" key="myExtractor" class="com.atlassian.confluence.plugins.MyExtractor" public="true">
        <interface>com.atlassian.confluence.plugins.index.api.Extractor2</interface>
    </component>

Notes:

  • You must declare this component as public="true" (otherwise, your extractor won't be picked up)
  • You must remove the old <extractor> declaration.

Now make the following change to your pom.xml:

    <build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-confluence-plugin</artifactId>
                <version>4.2.3</version>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Import-Package>
                            com.atlassian.confluence.plugins.index.api;version="[1,2)",
                            *;resolution:=optional
                        </Import-Package>
                        <Require-Bundle>
                            com.atlassian.confluence.plugins.confluence-extractor-api-plugin;bundle-version="${confluence-extractor-api-plugin.version}",
                            com.atlassian.labs.lucene-compat-plugin;bundle-version="${lucene-compat-plugin.version}"
                        </Require-Bundle>
                    </instructions>
                    <pluginDependencies><!-- this section declares the following as dependent bundles to be incorporated into the OBR -->
                        <pluginDependency>
                            <groupId>com.atlassian.confluence.plugins</groupId>
                            <artifactId>confluence-extractor-api-plugin</artifactId>
                        </pluginDependency>
                        <pluginDependency>
                            <groupId>com.atlassian.labs</groupId>
                            <artifactId>lucene-compat-plugin</artifactId>
                        </pluginDependency>
                    </pluginDependencies>
                    <bundledArtifacts><!-- this section is for atlas-XXX commands -->
                        <bundledArtifact>
                            <groupId>com.atlassian.confluence.plugins</groupId>
                            <artifactId>confluence-extractor-api-plugin</artifactId>
                        </bundledArtifact>
                        <bundledArtifact>
                            <groupId>com.atlassian.labs</groupId>
                            <artifactId>lucene-compat-plugin</artifactId>
                        </bundledArtifact>
                    </bundledArtifacts>
                </configuration>
            </plugin>
        </plugins>
    </build>

Notes:

  • <Import-Package> for com.atlassian.confluence.plugins.index.api will import this package which is exported by the confluence-extractor-api-plugin.
  • *;resolution:=optional is important to allow any additional imports to be automatically pulled in
  • <Require-Bundle> and <pluginDependencies> are the required tags to enable an OBR to be produced that bundles all the required dependencies (in this case, confluence-extractor-api-plugin and the lucene-compat-plugin).
  • The confluence-extractor-api-plugin is responsible for exporting the API packages
  • The lucene-compat-plugin is responsible for executing implementations of Extractor2.

After you have completed all these steps, mvn clean package will produce, in addition to the standard JAR file, an OBR file. If you are unfamiliar with this, it is basically an archive that contains the JAR file and all the dependent plugins required by your plugin. The UPM (Universal Plugin Manager) is aware of the OBR format and therefore OBR files can be uploaded using the UPM. As the marketplace also uses the UPM, we also recommend delivering your product as an OBR in the marketplace. 

Option 2 - Maintaining a separate branch

Confluence version 5.2+ has been upgraded from lucene 2.9 to lucene 4.3. If your plugin directly uses the lucene 2.9 API, we expect your plugin to either: 

  • not compile against Confluence 5.2
  • fail with a NoClassDefFoundError exception at runtime

Therefore, if you depend on lucene's API directly, you will have to maintain at least 2 branches of your plugin, one for lucene 2.9 and one for lucene 4.3. 

To avoid this, we suggest implementing Extractor2 which has been decoupled from lucene. 

Was this page helpful?

Have a question about this article?

See questions about this article

Powered by Confluence and Scroll Viewport