AUTH_CONFIG = name -> CONFIG_METADATA.apply(AUTH_HEADER, name);
-
private final SdkClientConfiguration clientConfig;
public ApplyUserAgentStage(HttpClientDependencies dependencies) {
this.clientConfig = dependencies.clientConfiguration();
}
- public static String resolveClientUserAgent(String userAgentPrefix,
- String internalUserAgent,
- ClientType clientType,
- SdkHttpClient syncHttpClient,
- SdkAsyncHttpClient asyncHttpClient,
- String retryMode) {
- String awsExecutionEnvironment = SdkSystemSetting.AWS_EXECUTION_ENV.getStringValue().orElse(null);
-
- StringBuilder userAgent = new StringBuilder(128);
-
- userAgent.append(StringUtils.trimToEmpty(userAgentPrefix));
-
- String systemUserAgent = SdkUserAgent.create().userAgent();
- if (!systemUserAgent.equals(userAgentPrefix)) {
- userAgent.append(COMMA).append(systemUserAgent);
- }
-
- String trimmedInternalUserAgent = StringUtils.trimToEmpty(internalUserAgent);
- if (!trimmedInternalUserAgent.isEmpty()) {
- userAgent.append(SPACE).append(trimmedInternalUserAgent);
- }
-
- if (!StringUtils.isEmpty(awsExecutionEnvironment)) {
- userAgent.append(SPACE).append(AWS_EXECUTION_ENV_PREFIX).append(awsExecutionEnvironment.trim());
- }
-
- if (clientType == null) {
- clientType = ClientType.UNKNOWN;
- }
-
- userAgent.append(SPACE)
- .append(IO)
- .append("/")
- .append(StringUtils.lowerCase(clientType.name()));
-
- userAgent.append(SPACE)
- .append(HTTP)
- .append("/")
- .append(SdkHttpUtils.urlEncode(clientName(clientType, syncHttpClient, asyncHttpClient)))
- .append(SPACE)
- .append(CONFIG)
- .append("/")
- .append(RETRY_MODE)
- .append("/")
- .append(StringUtils.lowerCase(retryMode));
-
- return userAgent.toString();
- }
-
@Override
- public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request, RequestExecutionContext context)
- throws Exception {
- return request.putHeader(HEADER_USER_AGENT, getUserAgent(clientConfig, context));
+ public SdkHttpFullRequest.Builder execute(SdkHttpFullRequest.Builder request,
+ RequestExecutionContext context) throws Exception {
+ String headerValue = finalizeUserAgent(context);
+ return request.putHeader(HEADER_USER_AGENT, headerValue);
}
- private String getUserAgent(SdkClientConfiguration config, RequestExecutionContext context) {
+ /**
+ * The final value sent in the user agent header consists of
+ *
+ * - an optional user provided prefix
+ * - SDK user agent values (governed by a common specification)
+ * - an optional set of API names, expressed as name/version pairs
+ * - an optional user provided suffix
+ *
+ *
+ * In general, usage of the optional values is discouraged since they do not follow a specification and can make
+ * the user agent too long.
+ *
+ * The SDK user agent values are constructed from static system values, client level values and request level
+ * values. This method adds request level values directly after the retrieved SDK client user agent string.
+ */
+ private String finalizeUserAgent(RequestExecutionContext context) {
String clientUserAgent = clientConfig.option(SdkClientOption.CLIENT_USER_AGENT);
if (clientUserAgent == null) {
log.warn(() -> "Client user agent configuration is missing, so request user agent will be incomplete.");
clientUserAgent = "";
}
- StringBuilder userAgent = new StringBuilder(clientUserAgent);
- //additional cfg information
- identityProviderName(context.executionAttributes())
- .ifPresent(providerName -> userAgent.append(SPACE).append(AUTH_CONFIG.apply(providerName)));
+ StringBuilder javaUserAgent = new StringBuilder();
+
+ String userPrefix = trim(clientConfig.option(SdkAdvancedClientOption.USER_AGENT_PREFIX));
+ if (!StringUtils.isEmpty(userPrefix)) {
+ javaUserAgent.append(userPrefix).append(SPACE);
+ }
+
+ javaUserAgent.append(clientUserAgent);
+
+ //add remaining SDK user agent properties
+ identityProviderName(context.executionAttributes()).ifPresent(
+ authSource -> appendSpaceAndField(javaUserAgent, CONFIG_METADATA, uaPair(AUTH_SOURCE, authSource)));
- //request API names
- requestApiNames(context.requestConfig().apiNames()).ifPresent(userAgent::append);
+ //treat ApiNames as an opaque set of values because it may contain user values
+ Optional apiNames = requestApiNames(context.requestConfig().apiNames());
+ apiNames.ifPresent(javaUserAgent::append);
- //suffix
- String userDefinedSuffix = config.option(SdkAdvancedClientOption.USER_AGENT_SUFFIX);
- if (!StringUtils.isEmpty(userDefinedSuffix)) {
- userAgent.append(COMMA).append(userDefinedSuffix.trim());
+ String userSuffix = trim(clientConfig.option(SdkAdvancedClientOption.USER_AGENT_SUFFIX));
+ if (!StringUtils.isEmpty(userSuffix)) {
+ javaUserAgent.append(SPACE).append(userSuffix);
}
- return userAgent.toString();
+ return javaUserAgent.toString();
}
private static Optional identityProviderName(ExecutionAttributes executionAttributes) {
@@ -170,26 +130,24 @@ private static Optional providerNameFromIdentity(Se
return identity.providerName().flatMap(IdentityProviderNameMapping::mapFrom);
}
+ /**
+ * This structure is used for external users as well as for internal tracking of features.
+ * It's not governed by a specification.
+ * Internal usage should be migrated to business metrics or another designated metadata field,
+ * leaving these values to be completely user-set, in which case the result would in most cases be empty.
+ *
+ * Currently tracking these SDK values (remove from list as they're migrated):
+ * PAGINATED/sdk-version, hll/s3Multipart, hll/ddb-enh, hll/cw-mp, hll/waiter, hll/cross-region, ft/s3-transfer
+ */
private Optional requestApiNames(List requestApiNames) {
if (requestApiNames.isEmpty()) {
return Optional.empty();
}
StringBuilder concatenatedNames = new StringBuilder();
requestApiNames.forEach(apiName -> concatenatedNames.append(SPACE)
- .append(API_NAMES.apply(apiName.name(),
- apiName.version())));
+ .append(apiName.name())
+ .append(SLASH)
+ .append(apiName.version()));
return Optional.of(concatenatedNames.toString());
}
-
- private static String clientName(ClientType clientType, SdkHttpClient syncHttpClient, SdkAsyncHttpClient asyncHttpClient) {
- if (clientType == ClientType.SYNC) {
- return syncHttpClient == null ? "null" : syncHttpClient.clientName();
- }
-
- if (clientType == ClientType.ASYNC) {
- return asyncHttpClient == null ? "null" : asyncHttpClient.clientName();
- }
-
- return ClientType.UNKNOWN.name();
- }
}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/DefaultSystemUserAgent.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/DefaultSystemUserAgent.java
new file mode 100644
index 000000000000..cac3777ed80b
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/DefaultSystemUserAgent.java
@@ -0,0 +1,146 @@
+/*
+ * 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.core.internal.useragent;
+
+import static software.amazon.awssdk.core.internal.useragent.SdkUserAgentBuilder.buildSystemUserAgentString;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.sanitizeInput;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.uaPair;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentLangValues.getAdditionalJvmLanguages;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.SdkSystemSetting;
+import software.amazon.awssdk.core.util.SystemUserAgent;
+import software.amazon.awssdk.core.util.VersionInfo;
+import software.amazon.awssdk.utils.JavaSystemSetting;
+import software.amazon.awssdk.utils.SystemSetting;
+
+/**
+ * Common system level user agent properties that can either be accessed as a string or as individual values.
+ * The former is useful when making generic calls, for instance to local endpoints when resolving identity, while
+ * the latter is when incorporating this information into a user agent header in an SDK request.
+ */
+@ThreadSafe
+@SdkProtectedApi
+public final class DefaultSystemUserAgent implements SystemUserAgent {
+
+ private static volatile DefaultSystemUserAgent instance;
+
+ private final String sdkVersion;
+ private final String osMetadata;
+ private final String langMetadata;
+ private final String envMetadata;
+ private final String vmMetadata;
+ private final String vendorMetadata;
+ private final Optional languageTagMetadata;
+ private final List additionalJvmLanguages;
+ private final String systemUserAgent;
+
+ private DefaultSystemUserAgent() {
+ sdkVersion = VersionInfo.SDK_VERSION;
+ osMetadata = uaPair(systemSetting(JavaSystemSetting.OS_NAME), systemSetting(JavaSystemSetting.OS_VERSION));
+ langMetadata = uaPair("java", systemSetting(JavaSystemSetting.JAVA_VERSION));
+ envMetadata = systemSetting(SdkSystemSetting.AWS_EXECUTION_ENV);
+ vmMetadata = uaPair(systemSetting(JavaSystemSetting.JAVA_VM_NAME), systemSetting(JavaSystemSetting.JAVA_VM_VERSION));
+ vendorMetadata = uaPair("vendor", systemSetting(JavaSystemSetting.JAVA_VENDOR));
+ languageTagMetadata = getLanguageTagMetadata();
+ additionalJvmLanguages = getAdditionalJvmLanguages();
+ systemUserAgent = getUserAgent();
+ }
+
+ public static DefaultSystemUserAgent getOrCreate() {
+ if (instance == null) {
+ synchronized (DefaultSystemUserAgent.class) {
+ if (instance == null) {
+ instance = new DefaultSystemUserAgent();
+ }
+ }
+ }
+
+ return instance;
+ }
+
+ /**
+ * A generic user agent string to be used when communicating with backend services.
+ * This string contains Java, OS and region information but does not contain client and request
+ * specific values.
+ */
+ @Override
+ public String userAgentString() {
+ return systemUserAgent;
+ }
+
+ @Override
+ public String sdkVersion() {
+ return sdkVersion;
+ }
+
+ @Override
+ public String osMetadata() {
+ return osMetadata;
+ }
+
+ @Override
+ public String langMetadata() {
+ return langMetadata;
+ }
+
+ @Override
+ public String envMetadata() {
+ return envMetadata;
+ }
+
+ @Override
+ public String vmMetadata() {
+ return vmMetadata;
+ }
+
+ @Override
+ public String vendorMetadata() {
+ return vendorMetadata;
+ }
+
+ @Override
+ public Optional languageTagMetadata() {
+ return languageTagMetadata;
+ }
+
+ @Override
+ public List additionalJvmLanguages() {
+ return Collections.unmodifiableList(additionalJvmLanguages);
+ }
+
+ private String getUserAgent() {
+ return buildSystemUserAgentString(this);
+ }
+
+ private String systemSetting(SystemSetting systemSetting) {
+ return sanitizeInput(systemSetting.getStringValue().orElse(null));
+ }
+
+ private Optional getLanguageTagMetadata() {
+ Optional language = JavaSystemSetting.USER_LANGUAGE.getStringValue();
+ Optional country = JavaSystemSetting.USER_COUNTRY.getStringValue();
+ String value = null;
+ if (language.isPresent() && country.isPresent()) {
+ value = sanitizeInput(language.get()) + "_" + sanitizeInput(country.get());
+ }
+ return Optional.ofNullable(value);
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/SdkClientUserAgentProperties.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/SdkClientUserAgentProperties.java
new file mode 100644
index 000000000000..95b2739ff201
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/SdkClientUserAgentProperties.java
@@ -0,0 +1,43 @@
+/*
+ * 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.core.internal.useragent;
+
+import java.util.HashMap;
+import java.util.Map;
+import software.amazon.awssdk.annotations.NotThreadSafe;
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+
+/**
+ * Represents AWS SDK user agent client values.
+ */
+@NotThreadSafe
+@SdkProtectedApi
+public final class SdkClientUserAgentProperties {
+
+ private final Map properties;
+
+ public SdkClientUserAgentProperties() {
+ this.properties = new HashMap<>(32);
+ }
+
+ public String getProperty(String property) {
+ return properties.get(property);
+ }
+
+ public void putProperty(String property, String value) {
+ properties.put(property, value);
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/SdkUserAgentBuilder.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/SdkUserAgentBuilder.java
new file mode 100644
index 000000000000..213653cbd307
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/SdkUserAgentBuilder.java
@@ -0,0 +1,127 @@
+/*
+ * 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.core.internal.useragent;
+
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.CONFIG_METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.ENV_METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.HTTP;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.INTERNAL_METADATA_MARKER;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.IO;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.JAVA_SDK_METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.LANG_METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.OS_METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.RETRY_MODE;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.UA_METADATA;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.appendFieldAndSpace;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.appendNonEmptyField;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.uaPair;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.uaPairOrNull;
+
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.util.SystemUserAgent;
+import software.amazon.awssdk.utils.StringUtils;
+
+/**
+ * Responsible for building user agent strings for different use cases.
+ */
+@ThreadSafe
+@SdkProtectedApi
+public final class SdkUserAgentBuilder {
+
+ private SdkUserAgentBuilder() {
+ }
+
+ /**
+ * Constructs a string representation of an SDK client user agent string, based on system and client data.
+ * Note that request level values must be added separately.
+ */
+ public static String buildClientUserAgentString(SystemUserAgent systemValues,
+ SdkClientUserAgentProperties userAgentProperties) {
+ StringBuilder uaString = new StringBuilder(255);
+
+ appendNonEmptyField(uaString, JAVA_SDK_METADATA, systemValues.sdkVersion());
+ appendAdditionalSdkMetadata(uaString, userAgentProperties);
+
+ String internalMarkerValue = userAgentProperties.getProperty(INTERNAL_METADATA_MARKER);
+ if (!StringUtils.isEmpty(internalMarkerValue)) {
+ appendFieldAndSpace(uaString, METADATA, INTERNAL_METADATA_MARKER);
+ }
+
+ appendNonEmptyField(uaString, UA_METADATA, "2.0");
+ appendNonEmptyField(uaString, OS_METADATA, systemValues.osMetadata());
+ appendNonEmptyField(uaString, LANG_METADATA, systemValues.langMetadata());
+ appendAdditionalJvmMetadata(uaString, systemValues);
+
+ String envMetadata = systemValues.envMetadata();
+ if (!isEmptyOrUnknown(envMetadata)) {
+ appendFieldAndSpace(uaString, ENV_METADATA, envMetadata);
+ }
+
+ String retryMode = userAgentProperties.getProperty(RETRY_MODE);
+ if (!StringUtils.isEmpty(retryMode)) {
+ appendFieldAndSpace(uaString, CONFIG_METADATA, uaPair(RETRY_MODE, retryMode));
+ }
+
+ removeFinalWhitespace(uaString);
+ return uaString.toString();
+ }
+
+ /**
+ * Constructs a string representation of system user agent values only, that can be used for any backend calls.
+ */
+ public static String buildSystemUserAgentString(SystemUserAgent systemValues) {
+ StringBuilder uaString = new StringBuilder(128);
+
+ appendNonEmptyField(uaString, JAVA_SDK_METADATA, systemValues.sdkVersion());
+ appendNonEmptyField(uaString, OS_METADATA, systemValues.osMetadata());
+ appendNonEmptyField(uaString, LANG_METADATA, systemValues.langMetadata());
+ appendAdditionalJvmMetadata(uaString, systemValues);
+
+ String envMetadata = systemValues.envMetadata();
+ if (!isEmptyOrUnknown(envMetadata)) {
+ appendFieldAndSpace(uaString, ENV_METADATA, systemValues.envMetadata());
+ }
+
+ removeFinalWhitespace(uaString);
+ return uaString.toString();
+ }
+
+ private static void removeFinalWhitespace(StringBuilder builder) {
+ if (builder.length() > 0 && builder.charAt(builder.length() - 1) == ' ') {
+ builder.deleteCharAt(builder.length() - 1);
+ }
+ }
+
+ private static boolean isEmptyOrUnknown(String envMetadata) {
+ return StringUtils.isEmpty(envMetadata) || envMetadata.equalsIgnoreCase("unknown");
+ }
+
+ private static void appendAdditionalSdkMetadata(StringBuilder builder, SdkClientUserAgentProperties userAgentProperties) {
+ appendNonEmptyField(builder, METADATA, uaPairOrNull(IO, userAgentProperties.getProperty(IO)));
+ appendNonEmptyField(builder, METADATA, uaPairOrNull(HTTP, userAgentProperties.getProperty(HTTP)));
+ }
+
+ private static void appendAdditionalJvmMetadata(StringBuilder builder, SystemUserAgent systemProperties) {
+ appendNonEmptyField(builder, METADATA, systemProperties.vmMetadata());
+ appendNonEmptyField(builder, METADATA, systemProperties.vendorMetadata());
+ systemProperties.languageTagMetadata().ifPresent(value -> appendFieldAndSpace(builder, METADATA, value));
+ for (String lang : systemProperties.additionalJvmLanguages()) {
+ appendNonEmptyField(builder, METADATA, lang);
+ }
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/UserAgentConstant.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/UserAgentConstant.java
new file mode 100644
index 000000000000..179f54df965b
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/UserAgentConstant.java
@@ -0,0 +1,125 @@
+/*
+ * 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.core.internal.useragent;
+
+import static software.amazon.awssdk.utils.StringUtils.trim;
+
+import java.util.regex.Pattern;
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+import software.amazon.awssdk.utils.StringUtils;
+
+@SdkProtectedApi
+public final class UserAgentConstant {
+
+ //Known SDK metadata tags/names
+ public static final String API_METADATA = "api";
+ public static final String OS_METADATA = "os";
+ public static final String LANG_METADATA = "lang";
+ public static final String UA_METADATA = "ua";
+ public static final String ENV_METADATA = "exec-env";
+ public static final String JAVA_SDK_METADATA = "aws-sdk-java";
+ public static final String FEATURE_METADATA = "ft";
+ public static final String CONFIG_METADATA = "cfg";
+ public static final String FRAMEWORK_METADATA = "lib";
+ public static final String METADATA = "md";
+ public static final String INTERNAL_METADATA_MARKER = "internal";
+
+ //Separators used in SDK user agent
+ public static final String SLASH = "/";
+ public static final String HASH = "#";
+ public static final String SPACE = " ";
+
+ //Java user agent tags/names
+ public static final String IO = "io";
+ public static final String HTTP = "http";
+ public static final String RETRY_MODE = "retry-mode";
+ public static final String AUTH_SOURCE = "auth-source";
+
+ /** Disallowed characters in the user agent token: @see RFC 7230 */
+ private static final String UA_DENYLIST_REGEX = "[() ,/:;<=>?@\\[\\]{}\\\\]";
+ private static final Pattern UA_DENYLIST_PATTERN = Pattern.compile(UA_DENYLIST_REGEX);
+ private static final String UNKNOWN = "unknown";
+
+ private UserAgentConstant() {
+ }
+
+ /**
+ * According to specifications, the SDK user agent consists of metadata fields separated by RWS characters - in
+ * this implementation, space.
+ * Each field is represented by the name of the field, as specified and the contents of the field, separated
+ * with a '/' (SLASH). Contents can be a single token, a specified value or a uaPair.
+ */
+ public static String field(String name, String value) {
+ return concat(name, trim(value), SLASH);
+ }
+
+ /**
+ * According to specifications, an SDK user agent pair is a name, value pair concatenated with a '#' (HASH).
+ */
+ public static String uaPair(String name, String value) {
+ return concat(name, value, HASH);
+ }
+
+ /**
+ * According to specifications, an SDK user agent pair is a name, value pair concatenated with a '#' (HASH).
+ */
+ public static String uaPairOrNull(String name, String value) {
+ return value != null ? uaPair(name, value) : null;
+ }
+
+ /**
+ * Add a metadata field to the string builder, followed by space. If 'value' can be empty, use
+ * {@link #appendNonEmptyField(StringBuilder, String, String)} instead.
+ */
+ public static void appendFieldAndSpace(StringBuilder builder, String name, String value) {
+ builder.append(name).append(SLASH).append(value);
+ builder.append(SPACE);
+ }
+
+ /**
+ * Add a metadata field to the string builder, preceded by space. If 'value' can be empty, use
+ * {@link #appendNonEmptyField(StringBuilder, String, String)} instead.
+ */
+ public static void appendSpaceAndField(StringBuilder builder, String name, String value) {
+ builder.append(SPACE);
+ builder.append(name).append(SLASH).append(value);
+ }
+
+ /**
+ * Add a metadata field to the string builder only if 'value' is non-empty.
+ * Also see {@link #appendFieldAndSpace(StringBuilder, String, String)}
+ */
+ public static void appendNonEmptyField(StringBuilder builder, String name, String value) {
+ if (!StringUtils.isEmpty(value)) {
+ appendFieldAndSpace(builder, name, value);
+ }
+ }
+
+ /**
+ * Replace any spaces, parentheses in the input with underscores.
+ */
+ public static String sanitizeInput(String input) {
+ return input == null ? UNKNOWN : UA_DENYLIST_PATTERN.matcher(input).replaceAll("_");
+ }
+
+ /**
+ * Concatenates two values with the specified separator, if the second value is not null/empty, otherwise
+ * returns the first value.
+ */
+ public static String concat(String prefix, String suffix, String separator) {
+ return suffix != null && !suffix.isEmpty() ? prefix + separator + suffix : prefix;
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/UserAgentLangValues.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/UserAgentLangValues.java
new file mode 100644
index 000000000000..8082510b80a1
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/useragent/UserAgentLangValues.java
@@ -0,0 +1,91 @@
+/*
+ * 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.core.internal.useragent;
+
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.concat;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.JarInputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.utils.IoUtils;
+
+@SdkInternalApi
+public final class UserAgentLangValues {
+
+ private static final Logger log = LoggerFactory.getLogger(UserAgentLangValues.class);
+
+ private UserAgentLangValues() {
+ }
+
+ public static List getAdditionalJvmLanguages() {
+ return Arrays.asList(scalaVersion(), kotlinVersion());
+ }
+
+ /**
+ * Attempt to determine if Scala is on the classpath and if so what version is in use.
+ * Does this by looking for a known Scala class (scala.util.Properties) and then calling
+ * a static method on that class via reflection to determine the versionNumberString.
+ *
+ * @return Scala version if any, else empty string
+ */
+ public static String scalaVersion() {
+ String scalaVersion = "";
+ try {
+ Class> scalaProperties = Class.forName("scala.util.Properties");
+ scalaVersion = "scala";
+ String version = (String) scalaProperties.getMethod("versionNumberString").invoke(null);
+ scalaVersion = concat(scalaVersion, version, "/");
+ } catch (ClassNotFoundException e) {
+ //Ignore
+ } catch (Exception e) {
+ if (log.isTraceEnabled()) {
+ log.trace("Exception attempting to get Scala version.", e);
+ }
+ }
+ return scalaVersion;
+ }
+
+ /**
+ * Attempt to determine if Kotlin is on the classpath and if so what version is in use.
+ * Does this by looking for a known Kotlin class (kotlin.Unit) and then loading the Manifest
+ * from that class' JAR to determine the Kotlin version.
+ *
+ * @return Kotlin version if any, else empty string
+ */
+ public static String kotlinVersion() {
+ String kotlinVersion = "";
+ JarInputStream kotlinJar = null;
+ try {
+ Class> kotlinUnit = Class.forName("kotlin.Unit");
+ kotlinVersion = "kotlin";
+ kotlinJar = new JarInputStream(kotlinUnit.getProtectionDomain().getCodeSource().getLocation().openStream());
+ String version = kotlinJar.getManifest().getMainAttributes().getValue("Implementation-Version");
+ kotlinVersion = concat(kotlinVersion, version, "/");
+ } catch (ClassNotFoundException e) {
+ //Ignore
+ } catch (Exception e) {
+ if (log.isTraceEnabled()) {
+ log.trace("Exception attempting to get Kotlin version.", e);
+ }
+ } finally {
+ IoUtils.closeQuietly(kotlinJar, log);
+ }
+ return kotlinVersion;
+ }
+}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SdkUserAgent.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SdkUserAgent.java
index bbaa7dd22531..602c4d94c6fe 100644
--- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SdkUserAgent.java
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SdkUserAgent.java
@@ -15,50 +15,20 @@
package software.amazon.awssdk.core.util;
-import java.util.Optional;
-import java.util.jar.JarInputStream;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.annotations.SdkTestInternalApi;
import software.amazon.awssdk.annotations.ThreadSafe;
-import software.amazon.awssdk.utils.IoUtils;
-import software.amazon.awssdk.utils.JavaSystemSetting;
-import software.amazon.awssdk.utils.StringUtils;
/**
- * Utility class for accessing AWS SDK versioning information.
+ * Utility class for accessing AWS SDK versioning information. Deprecated in favor of {@link SystemUserAgent}.
*/
@ThreadSafe
@SdkProtectedApi
+@Deprecated
public final class SdkUserAgent {
- private static final String UA_STRING = "aws-sdk-{platform}/{version} {os.name}/{os.version} {java.vm.name}/{java.vm"
- + ".version} Java/{java.version}{language.and.region}{additional.languages} "
- + "vendor/{java.vendor}";
-
- /** Disallowed characters in the user agent token: @see RFC 7230 */
- private static final String UA_DENYLIST_REGEX = "[() ,/:;<=>?@\\[\\]{}\\\\]";
-
- /** Shared logger for any issues while loading version information. */
- private static final Logger log = LoggerFactory.getLogger(SdkUserAgent.class);
- private static final String UNKNOWN = "unknown";
private static volatile SdkUserAgent instance;
- private static final String[] USER_AGENT_SEARCH = {
- "{platform}",
- "{version}",
- "{os.name}",
- "{os.version}",
- "{java.vm.name}",
- "{java.vm.version}",
- "{java.version}",
- "{java.vendor}",
- "{additional.languages}",
- "{language.and.region}"
- };
-
- /** User Agent info. */
private String userAgent;
private SdkUserAgent() {
@@ -97,94 +67,6 @@ private void initializeUserAgent() {
@SdkTestInternalApi
String getUserAgent() {
- Optional language = JavaSystemSetting.USER_LANGUAGE.getStringValue();
- Optional region = JavaSystemSetting.USER_REGION.getStringValue();
- String languageAndRegion = "";
- if (language.isPresent() && region.isPresent()) {
- languageAndRegion = " (" + sanitizeInput(language.get()) + "_" + sanitizeInput(region.get()) + ")";
- }
-
- return StringUtils.replaceEach(UA_STRING, USER_AGENT_SEARCH, new String[] {
- "java",
- VersionInfo.SDK_VERSION,
- sanitizeInput(JavaSystemSetting.OS_NAME.getStringValue().orElse(null)),
- sanitizeInput(JavaSystemSetting.OS_VERSION.getStringValue().orElse(null)),
- sanitizeInput(JavaSystemSetting.JAVA_VM_NAME.getStringValue().orElse(null)),
- sanitizeInput(JavaSystemSetting.JAVA_VM_VERSION.getStringValue().orElse(null)),
- sanitizeInput(JavaSystemSetting.JAVA_VERSION.getStringValue().orElse(null)),
- sanitizeInput(JavaSystemSetting.JAVA_VENDOR.getStringValue().orElse(null)),
- getAdditionalJvmLanguages(),
- languageAndRegion,
- });
- }
-
- /**
- * Replace any spaces, parentheses in the input with underscores.
- *
- * @param input the input
- * @return the input with spaces replaced by underscores
- */
- private static String sanitizeInput(String input) {
- return input == null ? UNKNOWN : input.replaceAll(UA_DENYLIST_REGEX, "_");
- }
-
- private static String getAdditionalJvmLanguages() {
- return concat(concat("", scalaVersion(), " "), kotlinVersion(), " ");
- }
-
- /**
- * Attempt to determine if Scala is on the classpath and if so what version is in use.
- * Does this by looking for a known Scala class (scala.util.Properties) and then calling
- * a static method on that class via reflection to determine the versionNumberString.
- *
- * @return Scala version if any, else empty string
- */
- private static String scalaVersion() {
- String scalaVersion = "";
- try {
- Class> scalaProperties = Class.forName("scala.util.Properties");
- scalaVersion = "scala";
- String version = (String) scalaProperties.getMethod("versionNumberString").invoke(null);
- scalaVersion = concat(scalaVersion, version, "/");
- } catch (ClassNotFoundException e) {
- //Ignore
- } catch (Exception e) {
- if (log.isTraceEnabled()) {
- log.trace("Exception attempting to get Scala version.", e);
- }
- }
- return scalaVersion;
- }
-
- /**
- * Attempt to determine if Kotlin is on the classpath and if so what version is in use.
- * Does this by looking for a known Kotlin class (kotlin.Unit) and then loading the Manifest
- * from that class' JAR to determine the Kotlin version.
- *
- * @return Kotlin version if any, else empty string
- */
- private static String kotlinVersion() {
- String kotlinVersion = "";
- JarInputStream kotlinJar = null;
- try {
- Class> kotlinUnit = Class.forName("kotlin.Unit");
- kotlinVersion = "kotlin";
- kotlinJar = new JarInputStream(kotlinUnit.getProtectionDomain().getCodeSource().getLocation().openStream());
- String version = kotlinJar.getManifest().getMainAttributes().getValue("Implementation-Version");
- kotlinVersion = concat(kotlinVersion, version, "/");
- } catch (ClassNotFoundException e) {
- //Ignore
- } catch (Exception e) {
- if (log.isTraceEnabled()) {
- log.trace("Exception attempting to get Kotlin version.", e);
- }
- } finally {
- IoUtils.closeQuietly(kotlinJar, log);
- }
- return kotlinVersion;
- }
-
- private static String concat(String prefix, String suffix, String separator) {
- return suffix != null && !suffix.isEmpty() ? prefix + separator + suffix : prefix;
+ return SystemUserAgent.getOrCreate().userAgentString();
}
}
diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SystemUserAgent.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SystemUserAgent.java
new file mode 100644
index 000000000000..7425ecb23406
--- /dev/null
+++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/util/SystemUserAgent.java
@@ -0,0 +1,59 @@
+/*
+ * 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.core.util;
+
+import java.util.List;
+import java.util.Optional;
+import software.amazon.awssdk.annotations.SdkProtectedApi;
+import software.amazon.awssdk.annotations.ThreadSafe;
+import software.amazon.awssdk.core.internal.useragent.DefaultSystemUserAgent;
+
+/**
+ * Common system level user agent properties that can either be accessed as a string or as individual values.
+ * The former is useful when making generic calls, for instance to local endpoints when resolving identity, while
+ * the latter is when incorporating this information into a user agent header in an SDK request.
+ */
+@ThreadSafe
+@SdkProtectedApi
+public interface SystemUserAgent {
+
+ static SystemUserAgent getOrCreate() {
+ return DefaultSystemUserAgent.getOrCreate();
+ }
+
+ /**
+ * A generic user agent string to be used when communicating with backend services.
+ * This string contains Java, OS and region information but does not contain client and request
+ * specific values.
+ */
+ String userAgentString();
+
+ String sdkVersion();
+
+ String osMetadata();
+
+ String langMetadata();
+
+ String envMetadata();
+
+ String vmMetadata();
+
+ String vendorMetadata();
+
+ Optional languageTagMetadata();
+
+ List additionalJvmLanguages();
+}
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java
index 859a2ac4eaca..c307a2f9ba4f 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/http/AmazonHttpClientTest.java
@@ -20,10 +20,12 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.HTTP;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.IO;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.RETRY_MODE;
import static software.amazon.awssdk.core.internal.util.ResponseHandlerTestUtils.combinedSyncResponseHandler;
import java.io.IOException;
-import java.net.URI;
import java.util.concurrent.ExecutorService;
import org.junit.Assert;
@@ -41,15 +43,18 @@
import software.amazon.awssdk.core.client.config.SdkClientOption;
import software.amazon.awssdk.core.exception.SdkClientException;
import software.amazon.awssdk.core.internal.http.AmazonSyncHttpClient;
-import software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage;
import software.amazon.awssdk.core.internal.http.timers.ClientExecutionAndRequestTimerTestUtils;
+import software.amazon.awssdk.core.internal.useragent.SdkClientUserAgentProperties;
+import software.amazon.awssdk.core.internal.useragent.SdkUserAgentBuilder;
import software.amazon.awssdk.core.retry.RetryMode;
-import software.amazon.awssdk.core.retry.RetryPolicy;
+import software.amazon.awssdk.core.util.SystemUserAgent;
import software.amazon.awssdk.http.ExecutableHttpRequest;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpResponse;
+import software.amazon.awssdk.utils.StringUtils;
+import software.amazon.awssdk.utils.http.SdkHttpUtils;
import utils.HttpTestUtils;
import utils.ValidSdkObjects;
@@ -81,13 +86,11 @@ public void testRetryIoExceptionFromExecute() throws Exception {
when(abortableCallable.call()).thenThrow(ioException);
- ExecutionContext context = ClientExecutionAndRequestTimerTestUtils.executionContext(null);
-
try {
client.requestExecutionBuilder()
.request(ValidSdkObjects.sdkHttpFullRequest().build())
.originalRequest(NoopTestRequest.builder().build())
- .executionContext(context)
+ .executionContext(executionContext())
.execute(combinedSyncResponseHandler(null, null));
Assert.fail("No exception when request repeatedly fails!");
@@ -101,18 +104,16 @@ public void testRetryIoExceptionFromExecute() throws Exception {
@Test
public void testRetryIoExceptionFromHandler() throws Exception {
- final IOException exception = new IOException("BOOM");
+ IOException exception = new IOException("BOOM");
HttpResponseHandler> mockHandler = mock(HttpResponseHandler.class);
when(mockHandler.handle(any(), any())).thenThrow(exception);
- ExecutionContext context = ClientExecutionAndRequestTimerTestUtils.executionContext(null);
-
try {
client.requestExecutionBuilder()
.request(ValidSdkObjects.sdkHttpFullRequest().build())
.originalRequest(NoopTestRequest.builder().build())
- .executionContext(context)
+ .executionContext(executionContext())
.execute(combinedSyncResponseHandler(mockHandler, null));
Assert.fail("No exception when request repeatedly fails!");
@@ -132,13 +133,17 @@ public void testUserAgentPrefixAndSuffixAreAdded() {
HttpResponseHandler> handler = mock(HttpResponseHandler.class);
- String clientUserAgent =
- ApplyUserAgentStage.resolveClientUserAgent(prefix, "", ClientType.SYNC, sdkHttpClient, null,
- RetryMode.STANDARD.toString());
+ SdkClientUserAgentProperties userAgentProperties = new SdkClientUserAgentProperties();
+ userAgentProperties.putProperty(RETRY_MODE, RetryMode.STANDARD.toString());
+ userAgentProperties.putProperty(IO, ClientType.SYNC.name());
+ userAgentProperties.putProperty(HTTP, SdkHttpUtils.urlEncode(sdkHttpClient.clientName()));
+ String clientUserAgent = SdkUserAgentBuilder.buildClientUserAgentString(SystemUserAgent.getOrCreate(),
+ userAgentProperties);
SdkClientConfiguration config = HttpTestUtils.testClientConfiguration().toBuilder()
- .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, suffix)
.option(SdkClientOption.CLIENT_USER_AGENT, clientUserAgent)
+ .option(SdkAdvancedClientOption.USER_AGENT_PREFIX, prefix)
+ .option(SdkAdvancedClientOption.USER_AGENT_SUFFIX, suffix)
.option(SdkClientOption.SYNC_HTTP_CLIENT, sdkHttpClient)
.build();
AmazonSyncHttpClient client = new AmazonSyncHttpClient(config);
@@ -146,14 +151,14 @@ public void testUserAgentPrefixAndSuffixAreAdded() {
client.requestExecutionBuilder()
.request(ValidSdkObjects.sdkHttpFullRequest().build())
.originalRequest(NoopTestRequest.builder().build())
- .executionContext(ClientExecutionAndRequestTimerTestUtils.executionContext(null))
+ .executionContext(executionContext())
.execute(combinedSyncResponseHandler(handler, null));
ArgumentCaptor httpRequestCaptor = ArgumentCaptor.forClass(HttpExecuteRequest.class);
verify(sdkHttpClient).prepareRequest(httpRequestCaptor.capture());
- final String userAgent = httpRequestCaptor.getValue().httpRequest().firstMatchingHeader("User-Agent")
- .orElseThrow(() -> new AssertionError("User-Agent header was not found"));
+ String userAgent = httpRequestCaptor.getValue().httpRequest().firstMatchingHeader("User-Agent")
+ .orElseThrow(() -> new AssertionError("User-Agent header was not found"));
Assert.assertTrue(userAgent.startsWith(prefix));
Assert.assertTrue(userAgent.endsWith(suffix));
@@ -163,39 +168,43 @@ public void testUserAgentPrefixAndSuffixAreAdded() {
public void testUserAgentContainsHttpClientInfo() {
HttpResponseHandler> handler = mock(HttpResponseHandler.class);
- String clientUserAgent =
- ApplyUserAgentStage.resolveClientUserAgent(null, null, ClientType.SYNC, sdkHttpClient, null,
- RetryMode.STANDARD.toString());
+ SdkClientUserAgentProperties userAgentProperties = new SdkClientUserAgentProperties();
+ userAgentProperties.putProperty(IO, StringUtils.lowerCase(ClientType.SYNC.name()));
+ userAgentProperties.putProperty(HTTP, SdkHttpUtils.urlEncode(sdkHttpClient.clientName()));
+ String clientUserAgent = SdkUserAgentBuilder.buildClientUserAgentString(SystemUserAgent.getOrCreate(),
+ userAgentProperties);
+
SdkClientConfiguration config = HttpTestUtils.testClientConfiguration().toBuilder()
+ .option(SdkClientOption.CLIENT_USER_AGENT, clientUserAgent)
.option(SdkClientOption.SYNC_HTTP_CLIENT, sdkHttpClient)
.option(SdkClientOption.CLIENT_TYPE, ClientType.SYNC)
- .option(SdkClientOption.CLIENT_USER_AGENT, clientUserAgent)
.build();
AmazonSyncHttpClient client = new AmazonSyncHttpClient(config);
client.requestExecutionBuilder()
.request(ValidSdkObjects.sdkHttpFullRequest().build())
.originalRequest(NoopTestRequest.builder().build())
- .executionContext(ClientExecutionAndRequestTimerTestUtils.executionContext(null))
+ .executionContext(executionContext())
.execute(combinedSyncResponseHandler(handler, null));
ArgumentCaptor httpRequestCaptor = ArgumentCaptor.forClass(HttpExecuteRequest.class);
verify(sdkHttpClient).prepareRequest(httpRequestCaptor.capture());
- final String userAgent = httpRequestCaptor.getValue().httpRequest().firstMatchingHeader("User-Agent")
- .orElseThrow(() -> new AssertionError("User-Agent header was not found"));
+ String userAgent = httpRequestCaptor.getValue().httpRequest().firstMatchingHeader("User-Agent")
+ .orElseThrow(() -> new AssertionError("User-Agent header was not found"));
- Assert.assertTrue(userAgent.contains("io/sync"));
- Assert.assertTrue(userAgent.contains("http/UNKNOWN"));
+ Assert.assertTrue(userAgent.contains("io#sync"));
+ Assert.assertTrue(userAgent.contains("http#UNKNOWN"));
}
@Test
public void testUserAgentContainsRetryModeInfo() {
HttpResponseHandler> handler = mock(HttpResponseHandler.class);
- String clientUserAgent =
- ApplyUserAgentStage.resolveClientUserAgent(null, null, ClientType.SYNC, sdkHttpClient, null,
- RetryMode.STANDARD.toString());
+ SdkClientUserAgentProperties userAgentProperties = new SdkClientUserAgentProperties();
+ userAgentProperties.putProperty(RETRY_MODE, RetryMode.STANDARD.toString().toLowerCase());
+ String clientUserAgent = SdkUserAgentBuilder.buildClientUserAgentString(SystemUserAgent.getOrCreate(),
+ userAgentProperties);
SdkClientConfiguration config = HttpTestUtils.testClientConfiguration().toBuilder()
.option(SdkClientOption.CLIENT_USER_AGENT, clientUserAgent)
@@ -206,16 +215,16 @@ public void testUserAgentContainsRetryModeInfo() {
client.requestExecutionBuilder()
.request(ValidSdkObjects.sdkHttpFullRequest().build())
.originalRequest(NoopTestRequest.builder().build())
- .executionContext(ClientExecutionAndRequestTimerTestUtils.executionContext(null))
+ .executionContext(executionContext())
.execute(combinedSyncResponseHandler(handler, null));
ArgumentCaptor httpRequestCaptor = ArgumentCaptor.forClass(HttpExecuteRequest.class);
verify(sdkHttpClient).prepareRequest(httpRequestCaptor.capture());
- final String userAgent = httpRequestCaptor.getValue().httpRequest().firstMatchingHeader("User-Agent")
- .orElseThrow(() -> new AssertionError("User-Agent header was not found"));
+ String userAgent = httpRequestCaptor.getValue().httpRequest().firstMatchingHeader("User-Agent")
+ .orElseThrow(() -> new AssertionError("User-Agent header was not found"));
- Assert.assertTrue(userAgent.contains("cfg/retry-mode/standard"));
+ Assert.assertTrue(userAgent.contains("cfg/retry-mode#standard"));
}
@Test
@@ -232,6 +241,10 @@ public void closeClient_shouldCloseDependencies() {
verify(executor).shutdown();
}
+ private ExecutionContext executionContext() {
+ return ClientExecutionAndRequestTimerTestUtils.executionContext(null);
+ }
+
private void stubSuccessfulResponse() throws Exception {
when(abortableCallable.call()).thenReturn(HttpExecuteResponse.builder().response(SdkHttpResponse.builder()
.statusCode(200)
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java
index 50b72aa58031..0eaee0e9500d 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java
@@ -16,7 +16,13 @@
package software.amazon.awssdk.core.internal.http.pipeline.stages;
import static org.assertj.core.api.Assertions.assertThat;
+import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.USER_AGENT_PREFIX;
+import static software.amazon.awssdk.core.client.config.SdkAdvancedClientOption.USER_AGENT_SUFFIX;
import static software.amazon.awssdk.core.internal.http.pipeline.stages.ApplyUserAgentStage.HEADER_USER_AGENT;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.HTTP;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.IO;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.RETRY_MODE;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.SPACE;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -35,6 +41,9 @@
import software.amazon.awssdk.core.interceptor.SdkInternalExecutionAttribute;
import software.amazon.awssdk.core.internal.http.HttpClientDependencies;
import software.amazon.awssdk.core.internal.http.RequestExecutionContext;
+import software.amazon.awssdk.core.internal.useragent.SdkClientUserAgentProperties;
+import software.amazon.awssdk.core.internal.useragent.SdkUserAgentBuilder;
+import software.amazon.awssdk.core.util.SystemUserAgent;
import software.amazon.awssdk.http.SdkHttpFullRequest;
import software.amazon.awssdk.http.auth.spi.scheme.AuthSchemeOption;
import software.amazon.awssdk.http.auth.spi.signer.HttpSigner;
@@ -49,7 +58,6 @@ public class ApplyUserAgentStageTest {
(HttpSigner) Mockito.mock(HttpSigner.class),
AuthSchemeOption.builder().schemeId("mock").build());
- private static final String SDK_UA_STRING = "aws-sdk-java/version vendor/unknown";
private static final String PROVIDER_SOURCE = "ProcessCredentialsProvider";
private static final AwsCredentialsIdentity IDENTITY_WITHOUT_SOURCE =
AwsCredentialsIdentity.create("akid", "secret");
@@ -59,38 +67,49 @@ public class ApplyUserAgentStageTest {
.providerName(PROVIDER_SOURCE).build();
@Test
- public void when_noAdditionalDataIsPresent_outputStringEqualsInputString() throws Exception {
- String clientBuildTimeUserAgentString = SDK_UA_STRING;
-
- ApplyUserAgentStage stage = new ApplyUserAgentStage(dependenciesWithUserAgent(clientBuildTimeUserAgentString));
+ public void when_noAdditionalDataIsPresent_userAgentOnlyHasSdkValues() throws Exception {
+ ApplyUserAgentStage stage = new ApplyUserAgentStage(dependencies(clientUserAgent()));
RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITHOUT_SOURCE), noOpRequest());
SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx);
List userAgentHeaders = request.headers().get(HEADER_USER_AGENT);
assertThat(userAgentHeaders).isNotNull().hasSize(1);
- assertThat(userAgentHeaders.get(0)).isEqualTo(SDK_UA_STRING);
+ String userAgentString = userAgentHeaders.get(0);
+ assertThat(userAgentString).startsWith("aws-sdk-java").endsWith("cfg/retry-mode#standard");
}
@Test
- public void when_identityContainsProvider_authSourceIsPresent() throws Exception {
- String clientBuildTimeUserAgentString = SDK_UA_STRING;
+ public void when_userPrefixIsPresent_itIsAddedToUserAgent() throws Exception {
+ String prefix = "Some completely opaque user prefix";
+ ApplyUserAgentStage stage = new ApplyUserAgentStage(dependencies(clientUserAgent(), prefix, null));
- ApplyUserAgentStage stage = new ApplyUserAgentStage(dependenciesWithUserAgent(clientBuildTimeUserAgentString));
+ RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITHOUT_SOURCE), noOpRequest());
+ SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx);
- RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITH_SOURCE), noOpRequest());
+ List userAgentHeaders = request.headers().get(HEADER_USER_AGENT);
+ assertThat(userAgentHeaders).isNotNull().hasSize(1);
+ String userAgentString = userAgentHeaders.get(0);
+ assertThat(userAgentString).startsWith(prefix + SPACE).endsWith("cfg/retry-mode#standard");
+ }
+
+ @Test
+ public void when_userSuffixIsPresent_itIsAddedToUserAgent() throws Exception {
+ String suffix = "Some completely opaque user suffix";
+ ApplyUserAgentStage stage = new ApplyUserAgentStage(dependencies(clientUserAgent(), null, suffix));
+
+ RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITHOUT_SOURCE), noOpRequest());
SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx);
List userAgentHeaders = request.headers().get(HEADER_USER_AGENT);
assertThat(userAgentHeaders).isNotNull().hasSize(1);
- assertThat(userAgentHeaders.get(0)).contains("auth-source#proc");
+ String userAgentString = userAgentHeaders.get(0);
+ assertThat(userAgentString).startsWith("aws-sdk-java").endsWith(SPACE + suffix);
}
@Test
public void when_requestContainsApiName_apiNamesArePresent() throws Exception {
- String clientBuildTimeUserAgentString = SDK_UA_STRING;
-
- ApplyUserAgentStage stage = new ApplyUserAgentStage(dependenciesWithUserAgent(clientBuildTimeUserAgentString));
+ ApplyUserAgentStage stage = new ApplyUserAgentStage(dependencies(clientUserAgent()));
RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITH_SOURCE),
requestWithApiName("myLib", "1.0"));
@@ -101,15 +120,44 @@ public void when_requestContainsApiName_apiNamesArePresent() throws Exception {
assertThat(userAgentHeaders.get(0)).contains("myLib/1.0");
}
- private static HttpClientDependencies dependenciesWithUserAgent(String userAgent) {
- SdkClientConfiguration clientConfiguration = SdkClientConfiguration.builder()
- .option(SdkClientOption.CLIENT_USER_AGENT, userAgent)
- .build();
+ @Test
+ public void when_identityContainsProvider_authSourceIsPresent() throws Exception {
+ ApplyUserAgentStage stage = new ApplyUserAgentStage(dependencies(clientUserAgent()));
+
+ RequestExecutionContext ctx = requestExecutionContext(executionAttributes(IDENTITY_WITH_SOURCE), noOpRequest());
+ SdkHttpFullRequest.Builder request = stage.execute(SdkHttpFullRequest.builder(), ctx);
+
+ List userAgentHeaders = request.headers().get(HEADER_USER_AGENT);
+ assertThat(userAgentHeaders).isNotNull().hasSize(1);
+ assertThat(userAgentHeaders.get(0)).contains("auth-source#proc");
+ }
+
+ private static HttpClientDependencies dependencies(String clientUserAgent) {
+ return dependencies(clientUserAgent, null, null);
+ }
+
+ private static HttpClientDependencies dependencies(String clientUserAgent, String prefix, String suffix) {
+ SdkClientConfiguration clientConfiguration =
+ SdkClientConfiguration.builder()
+ .option(SdkClientOption.CLIENT_USER_AGENT, clientUserAgent)
+ .option(USER_AGENT_PREFIX, prefix)
+ .option(USER_AGENT_SUFFIX, suffix)
+ .build();
return HttpClientDependencies.builder()
.clientConfiguration(clientConfiguration)
.build();
}
+ private String clientUserAgent() {
+ SdkClientUserAgentProperties clientProperties = new SdkClientUserAgentProperties();
+
+ clientProperties.putProperty(RETRY_MODE, "standard");
+ clientProperties.putProperty(IO, "async");
+ clientProperties.putProperty(HTTP, "netty");
+
+ return SdkUserAgentBuilder.buildClientUserAgentString(SystemUserAgent.getOrCreate(), clientProperties);
+ }
+
private static SdkRequest noOpRequest() {
return requestWithOverrideConfig(null);
}
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/SdkUserAgentBuilderTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/SdkUserAgentBuilderTest.java
new file mode 100644
index 000000000000..d8f9343843a8
--- /dev/null
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/SdkUserAgentBuilderTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.core.internal.useragent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.HTTP;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.INTERNAL_METADATA_MARKER;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.IO;
+import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.RETRY_MODE;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.awssdk.core.util.SystemUserAgent;
+
+class SdkUserAgentBuilderTest {
+
+ @ParameterizedTest(name = "{index} - {0}")
+ @MethodSource("inputValues")
+ void sdkUserAgentStringValidation(String description, String expected, SdkClientUserAgentProperties requestUserAgent,
+ SystemUserAgent systemUserAgent) {
+ String userAgent = SdkUserAgentBuilder.buildClientUserAgentString(systemUserAgent, requestUserAgent);
+ assertThat(userAgent).isEqualTo(expected);
+ }
+
+ private static Stream inputValues() {
+ SystemUserAgent standardValuesSysAgent =
+ customSysAgent("2.26.22-SNAPSHOT", "Mac_OS_X#14.6.1", "java#21.0.2", null,
+ "OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS", null, "en_US", null);
+ SystemUserAgent maximalSysAgent =
+ customSysAgent("2.26.22-SNAPSHOT", "Mac_OS_X#14.6.1", "java#21.0.2", "lambda",
+ "OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS", "vendor#Amazon.com_Inc.", "en_US",
+ Arrays.asList("Kotlin", "Scala"));
+
+ SdkClientUserAgentProperties minimalProperties = sdkProperties(null, null, null, null);
+ SdkClientUserAgentProperties maximalProperties = sdkProperties("standard", "arbitrary", "async", "Netty");
+
+ return Stream.of(
+ Arguments.of("default sysagent, empty requestvalues",
+ "aws-sdk-java/2.26.22-SNAPSHOT ua/2.0 os/Mac_OS_X#14.6.1 lang/java#21.0.2 md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/en_US",
+ minimalProperties,
+ standardValuesSysAgent),
+ Arguments.of("standard sysagent, request values - retry",
+ "aws-sdk-java/2.26.22-SNAPSHOT ua/2.0 os/Mac_OS_X#14.6.1 lang/java#21.0.2 "
+ + "md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala "
+ + "exec-env/lambda cfg/retry-mode#standard",
+ sdkProperties("standard", null, null, null),
+ maximalSysAgent),
+ Arguments.of("standard sysagent, request values - internalMarker",
+ "aws-sdk-java/2.26.22-SNAPSHOT md/internal ua/2.0 os/Mac_OS_X#14.6.1 lang/java#21.0.2 "
+ + "md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala exec-env/lambda",
+ sdkProperties(null, "arbitrary", null, null),
+ maximalSysAgent),
+ Arguments.of("standard sysagent, request values - io",
+ "aws-sdk-java/2.26.22-SNAPSHOT md/io#async ua/2.0 os/Mac_OS_X#14.6.1 lang/java#21.0.2 "
+ + "md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala exec-env/lambda",
+ sdkProperties(null, null, "async", null),
+ maximalSysAgent),
+ Arguments.of("standard sysagent, request values - http",
+ "aws-sdk-java/2.26.22-SNAPSHOT md/http#Apache ua/2.0 os/Mac_OS_X#14.6.1 lang/java#21.0.2 "
+ + "md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala exec-env/lambda",
+ sdkProperties(null, null, null, "Apache"),
+ maximalSysAgent),
+ Arguments.of("standard sysagent, request values - authSource",
+ "aws-sdk-java/2.26.22-SNAPSHOT ua/2.0 os/Mac_OS_X#14.6.1 lang/java#21.0.2 "
+ + "md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala "
+ + "exec-env/lambda",
+ sdkProperties(null, null, null, null),
+ maximalSysAgent),
+ Arguments.of("standard sysagent, request values - maximal",
+ "aws-sdk-java/2.26.22-SNAPSHOT md/io#async md/http#Netty md/internal ua/2.0 os/Mac_OS_X#14.6.1 "
+ + "lang/java#21.0.2 "
+ + "md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala "
+ + "exec-env/lambda cfg/retry-mode#standard",
+ maximalProperties,
+ maximalSysAgent)
+ );
+ }
+
+ private static SdkClientUserAgentProperties sdkProperties(String retryMode, String internalMarker, String io, String http) {
+ SdkClientUserAgentProperties properties = new SdkClientUserAgentProperties();
+
+ if (retryMode != null) {
+ properties.putProperty(RETRY_MODE, retryMode);
+ }
+
+ if (internalMarker != null) {
+ properties.putProperty(INTERNAL_METADATA_MARKER, internalMarker);
+ }
+
+ if (io != null) {
+ properties.putProperty(IO, io);
+ }
+
+ if (http != null) {
+ properties.putProperty(HTTP, http);
+ }
+
+ return properties;
+ }
+
+ private static SystemUserAgent customSysAgent(String sdkVersion, String osMetadata,
+ String langMetadata, String envMetadata, String vmMetadata,
+ String vendorMetadata, String languageTagMetadata,
+ List additionalJvmLanguages) {
+ return new SystemUserAgent() {
+ @Override
+ public String userAgentString() {
+ return null;
+ }
+
+ @Override
+ public String sdkVersion() {
+ return sdkVersion;
+ }
+
+ @Override
+ public String osMetadata() {
+ return osMetadata;
+ }
+
+ @Override
+ public String langMetadata() {
+ return langMetadata;
+ }
+
+ @Override
+ public String envMetadata() {
+ return envMetadata;
+ }
+
+ @Override
+ public String vmMetadata() {
+ return vmMetadata;
+ }
+
+ @Override
+ public String vendorMetadata() {
+ return vendorMetadata;
+ }
+
+ @Override
+ public Optional languageTagMetadata() {
+ return Optional.ofNullable(languageTagMetadata);
+ }
+
+ @Override
+ public List additionalJvmLanguages() {
+ return additionalJvmLanguages == null ? Collections.emptyList() : additionalJvmLanguages;
+ }
+ };
+ }
+
+}
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/SystemUserAgentTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/SystemUserAgentTest.java
new file mode 100644
index 000000000000..7be2f614e03d
--- /dev/null
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/useragent/SystemUserAgentTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.core.internal.useragent;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static software.amazon.awssdk.core.internal.useragent.SdkUserAgentBuilder.buildSystemUserAgentString;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Stream;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import software.amazon.awssdk.core.util.SystemUserAgent;
+
+class SystemUserAgentTest {
+
+ @Test
+ void when_userAgent_IsRetrived_itIsTheSameObject() {
+ SystemUserAgent userAgent = SystemUserAgent.getOrCreate();
+ SystemUserAgent userAgentCopy = SystemUserAgent.getOrCreate();
+ assertThat(userAgent).isEqualTo(userAgentCopy);
+ }
+
+ @Test
+ void when_defaultUserAgent_IsRetrived_itIsTheSameObject() {
+ DefaultSystemUserAgent userAgent = DefaultSystemUserAgent.getOrCreate();
+ DefaultSystemUserAgent userAgentCopy = DefaultSystemUserAgent.getOrCreate();
+ assertThat(userAgent).isEqualTo(userAgentCopy);
+ }
+
+ @Test
+ void when_defaultSystemUserAgent_isGenerated_itHasTheRightFormat() {
+ SystemUserAgent userAgent = DefaultSystemUserAgent.getOrCreate();
+ String[] userAgentFields = userAgent.userAgentString().split(" ");
+ assertThat(userAgentFields.length).isGreaterThan(0);
+ assertThat(Arrays.stream(userAgentFields).allMatch(field -> field.contains("/"))).isTrue();
+ }
+
+ @ParameterizedTest(name = "{index} - {0}")
+ @MethodSource("inputValues")
+ void when_systemAgentValues_areCustomized_resultingStringIsExpected(String description, String expected,
+ SystemUserAgent systemUserAgent) {
+ assertThat(systemUserAgent.userAgentString()).isEqualTo(expected);
+ }
+
+ private static Stream inputValues() {
+ return Stream.of(
+ Arguments.of("Minimal system agent",
+ "",
+ customSysAgent(null, null, null, null, null, null, null, null)),
+ Arguments.of("System agent no vendor, env",
+ "aws-sdk-java/2.26.22-SNAPSHOT os/Mac_OS_X#14.6.1 lang/java#21.0.2 md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/en_US md/Scala",
+ customSysAgent("2.26.22-SNAPSHOT", "Mac_OS_X#14.6.1", "java#21.0.2", "unknown",
+ "OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS", null, "en_US",
+ Arrays.asList("Scala"))),
+ Arguments.of("Maximal system agent",
+ "aws-sdk-java/2.26.22-SNAPSHOT os/Mac_OS_X#14.6.1 lang/java#21.0.2 md/OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS md/vendor#Amazon.com_Inc. md/en_US md/Kotlin md/Scala exec-env/lambda",
+ customSysAgent("2.26.22-SNAPSHOT", "Mac_OS_X#14.6.1", "java#21.0.2", "lambda",
+ "OpenJDK_64-Bit_Server_VM#21.0.2+13-LTS", "vendor#Amazon.com_Inc.", "en_US",
+ Arrays.asList("Kotlin", "Scala")))
+ );
+ }
+
+ private static SystemUserAgent customSysAgent(String sdkVersion, String osMetadata,
+ String langMetadata, String envMetadata, String vmMetadata,
+ String vendorMetadata, String languageTagMetadata,
+ List additionalJvmLanguages) {
+ return new SystemUserAgent() {
+ @Override
+ public String userAgentString() {
+ return buildSystemUserAgentString(this);
+ }
+
+ @Override
+ public String sdkVersion() {
+ return sdkVersion;
+ }
+
+ @Override
+ public String osMetadata() {
+ return osMetadata;
+ }
+
+ @Override
+ public String langMetadata() {
+ return langMetadata;
+ }
+
+ @Override
+ public String envMetadata() {
+ return envMetadata;
+ }
+
+ @Override
+ public String vmMetadata() {
+ return vmMetadata;
+ }
+
+ @Override
+ public String vendorMetadata() {
+ return vendorMetadata;
+ }
+
+ @Override
+ public Optional languageTagMetadata() {
+ return Optional.ofNullable(languageTagMetadata);
+ }
+
+ @Override
+ public List additionalJvmLanguages() {
+ return additionalJvmLanguages == null ? Collections.emptyList() : additionalJvmLanguages;
+ }
+ };
+ }
+}
diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/util/SdkUserAgentTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/util/SdkUserAgentTest.java
index 4c06a42e445f..fedd63fe854a 100644
--- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/util/SdkUserAgentTest.java
+++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/util/SdkUserAgentTest.java
@@ -16,37 +16,15 @@
package software.amazon.awssdk.core.util;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import java.util.Arrays;
import org.junit.jupiter.api.Test;
-import software.amazon.awssdk.utils.JavaSystemSetting;
-public class SdkUserAgentTest {
+class SdkUserAgentTest {
@Test
- public void userAgent() {
- String userAgent = SdkUserAgent.create().userAgent();
- assertNotNull(userAgent);
- Arrays.stream(userAgent.split(" ")).forEach(str -> assertThat(isValidInput(str)).isTrue());
- }
-
- @Test
- public void userAgent_HasVendor() {
- System.setProperty(JavaSystemSetting.JAVA_VENDOR.property(), "finks");
- String userAgent = SdkUserAgent.create().getUserAgent();
- System.clearProperty(JavaSystemSetting.JAVA_VENDOR.property());
- assertThat(userAgent).contains("vendor/finks");
- }
-
- @Test
- public void userAgent_HasUnknownVendor() {
- System.clearProperty(JavaSystemSetting.JAVA_VENDOR.property());
- String userAgent = SdkUserAgent.create().getUserAgent();
- assertThat(userAgent).contains("vendor/unknown");
- }
-
- private boolean isValidInput(String input) {
- return input.startsWith("(") || input.contains("/") && input.split("/").length == 2;
+ void when_callingDeprecatedClass_valueIsCorrect() {
+ String userAgentStringThroughOldClass = SdkUserAgent.create().getUserAgent();
+ String systemUserAgentString = SystemUserAgent.getOrCreate().userAgentString();
+ assertThat(userAgentStringThroughOldClass).isEqualTo(systemUserAgentString);
}
}
diff --git a/pom.xml b/pom.xml
index 5305b3830f22..944922ea1189 100644
--- a/pom.xml
+++ b/pom.xml
@@ -676,6 +676,8 @@
*.internal.*
software.amazon.awssdk.thirdparty.*
software.amazon.awssdk.regions.*
+
+ software.amazon.awssdk.utils.JavaSystemSetting
true
diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/customizeduseragent/InternalUserAgentTest.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/customizeduseragent/InternalUserAgentTest.java
index 5e6ecce8205b..ccb2f28f8b10 100644
--- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/customizeduseragent/InternalUserAgentTest.java
+++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/customizeduseragent/InternalUserAgentTest.java
@@ -83,7 +83,6 @@ public void syncWithInternalUserAgent_shouldContainInternalUserAgent() {
verifyUserAgent();
}
-
@Test
public void asyncWithInternalUserAgent_shouldContainInternalUserAgent() {
stubResponse();
@@ -95,7 +94,7 @@ public void asyncWithInternalUserAgent_shouldContainInternalUserAgent() {
public void syncWithoutInternalUserAgent_shouldNotContainInternalUserAgent() {
stubResponse();
clientWithoutInternalConfig.allTypes(SdkBuilder::build);
- verifyNotContainUserAgent();
+ verifyNotContainUserAgent();
}
@Test
@@ -106,11 +105,11 @@ public void asyncWithoutInternalUserAgent_shouldNotContainInternalUserAgent() {
}
private void verifyUserAgent() {
- verify(postRequestedFor(anyUrl()).withHeader("user-agent", containing("md/foobar")));
+ verify(postRequestedFor(anyUrl()).withHeader("user-agent", containing("md/internal")));
}
private void verifyNotContainUserAgent() {
- verify(postRequestedFor(anyUrl()).withHeader("user-agent", notMatching(".*md/foobar.*")));
+ verify(postRequestedFor(anyUrl()).withHeader("user-agent", notMatching(".*md/internal.*")));
}
private void stubResponse() {
diff --git a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/defaultsmode/ClientDefaultsModeTestSuite.java b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/defaultsmode/ClientDefaultsModeTestSuite.java
index 8042e0ccc321..68cc64d83a50 100644
--- a/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/defaultsmode/ClientDefaultsModeTestSuite.java
+++ b/test/codegen-generated-classes-test/src/test/java/software/amazon/awssdk/services/defaultsmode/ClientDefaultsModeTestSuite.java
@@ -50,7 +50,7 @@ public void legacyDefaultsMode_shouldUseLegacySetting() {
ClientT client = clientBuilder().overrideConfiguration(o -> o.retryPolicy(RetryMode.LEGACY)).build();
callAllTypes(client);
- WireMock.verify(postRequestedFor(anyUrl()).withHeader("User-Agent", containing("cfg/retry-mode/legacy")));
+ WireMock.verify(postRequestedFor(anyUrl()).withHeader("User-Agent", containing("cfg/retry-mode#legacy")));
}
@Test
@@ -59,7 +59,7 @@ public void standardDefaultsMode_shouldApplyStandardDefaults() {
ClientT client = clientBuilder().defaultsMode(DefaultsMode.STANDARD).build();
callAllTypes(client);
- WireMock.verify(postRequestedFor(anyUrl()).withHeader("User-Agent", containing("cfg/retry-mode/standard")));
+ WireMock.verify(postRequestedFor(anyUrl()).withHeader("User-Agent", containing("cfg/retry-mode#standard")));
}
@Test
@@ -69,7 +69,7 @@ public void retryModeOverridden_shouldTakePrecedence() {
clientBuilder().defaultsMode(DefaultsMode.STANDARD).overrideConfiguration(o -> o.retryPolicy(RetryMode.LEGACY)).build();
callAllTypes(client);
- WireMock.verify(postRequestedFor(anyUrl()).withHeader("User-Agent", containing("cfg/retry-mode/legacy")));
+ WireMock.verify(postRequestedFor(anyUrl()).withHeader("User-Agent", containing("cfg/retry-mode#legacy")));
}
private BuilderT clientBuilder() {
diff --git a/utils/src/main/java/software/amazon/awssdk/utils/JavaSystemSetting.java b/utils/src/main/java/software/amazon/awssdk/utils/JavaSystemSetting.java
index 67b88c0ba375..ecf2bf8578f4 100644
--- a/utils/src/main/java/software/amazon/awssdk/utils/JavaSystemSetting.java
+++ b/utils/src/main/java/software/amazon/awssdk/utils/JavaSystemSetting.java
@@ -33,7 +33,7 @@ public enum JavaSystemSetting implements SystemSetting {
USER_HOME("user.home"),
USER_LANGUAGE("user.language"),
- USER_REGION("user.region"),
+ USER_COUNTRY("user.country"),
USER_NAME("user.name"),
SSL_KEY_STORE("javax.net.ssl.keyStore"),