Despite all the questions I posted on this forum and all the useful answers I got, I still do not fully understand how the dependencies are managed with Confluence 9.
TL;DR: If I depend on a service from another app (both are mine) that uses a type provided by a Confluence Plugin (BlueprintContext in my example), the class is loaded by two different class loaders and the service cannot be called. How can I resolve this issue?
This is my problem when I work with two apps (only an issue with Confluence 9).
- One is providing services, the other is consuming it.
- As long as all the classes in my API (parameter types, return types) are not part of another dependency (in my case com.atlassian.confluence.plugins:confluence-create-content-api) everything works fine.
- But if there is such a type found in my API, I either get complaints at deploy time or at runtime that
- there are two classes named BlueprintContext
- there is no class with the name BlueprintContext (in case I do set the Confluence Content API to scope ‘provided’)
So this is my service interface that is implemented by a service in the same app. There are two methods. The execute() method can be called without issues, the transform() method has the type issue.
package com.example.lib.api;
import com.atlassian.confluence.plugins.createcontent.api.contextproviders.BlueprintContext;
public interface BlueprintContextTransformer {
void execute();
BlueprintContext transform(BlueprintContext context);
}
According to https://developer.atlassian.com/server/framework/atlassian-sdk/configuration-of-instructions-in-atlassian-plugins/ and https://developer.atlassian.com/server/framework/atlassian-sdk/spring-java-config-plugin-xml/ I should - as I understand this - not need to add any dependencies in the Import Instructions. But I assume it does not hurt and the errors the same.
Both apps declare this dependency:
<dependency>
<groupId>com.atlassian.confluence.plugins</groupId>
<artifactId>confluence-create-content-plugin</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.confluence.plugins</groupId>
<artifactId>confluence-create-content-api</artifactId>
</dependency>
These are the instructions for the service provider, exporting the package containing the service interface and service implementation:
<instructions>
<Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
<Export-Package>
com.example.lib.api
</Export-Package>
<Import-Package>
org.springframework.osgi.*;resolution:="optional",
org.eclipse.gemini.blueprint.*;resolution:="optional",
com.atlassian.confluence.plugins.createcontent.api.*;resolution:="optional",
*
</Import-Package>
<Spring-Context>*</Spring-Context>
</instructions>
The instructions for the service consumer:
<instructions>
<Atlassian-Plugin-Key>${atlassian.plugin.key}</Atlassian-Plugin-Key>
<Import-Package>
org.springframework.osgi.*;resolution:="optional",
org.eclipse.gemini.blueprint.*;resolution:="optional",
com.atlassian.confluence.plugins.createcontent.api.*;resolution:="optional",
com.example.lib.api.*,
*
</Import-Package>
<Spring-Context>*</Spring-Context>
</instructions>
I also tried to import com.atlassian.confluence.plugins.createcontent.api.contextproviders.* and usually prefer not using resolution optional (which should simplify the imports to a single ‘*’), but the results are basically the same.
Here is the error message:
confluence-1 | Uncaught exception b1a48162-5e75-4aa5-af14-91714d65c9fd thrown by REST service: loader constraint violation:
when resolving interface method
'BlueprintContext BlueprintContextTransformer.transform(BlueprintContext)'
the class loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @43ceadd8 of the current class, com/example/plugins/tutorial/confluence/simplebp/MyContextProvider,
and the class loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @77e190f0 for the method's defining class, com/example/lib/api/BlueprintContextTransformer,
have different Class objects for the type com/atlassian/confluence/plugins/createcontent/api/contextproviders/BlueprintContext
used in the signature
(com.example.plugins.tutorial.confluence.simplebp.MyContextProvider is in unnamed module of loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @43ceadd8,
parent loader org.apache.catalina.loader.ParallelWebappClassLoader @452acb84;
com.example.lib.api.BlueprintContextTransformer is in unnamed module of loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @77e190f0,
parent loader org.apache.catalina.loader.ParallelWebappClassLoader @452acb84)
confluence-1 | -- url: /rest/create-dialog/1.0/content-blueprint/create-draft | userName: admin | referer: http://localhost:8080/display/CBS/q5 | traceId: 2834180fd06ac39b
confluence-1 | java.lang.LinkageError: loader constraint violation:
when resolving interface method
'BlueprintContext BlueprintContextTransformer.transform(BlueprintContext)'
the class loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @43ceadd8 of the current class, com/example/plugins/tutorial/confluence/simplebp/MyContextProvider,
and the class loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @77e190f0 for the method's defining class, com/example/lib/api/BlueprintContextTransformer,
have different Class objects for the type com/atlassian/confluence/plugins/createcontent/api/contextproviders/BlueprintContext
used in the signature
(com.example.plugins.tutorial.confluence.simplebp.MyContextProvider is in unnamed module of loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @43ceadd8,
parent loader org.apache.catalina.loader.ParallelWebappClassLoader @452acb84;
com.example.lib.api.BlueprintContextTransformer is in unnamed module of loader org.apache.felix.framework.BundleWiringImpl$BundleClassLoader @77e190f0, parent loader org.apache.catalina.loader.ParallelWebappClassLoader @452acb84)
confluence-1 | at com.example.plugins.tutorial.confluence.simplebp.MyContextProvider.updateBlueprintContext(MyContextProvider.java:29)
confluence-1 | at com.atlassian.confluence.plugins.createcontent.api.contextproviders.AbstractBlueprintContextProvider.getContextMap(AbstractBlueprintContextProvider.java:41)
confluence-1 | at com.atlassian.confluence.plugins.createcontent.actions.DefaultBlueprintContentGenerator.getContentTemplateContext(DefaultBlueprintContentGenerator.java:196)
confluence-1 | at com.atlassian.confluence.plugins.createcontent.actions.DefaultBlueprintContentGenerator.generateBlueprintPageObject(DefaultBlueprintContentGenerator.java:115)
confluence-1 | at com.atlassian.confluence.plugins.createcontent.actions.DefaultBlueprintContentGenerator.generateBlueprintPageObject(DefaultBlueprintContentGenerator.java:93)
confluence-1 | at com.atlassian.confluence.plugins.createcontent.impl.DefaultContentBlueprintService.createContentDraft(DefaultContentBlueprintService.java:266)
confluence-1 | at com.atlassian.confluence.plugins.createcontent.rest.ContentBlueprintResource.createDraft(ContentBlueprintResource.java:95)
confluence-1 | at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Where
- BlueprintContext = com.atlassian.confluence.plugins.createcontent.api.contextproviders.BlueprintContext
- BlueprintContextTransformer = com.example.lib.api.BlueprintContextTransformer
MyContextProvider is the class in the service consuming app that uses the service BlueprintContextTransformer provided by the service providing app. Both reference the type BlueprintContext from the Confluence API dependency.
Everything works in Confluence < 9.
I do the injection with Spring Java Config. And since I can call a service (void execute()
in the example above) without types from another dependency successfully, I’d like to think that the issue is not in the Java classes.
I am using a modified version of the adjusted project Bitbucket which is based on the example provided by Atlassian (adjusted to run on Confluence 9 with Spring Java Config).
So this (finally) is my question:
Both of my apps load a class named BlueprintContext and when the consuming app tries to provide an instance of this class as an argument to the call of a service of the second app, the object of type BlueprintContext loaded by the calling app is not assignable to the parameter type BlueprintContext of the second service providing app.
Is my analysis correct?
- yes: how can I configure both apps so that they can actually work together? Is it with the Import, the Export, the scope of the dependencies, …? Are there special rules for the service providing app that I am missing?
- no: What is it then? Where can I look? What am I missing?
Edit: In case the original sources would be helpful to understand my problem: