diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventAttributesProvider.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventAttributesProvider.java index 3ad1a9395..5e924d397 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventAttributesProvider.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventAttributesProvider.java @@ -19,48 +19,14 @@ package org.springframework.cloud.function.cloudevent; import java.util.Map; import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; /** * * @author Oleg Zhurakousky * @since 3.1 */ +@FunctionalInterface public interface CloudEventAttributesProvider { - - /** - * Will construct instance of {@link CloudEventAttributes} setting its required attributes. - * - * @param ce_id value for Cloud Event 'id' attribute - * @param ce_specversion value for Cloud Event 'specversion' attribute - * @param ce_source value for Cloud Event 'source' attribute - * @param ce_type value for Cloud Event 'type' attribute - * @return instance of {@link CloudEventAttributes} - */ - CloudEventAttributes get(String ce_id, String ce_specversion, String ce_source, String ce_type); - - /** - * Will construct instance of {@link CloudEventAttributes} - * Should default/generate cloud event ID and SPECVERSION. - * - * @param ce_source value for Cloud Event 'source' attribute - * @param ce_type value for Cloud Event 'type' attribute - * @return instance of {@link CloudEventAttributes} - */ - CloudEventAttributes get(String ce_source, String ce_type); - - - /** - * Will construct instance of {@link CloudEventAttributes} from {@link MessageHeaders}. - * - * Should copy Cloud Event related headers into an instance of {@link CloudEventAttributes} - * NOTE: Certain headers must not be copied. - * - * @param headers instance of {@link MessageHeaders} - * @return modifiable instance of {@link CloudEventAttributes} - */ - RequiredAttributeAccessor get(MessageHeaders headers); - /** * * @param inputMessage input message used to invoke user functionality (e.g., function) diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventMessageUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventMessageUtils.java index c5cbf8e61..9ecd1c9fa 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventMessageUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventMessageUtils.java @@ -16,9 +16,13 @@ package org.springframework.cloud.function.cloudevent; +import java.util.HashMap; import java.util.Map; +import java.util.UUID; import org.springframework.messaging.Message; +import org.springframework.messaging.MessageHeaders; +import org.springframework.util.Assert; import org.springframework.util.MimeType; import org.springframework.util.MimeTypeUtils; @@ -155,4 +159,52 @@ public final class CloudEventMessageUtils { && headers.containsKey(CE_SPECVERSION) && headers.containsKey(CE_TYPE)); } + + + /** + * Will construct instance of {@link CloudEventAttributes} setting its required attributes. + * + * @param ce_id value for Cloud Event 'id' attribute + * @param ce_specversion value for Cloud Event 'specversion' attribute + * @param ce_source value for Cloud Event 'source' attribute + * @param ce_type value for Cloud Event 'type' attribute + * @return instance of {@link CloudEventAttributes} + */ + public static CloudEventAttributes get(String ce_id, String ce_specversion, String ce_source, String ce_type) { + Assert.hasText(ce_id, "'ce_id' must not be null or empty"); + Assert.hasText(ce_specversion, "'ce_specversion' must not be null or empty"); + Assert.hasText(ce_source, "'ce_source' must not be null or empty"); + Assert.hasText(ce_type, "'ce_type' must not be null or empty"); + Map requiredAttributes = new HashMap<>(); + requiredAttributes.put(CloudEventMessageUtils.CE_ID, ce_id); + requiredAttributes.put(CloudEventMessageUtils.CE_SPECVERSION, ce_specversion); + requiredAttributes.put(CloudEventMessageUtils.CE_SOURCE, ce_source); + requiredAttributes.put(CloudEventMessageUtils.CE_TYPE, ce_type); + return new CloudEventAttributes(requiredAttributes); + } + + /** + * Will construct instance of {@link CloudEventAttributes} + * Should default/generate cloud event ID and SPECVERSION. + * + * @param ce_source value for Cloud Event 'source' attribute + * @param ce_type value for Cloud Event 'type' attribute + * @return instance of {@link CloudEventAttributes} + */ + public static CloudEventAttributes get(String ce_source, String ce_type) { + return get(UUID.randomUUID().toString(), "1.0", ce_source, ce_type); + } + + /** + * Will construct instance of {@link CloudEventAttributes} from {@link MessageHeaders}. + * + * Should copy Cloud Event related headers into an instance of {@link CloudEventAttributes} + * NOTE: Certain headers must not be copied. + * + * @param headers instance of {@link MessageHeaders} + * @return modifiable instance of {@link CloudEventAttributes} + */ + public static RequiredAttributeAccessor get(MessageHeaders headers) { + return new RequiredAttributeAccessor(headers); + } } diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/DefaultCloudEventAttributesProvider.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/DefaultCloudEventAttributesProvider.java index d1ba9430e..8e7216b83 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/DefaultCloudEventAttributesProvider.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/DefaultCloudEventAttributesProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2019 the original author or authors. + * Copyright 2020-2020 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. @@ -17,7 +17,6 @@ package org.springframework.cloud.function.cloudevent; import java.util.Collections; -import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -27,8 +26,6 @@ import org.springframework.context.ApplicationContextAware; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.messaging.Message; -import org.springframework.messaging.MessageHeaders; -import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** @@ -41,38 +38,12 @@ public class DefaultCloudEventAttributesProvider implements CloudEventAttributes private ConfigurableApplicationContext applicationContext; - @Override - public CloudEventAttributes get(String ce_id, String ce_specversion, String ce_source, String ce_type) { - Assert.hasText(ce_id, "'ce_id' must not be null or empty"); - Assert.hasText(ce_specversion, "'ce_specversion' must not be null or empty"); - Assert.hasText(ce_source, "'ce_source' must not be null or empty"); - Assert.hasText(ce_type, "'ce_type' must not be null or empty"); - Map requiredAttributes = new HashMap<>(); - requiredAttributes.put(CloudEventMessageUtils.CE_ID, ce_id); - requiredAttributes.put(CloudEventMessageUtils.CE_SPECVERSION, ce_specversion); - requiredAttributes.put(CloudEventMessageUtils.CE_SOURCE, ce_source); - requiredAttributes.put(CloudEventMessageUtils.CE_TYPE, ce_type); - return new CloudEventAttributes(requiredAttributes); - } - - @Override - public CloudEventAttributes get(String ce_source, String ce_type) { - return this.get(UUID.randomUUID().toString(), "1.0", ce_source, ce_type); - } - - /** - * By default it will copy all the headers while exposing accessor to allow user to modify any of them. - */ - @Override - public RequiredAttributeAccessor get(MessageHeaders headers) { - return new RequiredAttributeAccessor(headers); - } @Override public Map generateDefaultCloudEventHeaders(Message inputMessage, Object result) { if (inputMessage.getHeaders().containsKey(CloudEventMessageUtils.CE_ID)) { // input is a cloud event String applicationName = this.getApplicationName(); - return this.get(inputMessage.getHeaders()) + return CloudEventMessageUtils.get(inputMessage.getHeaders()) .setId(UUID.randomUUID().toString()) .setType(result.getClass().getName()) .setSource(applicationName); diff --git a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/cloudevent/CloudEventTypeConversionTests.java b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/cloudevent/CloudEventTypeConversionTests.java index 55c04d649..d77f694a8 100644 --- a/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/cloudevent/CloudEventTypeConversionTests.java +++ b/spring-cloud-function-context/src/test/java/org/springframework/cloud/function/cloudevent/CloudEventTypeConversionTests.java @@ -45,8 +45,7 @@ public class CloudEventTypeConversionTests { @Test public void testFromMessageBinaryPayloadMatchesType() { SmartCompositeMessageConverter messageConverter = this.configure(DummyConfiguration.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); - CloudEventAttributes ceAttributes = ceAttrProvider + CloudEventAttributes ceAttributes = CloudEventMessageUtils .get(UUID.randomUUID().toString(), "1.0", "https://spring.io/", "org.springframework"); ceAttributes.setDataContentType("text/plain"); Message message = MessageBuilder.withPayload("Hello Ricky").copyHeaders(ceAttributes).build(); @@ -58,8 +57,7 @@ public class CloudEventTypeConversionTests { @Test public void testFromMessageBinaryPayloadDoesNotMatchType() { SmartCompositeMessageConverter messageConverter = this.configure(DummyConfiguration.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); - CloudEventAttributes ceAttributes = ceAttrProvider + CloudEventAttributes ceAttributes = CloudEventMessageUtils .get(UUID.randomUUID().toString(), "1.0", "https://spring.io/", "org.springframework"); Message message = MessageBuilder.withPayload("Hello Ricky".getBytes()) .copyHeaders(ceAttributes) @@ -70,12 +68,10 @@ public class CloudEventTypeConversionTests { assertThat(converted).isEqualTo("Hello Ricky"); } - @Test // JsonMessageConverter does some special things between byte[] and String so - // this works + @Test // JsonMessageConverter does some special things between byte[] and String so this works public void testFromMessageBinaryPayloadNoDataContentTypeToString() { SmartCompositeMessageConverter messageConverter = this.configure(DummyConfiguration.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); - CloudEventAttributes ceAttributes = ceAttrProvider + CloudEventAttributes ceAttributes = CloudEventMessageUtils .get(UUID.randomUUID().toString(), "1.0", "https://spring.io/", "org.springframework"); Message message = MessageBuilder.withPayload("Hello Ricky".getBytes()) .copyHeaders(ceAttributes) @@ -89,8 +85,7 @@ public class CloudEventTypeConversionTests { @Test // Unlike the previous test the type here is POJO so no special treatement public void testFromMessageBinaryPayloadNoDataContentTypeToPOJO() { SmartCompositeMessageConverter messageConverter = this.configure(DummyConfiguration.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); - CloudEventAttributes ceAttributes = ceAttrProvider.get("https://spring.io/", "org.springframework"); + CloudEventAttributes ceAttributes = CloudEventMessageUtils.get("https://spring.io/", "org.springframework"); Message message = MessageBuilder.withPayload("Hello Ricky".getBytes()) .copyHeaders(ceAttributes) .setHeader(MessageHeaders.CONTENT_TYPE, @@ -103,8 +98,7 @@ public class CloudEventTypeConversionTests { @Test // will fall on default CT which is json public void testFromMessageBinaryPayloadNoDataContentTypeToPOJOThatWorks() { SmartCompositeMessageConverter messageConverter = this.configure(DummyConfiguration.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); - CloudEventAttributes ceAttributes = ceAttrProvider.get("https://spring.io/", "org.springframework"); + CloudEventAttributes ceAttributes = CloudEventMessageUtils.get("https://spring.io/", "org.springframework"); Message message = MessageBuilder.withPayload("{\"name\":\"Ricky\"}".getBytes()) .copyHeaders(ceAttributes) .setHeader(MessageHeaders.CONTENT_TYPE, diff --git a/spring-cloud-function-samples/function-sample-cloudevent/src/main/java/io/spring/cloudevent/CloudeventDemoApplication.java b/spring-cloud-function-samples/function-sample-cloudevent/src/main/java/io/spring/cloudevent/CloudeventDemoApplication.java index 291c19001..c23b0fab8 100644 --- a/spring-cloud-function-samples/function-sample-cloudevent/src/main/java/io/spring/cloudevent/CloudeventDemoApplication.java +++ b/spring-cloud-function-samples/function-sample-cloudevent/src/main/java/io/spring/cloudevent/CloudeventDemoApplication.java @@ -16,11 +16,7 @@ package io.spring.cloudevent; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Collections; import java.util.Map; -import java.util.UUID; import java.util.function.Function; import org.springframework.boot.SpringApplication; @@ -28,8 +24,6 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.function.cloudevent.CloudEventAttributes; import org.springframework.cloud.function.cloudevent.CloudEventAttributesProvider; import org.springframework.cloud.function.cloudevent.CloudEventMessageUtils; -import org.springframework.cloud.function.cloudevent.DefaultCloudEventAttributesProvider; -import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.support.MessageBuilder; @@ -96,7 +90,7 @@ public class CloudeventDemoApplication { data.setVersion("2.0"); data.setReleaseDateAsString("01-10-2006"); - CloudEventAttributes ceAttributes = ceAttrProvider.get(ceMessage.getHeaders()) + CloudEventAttributes ceAttributes = CloudEventMessageUtils.get(ceMessage.getHeaders()) .setSource("https://interface21.com/") .setType("com.interface21"); diff --git a/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationFunctionTests.java b/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationFunctionTests.java index a3fef454c..5fe8a7a30 100644 --- a/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationFunctionTests.java +++ b/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationFunctionTests.java @@ -20,8 +20,7 @@ import java.util.function.Function; import org.junit.jupiter.api.Test; import org.springframework.boot.SpringApplication; -import org.springframework.cloud.function.cloudevent.CloudEventAttributesProvider; -import org.springframework.cloud.function.cloudevent.DefaultCloudEventAttributesProvider; +import org.springframework.cloud.function.cloudevent.CloudEventMessageUtils; import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.messaging.Message; @@ -39,10 +38,9 @@ public class CloudeventDemoApplicationFunctionTests { try(ConfigurableApplicationContext context = SpringApplication.run(CloudeventDemoApplication.class)) { FunctionCatalog catalog = context.getBean(FunctionCatalog.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); Message binaryCloudEventMessage = MessageBuilder .withPayload("{\"releaseDate\":\"24-03-2004\", \"releaseName\":\"Spring Framework\", \"version\":\"1.0\"}") - .copyHeaders(ceAttrProvider.get("spring.io/spring-event", "com.example.springevent")) + .copyHeaders(CloudEventMessageUtils.get("spring.io/spring-event", "com.example.springevent")) .build(); /* @@ -68,10 +66,9 @@ public class CloudeventDemoApplicationFunctionTests { public void demoPureFunctionProduceConsumeCloudEvent() { try(ConfigurableApplicationContext context = SpringApplication.run(CloudeventDemoApplication.class)) { FunctionCatalog catalog = context.getBean(FunctionCatalog.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); Message binaryCloudEventMessage = MessageBuilder .withPayload("{\"releaseDate\":\"24-03-2004\", \"releaseName\":\"Spring Framework\", \"version\":\"1.0\"}") - .copyHeaders(ceAttrProvider.get("spring.io/spring-event", "com.example.springevent")) + .copyHeaders(CloudEventMessageUtils.get("spring.io/spring-event", "com.example.springevent")) .build(); /* @@ -88,10 +85,9 @@ public class CloudeventDemoApplicationFunctionTests { public void demoPureFunctionProduceConsumeCloudEventAsPojo() { try(ConfigurableApplicationContext context = SpringApplication.run(CloudeventDemoApplication.class)) { FunctionCatalog catalog = context.getBean(FunctionCatalog.class); - CloudEventAttributesProvider ceAttrProvider = new DefaultCloudEventAttributesProvider(); Message binaryCloudEventMessage = MessageBuilder .withPayload("{\"releaseDate\":\"24-03-2004\", \"releaseName\":\"Spring Framework\", \"version\":\"1.0\"}") - .copyHeaders(ceAttrProvider.get("spring.io/spring-event", "com.example.springevent")) + .copyHeaders(CloudEventMessageUtils.get("spring.io/spring-event", "com.example.springevent")) .build(); /* diff --git a/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationRESTTests.java b/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationRESTTests.java index 1a9f3b195..3e2602afd 100644 --- a/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationRESTTests.java +++ b/spring-cloud-function-samples/function-sample-cloudevent/src/test/java/io/spring/cloudevent/CloudeventDemoApplicationRESTTests.java @@ -35,6 +35,7 @@ import org.springframework.cloud.function.cloudevent.CloudEventJsonMessageConver import org.springframework.cloud.function.cloudevent.CloudEventMessageUtils; import org.springframework.cloud.function.cloudevent.DefaultCloudEventAttributesProvider; import org.springframework.cloud.function.json.JsonMapper; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; @@ -241,6 +242,51 @@ public class CloudeventDemoApplicationRESTTests { .isEqualTo(Collections.singletonList(SpringReleaseEvent.class.getName())); } + @Test + public void testAsStructuralPojoToPojo() throws Exception { + ApplicationContext context = SpringApplication.run(CloudeventDemoApplication.class); + JsonMapper mapper = context.getBean(JsonMapper.class); + + String payload = "{\n" + + " \"specversion\" : \"1.0\",\n" + + " \"type\" : \"org.springframework\",\n" + + " \"source\" : \"https://spring.io/\",\n" + + " \"id\" : \"A234-1234-1234\",\n" + +// " \"ce-datacontenttype\" : \"application/json\",\n" + + " \"data\" : {\n" + + " \"version\" : \"1.0\",\n" + + " \"releaseName\" : \"Spring Framework\",\n" + + " \"releaseDate\" : \"24-03-2004\"\n" + + " }\n" + + "}"; + + System.out.println(payload); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.valueOf("application/cloudevents+json;charset=utf-8")); + + RequestEntity re = new RequestEntity<>(payload, headers, HttpMethod.POST, this.constructURI("/consumeAndProduceCloudEventAsPojoToPojo")); + ResponseEntity response = testRestTemplate.exchange(re, String.class); + + SpringReleaseEvent springReleaseEvent = mapper.fromJson(response.getBody(), SpringReleaseEvent.class); + + assertThat(springReleaseEvent.getReleaseName()).isEqualTo("Spring Framework"); + assertThat(springReleaseEvent.getVersion()).isEqualTo("2.0"); + + re = new RequestEntity<>(payload, headers, HttpMethod.POST, this.constructURI("/consumeAndProduceCloudEventAsMapToMap")); + response = testRestTemplate.exchange(re, String.class); + + springReleaseEvent = mapper.fromJson(response.getBody(), SpringReleaseEvent.class); + + assertThat(springReleaseEvent.getReleaseName()).isEqualTo("Spring Framework"); + assertThat(springReleaseEvent.getVersion()).isEqualTo("10.0"); + + +// assertThat(response.getHeaders().get(CloudEventMessageUtils.CE_SOURCE)) +// .isEqualTo(Collections.singletonList("http://spring.io/application-application")); +// assertThat(response.getHeaders().get(CloudEventMessageUtils.CE_TYPE)) +// .isEqualTo(Collections.singletonList(SpringReleaseEvent.class.getName())); + } + private URI constructURI(String path) throws Exception { return new URI("http://localhost:" + System.getProperty("server.port") + path); }