Skip to content

Commit f775edf

Browse files
JunggiKimolegz
authored andcommitted
Fix POJO functions to return Message consistently
POJO functions now return Message when input is Message, maintaining consistency with regular Function implementations. This ensures headers are preserved in POJO functions just like regular functions. Changes: - Add isPojoFunction flag to identify POJO functions - Mark POJO functions during discovery - Re-wrap output in Message for POJO functions when input is Message - Add test verifying Message return with header preservation - Add test verifying plain String input returns plain String output Fixes gh-1307 Signed-off-by: kjg <[email protected]> Resolves #1311
1 parent c313d38 commit f775edf

File tree

3 files changed

+84
-2
lines changed

3 files changed

+84
-2
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.lang.reflect.Type;
2222
import java.util.Arrays;
2323
import java.util.Collection;
24+
import java.util.Collections;
2425
import java.util.Set;
2526
import java.util.function.BiConsumer;
2627
import java.util.function.BiFunction;
@@ -171,6 +172,10 @@ else if (this.isFunctionPojo(functionCandidate, functionName)) {
171172
Method functionalMethod = FunctionTypeUtils.discoverFunctionalMethod(functionCandidate.getClass());
172173
functionCandidate = this.proxyTarget(functionCandidate, functionalMethod);
173174
functionType = FunctionTypeUtils.fromFunctionMethod(functionalMethod);
175+
// GH-1307: Mark this as a POJO function for special handling
176+
functionRegistration = new FunctionRegistration(functionCandidate, functionName)
177+
.type(functionType)
178+
.properties(Collections.singletonMap("isPojoFunction", "true"));
174179
}
175180
else if (this.isSpecialFunctionRegistration(functionNames, functionName)) {
176181
functionRegistration = this.applicationContext

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,17 @@ private FunctionInvocationWrapper findFunctionInFunctionRegistrations(String fun
291291
// ignore
292292
}
293293
}
294+
// GH-1307: Mark POJO functions for special Message wrapping behavior
295+
if (functionRegistration != null &&
296+
functionRegistration.getProperties().containsKey("isPojoFunction")) {
297+
try {
298+
String isPojoValue = functionRegistration.getProperties().get("isPojoFunction");
299+
function.setPojoFunction(Boolean.parseBoolean(isPojoValue));
300+
}
301+
catch (Exception e) {
302+
// ignore
303+
}
304+
}
294305
return function;
295306
}
296307

@@ -443,6 +454,8 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
443454

444455
private boolean wrappedBiConsumer;
445456

457+
private boolean isPojoFunction;
458+
446459
FunctionInvocationWrapper(String functionDefinition, Object target, Type inputType, Type outputType) {
447460
if (target instanceof PostProcessingFunction) {
448461
this.postProcessor = (PostProcessingFunction) target;
@@ -498,6 +511,14 @@ public void setWrappedBiConsumer(boolean wrappedBiConsumer) {
498511
this.wrappedBiConsumer = wrappedBiConsumer;
499512
}
500513

514+
public void setPojoFunction(boolean isPojoFunction) {
515+
this.isPojoFunction = isPojoFunction;
516+
}
517+
518+
public boolean isPojoFunction() {
519+
return this.isPojoFunction;
520+
}
521+
501522
public boolean isSkipOutputConversion() {
502523
return skipOutputConversion;
503524
}
@@ -1259,6 +1280,14 @@ else if (isExtractPayload((Message<?>) convertedOutput, type)) {
12591280
}
12601281

12611282
if (ObjectUtils.isEmpty(contentType)) {
1283+
// GH-1307: For POJO functions, wrap output in Message to maintain
1284+
// consistency with regular functions
1285+
if (this.isPojoFunction && output instanceof Message
1286+
&& !(convertedOutput instanceof Message)) {
1287+
convertedOutput = MessageBuilder.withPayload(convertedOutput)
1288+
.copyHeaders(((Message) output).getHeaders())
1289+
.build();
1290+
}
12621291
return convertedOutput;
12631292
}
12641293

spring-cloud-function-context/src/test/java/org/springframework/cloud/function/context/catalog/BeanFactoryAwarePojoFunctionRegistryTests.java

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ public void testWithPojoFunction() {
8383
Function<Integer, String> f2conversion = catalog.lookup("myFunctionLike");
8484
assertThat(f2conversion.apply(123)).isEqualTo("123");
8585

86-
Function<Message<String>, String> f2message = catalog.lookup("myFunctionLike");
87-
assertThat(f2message.apply(MessageBuilder.withPayload("message").build())).isEqualTo("MESSAGE");
86+
// GH-1307: POJO functions now return Message for consistency
87+
Function<Message<String>, Message<?>> f2message = catalog.lookup("myFunctionLike");
88+
Message<?> messageResult = f2message.apply(MessageBuilder.withPayload("message").build());
89+
assertThat(messageResult.getPayload()).isEqualTo("MESSAGE");
8890

8991
Function<Flux<String>, Flux<String>> f3 = catalog.lookup("myFunctionLike");
9092
assertThat(f3.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO");
@@ -100,6 +102,52 @@ public void testWithPojoFunctionComposition() {
100102
assertThat(f1.apply("foo")).isEqualTo("FOO");
101103
}
102104

105+
/**
106+
* GH-1307: POJO function should return Message consistently with regular functions
107+
* when no contentType is specified.
108+
*/
109+
@Test
110+
public void testPojoFunctionReturnsMessageWithoutContentType() {
111+
FunctionCatalog catalog = this.configureCatalog();
112+
113+
// Test POJO function without contentType
114+
Function<Message<String>, Object> pojoFunction = catalog.lookup("myFunctionLike");
115+
Message<String> input = MessageBuilder.withPayload("test")
116+
.setHeader("correlationId", "123")
117+
.build();
118+
119+
Object result = pojoFunction.apply(input);
120+
121+
// GH-1307: Verify POJO functions return Message for consistency
122+
assertThat(result)
123+
.as("POJO function should return Message, not plain value when input is Message")
124+
.isInstanceOf(Message.class);
125+
126+
Message<?> messageResult = (Message<?>) result;
127+
assertThat(messageResult.getPayload()).isEqualTo("TEST");
128+
assertThat(messageResult.getHeaders().get("correlationId"))
129+
.as("Headers should be preserved")
130+
.isEqualTo("123");
131+
}
132+
133+
/**
134+
* GH-1307: POJO function should NOT wrap output when input is plain String
135+
*/
136+
@Test
137+
public void testPojoFunctionDoesNotWrapPlainStringInput() {
138+
FunctionCatalog catalog = this.configureCatalog();
139+
140+
// GH-1307: POJO function with plain String input should return plain String
141+
Function<String, Object> pojoFunction = catalog.lookup("myFunctionLike");
142+
Object result = pojoFunction.apply("plainInput");
143+
144+
// Should return String, not Message
145+
assertThat(result)
146+
.as("POJO function should return plain String when input is plain String, not wrap in Message")
147+
.isInstanceOf(String.class)
148+
.isEqualTo("PLAININPUT");
149+
}
150+
103151

104152
@EnableAutoConfiguration
105153
@Configuration(proxyBeanMethods = false)

0 commit comments

Comments
 (0)