Skip to content

Commit 2621b20

Browse files
scordiofmbenhassine
authored andcommitted
Add MappingItemWriter
Signed-off-by: Stefano Cordio <stefano.cordio@gmail.com>
1 parent aa7543b commit 2621b20

File tree

3 files changed

+199
-0
lines changed

3 files changed

+199
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2025 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.batch.infrastructure.item.support;
18+
19+
import org.springframework.batch.infrastructure.item.Chunk;
20+
import org.springframework.batch.infrastructure.item.ItemWriter;
21+
22+
import java.util.function.Function;
23+
24+
/**
25+
* Adapts an {@link ItemWriter} accepting items of type {@link U} to one accepting items
26+
* of type {@link T} by applying a mapping function to each item before writing.
27+
* <p>
28+
* The {@code MappingItemWriter} is most useful when used in combination with a
29+
* {@link org.springframework.batch.infrastructure.item.support.CompositeItemWriter},
30+
* where the mapping function in front of the downstream writer can be a getter of the
31+
* input item or a more complex transformation logic.
32+
* <p>
33+
* This adapter mimics the behavior of
34+
* {@link java.util.stream.Collectors#mapping(Function, java.util.stream.Collector)}.
35+
*
36+
* @param <T> the type of the input items
37+
* @param <U> type of items accepted by downstream item writer
38+
* @author Stefano Cordio
39+
* @since 6.0
40+
*/
41+
public class MappingItemWriter<T, U> implements ItemWriter<T> {
42+
43+
private final Function<? super T, ? extends U> mapper;
44+
45+
private final ItemWriter<? super U> downstream;
46+
47+
/**
48+
* Create a new {@link MappingItemWriter}.
49+
* @param mapper the mapping function to apply to the input items
50+
* @param downstream the downstream item writer that accepts mapped items
51+
*/
52+
public MappingItemWriter(Function<? super T, ? extends U> mapper, ItemWriter<? super U> downstream) {
53+
this.mapper = mapper;
54+
this.downstream = downstream;
55+
}
56+
57+
@Override
58+
public void write(Chunk<? extends T> chunk) throws Exception {
59+
downstream.write(new Chunk<>(chunk.getItems().stream().map(mapper).toList()));
60+
}
61+
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright 2025 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.batch.infrastructure.item.support;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.batch.infrastructure.item.Chunk;
21+
import org.springframework.batch.infrastructure.item.ItemWriter;
22+
23+
import java.util.List;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* @author Stefano Cordio
29+
*/
30+
class MappingItemWriterIntegrationTests {
31+
32+
@Test
33+
void testWithCompositeItemWriter() throws Exception {
34+
// given
35+
record Person(String name, int age) {
36+
}
37+
38+
ListItemWriter<String> nameItemWriter = new ListItemWriter<>();
39+
ListItemWriter<Integer> ageItemWriter = new ListItemWriter<>();
40+
41+
ItemWriter<Person> underTest = new CompositeItemWriter<>(List.of( //
42+
new MappingItemWriter<>(Person::name, nameItemWriter), //
43+
new MappingItemWriter<>(Person::age, ageItemWriter)));
44+
45+
// when
46+
underTest.write(Chunk.of(new Person("Foo", 42), new Person("Bar", 24)));
47+
underTest.write(Chunk.of(new Person("Baz", 21), new Person("Qux", 12)));
48+
49+
// then
50+
assertThat(nameItemWriter.getWrittenItems()).containsExactly("Foo", "Bar", "Baz", "Qux");
51+
assertThat(ageItemWriter.getWrittenItems()).containsExactly(42, 24, 21, 12);
52+
}
53+
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2025 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.batch.infrastructure.item.support;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.batch.infrastructure.item.Chunk;
21+
22+
import java.util.function.Function;
23+
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.verify;
26+
27+
/**
28+
* @author Stefano Cordio
29+
*/
30+
class MappingItemWriterTests {
31+
32+
@Test
33+
void testWithMapperAcceptingItemSuperclass() throws Exception {
34+
// given
35+
Function<Entity, String> mapper = Entity::name;
36+
ListItemWriter<String> downstream = mock();
37+
MappingItemWriter<Person, String> underTest = new MappingItemWriter<>(mapper, downstream);
38+
39+
// when
40+
underTest.write(Chunk.of(new Person("Foo", 42)));
41+
42+
// then
43+
verify(downstream).write(Chunk.of("Foo"));
44+
}
45+
46+
@Test
47+
void testWithMapperProducingMappedItemSubclass() throws Exception {
48+
// given
49+
Function<Person, String> mapper = Person::name;
50+
ListItemWriter<CharSequence> downstream = mock();
51+
MappingItemWriter<Person, CharSequence> underTest = new MappingItemWriter<>(mapper, downstream);
52+
53+
// when
54+
underTest.write(Chunk.of(new Person("Foo", 42)));
55+
56+
// then
57+
verify(downstream).write(Chunk.of("Foo"));
58+
}
59+
60+
@Test
61+
void testWithDownstreamAcceptingMappedItemSuperclass() throws Exception {
62+
// given
63+
Function<Person, String> mapper = Person::name;
64+
ListItemWriter<CharSequence> downstream = mock();
65+
MappingItemWriter<Person, String> underTest = new MappingItemWriter<>(mapper, downstream);
66+
67+
// when
68+
underTest.write(Chunk.of(new Person("Foo", 42)));
69+
70+
// then
71+
verify(downstream).write(Chunk.of("Foo"));
72+
}
73+
74+
private interface Entity {
75+
76+
String name();
77+
78+
}
79+
80+
private record Person(String name, int age) implements Entity {
81+
}
82+
83+
}

0 commit comments

Comments
 (0)