diff --git a/core/src/main/java/io/grpc/internal/DnsNameResolver.java b/core/src/main/java/io/grpc/internal/DnsNameResolver.java index 9702215c7f..f81c577820 100644 --- a/core/src/main/java/io/grpc/internal/DnsNameResolver.java +++ b/core/src/main/java/io/grpc/internal/DnsNameResolver.java @@ -478,29 +478,20 @@ final class DnsNameResolver extends NameResolver { @Nullable private static final Double getPercentageFromChoice(Map serviceConfigChoice) { - if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY)) { - return null; - } - return JsonUtil.getDouble(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY); + return JsonUtil.getNumber(serviceConfigChoice, SERVICE_CONFIG_CHOICE_PERCENTAGE_KEY); } @Nullable private static final List getClientLanguagesFromChoice( Map serviceConfigChoice) { - if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY)) { - return null; - } - return JsonUtil.checkStringList( - JsonUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY)); + return JsonUtil.getListOfStrings( + serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_LANGUAGE_KEY); } @Nullable private static final List getHostnamesFromChoice(Map serviceConfigChoice) { - if (!serviceConfigChoice.containsKey(SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY)) { - return null; - } - return JsonUtil.checkStringList( - JsonUtil.getList(serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY)); + return JsonUtil.getListOfStrings( + serviceConfigChoice, SERVICE_CONFIG_CHOICE_CLIENT_HOSTNAME_KEY); } /** diff --git a/core/src/main/java/io/grpc/internal/JsonUtil.java b/core/src/main/java/io/grpc/internal/JsonUtil.java index d25698c635..fabc08d646 100644 --- a/core/src/main/java/io/grpc/internal/JsonUtil.java +++ b/core/src/main/java/io/grpc/internal/JsonUtil.java @@ -16,19 +16,23 @@ package io.grpc.internal; +import static com.google.common.math.LongMath.checkedAdd; + +import java.text.ParseException; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** - * Helper utility to work with JSON values in Java types. + * Helper utility to work with JSON values in Java types. Includes the JSON dialect used by + * Protocol Buffers. */ public class JsonUtil { /** * Gets a list from an object for the given key. If the key is not present, this returns null. * If the value is not a List, throws an exception. */ - @SuppressWarnings("unchecked") @Nullable public static List getList(Map obj, String key) { assert key != null; @@ -43,6 +47,34 @@ public class JsonUtil { return (List) value; } + /** + * Gets a list from an object for the given key, and verifies all entries are objects. If the key + * is not present, this returns null. If the value is not a List or an entry is not an object, + * throws an exception. + */ + @Nullable + public static List> getListOfObjects(Map obj, String key) { + List list = getList(obj, key); + if (list == null) { + return null; + } + return checkObjectList(list); + } + + /** + * Gets a list from an object for the given key, and verifies all entries are strings. If the key + * is not present, this returns null. If the value is not a List or an entry is not a string, + * throws an exception. + */ + @Nullable + public static List getListOfStrings(Map obj, String key) { + List list = getList(obj, key); + if (list == null) { + return null; + } + return checkStringList(list); + } + /** * Gets an object from an object for the given key. If the key is not present, this returns null. * If the value is not a Map, throws an exception. @@ -63,11 +95,11 @@ public class JsonUtil { } /** - * Gets a double from an object for the given key. If the key is not present, this returns null. + * Gets a number from an object for the given key. If the key is not present, this returns null. * If the value is not a Double, throws an exception. */ @Nullable - public static Double getDouble(Map obj, String key) { + public static Double getNumber(Map obj, String key) { assert key != null; if (!obj.containsKey(key)) { return null; @@ -80,6 +112,23 @@ public class JsonUtil { return (Double) value; } + /** + * Gets a number from an object for the given key, casted to an integer. If the key is not + * present, this returns null. If the value is not a Double or loses precision when cast to an + * integer, throws an exception. + */ + public static Integer getNumberAsInteger(Map obj, String key) { + Double d = getNumber(obj, key); + if (d == null) { + return null; + } + int i = d.intValue(); + if (i != d) { + throw new ClassCastException("Number expected to be integer: " + d); + } + return i; + } + /** * Gets a string from an object for the given key. If the key is not present, this returns null. * If the value is not a String, throws an exception. @@ -98,6 +147,23 @@ public class JsonUtil { return (String) value; } + /** + * Gets a string from an object for the given key, parsed as a duration (defined by protobuf). If + * the key is not present, this returns null. If the value is not a String or not properly + * formatted, throws an exception. + */ + public static Long getStringAsDuration(Map obj, String key) { + String value = getString(obj, key); + if (value == null) { + return null; + } + try { + return parseDuration(value); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } + /** * Gets a boolean from an object for the given key. If the key is not present, this returns null. * If the value is not a Boolean, throws an exception. @@ -146,4 +212,138 @@ public class JsonUtil { } return (List) rawList; } + + private static final long DURATION_SECONDS_MIN = -315576000000L; + private static final long DURATION_SECONDS_MAX = 315576000000L; + + /** + * Parse from a string to produce a duration. Copy of + * {@link com.google.protobuf.util.Durations#parse}. + * + * @return A Duration parsed from the string. + * @throws ParseException if parsing fails. + */ + private static long parseDuration(String value) throws ParseException { + // Must ended with "s". + if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { + throw new ParseException("Invalid duration string: " + value, 0); + } + boolean negative = false; + if (value.charAt(0) == '-') { + negative = true; + value = value.substring(1); + } + String secondValue = value.substring(0, value.length() - 1); + String nanoValue = ""; + int pointPosition = secondValue.indexOf('.'); + if (pointPosition != -1) { + nanoValue = secondValue.substring(pointPosition + 1); + secondValue = secondValue.substring(0, pointPosition); + } + long seconds = Long.parseLong(secondValue); + int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); + if (seconds < 0) { + throw new ParseException("Invalid duration string: " + value, 0); + } + if (negative) { + seconds = -seconds; + nanos = -nanos; + } + try { + return normalizedDuration(seconds, nanos); + } catch (IllegalArgumentException e) { + throw new ParseException("Duration value is out of range.", 0); + } + } + + /** + * Copy of {@link com.google.protobuf.util.Timestamps#parseNanos}. + */ + private static int parseNanos(String value) throws ParseException { + int result = 0; + for (int i = 0; i < 9; ++i) { + result = result * 10; + if (i < value.length()) { + if (value.charAt(i) < '0' || value.charAt(i) > '9') { + throw new ParseException("Invalid nanoseconds.", 0); + } + result += value.charAt(i) - '0'; + } + } + return result; + } + + private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); + + /** + * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}. + */ + @SuppressWarnings("NarrowingCompoundAssignment") + private static long normalizedDuration(long seconds, int nanos) { + if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { + seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); + nanos %= NANOS_PER_SECOND; + } + if (seconds > 0 && nanos < 0) { + nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) + seconds--; // no overflow since seconds is positive (and we're decrementing) + } + if (seconds < 0 && nanos > 0) { + nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) + seconds++; // no overflow since seconds is negative (and we're incrementing) + } + if (!durationIsValid(seconds, nanos)) { + throw new IllegalArgumentException(String.format( + "Duration is not valid. See proto definition for valid values. " + + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " + + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " + + "Nanos must have the same sign as seconds", seconds, nanos)); + } + return saturatedAdd(TimeUnit.SECONDS.toNanos(seconds), nanos); + } + + /** + * Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code + * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} + * value must be in the range [-999,999,999, +999,999,999]. + * + *

Note: Durations less than one second are represented with a 0 {@code seconds} field + * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero + * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. + * + *

Copy of {@link com.google.protobuf.util.Duration#isValid}.

+ */ + private static boolean durationIsValid(long seconds, int nanos) { + if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { + return false; + } + if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { + return false; + } + if (seconds < 0 || nanos < 0) { + if (seconds > 0 || nanos > 0) { + return false; + } + } + return true; + } + + /** + * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case + * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. + * + *

Copy of {@link com.google.common.math.LongMath#saturatedAdd}.

+ * + */ + @SuppressWarnings("ShortCircuitBoolean") + private static long saturatedAdd(long a, long b) { + long naiveSum = a + b; + if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) { + // If a and b have different signs or a has the same sign as the result then there was no + // overflow, return. + return naiveSum; + } + // we did over/under flow, if the sign is negative we should return MAX otherwise MIN + return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1); + } } diff --git a/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java b/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java index abc0d9e461..e72937946b 100644 --- a/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java +++ b/core/src/main/java/io/grpc/internal/ServiceConfigUtil.java @@ -19,7 +19,6 @@ package io.grpc.internal; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Verify.verify; -import static com.google.common.math.LongMath.checkedAdd; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.MoreObjects; @@ -27,7 +26,6 @@ import com.google.common.base.Objects; import com.google.common.base.VerifyException; import io.grpc.Status; import io.grpc.internal.RetriableStream.Throttle; -import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; @@ -35,7 +33,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** @@ -45,37 +42,6 @@ import javax.annotation.Nullable; */ public final class ServiceConfigUtil { - private static final String SERVICE_CONFIG_METHOD_CONFIG_KEY = "methodConfig"; - private static final String SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY = "loadBalancingPolicy"; - private static final String SERVICE_CONFIG_LOAD_BALANCING_CONFIG_KEY = "loadBalancingConfig"; - private static final String XDS_CONFIG_CHILD_POLICY_KEY = "childPolicy"; - private static final String XDS_CONFIG_FALLBACK_POLICY_KEY = "fallbackPolicy"; - private static final String XDS_CONFIG_EDS_SERVICE_NAME = "edsServiceName"; - private static final String XDS_CONFIG_LRS_SERVER_NAME = "lrsLoadReportingServerName"; - private static final String SERVICE_CONFIG_STICKINESS_METADATA_KEY = "stickinessMetadataKey"; - private static final String METHOD_CONFIG_NAME_KEY = "name"; - private static final String METHOD_CONFIG_TIMEOUT_KEY = "timeout"; - private static final String METHOD_CONFIG_WAIT_FOR_READY_KEY = "waitForReady"; - private static final String METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY = - "maxRequestMessageBytes"; - private static final String METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY = - "maxResponseMessageBytes"; - private static final String METHOD_CONFIG_RETRY_POLICY_KEY = "retryPolicy"; - private static final String METHOD_CONFIG_HEDGING_POLICY_KEY = "hedgingPolicy"; - private static final String NAME_SERVICE_KEY = "service"; - private static final String NAME_METHOD_KEY = "method"; - private static final String RETRY_POLICY_MAX_ATTEMPTS_KEY = "maxAttempts"; - private static final String RETRY_POLICY_INITIAL_BACKOFF_KEY = "initialBackoff"; - private static final String RETRY_POLICY_MAX_BACKOFF_KEY = "maxBackoff"; - private static final String RETRY_POLICY_BACKOFF_MULTIPLIER_KEY = "backoffMultiplier"; - private static final String RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY = "retryableStatusCodes"; - private static final String HEDGING_POLICY_MAX_ATTEMPTS_KEY = "maxAttempts"; - private static final String HEDGING_POLICY_HEDGING_DELAY_KEY = "hedgingDelay"; - private static final String HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY = "nonFatalStatusCodes"; - - private static final long DURATION_SECONDS_MIN = -315576000000L; - private static final long DURATION_SECONDS_MAX = 315576000000L; - private ServiceConfigUtil() {} /** @@ -83,9 +49,7 @@ public final class ServiceConfigUtil { */ @Nullable public static String getHealthCheckedServiceName(@Nullable Map serviceConfig) { - String healthCheckKey = "healthCheckConfig"; - String serviceNameKey = "serviceName"; - if (serviceConfig == null || !serviceConfig.containsKey(healthCheckKey)) { + if (serviceConfig == null) { return null; } @@ -97,8 +61,8 @@ public final class ServiceConfigUtil { } } */ - Map healthCheck = JsonUtil.getObject(serviceConfig, healthCheckKey); - if (!healthCheck.containsKey(serviceNameKey)) { + Map healthCheck = JsonUtil.getObject(serviceConfig, "healthCheckConfig"); + if (healthCheck == null) { return null; } return JsonUtil.getString(healthCheck, "serviceName"); @@ -106,8 +70,7 @@ public final class ServiceConfigUtil { @Nullable static Throttle getThrottlePolicy(@Nullable Map serviceConfig) { - String retryThrottlingKey = "retryThrottling"; - if (serviceConfig == null || !serviceConfig.containsKey(retryThrottlingKey)) { + if (serviceConfig == null) { return null; } @@ -130,11 +93,14 @@ public final class ServiceConfigUtil { } */ - Map throttling = JsonUtil.getObject(serviceConfig, retryThrottlingKey); + Map throttling = JsonUtil.getObject(serviceConfig, "retryThrottling"); + if (throttling == null) { + return null; + } // TODO(dapengzhang0): check if this is null. - float maxTokens = JsonUtil.getDouble(throttling, "maxTokens").floatValue(); - float tokenRatio = JsonUtil.getDouble(throttling, "tokenRatio").floatValue(); + float maxTokens = JsonUtil.getNumber(throttling, "maxTokens").floatValue(); + float tokenRatio = JsonUtil.getNumber(throttling, "tokenRatio").floatValue(); checkState(maxTokens > 0f, "maxToken should be greater than zero"); checkState(tokenRatio > 0f, "tokenRatio should be greater than zero"); return new Throttle(maxTokens, tokenRatio); @@ -142,44 +108,30 @@ public final class ServiceConfigUtil { @Nullable static Integer getMaxAttemptsFromRetryPolicy(Map retryPolicy) { - if (!retryPolicy.containsKey(RETRY_POLICY_MAX_ATTEMPTS_KEY)) { - return null; - } - return JsonUtil.getDouble(retryPolicy, RETRY_POLICY_MAX_ATTEMPTS_KEY).intValue(); + return JsonUtil.getNumberAsInteger(retryPolicy, "maxAttempts"); } @Nullable static Long getInitialBackoffNanosFromRetryPolicy(Map retryPolicy) { - if (!retryPolicy.containsKey(RETRY_POLICY_INITIAL_BACKOFF_KEY)) { - return null; - } - String rawInitialBackoff = JsonUtil.getString(retryPolicy, RETRY_POLICY_INITIAL_BACKOFF_KEY); - try { - return parseDuration(rawInitialBackoff); - } catch (ParseException e) { - throw new RuntimeException(e); - } + return JsonUtil.getStringAsDuration(retryPolicy, "initialBackoff"); } @Nullable static Long getMaxBackoffNanosFromRetryPolicy(Map retryPolicy) { - if (!retryPolicy.containsKey(RETRY_POLICY_MAX_BACKOFF_KEY)) { - return null; - } - String rawMaxBackoff = JsonUtil.getString(retryPolicy, RETRY_POLICY_MAX_BACKOFF_KEY); - try { - return parseDuration(rawMaxBackoff); - } catch (ParseException e) { - throw new RuntimeException(e); - } + return JsonUtil.getStringAsDuration(retryPolicy, "maxBackoff"); } @Nullable static Double getBackoffMultiplierFromRetryPolicy(Map retryPolicy) { - if (!retryPolicy.containsKey(RETRY_POLICY_BACKOFF_MULTIPLIER_KEY)) { + return JsonUtil.getNumber(retryPolicy, "backoffMultiplier"); + } + + private static Set getListOfStatusCodesAsSet(Map obj, String key) { + List statuses = JsonUtil.getList(obj, key); + if (statuses == null) { return null; } - return JsonUtil.getDouble(retryPolicy, RETRY_POLICY_BACKOFF_MULTIPLIER_KEY); + return getStatusCodesFromList(statuses); } private static Set getStatusCodesFromList(List statuses) { @@ -209,92 +161,58 @@ public final class ServiceConfigUtil { } static Set getRetryableStatusCodesFromRetryPolicy(Map retryPolicy) { - verify( - retryPolicy.containsKey(RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY), - "%s is required in retry policy", RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY); - Set codes = - getStatusCodesFromList( - JsonUtil.getList(retryPolicy, RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY)); - verify(!codes.isEmpty(), "%s must not be empty", RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY); - verify( - !codes.contains(Status.Code.OK), - "%s must not contain OK", RETRY_POLICY_RETRYABLE_STATUS_CODES_KEY); + String retryableStatusCodesKey = "retryableStatusCodes"; + Set codes = getListOfStatusCodesAsSet(retryPolicy, retryableStatusCodesKey); + verify(codes != null, "%s is required in retry policy", retryableStatusCodesKey); + verify(!codes.isEmpty(), "%s must not be empty", retryableStatusCodesKey); + verify(!codes.contains(Status.Code.OK), "%s must not contain OK", retryableStatusCodesKey); return codes; } @Nullable static Integer getMaxAttemptsFromHedgingPolicy(Map hedgingPolicy) { - if (!hedgingPolicy.containsKey(HEDGING_POLICY_MAX_ATTEMPTS_KEY)) { - return null; - } - return JsonUtil.getDouble(hedgingPolicy, HEDGING_POLICY_MAX_ATTEMPTS_KEY).intValue(); + return JsonUtil.getNumberAsInteger(hedgingPolicy, "maxAttempts"); } @Nullable static Long getHedgingDelayNanosFromHedgingPolicy(Map hedgingPolicy) { - if (!hedgingPolicy.containsKey(HEDGING_POLICY_HEDGING_DELAY_KEY)) { - return null; - } - String rawHedgingDelay = JsonUtil.getString(hedgingPolicy, HEDGING_POLICY_HEDGING_DELAY_KEY); - try { - return parseDuration(rawHedgingDelay); - } catch (ParseException e) { - throw new RuntimeException(e); - } + return JsonUtil.getStringAsDuration(hedgingPolicy, "hedgingDelay"); } static Set getNonFatalStatusCodesFromHedgingPolicy(Map hedgingPolicy) { - if (!hedgingPolicy.containsKey(HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY)) { + String nonFatalStatusCodesKey = "nonFatalStatusCodes"; + Set codes = getListOfStatusCodesAsSet(hedgingPolicy, nonFatalStatusCodesKey); + if (codes == null) { return Collections.unmodifiableSet(EnumSet.noneOf(Status.Code.class)); } - Set codes = - getStatusCodesFromList( - JsonUtil.getList(hedgingPolicy, HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY)); - verify( - !codes.contains(Status.Code.OK), - "%s must not contain OK", HEDGING_POLICY_NON_FATAL_STATUS_CODES_KEY); + verify(!codes.contains(Status.Code.OK), "%s must not contain OK", nonFatalStatusCodesKey); return codes; } @Nullable static String getServiceFromName(Map name) { - if (!name.containsKey(NAME_SERVICE_KEY)) { - return null; - } - return JsonUtil.getString(name, NAME_SERVICE_KEY); + return JsonUtil.getString(name, "service"); } @Nullable static String getMethodFromName(Map name) { - if (!name.containsKey(NAME_METHOD_KEY)) { - return null; - } - return JsonUtil.getString(name, NAME_METHOD_KEY); + return JsonUtil.getString(name, "method"); } @Nullable static Map getRetryPolicyFromMethodConfig(Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_RETRY_POLICY_KEY)) { - return null; - } - return JsonUtil.getObject(methodConfig, METHOD_CONFIG_RETRY_POLICY_KEY); + return JsonUtil.getObject(methodConfig, "retryPolicy"); } @Nullable static Map getHedgingPolicyFromMethodConfig(Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_HEDGING_POLICY_KEY)) { - return null; - } - return JsonUtil.getObject(methodConfig, METHOD_CONFIG_HEDGING_POLICY_KEY); + return JsonUtil.getObject(methodConfig, "hedgingPolicy"); } @Nullable static List> getNameListFromMethodConfig( Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_NAME_KEY)) { - return null; - } - return JsonUtil.checkObjectList(JsonUtil.getList(methodConfig, METHOD_CONFIG_NAME_KEY)); + return JsonUtil.getListOfObjects(methodConfig, "name"); } /** @@ -304,50 +222,28 @@ public final class ServiceConfigUtil { */ @Nullable static Long getTimeoutFromMethodConfig(Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_TIMEOUT_KEY)) { - return null; - } - String rawTimeout = JsonUtil.getString(methodConfig, METHOD_CONFIG_TIMEOUT_KEY); - try { - return parseDuration(rawTimeout); - } catch (ParseException e) { - throw new RuntimeException(e); - } + return JsonUtil.getStringAsDuration(methodConfig, "timeout"); } @Nullable static Boolean getWaitForReadyFromMethodConfig(Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_WAIT_FOR_READY_KEY)) { - return null; - } - return JsonUtil.getBoolean(methodConfig, METHOD_CONFIG_WAIT_FOR_READY_KEY); + return JsonUtil.getBoolean(methodConfig, "waitForReady"); } @Nullable static Integer getMaxRequestMessageBytesFromMethodConfig(Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY)) { - return null; - } - return JsonUtil.getDouble(methodConfig, METHOD_CONFIG_MAX_REQUEST_MESSAGE_BYTES_KEY).intValue(); + return JsonUtil.getNumberAsInteger(methodConfig, "maxRequestMessageBytes"); } @Nullable static Integer getMaxResponseMessageBytesFromMethodConfig(Map methodConfig) { - if (!methodConfig.containsKey(METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY)) { - return null; - } - return JsonUtil.getDouble(methodConfig, METHOD_CONFIG_MAX_RESPONSE_MESSAGE_BYTES_KEY) - .intValue(); + return JsonUtil.getNumberAsInteger(methodConfig, "maxResponseMessageBytes"); } @Nullable static List> getMethodConfigFromServiceConfig( Map serviceConfig) { - if (!serviceConfig.containsKey(SERVICE_CONFIG_METHOD_CONFIG_KEY)) { - return null; - } - return JsonUtil - .checkObjectList(JsonUtil.getList(serviceConfig, SERVICE_CONFIG_METHOD_CONFIG_KEY)); + return JsonUtil.getListOfObjects(serviceConfig, "methodConfig"); } /** @@ -371,17 +267,15 @@ public final class ServiceConfigUtil { } */ List> lbConfigs = new ArrayList<>(); - if (serviceConfig.containsKey(SERVICE_CONFIG_LOAD_BALANCING_CONFIG_KEY)) { - List configs = JsonUtil.getList(serviceConfig, SERVICE_CONFIG_LOAD_BALANCING_CONFIG_KEY); - for (Map config : JsonUtil.checkObjectList(configs)) { - lbConfigs.add(config); - } + String loadBalancingConfigKey = "loadBalancingConfig"; + if (serviceConfig.containsKey(loadBalancingConfigKey)) { + lbConfigs.addAll(JsonUtil.getListOfObjects( + serviceConfig, loadBalancingConfigKey)); } if (lbConfigs.isEmpty()) { // No LoadBalancingConfig found. Fall back to the deprecated LoadBalancingPolicy - if (serviceConfig.containsKey(SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY)) { - // TODO(zhangkun83): check if this is null. - String policy = JsonUtil.getString(serviceConfig, SERVICE_CONFIG_LOAD_BALANCING_POLICY_KEY); + String policy = JsonUtil.getString(serviceConfig, "loadBalancingPolicy"); + if (policy != null) { // Convert the policy to a config, so that the caller can handle them in the same way. policy = policy.toLowerCase(Locale.ROOT); Map fakeConfig = Collections.singletonMap(policy, Collections.emptyMap()); @@ -410,6 +304,9 @@ public final class ServiceConfigUtil { * Given a JSON list of LoadBalancingConfigs, and convert it into a list of LbConfig. */ public static List unwrapLoadBalancingConfigList(List> list) { + if (list == null) { + return null; + } ArrayList result = new ArrayList<>(); for (Map rawChildPolicy : list) { result.add(unwrapLoadBalancingConfig(rawChildPolicy)); @@ -422,7 +319,7 @@ public final class ServiceConfigUtil { */ @Nullable public static String getEdsServiceNameFromXdsConfig(Map rawXdsConfig) { - return JsonUtil.getString(rawXdsConfig, XDS_CONFIG_EDS_SERVICE_NAME); + return JsonUtil.getString(rawXdsConfig, "edsServiceName"); } /** @@ -430,7 +327,7 @@ public final class ServiceConfigUtil { */ @Nullable public static String getLrsServerNameFromXdsConfig(Map rawXdsConfig) { - return JsonUtil.getString(rawXdsConfig, XDS_CONFIG_LRS_SERVER_NAME); + return JsonUtil.getString(rawXdsConfig, "lrsLoadReportingServerName"); } /** @@ -438,11 +335,8 @@ public final class ServiceConfigUtil { */ @Nullable public static List getChildPolicyFromXdsConfig(Map rawXdsConfig) { - List rawChildPolicies = JsonUtil.getList(rawXdsConfig, XDS_CONFIG_CHILD_POLICY_KEY); - if (rawChildPolicies != null) { - return unwrapLoadBalancingConfigList(JsonUtil.checkObjectList(rawChildPolicies)); - } - return null; + return unwrapLoadBalancingConfigList( + JsonUtil.getListOfObjects(rawXdsConfig, "childPolicy")); } /** @@ -450,11 +344,8 @@ public final class ServiceConfigUtil { */ @Nullable public static List getFallbackPolicyFromXdsConfig(Map rawXdsConfig) { - List rawFallbackPolicies = JsonUtil.getList(rawXdsConfig, XDS_CONFIG_FALLBACK_POLICY_KEY); - if (rawFallbackPolicies != null) { - return unwrapLoadBalancingConfigList(JsonUtil.checkObjectList(rawFallbackPolicies)); - } - return null; + return unwrapLoadBalancingConfigList( + JsonUtil.getListOfObjects(rawXdsConfig, "fallbackPolicy")); } /** @@ -463,141 +354,7 @@ public final class ServiceConfigUtil { @Nullable public static String getStickinessMetadataKeyFromServiceConfig( Map serviceConfig) { - if (!serviceConfig.containsKey(SERVICE_CONFIG_STICKINESS_METADATA_KEY)) { - return null; - } - return JsonUtil.getString(serviceConfig, SERVICE_CONFIG_STICKINESS_METADATA_KEY); - } - - /** - * Parse from a string to produce a duration. Copy of - * {@link com.google.protobuf.util.Durations#parse}. - * - * @return A Duration parsed from the string. - * @throws ParseException if parsing fails. - */ - private static long parseDuration(String value) throws ParseException { - // Must ended with "s". - if (value.isEmpty() || value.charAt(value.length() - 1) != 's') { - throw new ParseException("Invalid duration string: " + value, 0); - } - boolean negative = false; - if (value.charAt(0) == '-') { - negative = true; - value = value.substring(1); - } - String secondValue = value.substring(0, value.length() - 1); - String nanoValue = ""; - int pointPosition = secondValue.indexOf('.'); - if (pointPosition != -1) { - nanoValue = secondValue.substring(pointPosition + 1); - secondValue = secondValue.substring(0, pointPosition); - } - long seconds = Long.parseLong(secondValue); - int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue); - if (seconds < 0) { - throw new ParseException("Invalid duration string: " + value, 0); - } - if (negative) { - seconds = -seconds; - nanos = -nanos; - } - try { - return normalizedDuration(seconds, nanos); - } catch (IllegalArgumentException e) { - throw new ParseException("Duration value is out of range.", 0); - } - } - - /** - * Copy of {@link com.google.protobuf.util.Timestamps#parseNanos}. - */ - private static int parseNanos(String value) throws ParseException { - int result = 0; - for (int i = 0; i < 9; ++i) { - result = result * 10; - if (i < value.length()) { - if (value.charAt(i) < '0' || value.charAt(i) > '9') { - throw new ParseException("Invalid nanoseconds.", 0); - } - result += value.charAt(i) - '0'; - } - } - return result; - } - - private static final long NANOS_PER_SECOND = TimeUnit.SECONDS.toNanos(1); - - /** - * Copy of {@link com.google.protobuf.util.Durations#normalizedDuration}. - */ - @SuppressWarnings("NarrowingCompoundAssignment") - private static long normalizedDuration(long seconds, int nanos) { - if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) { - seconds = checkedAdd(seconds, nanos / NANOS_PER_SECOND); - nanos %= NANOS_PER_SECOND; - } - if (seconds > 0 && nanos < 0) { - nanos += NANOS_PER_SECOND; // no overflow since nanos is negative (and we're adding) - seconds--; // no overflow since seconds is positive (and we're decrementing) - } - if (seconds < 0 && nanos > 0) { - nanos -= NANOS_PER_SECOND; // no overflow since nanos is positive (and we're subtracting) - seconds++; // no overflow since seconds is negative (and we're incrementing) - } - if (!durationIsValid(seconds, nanos)) { - throw new IllegalArgumentException(String.format( - "Duration is not valid. See proto definition for valid values. " - + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]. " - + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. " - + "Nanos must have the same sign as seconds", seconds, nanos)); - } - return saturatedAdd(TimeUnit.SECONDS.toNanos(seconds), nanos); - } - - /** - * Returns true if the given number of seconds and nanos is a valid {@code Duration}. The {@code - * seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The {@code nanos} - * value must be in the range [-999,999,999, +999,999,999]. - * - *

Note: Durations less than one second are represented with a 0 {@code seconds} field - * and a positive or negative {@code nanos} field. For durations of one second or more, a non-zero - * value for the {@code nanos} field must be of the same sign as the {@code seconds} field. - * - *

Copy of {@link com.google.protobuf.util.Duration#isValid}.

- */ - private static boolean durationIsValid(long seconds, int nanos) { - if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) { - return false; - } - if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) { - return false; - } - if (seconds < 0 || nanos < 0) { - if (seconds > 0 || nanos > 0) { - return false; - } - } - return true; - } - - /** - * Returns the sum of {@code a} and {@code b} unless it would overflow or underflow in which case - * {@code Long.MAX_VALUE} or {@code Long.MIN_VALUE} is returned, respectively. - * - *

Copy of {@link com.google.common.math.LongMath#saturatedAdd}.

- * - */ - @SuppressWarnings("ShortCircuitBoolean") - private static long saturatedAdd(long a, long b) { - long naiveSum = a + b; - if ((a ^ b) < 0 | (a ^ naiveSum) >= 0) { - // If a and b have different signs or a has the same sign as the result then there was no - // overflow, return. - return naiveSum; - } - // we did over/under flow, if the sign is negative we should return MAX otherwise MIN - return Long.MAX_VALUE + ((naiveSum >>> (Long.SIZE - 1)) ^ 1); + return JsonUtil.getString(serviceConfig, "stickinessMetadataKey"); } /**