Skip to content

Commit 56fd87b

Browse files
dsgrievetimtebeekgithub-actions[bot]claude
authored
Replace ApiKey with SecurityScheme and AuthorizationScope with Scopes (#808)
* add some transforms of springfox security api to swagger v3 * Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * Add recipes to SpringFoxToSpringDoc * change package to org.openrewrite.java.springdoc * Use backticks around class names in markdown * replace body of passAsToSecuritySchemeIn with more robust implementation * Apply best practice rewrites - Format ternary operators and else-if chains in FindConfigurationProperties - Apply Yoda conditions in test assertions - Add @DocumentExample annotation to test - Reorder examples in YAML configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Remove copied comment and inline local variable * Polish `FindConfigurationProperties` --------- Co-authored-by: Tim te Beek <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude <[email protected]>
1 parent 3a571f1 commit 56fd87b

File tree

10 files changed

+323
-67
lines changed

10 files changed

+323
-67
lines changed

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ dependencies {
187187
implementation("org.openrewrite:rewrite-yaml")
188188
implementation("org.openrewrite:rewrite-gradle")
189189
implementation("org.openrewrite:rewrite-maven")
190+
implementation("org.openrewrite.meta:rewrite-analysis:${rewriteVersion}")
190191
implementation("org.openrewrite.recipe:rewrite-java-dependencies:${rewriteVersion}")
191192
implementation("org.openrewrite.recipe:rewrite-static-analysis:${rewriteVersion}")
192193
implementation("org.openrewrite.gradle.tooling:model:${rewriteVersion}")

src/main/java/org/openrewrite/java/spring/search/FindConfigurationProperties.java

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.openrewrite.Recipe;
2222
import org.openrewrite.SourceFile;
2323
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.internal.ListUtils;
2425
import org.openrewrite.java.JavaIsoVisitor;
2526
import org.openrewrite.java.spring.table.ConfigurationPropertiesTable;
2627
import org.openrewrite.java.tree.Expression;
@@ -45,7 +46,7 @@ public String getDisplayName() {
4546
public String getDescription() {
4647
//language=markdown
4748
return "Find all classes annotated with `@ConfigurationProperties` and extract their prefix values. " +
48-
"This is useful for discovering all externalized configuration properties in Spring Boot applications.";
49+
"This is useful for discovering all externalized configuration properties in Spring Boot applications.";
4950
}
5051

5152
@Override
@@ -67,14 +68,11 @@ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, Ex
6768
prefixInfo.value
6869
));
6970

70-
String marker = prefixInfo.isConstant
71-
? "@ConfigurationProperties(" + prefixInfo.source + " = \"" + prefixInfo.value + "\")"
72-
: "@ConfigurationProperties(\"" + prefixInfo.value + "\")";
73-
c = c.withLeadingAnnotations(
74-
c.getLeadingAnnotations().stream()
75-
.map(a -> a == annotation ? SearchResult.found(a, marker) : a)
76-
.collect(java.util.stream.Collectors.toList())
77-
);
71+
String marker = prefixInfo.isConstant ?
72+
"@ConfigurationProperties(" + prefixInfo.source + " = \"" + prefixInfo.value + "\")" :
73+
"@ConfigurationProperties(\"" + prefixInfo.value + "\")";
74+
c = c.withLeadingAnnotations(ListUtils.map(c.getLeadingAnnotations(),
75+
a -> a == annotation ? SearchResult.found(a, marker) : a));
7876
break;
7977
}
8078
}
@@ -105,13 +103,16 @@ private PrefixInfo extractPrefix(J.Annotation annotation, J.ClassDeclaration cla
105103
Object value = ((J.Literal) arg).getValue();
106104
String prefix = value != null ? value.toString() : "";
107105
return new PrefixInfo(prefix, prefix, false);
108-
} else if (arg instanceof J.Identifier) {
106+
}
107+
if (arg instanceof J.Identifier) {
109108
// Handle @ConfigurationProperties(PREFIX)
110109
return resolveFieldValue((J.Identifier) arg, classDecl);
111-
} else if (arg instanceof J.FieldAccess) {
110+
}
111+
if (arg instanceof J.FieldAccess) {
112112
// Handle @ConfigurationProperties(SomeClass.PREFIX)
113113
return resolveFieldAccessValue((J.FieldAccess) arg);
114-
} else if (arg instanceof J.Assignment) {
114+
}
115+
if (arg instanceof J.Assignment) {
115116
// Handle @ConfigurationProperties(value = "prefix") or @ConfigurationProperties(prefix = "prefix")
116117
J.Assignment assignment = (J.Assignment) arg;
117118
if (assignment.getVariable() instanceof J.Identifier) {
@@ -122,9 +123,11 @@ private PrefixInfo extractPrefix(J.Annotation annotation, J.ClassDeclaration cla
122123
Object value = ((J.Literal) assignmentValue).getValue();
123124
String prefix = value != null ? value.toString() : "";
124125
return new PrefixInfo(prefix, prefix, false);
125-
} else if (assignmentValue instanceof J.Identifier) {
126+
}
127+
if (assignmentValue instanceof J.Identifier) {
126128
return resolveFieldValue((J.Identifier) assignmentValue, classDecl);
127-
} else if (assignmentValue instanceof J.FieldAccess) {
129+
}
130+
if (assignmentValue instanceof J.FieldAccess) {
128131
return resolveFieldAccessValue((J.FieldAccess) assignmentValue);
129132
}
130133
}

src/main/java/org/openrewrite/java/spring/swagger/ApiInfoBuilderToInfo.java renamed to src/main/java/org/openrewrite/java/springdoc/ApiInfoBuilderToInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.openrewrite.java.spring.swagger;
16+
package org.openrewrite.java.springdoc;
1717

1818
import org.jspecify.annotations.Nullable;
1919
import org.openrewrite.*;
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (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://docs.moderne.io/licensing/moderne-source-available-license
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.springdoc;
17+
18+
import org.openrewrite.*;
19+
import org.openrewrite.analysis.constantfold.ConstantFold;
20+
import org.openrewrite.analysis.util.CursorUtil;
21+
import org.openrewrite.java.*;
22+
import org.openrewrite.java.search.UsesMethod;
23+
import org.openrewrite.java.tree.Expression;
24+
import org.openrewrite.java.tree.J;
25+
26+
import java.util.Arrays;
27+
import java.util.List;
28+
29+
public class SecurityContextToSecurityScheme extends Recipe {
30+
private static final MethodMatcher APIKEY_MATCHER = new MethodMatcher("springfox.documentation.service.ApiKey <constructor>(String, String, String)");
31+
private static final MethodMatcher AUTHORIZATION_SCOPE_MATCHER = new MethodMatcher("springfox.documentation.service.AuthorizationScope <constructor>(String, String)");
32+
33+
@Override
34+
public String getDisplayName() {
35+
return "Replace elements of SpringFox's security with Swagger's security models";
36+
}
37+
38+
@Override
39+
public String getDescription() {
40+
return "Replace `ApiKey`, `AuthorizationScope`, and `SecurityScheme` elements with Swagger's equivalents.";
41+
}
42+
43+
@Override
44+
public TreeVisitor<?, ExecutionContext> getVisitor() {
45+
46+
return new TreeVisitor<Tree, ExecutionContext>() {
47+
@Override
48+
public Tree preVisit(Tree tree, ExecutionContext ctx) {
49+
stopAfterPreVisit();
50+
51+
Tree t = replaceApiKey(ctx, tree);
52+
return replaceAuthorizationScope(ctx, t);
53+
}
54+
55+
private Tree replaceApiKey(ExecutionContext ctx, Tree t) {
56+
return Preconditions.check(new UsesMethod<>(APIKEY_MATCHER), new JavaVisitor<ExecutionContext>() {
57+
// Replace `Contact` constructor
58+
@Override
59+
public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) {
60+
if (APIKEY_MATCHER.matches(newClass)) {
61+
String inValue = passAsToSecuritySchemeIn(newClass.getArguments().get(2));
62+
maybeRemoveImport("springfox.documentation.service.ApiKey");
63+
maybeAddImport("io.swagger.v3.oas.models.security.SecurityScheme");
64+
return JavaTemplate.builder("new SecurityScheme()\n.type(SecurityScheme.Type.APIKEY)\n.name(#{any(String)})\n.in(" + inValue +")")
65+
.imports("io.swagger.v3.oas.models.security.SecurityScheme")
66+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "swagger-models"))
67+
.build()
68+
.apply(getCursor(), newClass.getCoordinates().replace(), newClass.getArguments().get(1));
69+
}
70+
return super.visitNewClass(newClass, ctx);
71+
}
72+
73+
private String passAsToSecuritySchemeIn(Expression passAsExpr) {
74+
return CursorUtil.findCursorForTree(getCursor(), passAsExpr)
75+
.bind(c -> ConstantFold.findConstantLiteralValue(c, String.class))
76+
.map(passAs -> {
77+
switch (passAs) {
78+
case "cookie": return "SecurityScheme.In.COOKIE";
79+
case "query": return "SecurityScheme.In.QUERY";
80+
default: return "SecurityScheme.In.HEADER";
81+
}
82+
})
83+
.orSome("SecurityScheme.In.HEADER");
84+
}
85+
}).visitNonNull(t, ctx, getCursor().getParentOrThrow());
86+
}
87+
88+
private Tree replaceAuthorizationScope(ExecutionContext ctx, Tree t) {
89+
return Preconditions.check(new UsesMethod<>(AUTHORIZATION_SCOPE_MATCHER), new JavaVisitor<ExecutionContext>() {
90+
@Override
91+
public J visitNewClass(J.NewClass newClass, ExecutionContext ctx) {
92+
if (AUTHORIZATION_SCOPE_MATCHER.matches(newClass)) {
93+
maybeRemoveImport("springfox.documentation.service.AuthorizationScope");
94+
maybeAddImport("io.swagger.v3.oas.models.security.Scopes");
95+
return JavaTemplate.builder("new Scopes().addString(#{any(String)}, #{any(String)})")
96+
.imports("io.swagger.v3.oas.models.security.Scopes")
97+
.javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "swagger-models"))
98+
.build()
99+
.apply(getCursor(), newClass.getCoordinates().replace(), newClass.getArguments().get(0), newClass.getArguments().get(1));
100+
}
101+
return super.visitNewClass(newClass, ctx);
102+
}
103+
104+
}).visitNonNull(t, ctx, getCursor().getParentOrThrow());
105+
}
106+
};
107+
}
108+
109+
@Override
110+
public List<Recipe> getRecipeList() {
111+
return Arrays.asList(
112+
new ChangeType(
113+
"springfox.documentation.service.ApiKey",
114+
"io.swagger.v3.oas.models.security.SecurityScheme",
115+
true),
116+
new ChangeType(
117+
"springfox.documentation.service.AuthorizationScope",
118+
"io.swagger.v3.oas.models.security.Scopes",
119+
true),
120+
new ChangeType(
121+
"springfox.documentation.service.SecurityScheme",
122+
"io.swagger.v3.oas.models.security.SecurityScheme",
123+
true)
124+
);
125+
}
126+
}

