Skip to content

Commit f779aa8

Browse files
timtebeekclaude
andauthored
Add Dropwizard 4.x to 5.0.x upgrade recipes (#13)
* Add Dropwizard 4.x to 5.0.x upgrade recipes Implements recipes for migrating Dropwizard applications from 4.x to 5.0.x: - Declarative recipe to upgrade all io.dropwizard dependencies to 5.0.x - Declarative recipe to remove deprecated server.maxQueuedRequests config - Imperative recipe to migrate Jetty 11 AbstractHandler to Jetty 12 Handler.Abstract (new handle method signature, Callback pattern) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Replace `getDisplayName()` and `getDescription()` methods with fields Fix recipe.csv validation issues Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Apply best practices * Light polish * Adopt method matchers * Polish tests * Add missing package reference * Add additional required upgrades for 5.x * Update documentation * Rename test to match recipe * Add Java 17 directive as needed for DW7 --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent d2850a6 commit f779aa8

File tree

8 files changed

+627
-6
lines changed

8 files changed

+627
-6
lines changed

build.gradle.kts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@ dependencies {
1717
implementation("org.openrewrite:rewrite-properties")
1818
implementation("org.openrewrite:rewrite-yaml")
1919

20+
implementation("org.openrewrite.recipe:rewrite-hibernate:${rewriteVersion}")
21+
implementation("org.openrewrite.recipe:rewrite-migrate-java:${rewriteVersion}")
2022
implementation("org.openrewrite.recipe:rewrite-static-analysis:${rewriteVersion}")
2123
implementation("org.openrewrite.recipe:rewrite-testing-frameworks:${rewriteVersion}")
2224

2325
runtimeOnly("org.openrewrite:rewrite-java-21")
2426

25-
testImplementation("org.junit.jupiter:junit-jupiter-api:5.+")
26-
testImplementation("org.junit.jupiter:junit-jupiter-params:5.+")
27-
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.3")
28-
2927
testImplementation("org.openrewrite:rewrite-test")
3028

3129
testRuntimeOnly("io.dropwizard.metrics:metrics-annotation:4.1.+")
@@ -35,4 +33,6 @@ dependencies {
3533
testRuntimeOnly("org.projectlombok:lombok:1.18.+")
3634
testRuntimeOnly("net.sourceforge.argparse4j:argparse4j:0.9.0")
3735
testRuntimeOnly("io.dropwizard:dropwizard-testing:1.3.29")
36+
testRuntimeOnly("org.eclipse.jetty:jetty-server:11.+")
37+
testRuntimeOnly("jakarta.servlet:jakarta.servlet-api:5.+")
3838
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.dropwizard.jetty;
17+
18+
import lombok.EqualsAndHashCode;
19+
import lombok.Value;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.Preconditions;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.java.JavaIsoVisitor;
25+
import org.openrewrite.java.MethodMatcher;
26+
import org.openrewrite.java.search.UsesType;
27+
import org.openrewrite.java.tree.*;
28+
import org.openrewrite.marker.Markers;
29+
30+
import java.util.ArrayList;
31+
import java.util.List;
32+
33+
import static java.util.Collections.emptyList;
34+
import static java.util.Collections.singletonList;
35+
import static org.openrewrite.Tree.randomId;
36+
import static org.openrewrite.java.tree.TypeUtils.isOfClassType;
37+
38+
@EqualsAndHashCode(callSuper = false)
39+
@Value
40+
public class MigrateJettyHandlerSignature extends Recipe {
41+
42+
String displayName = "Migrate Jetty `AbstractHandler` to Jetty 12 `Handler.Abstract`";
43+
44+
String description = "Migrates custom Jetty handler implementations from Jetty 11's `AbstractHandler` " +
45+
"(used in Dropwizard 4.x) to Jetty 12's `Handler.Abstract` (used in Dropwizard 5.x). " +
46+
"This changes the `handle` method signature and updates `baseRequest.setHandled(true)` " +
47+
"to use `Callback` and return `true`.";
48+
49+
private static final String ABSTRACT_HANDLER = "org.eclipse.jetty.server.handler.AbstractHandler";
50+
private static final String JETTY_REQUEST = "org.eclipse.jetty.server.Request";
51+
private static final String JETTY_RESPONSE = "org.eclipse.jetty.server.Response";
52+
private static final String JETTY_CALLBACK = "org.eclipse.jetty.util.Callback";
53+
54+
private static final MethodMatcher HANDLE_METHOD_MATCHER = new MethodMatcher(
55+
ABSTRACT_HANDLER + " handle(String, org.eclipse.jetty.server.Request, ..)", true);
56+
private static final MethodMatcher SET_HANDLED_MATCHER = new MethodMatcher(
57+
JETTY_REQUEST + " setHandled(boolean)", false);
58+
59+
@Override
60+
public TreeVisitor<?, ExecutionContext> getVisitor() {
61+
return Preconditions.check(
62+
new UsesType<>(ABSTRACT_HANDLER, false),
63+
new JavaIsoVisitor<ExecutionContext>() {
64+
65+
@Override
66+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
67+
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
68+
69+
if (cd.getExtends() != null && isOfClassType(cd.getExtends().getType(), ABSTRACT_HANDLER)) {
70+
JavaType.ShallowClass handlerAbstractType = JavaType.ShallowClass.build(
71+
"org.eclipse.jetty.server.Handler.Abstract");
72+
73+
J.Identifier handlerIdent = new J.Identifier(
74+
randomId(), Space.EMPTY, Markers.EMPTY,
75+
emptyList(), "Handler",
76+
JavaType.ShallowClass.build("org.eclipse.jetty.server.Handler"), null
77+
);
78+
J.Identifier abstractIdent = new J.Identifier(
79+
randomId(), Space.EMPTY, Markers.EMPTY,
80+
emptyList(), "Abstract",
81+
handlerAbstractType, null
82+
);
83+
cd = cd.withExtends(new J.FieldAccess(
84+
randomId(), cd.getExtends().getPrefix(), Markers.EMPTY,
85+
handlerIdent, JLeftPadded.build(abstractIdent),
86+
handlerAbstractType
87+
));
88+
89+
maybeRemoveImport(ABSTRACT_HANDLER);
90+
maybeRemoveImport("jakarta.servlet.ServletException");
91+
maybeRemoveImport("jakarta.servlet.http.HttpServletRequest");
92+
maybeRemoveImport("jakarta.servlet.http.HttpServletResponse");
93+
maybeRemoveImport("javax.servlet.ServletException");
94+
maybeRemoveImport("javax.servlet.http.HttpServletRequest");
95+
maybeRemoveImport("javax.servlet.http.HttpServletResponse");
96+
maybeAddImport(JETTY_CALLBACK);
97+
maybeAddImport("org.eclipse.jetty.server.Handler");
98+
maybeAddImport(JETTY_RESPONSE);
99+
}
100+
101+
return cd;
102+
}
103+
104+
@Override
105+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
106+
J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx);
107+
108+
J.ClassDeclaration enclosingClass = getCursor().firstEnclosing(J.ClassDeclaration.class);
109+
if (enclosingClass == null || !HANDLE_METHOD_MATCHER.matches(md, enclosingClass)) {
110+
return md;
111+
}
112+
113+
// Change return type from void to boolean
114+
md = md.withReturnTypeExpression(
115+
new J.Primitive(randomId(),
116+
md.getReturnTypeExpression() != null ? md.getReturnTypeExpression().getPrefix() : Space.SINGLE_SPACE,
117+
Markers.EMPTY, JavaType.Primitive.Boolean)
118+
);
119+
120+
// Build new parameter list: Request request, Response response, Callback callback
121+
List<Statement> newParams = new ArrayList<>();
122+
newParams.add(buildParameter("Request", JETTY_REQUEST, "request"));
123+
newParams.add(buildParameter("Response", JETTY_RESPONSE, "response"));
124+
newParams.add(buildParameter("Callback", JETTY_CALLBACK, "callback"));
125+
newParams.set(1, newParams.get(1).withPrefix(Space.SINGLE_SPACE));
126+
newParams.set(2, newParams.get(2).withPrefix(Space.SINGLE_SPACE));
127+
md = md.withParameters(newParams);
128+
129+
// Update throws clause to just Exception
130+
if (md.getThrows() != null) {
131+
J.Identifier exceptionType = new J.Identifier(
132+
randomId(), Space.SINGLE_SPACE, Markers.EMPTY,
133+
emptyList(), "Exception",
134+
JavaType.ShallowClass.build("java.lang.Exception"), null
135+
);
136+
md = md.withThrows(singletonList(exceptionType));
137+
}
138+
139+
return md;
140+
}
141+
142+
@Override
143+
public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
144+
J.MethodInvocation mi = super.visitMethodInvocation(method, ctx);
145+
146+
if (!SET_HANDLED_MATCHER.matches(mi)) {
147+
return mi;
148+
}
149+
150+
Expression arg = mi.getArguments().get(0);
151+
if (!(arg instanceof J.Literal) || !Boolean.TRUE.equals(((J.Literal) arg).getValue())) {
152+
return mi;
153+
}
154+
155+
// Replace with callback.succeeded()
156+
JavaType.Method succeededType = new JavaType.Method(
157+
null, 1L, JavaType.ShallowClass.build(JETTY_CALLBACK),
158+
"succeeded", JavaType.Primitive.Void,
159+
emptyList(), emptyList(),
160+
emptyList(), emptyList(),
161+
emptyList(), emptyList()
162+
);
163+
J.Identifier newName = new J.Identifier(
164+
randomId(), mi.getName().getPrefix(), Markers.EMPTY,
165+
emptyList(), "succeeded",
166+
succeededType, null
167+
);
168+
return mi
169+
.withSelect(new J.Identifier(
170+
randomId(), mi.getSelect().getPrefix(), Markers.EMPTY,
171+
emptyList(), "callback",
172+
JavaType.ShallowClass.build(JETTY_CALLBACK), null
173+
))
174+
.withName(newName)
175+
.withMethodType(succeededType)
176+
.withArguments(emptyList());
177+
}
178+
179+
@Override
180+
public J.Return visitReturn(J.Return return_, ExecutionContext ctx) {
181+
J.Return ret = super.visitReturn(return_, ctx);
182+
183+
J.MethodDeclaration enclosingMethod = getCursor().firstEnclosing(J.MethodDeclaration.class);
184+
J.ClassDeclaration enclosingClass = getCursor().firstEnclosing(J.ClassDeclaration.class);
185+
if (enclosingMethod != null && enclosingClass != null &&
186+
HANDLE_METHOD_MATCHER.matches(enclosingMethod, enclosingClass) &&
187+
ret.getExpression() == null) {
188+
ret = ret.withExpression(
189+
new J.Literal(randomId(), Space.SINGLE_SPACE, Markers.EMPTY, true, "true",
190+
null, JavaType.Primitive.Boolean)
191+
);
192+
}
193+
194+
return ret;
195+
}
196+
197+
private J.VariableDeclarations buildParameter(String typeName, String fqn, String paramName) {
198+
JavaType.ShallowClass type = JavaType.ShallowClass.build(fqn);
199+
J.Identifier typeExpr = new J.Identifier(
200+
randomId(), Space.EMPTY, Markers.EMPTY,
201+
emptyList(), typeName, type, null
202+
);
203+
J.VariableDeclarations.NamedVariable namedVar = new J.VariableDeclarations.NamedVariable(
204+
randomId(), Space.SINGLE_SPACE, Markers.EMPTY,
205+
new J.Identifier(randomId(), Space.EMPTY, Markers.EMPTY,
206+
emptyList(), paramName, type,
207+
new JavaType.Variable(null, 0, paramName, null, type, null)),
208+
emptyList(), null, null
209+
);
210+
return new J.VariableDeclarations(
211+
randomId(), Space.EMPTY, Markers.EMPTY,
212+
emptyList(), emptyList(),
213+
typeExpr, null,
214+
singletonList(JRightPadded.build(namedVar))
215+
);
216+
}
217+
}
218+
);
219+
}
220+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2026 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
@NullMarked
17+
@NonNullFields
18+
package org.openrewrite.java.dropwizard.jetty;
19+
20+
import org.jspecify.annotations.NullMarked;
21+
import org.openrewrite.internal.lang.NonNullFields;
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#
2+
# Copyright 2025 the original author or authors.
3+
# <p>
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
# <p>
8+
# https://www.apache.org/licenses/LICENSE-2.0
9+
# <p>
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
type: specs.openrewrite.org/v1beta/recipe
18+
name: org.openrewrite.java.dropwizard.MigrateToDropwizard5
19+
displayName: Migrate to Dropwizard 5.0.x from 4.x
20+
description: >-
21+
Apply changes required to upgrade a Dropwizard 4.x application to 5.0.x.
22+
This includes upgrading dependencies, removing deprecated configuration options,
23+
and migrating Jetty handler implementations.
24+
25+
Includes required migrations to Java 17, Jakarta EE 10, JUnit 5.14, Jackson 2.x, and Hibernate 6.6.
26+
See [the upgrade guide](https://www.dropwizard.io/en/stable/manual/upgrade-notes/upgrade-notes-5_0_x.html).
27+
recipeList:
28+
- org.openrewrite.java.dependencies.UpgradeDependencyVersion:
29+
groupId: io.dropwizard
30+
artifactId: "*"
31+
newVersion: "5.0.x"
32+
- org.openrewrite.java.dependencies.UpgradeDependencyVersion:
33+
groupId: io.dropwizard.modules
34+
artifactId: "*"
35+
newVersion: "5.0.x"
36+
37+
- org.openrewrite.java.migrate.UpgradeToJava17
38+
- org.openrewrite.java.migrate.jakarta.JakartaEE10
39+
- org.openrewrite.java.testing.junit5.UpgradeToJUnit514
40+
- org.openrewrite.java.dependencies.UpgradeDependencyVersion:
41+
groupId: com.fasterxml.jackson*
42+
artifactId: "*"
43+
newVersion: 2.x
44+
overrideManagedVersion: false
45+
- org.openrewrite.hibernate.MigrateToHibernate66
46+
47+
- org.openrewrite.yaml.DeleteKey:
48+
keyPath: $.server.maxQueuedRequests
49+
- org.openrewrite.java.dropwizard.jetty.MigrateJettyHandlerSignature

0 commit comments

Comments
 (0)