diff --git a/README.adoc b/README.adoc index dd6aff2b..ec988f7d 100644 --- a/README.adoc +++ b/README.adoc @@ -30,61 +30,81 @@ The following are the various components of this repository. |link:functions/supplier/cdc-debezium-supplier/README.adoc[CDC Debezium] |link:functions/function/aggregator-function/README.adoc[Aggregator] |link:functions/consumer/analytics-consumer/README.adoc[Analytics] + |link:functions/supplier/file-supplier/README.adoc[File] |link:functions/function/filter-function/README.adoc[Filter] |link:functions/consumer/cassandra-consumer/README.adoc[Cassandra] + |link:functions/supplier/ftp-supplier/README.adoc[FTP] |link:functions/function/header-enricher-function/README.adoc[Header-Enricher] |link:functions/consumer/elasticsearch-consumer/README.adoc[Elasticsearch] | -|link:functions/function/http-request-function/README.adoc[HTTP Request] +|link:functions/function/header-filter-function/README.adoc[Header-Filter] |link:functions/consumer/file-consumer/README.adoc[File] + |link:functions/supplier/http-supplier/README.adoc[HTTP] -|link:functions/function/image-recognition-function/README.adoc[Image Recognition(Tensorflow)] +|link:functions/function/http-request-function/README.adoc[HTTP Request] |link:functions/consumer/ftp-consumer/README.adoc[FTP] + |link:functions/supplier/jdbc-supplier/README.adoc[JDBC] -|link:functions/function/object-detection-function/README.adoc[Object Detection(Tensorflow)] +|link:functions/function/image-recognition-function/README.adoc[Image Recognition(Tensorflow)] | |link:functions/supplier/jms-supplier/README.adoc[JMS] -|link:functions/function/semantic-segmentation-function/README.adoc[Semantic Segmentation(Tensorflow)] + +|link:functions/function/object-detection-function/README.adoc[Object Detection(Tensorflow)] |link:functions/consumer/jdbc-consumer/README.adoc[JDBC] + |link:functions/supplier/mail-supplier/README.adoc[Mail] -|link:functions/function/spel-function/README.adoc[SpEL] + +|link:functions/function/semantic-segmentation-function/README.adoc[Semantic Segmentation(Tensorflow)] |link:functions/consumer/log-consumer/README.adoc[Log] + |link:functions/supplier/mongodb-supplier/README.adoc[MongoDB] -|link:functions/function/splitter-function/README.adoc[Splitter] + +|link:functions/function/spel-function/README.adoc[SpEL] |link:functions/consumer/mongodb-consumer/README.adoc[MongoDB] + |link:functions/supplier/mqtt-supplier/README.adoc[MQTT] -|link:functions/function/task-launch-request-function/README.adoc[Task Launch Request] + +|link:functions/function/splitter-function/README.adoc[Splitter] |link:functions/consumer/mqtt-consumer/README.adoc[MQTT] + |link:functions/supplier/rabbit-supplier/README.adoc[RabbitMQ] -|link:functions/function/tasklauncher-function/README.adoc[Task Launcher] +|link:functions/function/task-launch-request-function/README.adoc[Task Launch Request] |link:functions/consumer/rabbit-consumer/README.adoc[RabbitMQ] + |link:functions/supplier/s3-supplier/README.adoc[AWS S3] -|link:functions/function/twitter-function/README.adoc[Twitter] +|link:functions/function/tasklauncher-function/README.adoc[Task Launcher] |link:functions/consumer/redis-consumer/README.adoc[Redis] + |link:functions/supplier/sftp-supplier/README.adoc[SFTP] | |link:functions/consumer/rsocket-consumer/README.adoc[RSocket] + |link:functions/supplier/syslog-supplier/README.adoc[Syslog] | |link:functions/consumer/s3-consumer/README.adoc[AWS S3] + |link:functions/supplier/tcp-supplier/README.adoc[TCP] | |link:functions/consumer/sftp-consumer/README.adoc[SFTP] + |link:functions/supplier/time-supplier/README.adoc[Time] | |link:functions/consumer/tcp-consumer/README.adoc[TCP] + |link:functions/supplier/twitter-supplier/README.adoc[Twitter] -| +|link:functions/function/twitter-function/README.adoc[Twitter] |link:functions/consumer/twitter-consumer/README.adoc[Twitter] + |link:functions/supplier/websocket-supplier/README.adoc[Websocket] | |link:functions/consumer/websocket-consumer/README.adoc[Websocket] -|link:functions/supplier/xmpp-supplier/README.adoc[XMPP] + +| | |link:functions/consumer/wavefront-consumer/README.adoc[Wavefront] -| +|link:functions/supplier/xmpp-supplier/README.adoc[XMPP] | |link:functions/consumer/xmpp-consumer/README.adoc[XMPP] |=== diff --git a/applications/processor/header-filter-processor/README.adoc b/applications/processor/header-filter-processor/README.adoc new file mode 100644 index 00000000..9d81c14a --- /dev/null +++ b/applications/processor/header-filter-processor/README.adoc @@ -0,0 +1,17 @@ +//tag::ref-doc[] += Header Filter Processor + +Use the header-filter app to remove message headers. + +The names of headers to remove are presented as a comma or space separated list. For example `--remove='foo,bar,pf-*'`. + +== Options + +The **$$header-filter$$** $$processor$$ has the following options: + +//tag::configuration-properties[] +$$header.filter.delete-all$$:: $$Indicates the need to remove all headers.$$ *($$Boolean$$, default: `$$false$$`)* +$$header.filter.remove$$:: $$Remove all headers named. A comma, space separated list of header names. The names may contain patterns.$$ *($$String$$, default: `$$$$`)* +//end::configuration-properties[] + +//end::ref-doc[] diff --git a/applications/processor/header-filter-processor/pom.xml b/applications/processor/header-filter-processor/pom.xml new file mode 100644 index 00000000..31b8ff17 --- /dev/null +++ b/applications/processor/header-filter-processor/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + header-filter-processor + header-filter-processor + header-filter processor apps + + + org.springframework.cloud.stream.app + stream-applications-core + 4.0.0-SNAPSHOT + ../../stream-applications-core/pom.xml + + + + + org.springframework.cloud.fn + header-filter-function + + + org.springframework.integration + spring-integration-test-support + test + + + org.assertj + assertj-core + test + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0 + + false + + + + org.springframework.cloud + spring-cloud-dataflow-apps-docs-plugin + + + org.springframework.cloud + spring-cloud-dataflow-apps-generator-plugin + + + header-filter + processor + ${project.version} + org.springframework.cloud.fn.header.filter.HeaderFilterFunctionConfiguration.class + headerFilterFunction + + + + org.springframework.cloud.fn + header-filter-function + + + + + + + + + + diff --git a/applications/processor/header-filter-processor/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties b/applications/processor/header-filter-processor/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties new file mode 100644 index 00000000..df946ed8 --- /dev/null +++ b/applications/processor/header-filter-processor/src/main/resources/META-INF/dataflow-configuration-metadata-whitelist.properties @@ -0,0 +1,2 @@ +configuration-properties.classes=org.springframework.cloud.fn.header.filter.HeaderFilterFunctionProperties + diff --git a/applications/processor/header-filter-processor/src/main/resources/META-INF/dataflow-configuration-metadata.properties b/applications/processor/header-filter-processor/src/main/resources/META-INF/dataflow-configuration-metadata.properties new file mode 100644 index 00000000..df946ed8 --- /dev/null +++ b/applications/processor/header-filter-processor/src/main/resources/META-INF/dataflow-configuration-metadata.properties @@ -0,0 +1,2 @@ +configuration-properties.classes=org.springframework.cloud.fn.header.filter.HeaderFilterFunctionProperties + diff --git a/applications/processor/header-filter-processor/src/test/java/org/springframework/cloud/stream/app/processor/header/filter/HeaderFilterProcessorTests.java b/applications/processor/header-filter-processor/src/test/java/org/springframework/cloud/stream/app/processor/header/filter/HeaderFilterProcessorTests.java new file mode 100644 index 00000000..bcef9b86 --- /dev/null +++ b/applications/processor/header-filter-processor/src/test/java/org/springframework/cloud/stream/app/processor/header/filter/HeaderFilterProcessorTests.java @@ -0,0 +1,111 @@ +/* + * Copyright 2023-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.stream.app.processor.header.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; + +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.cloud.fn.header.filter.HeaderFilterFunctionConfiguration; +import org.springframework.cloud.stream.binder.test.InputDestination; +import org.springframework.cloud.stream.binder.test.OutputDestination; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Christian Tzolov + * @author Soby Chacko + */ +public class HeaderFilterProcessorTests { + + @Test + public void testHeaderFilterProcessor() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(HeaderEnricherProcessorTestApplication.class)) + .web(WebApplicationType.NONE) + .run("--spring.cloud.function.definition=headerFilterFunction", + "--header.filter.remove=foo,bar")) { + + InputDestination processorInput = context.getBean(InputDestination.class); + OutputDestination processorOutput = context.getBean(OutputDestination.class); + + final Message message = MessageBuilder.withPayload("hello") + .setHeader("bar", "foo") + .setHeader("foo", "bar") + .setHeader("foo-bar", "fubar") + .build(); + processorInput.send(message); + Message results = processorOutput.receive(10000); + Set headers = getNonReadOnlyHeaders(results); + assertThat(headers).isEqualTo(Set.of("foo-bar", "contentType", "target-protocol")); + } + } + + @NotNull + private static Set getNonReadOnlyHeaders(Message message) { + var headers = new HashSet<>(message.getHeaders().keySet()); + var accessor = new IntegrationMessageHeaderAccessor(message); + headers.removeIf(accessor::isReadOnly); + return headers; + } + + @Test + public void testHeaderFilterRemoveAllProcessor() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder( + TestChannelBinderConfiguration.getCompleteConfiguration(HeaderEnricherProcessorTestApplication.class)) + .web(WebApplicationType.NONE) + .run("--spring.cloud.function.definition=headerFilterFunction", + "--header.filter.delete-all=true")) { + + InputDestination processorInput = context.getBean(InputDestination.class); + OutputDestination processorOutput = context.getBean(OutputDestination.class); + + final Message message = MessageBuilder.withPayload("hello") + .setHeader("bar", "foo") + .setHeader("foo", "bar") + .setHeader("foo-bar", "fubar") + .build(); + processorInput.send(message); + Message result = processorOutput.receive(10000); + var headers = getNonReadOnlyHeaders(result); + assertThat(headers).isEqualTo(Set.of("contentType")); + } + } + + @SpringBootApplication + @Import({HeaderFilterFunctionConfiguration.class}) + public static class HeaderEnricherProcessorTestApplication { + + @Bean + public String value() { + return "beanValue"; + } + } + +} diff --git a/applications/processor/pom.xml b/applications/processor/pom.xml index 1dd49636..a85a02db 100644 --- a/applications/processor/pom.xml +++ b/applications/processor/pom.xml @@ -15,6 +15,7 @@ filter-processor groovy-processor header-enricher-processor + header-filter-processor http-request-processor splitter-processor transform-processor diff --git a/build-apps.sh b/build-apps.sh new file mode 100755 index 00000000..620d0ba0 --- /dev/null +++ b/build-apps.sh @@ -0,0 +1,35 @@ +#!/bin/bash +SCDIR=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") +SCDIR=$(realpath "$SCDIR") +(return 0 2>/dev/null) && sourced=1 || sourced=0 +function check_env() { + eval ev='$'$1 + if [ "$ev" == "" ]; then + echo "$1 not defined" + if (( sourced != 0 )); then + return 1 + else + exit 1 + fi + fi +} + +if [ "$1" = "" ]; then + MAVEN_GOAL="install" +else + MAVEN_GOAL="$*" +fi + +$SCDIR/create-matrices.sh +PROCESSORS=$(jq -c '.processors | .[]' matrix.json | sed 's/\"//g') +SINKS=$(jq -c '.sinks | .[]' matrix.json | sed 's/\"//g') +SOURCES=$(jq -c '.sources | .[]' matrix.json | sed 's/\"//g') +for app in $PROCESSORS; do + $SCDIR/build-app.sh . "applications/processor/$app" +done +for app in $SOURCES; do + $SCDIR/build-app.sh . "applications/source/$app" +done +for app in $SINKS; do + $SCDIR/build-app.sh . "applications/sink/$app" +done diff --git a/build-release-train.sh b/build-release-train.sh new file mode 100755 index 00000000..19580a08 --- /dev/null +++ b/build-release-train.sh @@ -0,0 +1,23 @@ +#!/bin/bash +SCDIR=$(dirname "$(readlink -f "${BASH_SOURCE[0]}")") +SCDIR=$(realpath "$SCDIR") +(return 0 2>/dev/null) && sourced=1 || sourced=0 +function check_env() { + eval ev='$'$1 + if [ "$ev" == "" ]; then + echo "$1 not defined" + if (( sourced != 0 )); then + return 1 + else + exit 1 + fi + fi +} + +if [ "$1" = "" ]; then + MAVEN_GOAL="install" +else + MAVEN_GOAL="$*" +fi + +$SCDIR/build-folder.sh stream-applications-release-train "$MAVEN_GOAL" diff --git a/functions/function-dependencies/pom.xml b/functions/function-dependencies/pom.xml index 7aef8f65..4afc5601 100644 --- a/functions/function-dependencies/pom.xml +++ b/functions/function-dependencies/pom.xml @@ -227,6 +227,11 @@ header-enricher-function ${project.version} + + org.springframework.cloud.fn + header-filter-function + ${project.version} + org.springframework.cloud.fn http-request-function diff --git a/functions/function/header-filter-function/README.adoc b/functions/function/header-filter-function/README.adoc new file mode 100644 index 00000000..b9c9f93e --- /dev/null +++ b/functions/function/header-filter-function/README.adoc @@ -0,0 +1,25 @@ += Header Enricher Function + +This module provides a header enricher function that can be reused and composed in other applications. + +== Beans for injection + +You can import the `HeaderEnricherFunctionConfiguration` in a Spring Boot application and then inject the following bean. + +`headerFilterFunction` + +You can use `headerFilterFunction` as a qualifier when injecting. + +Once injected, you can use the `apply` method of the `Function` to invoke it and get the result. + +== Configuration Options + +For more information on the various options available, please see link:src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java[HeaderFilterFunctionProperties.java] + +== Tests + +See this link:src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java[test suite] for examples of how this function is used. + +== Other usage + +See this link:../../../applications/processor/header-filter-processor/README.adoc[README] where this function is used to create a Spring Cloud Stream application. \ No newline at end of file diff --git a/functions/function/header-filter-function/pom.xml b/functions/function/header-filter-function/pom.xml new file mode 100644 index 00000000..4c2f1324 --- /dev/null +++ b/functions/function/header-filter-function/pom.xml @@ -0,0 +1,30 @@ + + + 4.0.0 + + + org.springframework.cloud.fn + spring-functions-parent + 4.0.0-SNAPSHOT + ../../spring-functions-parent/pom.xml + + + header-filter-function + header-filter-function + Spring Native Function for applying message filters + + + + org.springframework.cloud.fn + payload-converter-function + ${project.version} + + + org.assertj + assertj-core + test + + + + diff --git a/functions/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java b/functions/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java new file mode 100644 index 00000000..2a5fdd2f --- /dev/null +++ b/functions/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.header.filter; + +import java.util.HashSet; +import java.util.function.Function; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.integration.transformer.HeaderFilter; +import org.springframework.messaging.Message; +import org.springframework.util.StringUtils; + +/** + * Configure a function using {@link HeaderFilter}. + * + * @author Corneil du Plessis + */ +@AutoConfiguration +@EnableConfigurationProperties(HeaderFilterFunctionProperties.class) +@ConditionalOnExpression("'${header.filter.remove}'!='' or '${header.filter.delete-all}' != ''") +public class HeaderFilterFunctionConfiguration { + private final HeaderFilterFunctionProperties properties; + public HeaderFilterFunctionConfiguration(HeaderFilterFunctionProperties properties) { + this.properties = properties; + } + + @Bean + public Function, Message> headerFilterFunction() { + if (properties.isDeleteAll()) { + return (message) -> { + var accessor = new IntegrationMessageHeaderAccessor(message); + var headers = new HashSet<>(message.getHeaders().keySet()); + headers.removeIf(accessor::isReadOnly); + HeaderFilter filter = new HeaderFilter(headers.toArray(new String[0])); + return filter.transform(message); + }; + } + else { + return headerFilter()::transform; + } + } + + @Bean + public HeaderFilter headerFilter() { + if (properties.getRemove() != null) { + String[] remove = StringUtils.tokenizeToStringArray(properties.getRemove(), ", ", true, true); + HeaderFilter filter = new HeaderFilter(remove); + if (properties.getRemove().contains("*")) { + filter.setPatternMatch(true); + } + return filter; + } + else { + return new HeaderFilter(""); + } + } + +} diff --git a/functions/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java b/functions/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java new file mode 100644 index 00000000..e0a873ec --- /dev/null +++ b/functions/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.header.filter; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties for configuration of header-filter-function. + * @author Corneil du Plessis + */ +@ConfigurationProperties("header.filter") +public class HeaderFilterFunctionProperties { + /** + * Indicates the need to remove all headers. + */ + private boolean deleteAll = false; + + /** + * Remove all headers named. A comma, space separated list of header names. + * The names may contain patterns. + */ + private String remove; + + public boolean isDeleteAll() { + return deleteAll; + } + + public void setDeleteAll(boolean deleteAll) { + this.deleteAll = deleteAll; + } + + public String getRemove() { + return remove; + } + + public void setRemove(String remove) { + this.remove = remove; + } +} diff --git a/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java b/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java new file mode 100644 index 00000000..df7debf1 --- /dev/null +++ b/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.header.filter; + +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = { + HeaderFilterFunctionApplicationDeleteAllTests.HeaderFilterFunctionTestApplication.class, + HeaderFilterFunctionConfiguration.class +}, + properties = {"header.filter.delete-all=true"} +) +public class HeaderFilterFunctionApplicationDeleteAllTests { + @Autowired + protected Function, Message> headerFilter; + + @Test + public void testRemoveLeavesIdTimestampAll() { + // given + final Message message = MessageBuilder.withPayload("hello") + .setHeader("foo", "bar") + .setHeader("bar", "foo") + .build(); + Message result = headerFilter.apply(message); + var headers = result.getHeaders().keySet(); + assertThat(headers).isEqualTo(Set.of("id", "timestamp")); + } + + @SpringBootApplication + static class HeaderFilterFunctionTestApplication { + public static void main(String[] args) throws Exception { + SpringApplication.main(args); + } + } + +} diff --git a/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java b/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java new file mode 100644 index 00000000..6d94aa60 --- /dev/null +++ b/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2023-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.header.filter; + +import java.util.Set; +import java.util.function.Function; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.messaging.Message; +import org.springframework.messaging.support.MessageBuilder; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest(classes = { + HeaderFilterFunctionApplicationTests.HeaderFilterFunctionTestApplication.class, + HeaderFilterFunctionConfiguration.class +}, + properties = {"header.filter.remove=foo,bar,pf-*"} +) +public class HeaderFilterFunctionApplicationTests { + @Autowired + protected Function, Message> headerFilter; + + @Test + public void testRemoveAll() { + // given + final Message message = MessageBuilder.withPayload("hello") + .setHeader("foo", "bar") + .setHeader("bar", "foo") + .build(); + Message result = headerFilter.apply(message); + var headers = HeaderUtils.getNonReadOnlyHeaders(result); + assertThat(headers).isEmpty(); + } + + @Test + public void testRemoveSome() { + // given + final Message message = MessageBuilder.withPayload("hello") + .setHeader("foo", "bar") + .setHeader("foo-bar", "bar") + .setHeader("bar", "foo") + .build(); + Message result = headerFilter.apply(message); + var headers = HeaderUtils.getNonReadOnlyHeaders(result); + assertThat(headers).isEqualTo(Set.of("foo-bar")); + } + + @Test + public void testRemoveSomeWithWildcard() { + // given + final Message message = MessageBuilder.withPayload("hello") + .setHeader("foo", "bar") + .setHeader("pf-foo", "bar") + .setHeader("pf-bar", "bar") + .setHeader("pfBar", "bar") + .setHeader("bar", "foo") + .build(); + Message result = headerFilter.apply(message); + var headers = HeaderUtils.getNonReadOnlyHeaders(result); + assertThat(result.getHeaders().keySet()).isEqualTo(Set.of("pfBar")); + } + + @Test + public void testRemoveLeavesIdTimestampAll() { + // given + final Message message = MessageBuilder.withPayload("hello") + .setHeader("foo", "bar") + .setHeader("bar", "foo") + .build(); + Message result = headerFilter.apply(message); + + assertThat(result.getHeaders().keySet()).isEqualTo(Set.of("id", "timestamp")); + } + + @SpringBootApplication + static class HeaderFilterFunctionTestApplication { + public static void main(String[] main) { + + } + } + +} diff --git a/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java b/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java new file mode 100644 index 00000000..d5ac05ce --- /dev/null +++ b/functions/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023-2023 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.fn.header.filter; + +import java.util.HashSet; +import java.util.Set; + +import org.jetbrains.annotations.NotNull; + +import org.springframework.integration.IntegrationMessageHeaderAccessor; +import org.springframework.messaging.Message; + +final public class HeaderUtils { + private HeaderUtils() { + } + + @NotNull + public static Set getNonReadOnlyHeaders(Message message) { + var headers = new HashSet<>(message.getHeaders().keySet()); + var accessor = new IntegrationMessageHeaderAccessor(message); + headers.removeIf(accessor::isReadOnly); + return headers; + } +} diff --git a/functions/function/header-filter-function/src/test/resources/test.properties b/functions/function/header-filter-function/src/test/resources/test.properties new file mode 100644 index 00000000..4f937e4c --- /dev/null +++ b/functions/function/header-filter-function/src/test/resources/test.properties @@ -0,0 +1 @@ +logging.level.root=debug diff --git a/functions/function/pom.xml b/functions/function/pom.xml index 9db3f927..1f56891f 100644 --- a/functions/function/pom.xml +++ b/functions/function/pom.xml @@ -14,6 +14,7 @@ aggregator-function filter-function header-enricher-function + header-filter-function http-request-function spel-function payload-converter-function