From bd5bf620be13eb983bfaa83ff70688621fc65702 Mon Sep 17 00:00:00 2001 From: Corneil du Plessis Date: Wed, 5 Apr 2023 17:59:21 +0200 Subject: [PATCH] Added header-filter function and processor. --- function-dependencies/pom.xml | 5 + function/header-filter-function/README.adoc | 25 +++++ function/header-filter-function/pom.xml | 30 ++++++ .../HeaderFilterFunctionConfiguration.java | 76 +++++++++++++ .../HeaderFilterFunctionProperties.java | 53 +++++++++ ...lterFunctionApplicationDeleteAllTests.java | 62 +++++++++++ .../HeaderFilterFunctionApplicationTests.java | 101 ++++++++++++++++++ .../cloud/fn/header/filter/HeaderUtils.java | 38 +++++++ .../src/test/resources/test.properties | 1 + function/pom.xml | 1 + 10 files changed, 392 insertions(+) create mode 100644 function/header-filter-function/README.adoc create mode 100644 function/header-filter-function/pom.xml create mode 100644 function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java create mode 100644 function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java create mode 100644 function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java create mode 100644 function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java create mode 100644 function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java create mode 100644 function/header-filter-function/src/test/resources/test.properties diff --git a/function-dependencies/pom.xml b/function-dependencies/pom.xml index 7aef8f65..4afc5601 100644 --- a/function-dependencies/pom.xml +++ b/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/function/header-filter-function/README.adoc b/function/header-filter-function/README.adoc new file mode 100644 index 00000000..b9c9f93e --- /dev/null +++ b/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/function/header-filter-function/pom.xml b/function/header-filter-function/pom.xml new file mode 100644 index 00000000..4c2f1324 --- /dev/null +++ b/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/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionConfiguration.java b/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/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/function/header-filter-function/src/main/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionProperties.java b/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/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/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationDeleteAllTests.java b/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/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/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderFilterFunctionApplicationTests.java b/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/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/function/header-filter-function/src/test/java/org/springframework/cloud/fn/header/filter/HeaderUtils.java b/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/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/function/header-filter-function/src/test/resources/test.properties b/function/header-filter-function/src/test/resources/test.properties new file mode 100644 index 00000000..4f937e4c --- /dev/null +++ b/function/header-filter-function/src/test/resources/test.properties @@ -0,0 +1 @@ +logging.level.root=debug diff --git a/function/pom.xml b/function/pom.xml index 9db3f927..1f56891f 100644 --- a/function/pom.xml +++ b/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