Maven build - unresolved constraints in Google packages

I am trying to develop a Jira Server add on that uses a few Google Java packages to interact with Google Sheets. While everything looks fine in Eclipse, I cannot get the Maven build to work to test the add on when running ‘atlas-run’, and it is driving me bonkers. I feel like I must have done something very wrong, because the build tends to be inconsistent in how it fails. Recently it’s been this error:

[INFO] [talledLocalContainer]     ___ FAILED PLUGIN REPORT _____________________
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]     1 plugin failed to load during JIRA startup.
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]           'com.addon.test.sheets' - 'Google Sheet Updater'  failed to load.
[INFO] [talledLocalContainer]                   Cannot start plugin: com.addon.test.sheets
[INFO] [talledLocalContainer]                           Unresolved constraint in bundle com.addon.test.sheets [179]: Unable to resolve 179.0: missing requirement [179.0] osgi.wiring.package; (osgi.wiring.package=com.google.api.client.auth.oauth2)
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]                   It was loaded from C:\target\jira\home\plugins\installed-plugins\sheets-1.0.jar
[INFO] [talledLocalContainer]

Even though it is my understanding that this package is part of com.google.api-client, which is in the dependencies in my pom.xml (see below). Can anyone help me understand what is going wrong here? Also note that sometimes I can blow away my Maven repository, re-download everything and get the add on to build, but then it gets a run time error! Forgive me, I’m new to Maven but this experience has not convinced me that it’s better than the old days where you found all your jar files and referenced them manually. Any help is greatly appreciated!

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>com.addon.test</groupId>
    <artifactId>sheets</artifactId>
    <version>1.0</version>

    <organization>
        <name>DevTest, LLC</name>
        <url>http://www.google.com</url>
    </organization>

    <name>Google Sheet Updater</name>
    <description></description>
    <packaging>atlassian-plugin</packaging>
	
	<profiles>
		<profile>
			<id>default-profile</id>
			<activation>
				<activeByDefault>true</activeByDefault>
				<file>
					<exists>${java.home}/../lib/tools.jar</exists>
				</file>
			</activation>
			<properties>
				<toolsjar>${java.home}/../lib/tools.jar</toolsjar>
			</properties>
		</profile>
		<profile>
			<id>mac-profile</id>
			<activation>
				<activeByDefault>false</activeByDefault>
				<file>
					<exists>${java.home}/../Classes/classes.jar</exists>
				</file>
			</activation>
			<properties>
				<toolsjar>${java.home}/../Classes/classes.jar</toolsjar>
			</properties>
		</profile>
	</profiles>
	
    <dependencies>
        <dependency>
            <groupId>com.atlassian.jira</groupId>
            <artifactId>jira-api</artifactId>
            <version>${jira.version}</version>
            <scope>provided</scope>
        </dependency>
		
        <dependency>
            <groupId>com.atlassian.activeobjects</groupId>
            <artifactId>activeobjects-plugin</artifactId>
            <version>${ao.version}</version>
            <scope>provided</scope>
        </dependency>		

		<dependency>
			<groupId>com.atlassian.sal</groupId>
			<artifactId>sal-api</artifactId>
			<version>2.0.17</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>com.atlassian.plugins.rest</groupId>
			<artifactId>atlassian-rest-common</artifactId>
			<version>2.7.0</version>
			<scope>provided</scope>
		</dependency>

		<dependency>
			<groupId>com.atlassian.templaterenderer</groupId>
			<artifactId>atlassian-template-renderer-api</artifactId>
			<version>1.1.1</version>
			<scope>provided</scope>
		</dependency>		
		
		<dependency>
			<groupId>org.glassfish.jersey.media</groupId>
			<artifactId>jersey-media-multipart</artifactId>
			<version>2.10</version>
			<scope>provided</scope>
		</dependency>
		
		<dependency>
			<groupId>org.joda</groupId>
			<artifactId>joda-convert</artifactId>
			<version>1.8.1</version>
		</dependency>		
		
        <dependency>
			<groupId>com.sun</groupId>
			<artifactId>tools</artifactId>
			<version>${java.version}</version>
			<systemPath>${toolsjar}</systemPath>
			<scope>system</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>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>compile</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>com.google.collections</groupId>
			<artifactId>google-collections</artifactId>
			<version>1.0</version>
			<scope>provided</scope>
		</dependency>		

        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.2-atlassian-1</version>
        </dependency>
        
        <dependency>
        	<groupId>com.google.api-client</groupId>
        	<artifactId>google-api-client</artifactId>
        	<version>1.23.0</version>
        </dependency>
        
        <dependency>
        	<groupId>com.google.oauth-client</groupId>
        	<artifactId>google-oauth-client</artifactId>
        	<version>1.23.0</version>
        </dependency>
                
        <dependency>
          <groupId>com.google.apis</groupId>
          <artifactId>google-api-services-sheets</artifactId>
          <version>v4-rev487-1.23.0</version>
        </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>

		<!-- 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>com.atlassian.upm</groupId>
    		<artifactId>licensing-api</artifactId>
    		<version>2.0.1</version>
    		<scope>provided</scope>
		</dependency>
		<dependency>
    		<groupId>com.atlassian.upm</groupId>
    		<artifactId>upm-api</artifactId>
    		<version>2.0.1</version>
    		<scope>provided</scope>
		</dependency>
    </dependencies>

    <build>
        <plugins>

			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.5.1</version>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
			
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-surefire-plugin</artifactId>
				<version>2.20.1</version>
				<configuration>
					<forkCount>3</forkCount>
					<reuseForks>true</reuseForks>
					<argLine>-Xmx1024m -XX:MaxPermSize=256m</argLine>
				</configuration>
			</plugin>			
			
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>			
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
						<id>copy-dependencies</id>
						<phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.outputDirectory}</outputDirectory>
                            <includeArtifactIds></includeArtifactIds>
                            <stripVersion>true</stripVersion>
							<overWriteIfNewer>true</overWriteIfNewer>
							<includeScope>runtime</includeScope>
							<excludeGroupIds>${project.groupId}</excludeGroupIds>
							<excludeArtifactIds>...</excludeArtifactIds>							
                        </configuration>
                    </execution>
                </executions>
            </plugin>				

            <plugin>
                <groupId>com.atlassian.maven.plugins</groupId>
                <artifactId>maven-jira-plugin</artifactId>
                <version>${amps.version}</version>
                <extensions>true</extensions>
                <configuration>
                    <productVersion>${jira.version}</productVersion>
                    <productDataVersion>${jira.version}</productDataVersion>
					<!-- 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.addon.test.sheets.api,
							*
                        </Export-Package>

                        <!-- Add package import here -->
                         <Import-Package>
							*
                         </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>
        <jira.version>7.1.7</jira.version>
        <amps.version>6.2.4</amps.version>
        <ao.version>1.1.2</ao.version>		
        <plugin.testrunner.version>1.2.3</plugin.testrunner.version>
        <atlassian.spring.scanner.version>1.2.6</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>
    </properties>

