fix: remove unused Platform serialization code path by maxday · Pull Request #648 · aws/aws-lambda-java-libs · GitHub
Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions aws-lambda-java-runtime-interface-client/RELEASE.CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

package com.amazonaws.services.lambda.runtime.api.client;

import com.amazonaws.services.lambda.runtime.ClientContext;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.LambdaRuntimeInternal;
Expand Down Expand Up @@ -38,11 +37,9 @@
import java.lang.reflect.TypeVariable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import static com.amazonaws.services.lambda.runtime.api.client.UserFault.filterStackTrace;
import static com.amazonaws.services.lambda.runtime.api.client.UserFault.makeUserFault;
Expand All @@ -51,16 +48,10 @@
public final class EventHandlerLoader {
private static final byte[] _JsonNull = new byte[]{'n', 'u', 'l', 'l'};

private enum Platform {
ANDROID,
IOS,
UNKNOWN
}

private static volatile ThreadLocal<PojoSerializer<LambdaClientContext>> contextSerializer = new ThreadLocal<>();
private static volatile ThreadLocal<PojoSerializer<LambdaCognitoIdentity>> cognitoSerializer = new ThreadLocal<>();

private static final ThreadLocal<EnumMap<Platform, Map<Type, PojoSerializer<Object>>>> typeCache = ThreadLocal.withInitial(() -> new EnumMap<>(Platform.class));
private static final ThreadLocal<Map<Type, PojoSerializer<Object>>> typeCache = ThreadLocal.withInitial(HashMap::new);

private static final Comparator<Method> methodPriority = new Comparator<Method>() {
public int compare(Method lhs, Method rhs) {
Expand Down Expand Up @@ -97,16 +88,15 @@ private EventHandlerLoader() {
}

/**
* returns the appropriate serializer for the class based on platform and whether the class is a supported event
* returns the serializer for the type: the customer's CustomPojoSerializer if one is registered, the event
* serializer if the type is a supported Lambda event, and Jackson otherwise
*
* @param platform enum platform
* @param type Type of object used
* @param type Type of object used
* @return PojoSerializer
* @see Platform for which platforms are used
* @see LambdaEventSerializers for how mixins and modules are added to the serializer
*/
@SuppressWarnings({"unchecked", "rawtypes"})
private static PojoSerializer<Object> getSerializer(Platform platform, Type type) {
private static PojoSerializer<Object> getSerializer(Type type) {
PojoSerializer<Object> customSerializer = PojoSerializerLoader.getCustomerSerializer(type);
if (customSerializer != null) {
return customSerializer;
Expand All @@ -119,24 +109,15 @@ private static PojoSerializer<Object> getSerializer(Platform platform, Type type
return LambdaEventSerializers.serializerFor(clazz, AWSLambda.getCustomerClassLoader());
}
}
// else platform dependent (Android uses GSON but all other platforms use Jackson)
if (Objects.requireNonNull(platform) == Platform.ANDROID) {
return GsonFactory.getInstance().getSerializer(type);
}
return JacksonFactory.getInstance().getSerializer(type);
}

private static PojoSerializer<Object> getSerializerCached(Platform platform, Type type) {
EnumMap<Platform, Map<Type, PojoSerializer<Object>>> threadTypeCache = typeCache.get();
Map<Type, PojoSerializer<Object>> cache = threadTypeCache.get(platform);
if (cache == null) {
cache = new HashMap<>();
threadTypeCache.put(platform, cache);
}
private static PojoSerializer<Object> getSerializerCached(Type type) {
Map<Type, PojoSerializer<Object>> cache = typeCache.get();

PojoSerializer<Object> serializer = cache.get(type);
if (serializer == null) {
serializer = getSerializer(platform, type);
serializer = getSerializer(type);
cache.put(type, serializer);
}

Expand All @@ -158,31 +139,6 @@ private static PojoSerializer<LambdaCognitoIdentity> getCognitoSerializer() {
}


private static Platform getPlatform(Context context) {
ClientContext cc = context.getClientContext();
if (cc == null) {
return Platform.UNKNOWN;
}

Map<String, String> env = cc.getEnvironment();
if (env == null) {
return Platform.UNKNOWN;
}

String platform = env.get("platform");
if (platform == null) {
return Platform.UNKNOWN;
}

if ("Android".equalsIgnoreCase(platform)) {
return Platform.ANDROID;
} else if ("iPhoneOS".equalsIgnoreCase(platform)) {
return Platform.IOS;
} else {
return Platform.UNKNOWN;
}
}

private static boolean isVoid(Type type) {
return Void.TYPE.equals(type) || (type instanceof Class) && Void.class.isAssignableFrom((Class<?>) type);
}
Expand Down Expand Up @@ -629,11 +585,11 @@ public PojoHandlerAsStreamHandler(


if (inputType.isPresent()) {
getSerializerCached(Platform.UNKNOWN, inputType.get());
getSerializerCached(inputType.get());
}

if (outputType.isPresent()) {
getSerializerCached(Platform.UNKNOWN, outputType.get());
getSerializerCached(outputType.get());
}
}

Expand All @@ -642,10 +598,9 @@ public PojoHandlerAsStreamHandler(
public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
throws IOException {
final Object input;
final Platform platform = getPlatform(context);
try {
if (inputType.isPresent()) {
input = getSerializerCached(platform, inputType.get()).fromJson(inputStream);
input = getSerializerCached(inputType.get()).fromJson(inputStream);
} else {
input = null;
}
Expand All @@ -662,7 +617,7 @@ public void handleRequest(InputStream inputStream, OutputStream outputStream, Co

try {
if (outputType.isPresent()) {
PojoSerializer<Object> serializer = getSerializerCached(platform, outputType.get());
PojoSerializer<Object> serializer = getSerializerCached(outputType.get());
serializer.toJson(output, outputStream);
} else {
outputStream.write(_JsonNull);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.amazonaws.services.lambda.runtime.api.client.runtimeapi.dto.InvocationRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
Expand All @@ -11,6 +13,7 @@
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
Expand Down Expand Up @@ -59,6 +62,46 @@ void PojoHandlerTest_twoParams() throws Exception {
assertSuccessfulInvocation(lambdaRequestHandler);
}

// Before 2.13.0, a client context with env.platform equal to "Android" (case-insensitive) switched POJO
// serialization to Gson, which binds fields: pojoOutputHandler returned {"internalField":"field-based-value"}
// and pojoInputHandler returned "x:true". Every client context must now produce the Jackson result.

static Stream<String> clientContexts() {
return Stream.of(
null,
"{\"env\":{\"platform\":\"Android\"}}",
"{\"env\":{\"platform\":\"android\"}}",
"{\"env\":{\"platform\":\"iPhoneOS\"}}",
"{\"env\":{}}",
"{}");
}

@ParameterizedTest(name = "clientContext={0}")
@MethodSource("clientContexts")
void PojoHandler_outputSerializer_ignoresClientContextPlatform(String clientContext) throws Exception {
LambdaRequestHandler handler =
getLambdaRequestHandler("test.lambda.handlers.POJOHanlderImpl::pojoOutputHandler");

InvocationRequest request = getTestInvocationRequest();
request.setClientContext(clientContext);

assertEquals("{\"beanProperty\":\"property-based-value\"}", handler.call(request).toString());
}

@ParameterizedTest(name = "clientContext={0}")
@MethodSource("clientContexts")
void PojoHandler_inputSerializer_ignoresClientContextPlatform(String clientContext) throws Exception {
LambdaRequestHandler handler =
getLambdaRequestHandler("test.lambda.handlers.POJOHanlderImpl::pojoInputHandler");

InvocationRequest request = getTestInvocationRequest();
request.setClientContext(clientContext);
request.setContent("{\"name\":\"x\",\"locked\":true}".getBytes());

// name has a setter and binds; locked has only a getter and keeps its default
assertEquals("\"x:false\"", handler.call(request).toString());
}

private LambdaRequestHandler getLambdaRequestHandler(String handler) throws ClassNotFoundException {
ClassLoader cl = this.getClass().getClassLoader();
HandlerInfo handlerInfo = HandlerInfo.fromString(handler, cl);
Expand Down
Loading
Loading