Skip to content

Commit

Permalink
Rewrite recipes that upgrade jaxb/jax-ws and add explicit runtimes. F…
Browse files Browse the repository at this point in the history
…ixes #145
  • Loading branch information
tkvangorder committed Nov 14, 2022
1 parent 5f55cfa commit 17b4a10
Show file tree
Hide file tree
Showing 13 changed files with 824 additions and 508 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.j2ee;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.maven.*;
import org.openrewrite.maven.tree.*;
import org.openrewrite.xml.tree.Xml;

import java.time.Duration;
import java.util.*;

@Value
@EqualsAndHashCode(callSuper = true)
public class UpdateJaxbRuntimeToJakartaEE8 extends Recipe {

private static final String LEGACY_JAVA_JAXB_API_GROUP = "javax.xml.bind";
private static final String LEGACY_JAVA_JAXB_API_ARTIFACT = "jaxb-api";

private static final String JAKARTA_API_GROUP = "jakarta.xml.bind";
private static final String JAKARTA_API_ARTIFACT = "jakarta.xml.bind-api";

private static final String SUN_JAXB_RUNTIME_GROUP = "com.sun.xml.bind";
private static final String SUN_JAXB_RUNTIME_ARTIFACT = "jaxb-impl";

private static final String GLASSFISH_JAXB_RUNTIME_GROUP = "org.glassfish.jaxb";
private static final String GLASSFISH_JAXB_RUNTIME_ARTIFACT = "jaxb-runtime";

@Option(displayName = "JAXB run-time",
description = "Which implementation of the JAXB run-time that will be added to maven projects that have transitive dependencies on the JAXB API",
valid = {"glassfish", "sun"},
example = "glassfish")
String runtime;

@Override
public String getDisplayName() {
return "Use latest JAXB API and runtime for Jakarta EE 8";
}

@Override
public String getDescription() {
return "Update maven build files to use the latest JAXB API from Jakarta EE8 and add a compatible runtime " +
"dependency to maintain compatibility with Java versions greater than Java 8. This recipe will change " +
"existing dependencies on `javax.xml.bind:jax-api` to `jakarta.xml.bind:jakarta.xml.bind-api`. The " +
"recipe will also add a JAXB run-time, in `provided` scope, to any project that has a transitive dependency " +
"on the JAXB API. **The resulting dependencies still use the `javax` namespace, despite the move " +
"to the Jakarta artifact.**";
}

@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(30);
}

@Override
public Set<String> getTags() {
return new HashSet<>(Arrays.asList("javax", "jakarta", "j2ee", "jaxb", "glassfish", "java11"));
}

@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
return new MavenIsoVisitor<ExecutionContext>() {
@SuppressWarnings("ConstantConditions")
@Override
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
//remove legacy jaxb-core, regardless of which runtime is being used.
doAfterVisit(new RemoveDependency("com.sun.xml.bind", "jaxb-core", null));
Xml.Document d = super.visitDocument(document, ctx);

d = (Xml.Document) new ChangeDependencyGroupIdAndArtifactId(
LEGACY_JAVA_JAXB_API_GROUP, LEGACY_JAVA_JAXB_API_ARTIFACT,
JAKARTA_API_GROUP, JAKARTA_API_ARTIFACT, "2.3.2", null
).getVisitor().visit(d, ctx);
d = (Xml.Document) new ChangeManagedDependencyGroupIdAndArtifactId(
LEGACY_JAVA_JAXB_API_GROUP, LEGACY_JAVA_JAXB_API_ARTIFACT,
JAKARTA_API_GROUP, JAKARTA_API_ARTIFACT, "2.3.2"
).getVisitor().visit(d, ctx);

//Normalize any existing runtimes to the one selected in this recipe.
if ("sun".equals(runtime)) {
d = (Xml.Document) new ChangeDependencyGroupIdAndArtifactId(
GLASSFISH_JAXB_RUNTIME_GROUP, GLASSFISH_JAXB_RUNTIME_ARTIFACT,
SUN_JAXB_RUNTIME_GROUP, SUN_JAXB_RUNTIME_ARTIFACT, "2.3.2", null
).getVisitor().visit(d, ctx);
d = (Xml.Document) new ChangeManagedDependencyGroupIdAndArtifactId(
GLASSFISH_JAXB_RUNTIME_GROUP, GLASSFISH_JAXB_RUNTIME_ARTIFACT,
SUN_JAXB_RUNTIME_GROUP, SUN_JAXB_RUNTIME_ARTIFACT, "2.3.2"
).getVisitor().visit(d, ctx);
} else {
d = (Xml.Document) new ChangeDependencyGroupIdAndArtifactId(
SUN_JAXB_RUNTIME_GROUP, SUN_JAXB_RUNTIME_ARTIFACT,
GLASSFISH_JAXB_RUNTIME_GROUP, GLASSFISH_JAXB_RUNTIME_ARTIFACT, "2.3.2", null
).getVisitor().visit(d, ctx);
d = (Xml.Document) new ChangeManagedDependencyGroupIdAndArtifactId(
SUN_JAXB_RUNTIME_GROUP, SUN_JAXB_RUNTIME_ARTIFACT,
GLASSFISH_JAXB_RUNTIME_GROUP, GLASSFISH_JAXB_RUNTIME_ARTIFACT, "2.3.2"
).getVisitor().visit(d, ctx);
}
if (d != document) {
//If any changes to dependencies were made, return now to allow the maven model to be updated.
//The logic for adding missing dependencies will happen in the next cycle.
return d;
}
d = maybeAddRuntimeDependency(d, ctx);
if (d != document) {
doAfterVisit(new RemoveRedundantDependencyVersions("org.glassfish.jaxb", "*", true));
doAfterVisit(new RemoveRedundantDependencyVersions("com.sun.xml.bind", "*", true));
doAfterVisit(new RemoveRedundantDependencyVersions("jakarta.xml.bind", "*", true));
}
return d;
}

