Skip to content

Commit e860bba

Browse files
authored
Add S3StreamingResponseToV2 Recipe (#5173)
* Add S3StreamingResponseToV2 Recipe This recipe transforms usages of `s3.getObject(..)` such as ``` S3Object myObject = s3.getObject(request); ``` to the equivalent in v2: ``` ResponseInputStream<GetObjectResponse> myObject = s3.getOBject(request); ``` In addition, because of the inversion of streaming and non-streaming members when moving to ResponseInputStream, it further transforms usages of myObject so that calls in v1 that access the stream: ``` myObject.getObjectContent().close() ``` become ``` myObject.close() ``` Likewise, methods that access the non-streaming members ``` myObject.getKey(); ``` become ``` myObject.response().getKey(); ``` * Review comments
1 parent 3f0cbe1 commit e860bba

File tree

5 files changed

+463
-12
lines changed

5 files changed

+463
-12
lines changed
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.migration.internal.recipe;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import java.util.stream.Collectors;
21+
import org.openrewrite.ExecutionContext;
22+
import org.openrewrite.Recipe;
23+
import org.openrewrite.Tree;
24+
import org.openrewrite.TreeVisitor;
25+
import org.openrewrite.java.JavaVisitor;
26+
import org.openrewrite.java.MethodMatcher;
27+
import org.openrewrite.java.tree.Expression;
28+
import org.openrewrite.java.tree.J;
29+
import org.openrewrite.java.tree.JContainer;
30+
import org.openrewrite.java.tree.JRightPadded;
31+
import org.openrewrite.java.tree.JavaType;
32+
import org.openrewrite.java.tree.Space;
33+
import org.openrewrite.java.tree.TypeUtils;
34+
import org.openrewrite.marker.Markers;
35+
import software.amazon.awssdk.annotations.SdkInternalApi;
36+
import software.amazon.awssdk.migration.internal.utils.IdentifierUtils;
37+
38+
@SdkInternalApi
39+
public class S3StreamingResponseToV2 extends Recipe {
40+
private static final JavaType.FullyQualified S3_OBJECT_TYPE =
41+
TypeUtils.asFullyQualified(JavaType.buildType("com.amazonaws.services.s3.model.S3Object"));
42+
private static final JavaType.FullyQualified S3_GET_OBJECT_RESPONSE_TYPE =
43+
TypeUtils.asFullyQualified(JavaType.buildType("software.amazon.awssdk.services.s3.model.GetObjectResponse"));
44+
45+
private static final JavaType.FullyQualified RESPONSE_INPUT_STREAM_TYPE =
46+
TypeUtils.asFullyQualified(JavaType.buildType("software.amazon.awssdk.core.ResponseInputStream"));
47+
48+
private static final MethodMatcher GET_OBJECT_CONTENT =
49+
new MethodMatcher("com.amazonaws.services.s3.model.S3Object getObjectContent()");
50+
51+
private static final MethodMatcher OBJECT_INPUT_STREAM_METHOD =
52+
new MethodMatcher("com.amazonaws.services.s3.model.S3ObjectInputStream *(..)");
53+
54+
@Override
55+
public String getDisplayName() {
56+
return "V1 S3Object to V2";
57+
}
58+
59+
@Override
60+
public String getDescription() {
61+
return "Transform usage of V1 S3Object to V2.";
62+
}
63+
64+
@Override
65+
public TreeVisitor<?, ExecutionContext> getVisitor() {
66+
return new Visitor();
67+
}
68+
69+
private static class Visitor extends JavaVisitor<ExecutionContext> {
70+
71+
// Transform an S3Object myObject = ...
72+
// to
73+
// ResponseInputStream<GetObjectResponse> myObject = ...
74+
@Override
75+
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable,
76+
ExecutionContext executionContext) {
77+
if (TypeUtils.isAssignableTo(S3_OBJECT_TYPE, multiVariable.getType())) {
78+
JavaType.Parameterized newType =
79+
new JavaType.Parameterized(null, RESPONSE_INPUT_STREAM_TYPE,
80+
Collections.singletonList(S3_GET_OBJECT_RESPONSE_TYPE));
81+
82+
maybeAddS3ResponseImports();
83+
84+
multiVariable = multiVariable.withType(newType)
85+
.withTypeExpression(IdentifierUtils.makeId(IdentifierUtils.simpleName(newType),
86+
newType));
87+
88+
List<J.VariableDeclarations.NamedVariable> variables = multiVariable.getVariables().stream()
89+
.map(nv -> nv.withType(newType))
90+
.collect(Collectors.toList());
91+
92+
multiVariable = multiVariable.withVariables(variables);
93+
}
94+
95+
multiVariable = super.visitVariableDeclarations(multiVariable, executionContext).cast();
96+
97+
return multiVariable;
98+
}
99+
100+
private void maybeAddS3ResponseImports() {
101+
maybeAddImport(RESPONSE_INPUT_STREAM_TYPE);
102+
// Note: 'onlyIfReferenced' set to false because OpenRewrite does not seem to consider just having the type as a type
103+
// parameter as being in use, so will not add the import if set to true.
104+
maybeAddImport(S3_GET_OBJECT_RESPONSE_TYPE.getFullyQualifiedName(), null, false);
105+
}
106+
107+
// In v2, the request is inverted. Whereas S3Object contains the response stream, in V2, a response stream object
108+
// wraps the response object. so the InputStream methods are directly on the "response" object now, and to access the
109+
// non-streaming members, we need to insert a call to response().
110+
@Override
111+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
112+
method = super.visitMethodInvocation(method, executionContext).cast();
113+
114+
Expression select = method.getSelect();
115+
116+
if (select == null) {
117+
return method;
118+
}
119+
120+
if (GET_OBJECT_CONTENT.matches(method)) {
121+
return select;
122+
}
123+
124+
JavaType.Method methodType = method.getMethodType();
125+
126+
if (methodType == null) {
127+
return method;
128+
}
129+
130+
if (!TypeUtils.isAssignableTo(S3_OBJECT_TYPE, methodType.getDeclaringType())) {
131+
return method;
132+
}
133+
134+
// Calling a method on the stream, just change the declaring type
135+
if (isObjectContentMethod(method)) {
136+
method = method.withMethodType(methodType.withDeclaringType(RESPONSE_INPUT_STREAM_TYPE));
137+
return method;
138+
}
139+
140+
JavaType.Method responseGetterType = new JavaType.Method(
141+
null,
142+
0L,
143+
RESPONSE_INPUT_STREAM_TYPE,
144+
"response",
145+
S3_GET_OBJECT_RESPONSE_TYPE,
146+
Collections.emptyList(),
147+
Collections.emptyList(),
148+
Collections.emptyList(),
149+
Collections.emptyList()
150+
);
151+
152+
// Calling a method on the response, insert a response() getter
153+
J.Identifier responseGetterId = IdentifierUtils.makeId("response", responseGetterType);
154+
155+
J.MethodInvocation getResponse = new J.MethodInvocation(
156+
Tree.randomId(),
157+
Space.EMPTY,
158+
Markers.EMPTY,
159+
JRightPadded.build(select),
160+
null,
161+
responseGetterId,
162+
JContainer.empty(),
163+
responseGetterType
164+
);
165+
166+
methodType = methodType.withDeclaringType(S3_GET_OBJECT_RESPONSE_TYPE);
167+
method = method.withSelect(getResponse)
168+
.withName(method.getName().withType(methodType))
169+
.withMethodType(methodType);
170+
171+
return method;
172+
}
173+
174+
private static boolean isObjectContentMethod(J.MethodInvocation method) {
175+
Expression select = method.getSelect();
176+
if (select == null || !TypeUtils.isAssignableTo(S3_OBJECT_TYPE, select.getType())) {
177+
return false;
178+
}
179+
return OBJECT_INPUT_STREAM_METHOD.matches(method);
180+
}
181+
}
182+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.migration.internal.utils;
17+
18+
import java.util.Collections;
19+
import java.util.List;
20+
import org.openrewrite.Tree;
21+
import org.openrewrite.java.tree.J;
22+
import org.openrewrite.java.tree.JavaType;
23+
import org.openrewrite.java.tree.Space;
24+
import org.openrewrite.java.tree.TypeUtils;
25+
import org.openrewrite.marker.Markers;
26+
import software.amazon.awssdk.annotations.SdkInternalApi;
27+
28+
@SdkInternalApi
29+
public final class IdentifierUtils {
30+
private IdentifierUtils() {
31+
}
32+
33+
/**
34+
* Utility method for creating a {@link J.Identifier}.
35+
*/
36+
public static J.Identifier makeId(String simpleName, JavaType type) {
37+
return new J.Identifier(
38+
Tree.randomId(),
39+
Space.EMPTY,
40+
Markers.EMPTY,
41+
Collections.emptyList(),
42+
simpleName,
43+
type,
44+
null
45+
);
46+
}
47+
48+
/**
49+
* Simple method for creating a simple name for a {@link JavaType.Parameterized} instance. This is a naive implementation
50+
* that currently requires all type parameters to be {@link JavaType.FullyQualified}, and does not handle nested
51+
* parameterized types.
52+
*/
53+
public static String simpleName(JavaType.Parameterized parameterized) {
54+
StringBuilder sb = new StringBuilder(parameterized.getClassName())
55+
.append('<');
56+
57+
List<JavaType> typeParams = parameterized.getTypeParameters();
58+
59+
for (int i = 0; i < typeParams.size(); ++i) {
60+
JavaType.FullyQualified fqParamType = TypeUtils.asFullyQualified(typeParams.get(i));
61+
if (fqParamType == null) {
62+
throw new RuntimeException("Encountered non fully qualified type");
63+
}
64+
sb.append(fqParamType.getClassName());
65+
66+
if (i + 1 != typeParams.size()) {
67+
sb.append(", ");
68+
}
69+
}
70+
71+
return sb.append('>').toString();
72+
}
73+
}