src/main/java/org/openrewrite/java/spring/swagger/package-info.java renamed to src/main/java/org/openrewrite/java/springdoc/package-info.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
@NullMarked @NonNullFields
17-
package org.openrewrite.java.spring.swagger;
17+
package org.openrewrite.java.springdoc;
1818

1919
import org.jspecify.annotations.NullMarked;
2020
import org.openrewrite.internal.lang.NonNullFields;

src/main/resources/META-INF/rewrite/examples.yml

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5142,7 +5142,41 @@ examples:
51425142
language: java
51435143
---
51445144
type: specs.openrewrite.org/v1beta/example
5145-
recipeName: org.openrewrite.java.spring.swagger.ApiInfoBuilderToInfo
5145+
recipeName: org.openrewrite.java.spring.test.SpringRulesToJUnitExtension
5146+
examples:
5147+
- description: '`SpringRulesToJUnitExtensionTest#migrateWithSpringBootTestPresent`'
5148+
sources:
5149+
- before: |
5150+
import org.springframework.boot.test.context.SpringBootTest;
5151+
import org.springframework.test.context.junit4.rules.SpringClassRule;
5152+
import org.springframework.test.context.junit4.rules.SpringMethodRule;
5153+
import org.junit.ClassRule;
5154+
import org.junit.Rule;
5155+
5156+
@SpringBootTest
5157+
class SomeTest {
5158+
5159+
@ClassRule
5160+
public static final SpringClassRule springClassRule = new SpringClassRule();
5161+
5162+
@Rule
5163+
public final SpringMethodRule springMethodRule = new SpringMethodRule();
5164+
5165+
}
5166+
after: |
5167+
import org.junit.jupiter.api.extension.ExtendWith;
5168+
import org.springframework.boot.test.context.SpringBootTest;
5169+
import org.springframework.test.context.junit.jupiter.SpringExtension;
5170+
5171+
@ExtendWith(SpringExtension.class)
5172+
@SpringBootTest
5173+
class SomeTest {
5174+
5175+
}
5176+
language: java
5177+
---
5178+
type: specs.openrewrite.org/v1beta/example
5179+
recipeName: org.openrewrite.java.springdoc.ApiInfoBuilderToInfo
51465180
examples:
51475181
- description: '`ApiInfoBuilderToInfoTest#transformApiInfoBuilder`'
51485182
sources:
@@ -5183,40 +5217,6 @@ examples:
51835217
language: java
51845218
---
51855219
type: specs.openrewrite.org/v1beta/example
5186-
recipeName: org.openrewrite.java.spring.test.SpringRulesToJUnitExtension
5187-
examples:
5188-
- description: '`SpringRulesToJUnitExtensionTest#migrateWithSpringBootTestPresent`'
5189-
sources:
5190-
- before: |
5191-
import org.springframework.boot.test.context.SpringBootTest;
5192-
import org.springframework.test.context.junit4.rules.SpringClassRule;
5193-
import org.springframework.test.context.junit4.rules.SpringMethodRule;
5194-
import org.junit.ClassRule;
5195-
import org.junit.Rule;
5196-
5197-
@SpringBootTest
5198-
class SomeTest {
5199-
5200-
@ClassRule
5201-
public static final SpringClassRule springClassRule = new SpringClassRule();
5202-
5203-
@Rule
5204-
public final SpringMethodRule springMethodRule = new SpringMethodRule();
5205-
5206-
}
5207-
after: |
5208-
import org.junit.jupiter.api.extension.ExtendWith;
5209-
import org.springframework.boot.test.context.SpringBootTest;
5210-
import org.springframework.test.context.junit.jupiter.SpringExtension;
5211-
5212-
@ExtendWith(SpringExtension.class)
5213-
@SpringBootTest
5214-
class SomeTest {
5215-
5216-
}
5217-
language: java
5218-
---
5219-
type: specs.openrewrite.org/v1beta/example
52205220
recipeName: org.openrewrite.java.springdoc.MigrateSpringdocCommon
52215221
examples:
52225222
- description: '`MigrateSpringdocCommonTest#fixCustomiserAndGroupedOpenApi`'
@@ -5266,6 +5266,32 @@ examples:
52665266
language: java
52675267
---
52685268
type: specs.openrewrite.org/v1beta/example
5269+
recipeName: org.openrewrite.java.springdoc.SecurityContextToSecurityScheme
5270+
examples:
5271+
- description: '`SecurityContextToSecuritySchemeTest#apiKeyToSecurityScheme`'
5272+
sources:
5273+
- before: |
5274+
import springfox.documentation.service.ApiKey;
5275+
5276+
class Test {
5277+
ApiKey apiKey() {
5278+
return new ApiKey("api_key", "X-API-KEY", "header");
5279+
}
5280+
}
5281+
after: |
5282+
import io.swagger.v3.oas.models.security.SecurityScheme;
5283+
5284+
class Test {
5285+
SecurityScheme apiKey() {
5286+
return new SecurityScheme()
5287+
.type(SecurityScheme.Type.APIKEY)
5288+
.name("X-API-KEY")
5289+
.in(SecurityScheme.In.HEADER);
5290+
}
5291+
}
5292+
language: java
5293+
---
5294+
type: specs.openrewrite.org/v1beta/example
52695295
recipeName: org.openrewrite.java.springdoc.UpgradeSpringDoc_2
52705296
examples:
52715297
- description: '`UpgradeSpringDoc2Test#upgradeMaven`'

src/main/resources/META-INF/rewrite/springdoc.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ tags:
2727
recipeList:
2828
- org.openrewrite.java.springdoc.SwaggerToSpringDoc
2929
- org.openrewrite.java.springdoc.ReplaceSpringFoxDependencies
30+
- org.openrewrite.java.springdoc.ApiInfoBuilderToInfo
31+
- org.openrewrite.java.springdoc.SecurityContextToSecurityScheme
3032
- org.openrewrite.java.springdoc.MigrateSpringdocCommon
3133

3234
---

0 commit comments

Comments
 (0)