@SuppressWarnings("ConstantConditions")
private Xml.Document maybeAddRuntimeDependency(Xml.Document d, ExecutionContext ctx) {

MavenResolutionResult mavenModel = getResolutionResult();

Scope apiScope = getTransitiveDependencyScope(mavenModel, JAKARTA_API_GROUP, JAKARTA_API_ARTIFACT);
Scope runtimeScope = "sun".equals(runtime) ?
getTransitiveDependencyScope(mavenModel, SUN_JAXB_RUNTIME_GROUP, SUN_JAXB_RUNTIME_ARTIFACT) :
getTransitiveDependencyScope(mavenModel, GLASSFISH_JAXB_RUNTIME_GROUP, GLASSFISH_JAXB_RUNTIME_ARTIFACT);

if (apiScope != null && (runtimeScope == null || !apiScope.isInClasspathOf(runtimeScope))) {
String resolvedScope = apiScope == Scope.Test ? "test" : "provided";
AddDependencyVisitor addDependency = "sun".equals(runtime) ?
new AddDependencyVisitor(SUN_JAXB_RUNTIME_GROUP, SUN_JAXB_RUNTIME_ARTIFACT, "2.3.2", null, resolvedScope, null, null, null, null, null) :
new AddDependencyVisitor(GLASSFISH_JAXB_RUNTIME_GROUP, GLASSFISH_JAXB_RUNTIME_ARTIFACT, "2.3.2", null, resolvedScope, null, null, null, null, null);
return (Xml.Document) addDependency.visit(d, ctx);
}

return d;
}

};
}

/**
* Finds the highest scope for a given group/artifact.
*
* @param mavenModel The maven model to search for a dependency.
* @param groupId The group ID of the dependency
* @param artifactId The artifact ID of the dependency
* @return The highest scope of the given dependency or null if the dependency does not exist.
*/
@Nullable
private Scope getTransitiveDependencyScope(MavenResolutionResult mavenModel, String groupId, String artifactId) {

Scope maxScope = null;
for (Map.Entry<Scope, List<ResolvedDependency>> entry : mavenModel.getDependencies().entrySet()) {
for (ResolvedDependency dependency : entry.getValue()) {
if (groupId.equals(dependency.getGroupId()) && artifactId.equals(dependency.getArtifactId())) {
maxScope = Scope.maxPrecedence(maxScope, entry.getKey());
if (Scope.Compile.equals(maxScope)) {
return maxScope;
}
break;
}
}
}
return maxScope;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/*
* Copyright 2021 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.j2ee;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.maven.*;
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.time.Duration;
import java.util.*;

@Value
@EqualsAndHashCode(callSuper = true)
public class UpdateJaxwsRuntimeToJakartaEE8 extends Recipe {

private static final String LEGACY_JAVA_JAXWS_API_GROUP = "javax.xml.ws";
private static final String LEGACY_JAVA_JAXWS_API_ARTIFACT = "jaxws-api";

private static final String JAKARTA_JAXWS_API_GROUP = "jakarta.xml.ws";
private static final String JAKARTA_JAXWS_API_ARTIFACT = "jakarta.xml.ws-api";

private static final String SUN_JAXWS_RUNTIME_GROUP = "com.sun.xml.ws";
private static final String SUN_JAXWS_RUNTIME_ARTIFACT = "jaxws-rt";

@Override
public String getDisplayName() {
return "Use the latest JAX-WS API and runtime for Jakarta EE 8";
}

@Override
public String getDescription() {
return "Update maven build files to use the latest JAX-WS API from Jakarta EE 8 and add a compatible runtime " +
"dependency to maintain compatibility with Java version greater than Java 8. This recipe will change " +
"existing dependencies on `javax.xml.ws:jaxws-api` to `jakarta.xml.ws:jakarta.xml.ws-api`. The " +
"recipe will also add a JAXWS run-time, in `provided` scope, to any project that has a transitive dependency " +
"on the JAXWS API. **The resulting dependencies still use the `javax` namespace, despite the move " +
"to the Jakarta artifact.**";
}

@Override
public Duration getEstimatedEffortPerOccurrence() {
return Duration.ofMinutes(30);
}

@Override
public Set<String> getTags() {
return new HashSet<>(Arrays.asList("javax", "jakarta", "j2ee", "jax-ws", "java11"));
}


@Override
protected TreeVisitor<?, ExecutionContext> getVisitor() {
return new MavenIsoVisitor<ExecutionContext>() {
@SuppressWarnings({"ReassignedVariable", "ConstantConditions"})
@Override
public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) {
// Remove legacy jaxws-ri library (in favor of the jaxws-rt)
doAfterVisit(new RemoveDependency(SUN_JAXWS_RUNTIME_GROUP, "jaxws-ri", null));
doAfterVisit(new RemoveManagedDependency(SUN_JAXWS_RUNTIME_GROUP, "jaxws-ri", null));
Xml.Document d = super.visitDocument(document, ctx);

d = (Xml.Document) new ChangeDependencyGroupIdAndArtifactId(
LEGACY_JAVA_JAXWS_API_GROUP, LEGACY_JAVA_JAXWS_API_ARTIFACT,
JAKARTA_JAXWS_API_GROUP, JAKARTA_JAXWS_API_ARTIFACT, "2.3.2", null
).getVisitor().visit(d, ctx);
// Change javax.xml.ws:jaxws-api to jakarta.xml.ws:jakarta.xml.ws-api
d = (Xml.Document) new ChangeManagedDependencyGroupIdAndArtifactId(
LEGACY_JAVA_JAXWS_API_GROUP, LEGACY_JAVA_JAXWS_API_ARTIFACT,
JAKARTA_JAXWS_API_GROUP, JAKARTA_JAXWS_API_ARTIFACT, "2.3.2"
).getVisitor().visit(d, ctx);

if (d != document) {
//If any changes to dependencies were made, return now to allow the maven model to be updated.
//The logic for adding missing dependencies will happen in the next cycle.
return d;
}

d = maybeAddRuntimeDependency(d, ctx);
if (d != document) {
doAfterVisit(new RemoveRedundantDependencyVersions("jakarta.xml.ws", "*", true));
doAfterVisit(new RemoveRedundantDependencyVersions("com.sun.xml.ws", "*", true));
}
return d;
}

@SuppressWarnings("ConstantConditions")
private Xml.Document maybeAddRuntimeDependency(Xml.Document d, ExecutionContext ctx) {

MavenResolutionResult mavenModel = getResolutionResult();

//Find the highest scope of a transitive dependency on the JAX-WS API (if it exists at all)
Scope apiScope = getTransitiveDependencyScope(mavenModel, JAKARTA_JAXWS_API_GROUP, JAKARTA_JAXWS_API_ARTIFACT);
//Find the highest scope of a transitive dependency on the JAX-WS runtime (if it exists at all)
Scope runtimeScope = getTransitiveDependencyScope(mavenModel, SUN_JAXWS_RUNTIME_GROUP, SUN_JAXWS_RUNTIME_ARTIFACT);

if (apiScope != null && (runtimeScope == null || !apiScope.isInClasspathOf(runtimeScope))) {
String resolvedScope = apiScope == Scope.Test ? "test" : "provided";
d = (Xml.Document) new AddDependencyVisitor(SUN_JAXWS_RUNTIME_GROUP, SUN_JAXWS_RUNTIME_ARTIFACT,
"2.3.2", null, resolvedScope, null, null, null, null, null).visit(d, ctx);
}

return d;
}

};
}

/**
* Finds the highest scope for a given group/artifact.
*
* @param mavenModel The maven model to search for a dependency.
* @param groupId The group ID of the dependency
* @param artifactId The artifact ID of the dependency
* @return The highest scope of the given dependency or null if the dependency does not exist.
*/
@Nullable
private Scope getTransitiveDependencyScope(MavenResolutionResult mavenModel, String groupId, String artifactId) {

Scope maxScope = null;
for (Map.Entry<Scope, List<ResolvedDependency>> entry : mavenModel.getDependencies().entrySet()) {
for (ResolvedDependency dependency : entry.getValue()) {
if (groupId.equals(dependency.getGroupId()) && artifactId.equals(dependency.getArtifactId())) {
maxScope = Scope.maxPrecedence(maxScope, entry.getKey());
if (Scope.Compile.equals(maxScope)) {
return maxScope;
}
break;
}
}
}
return maxScope;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/
@NonNullApi
@NonNullFields
package org.openrewrite.java.migrate.javax;
package org.openrewrite.java.migrate.j2ee;

import org.openrewrite.internal.lang.NonNullApi;
import org.openrewrite.internal.lang.NonNullFields;
Loading

0 comments on commit 17b4a10

Please sign in to comment.