migration-tool/src/main/resources/META-INF/rewrite/upgrade-sdk-dependencies.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -845,12 +845,6 @@ recipeList:
845845
newGroupId: software.amazon.awssdk
846846
newArtifactId: transfer
847847
newVersion: 2.23.16-SNAPSHOT
848-
- org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId:
849-
oldGroupId: com.amazonaws
850-
oldArtifactId: aws-java-sdk-deadline
851-
newGroupId: software.amazon.awssdk
852-
newArtifactId: deadline
853-
newVersion: 2.23.16-SNAPSHOT
854848
- org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId:
855849
oldGroupId: com.amazonaws
856850
oldArtifactId: aws-java-sdk-braket
@@ -2177,12 +2171,6 @@ recipeList:
21772171
newGroupId: software.amazon.awssdk
21782172
newArtifactId: ram
21792173
newVersion: 2.23.16-SNAPSHOT
2180-
- org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId:
2181-
oldGroupId: com.amazonaws
2182-
oldArtifactId: aws-java-sdk-codeconnections
2183-
newGroupId: software.amazon.awssdk
2184-
newArtifactId: codeconnections
2185-
newVersion: 2.23.16-SNAPSHOT
21862174
- org.openrewrite.maven.ChangeDependencyGroupIdAndArtifactId:
21872175
oldGroupId: com.amazonaws
21882176
oldArtifactId: aws-java-sdk-efs

0 commit comments

Comments
 (0)