
software.amazon.awssdk.codegen.poet.client.SyncClientInterface Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package software.amazon.awssdk.codegen.poet.client;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static javax.lang.model.element.Modifier.DEFAULT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
import static software.amazon.awssdk.codegen.internal.Constant.SYNC_CLIENT_DESTINATION_PATH_PARAM_NAME;
import static software.amazon.awssdk.codegen.internal.Constant.SYNC_CLIENT_SOURCE_PATH_PARAM_NAME;
import static software.amazon.awssdk.codegen.internal.Constant.SYNC_STREAMING_INPUT_PARAM;
import static software.amazon.awssdk.codegen.internal.Constant.SYNC_STREAMING_OUTPUT_PARAM;
import static software.amazon.awssdk.codegen.poet.client.AsyncClientInterface.STREAMING_TYPE_VARIABLE;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.annotations.ThreadSafe;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.awscore.AwsClient;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.codegen.docs.ClientType;
import software.amazon.awssdk.codegen.docs.DocConfiguration;
import software.amazon.awssdk.codegen.docs.SimpleMethodOverload;
import software.amazon.awssdk.codegen.docs.WaiterDocs;
import software.amazon.awssdk.codegen.model.config.customization.UtilitiesMethod;
import software.amazon.awssdk.codegen.model.intermediate.IntermediateModel;
import software.amazon.awssdk.codegen.model.intermediate.OperationModel;
import software.amazon.awssdk.codegen.poet.ClassSpec;
import software.amazon.awssdk.codegen.poet.PoetExtension;
import software.amazon.awssdk.codegen.poet.PoetUtils;
import software.amazon.awssdk.codegen.poet.model.DeprecationUtils;
import software.amazon.awssdk.codegen.utils.PaginatorUtils;
import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.core.sync.ResponseTransformer;
import software.amazon.awssdk.regions.ServiceMetadata;
import software.amazon.awssdk.regions.ServiceMetadataProvider;
import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
public class SyncClientInterface implements ClassSpec {
private final IntermediateModel model;
private final ClassName className;
private final String clientPackageName;
private final PoetExtension poetExtensions;
public SyncClientInterface(IntermediateModel model) {
this.model = model;
this.clientPackageName = model.getMetadata().getFullClientPackageName();
this.className = ClassName.get(clientPackageName, model.getMetadata().getSyncInterface());
this.poetExtensions = new PoetExtension(model);
}
@Override
public final TypeSpec poetSpec() {
TypeSpec.Builder result = createTypeSpec();
addInterfaceClass(result);
addAnnotations(result);
addModifiers(result);
addFields(result);
result.addMethods(operations());
if (model.getCustomizationConfig().getUtilitiesMethod() != null) {
result.addMethod(utilitiesMethod());
}
if (model.hasWaiters()) {
result.addMethod(waiterMethod());
}
addAdditionalMethods(result);
result.addMethod(serviceClientConfigMethod());
addCloseMethod(result);
return result.build();
}
protected void addInterfaceClass(TypeSpec.Builder type) {
type.addSuperinterface(AwsClient.class);
}
protected TypeSpec.Builder createTypeSpec() {
return PoetUtils.createInterfaceBuilder(className);
}
protected void addAnnotations(TypeSpec.Builder type) {
type.addAnnotation(SdkPublicApi.class)
.addAnnotation(ThreadSafe.class);
}
protected void addModifiers(TypeSpec.Builder type) {
}
protected void addCloseMethod(TypeSpec.Builder type) {
}
protected void addFields(TypeSpec.Builder type) {
type.addField(FieldSpec.builder(String.class, "SERVICE_NAME")
.addModifiers(PUBLIC, STATIC, FINAL)
.initializer("$S", model.getMetadata().getSigningName())
.build())
.addField(FieldSpec.builder(String.class, "SERVICE_METADATA_ID")
.addModifiers(PUBLIC, STATIC, FINAL)
.initializer("$S", model.getMetadata().getEndpointPrefix())
.addJavadoc("Value for looking up the service's metadata from the {@link $T}.",
ServiceMetadataProvider.class)
.build());
}
protected void addAdditionalMethods(TypeSpec.Builder type) {
if (!model.getCustomizationConfig().isExcludeClientCreateMethod()) {
type.addMethod(create());
}
type.addMethod(builder())
.addMethod(serviceMetadata());
PoetUtils.addJavadoc(type::addJavadoc, getJavadoc());
}
@Override
public ClassName className() {
return className;
}
private String getJavadoc() {
return "Service client for accessing " + model.getMetadata().getDescriptiveServiceName() + ". This can be "
+ "created using the static {@link #builder()} method.\n\n" + model.getMetadata().getDocumentation();
}
private MethodSpec create() {
return MethodSpec.methodBuilder("create")
.returns(className)
.addModifiers(STATIC, PUBLIC)
.addJavadoc(
"Create a {@link $T} with the region loaded from the {@link $T} and credentials loaded from the "
+ "{@link $T}.", className, DefaultAwsRegionProviderChain.class,
DefaultCredentialsProvider.class)
.addStatement("return builder().build()")
.build();
}
private MethodSpec builder() {
ClassName builderClass = ClassName.get(clientPackageName, model.getMetadata().getSyncBuilder());
ClassName builderInterface = ClassName.get(clientPackageName, model.getMetadata().getSyncBuilderInterface());
return MethodSpec.methodBuilder("builder")
.returns(builderInterface)
.addModifiers(STATIC, PUBLIC)
.addJavadoc("Create a builder that can be used to configure and create a {@link $T}.", className)
.addStatement("return new $T()", builderClass)
.build();
}
private MethodSpec serviceMetadata() {
return MethodSpec.methodBuilder("serviceMetadata")
.returns(ServiceMetadata.class)
.addModifiers(STATIC, PUBLIC)
.addStatement("return $T.of(SERVICE_METADATA_ID)", ServiceMetadata.class)
.build();
}
protected Iterable operations() {
return model.getOperations().values().stream()
// TODO Sync not supported for event streaming yet. Revisit after sync/async merge
.filter(o -> !o.hasEventStreamInput())
.filter(o -> !o.hasEventStreamOutput())
.flatMap(this::operationsWithVariants)
.collect(toList());
}
private Stream operationsWithVariants(OperationModel opModel) {
List methods = new ArrayList<>();
methods.addAll(traditionalMethodWithConsumerVariant(opModel));
methods.addAll(overloadedMethods(opModel));
methods.addAll(paginatedMethods(opModel));
return methods.stream()
// Add Deprecated annotation if needed to all overloads
.map(m -> DeprecationUtils.checkDeprecated(opModel, m));
}
private List traditionalMethodWithConsumerVariant(OperationModel opModel) {
List methods = new ArrayList<>();
MethodSpec.Builder builder = operationMethodSignature(model, opModel);
MethodSpec method = operationBody(builder, opModel).build();
methods.add(method);
addConsumerMethod(methods, method, SimpleMethodOverload.NORMAL, opModel);
return methods;
}
private static MethodSpec.Builder operationBaseSignature(IntermediateModel model,
OperationModel opModel,
Consumer addFirstParameter,
SimpleMethodOverload simpleMethodOverload,
String methodName) {
TypeName responseType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getReturnType().getReturnType());
TypeName returnType = opModel.hasStreamingOutput() ? STREAMING_TYPE_VARIABLE : responseType;
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName)
.returns(returnType)
.addModifiers(PUBLIC)
.addJavadoc(opModel.getDocs(model, ClientType.SYNC,
simpleMethodOverload))
.addExceptions(getExceptionClasses(model, opModel));
addFirstParameter.accept(methodBuilder);
streamingMethod(methodBuilder, opModel, responseType);
return methodBuilder;
}
protected MethodSpec.Builder operationBody(MethodSpec.Builder builder, OperationModel opModel) {
return builder.addModifiers(DEFAULT)
.addStatement("throw new $T()", UnsupportedOperationException.class);
}
static MethodSpec.Builder operationMethodSignature(IntermediateModel model,
OperationModel opModel) {
return operationMethodSignature(model, opModel, SimpleMethodOverload.NORMAL, opModel.getMethodName());
}
// TODO This is inconsistent with how async client reuses method signature
static MethodSpec.Builder operationMethodSignature(IntermediateModel model,
OperationModel opModel,
SimpleMethodOverload simpleMethodOverload,
String methodName) {
ClassName requestType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getInput().getVariableType());
return operationBaseSignature(model, opModel, b -> b.addParameter(requestType, opModel.getInput().getVariableName()),
simpleMethodOverload, methodName);
}
private MethodSpec.Builder operationSimpleMethodSignature(IntermediateModel model,
OperationModel opModel,
String methodName) {
TypeName returnType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getReturnType().getReturnType());
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
.returns(returnType)
.addModifiers(PUBLIC)
.addExceptions(getExceptionClasses(model, opModel));
return simpleMethodModifier(builder);
}
protected List paginatedMethods(OperationModel opModel) {
List paginatedMethodSpecs = new ArrayList<>();
if (opModel.isPaginated()) {
if (opModel.getInputShape().isSimpleMethod()) {
paginatedMethodSpecs.add(paginatedSimpleMethod(opModel));
}
MethodSpec.Builder paginatedMethodBuilder =
operationMethodSignature(model,
opModel,
SimpleMethodOverload.PAGINATED,
PaginatorUtils.getPaginatedMethodName(opModel.getMethodName()))
.returns(poetExtensions.getResponseClassForPaginatedSyncOperation(opModel.getOperationName()));
MethodSpec paginatedMethod = paginatedMethodBody(paginatedMethodBuilder, opModel).build();
paginatedMethodSpecs.add(paginatedMethod);
addConsumerMethod(paginatedMethodSpecs, paginatedMethod, SimpleMethodOverload.PAGINATED, opModel);
}
return paginatedMethodSpecs;
}
private MethodSpec paginatedSimpleMethod(OperationModel opModel) {
String paginatedMethodName = PaginatorUtils.getPaginatedMethodName(opModel.getMethodName());
ClassName requestType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getInput().getVariableType());
return operationSimpleMethodSignature(model, opModel, paginatedMethodName)
.returns(poetExtensions.getResponseClassForPaginatedSyncOperation(opModel.getOperationName()))
.addStatement("return $L($T.builder().build())", paginatedMethodName, requestType)
.addJavadoc(opModel.getDocs(model, ClientType.SYNC, SimpleMethodOverload.NO_ARG_PAGINATED))
.build();
}
protected MethodSpec.Builder paginatedMethodBody(MethodSpec.Builder builder, OperationModel operationModel) {
return builder.addModifiers(DEFAULT, PUBLIC)
.addStatement("return new $T(this, $L)",
poetExtensions.getResponseClassForPaginatedSyncOperation(operationModel.getOperationName()),
operationModel.getInput().getVariableName());
}
private static void streamingMethod(MethodSpec.Builder methodBuilder, OperationModel opModel, TypeName responseType) {
if (opModel.hasStreamingInput()) {
methodBuilder.addParameter(ClassName.get(RequestBody.class), SYNC_STREAMING_INPUT_PARAM);
}
if (opModel.hasStreamingOutput()) {
methodBuilder.addTypeVariable(STREAMING_TYPE_VARIABLE);
ParameterizedTypeName streamingResponseHandlerType = ParameterizedTypeName
.get(ClassName.get(ResponseTransformer.class), responseType, STREAMING_TYPE_VARIABLE);
methodBuilder.addParameter(streamingResponseHandlerType, SYNC_STREAMING_OUTPUT_PARAM);
}
}
/**
* @param opModel Operation to generate simple methods for.
* @return All simple method overloads for a given operation.
*/
private List overloadedMethods(OperationModel opModel) {
TypeName responseType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getReturnType().getReturnType());
ClassName requestType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getInput().getVariableType());
List simpleMethods = new ArrayList<>();
if (opModel.getInputShape().isSimpleMethod()) {
simpleMethods.add(simpleMethodWithNoArgs(opModel));
}
if (opModel.hasStreamingInput() && opModel.hasStreamingOutput()) {
MethodSpec simpleMethod = streamingInputOutputFileSimpleMethod(opModel, responseType, requestType);
simpleMethods.add(simpleMethod);
addConsumerMethod(simpleMethods, simpleMethod, SimpleMethodOverload.FILE, opModel);
} else if (opModel.hasStreamingInput()) {
MethodSpec simpleMethod = uploadFromFileSimpleMethod(opModel, responseType, requestType);
simpleMethods.add(simpleMethod);
addConsumerMethod(simpleMethods, simpleMethod, SimpleMethodOverload.FILE, opModel);
} else if (opModel.hasStreamingOutput()) {
MethodSpec downloadToFileSimpleMethod = downloadToFileSimpleMethod(opModel, responseType, requestType);
MethodSpec inputStreamSimpleMethod = inputStreamSimpleMethod(opModel, responseType, requestType);
MethodSpec bytesSimpleMethod = bytesSimpleMethod(opModel, responseType, requestType);
simpleMethods.add(downloadToFileSimpleMethod);
addConsumerMethod(simpleMethods, downloadToFileSimpleMethod, SimpleMethodOverload.FILE, opModel);
simpleMethods.add(inputStreamSimpleMethod);
addConsumerMethod(simpleMethods, inputStreamSimpleMethod, SimpleMethodOverload.INPUT_STREAM, opModel);
simpleMethods.add(bytesSimpleMethod);
addConsumerMethod(simpleMethods, bytesSimpleMethod, SimpleMethodOverload.BYTES, opModel);
}
return simpleMethods;
}
private MethodSpec simpleMethodWithNoArgs(OperationModel opModel) {
ClassName requestType = ClassName.get(model.getMetadata().getFullModelPackageName(),
opModel.getInput().getVariableType());
return operationSimpleMethodSignature(model, opModel, opModel.getMethodName())
.addStatement("return $L($T.builder().build())", opModel.getMethodName(), requestType)
.addJavadoc(opModel.getDocs(model, ClientType.SYNC, SimpleMethodOverload.NO_ARG))
.build();
}
protected void addConsumerMethod(List specs, MethodSpec spec, SimpleMethodOverload overload,
OperationModel opModel) {
String fileConsumerBuilderJavadoc = consumerBuilderJavadoc(opModel, overload);
specs.add(ClientClassUtils.consumerBuilderVariant(spec, fileConsumerBuilderJavadoc));
}
/**
* @return Simple method for streaming input operations to read data from a file.
*/
private MethodSpec uploadFromFileSimpleMethod(OperationModel opModel, TypeName responseType, ClassName requestType) {
String methodName = opModel.getMethodName();
ParameterSpec inputVarParam = ParameterSpec.builder(requestType, opModel.getInput().getVariableName()).build();
ParameterSpec srcPathParam = ParameterSpec.builder(ClassName.get(Path.class),
SYNC_CLIENT_SOURCE_PATH_PARAM_NAME).build();
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
.returns(responseType)
.addModifiers(PUBLIC)
.addParameter(inputVarParam)
.addParameter(srcPathParam)
.addJavadoc(opModel.getDocs(model, ClientType.SYNC, SimpleMethodOverload.FILE))
.addExceptions(getExceptionClasses(model, opModel))
.addStatement("return $L($N, $T.fromFile($N))", methodName,
inputVarParam,
ClassName.get(RequestBody.class),
srcPathParam);
return simpleMethodModifier(builder).build();
}
/**
* @return Simple method for streaming output operations to get content as an input stream.
*/
private MethodSpec inputStreamSimpleMethod(OperationModel opModel, TypeName responseType, ClassName requestType) {
TypeName returnType = ParameterizedTypeName.get(ClassName.get(ResponseInputStream.class), responseType);
MethodSpec.Builder builder = MethodSpec.methodBuilder(opModel.getMethodName())
.returns(returnType)
.addModifiers(PUBLIC)
.addParameter(requestType, opModel.getInput().getVariableName())
.addJavadoc(opModel.getDocs(model, ClientType.SYNC,
SimpleMethodOverload.INPUT_STREAM))
.addExceptions(getExceptionClasses(model, opModel))
.addStatement("return $L($L, $T.toInputStream())", opModel.getMethodName(),
opModel.getInput().getVariableName(),
ClassName.get(ResponseTransformer.class));
return simpleMethodModifier(builder).build();
}
/**
* @return Simple method for streaming output operations to get the content as a byte buffer or other in-memory types.
*/
private MethodSpec bytesSimpleMethod(OperationModel opModel, TypeName responseType, ClassName requestType) {
TypeName returnType = ParameterizedTypeName.get(ClassName.get(ResponseBytes.class), responseType);
MethodSpec.Builder builder = MethodSpec.methodBuilder(opModel.getMethodName() + "AsBytes")
.returns(returnType)
.addModifiers(PUBLIC)
.addParameter(requestType, opModel.getInput().getVariableName())
.addJavadoc(opModel.getDocs(model, ClientType.SYNC, SimpleMethodOverload.BYTES))
.addExceptions(getExceptionClasses(model, opModel))
.addStatement("return $L($L, $T.toBytes())", opModel.getMethodName(),
opModel.getInput().getVariableName(),
ClassName.get(ResponseTransformer.class));
return simpleMethodModifier(builder).build();
}
/**
* @return Simple method for streaming output operations to write response content to a file.
*/
private MethodSpec downloadToFileSimpleMethod(OperationModel opModel, TypeName responseType, ClassName requestType) {
String methodName = opModel.getMethodName();
ParameterSpec inputVarParam = ParameterSpec.builder(requestType, opModel.getInput().getVariableName()).build();
ParameterSpec dstFileParam =
ParameterSpec.builder(ClassName.get(Path.class), SYNC_CLIENT_DESTINATION_PATH_PARAM_NAME).build();
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
.returns(responseType)
.addModifiers(PUBLIC)
.addParameter(inputVarParam)
.addParameter(dstFileParam)
.addJavadoc(opModel.getDocs(model, ClientType.SYNC, SimpleMethodOverload.FILE))
.addExceptions(getExceptionClasses(model, opModel))
.addStatement("return $L($N, $T.toFile($N))", methodName,
inputVarParam,
ClassName.get(ResponseTransformer.class),
dstFileParam);
return simpleMethodModifier(builder).build();
}
/**
* Generate a simple method for operations with streaming input and output members.
* Streaming input member that reads data from a file and a streaming output member that write response content to a file.
*/
private MethodSpec streamingInputOutputFileSimpleMethod(OperationModel opModel,
TypeName responseType,
ClassName requestType) {
String methodName = opModel.getMethodName();
ParameterSpec inputVarParam = ParameterSpec.builder(requestType, opModel.getInput().getVariableName()).build();
ParameterSpec srcFileParam = ParameterSpec.builder(ClassName.get(Path.class), SYNC_CLIENT_SOURCE_PATH_PARAM_NAME).build();
ParameterSpec dstFileParam =
ParameterSpec.builder(ClassName.get(Path.class), SYNC_CLIENT_DESTINATION_PATH_PARAM_NAME).build();
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
.returns(responseType)
.addModifiers(PUBLIC)
.addParameter(inputVarParam)
.addParameter(srcFileParam)
.addParameter(dstFileParam)
.addJavadoc(opModel.getDocs(model, ClientType.SYNC, SimpleMethodOverload.FILE))
.addExceptions(getExceptionClasses(model, opModel))
.addStatement("return $L($N, $T.fromFile($N), $T.toFile($N))",
methodName,
inputVarParam,
ClassName.get(RequestBody.class), srcFileParam,
ClassName.get(ResponseTransformer.class),
dstFileParam);
return simpleMethodModifier(builder).build();
}
protected MethodSpec.Builder simpleMethodModifier(MethodSpec.Builder builder) {
return builder.addModifiers(DEFAULT);
}
private static List getExceptionClasses(IntermediateModel model, OperationModel opModel) {
List exceptions = opModel.getExceptions().stream()
.map(e -> ClassName.get(model.getMetadata().getFullModelPackageName(),
e.getExceptionName()))
.collect(toCollection(ArrayList::new));
Collections.addAll(exceptions, ClassName.get(AwsServiceException.class),
ClassName.get(SdkClientException.class),
ClassName.get(model.getMetadata().getFullModelPackageName(),
model.getSdkModeledExceptionBaseClassName()));
return exceptions;
}
private String consumerBuilderJavadoc(OperationModel opModel, SimpleMethodOverload overload) {
return opModel.getDocs(model, ClientType.SYNC, overload, new DocConfiguration().isConsumerBuilder(true));
}
protected MethodSpec utilitiesMethod() {
UtilitiesMethod config = model.getCustomizationConfig().getUtilitiesMethod();
ClassName returnType = PoetUtils.classNameFromFqcn(config.getReturnType());
MethodSpec.Builder builder = MethodSpec.methodBuilder(UtilitiesMethod.METHOD_NAME)
.returns(returnType)
.addModifiers(PUBLIC)
.addJavadoc("Creates an instance of {@link $T} object with the "
+ "configuration set on this client.", returnType);
return utilitiesOperationBody(builder).build();
}
protected MethodSpec serviceClientConfigMethod() {
return MethodSpec.methodBuilder("serviceClientConfiguration")
.addAnnotation(Override.class)
.addModifiers(PUBLIC, DEFAULT)
.addStatement("throw new $T()", UnsupportedOperationException.class)
.returns(new PoetExtension(model).getServiceConfigClass())
.build();
}
protected MethodSpec.Builder utilitiesOperationBody(MethodSpec.Builder builder) {
return builder.addModifiers(DEFAULT).addStatement("throw new $T()", UnsupportedOperationException.class);
}
protected MethodSpec waiterMethod() {
MethodSpec.Builder builder = MethodSpec.methodBuilder("waiter")
.addModifiers(PUBLIC)
.returns(poetExtensions.getSyncWaiterInterface())
.addJavadoc(WaiterDocs.waiterMethodInClient(poetExtensions
.getSyncWaiterInterface()));
return waiterOperationBody(builder).build();
}
protected MethodSpec.Builder waiterOperationBody(MethodSpec.Builder builder) {
return builder.addModifiers(DEFAULT, PUBLIC)
.addStatement("throw new $T()", UnsupportedOperationException.class);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy