Skip to content

Commit 56fc6ad

Browse files
committed
Support S3 SelectObjectContent
This adds support for S3's SelectObjectContent operation by enabling operations with EventStreams as output for the XML protocol.
1 parent abe96fd commit 56fc6ad

File tree

15 files changed

+1147
-126
lines changed

15 files changed

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

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

Lines changed: 179 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

@@ -160,34 +174,64 @@ public CodeBlock asyncExecutionHandler(IntermediateModel intermediateModel, Oper
160174
ClassName requestType = poetExtensions.getModelClass(opModel.getInput().getVariableType());
161175
ClassName marshaller = poetExtensions.getRequestTransformClass(opModel.getInputShape().getShapeName() + "Marshaller");
162176

177+
CodeBlock.Builder builder = CodeBlock.builder();
178+
179+
if (opModel.hasEventStreamOutput()) {
180+
builder.add(eventStreamResponseTransformers(opModel));
181+
}
182+
163183
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));
184+
String executionResponseTransformerName = "asyncResponseTransformer";
185+
186+
if (opModel.hasEventStreamOutput()) {
187+
executionResponseTransformerName = "restAsyncResponseTransformer";
188+
}
189+
190+
builder.add("\n\n$T<$T> executeFuture = clientHandler.execute(new $T<$T, $T>()\n",
191+
CompletableFuture.class, executeFutureValueType,
192+
ClientExecutionParams.class, requestType, pojoResponseType)
193+
.add(".withOperationName(\"$N\")\n", opModel.getOperationName())
194+
.add(".withMarshaller($L)\n", asyncMarshaller(intermediateModel, opModel, marshaller, "protocolFactory"));
195+
196+
if (opModel.hasEventStreamOutput()) {
197+
builder.add(".withResponseHandler(responseHandler)");
198+
} else {
199+
builder.add(".withCombinedResponseHandler(responseHandler)");
200+
}
201+
202+
builder.add(hostPrefixExpression(opModel))
203+
.add(".withMetricCollector(apiCallMetricCollector)\n")
204+
.add(asyncRequestBody(opModel))
205+
.add(HttpChecksumRequiredTrait.putHttpChecksumAttribute(opModel));
176206

177207
s3ArnableFields(opModel, model).ifPresent(builder::add);
178-
builder.add(".withInput($L) $L);", opModel.getInput().getVariableName(), opModel.hasStreamingOutput() ?
179-
", asyncResponseTransformer" : "");
208+
209+
builder.add(".withInput($L)", opModel.getInput().getVariableName());
210+
if (opModel.hasStreamingOutput() || opModel.hasEventStreamOutput()) {
211+
builder.add(", $N", executionResponseTransformerName);
212+
}
213+
builder.addStatement(")");
214+
180215
String whenCompleteFutureName = "whenCompleteFuture";
181216
builder.addStatement("$T $N = null", ParameterizedTypeName.get(ClassName.get(CompletableFuture.class),
182217
executeFutureValueType), whenCompleteFutureName);
183-
if (opModel.hasStreamingOutput()) {
218+
219+
if (opModel.hasStreamingOutput() || opModel.hasEventStreamOutput()) {
184220
builder.addStatement("$N = executeFuture$L", whenCompleteFutureName,
185-
streamingOutputWhenComplete("asyncResponseTransformer"));
221+
whenCompleteBlock(opModel, "asyncResponseHandler"));
186222
} else {
187223
builder.addStatement("$N = executeFuture$L", whenCompleteFutureName, publishMetricsWhenComplete());
188224
}
189-
builder.addStatement("return $T.forwardExceptionTo($N, executeFuture)", CompletableFutureUtils.class,
225+
226+
builder.addStatement("$T.forwardExceptionTo($N, executeFuture)", CompletableFutureUtils.class,
190227
whenCompleteFutureName);
228+
229+
if (opModel.hasEventStreamOutput()) {
230+
builder.addStatement("return $T.forwardExceptionTo(future, executeFuture)", CompletableFutureUtils.class);
231+
} else {
232+
builder.addStatement("return $N", whenCompleteFutureName);
233+
}
234+
191235
return builder.build();
192236
}
193237

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

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)