Multi-release dependency JAR files with version-specific classes and extracted dependencies cause apps REST resources to fail loading

Hi there,

I’m having trouble getting some updated dependencies to run. The dependencies are in the Multi-release JAR format and contain several directories under META-INF/versions. These contain major-version-specific class files, which cause the REST module scanner to barf once a REST resource is called and supposed to be lazily loaded:

Somewhat large stack trace of rest scanner failing

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.scanner.AnnotatedClassScanner.getClassReader(AnnotatedClassScanner.java:163)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.scanner.AnnotatedClassScanner.analyzeClassFile(AnnotatedClassScanner.java:153)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.scanner.AnnotatedClassScanner.indexJar(AnnotatedClassScanner.java:126)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.scanner.AnnotatedClassScanner.scan(AnnotatedClassScanner.java:58)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.OsgiResourceConfig.scanForAnnotatedClasses(OsgiResourceConfig.java:94)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.OsgiResourceConfig.getClasses(OsgiResourceConfig.java:86)

12/4/2021 12:09:43 AM at com.sun.jersey.spi.container.servlet.WebComponent.configure(WebComponent.java:566)

12/4/2021 12:09:43 AM at com.sun.jersey.spi.container.servlet.ServletContainer$InternalWebComponent.configure(ServletContainer.java:332)

12/4/2021 12:09:43 AM at com.sun.jersey.spi.container.servlet.WebComponent.load(WebComponent.java:604)

12/4/2021 12:09:43 AM at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:207)

12/4/2021 12:09:43 AM at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:394)

12/4/2021 12:09:43 AM at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:744)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.RestDelegatingServletFilter.initServletContainer(RestDelegatingServletFilter.java:92)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.RestDelegatingServletFilter.init(RestDelegatingServletFilter.java:65)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.filter.DelegatingPluginFilter.init(DelegatingPluginFilter.java:38)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.DefaultServletModuleManager$LazyLoadedFilterReference.create(DefaultServletModuleManager.java:500)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.DefaultServletModuleManager$LazyLoadedFilterReference.create(DefaultServletModuleManager.java:487)

12/4/2021 12:09:43 AM at io.atlassian.util.concurrent.LazyReference$Sync.run(LazyReference.java:332)

12/4/2021 12:09:43 AM at io.atlassian.util.concurrent.LazyReference.getInterruptibly(LazyReference.java:150)

12/4/2021 12:09:43 AM at io.atlassian.util.concurrent.LazyReference.get(LazyReference.java:116)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.DefaultServletModuleManager.getInstance(DefaultServletModuleManager.java:431)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.DefaultServletModuleManager.getFilter(DefaultServletModuleManager.java:424)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.DefaultServletModuleManager.getFilters(DefaultServletModuleManager.java:289)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.servlet.DefaultRestServletModuleManager.getFilters(DefaultRestServletModuleManager.java:125)

12/4/2021 12:09:43 AM at com.atlassian.plugin.servlet.filter.ServletFilterModuleContainerFilter.doFilter(ServletFilterModuleContainerFilter.java:53)
SNIP

12/4/2021 12:09:43 AM at java.lang.Thread.run(Thread.java:748)

12/4/2021 12:09:43 AM Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 59

SNIP

12/4/2021 12:09:43 AM at org.objectweb.asm.ClassReader.<init>(ClassReader.java:283)

12/4/2021 12:09:43 AM at com.atlassian.plugins.rest.module.scanner.AnnotatedClassScanner.getClassReader(AnnotatedClassScanner.java:161)

12/4/2021 12:09:43 AM ... 321 more

12/4/2021 12:09:43 AM 03-Dec-2021 23:09:43.652 SEVERE [http-nio-8080-exec-9] com.sun.jersey.server.impl.application.RootResourceUriRules.<init> The ResourceConfig instance does not contain any root resource classes.

In this example, I used this dependency which contains the version-specific releases:

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcpkix-jdk15on</artifactId>
  <version>1.70</version>
  <scope>compile</scope>
</dependency>

Importantly, this all works as long as the amps-maven-plugin (We’re using 8.3.1 btw) option extractDependencies is set to false. We have to set it to true though, because we bundle our frontend in a separate Maven module and therefore need to unpack it.

As a result though, the files in META-INF/versions/... from that dependency land directly in our JAR and are picked up by that scanner, which cannot read them.

I’ve also tried to use the maven-shade-plugin, but since the obr build stage in AMPS happens before the shade plugin can do its magic to the JAR file, that approach didn’t work either. I think it should be possible to reorder these steps using some maven magic, but I’m honestly not good enough at maven to come up with a solution for this (maybe define the AMPS plugin twice, once before and once after the shade plugin with the appropriate steps disabled?)

So I’m looking for a solution that would allow me to fix this. Either…

  • An AMPS update that would filter out this META-INF/versions directory similar to how module-info.class files were filtered out. I have never touched Maven plugin development, so I’m hesitant to open a PR without knowing if such a filter would even make sense
  • A Maven config/hack that lets me modify the JAR using the shade plugin before the OBR is generated (or a way to delete this directory from target/classes before the JAR is even created)
  • A way to stop the REST scanner from dying from those too new class files (like maybe an instruction that would let me exclude this directory from being scanned)
  • A way to extract the frontend resources from that one dependency that contains them without having to set extractDependencies=true to extract all dependencies

Thanks in advance for your time :slight_smile:
Tobi

2 Likes

Have you tried setting the <package> element within the <rest> element? That should cause only that package to be scanned. For example:

<rest ... >
    <package>com.example.rest</package>
</rest>
1 Like

@aswan YES! That works, thank you!

Another solution that @dennis.fischer proposed was to set extractDependencies to false and use the maven-dependency-plugin to extract my frontend dependency.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <id>unpack</id>
            <phase>generate-resources</phase>
            <goals>
                <goal>unpack</goal>
            </goals>
            <configuration>
                <artifactItems>
                    <artifactItem>
                        <groupId>${project.groupId}</groupId>
                        <artifactId>our-frontend</artifactId>
                        <version>${project.version}</version>
                        <outputDirectory>${project.build.directory}/classes</outputDirectory>
                    </artifactItem>
                </artifactItems>
            </configuration>
        </execution>
    </executions>
</plugin>

Both of these work and I think combining them would yield the best performance both at compile time and at runtime.

Thanks all! Much appreciated :slight_smile: