Skip to content

Commit 9c58e6d

Browse files
committed
GH-422 GH-606 Add support for simplifying message headers to attribute mapping
Added CloudEventAttributesProvider and default implementation Added CloudEventMessageUtils
1 parent f999cdd commit 9c58e6d

File tree

12 files changed

+438
-69
lines changed

12 files changed

+438
-69
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2019-2019 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 org.springframework.cloud.function.cloudevent;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
23+
/**
24+
*
25+
* @author Oleg Zhurakousky
26+
* @since 3.1
27+
*/
28+
public class CloudEventAttributes extends HashMap<String, Object> {
29+
30+
/**
31+
*
32+
*/
33+
private static final long serialVersionUID = 5393610770855366497L;
34+
35+
36+
37+
CloudEventAttributes(Map<String, Object> headers) {
38+
super(headers);
39+
}
40+
41+
@SuppressWarnings("unchecked")
42+
public <A> A getId() {
43+
return this.containsKey(CloudEventMessageUtils.CE_ID)
44+
? (A) this.get(CloudEventMessageUtils.CE_ID)
45+
: (A) this.get(CloudEventMessageUtils.ID);
46+
}
47+
48+
@SuppressWarnings("unchecked")
49+
public <A> A getSource() {
50+
return this.containsKey(CloudEventMessageUtils.CE_SOURCE)
51+
? (A) this.get(CloudEventMessageUtils.CE_SOURCE)
52+
: (A) this.get(CloudEventMessageUtils.SOURCE);
53+
}
54+
55+
@SuppressWarnings("unchecked")
56+
public <A> A getSpecversion() {
57+
return this.containsKey(CloudEventMessageUtils.CE_SPECVERSION)
58+
? (A) this.get(CloudEventMessageUtils.CE_SPECVERSION)
59+
: (A) this.get(CloudEventMessageUtils.SPECVERSION);
60+
}
61+
62+
@SuppressWarnings("unchecked")
63+
public <A> A getType() {
64+
return this.containsKey(CloudEventMessageUtils.CE_TYPE)
65+
? (A) this.get(CloudEventMessageUtils.CE_TYPE)
66+
: (A) this.get(CloudEventMessageUtils.TYPE);
67+
}
68+
69+
@SuppressWarnings("unchecked")
70+
public <A> A getDataContentType() {
71+
return this.containsKey(CloudEventMessageUtils.CE_DATACONTENTTYPE)
72+
? (A) this.get(CloudEventMessageUtils.CE_DATACONTENTTYPE)
73+
: (A) this.get(CloudEventMessageUtils.DATACONTENTTYPE);
74+
}
75+
76+
public void setDataContentType(String datacontenttype) {
77+
this.put(CloudEventMessageUtils.CE_DATACONTENTTYPE, datacontenttype);
78+
}
79+
80+
@SuppressWarnings("unchecked")
81+
public <A> A getAtttribute(String name) {
82+
return (A) this.get(name);
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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 org.springframework.cloud.function.cloudevent;
18+
19+
import org.springframework.messaging.MessageHeaders;
20+
21+
/**
22+
*
23+
* @author Oleg Zhurakousky
24+
* @since 3.1
25+
*/
26+
public interface CloudEventAtttributesProvider {
27+
28+
/**
29+
* Will construct instance of {@link CloudEventAttributes} setting its required attributes.
30+
*
31+
* @param ce_id value for Cloud Event 'id' attribute
32+
* @param ce_specversion value for Cloud Event 'specversion' attribute
33+
* @param ce_source value for Cloud Event 'source' attribute
34+
* @param ce_type value for Cloud Event 'type' attribute
35+
* @return instance of {@link CloudEventAttributes}
36+
*/
37+
CloudEventAttributes get(String ce_id, String ce_specversion, String ce_source, String ce_type);
38+
39+
/**
40+
* Will construct instance of {@link CloudEventAttributes}
41+
* Should default/generate cloud event ID and SPECVERSION.
42+
*
43+
* @param ce_source value for Cloud Event 'source' attribute
44+
* @param ce_type value for Cloud Event 'type' attribute
45+
* @return instance of {@link CloudEventAttributes}
46+
*/
47+
CloudEventAttributes get(String ce_source, String ce_type);
48+
49+
50+
/**
51+
* Will construct instance of {@link CloudEventAttributes} from {@link MessageHeaders}.
52+
*
53+
* Should copy Cloud Event related headers into an instance of {@link CloudEventAttributes}
54+
* NOTE: Certain headers must not be copied.
55+
*
56+
* @param headers instance of {@link MessageHeaders}
57+
* @return modifiable instance of {@link CloudEventAttributes}
58+
*/
59+
RequiredAttributeAccessor get(MessageHeaders headers);
60+
}

spring-cloud-function-context/src/main/java/org/springframework/cloud/function/cloudevent/CloudEventDataContentTypeMessagePreProcessor.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public class CloudEventDataContentTypeMessagePreProcessor implements Function<Me
5050

5151
private final ContentTypeResolver contentTypeResolver = new DefaultContentTypeResolver();
5252

53-
private final MimeType cloudEventContentType = MimeTypeUtils.parseMimeType("application/cloudevents");
53+
private final MimeType cloudEventContentType = CloudEventMessageUtils.APPLICATION_CLOUDEVENTS;
5454

5555
private final CompositeMessageConverter messageConverter;
5656

@@ -62,7 +62,7 @@ public CloudEventDataContentTypeMessagePreProcessor(CompositeMessageConverter me
6262
@SuppressWarnings("unchecked")
6363
@Override
6464
public Message<?> apply(Message<?> inputMessage) {
65-
if (CloudEventUtils.isBinary(inputMessage)) {
65+
if (CloudEventMessageUtils.isBinary(inputMessage.getHeaders())) {
6666
String dataContentType = this.getDataContentType(inputMessage.getHeaders());
6767
Message<?> message = MessageBuilder.fromMessage(inputMessage)
6868
.setHeader(MessageHeaders.CONTENT_TYPE, dataContentType)
@@ -78,7 +78,7 @@ else if (this.isStructured(inputMessage)) {
7878
.parseMimeType(contentType.getType() + "/" + suffix);
7979
Message<?> cloudEventMessage = MessageBuilder.fromMessage(inputMessage)
8080
.setHeader(MessageHeaders.CONTENT_TYPE, cloudEventDeserializationContentType)
81-
.setHeader(CloudEventUtils.CE_DATACONTENTTYPE, dataContentType).build();
81+
.setHeader(CloudEventMessageUtils.CE_DATACONTENTTYPE, dataContentType).build();
8282
Map<String, Object> structuredCloudEvent = (Map<String, Object>) this.messageConverter
8383
.fromMessage(cloudEventMessage, Map.class);
8484
Message<?> binaryCeMessage = this.buildCeMessageFromStructured(structuredCloudEvent);
@@ -90,27 +90,27 @@ else if (this.isStructured(inputMessage)) {
9090
}
9191

9292
private Message<?> buildCeMessageFromStructured(Map<String, Object> structuredCloudEvent) {
93-
MessageBuilder<?> builder = MessageBuilder.withPayload(structuredCloudEvent.get(CloudEventUtils.DATA));
94-
structuredCloudEvent.remove(CloudEventUtils.DATA);
93+
MessageBuilder<?> builder = MessageBuilder.withPayload(structuredCloudEvent.get(CloudEventMessageUtils.DATA));
94+
structuredCloudEvent.remove(CloudEventMessageUtils.DATA);
9595
builder.copyHeaders(structuredCloudEvent);
9696
return builder.build();
9797
}
9898

9999
private String getDataContentType(MessageHeaders headers) {
100-
if (headers.containsKey(CloudEventUtils.DATACONTENTTYPE)) {
101-
return (String) headers.get(CloudEventUtils.DATACONTENTTYPE);
100+
if (headers.containsKey(CloudEventMessageUtils.DATACONTENTTYPE)) {
101+
return (String) headers.get(CloudEventMessageUtils.DATACONTENTTYPE);
102102
}
103-
else if (headers.containsKey(CloudEventUtils.CE_DATACONTENTTYPE)) {
104-
return (String) headers.get(CloudEventUtils.CE_DATACONTENTTYPE);
103+
else if (headers.containsKey(CloudEventMessageUtils.CE_DATACONTENTTYPE)) {
104+
return (String) headers.get(CloudEventMessageUtils.CE_DATACONTENTTYPE);
105105
}
106106
else if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) {
107107
return headers.get(MessageHeaders.CONTENT_TYPE).toString();
108108
}
109-
return "application/json";
109+
return MimeTypeUtils.APPLICATION_JSON_VALUE;
110110
}
111111

112112
private boolean isStructured(Message<?> message) {
113-
if (!CloudEventUtils.isBinary(message)) {
113+
if (!CloudEventMessageUtils.isBinary(message.getHeaders())) {
114114
Map<String, Object> headers = message.getHeaders();
115115

116116
if (headers.containsKey(MessageHeaders.CONTENT_TYPE)) {

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
public class CloudEventJsonMessageConverter extends JsonMessageConverter {
3333

3434
public CloudEventJsonMessageConverter(JsonMapper jsonMapper) {
35-
super(jsonMapper, new MimeType("application", "cloudevents+json"));
35+
super(jsonMapper, new MimeType(CloudEventMessageUtils.APPLICATION_CLOUDEVENTS.getType(),
36+
CloudEventMessageUtils.APPLICATION_CLOUDEVENTS.getSubtype() + "+json"));
3637
this.setStrictContentTypeMatch(true);
3738
}
3839
}
Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import java.util.Map;
2020

2121
import org.springframework.messaging.Message;
22+
import org.springframework.util.MimeType;
23+
import org.springframework.util.MimeTypeUtils;
2224

2325
/**
2426
* Miscellaneous utility methods to deal with Cloud Events - https://cloudevents.io/.
@@ -28,12 +30,22 @@
2830
* @author Oleg Zhurakousky
2931
* @since 3.1
3032
*/
31-
public final class CloudEventUtils {
33+
public final class CloudEventMessageUtils {
3234

33-
private CloudEventUtils() {
35+
private CloudEventMessageUtils() {
3436

3537
}
3638

39+
/**
40+
* String value of 'application/cloudevents' mime type.
41+
*/
42+
public static String APPLICATION_CLOUDEVENTS_VALUE = "application/cloudevents";
43+
44+
/**
45+
* {@link MimeType} instance representing 'application/cloudevents' mime type.
46+
*/
47+
public static MimeType APPLICATION_CLOUDEVENTS = MimeTypeUtils.parseMimeType(APPLICATION_CLOUDEVENTS_VALUE);
48+
3749
/**
3850
* Prefix for attributes.
3951
*/
@@ -132,16 +144,15 @@ private CloudEventUtils() {
132144
/**
133145
* Checks if {@link Message} represents cloud event in binary-mode.
134146
*/
135-
public static boolean isBinary(Message<?> message) {
136-
Map<String, Object> headers = message.getHeaders();
137-
return (headers.containsKey("id")
138-
&& headers.containsKey("source")
139-
&& headers.containsKey("specversion")
140-
&& headers.containsKey("type"))
147+
public static boolean isBinary(Map<String, Object> headers) {
148+
return (headers.containsKey(ID)
149+
&& headers.containsKey(SOURCE)
150+
&& headers.containsKey(SPECVERSION)
151+
&& headers.containsKey(TYPE))
141152
||
142-
(headers.containsKey("ce_id")
143-
&& headers.containsKey("ce_source")
144-
&& headers.containsKey("ce_specversion")
145-
&& headers.containsKey("ce_type"));
153+
(headers.containsKey(CE_ID)
154+
&& headers.containsKey(CE_SOURCE)
155+
&& headers.containsKey(CE_SPECVERSION)
156+
&& headers.containsKey(CE_TYPE));
146157
}
147158
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2019-2019 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 org.springframework.cloud.function.cloudevent;
18+
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
import java.util.UUID;
22+
23+
import org.springframework.messaging.MessageHeaders;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
*
28+
* @author Oleg Zhurakousky
29+
* @since 3.1
30+
*
31+
*/
32+
public class DefaultCloudEventAttributesProvider implements CloudEventAtttributesProvider {
33+
/*
34+
* should i provide instance() method for convinience or should it be always injected into function
35+
*/
36+
37+
@Override
38+
public CloudEventAttributes get(String ce_id, String ce_specversion, String ce_source, String ce_type) {
39+
Assert.hasText(ce_id, "'ce_id' must not be null or empty");
40+
Assert.hasText(ce_specversion, "'ce_specversion' must not be null or empty");
41+
Assert.hasText(ce_source, "'ce_source' must not be null or empty");
42+
Assert.hasText(ce_type, "'ce_type' must not be null or empty");
43+
Map<String, Object> requiredAttributes = new HashMap<>();
44+
requiredAttributes.put(CloudEventMessageUtils.CE_ID, ce_id);
45+
requiredAttributes.put(CloudEventMessageUtils.CE_SPECVERSION, ce_specversion);
46+
requiredAttributes.put(CloudEventMessageUtils.CE_SOURCE, ce_source);
47+
requiredAttributes.put(CloudEventMessageUtils.CE_TYPE, ce_type);
48+
return new CloudEventAttributes(requiredAttributes);
49+
}
50+
51+
@Override
52+
public CloudEventAttributes get(String ce_source, String ce_type) {
53+
return this.get(UUID.randomUUID().toString(), "1.0", ce_source, ce_type);
54+
}
55+
56+
/**
57+
* By default it will copy all the headers while exposing accessor to allow user to modify any of them.
58+
*/
59+
@Override
60+
public RequiredAttributeAccessor get(MessageHeaders headers) {
61+
return new RequiredAttributeAccessor(headers);
62+
}
63+
64+
}

0 commit comments

Comments
 (0)