</project>

Show us the annotations on your classes in com.addon.test.sheets.api. You’re using Spring Scanner so we’ll need to see how you’ve defined the component imports.

That is a good point… Looking at my code, I don’t actually have any component import annotations (or any annotations for that matter, except for those from javax.ws.rs for the web services… Is there anything I need to do to disable the Spring Scanner, other than remove the Spring-Context node in the pom.xml? I understand not using Spring Scanner will make the plugin take longer to load, but I’d rather get it working first then deal with speed.

That would be a step backward: You should focus on getting Spring Scanner working sine it’s the preferred method of working with Atlassian Plugins.

I do not know how to remove Spring Scanner from a plugin, I’ve only started with Spring Scanner with new projects.

You should have a @Named(“MyClass”) annotation
You should have a @ComponentImport annotation above each item you’re injecting
You should have an @Inject annotation above your constructor where you are injecting dependancies

In practice, the top of your code would look like this:

@Named("ConfigManager")
public class ConfigManager {

    @ComponentImport
    private final ActiveObjects ao;
    @ComponentImport
    private final ProjectManager projectManager;
    @ComponentImport
    private final UserManager userManager;

    @Inject
    public ConfigManager(ActiveObjects ao, ProjectManager projectManager, UserManager userManager) {
        this.ao = ao;
        this.projectManager = projectManager;
        this.userManager = userManager;
    }

Thanks for the pointers. I started annotated everything and at first ran in to some issues that required atlassian-plugin.xml changes (NoUniqueBeanDefinitionException) and now I get new and different errors. I suppose this is progress.

Currently dealing with this one. The format is slightly different than previous errors, but it’s still coming from the Maven build and referring to a ‘missing’ package that Eclipse has through my Maven repository. If I look in the resulting sheets-1.0.jar, the “google-api-client-http.jar” is in the root folder there, and it contains the com/google/api/client/http path, which contains HttpTransport.class.

[INFO] [talledLocalContainer]     ___ FAILED PLUGIN REPORT _____________________
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]     1 plugin failed to load during JIRA startup.
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]           'com.addon.test.sheets' - 'Google Sheet Updater'  failed to load.
[INFO] [talledLocalContainer]                   com/google/api/client/http/HttpTransport
[INFO] [talledLocalContainer]                           com.google.api.client.http.HttpTransport not found by com.addon.test.sheets [179]
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]                   It was loaded from C:\target\jira\home\plugins\installed-plugins\sheets-1.0.jar
[INFO] [talledLocalContainer]

Here is my class with annotations. I have noticed that if I remove the HttpTransport element from the class, it will build just fine. So it seems like something involved in the build process really cannot find the class, but since the class is in the jar that results from the build, I can’t understand it.

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletRequest;

import com.atlassian.activeobjects.external.ActiveObjects;
import com.atlassian.jira.bc.issue.search.SearchService;
import com.atlassian.jira.component.ComponentAccessor;
import com.atlassian.jira.issue.CustomFieldManager;
import com.atlassian.jira.issue.Issue;
import com.atlassian.jira.issue.IssueFactory;
import com.atlassian.jira.issue.IssueManager;
import com.atlassian.jira.issue.fields.CustomField;
import com.atlassian.jira.issue.fields.FieldManager;
import com.atlassian.jira.issue.search.SearchException;
import com.atlassian.jira.issue.search.SearchProvider;
import com.atlassian.jira.issue.search.SearchProviderFactory;
import com.atlassian.jira.issue.search.SearchRequest;
import com.atlassian.jira.issue.search.SearchResults;
import com.atlassian.jira.plugin.searchrequestview.AbstractSearchRequestView;
import com.atlassian.jira.plugin.searchrequestview.SearchRequestParams;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.util.json.JSONArray;
import com.atlassian.jira.util.json.JSONException;
import com.atlassian.jira.util.json.JSONObject;
import com.atlassian.jira.web.ExecutingHttpRequest;
import com.atlassian.jira.web.bean.PagerFilter;
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
import com.atlassian.sal.api.pluginsettings.PluginSettingsFactory;
import com.atlassian.templaterenderer.TemplateRenderer;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.sheets.v4.Sheets;
import com.google.api.services.sheets.v4.SheetsScopes;
import com.google.gson.Gson;
import com.addon.test.sheets.ao.Configuration;
import com.addon.test.sheets.rest.RESTRequest;

import static com.google.common.base.Preconditions.*;

@Named("Sheets")
public class Sheets extends AbstractSearchRequestView {
	
	@ComponentImport
	private final JiraAuthenticationContext authenticationContext;
	
	@ComponentImport
	private final CustomFieldManager customFieldManager;
	
	@ComponentImport
	private final FieldManager fieldManager;
	
	@ComponentImport
	private final TemplateRenderer templateRenderer;
	
	@ComponentImport
	private final ActiveObjects ao;
	
	private List<com.atlassian.jira.issue.fields.Field> systemFields = null;

	@ComponentImport
	private static final JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

	@ComponentImport
	private HttpTransport httpTransport;
	
	@Inject
	public Sheets(ActiveObjects ao, JiraAuthenticationContext authenticationContext, SearchProviderFactory searchProviderFactory, 
		IssueFactory issueFactory, SearchProvider searchProvider, CustomFieldManager customFieldManager, IssueManager issueManager, FieldManager fieldManager, 
		TemplateRenderer templateRenderer, PluginSettingsFactory pluginSettingsFactory, HttpTransport httpTransport) {
    	
        this.authenticationContext = authenticationContext;
        this.customFieldManager = customFieldManager;
        this.fieldManager = fieldManager;
        this.templateRenderer = templateRenderer; 
        this.systemFields = getSystemFieldsList();
        this.ao = checkNotNull(ao);
        this.httpTransport = httpTransport;
        
        try {
        	this.httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        } catch (Exception ex) { }
}	
	<SNIP>