Added header-filter function and processor.

This commit is contained in:
Corneil du Plessis
2023-04-05 17:59:21 +02:00
committed by Corneil du Plessis
parent 670595e2ce
commit 2b66ea3bff
19 changed files with 687 additions and 12 deletions

View File

@@ -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.

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.cloud.fn</groupId>
<artifactId>spring-functions-parent</artifactId>
<version>4.0.0-SNAPSHOT</version>
<relativePath>../../spring-functions-parent/pom.xml</relativePath>
</parent>
<artifactId>header-filter-function</artifactId>
<name>header-filter-function</name>
<description>Spring Native Function for applying message filters</description>
<dependencies>
<dependency>
<groupId>org.springframework.cloud.fn</groupId>
<artifactId>payload-converter-function</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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<?>, 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("");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<?>, 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);
}
}
}

View File

@@ -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<?>, 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) {
}
}
}

View File

@@ -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<String> getNonReadOnlyHeaders(Message<?> message) {
var headers = new HashSet<>(message.getHeaders().keySet());
var accessor = new IntegrationMessageHeaderAccessor(message);
headers.removeIf(accessor::isReadOnly);
return headers;
}
}

View File

@@ -0,0 +1 @@
logging.level.root=debug