Skip to content

Commit 313edb9

Browse files
authored
Support S3 SelectObjectContent (#2943)
* Support S3 SelectObjectContent This adds support for S3's SelectObjectContent operation by enabling operations with EventStreams as output for the XML protocol.
1 parent fa97ff8 commit 313edb9

File tree

16 files changed

+1085
-62
lines changed

16 files changed

+1085
-62
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon S3",
3+
"contributor": "",
4+
"type": "feature",
5+
"description": "Add support for `SelectObjectContent`."
6+
}

codegen/src/main/java/software/amazon/awssdk/codegen/poet/client/specs/XmlProtocolSpec.java

Lines changed: 184 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,23 @@
2121
import com.squareup.javapoet.CodeBlock;
2222
import com.squareup.javapoet.ParameterizedTypeName;
2323
import com.squareup.javapoet.TypeName;
24+
import com.squareup.javapoet.WildcardTypeName;
2425
import java.util.Map;
2526
import java.util.Optional;
2627
import java.util.concurrent.CompletableFuture;
28+
import software.amazon.awssdk.awscore.eventstream.EventStreamAsyncResponseTransformer;
29+
import software.amazon.awssdk.awscore.eventstream.EventStreamTaggedUnionPojoSupplier;
30+
import software.amazon.awssdk.awscore.eventstream.RestEventStreamAsyncResponseTransformer;
31+
import software.amazon.awssdk.awscore.exception.AwsServiceException;
2732
import software.amazon.awssdk.codegen.model.config.customization.S3ArnableFieldConfig;
2833
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
2934
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
35+
import software.amazon.awssdk.codegen.model.intermediate.ShapeModel;
3036
import software.amazon.awssdk.codegen.poet.PoetExtensions;
3137
import software.amazon.awssdk.codegen.poet.client.traits.HttpChecksumRequiredTrait;
38+
import software.amazon.awssdk.codegen.poet.eventstream.EventStreamUtils;
39+
import software.amazon.awssdk.codegen.poet.model.EventStreamSpecHelper;
40+
import software.amazon.awssdk.core.SdkPojoBuilder;
3241
import software.amazon.awssdk.core.client.handler.ClientExecutionParams;
3342
import software.amazon.awssdk.core.http.HttpResponseHandler;
3443
import software.amazon.awssdk.protocols.xml.AwsXmlProtocolFactory;
@@ -60,21 +69,26 @@ protected Class<?> protocolFactoryClass() {
6069
@Override
6170
public CodeBlock responseHandler(IntermediateModel model,
6271
OperationModel opModel) {
63-
6472
if (opModel.hasStreamingOutput()) {
6573
return streamingResponseHandler(opModel);
6674
}
6775

6876
ClassName responseType = poetExtensions.getModelClass(opModel.getReturnType().getReturnType());
6977

78+
if (opModel.hasEventStreamOutput()) {
79+
return CodeBlock.builder()
80+
.add(eventStreamResponseHandlers(opModel, responseType))
81+
.build();
82+
}
83+
7084
TypeName handlerType = ParameterizedTypeName.get(
7185
ClassName.get(HttpResponseHandler.class),
7286
ParameterizedTypeName.get(ClassName.get(software.amazon.awssdk.core.Response.class), responseType));
7387

7488
return CodeBlock.builder()
7589
.addStatement("\n\n$T responseHandler = protocolFactory.createCombinedResponseHandler($T::builder, "
7690
+ "new $T().withHasStreamingSuccessResponse($L))",
77-
handlerType, responseType, XmlOperationMetadata.class, opModel.hasStreamingOutput())
91+
handlerType, responseType, XmlOperationMetadata.class, opModel.hasStreamingOutput())
7892
.build();
7993
}
8094

@@ -159,35 +173,68 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper
159173
ClassName pojoResponseType = poetExtensions.getModelClass(opModel.getReturnType().getReturnType());
160174
ClassName requestType = poetExtensions.getModelClass(opModel.getInput().getVariableType());
161175
ClassName marshaller = poetExtensions.getRequestTransformClass(opModel.getInputShape().getShapeName() + "Marshaller");
176+
String eventStreamTransformFutureName = "eventStreamTransformFuture";
177+
178+
CodeBlock.Builder builder = CodeBlock.builder();
179+
180+
if (opModel.hasEventStreamOutput()) {
181+
builder.add(eventStreamResponseTransformers(opModel, eventStreamTransformFutureName));
182+
}
162183

