I’m implementing the App Migration Platform for my Confluence Server plugin to migrate app data to a Forge cloud app via CMA. My plugin uses a hand-written plugin-context.xml for Spring bean wiring (not pure Spring Scanner annotations). I cannot get CMA to discover my DiscoverableForgeListener implementation.
Setup
Confluence Server 9.5.4
atlassian-app-cloud-migration-listener 1.8.7
atlassian-spring-scanner-annotation 5.1.0
CMA (migration-agent) 3.13.8
Dev mode enabled
My migration listener class:
@ConfluenceComponent
@ExportAsService({DiscoverableForgeListener.class, ConfluenceAppCloudMigrationListenerV1.class})
public class DigitalSignatureMigrationListener
implements DiscoverableForgeListener, ConfluenceAppCloudMigrationListenerV1 {
public DigitalSignatureMigrationListener(@ComponentImport BandanaManager bandanaManager) {
this.bandanaManager = bandanaManager;
}
// ... getForgeAppId(), onStartAppMigration(), etc.
}
Problem
My plugin has two other beans (DigitalSignatureMacro and DigitalSignatureService) that are defined in META-INF/spring/plugin-context.xml using manual Spring XML wiring. Because this file exists, Spring Scanner does not process the @ExportAsService annotation on my migration listener — so the bean is never registered as an OSGi service, and CMA shows appStatus: NO_APPS.
What I’ve tried
Approach
Result
@ExportAsService + @ConfluenceComponent
Annotations ignored because plugin-context.xml exists
Delete plugin-context.xml, use annotations everywhere
Plugin starts but macro/REST beans break (HK2 conflict)
<osgi:service> in plugin-context.xml
Plugin won’t start — CCMA doesn’t export com.atlassian.migration.app.listener or .confluence sub-packages, only the flat com.atlassian.migration.app
<component> in atlassian-plugin.xml
Build fails: “not allowed when Atlassian-Plugin-Key is set”
<scanner:scan-packages> in a second Spring XML file
Plugin won’t start — duplicate bean conflicts
Programmatic BundleContext.registerService() from constructor
Plugin starts, service registered, but CMA can’t find it (classloader mismatch — our bundled interfaces vs CCMA’s interfaces)
Bean-only in plugin-context.xml (no service export)
Plugin works perfectly, but CMA shows NO_APPS
Key observation
CCMA (bundle 272) exports com.atlassian.migration.app version 1.0.0, but does NOT export:
This means the CMA listener dependency must be bundled (compile scope), which creates a split-package situation — our bundle’s classloader has different class instances than CCMA’s classloader, so OSGi service lookups never match.
Questions
How do other Marketplace vendors register their DiscoverableForgeListener? Is there something I’m missing about the service discovery mechanism?
Should the CMA listener interfaces be imported from CCMA or bundled? The documentation says “the Cloud Migration Assistants export the library’s classes in the runtime” — but in practice, the sub-packages are not exported.
Is there a way to use plugin-context.xml alongside Spring Scanner’s @ExportAsService? My plugin’s macro and REST beans require manual XML wiring, but the migration listener needs annotation-based service export.
Environment details
pom.xml CMA dependency: <scope>provided</scope> (also tried compile with !com.atlassian.migration.app.* in Import-Package)
Alternatively you can also add the @ComponentImport annotations directly in your bean constructors.
We annotate the listener like this:
@ExportAsService(DiscoverableListener.class)
@Component
public class CloudMigrationListener implements DiscoverableListener {
...
}
Other non-public components are only annotated with @Component.
Make sure to read atlassian-spring-scanner README and don’t forget to add <Atlassian-Plugin-Key>${addOn.key}</Atlassian-Plugin-Key> in the confluence-maven-plugin in pom.xml.
Btw. your macro class probably shouldn’t be added as a spring bean. Confluence automatically injects dependencies into macros when it creates instances.
Hi @jens ,
thanks for your detailed answer. I tried the scan-indexes approach but the plugin fails to enable (hangs during startup with no errors in logs).
The dependency version is taken from our Confluence compile target, e.g. 3.0.3 for Confluence 8.5.3, but typically that version number doesn’t really matter.
I’d recommend to check the text files generated by the scanner after building. They are located in target/classes/META-INF/plugin-components. The contents should reflect your @Component, @ComponentImport and @ExportAsService annotations.
For example our exports file has a line for the DiscoverableListener: