Skip to content

Commit 870bb4b

Browse files
committed
GH-1023 Add support for post function processing
1 parent 81d08d1 commit 870bb4b

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

spring-cloud-function-adapters/spring-cloud-function-adapter-aws/src/main/java/org/springframework/cloud/function/adapter/aws/AWSLambdaUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.springframework.http.HttpStatus;
3434
import org.springframework.messaging.Message;
3535
import org.springframework.messaging.MessageHeaders;
36-
import org.springframework.messaging.support.GenericMessage;
3736
import org.springframework.messaging.support.MessageBuilder;
3837
import org.springframework.util.StreamUtils;
3938

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2023-2023 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.context;
18+
19+
import java.util.function.Function;
20+
21+
import org.springframework.messaging.Message;
22+
23+
/**
24+
* Strategy for implementing function with post processing behavior.
25+
* <br>
26+
* The core framework only provides support for the post-processing behavior.
27+
* The actual invocation of post-processing is left to the end user or the framework which
28+
* integrates Spring Cloud Function. This is because post-processing can mean different things
29+
* in different execution contexts. See {@link #postProcess(Message)} method for more information.
30+
*
31+
* @param <I> - input type
32+
* @param <O> - output type
33+
*
34+
* @author Oleg Zhurakousky
35+
* @since 4.0.3
36+
*
37+
*/
38+
public interface PostProcessingFunction<I, O> extends Function<I, O> {
39+
40+
/**
41+
* Will post process the result of this's function invocation after this function has been triggered.
42+
* <br>
43+
* This operation is not managed/invoked by the core functionality of the Spring Cloud Function.
44+
* It is specifically designed as a hook for other frameworks and extensions to invoke after
45+
* this function was "triggered" and there is a requirement to do some post processing. The word "triggered"
46+
* can mean different things in different execution contexts. For example, in spring-cloud-stream it means
47+
* that the function has been invoked and the result of the function has been sent to the target destination.
48+
*
49+
* The boolean value argument - 'success' - allows the triggering framework to signal success or
50+
* failure of its triggering operation whatever that may mean.
51+
*
52+
* @param result - the result of function invocation as an instance of {@link Message} including all the metadata as message headers.
53+
*/
54+
default void postProcess(Message<O> result) {
55+
}
56+
}

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.springframework.cloud.function.context.FunctionProperties.FunctionConfigurationProperties;
5353
import org.springframework.cloud.function.context.FunctionRegistration;
5454
import org.springframework.cloud.function.context.FunctionRegistry;
55+
import org.springframework.cloud.function.context.PostProcessingFunction;
5556
import org.springframework.cloud.function.context.config.RoutingFunction;
5657
import org.springframework.cloud.function.core.FunctionInvocationHelper;
5758
import org.springframework.cloud.function.json.JsonMapper;
@@ -414,6 +415,10 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
414415

415416
private boolean wrapped;
416417

418+
private final ThreadLocal<Message<Object>> unconvertedResult = new ThreadLocal<>();
419+
420+
private PostProcessingFunction postProcessor;
421+
417422
/*
418423
* This is primarily to support Stream's ability to access
419424
* un-converted payload (e.g., to evaluate expression on some attribute of a payload)
@@ -425,6 +430,9 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
425430
private boolean wrappedBiConsumer;
426431

427432
FunctionInvocationWrapper(String functionDefinition, Object target, Type inputType, Type outputType) {
433+
if (target instanceof PostProcessingFunction) {
434+
this.postProcessor = (PostProcessingFunction) target;
435+
}
428436
this.target = target;
429437
this.inputType = this.normalizeType(inputType);
430438
this.outputType = this.normalizeType(outputType);
@@ -441,6 +449,25 @@ public class FunctionInvocationWrapper implements Function<Object, Object>, Cons
441449
}
442450
}
443451

452+
@SuppressWarnings("unchecked")
453+
public void postProcess() {
454+
if (this.postProcessor != null) {
455+
Message result = this.unconvertedResult.get();
456+
if (result != null) {
457+
try {
458+
this.postProcessor.postProcess(result);
459+
}
460+
catch (Exception ex) {
461+
logger.warn("Failed to post process function "
462+
+ this.functionDefinition + "; Result of the invocation before post processing is " + result, ex);
463+
}
464+
finally {
465+
this.unconvertedResult.remove();
466+
}
467+
}
468+
}
469+
}
470+
444471
public boolean isWrappedBiConsumer() {
445472
return wrappedBiConsumer;
446473
}
@@ -652,6 +679,9 @@ else if (this.outputType == null) {
652679
String composedName = this.functionDefinition + "|" + afterWrapper.functionDefinition;
653680
FunctionInvocationWrapper composedFunction = invocationWrapperInstance(composedName, rawComposedFunction, composedFunctionType);
654681
composedFunction.composed = true;
682+
if (((FunctionInvocationWrapper) after).target instanceof PostProcessingFunction) {
683+
composedFunction.postProcessor = (PostProcessingFunction) ((FunctionInvocationWrapper) after).target;
684+
}
655685

656686
return (Function<Object, V>) composedFunction;
657687
}
@@ -704,6 +734,10 @@ else if (this.isConsumer()) {
704734
result = this.invokeFunction(convertedInput);
705735
}
706736

737+
if (this.postProcessor != null) {
738+
this.unconvertedResult.set((Message<Object>) result);
739+
}
740+
707741
if (result != null && this.outputType != null) {
708742
result = this.convertOutputIfNecessary(result, this.outputType, this.expectedOutputContentType);
709743
}

0 commit comments

Comments
 (0)