Skip to content

Commit a6eb833

Browse files
committed
GH-422 Improvements in cloud event samples
Added initial README Polished tests
1 parent 2a88b52 commit a6eb833

File tree

7 files changed

+179
-46
lines changed

7 files changed

+179
-46
lines changed

spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/catalog/SimpleFunctionRegistry.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ else if (this.skipInputConversion) {
806806
else if (input instanceof Message) {
807807
convertedInput = this.convertInputMessageIfNecessary((Message) input, type);
808808
if (convertedInput == null) { // give ConversionService a chance
809-
convertedInput = this.convertNonMessageInputIfNecessary(type, ((Message) input).getPayload());
809+
convertedInput = this.convertNonMessageInputIfNecessary(type, ((Message) input).getPayload(), false);
810810
}
811811
if (convertedInput != null && !FunctionTypeUtils.isMultipleArgumentType(this.inputType)) {
812812
convertedInput = !convertedInput.equals(input)
@@ -818,7 +818,7 @@ else if (input instanceof Message) {
818818
}
819819
}
820820
else {
821-
convertedInput = this.convertNonMessageInputIfNecessary(type, input);
821+
convertedInput = this.convertNonMessageInputIfNecessary(type, input, JsonMapper.isJsonString(input));
822822
if (convertedInput != null && logger.isDebugEnabled()) {
823823
logger.debug("Converted input: " + input + " to: " + convertedInput);
824824
}
@@ -827,6 +827,7 @@ else if (input instanceof Message) {
827827
if (this.isWrapConvertedInputInMessage(convertedInput)) {
828828
convertedInput = MessageBuilder.withPayload(convertedInput).build();
829829
}
830+
Assert.notNull(convertedInput, "Failed to convert input: " + input + " to " + type);
830831
return convertedInput;
831832
}
832833

@@ -897,13 +898,13 @@ private boolean containsRetainMessageSignalInHeaders(Message message) {
897898
/*
898899
*
899900
*/
900-
private Object convertNonMessageInputIfNecessary(Type inputType, Object input) {
901+
private Object convertNonMessageInputIfNecessary(Type inputType, Object input, boolean maybeJson) {
901902
Object convertedInput = null;
902903
Class<?> rawInputType = this.isTypePublisher(inputType) || this.isInputTypeMessage()
903904
? FunctionTypeUtils.getRawType(FunctionTypeUtils.getGenericType(inputType))
904905
: this.getRawClassFor(inputType);
905906

906-
if (JsonMapper.isJsonString(input) && !Message.class.isAssignableFrom(rawInputType)) {
907+
if (maybeJson && !Message.class.isAssignableFrom(rawInputType)) {
907908
if (FunctionTypeUtils.isMessage(inputType)) {
908909
inputType = FunctionTypeUtils.getGenericType(inputType);
909910
}

spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/config/CloudEventJsonMessageConverter.java

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.cloud.function.context.config;
1818

1919
import java.lang.reflect.Type;
20+
import java.util.Collection;
2021
import java.util.Map;
2122

2223
import org.springframework.cloud.function.json.JsonMapper;
@@ -50,6 +51,9 @@ protected Object convertFromInternal(Message<?> message, Class<?> targetClass, @
5051
return super.convertFromInternal(message, targetClass, conversionHint);
5152
}
5253
else {
54+
if (targetClass.isInstance(message.getPayload()) && !(message.getPayload() instanceof Collection<?>)) {
55+
return message.getPayload();
56+
}
5357
Type convertToType = conversionHint == null ? targetClass : (Type) conversionHint;
5458
String jsonString = (String) message.getPayload();
5559
Map<String, Object> mapEvent = this.mapper.fromJson(jsonString, Map.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
## Cloud Events with Spring samples
2+
3+
### Introduction
4+
The current example uses spring-cloud-function framework as its core which allows users to only worry about functional aspects of
5+
their requirement while taking care-off non-functional aspects. For more information on Spring Cloud Function please visit
6+
our https://spring.io/projects/spring-cloud-function[project page].
7+
The example provides dependency and instructions to demonstrate several distinct invocation models:
8+
9+
- Direct function invocation
10+
- Function as a REST endpoint
11+
- Function as message handler (e.g., Kafka, RabbitMQ etc)
12+
- Function invocation via RSocket
13+
14+
The POM file defines all the necessary dependency in a segregated way, so you can choose the one you're interested in.
15+
16+
#### Direct function invocation
17+
18+
#### Function as a REST endpoint
19+
20+
Given that SCF allows function to be exposed as REST endpoints, you can post cloud event to any of the
21+
functions by using function name as path (e.g., localhost:8080/<function_name>)
22+
23+
Here is an example of curl command posting a cloud event in binary-mode:
24+
25+
[source, text]
26+
----
27+
curl -w'\n' localhost:8080/asPOJO \
28+
-H "ce-Specversion: 1.0" \
29+
-H "ce-Type: com.example.springevent" \
30+
-H "ce-Source: spring.io/spring-event" \
31+
-H "Content-Type: application/json" \
32+
-H "ce-Id: 0001" \
33+
-d '{"releaseDate":"24-03-2004", "releaseName":"Spring Framework", "version":"1.0"}'
34+
----
35+
36+
And here is an example of curl command posting a cloud event in structured-mode:
37+
38+
[source, text]
39+
----
40+
curl -w'\n' localhost:8080/asString \
41+
-H "ce-Specversion: 1.0" \
42+
-H "ce-Type: com.example.springevent" \
43+
-H "ce-Source: spring.io/spring-event" \
44+
-H "Content-Type: application/cloudevents+json" \
45+
-H "ce-Id: 0001" \
46+
-d '{
47+
"specversion" : "1.0",
48+
"type" : "org.springframework",
49+
"source" : "https://spring.io/",
50+
"id" : "A234-1234-1234",
51+
"datacontenttype" : "application/json",
52+
"data" : {
53+
"version" : "1.0",
54+
"releaseName" : "Spring Framework",
55+
"releaseDate" : "24-03-2004"
56+
}
57+
}'
58+
----
59+
60+
#### Function as message handler (e.g., Kafka, RabbitMQ etc)
61+
62+
Streaming support for Kafka and Rabbit is provided via Spring Cloud Stream framework (link). In fact we're only mentioning Kafka and Rabbit here as an example.
63+
Streaming support is automatically provided for any existing binders (e.g., Solace, GCP, AWS etc) (link)
64+
Binders are components of SCSt responsible to bind user code (e.g., function) to broker destinations so execution is triggered
65+
by messages on broker destination and results of execution are sent to broker destinations. Binders also provide support consumer
66+
groups and partitioning for both Kafka and RabbitMQ messaging systems.
67+
68+
69+
#### Function invocation via RSocket
70+
71+
TBD

spring-cloud-function-samples/function-sample-cloudevent/pom.xml

+21-1
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,42 @@
2323
<groupId>org.springframework.boot</groupId>
2424
<artifactId>spring-boot-starter</artifactId>
2525
</dependency>
26+
27+
<!-- REST - only needed if you intend to invoke via HTTP -->
2628
<dependency>
2729
<groupId>org.springframework.boot</groupId>
2830
<artifactId>spring-boot-starter-web</artifactId>
2931
</dependency>
30-
3132
<dependency>
3233
<groupId>org.springframework.cloud</groupId>
3334
<artifactId>spring-cloud-function-web</artifactId>
3435
<version>3.1.0-SNAPSHOT</version>
3536
</dependency>
37+
<!-- end REST -->
38+
39+
<!-- RSocket - only needed if you intend to invoke via RSocket -->
40+
<!-- <dependency> -->
41+
<!-- <groupId>org.springframework.cloud</groupId> -->
42+
<!-- <artifactId>spring-cloud-function-rsocket</artifactId> -->
43+
<!-- <version>3.1.0-SNAPSHOT</version> -->
44+
<!-- </dependency> -->
45+
<!-- end RSocket -->
3646

47+
<!-- RabbitMQ - only needed if you intend to invoke via RabbitMQ -->
3748
<!-- <dependency> -->
3849
<!-- <groupId>org.springframework.cloud</groupId> -->
3950
<!-- <artifactId>spring-cloud-stream-binder-rabbit</artifactId> -->
4051
<!-- <version>3.1.0-SNAPSHOT</version> -->
4152
<!-- </dependency> -->
53+
<!-- end RabbitMQ -->
54+
55+
<!-- Kafka - only needed if you intend to invoke via RabbitMQ -->
56+
<!-- <dependency> -->
57+
<!-- <groupId>org.springframework.cloud</groupId> -->
58+
<!-- <artifactId>spring-cloud-stream-binder-kafka</artifactId> -->
59+
<!-- <version>3.1.0-SNAPSHOT</version> -->
60+
<!-- </dependency> -->
61+
<!-- end Kafka -->
4262

4363
<dependency>
4464
<groupId>org.springframework.boot</groupId>

spring-cloud-function-samples/function-sample-cloudevent/src/main/java/io/spring/cloudevent/CloudeventDemoApplication.java

+19-40
Original file line numberDiff line numberDiff line change
@@ -43,59 +43,38 @@ public static void main(String[] args) throws Exception {
4343
SpringApplication.run(CloudeventDemoApplication.class, args);
4444
}
4545

46-
/*
47-
* curl -w'\n' localhost:8080/asStringMessage \
48-
* -H "Ce-Specversion: 1.0" \
49-
* -H "Ce-Type: com.example.springevent" \
50-
* -H "Ce-Source: spring.io/spring-event" \
51-
* -H "Content-Type: application/json" \
52-
* -H "Ce-Id: 0001" \
53-
* -d '{"releaseDate":"2004-03-24", "releaseName":"Spring Framework", "version":"1.0"}'
54-
*/
5546
@Bean
5647
public Function<Message<String>, String> asStringMessage() {
57-
return v -> v.getPayload().toString();
48+
return v -> {
49+
System.out.println("Received Cloud Event with raw data: " + v);
50+
return v.getPayload();
51+
};
5852
}
5953

60-
/*
61-
* curl -w'\n' localhost:8080/asString \
62-
* -H "Ce-Specversion: 1.0" \
63-
* -H "Ce-Type: com.example.springevent" \
64-
* -H "Ce-Source: spring.io/spring-event" \
65-
* -H "Content-Type: application/json" \
66-
* -H "Ce-Id: 0001" \
67-
* -d '{"releaseDate":"2004-03-24", "releaseName":"Spring Framework", "version":"1.0"}'
68-
*/
54+
6955
@Bean
7056
public Function<String, String> asString() {
71-
return v -> v;
57+
return v -> {
58+
System.out.println("Received raw Cloud Event data: " + v);
59+
return v;
60+
};
7261
}
7362

74-
/*
75-
* curl -w'\n' localhost:8080/asPOJOMessage \
76-
* -H "Ce-Specversion: 1.0" \
77-
* -H "Ce-Type: com.example.springevent" \
78-
* -H "Ce-Source: spring.io/spring-event" \
79-
* -H "Content-Type: application/json" \
80-
* -H "Ce-Id: 0001" \
81-
* -d '{"releaseDate":"2004-03-24", "releaseName":"Spring Framework", "version":"1.0"}'
82-
*/
63+
8364
@Bean
8465
public Function<Message<SpringReleaseEvent>, String> asPOJOMessage() {
85-
return v -> v.getPayload().toString();
66+
return v -> {
67+
System.out.println("Received Cloud Event with POJO data: " + v);
68+
return v.getPayload().toString();
69+
};
8670
}
8771

88-
/*
89-
* curl -w'\n' localhost:8080/asPOJO \
90-
* -H "Ce-Specversion: 1.0" \
91-
* -H "Ce-Type: com.example.springevent" \
92-
* -H "Ce-Source: spring.io/spring-event" \
93-
* -H "Content-Type: application/json" \
94-
* -H "Ce-Id: 0001" \
95-
* -d '{"releaseDate":"2004-03-24", "releaseName":"Spring Framework", "version":"1.0"}'
96-
*/
72+
9773
@Bean
9874
public Function<SpringReleaseEvent, String> asPOJO() {
99-
return v -> v.toString();
75+
return v -> {
76+
System.out.println("Received POJO Cloud Event data: " + v);
77+
return v.toString();
78+
};
10079
}
10180
}
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
* @author Oleg Zhurakousky
4848
*
4949
*/
50-
public class CloudeventDemoApplicationTests {
50+
public class CloudeventDemoApplicationRESTTests {
5151

5252
private TestRestTemplate testRestTemplate = new TestRestTemplate();
5353

@@ -167,6 +167,37 @@ public void testAsStracturalFormatToPOJO() throws Exception {
167167
assertThat(response.getBody()).isEqualTo("releaseDate:24-03-2004; releaseName:Spring Framework; version:1.0");
168168
}
169169

170+
@Test
171+
public void testAsStracturalFormatToString() throws Exception {
172+
SpringApplication.run(CloudeventDemoApplication.class);
173+
174+
String payload = "{\n" +
175+
" \"specversion\" : \"1.0\",\n" +
176+
" \"type\" : \"org.springframework\",\n" +
177+
" \"source\" : \"https://spring.io/\",\n" +
178+
" \"id\" : \"A234-1234-1234\",\n" +
179+
" \"datacontenttype\" : \"application/json\",\n" +
180+
" \"data\" : {\n" +
181+
" \"version\" : \"1.0\",\n" +
182+
" \"releaseName\" : \"Spring Framework\",\n" +
183+
" \"releaseDate\" : \"24-03-2004\"\n" +
184+
" }\n" +
185+
"}";
186+
187+
HttpHeaders headers = new HttpHeaders();
188+
headers.setContentType(MediaType.valueOf("application/cloudevents+json;charset=utf-8"));
189+
190+
RequestEntity<String> re = new RequestEntity<>(payload, headers, HttpMethod.POST, this.constructURI("/asStringMessage"));
191+
ResponseEntity<String> response = testRestTemplate.exchange(re, String.class);
192+
193+
assertThat(response.getBody()).isEqualTo(payload);
194+
195+
re = new RequestEntity<>(payload, headers, HttpMethod.POST, this.constructURI("/asString"));
196+
response = testRestTemplate.exchange(re, String.class);
197+
198+
assertThat(response.getBody()).isEqualTo(payload);
199+
}
200+
170201

171202
@Configuration
172203
public static class FooBarConverterConfiguration {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2020-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.cloudevent;
18+
19+
/**
20+
*
21+
* @author Oleg Zhurakousky
22+
*
23+
*/
24+
public class CloudeventDemoApplicationStreamTests {
25+
26+
27+
}

0 commit comments

Comments
 (0)