163184
TypeName executeFutureValueType = executeFutureValueType(opModel, poetExtensions);
164-
CodeBlock.Builder builder =
165-
CodeBlock.builder()
166-
.add("\n\n$T<$T> executeFuture = clientHandler.execute(new $T<$T, $T>()\n",
167-
CompletableFuture.class, executeFutureValueType,
168-
ClientExecutionParams.class, requestType, pojoResponseType)
169-
.add(".withOperationName(\"$N\")\n", opModel.getOperationName())
170-
.add(".withMarshaller($L)\n", asyncMarshaller(intermediateModel, opModel, marshaller, "protocolFactory"))
171-
.add(".withCombinedResponseHandler(responseHandler)\n")
172-
.add(hostPrefixExpression(opModel))
173-
.add(".withMetricCollector(apiCallMetricCollector)\n")
174-
.add(asyncRequestBody(opModel))
175-
.add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel));
185+
String executionResponseTransformerName = "asyncResponseTransformer";
186+
187+
if (opModel.hasEventStreamOutput()) {
188+
executionResponseTransformerName = "restAsyncResponseTransformer";
189+
}
190+
191+
builder.add("\n\n$T<$T> executeFuture = clientHandler.execute(new $T<$T, $T>()\n",
192+
CompletableFuture.class, executeFutureValueType,
193+
ClientExecutionParams.class, requestType, pojoResponseType)
194+
.add(".withOperationName(\"$N\")\n", opModel.getOperationName())
195+
.add(".withMarshaller($L)\n", asyncMarshaller(intermediateModel, opModel, marshaller, "protocolFactory"));
196+
197+
if (opModel.hasEventStreamOutput()) {
198+
builder.add(".withResponseHandler(responseHandler)");
199+
} else {
200+
builder.add(".withCombinedResponseHandler(responseHandler)");
201+
}
202+
203+
builder.add(hostPrefixExpression(opModel))
204+
.add(".withMetricCollector(apiCallMetricCollector)\n")
205+
.add(asyncRequestBody(opModel))
206+
.add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel));
176207

177208
s3ArnableFields(opModel, model).ifPresent(builder::add);
178-
builder.add(".withInput($L) $L);", opModel.getInput().getVariableName(), opModel.hasStreamingOutput() ?
179-
", asyncResponseTransformer" : "");
209+
210+
builder.add(".withInput($L)", opModel.getInput().getVariableName());
211+
if (opModel.hasStreamingOutput() || opModel.hasEventStreamOutput()) {
212+
builder.add(", $N", executionResponseTransformerName);
213+
}
214+
builder.addStatement(")");
215+
180216
String whenCompleteFutureName = "whenCompleteFuture";
181217
builder.addStatement("$T $N = null", ParameterizedTypeName.get(ClassName.get(CompletableFuture.class),
182218
executeFutureValueType), whenCompleteFutureName);
183-
if (opModel.hasStreamingOutput()) {
219+
220+
if (opModel.hasStreamingOutput() || opModel.hasEventStreamOutput()) {
184221
builder.addStatement("$N = executeFuture$L", whenCompleteFutureName,
185-
streamingOutputWhenComplete("asyncResponseTransformer"));
222+
whenCompleteBlock(opModel, "asyncResponseHandler",
223+
eventStreamTransformFutureName));
186224
} else {
187225
builder.addStatement("$N = executeFuture$L", whenCompleteFutureName, publishMetricsWhenComplete());
188226
}
189-
builder.addStatement("return $T.forwardExceptionTo($N, executeFuture)", CompletableFutureUtils.class,
227+
228+
builder.addStatement("$T.forwardExceptionTo($N, executeFuture)", CompletableFutureUtils.class,
190229
whenCompleteFutureName);
230+
231+
if (opModel.hasEventStreamOutput()) {
232+
builder.addStatement("return $T.forwardExceptionTo($N, executeFuture)", CompletableFutureUtils.class,
233+
eventStreamTransformFutureName);
234+
} else {
235+
builder.addStatement("return $N", whenCompleteFutureName);
236+
}
237+
191238
return builder.build();
192239
}
193240

