diff --git a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java index 11a7022dac..2f8589908a 100644 --- a/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/plugin/PluginProcessor.java @@ -84,6 +84,8 @@ public final class PluginProcessor /** Field description */ private static final String DIRECTORY_INSTALLED = ".installed"; + public static final String JAKARTA_COMPATIBLE = ".jakarta-compatible"; + /** Field description */ private static final String DIRECTORY_METAINF = "META-INF"; @@ -175,9 +177,10 @@ public final class PluginProcessor logger.debug("found {} installed plugins", installedPlugins.size()); for (ExplodedSmp installedPlugin : installedPlugins) { - if (installedPlugin.getPlugin().getScmVersion() < 3) { + if (shouldTransform(installedPlugin)) { logger.debug("start jakarta transformation of already installed plugin: {}", installedPlugin.getPlugin().getInformation().getName()); PluginTransformer.transform(installedPlugin.getPath()); + Files.createFile(installedPlugin.getPath().resolve(JAKARTA_COMPATIBLE)); } } @@ -185,9 +188,10 @@ public final class PluginProcessor logger.debug("finished installation of {} plugins", newlyInstalledPlugins.size()); for (ExplodedSmp newInstalledSmp : newlyInstalledPlugins) { - if (newInstalledSmp.getPlugin().getScmVersion() < 3) { + if (shouldTransform(newInstalledSmp)) { logger.debug("start jakarta transformation of newly installed smp: {}", newInstalledSmp.getPlugin().getInformation().getName()); PluginTransformer.transform(newInstalledSmp.getPath()); + Files.createFile(newInstalledSmp.getPath().resolve(JAKARTA_COMPATIBLE)); } } @@ -209,6 +213,11 @@ public final class PluginProcessor return ImmutableSet.copyOf(wrappers); } + private boolean shouldTransform(ExplodedSmp newInstalledSmp) { + return newInstalledSmp.getPlugin().getScmVersion() < 3 + && !newInstalledSmp.getPath().resolve(JAKARTA_COMPATIBLE).toFile().exists(); + } + private Set concat(Set installedPlugins, Set newlyInstalledPlugins) { // We first add all newly installed plugins, // after that we add the missing plugins, which are already installed. diff --git a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java index bc474897fd..873c57dc10 100644 --- a/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/plugin/PluginProcessorTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; import org.mockito.Mock; +import org.mockito.MockedStatic; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import sonia.scm.lifecycle.classloading.ClassLoaderLifeCycle; @@ -52,6 +53,9 @@ import java.util.zip.ZipOutputStream; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; /** * @@ -89,6 +93,10 @@ class PluginProcessorTest { new PluginResource("sonia/scm/plugin/scm-f-plugin-1.0.1.smp", "scm-f-plugin.smp", "scm-f-plugin:1.0.1"); + private static final PluginResource PLUGIN_THIRD_MAJOR = + new PluginResource("sonia/scm/plugin/scm-thirdmajor-plugin.smp", + "scm-thirdmajor-plugin.smp", "scm-thirdmajor-plugin:1.0.0"); + private static final String PLUGIN_G = "scm-g-plugin"; private static final String PLUGIN_H = "scm-h-plugin"; private static final String PLUGIN_I = "scm-i-plugin"; @@ -185,7 +193,7 @@ class PluginProcessorTest { copySmp(PLUGIN_A); collectAndGetFirst(); - Mockito.verify(pluginArchiveCleaner).cleanup(pluginDirectory.toPath().resolve(".installed")); + verify(pluginArchiveCleaner).cleanup(pluginDirectory.toPath().resolve(".installed")); } @Test @@ -258,6 +266,37 @@ class PluginProcessorTest { assertThat(result).isEqualTo("hello again"); } + @Test + void shouldTransformSecondMajorPlugin() throws Exception { + copySmps(PLUGIN_A); + + Set plugins = collectPlugins(); + + assertThat(plugins.iterator().next().getDirectory().resolve(".jakarta-compatible")).exists(); + } + + @Test + void shouldNotTransformThirdMajorPlugin() throws Exception { + copySmps(PLUGIN_THIRD_MAJOR); + + Set plugins = collectPlugins(); + + assertThat(plugins.iterator().next().getDirectory().resolve(".jakarta-compatible")).doesNotExist(); + } + + @Test + void shouldNotTransformAlreadyTransformedPlugins() throws Exception { + try (MockedStatic staticMock = Mockito.mockStatic(PluginTransformer.class)) { + copySmps(PLUGIN_A); + + collectPlugins(); + collectPlugins(); + collectPlugins(); + + staticMock.verify(() -> PluginTransformer.transform(any()), times(1)); + } + } + @Test @SuppressWarnings("UnstableApiUsage") void shouldCreatePluginWebResourceLoader() throws IOException { diff --git a/scm-webapp/src/test/resources/sonia/scm/plugin/scm-thirdmajor-plugin.smp b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-thirdmajor-plugin.smp new file mode 100644 index 0000000000..a294774371 Binary files /dev/null and b/scm-webapp/src/test/resources/sonia/scm/plugin/scm-thirdmajor-plugin.smp differ