Missed Team ’24? Catch up on announcements here.

×
Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in

Trying to implement Marketplace Licensing, getting ClassNotFoundException

Scott Dudley [Inactive]
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.
April 17, 2013

Hi all,

I am trying to enable a JIRA plugin for Atlassian-paid Marketplace licensing, but I am running into problems after applying the atlas-create-jira-plugin-module code to our plugin.

The main issue is that we are getting the following exception when trying to load our plugin into the host product:

2013-04-18 12:10:05,305 Spring executor 19 ERROR admin 730x11x1 18pyy3 127.0.0.1 /rest/plugins/1.0/ [extender.internal.activator.ContextLoaderListener] Application context refresh failed (NonValidatingOsgiBundleXmlApplicationContext(bundle=com.mycompany.myproduct, config=osgibundle:/META-INF/spring/*.xml))
java.lang.NoClassDefFoundError: com/atlassian/upm/license/storage/plugin/PluginLicenseStorageManager
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2451)
	at java.lang.Class.getDeclaredMethods(Class.java:1810)

After some painful bisection (comparing our product to the upm-licensing-compatibility sample), we tracked the issue down to a custom Spring configuration file of ours.

In particular, our plugin has the following in src/main/resources/META-INF/spring/spring.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
">

    <context:annotation-config/>
    <context:component-scan base-package="com.mycompany.myproduct" />
</beans>

Our spring.xml exists only because, contrary to what the docs seem to say, JIRA does not invoke any of our @PreDestroy annotated-methods unless we include the <context:annotation-config> element in the Spring configuration.

If I remove our custom spring.xml, the above PluginLicenseStorageManager error goes away and the plugin loads correctly, but then our @PreDestroy-annotated methods don't get called. And if I remove Marketplace licensing support, I can also build and install using our custom spring.xml (and get @PreDestroy annotations to work) with no problems. I just can't seem to get them to work all together.

Ideas?

For example, is there any way to tell the plugin framework to insert instructions directly into the auto-generated Spring configurations, rather than creating our own file? Or to adjust our spring.xml to be Marketplace Licensing-compatible?

(At runtime, in the transformed-plugins directory, I see that our spring.xml is just added to the three Spring configs that the plugin framework generates automatically, and I suspect the problem may be that we're having our code instantiated in a different context than the plugin licensing code.)

Alternatively, is there a way to get @PreDestroy to work without having to hack Spring?

Thanks,
Scott

2 answers

1 accepted

Comments for this post are closed

Community moderators have prevented the ability to post new answers.

Post a new question

3 votes
Answer accepted
Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 22, 2013

Have you tried implementing DisposableBean instead of annotating with the @PreDestroy annotation? That usually works for me!

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 22, 2013

OK, no problem. Let me go digging for more answers.

Scott Dudley [Inactive]
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.
April 22, 2013

Hi Joe,

Thanks for your answer! As it happens, we are already using DisposableBean for other things...but:

The issue is that we needed to move some specific processing to @PreDestroy because we could not make it work inside the regular DisposableBean destroy() method. This happened a while ago, so I am only 85% sure...but I believe our plugin was unable to access other required beans during the destroy() phase. I also seem to recall that the problems occurred during JIRA shutdown (because the other beans were already getting destroyed?), and the end result caused the JVM to fail to shut down cleanly.

To make a long story short: if at all possible, we'd like to keep the @PreDestroy method so that we don't need to retest that stuff and find other workarounds to that original problem.

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 23, 2013

I haven't been able to re-produce your problem so far. Here's a skeleton plugin I've been testing with that has a component with the @PreDestroy annotation, a custom Spring.xml and the UPM plugin licensing code (added via the atlas-create-jira-plugin-module) command.

https://dl.dropboxusercontent.com/u/7602897/test-licensing-plugin.zip

The plugin loads successfully, I can access the licensing servlets, and the PreDestroy code is fired correctly when the plugin is disabled.

Can you identify any key differences between your plugin and this? Do you have any additional content in your custom spring.xml? Any custom bundle-instructions in your pom.xml or atlassian-plugin.xml?

If you want to share your code with me so I can examine it, feel free - you know my email address :-)

Scott Dudley [Inactive]
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.
April 23, 2013

For what it's worth, I was also able to reproduce the same error by starting with the stock UPM licensing compatibility and modifying it as follows:

git clone https://bitbucket.org/atlassian_tutorial/upm-licensing-compatibility.git
cd upm-licensing-compatibility/
mkdir -p src/main/resources/META-INF/spring
mv /tmp/spring.xml src/main/resources/META-INF/spring/
atlas-cli -p 8085 --context-path ""
maven2&gt; pi

...where /tmp/spring.xml consists of exactly this:

&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
                        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
"&gt;

    &lt;context:annotation-config/&gt;
    &lt;context:component-scan base-package="com.example.plugins.tutorial" /&gt;
&lt;/beans&gt;
Scott Dudley [Inactive]
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.
April 23, 2013

Unfortunately, your code doesn't install here either!

I am building with SDK 4.1.7 and uploading to a JIRA 5.2.9-standalone instance w/production dB, all running on OS X 10.8.3 with Java 1.7u21. Installing with atlas-cli "pi" yields the following error for your code:

2013-04-23 23:08:58,130 pool-8-thread-1 INFO admin 1388x2x1 1klw6jh 127.0.0.1 /rest/plugins/1.0/ [atlassian.plugin.util.WaitUntil] Plugins that have yet to be enabled: [com.atlassian.jira.test.test-licensing-plugin], 60 seconds remaining
2013-04-23 23:08:58,508 Spring executor 11 ERROR      [plugin.osgi.factory.OsgiPlugin] Unable to start the Spring context for plugin com.atlassian.jira.test.test-licensing-plugin
java.lang.NoClassDefFoundError: com/atlassian/upm/license/storage/plugin/PluginLicenseStorageManager
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2436)
	at java.lang.Class.getDeclaredMethods(Class.java:1793)
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:460)
	at org.springframework.util.ReflectionUtils.doWithMethods(ReflectionUtils.java:443)

Is there anything I should be looking at in my configuration?

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 23, 2013

Hm, I was testing on JDK 6. Let me see if that makes a difference :)

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 23, 2013

OK, I can repro the error if I use atlas-cli to install the plugin in a JIRA instance. If I use "atlas-run" or "atlas-debug" to simultaneously start a version of JIRA and install the upm licensing compatibility test plugin, then it works.

I believe that this has to do with the bundledArtifacts/pluginArtifacts configuration in the pom.xml, but not sure 100% sure as to the root cause yet.

Scott Dudley [Inactive]
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.
April 24, 2013

This is getting interesting now. :-)

I took the .JAR that was build by running "pi" and tried to upload it manually via the UPM--no dice.

I cleaned up target and rebuilt the JAR from "atlas-mvn package", then tried to upload it manually via UPM (again to a production JIRA instance), but that was also still broken.

However, I noticed that the "atlas-mvn package" JAR was significantly bigger, so I went to investigate:

  • in META-INF/MANIFEST.MF, the "atlas-mvn package" version of the .JAR is missing the line "Originally-Created-By: Apache Maven Bundle Plugin". Does this point to it perhaps having been created with a different build tool than when it was created with "atlas-cli pi"?
  • the "atlas-mvn package" version has a copy of plugin-license-storage-plugin.jar in the root of the .JAR, whereas the "atlas-cli pi" version does not (!)

For what it's worth, our product will support only JIRA 5.0.1+, so we don't even need to bundle the licensing JARs anyway. Is there documentation available on using Atlassian licensing without going through ThirdPartyPluginManager, or is there perhaps an easier way to handle it?

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 29, 2013

OK, yeah, that does seem to be the problem. If the jar doesn't have the plugin-license-storage-plugin jar embedded within it, then the aforementioned class is actually missing. For some reason, when you run atlas-cli > pi, the the following declaration in your plugin's pom.xml gets ignored:

&lt;plugin&gt;
  &lt;artifactId&gt;maven-dependency-plugin&lt;/artifactId&gt;
  &lt;executions&gt;
    &lt;execution&gt;
      &lt;id&gt;copy-storage-plugin&lt;/id&gt;
      &lt;phase&gt;process-resources&lt;/phase&gt;
      &lt;goals&gt;
        &lt;goal&gt;copy-dependencies&lt;/goal&gt;
      &lt;/goals&gt;
      &lt;configuration&gt;
        &lt;outputDirectory&gt;${project.build.outputDirectory}&lt;/outputDirectory&gt;
        &lt;includeArtifactIds&gt;plugin-license-storage-plugin&lt;/includeArtifactIds&gt;
        &lt;stripVersion&gt;true&lt;/stripVersion&gt;
      &lt;/configuration&gt;
    &lt;/execution&gt;
  &lt;/executions&gt;
&lt;/plugin&gt;

So this definitely seems to be a bug in the atlas-cli. However, I wonder if maybe we're heading down a red herring trail here. JIRA 5.2.9 should come pre-packaged with a version of the UPM that provides the necessary license storage APIs, right? So the special "UPM License Storage" plugin should never actually be required?

So that makes me wonder if your custom spring.xml is somehow getting your plugin confused into thinking that the UPM's built-in functionality isn't available, causing it to fall back to attempting to use the legacy storage library, which then is not available for use due to the CLI bug. This is total theory-crafting here, I don't have any evidence to prove that this is what's happening. Still digging further, but at least we're making progress.

As for "other" ways to do this, I'm not sure. I've never deviated from the instructions provided by the UPM team.

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 29, 2013

OK, I take back that theory. It looks like the ThirdPartyPluginLicenseStorageManager stuff relies on the license storage plugin classes being present, even if the UPM is third-party license aware.

Scott Dudley [Inactive]
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.
April 29, 2013

Joe,

Thanks for all of the research thus far! Perhaps I should have called it out more explicitly in my last comment, but I should mention that even the JAR built with "atlas-mvn package" is still broken for me in the same way described above (despite it having the appropriate JAR included). In other words, I'm not able to get the JAR to load at all, regardless of which way I build or install it. :-(

Joe Clark
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
April 30, 2013

Yeah, I just realised that after I made my comment. I had a chat with one of the marketplace/UPM devs who gave me a better understanding of the situation.

The problem seems to be that Spring's annotation-config processor's just can't handle the way in which the ThirdPartyPluginLicenseStorageManager is instantiated, and the 'lazy' way in which the plugin license storage plugin is installed.

So you have two ways to work around this, if you really need to use the spring annotations.

1) Distribute your plugin as an OBR, instead of a jar. This will ensure that all required dependencies are extracted and made available in the correct order. Some relevant details here: https://developer.atlassian.com/display/UPM/Alternate+Deployment+Model+for+Licensed+Plugins The downside to this approach is that you can't use atlas-cli or atlas-install-plugin when working with a plugin that's built as an OBR - the plugin SDK will continue to try to use the jar.

2) Since you know you will only ever run your plugin against versions of JIRA that have UPM 2.0 or higher, you can ignore the "ThirdPartyPluginLicenseManager" compatibility layer, and code against the UPM licensing API directly. There's some rough javadoc on this API here: https://developer.atlassian.com/static/javadoc/upm/licensing/2.0/reference/com/atlassian/upm/api/license/PluginLicenseManager.html

This is not yet the "officially sanctioned" way to write your licensing code, but it will become more prevalent as old versions of the UPM disappear from the wild. So you're just being an early adopter :-) To use this API, you need to remove all licensing-related elements from your pom.xml and instead depend on the UPM licensing library in your pom. Then you can switch all your references from "ThirdPartyPluginLicenseStorageManager" to "PluginLicenseManager".

Scott Dudley [Inactive]
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.
May 1, 2013

Step 4 may be optional--a feature of newer plugin SDKs is that they apparently unpack any dependent JARs and explode all of the class files within the top level of your plugin JAR, rather than leaving them as separately packaged JARs in META-INF/lib. We do some post-processing of our JAR and we had to turn this off to get it to work properly. Unless there's some good reason that we should be extracting the dependencies (was this done just for Marketplace licensing reasons?), we'd otherwise like to leave it turned off. Not only does it help with our post-processing, but it seems a good idea not to commingle our plugin classes with other potentially-conflicting GPL/BSD/etc licenses.

With all of that being said and done, everything now works perfectly. Thanks again!

Scott Dudley [Inactive]
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.
May 1, 2013

3) Added licensing code to the appropriate points in our app (which also needs to be declared as a separate component itself):

+import com.atlassian.upm.api.license.PluginLicenseManager;
+import com.atlassian.upm.api.license.entity.PluginLicense;
 
...
 
public class MyAppLicenseManager
{
+    private PluginLicenseManager licenseManager;
+
+    public MyAppLicenseManager(PluginLicenseManager licenseManager)
+    {
+        this.licenseManager = licenseManager;
+    }
+
 
...
 
+    public boolean testLicenseStatus()
+    {
+         if (!licenseManager.getLicense().isDefined())
+            {
+                return false;
+            }
+
+            PluginLicense pluginLicense = licenseManager.getLicense().get();
+
+            // We have a valid license and no error is listed, so we're all good.
+
+            if (! pluginLicense.getError().isDefined())
+            {
+                return true;
+            }
+
+            switch (pluginLicense.getError().get())
+            {
+                 // handle different license error conditions here
...

4) Made one further edit to pom.xml to turn off the "extractDependencies" option:

&lt;plugin&gt;
                &lt;groupId&gt;com.atlassian.maven.plugins&lt;/groupId&gt;
                &lt;artifactId&gt;maven-jira-plugin&lt;/artifactId&gt;
                &lt;version&gt;${amps.version}&lt;/version&gt;
                &lt;extensions&gt;true&lt;/extensions&gt;
                &lt;configuration&gt;
                    &lt;productVersion&gt;${jira.version}&lt;/productVersion&gt;
+                    &lt;extractDependencies&gt;false&lt;/extractDependencies&gt;
                 &lt;/configuration&gt;
             &lt;/plugin&gt;

..continued

Scott Dudley [Inactive]
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.
May 1, 2013

Joe,

Thanks for all of the assistance! We're trying to avoid having to ship OBRs, so we decided on option #2.

Just to clarify your instructions a little, for the sake of everyone else, here's what we ended up doing (starting from a completely non-Marketplace-enabled build):

1) Added to pom.xml:

+        &lt;dependency&gt;
+            &lt;groupId&gt;com.atlassian.upm&lt;/groupId&gt;
+            &lt;artifactId&gt;upm-api&lt;/artifactId&gt;
+            &lt;version&gt;${upm.license.compatibility.version}&lt;/version&gt;
+            &lt;scope&gt;provided&lt;/scope&gt;
+        &lt;/dependency&gt;
+        &lt;dependency&gt;
+            &lt;groupId&gt;com.atlassian.upm&lt;/groupId&gt;
+            &lt;artifactId&gt;licensing-api&lt;/artifactId&gt;
+            &lt;version&gt;${upm.license.compatibility.version}&lt;/version&gt;
+            &lt;scope&gt;provided&lt;/scope&gt;
+        &lt;/dependency&gt;
...
     &lt;properties&gt;
+        &lt;upm.license.compatibility.version&gt;2.4.1&lt;/upm.license.compatibility.version&gt;
     &lt;/properties&gt;

2) Added to atlassian-plugin.xml:

&lt;plugin-info&gt;
+        &lt;param name="atlassian-licensing-enabled"&gt;true&lt;/param&gt;
     &lt;/plugin-info&gt;
...
+    &lt;component-import key="pluginLicenseManager" interface="com.atlassian.upm.api.license.PluginLicenseManager"/&gt;
... continued in next comment

BenW
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
May 1, 2013

Yes, step 4 is optional. Extracting dependencies will cause your plugin build time to increase a bit but ultimately will decrease plugin startup time. It was added to the SDK primarily for the remotable plugins work that is ongoing, however, can also be utilized by P2 plugins.

The one thing I noticed with your changes is that you're compiling against version 2.4.1 of UPM's API. Since this API no longer installs the necessary components at plugin startup (like the previous API), your plugin expects the licensing API to already be present. As a result, your plugin will only work in product versions where UPM 2.4.1 or later is installed. This may or may not be true; please check this page [1] to confirm and possibly require a lower minimum version if needed.

Cheers,
Ben

[1] https://ecosystem.atlassian.net/wiki/display/UPM/UPM+Bundling+Matrix

Scott Dudley [Inactive]
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.
July 16, 2013

So...two months later, everything was working fine in our labs and the product is now in the field. We have recently gotten sporadic (2) reports of users unable being able to install the plugin. The problems all display the symptoms in the log shown below, related to the PluginLicenseManagerImpl apparently not exposing the correct signature for the "Option getLicense()" method.

The two users who reported this problem were using JIRA 5.0.6 or 5.1.7 and (respectively) and they were all on the bundled UPM 2.1.5.

In our product, we are building with a upm.license.compatibility.version of 2.0.5. Did anything inadvertently change in 2.1.5 that might cause this problem to occur? (I admit that I cannot reproduce this issue on any of our systems, even using UPM 2.1.5.)

For at least one of the users, asking him to upgrade UPM to 2.12.1 and then reinstall our plugin got things working.

Here is the log:

org.springframework.aop.AopInvocationException: AOP configuration
seems to be invalid: tried calling method [public abstract
com.atlassian.upm.api.util.Option
com.atlassian.upm.api.license.PluginLicenseManager.getLicense()]
on target
[com.atlassian.upm.license.internal.impl.PluginLicenseManagerImpl@94a1ceb];
nested exception is java.lang.IllegalArgumentException: object is
not an instance of declaring class
at
org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:315)
[...]
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy1986.getLicense(Unknown Source)
at
com.arsenalesystems.myproduct.licensing.AppLicenseManager.getLicenseError(SourceFile:49)
at
com.arsenalesystems.myproduct.actions.AppConfigAction.doDefault(SourceFile:31)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown
Source)
at java.lang.reflect.Method.invoke(Unknown Source)

BenW
Atlassian Team
Atlassian Team members are employees working across the company in a wide variety of roles.
July 16, 2013

Hi Scott,

I don't know of any inadvertant changes in UPM 2.1.5 that would break licensing as you describe. If you or your customers want to file a UPM ticket [1], we can take a look and try to reproduce the problems.

Cheers,
Ben

[1] https://ecosystem.atlassian.net/browse/UPM

0 votes
Florin Manaila
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.
April 22, 2013

You could add the PluginLicenseStorageManager to your spring.xml if there's no better way around it. Just look at what the build generates for that manager if you don't provide a spring.xml of your own and then add it. However, there should be a better way of handling the problem.

Scott Dudley [Inactive]
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.
April 22, 2013

I tried this. I had a look at the transformed/generated atlassian-plugins-components.xml and found only the following:

&lt;beans:bean id="thirdPartyPluginLicenseStorageManager" autowire="default" class="com.atlassian.upm.license.storage.lib.ThirdPartyPluginLicenseStorageManagerImpl"/&gt;
  &lt;beans:bean id="pluginLicenseStoragePluginInstaller" autowire="default" class="com.atlassian.upm.license.storage.lib.PluginLicenseStoragePluginInstaller"/&gt;
  &lt;beans:bean id="atlassianMarketplaceUriFactory" autowire="default" class="com.atlassian.upm.license.storage.lib.AtlassianMarketplaceUriFactoryImpl"/&gt;

I notice that the exception refers to "com.atlassian.upm.license.storage.plugin.PluginLicenseStorageManager", but that bean doesn't seem to exist anywhere in the generated Spring configuration files. (I looked at atlassian-plugins-host-components.xml, atlassian-plugins-component-imports.xml, and atlassian-plugins-components.xml.)

That notwithstanding, I tried the above three lines by removing the "beans" namespace and adding them back to my own Spring configuration (above). Unfortunately, this still produced the same error on startup.

Florin Manaila
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.
April 23, 2013

Now that Joseph mentioned it, I recall that we had the same problem only when trying to re-upload a plugin using the atlassian sdk (either atlas-cli or fastdev- which uses atlas-cli). On a fresh start using atlas-run/debug or install from "Manage Plugins" on a standalone instance it works well. So it might be sdk-related.

TAGS
AUG Leaders

Atlassian Community Events