Atlassian Spring Scanner: Plugin modules not enabled

Samantha Webber November 23, 2017

Hello,

 

When I start JIRA from the SDK, I notice my plugin is not enabled under Manage Add-Ons. Upon clicking Enable, I receive the following message:

"This add-on failed to enable. Refer to the logs for more information."

 

The logs say,

"Cannot execute atlasian-spring-scanner-runtime: plugin has an extra copy of atlassian-spring-scanner-annotation clases, perhaps embedded inside the target plugin 'com.psi.intern.jira.custom-fields'; embedding scanner-annotations is not supported since scanner version 2.0. Use 'mvn dependency:tree' and ensure the atlassian-spring-scanner-annotation depenency in your plugin has <scope>provided</scope>, not 'runtime' or 'compile', and you have NO dependency on atlassian-spring-scanner-runtime.

 

I changed these dependencies in the pom accordingly, however, I receive this message nonetheless. Uncertain where else I should look for this problem.

 

My pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>xxx</groupId>
    <artifactId>custom-fields</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>atlassian-plugin</packaging>

    <name>JIRA Add-On :: Custom Fields AND One Gadget</name>
    <description>This is the container plugin for all internally used custom fields for Atlassian JIRA and one gadget.</description>

    <organization>
       
    </organization>

    <dependencies>
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-api</artifactId>
            <version>${jira.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- Add dependency on jira-core if you want access to JIRA implementation classes as well as the sanctioned API. -->
        <!-- This is not normally recommended, but may be required eg when migrating a plugin originally developed against JIRA 4.x -->
        
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-core</artifactId>
            <version>${jira.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-rest-plugin</artifactId>
            <version>7.1.0-QR20151013113456</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugins.rest</groupId>
            <artifactId>atlassian-rest-common</artifactId>
            <version>${rest.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-gadgets-plugin</artifactId>
            <version>${jira.version}</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.atlassian.plugin</groupId>
            <artifactId>atlassian-spring-scanner-annotation</artifactId>
            <version>${atlassian.spring.scanner.version}</version>
            <scope>provided</scope>
        </dependency>
       <!-- <dependency>
            <groupId>com.atlassian.plugin</groupId>
            <artifactId>atlassian-spring-scanner-runtime</artifactId>
            <version>${atlassian.spring.scanner.version}</version>
            <scope>runtime</scope>
        </dependency> -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
            <scope>provided</scope>
        </dependency>
        <!-- WIRED TEST RUNNER DEPENDENCIES -->
       <dependency>
            <groupId>com.atlassian.plugins</groupId>
            <artifactId>atlassian-plugins-osgi-testrunner</artifactId>
            <version>${plugin.testrunner.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>jsr311-api</artifactId>
            <version>1.1.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.2-atlassian-1</version>
        </dependency>
        <!-- Uncomment to use TestKit in your project. Details at https://bitbucket.org/atlassian/jira-testkit -->
        <!-- You can read more about TestKit at https://developer.atlassian.com/display/JIRADEV/Plugin+Tutorial+-+Smarter+integration+testing+with+TestKit -->
        <!--
        <dependency>
            <groupId>com.atlassian.jira.tests</groupId>
            <artifactId>jira-testkit-client</artifactId>
            <version>${testkit.version}</version>
            <scope>test</scope>
        </dependency>
        -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.6</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.8.5</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-jira-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <noWebapp>true</noWebapp>
                    <productVersion>${jira.version}</productVersion>
                    <productDataVersion>${jira.version}</productDataVersion>
                    <allowGoogleTracking>false</allowGoogleTracking>
                    <!-- Uncomment to install TestKit backdoor in JIRA. -->
                    <!--
                    <pluginArtifacts>
                        <pluginArtifact>
                            <groupId>com.atlassian.jira.tests</groupId>
                            <artifactId>jira-testkit-plugin</artifactId>
                            <version>${testkit.version}</version>
                        </pluginArtifact>
                    </pluginArtifacts>
                    -->
                    <enableQuickReload>true</enableQuickReload>
                    <enableFastdev>false</enableFastdev>
                    <!-- See here for an explanation of default instructions: -->
                    <!-- https://developer.atlassian.com/docs/advanced-topics/configuration-of-instructions-in-atlassian-plugins -->
                    <instructions>
                        <Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
                        <!-- Add package to export here -->
                        <Export-Package>com.psi.intern.jira.customfield.api.*</Export-Package>
                        <!-- Add package import here -->
                        <Import-Package>org.springframework.osgi.*;resolution:="optional", org.eclipse.gemini.blueprint.*;resolution:="optional", *</Import-Package>
                        <!-- Ensure plugin is spring powered -->
                        <Spring-Context>*</Spring-Context>
                    </instructions>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.atlassian.plugin</groupId>
                <artifactId>atlassian-spring-scanner-maven-plugin</artifactId>
                <version>${atlassian.spring.scanner.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>atlassian-spring-scanner</goal>
                        </goals>
                        <phase>process-classes</phase>
                    </execution>
                </executions>
                <configuration>
                    <scannedDependencies>
                        <dependency>
                            <groupId>com.atlassian.plugin</groupId>
                            <artifactId>atlassian-spring-scanner-external-jar</artifactId>
                        </dependency>
                    </scannedDependencies>
                    <verbose>false</verbose>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <jira.version>7.3.0</jira.version>
        <amps.version>6.2.6</amps.version>
        <plugin.testrunner.version>1.2.3</plugin.testrunner.version>
        <atlassian.spring.scanner.version>1.2.13</atlassian.spring.scanner.version>
        <!-- This key is used to keep the consistency between the key in atlassian-plugin.xml and the key to generate bundle. -->
        <atlassian.plugin.key>${project.groupId}.${project.artifactId}</atlassian.plugin.key>
        <!-- TestKit version 6.x for JIRA 6.x -->
        <testkit.version>6.3.11</testkit.version>
        <rest.version>3.2.14</rest.version>
    </properties>
</project>

 

Thanks!

3 answers

1 vote
peter_niederer_protonmail_com January 7, 2019

some good examples -that really work, would be helpful. I tried some tutorials,  but they all finally led to exceptions..... frustrating

steve-killelay April 3, 2019

Agreed, some (most - all the ones I've worked thorugh) of the tutorials provided by Atlassian are somewhat dated now - I too have hit a number of issues continually refactoring plugins against endless list of deprecated, removed and repackaged objects...

1 vote
Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 24, 2017

Hello,

I just tried your code with your pom and everything worked fine for me. There must be something else.

and you should change //@Autowired to @Inject

Samantha Webber November 24, 2017

Hi,

I deleted the class MyPluginComponentImpl because I wasn't using it anyway, and everything is working fine now.

 

Why should @Autowired be @Inject? What is the difference there exactly?

Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 24, 2017

I had problems with autowired in constructors before. I use inject and had no ptoblems so far. Instead of scanned i use named. also the same reason. You removed the file but it does not mean you solved the problem. You can get same error later when you call your custom field.

Samantha Webber November 30, 2017

Okay, I restored the file and changed @Autowired to @Inject, as well as @Scanned to @Named, and I get the following errors:

 

cannot find symbol

symbol class Named

 

and the same for Inject. Sorry when the solution is obvious but after a Google Search the solution for 'cannot find symbol' seems to be injecting a dependency into the constructor, which I think is exactly what I'm doing right now...?

Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 30, 2017

Can you add imports to the class

import javax.inject.Inject;
import javax.inject.Named;

add dependency to the pom.xml
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
<scope>provided</scope>
</dependency>
Samantha Webber November 30, 2017

Thanks. Now I get BUILD SUCCESSFUL with atlas-mvn install.

 

However now I receive "Plugin 'com.psi.intern.jira.custom-fields-tests' never resolved service '&myComponent' with filter '(objectClass=com.psi.intern.jira.customfield.api.MyPluginComponent)'"

Class in question:

package com.psi.intern.jira.customfield.api;

public interface MyPluginComponent
{
    String getName();
}

 

Do you have an idea where I should start looking for the problem?

Samantha Webber November 30, 2017

I also don't understand why it's telling me the Plugin 'com.psi.intern.jira.custom-fields-tests' is not resolving. This is the plugin key in an atlassian-plugin.xml in target\test-classes. Where is that coming from?

Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 30, 2017

You can delete test folder in your plugin. And run atlas-mvn clean install

Samantha Webber December 1, 2017

Hello Alexey. Thank you for your continued support. I deleted the test folder and ran atlas-mvn clean install.

Now everything is running, I am getting a new error but this I can discuss in a different post as it is a different topic. Important for this topic, the modules of my plugin are enabled again!

Here is my code:

PercentageField.java:

package com.psi.intern.jira.customfield;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.atlassian.jira.issue.customfields.impl.AbstractSingleFieldType;
import com.atlassian.jira.issue.customfields.impl.FieldValidationException;
import com.atlassian.jira.issue.customfields.manager.GenericConfigManager;
import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister;
import com.atlassian.jira.issue.customfields.persistence.PersistenceFieldType;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import javax.inject.Inject;
import javax.inject.Named;

import org.springframework.beans.factory.annotation.Autowired;

import java.math.BigDecimal;

@Scanned
public class PercentageField extends AbstractSingleFieldType<BigDecimal> {
    private static final Logger log = LoggerFactory.getLogger(PercentageField.class);

    //@Inject
    public PercentageField(@ComponentImport CustomFieldValuePersister customFieldValuePersister, @ComponentImport GenericConfigManager genericConfigManager) {

        super(customFieldValuePersister, genericConfigManager);

    }

    @Override
    protected PersistenceFieldType getDatabaseType()
    {
        return PersistenceFieldType.TYPE_DECIMAL;
    }

    @Override
    protected Object getDbValueFromObject(final BigDecimal customFieldObject)
    {
        return getStringFromSingularObject(customFieldObject);
    }

    @Override
    protected BigDecimal getObjectFromDbValue(final Object databaseValue)
            throws FieldValidationException
    {
        return getSingularObjectFromString(String.valueOf(databaseValue));
    }

    @Override
    public String getStringFromSingularObject(final BigDecimal singularObject)
    {
        if (singularObject == null) {
            return "";
        }
        // format
        return singularObject.toString();
    }

    @Override
    public BigDecimal getSingularObjectFromString(final String string)
            throws FieldValidationException
    {
        if (string == null)
            return null;
        try
        {
            final BigDecimal decimal = new BigDecimal(string);
            // Check that we don't have too many decimal places
            if (decimal.scale() > 2)
            {
                throw new FieldValidationException(
                        "Maximum of 2 decimal places are allowed.");
            }
            return decimal.setScale(2);
        }
        catch (NumberFormatException ex)
        {
            throw new FieldValidationException("Not a valid number.");
        }
    }
}

MyPluginComponentImpl.java:

package com.psi.intern.jira.customfield.impl;

import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
//import com.atlassian.sal.api.ApplicationProperties;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.psi.intern.jira.customfield.api.MyPluginComponent;

import javax.inject.Inject;
import javax.inject.Named;

import org.springframework.beans.factory.annotation.Autowired;

@ExportAsService ({MyPluginComponent.class})
@Named ("myPluginComponent")
public class MyPluginComponentImpl implements MyPluginComponent
{
    private final ApplicationProperties applicationProperties;

    @Inject
    public MyPluginComponentImpl(final ApplicationProperties applicationProperties)
    {
        this.applicationProperties = applicationProperties;
    }

    public String getName()
    {
        if(null != applicationProperties)
        {
            return "myComponent:";
        }
        
        return "myComponent";
    }
}

 MyPluginComponent.java:

package com.psi.intern.jira.customfield.api;

public interface MyPluginComponent
{
    //String getString(String name);
}

 

As you'll notice, I had to comment out the method getString() in the MyPluginComponent interface, as well as remove the call to getDisplayName() in the MyPluginComponentImpl.java, because these methods appear not to exist in com.atlassian.jira.config.properties.ApplicationProperties and I am just having problems when I import com.atlassian.sal.api.ApplicationProperties.

Samantha Webber December 1, 2017

I find it quite frustrating that I have to change so much from what's given in the tutorials, though. Why do the tutorials include methods that don't exist in the classes they import? Why is there a whole test folder which I should just delete?

 

In any case thank you very much for your help, I would have been stuck in the same place for a while otherwise!

0 votes
Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 23, 2017

Try to go to the folder of your plugin and make a clean package

atlas-mvn clean package

Samantha Webber November 24, 2017

Thanks Alexey, that helped change the error message. Now I am getting "No qualifying bean of type [com.atlassian.jira.config.properties.ApplicationProperties] ... expected at least 1 bean which qualifies as autowire candidate for this dependency."

 

Will try to figure this out and get back to this question with an update.

Alexey Matveev
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
November 24, 2017

Hello,

Unfortunately I can not provide feedback about the new error. I would need to see your code.

Samantha Webber November 24, 2017

Here is the Java class for the custom field in my plugin, as well as MyPluginComponentImpl.java:

 

package com.psi.intern.jira.customfield;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.atlassian.jira.issue.customfields.impl.AbstractSingleFieldType;
import com.atlassian.jira.issue.customfields.impl.FieldValidationException;
import com.atlassian.jira.issue.customfields.manager.GenericConfigManager;
import com.atlassian.jira.issue.customfields.persistence.CustomFieldValuePersister;
import com.atlassian.jira.issue.customfields.persistence.PersistenceFieldType;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;

import java.math.BigDecimal;

@Scanned
public class PercentageField extends AbstractSingleFieldType<BigDecimal> {
    private static final Logger log = LoggerFactory.getLogger(PercentageField.class);

    //@Autowired
    public PercentageField(@ComponentImport CustomFieldValuePersister customFieldValuePersister, @ComponentImport GenericConfigManager genericConfigManager ) {

        super(customFieldValuePersister, genericConfigManager);

    }

    @Override
    protected PersistenceFieldType getDatabaseType()
    {
        return PersistenceFieldType.TYPE_DECIMAL;
    }

    @Override
    protected Object getDbValueFromObject(final BigDecimal customFieldObject)
    {
        return getStringFromSingularObject(customFieldObject);
    }

    @Override
    protected BigDecimal getObjectFromDbValue(final Object databaseValue)
            throws FieldValidationException
    {
        return getSingularObjectFromString(String.valueOf(databaseValue));
    }

    @Override
    public String getStringFromSingularObject(final BigDecimal singularObject)
    {
        if (singularObject == null) {
            return "";
        }
        // format
        return singularObject.toString();
    }

    @Override
    public BigDecimal getSingularObjectFromString(final String string)
            throws FieldValidationException
    {
        if (string == null)
            return null;
        try
        {
            final BigDecimal decimal = new BigDecimal(string);
            // Check that we don't have too many decimal places
            if (decimal.scale() > 2)
            {
                throw new FieldValidationException(
                        "Maximum of 2 decimal places are allowed.");
            }
            return decimal.setScale(2);
        }
        catch (NumberFormatException ex)
        {
            throw new FieldValidationException("Not a valid number.");
        }
    }
}

 

package com.psi.intern.jira.customfield.impl;

import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.sal.api.ApplicationProperties;
import com.psi.intern.jira.customfield.api.MyPluginComponent;

import javax.inject.Inject;
import javax.inject.Named;

@ExportAsService ({MyPluginComponent.class})
@Named ("myPluginComponent")
public class MyPluginComponentImpl implements MyPluginComponent
{
    @ComponentImport
    private final ApplicationProperties applicationProperties;

    @Inject
    public MyPluginComponentImpl(final ApplicationProperties applicationProperties)
    {
        this.applicationProperties = applicationProperties;
    }

    public String getName()
    {
        if(null != applicationProperties)
        {
            return "myComponent:" + applicationProperties.getDisplayName();
        }
        
        return "myComponent";
    }
}
Andrew S
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
October 14, 2019

It sounds like you are now being bitten by https://ecosystem.atlassian.net/browse/SCANNER-67. Either try the workaround suggested there, or migrate to Spring Java config, which doesn't have this bug.

Suggest an answer

Log in or Sign up to answer