@@ -198,4 +245,122 @@ private String asyncRequestBody(OperationModel opModel) {
198245
private CodeBlock asyncStreamingExecutionHandler(IntermediateModel intermediateModel, OperationModel opModel) {
199246
return super.asyncExecutionHandler(intermediateModel, opModel);
200247
}
248+
249+
private CodeBlock eventStreamResponseHandlers(OperationModel opModel, TypeName pojoResponseType) {
250+
CodeBlock streamResponseOpMd = CodeBlock.builder()
251+
.add("$T.builder()", XmlOperationMetadata.class)
252+
.add(".hasStreamingSuccessResponse(true)")
253+
.add(".build()")
254+
.build();
255+
256+
257+
CodeBlock.Builder builder = CodeBlock.builder();
258+
259+
// Response handler for handling the initial response from the operation. Note, this does not handle the event stream
260+
// messages, that is the job of "eventResponseHandler" below
261+
builder.addStatement("$T<$T> responseHandler = protocolFactory.createResponseHandler($T::builder, $L)",
262+
HttpResponseHandler.class,
263+
pojoResponseType,
264+
pojoResponseType,
265+
streamResponseOpMd);
266+
267+
// Response handler responsible for errors for the API call itself, as well as errors sent over the event stream
268+
builder.addStatement("$T errorResponseHandler = protocolFactory"
269+
+ ".createErrorResponseHandler()", ParameterizedTypeName.get(HttpResponseHandler.class,
270+
AwsServiceException.class));
271+
272+
273+
ShapeModel eventStreamShape = EventStreamUtils.getEventStreamInResponse(opModel.getOutputShape());
274+
ClassName eventStream = poetExtensions.getModelClassFromShape(eventStreamShape);
275+
EventStreamSpecHelper eventStreamSpecHelper = new EventStreamSpecHelper(eventStreamShape, intermediateModel);
276+
277+
CodeBlock.Builder supplierBuilder = CodeBlock.builder()
278+
.add("$T.builder()", EventStreamTaggedUnionPojoSupplier.class);
279+
EventStreamUtils.getEvents(eventStreamShape).forEach(m -> {
280+
String builderName = eventStreamSpecHelper.eventBuilderMethodName(m);
281+
supplierBuilder.add(".putSdkPojoSupplier($S, $T::$N)", m.getName(), eventStream, builderName);
282+
});
283+
supplierBuilder.add(".defaultSdkPojoSupplier(() -> new $T($T.UNKNOWN))", SdkPojoBuilder.class, eventStream);
284+
CodeBlock supplierCodeBlock = supplierBuilder.add(".build()").build();
285+
286+
CodeBlock nonStreamingOpMd = CodeBlock.builder()
287+
.add("$T.builder()", XmlOperationMetadata.class)
288+
.add(".hasStreamingSuccessResponse(false)")
289+
.add(".build()")
290+
.build();
291+
292+
// The response handler responsible for unmarshalling each event
293+
builder.addStatement("$T eventResponseHandler = protocolFactory.createResponseHandler($L, $L)",
294+
ParameterizedTypeName.get(ClassName.get(HttpResponseHandler.class),
295+
WildcardTypeName.subtypeOf(eventStream)),
296+
supplierCodeBlock,
297+
nonStreamingOpMd);
298+
299+
300+
return builder.build();
301+
}
302+
303+
private CodeBlock eventStreamResponseTransformers(OperationModel opModel, String eventTransformerFutureName) {
304+
ShapeModel shapeModel = EventStreamUtils.getEventStreamInResponse(opModel.getOutputShape());
305+
ClassName pojoResponseType = poetExtensions.getModelClass(opModel.getReturnType().getReturnType());
306+
ClassName eventStreamBaseClass = poetExtensions.getModelClassFromShape(shapeModel);
307+
308+
CodeBlock.Builder builder = CodeBlock.builder();
309+
310+
ParameterizedTypeName transformerType = ParameterizedTypeName.get(
311+
ClassName.get(EventStreamAsyncResponseTransformer.class),
312+
pojoResponseType,
313+
eventStreamBaseClass);
314+
315+
builder.addStatement("$1T<$2T> $3N = new $1T<>()", ClassName.get(CompletableFuture.class),
316+
ClassName.get(Void.class), eventTransformerFutureName)
317+
.add("$T asyncResponseTransformer = $T.<$T, $T>builder()\n",
318+
transformerType, ClassName.get(EventStreamAsyncResponseTransformer.class), pojoResponseType,
319+
eventStreamBaseClass)
320+
.add(".eventStreamResponseHandler(asyncResponseHandler)\n")
321+
.add(".eventResponseHandler(eventResponseHandler)\n")
322+
.add(".initialResponseHandler(responseHandler)\n")
323+
.add(".exceptionResponseHandler(errorResponseHandler)\n")
324+
.add(".future($N)\n", eventTransformerFutureName)
325+
.add(".executor(executor)\n")
326+
.add(".serviceName(serviceName())\n")
327+
.addStatement(".build()");
328+
329+
ParameterizedTypeName restTransformType =
330+
ParameterizedTypeName.get(ClassName.get(RestEventStreamAsyncResponseTransformer.class), pojoResponseType,
331+
eventStreamBaseClass);
332+
333+
// Wrap the event transformer with this so that the caller's response handler's onResponse() method is invoked. See
334+
// docs for RestEventStreamAsyncResponseTransformer for more info on why it's needed
335+
builder.addStatement("$T restAsyncResponseTransformer = $T.<$T, $T>builder()\n"
336+
+ ".eventStreamAsyncResponseTransformer(asyncResponseTransformer)\n"
337+
+ ".eventStreamResponseHandler(asyncResponseHandler)\n"
338+
+ ".build()", restTransformType, RestEventStreamAsyncResponseTransformer.class,
339+
pojoResponseType, eventStreamBaseClass);
340+
341+
return builder.build();
342+
}
343+
344+
private CodeBlock whenCompleteBlock(OperationModel operationModel, String responseHandlerName,
345+
String eventTransformerFutureName) {
346+
CodeBlock.Builder whenComplete = CodeBlock.builder()
347+
.add(".whenComplete((r, e) -> ")
348+
.beginControlFlow("")
349+
.beginControlFlow("if (e != null)")
350+
.add("runAndLogError(log, $S, () -> $N.exceptionOccurred(e));",
351+
"Exception thrown in exceptionOccurred callback, ignoring",
352+
responseHandlerName);
353+
354+
if (operationModel.hasEventStreamOutput()) {
355+
whenComplete.add("$N.completeExceptionally(e);", eventTransformerFutureName);
356+
}
357+
358+
whenComplete.endControlFlow()
359+
.add(publishMetrics())
360+
.endControlFlow()
361+
.add(")")
362+
.build();
363+
364+
return whenComplete.build();
365+
}
201366
}

