Try to detect single-valued returns in AWS
Lambdas generally only deal with single values anyway, but for consistency we should return similar results from a Lambda handler as from an MVC handler in s-c-f-web.
This commit is contained in:
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2017 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License 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 org.springframework.cloud.function.adapter.aws;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Mark Fisher
|
|
||||||
*/
|
|
||||||
public class FunctionInvokingKinesisEventHandler extends SpringBootRequestHandler<KinesisEvent, String> {
|
|
||||||
|
|
||||||
public FunctionInvokingKinesisEventHandler(Class<?> configurationClass) {
|
|
||||||
super(configurationClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String convertEvent(KinesisEvent event) {
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
for (KinesisEventRecord record : event.getRecords()) {
|
|
||||||
String id = record.getEventID();
|
|
||||||
String name = record.getEventName();
|
|
||||||
String source = record.getEventSource();
|
|
||||||
Date timestamp = record.getKinesis().getApproximateArrivalTimestamp();
|
|
||||||
String partitionKey = record.getKinesis().getPartitionKey();
|
|
||||||
String sequenceNumber = record.getKinesis().getSequenceNumber();
|
|
||||||
ByteBuffer data = record.getKinesis().getData();
|
|
||||||
String dataString = new String(data.array(), Charset.forName("UTF-8"));
|
|
||||||
result.append(String.format("id=%s,name=%s,source=%s,timestamp=%s,partitionKey=%s,sequenceNumber=%s,data=%s",
|
|
||||||
id, name, source, timestamp, partitionKey, sequenceNumber, dataString));
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,19 +16,28 @@
|
|||||||
|
|
||||||
package org.springframework.cloud.function.adapter.aws;
|
package org.springframework.cloud.function.adapter.aws;
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.S3Event;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.KinesisEvent;
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.KinesisEvent.KinesisEventRecord;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
*/
|
*/
|
||||||
public class FunctionInvokingS3EventHandler extends SpringBootRequestHandler<S3Event, String> {
|
public class SpringBootKinesisEventHandler
|
||||||
|
extends SpringBootRequestHandler<KinesisEvent, String> {
|
||||||
|
|
||||||
public FunctionInvokingS3EventHandler(Class<?> configurationClass) {
|
public SpringBootKinesisEventHandler() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpringBootKinesisEventHandler(Class<?> configurationClass) {
|
||||||
super(configurationClass);
|
super(configurationClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String convertEvent(S3Event event) {
|
protected List<KinesisEventRecord> convertEvent(KinesisEvent event) {
|
||||||
return event.toJson();
|
// TODO: maybe convert to List<Message>
|
||||||
|
return event.getRecords();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -28,7 +28,7 @@ import reactor.core.publisher.Flux;
|
|||||||
/**
|
/**
|
||||||
* @author Mark Fisher
|
* @author Mark Fisher
|
||||||
*/
|
*/
|
||||||
public class SpringBootRequestHandler<E, O> extends SpringFunctionInitializer implements RequestHandler<E, List<O>> {
|
public class SpringBootRequestHandler<E, O> extends SpringFunctionInitializer implements RequestHandler<E, Object> {
|
||||||
|
|
||||||
public SpringBootRequestHandler(Class<?> configurationClass) {
|
public SpringBootRequestHandler(Class<?> configurationClass) {
|
||||||
super(configurationClass);
|
super(configurationClass);
|
||||||
@@ -39,24 +39,26 @@ public class SpringBootRequestHandler<E, O> extends SpringFunctionInitializer im
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<O> handleRequest(E event, Context context) {
|
public Object handleRequest(E event, Context context) {
|
||||||
initialize();
|
initialize();
|
||||||
Object input = convertEvent(event);
|
Object input = convertEvent(event);
|
||||||
Flux<?> output = apply(extract(input));
|
Flux<?> output = apply(extract(input));
|
||||||
return result(output);
|
return result(input, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<O> result(Flux<?> output) {
|
private Object result(Object input, Flux<?> output) {
|
||||||
List<Object> result = new ArrayList<>();
|
List<Object> result = new ArrayList<>();
|
||||||
for (Object value : output.toIterable()) {
|
for (Object value : output.toIterable()) {
|
||||||
result.add(value);
|
result.add(value);
|
||||||
}
|
}
|
||||||
return convertResult(result);
|
if (isSingleValue(input) && result.size()==1) {
|
||||||
|
return result.get(0);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
private boolean isSingleValue(Object input) {
|
||||||
protected List<O> convertResult(List<Object> value) {
|
return !(input instanceof Collection);
|
||||||
return (List<O>) value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<?> extract(Object input) {
|
private Flux<?> extract(Object input) {
|
||||||
|
|||||||
@@ -54,17 +54,24 @@ public class SpringBootStreamHandler extends SpringFunctionInitializer
|
|||||||
initialize();
|
initialize();
|
||||||
Object value = convertStream(input);
|
Object value = convertStream(input);
|
||||||
Flux<?> flux = apply(extract(value));
|
Flux<?> flux = apply(extract(value));
|
||||||
mapper.writeValue(output, result(flux));
|
mapper.writeValue(output, result(value, flux));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object result(Flux<?> flux) {
|
private Object result(Object input, Flux<?> flux) {
|
||||||
List<Object> result = new ArrayList<>();
|
List<Object> result = new ArrayList<>();
|
||||||
for (Object value : flux.toIterable()) {
|
for (Object value : flux.toIterable()) {
|
||||||
result.add(value);
|
result.add(value);
|
||||||
}
|
}
|
||||||
|
if (isSingleValue(input) && result.size()==1) {
|
||||||
|
return result.get(0);
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isSingleValue(Object input) {
|
||||||
|
return !(input instanceof Collection);
|
||||||
|
}
|
||||||
|
|
||||||
private Flux<?> extract(Object input) {
|
private Flux<?> extract(Object input) {
|
||||||
if (input instanceof Collection) {
|
if (input instanceof Collection) {
|
||||||
return Flux.fromIterable((Iterable<?>) input);
|
return Flux.fromIterable((Iterable<?>) input);
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License 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 org.springframework.cloud.function.adapter.aws;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||||
|
import org.springframework.cloud.function.context.ContextFunctionCatalogAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SpringBootRequestHandlerTests {
|
||||||
|
|
||||||
|
private SpringBootRequestHandler<Foo, Bar> handler;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void functionBean() throws Exception {
|
||||||
|
handler = new SpringBootRequestHandler<Foo, Bar>(FunctionConfig.class);
|
||||||
|
handler.initialize();
|
||||||
|
Object output = handler.handleRequest(new Foo("foo"), null);
|
||||||
|
assertThat(output).isInstanceOf(Bar.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import({ ContextFunctionCatalogAutoConfiguration.class,
|
||||||
|
JacksonAutoConfiguration.class })
|
||||||
|
protected static class FunctionConfig {
|
||||||
|
@Bean
|
||||||
|
public Function<Foo, Bar> function() {
|
||||||
|
return foo -> new Bar(foo.getValue().toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Foo {
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Foo(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Bar {
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Bar() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bar(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016-2017 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License 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 org.springframework.cloud.function.adapter.aws;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||||
|
import org.springframework.cloud.function.context.ContextFunctionCatalogAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Dave Syer
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SpringBootStreamHandlerTests {
|
||||||
|
|
||||||
|
private SpringBootStreamHandler handler;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void functionBean() throws Exception {
|
||||||
|
handler = new SpringBootStreamHandler(FunctionConfig.class);
|
||||||
|
handler.initialize();
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
handler.handleRequest(new ByteArrayInputStream("{\"value\":\"foo\"}".getBytes()),
|
||||||
|
output, null);
|
||||||
|
assertThat(output.toString()).isEqualTo("{\"value\":\"FOO\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Import({ ContextFunctionCatalogAutoConfiguration.class,
|
||||||
|
JacksonAutoConfiguration.class })
|
||||||
|
protected static class FunctionConfig {
|
||||||
|
@Bean
|
||||||
|
public Function<Foo, Bar> function() {
|
||||||
|
return foo -> new Bar(foo.getValue().toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Foo {
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Bar {
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
public Bar() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bar(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user