diff --git a/src/main/java/org/openrewrite/java/migrate/jakarta/AddJakartaXmlBindRuntime.java b/src/main/java/org/openrewrite/java/migrate/jakarta/AddJakartaXmlBindRuntime.java new file mode 100644 index 0000000000..2a636355c6 --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/jakarta/AddJakartaXmlBindRuntime.java @@ -0,0 +1,164 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.jakarta; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.ExecutionContext; +import org.openrewrite.ScanningRecipe; +import org.openrewrite.Tree; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.marker.JavaSourceSet; +import org.openrewrite.java.tree.J; +import org.openrewrite.maven.AddDependencyVisitor; +import org.openrewrite.maven.MavenIsoVisitor; +import org.openrewrite.maven.tree.MavenResolutionResult; +import org.openrewrite.maven.tree.ResolvedDependency; +import org.openrewrite.maven.tree.Scope; +import org.openrewrite.xml.tree.Xml; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Value +@EqualsAndHashCode(callSuper = false) +public class AddJakartaXmlBindRuntime extends ScanningRecipe { + String displayName = "Add a Jakarta JAXB runtime for Maven applications"; + String description = "Add a Jakarta JAXB 3 runtime when a Maven module calls JAXBContext.newInstance and has no " + + "explicit JAXB provider or provided Jakarta platform. Java 8's built-in javax provider cannot serve Jakarta calls. " + + "Annotation-only modules are left alone and test-only calls receive a test dependency."; + + public static class Accumulator { + final Set projects = new HashSet<>(); + final Map calls = new HashMap<>(); + + boolean belongsTo(Path source, Path project) { + Path nearest = null; + for (Path candidate : projects) { + if ((candidate.toString().isEmpty() || source.startsWith(candidate)) && + (nearest == null || candidate.toString().length() > nearest.toString().length())) { + nearest = candidate; + } + } + return project.equals(nearest); + } + } + + @Override + public Accumulator getInitialValue(ExecutionContext ctx) { + return new Accumulator(); + } + + private static Path directory(Path source) { + return source.getParent() == null ? Paths.get("") : source.getParent(); + } + + @Override + public TreeVisitor getScanner(Accumulator acc) { + return new TreeVisitor() { + @Override + public Tree preVisit(Tree tree, ExecutionContext ctx) { + stopAfterPreVisit(); + if (tree instanceof Xml.Document && tree.getMarkers().findFirst(MavenResolutionResult.class).isPresent()) { + acc.projects.add(directory(((Xml.Document) tree).getSourcePath())); + } else if (tree instanceof J.CompilationUnit) { + J.CompilationUnit cu = (J.CompilationUnit) tree; + boolean imported = cu.getImports().stream().anyMatch(i -> { + String name = i.getQualid().printTrimmed(); + return "javax.xml.bind.JAXBContext".equals(name) || "jakarta.xml.bind.JAXBContext".equals(name) || + "javax.xml.bind.*".equals(name) || "jakarta.xml.bind.*".equals(name); + }); + new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) { + if ("newInstance".equals(method.getSimpleName())) { + String owner = method.getMethodType() == null ? "" : + method.getMethodType().getDeclaringType().getFullyQualifiedName(); + String select = method.getSelect() == null ? "" : method.getSelect().printTrimmed(); + if ("javax.xml.bind.JAXBContext".equals(owner) || "jakarta.xml.bind.JAXBContext".equals(owner) || + "javax.xml.bind.JAXBContext".equals(select) || "jakarta.xml.bind.JAXBContext".equals(select) || + imported && "JAXBContext".equals(select)) { + boolean test = cu.getMarkers().findFirst(JavaSourceSet.class) + .map(s -> "test".equals(s.getName())) + .orElse(cu.getSourcePath().toString().replace('\\', '/').contains("src/test/")); + acc.calls.merge(cu.getSourcePath(), test, (a, b) -> a && b); + } + } + return super.visitMethodInvocation(method, ctx); + } + }.visit(cu, ctx); + } + return tree; + } + }; + } + + @Override + public TreeVisitor getVisitor(Accumulator acc) { + return new MavenIsoVisitor() { + private boolean isProvidedPlatform(MavenResolutionResult model, ResolvedDependency dependency) { + if (!dependency.isDirect() || + !("jakarta.platform".equals(dependency.getGroupId()) && + ("jakarta.jakartaee-api".equals(dependency.getArtifactId()) || "jakarta.jakartaee-web-api".equals(dependency.getArtifactId())) || + "javax".equals(dependency.getGroupId()) && + ("javaee-api".equals(dependency.getArtifactId()) || "javaee-web-api".equals(dependency.getArtifactId())))) { + return false; + } + String declaredScope = model.getPom().getValue(dependency.getRequested().getScope()); + Scope scope = declaredScope == null ? model.getPom().getManagedScope(dependency.getGroupId(), + dependency.getArtifactId(), dependency.getType(), dependency.getClassifier()) : Scope.fromName(declaredScope); + return scope == Scope.Provided; + } + + @Override + public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { + Path project = directory(document.getSourcePath()); + boolean used = false; + boolean testOnly = true; + for (Map.Entry call : acc.calls.entrySet()) { + if (acc.belongsTo(call.getKey(), project)) { + used = true; + testOnly &= call.getValue(); + } + } + if (!used) { + return document; + } + MavenResolutionResult model = getResolutionResult(); + final boolean tests = testOnly; + boolean hasProvider = model.getDependencies().entrySet().stream() + .filter(e -> tests || e.getKey() != Scope.Test) + .flatMap(e -> e.getValue().stream()) + .anyMatch(d -> + "org.glassfish.jaxb".equals(d.getGroupId()) && "jaxb-runtime".equals(d.getArtifactId()) || + "com.sun.xml.bind".equals(d.getGroupId()) && "jaxb-impl".equals(d.getArtifactId()) || + "org.eclipse.persistence".equals(d.getGroupId()) && + ("org.eclipse.persistence.moxy".equals(d.getArtifactId()) || "eclipselink".equals(d.getArtifactId())) || + isProvidedPlatform(model, d)); + if (hasProvider || "war".equals(model.getPom().getPackaging()) || "ear".equals(model.getPom().getPackaging())) { + return document; + } + return (Xml.Document) new AddDependencyVisitor("org.glassfish.jaxb", "jaxb-runtime", "3.0.x", null, + testOnly ? "test" : "runtime", null, null, null, null, null).visitNonNull(document, ctx); + } + }; + } +} diff --git a/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml b/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml index ec8a2215b8..a11596c291 100644 --- a/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml +++ b/src/main/resources/META-INF/rewrite/jakarta-ee-9.yml @@ -1006,6 +1006,7 @@ recipeList: groupId: jakarta.xml.bind artifactId: jakarta.xml.bind-api version: 3.0.x + - org.openrewrite.java.migrate.jakarta.AddJakartaXmlBindRuntime - org.openrewrite.java.ChangePackage: oldPackageName: javax.xml.bind newPackageName: jakarta.xml.bind diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 0850409fcd..e0b12b651a 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -210,6 +210,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.i maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.io.ReplaceFileInOrOutputStreamFinalizeWithClose,Replace invocations of `finalize()` on `FileInputStream` and `FileOutputStream` with `close()`,Replace invocations of the deprecated `finalize()` method on `FileInputStream` and `FileOutputStream` with `close()`.,1,,`java.io` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.io.ReplaceSystemOutWithIOPrint,Migrate `System.out.print` to Java 25 IO utility class,"Replace `System.out.print()`, `System.out.println()` with `IO.print()` and `IO.println()`. Migrates to the new IO utility class introduced in Java 25.",1,,`java.io` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jacoco.UpgradeJaCoCo,Upgrade JaCoCo,"This recipe will upgrade JaCoCo to the latest patch version, which traditionally advertises full backwards compatibility for older Java versions.",3,,JaCoCo,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.AddJakartaXmlBindRuntime,Add a Jakarta JAXB runtime for Maven applications,Add a Jakarta JAXB 3 runtime when a Maven module calls JAXBContext.newInstance and has no explicit JAXB provider or provided Jakarta platform. Java 8's built-in javax provider cannot serve Jakarta calls. Annotation-only modules are left alone and test-only calls receive a test dependency.,1,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.ApplicationPathWildcardNoLongerAccepted,Remove trailing slash from `jakarta.ws.rs.ApplicationPath` values,Remove trailing `/*` from `jakarta.ws.rs.ApplicationPath` values.,1,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.DeprecatedCDIAPIsRemoved40,Remove deprecated API's not supported in CDI4.0,Deprecated APIs have been removed in CDI 4.0. This recipe removes and updates the corresponding deprecated methods.,4,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.EhcacheJavaxToJakarta,Migrate Ehcache from javax to jakarta namespace,Java EE has been rebranded to Jakarta EE. This recipe replaces existing Ehcache dependencies with their counterparts that are compatible with Jakarta EE 9.,5,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -221,8 +222,8 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.FileuploadToFileUpload2,Migrate deprecated `org.apache.commons.fileload` packages to `org.apache.commons.fileload.core`,Migrate deprecated `org.apache.commons.fileload` packages to `org.apache.commons.fileload.core`.,6,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.HasNoJakartaAnnotations,Project has no Jakarta annotations,Mark all source as found per `JavaProject` where no Jakarta annotations are found. This is useful mostly as a precondition for recipes that require Jakarta annotations to be present.,1,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JacksonJavaxToJakarta,Migrate Jackson from javax to jakarta namespace,Java EE has been rebranded to Jakarta EE. This recipe replaces existing Jackson dependencies with their counterparts that are compatible with Jakarta EE 9.,23,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE10,Migrate to Jakarta EE 10,"These recipes help with the Migration to Jakarta EE 10, flagging and updating deprecated methods.",605,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE11,Migrate to Jakarta EE 11,"These recipes help with the Migration to Jakarta EE 11, flagging and updating deprecated methods.",623,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE10,Migrate to Jakarta EE 10,"These recipes help with the Migration to Jakarta EE 10, flagging and updating deprecated methods.",606,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaEE11,Migrate to Jakarta EE 11,"These recipes help with the Migration to Jakarta EE 11, flagging and updating deprecated methods.",624,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaFacesConfigXml4,Migrate xmlns entries in `faces-config.xml` files,Jakarta EE 10 uses Faces version 4.,3,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaFacesEcmaScript,Migrate JSF values inside EcmaScript files,"Convert JSF to Faces values inside JavaScript,TypeScript, and Properties files.",4,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JakartaFacesTagLibraryXml4,Migrate xmlns entries in `taglib.xml` files,Faces 4 uses facelet-taglib 4.0.,3,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -252,7 +253,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxJspToJakartaJsp,Migrate deprecated `javax.jsp` packages to `jakarta.jsp`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",6,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxJwsToJakartaJws,Migrate deprecated `javax.jws` packages to `jakarta.jws`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",6,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxMailToJakartaMail,Migrate deprecated `javax.mail` packages to `jakarta.mail`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",8,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta,Migrate to Jakarta EE 9,Jakarta EE 9 is the first version of Jakarta EE that uses the new `jakarta` namespace.,379,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxMigrationToJakarta,Migrate to Jakarta EE 9,Jakarta EE 9 is the first version of Jakarta EE that uses the new `jakarta` namespace.,380,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxOrmXmlToJakartaOrmXml,Migrate xmlns entries in `orm.xml` files,"Java EE has been rebranded to Jakarta EE, necessitating an XML namespace relocation.",4,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxPersistenceToJakartaPersistence,Migrate deprecated `javax.persistence` packages to `jakarta.persistence`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",6,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxPersistenceXmlToJakartaPersistenceXml,Migrate xmlns entries in `persistence.xml` files,"Java EE has been rebranded to Jakarta EE, necessitating an XML namespace relocation.",6,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -266,7 +267,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxWebXmlToJakartaWebXml,Migrate xmlns entries in `web.xml` files,"Java EE has been rebranded to Jakarta EE, necessitating an XML namespace relocation.",6,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxWebsocketToJakartaWebsocket,Migrate deprecated `javax.websocket` packages to `jakarta.websocket`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",9,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxWsToJakartaWs,Migrate deprecated `javax.ws` packages to `jakarta.ws`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",5,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxXmlBindMigrationToJakartaXmlBind,Migrate deprecated `javax.xml.bind` packages to `jakarta.xml.bind`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",21,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxXmlBindMigrationToJakartaXmlBind,Migrate deprecated `javax.xml.bind` packages to `jakarta.xml.bind`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",22,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxXmlSoapToJakartaXmlSoap,Migrate deprecated `javax.soap` packages to `jakarta.soap`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",5,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxXmlToJakartaXmlXJCBinding,Migrate XJC Bindings to Jakata XML,"Java EE has been rebranded to Jakarta EE, migrates the namespace and version in XJC bindings.",3,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.JavaxXmlWsMigrationToJakartaXmlWs,Migrate deprecated `javax.xml.ws` packages to `jakarta.xml.ws`,"Java EE has been rebranded to Jakarta EE, necessitating a package relocation.",8,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, diff --git a/src/test/java/org/openrewrite/java/migrate/jakarta/JakartaXmlBindRuntimeTest.java b/src/test/java/org/openrewrite/java/migrate/jakarta/JakartaXmlBindRuntimeTest.java new file mode 100644 index 0000000000..a85dc711e2 --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/jakarta/JakartaXmlBindRuntimeTest.java @@ -0,0 +1,290 @@ +/* + * Copyright 2025 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.jakarta; + +import org.junit.jupiter.api.Test; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.openrewrite.java.Assertions.*; +import static org.openrewrite.maven.Assertions.pomXml; + +class JakartaXmlBindRuntimeTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipeFromResources("org.openrewrite.java.migrate.jakarta.JavaxXmlBindMigrationToJakartaXmlBind") + .parser(JavaParser.fromJavaVersion().dependsOn( + "package javax.xml.bind; public abstract class JAXBContext { public static JAXBContext newInstance(Class... types) { return null; } }")); + } + + @Test + void addsRuntimeForContextFactory() { + rewriteRun( + mavenProject("app", + pomXml( + """ + + 4.0.0 + com.example + app + 1.0 + + + javax.xml.bind + jaxb-api + 2.3.1 + + + + """, + spec -> spec.after(pom -> { + assertThat(pom).contains("jaxb-runtime", "runtime"); + return pom; + }) + ), + srcMainJava(java( + """ + import javax.xml.bind.JAXBContext; + class A { + JAXBContext context = JAXBContext.newInstance(A.class); + } + """, + """ + import jakarta.xml.bind.JAXBContext; + class A { + JAXBContext context = JAXBContext.newInstance(A.class); + } + """ + )) + ) + ); + } + + private static final String POM = """ + + 4.0.0 + com.example + app + 1.0 + + """; + + private static final String FACTORY = """ + import javax.xml.bind.JAXBContext; + class A { JAXBContext context = JAXBContext.newInstance(A.class); } + """; + + @Test + void testOnlyFactoryGetsTestScope() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM, spec -> spec.after(pom -> { + assertThat(pom).contains("jaxb-runtime", "test"); + return pom; + })), + srcTestJava(java(FACTORY)) + ) + ); + } + + @Test + void doesNotAddRuntimeForApiReferencesWithoutFactoryCalls() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM), + srcMainJava(java("import javax.xml.bind.JAXBContext; class A { JAXBContext context; }")) + ) + ); + } + + @Test + void leavesOtherModulesAlone() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM, spec -> spec.after(pom -> { + assertThat(pom).contains("jaxb-runtime", "runtime"); + return pom; + })), + srcMainJava(java(FACTORY)), + mavenProject("nested", pomXml(POM)) + ), + mavenProject("unrelated", pomXml(POM)) + ); + } + + @Test + void respectsExplicitMoxyProvider() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM.replace("", """ + + + org.eclipse.persistence + org.eclipse.persistence.moxy + 3.0.4 + + + + """)), + srcMainJava(java(FACTORY)) + ) + ); + } + + @Test + void respectsProvidedJakartaPlatform() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM.replace("", """ + + + jakarta.platform + jakarta.jakartaee-api + 9.1.0 + provided + + + + """)), + srcMainJava(java(FACTORY)) + ) + ); + } + + @Test + void recognizesUnattributedFactoryCalls() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()).typeValidationOptions(TypeValidation.none()), + mavenProject("app", + pomXml(POM, spec -> spec.after(pom -> { + assertThat(pom).contains("jaxb-runtime", "runtime"); + return pom; + })), + srcMainJava(java(FACTORY, spec -> spec.beforeRecipe(cu -> new JavaIsoVisitor() { + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer unused) { + return super.visitMethodInvocation(method, unused).withMethodType(null); + } + }.visitNonNull(cu, 0)))) + ) + ); + } + + @Test + void productionUsageWinsOverTestUsage() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM, spec -> spec.after(pom -> { + assertThat(pom).contains("jaxb-runtime", "runtime") + .doesNotContain("test"); + return pom; + })), + srcMainJava(java(FACTORY)), + srcTestJava(java(FACTORY.replace("class A", "class B").replace("A.class", "B.class"))) + ) + ); + } + + @Test + void dependencyFreeApplicationGetsCompileApiAndRuntime() { + rewriteRun( + mavenProject("app", + pomXml(POM, spec -> spec.after(pom -> { + assertThat(pom).contains("jakarta.xml.bind-api", + "jaxb-runtime", "runtime"); + return pom; + })), + srcMainJava(java(FACTORY, FACTORY.replace("javax.xml.bind", "jakarta.xml.bind"))) + ) + ); + } + + @Test + void unattributedFactoryGetsCompileApiBeforeRuntime() { + rewriteRun( + spec -> spec.typeValidationOptions(TypeValidation.none()), + mavenProject("app", + pomXml(POM, spec -> spec.after(pom -> { + assertThat(pom).contains("jakarta.xml.bind-api", + "jaxb-runtime"); + return pom; + })), + srcMainJava(java(FACTORY, FACTORY.replace("javax.xml.bind", "jakarta.xml.bind"), + spec -> spec.mapBeforeRecipe(cu -> (J.CompilationUnit) new JavaIsoVisitor() { + @Override + public J.Identifier visitIdentifier(J.Identifier id, Integer p) { + return id.getType() != null && id.getType().toString().startsWith("javax.xml.bind") ? + id.withType(JavaType.Unknown.getInstance()).withFieldType(null) : id; + } + + @Override + public J.FieldAccess visitFieldAccess(J.FieldAccess fa, Integer p) { + J.FieldAccess f = super.visitFieldAccess(fa, p); + return f.getType() != null && f.getType().toString().startsWith("javax.xml.bind") ? + f.withType(JavaType.Unknown.getInstance()) : f; + } + + @Override + public J.VariableDeclarations.NamedVariable visitVariable(J.VariableDeclarations.NamedVariable v, Integer p) { + J.VariableDeclarations.NamedVariable n = super.visitVariable(v, p); + return n.getVariableType() != null && n.getVariableType().getType().toString().startsWith("javax.xml.bind") ? + n.withVariableType(n.getVariableType().withType(JavaType.Unknown.getInstance())) : n; + } + + @Override + public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, Integer p) { + return super.visitMethodInvocation(method, p).withMethodType(null); + } + }.visit(cu, 0)))) + ) + ); + } + + @Test + void compileScopedPlatformDoesNotProvideRuntime() { + rewriteRun( + spec -> spec.recipe(new AddJakartaXmlBindRuntime()), + mavenProject("app", + pomXml(POM.replace("", """ + + + jakarta.platform + jakarta.jakartaee-api + 9.1.0 + + + + """).stripTrailing(), spec -> spec.after(pom -> { + assertThat(pom).contains("jaxb-runtime", "runtime"); + return pom; + })), + srcMainJava(java(FACTORY)) + ) + ); + } +}