codegen/src/test/resources/software/amazon/awssdk/codegen/poet/client/c2j/xml/service-2.json

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,19 @@
8181
"shape": "StructureWithStreamingMember"
8282
},
8383
"documentation": "Some operation with a streaming output"
84+
},
85+
"EventStreamOperation": {
86+
"name": "EventStreamOperation",
87+
"http": {
88+
"method": "POST",
89+
"requestUri": "/2016-03-11/eventStreamOperation"
90+
},
91+
"input": {
92+
"shape": "EventStreamOperationRequest"
93+
},
94+
"output": {
95+
"shape": "EventStreamOutput"
96+
}
8497
}
8598
},
8699
"shapes": {
@@ -162,6 +175,9 @@
162175
},
163176
"documentation": "<p>A shape with nested sub-members"
164177
},
178+
"String": {
179+
"type": "string"
180+
},
165181
"subMember": {
166182
"type": "string",
167183
"max": 63,
@@ -187,6 +203,61 @@
187203
}
188204
},
189205
"payload": "StreamingMember"
206+
},
207+
"EventStreamOperationRequest": {
208+
"type": "structure",
209+
"members": {
210+
}
211+
},
212+
"EventStreamOutput": {
213+
"type": "structure",
214+
"required": [
215+
"EventStream"
216+
],
217+
"members": {
218+
"HeaderMember": {
219+
"shape": "String",
220+
"location": "header",
221+
"locationName": "Header-Member"
222+
},
223+
"EventStream": {
224+
"shape": "EventStream"
225+
}
226+
}
227+
},
228+
"EventStream": {
229+
"type": "structure",
230+
"members": {
231+
"EventPayloadEvent": {
232+
"shape": "EventPayloadEvent"
233+
},
234+
"NonEventPayloadEvent": {
235+
"shape": "NonEventPayloadEvent"
236+
},
237+
"SecondEventPayloadEvent": {
238+
"shape": "EventPayloadEvent"
239+
}
240+
},
241+
"eventstream": true
242+
},
243+
"EventPayloadEvent": {
244+
"type": "structure",
245+
"members": {
246+
"Foo": {
247+
"shape": "String",
248+
"eventpayload": true
249+
}
250+
},
251+
"event": true
252+
},
253+
"NonEventPayloadEvent": {
254+
"type": "structure",
255+
"members": {
256+
"Bar": {
257+
"shape": "String"
258+
}
259+
},
260+
"event": true
190261
}
191262
},
192263
"documentation": "A service that is implemented using the xml protocol"

0 commit comments

Comments
 (0)