Skip to content

Commit 3703be5

Browse files
committed
MessageHeaderAccessor handle self-copy correctly
1. Revert changes in setHeader from 5.2.9 that caused regression on self-copy. 2. Update copyHeaders/IfAbsent to ensure a copy of native headers. 3. Exit if source and target are the same instance, as an optimization. Closes spring-projectsgh-26155
1 parent b929edb commit 3703be5

File tree

3 files changed

+101
-40
lines changed

3 files changed

+101
-40
lines changed

spring-messaging/src/main/java/org/springframework/messaging/support/MessageHeaderAccessor.java

+14-12
Original file line numberDiff line numberDiff line change
@@ -378,27 +378,29 @@ private List<String> getMatchingHeaderNames(String pattern, @Nullable Map<String
378378
* {@link #copyHeadersIfAbsent(Map)} to avoid overwriting values.
379379
*/
380380
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
381-
if (headersToCopy != null) {
382-
headersToCopy.forEach((key, value) -> {
383-
if (!isReadOnly(key)) {
384-
setHeader(key, value);
385-
}
386-
});
381+
if (headersToCopy == null || this.headers == headersToCopy) {
382+
return;
387383
}
384+
headersToCopy.forEach((key, value) -> {
385+
if (!isReadOnly(key)) {
386+
setHeader(key, value);
387+
}
388+
});
388389
}
389390

390391
/**
391392
* Copy the name-value pairs from the provided Map.
392393
* <p>This operation will <em>not</em> overwrite any existing values.
393394
*/
394395
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
395-
if (headersToCopy != null) {
396-
headersToCopy.forEach((key, value) -> {
397-
if (!isReadOnly(key)) {
398-
setHeaderIfAbsent(key, value);
399-
}
400-
});
396+
if (headersToCopy == null || this.headers == headersToCopy) {
397+
return;
401398
}
399+
headersToCopy.forEach((key, value) -> {
400+
if (!isReadOnly(key)) {
401+
setHeaderIfAbsent(key, value);
402+
}
403+
});
402404
}
403405

404406
protected boolean isReadOnly(String headerName) {

spring-messaging/src/main/java/org/springframework/messaging/support/NativeMessageHeaderAccessor.java

+52-20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.messaging.support;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collections;
2021
import java.util.LinkedList;
2122
import java.util.List;
@@ -75,6 +76,8 @@ protected NativeMessageHeaderAccessor(@Nullable Message<?> message) {
7576
@SuppressWarnings("unchecked")
7677
Map<String, List<String>> map = (Map<String, List<String>>) getHeader(NATIVE_HEADERS);
7778
if (map != null) {
79+
// setHeader checks for equality but we need copy of native headers
80+
setHeader(NATIVE_HEADERS, null);
7881
setHeader(NATIVE_HEADERS, new LinkedMultiValueMap<>(map));
7982
}
8083
}
@@ -103,38 +106,43 @@ public void setImmutable() {
103106
if (isMutable()) {
104107
Map<String, List<String>> map = getNativeHeaders();
105108
if (map != null) {
109+
// setHeader checks for equality but we need immutable wrapper
110+
setHeader(NATIVE_HEADERS, null);
106111
setHeader(NATIVE_HEADERS, Collections.unmodifiableMap(map));
107112
}
108113
super.setImmutable();
109114
}
110115
}
111116

112117
@Override
113-
public void setHeader(String name, @Nullable Object value) {
114-
if (name.equalsIgnoreCase(NATIVE_HEADERS)) {
115-
// Force removal since setHeader checks for equality
116-
super.setHeader(NATIVE_HEADERS, null);
118+
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
119+
if (headersToCopy == null) {
120+
return;
121+
}
122+
123+
@SuppressWarnings("unchecked")
124+
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
125+
if (map != null && map != getNativeHeaders()) {
126+
map.forEach(this::setNativeHeaderValues);
117127
}
118-
super.setHeader(name, value);
128+
129+
// setHeader checks for equality, native headers should be equal by now
130+
super.copyHeaders(headersToCopy);
119131
}
120132

121133
@Override
122-
@SuppressWarnings("unchecked")
123-
public void copyHeaders(@Nullable Map<String, ?> headersToCopy) {
124-
if (headersToCopy != null) {
125-
Map<String, List<String>> nativeHeaders = getNativeHeaders();
126-
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
127-
if (map != null) {
128-
if (nativeHeaders != null) {
129-
nativeHeaders.putAll(map);
130-
}
131-
else {
132-
nativeHeaders = new LinkedMultiValueMap<>(map);
133-
}
134-
}
135-
super.copyHeaders(headersToCopy);
136-
setHeader(NATIVE_HEADERS, nativeHeaders);
134+
public void copyHeadersIfAbsent(@Nullable Map<String, ?> headersToCopy) {
135+
if (headersToCopy == null) {
136+
return;
137+
}
138+
139+
@SuppressWarnings("unchecked")
140+
Map<String, List<String>> map = (Map<String, List<String>>) headersToCopy.get(NATIVE_HEADERS);
141+
if (map != null && getNativeHeaders() == null) {
142+
map.forEach(this::setNativeHeaderValues);
137143
}
144+
145+
super.copyHeadersIfAbsent(headersToCopy);
138146
}
139147

140148
/**
@@ -201,6 +209,30 @@ public void setNativeHeader(String name, @Nullable String value) {
201209
}
202210
}
203211

212+
/**
213+
* Variant of {@link #addNativeHeader(String, String)} for all values.
214+
* @since 5.2.12
215+
*/
216+
public void setNativeHeaderValues(String name, @Nullable List<String> values) {
217+
Assert.state(isMutable(), "Already immutable");
218+
Map<String, List<String>> map = getNativeHeaders();
219+
if (values == null) {
220+
if (map != null && map.get(name) != null) {
221+
setModified(true);
222+
map.remove(name);
223+
}
224+
return;
225+
}
226+
if (map == null) {
227+
map = new LinkedMultiValueMap<>(3);
228+
setHeader(NATIVE_HEADERS, map);
229+
}
230+
if (!ObjectUtils.nullSafeEquals(values, getHeader(name))) {
231+
setModified(true);
232+
map.put(name, new ArrayList<>(values));
233+
}
234+
}
235+
204236
/**
205237
* Add the specified native header value to existing values.
206238
* <p>In order for this to work, the accessor must be {@link #isMutable()

spring-messaging/src/test/java/org/springframework/messaging/support/NativeMessageHeaderAccessorTests.java

+35-8
Original file line numberDiff line numberDiff line change
@@ -226,19 +226,46 @@ public void setImmutableIdempotent() {
226226

227227
@Test // gh-25821
228228
void copyImmutableToMutable() {
229-
NativeMessageHeaderAccessor source = new NativeMessageHeaderAccessor();
230-
source.addNativeHeader("foo", "bar");
231-
Message<String> message = MessageBuilder.createMessage("payload", source.getMessageHeaders());
229+
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
230+
sourceAccessor.addNativeHeader("foo", "bar");
231+
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
232232

233-
NativeMessageHeaderAccessor target = new NativeMessageHeaderAccessor();
234-
target.copyHeaders(message.getHeaders());
235-
target.setLeaveMutable(true);
236-
message = MessageBuilder.createMessage(message.getPayload(), target.getMessageHeaders());
233+
NativeMessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
234+
targetAccessor.copyHeaders(source.getHeaders());
235+
targetAccessor.setLeaveMutable(true);
236+
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
237237

238-
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(message);
238+
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
239239
assertThat(accessor.isMutable());
240240
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
241241
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
242242
}
243243

244+
@Test // gh-25821
245+
void copyIfAbsentImmutableToMutable() {
246+
NativeMessageHeaderAccessor sourceAccessor = new NativeMessageHeaderAccessor();
247+
sourceAccessor.addNativeHeader("foo", "bar");
248+
Message<String> source = MessageBuilder.createMessage("payload", sourceAccessor.getMessageHeaders());
249+
250+
MessageHeaderAccessor targetAccessor = new NativeMessageHeaderAccessor();
251+
targetAccessor.copyHeadersIfAbsent(source.getHeaders());
252+
targetAccessor.setLeaveMutable(true);
253+
Message<?> target = MessageBuilder.createMessage(source.getPayload(), targetAccessor.getMessageHeaders());
254+
255+
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(target);
256+
assertThat(accessor.isMutable());
257+
((NativeMessageHeaderAccessor) accessor).addNativeHeader("foo", "baz");
258+
assertThat(((NativeMessageHeaderAccessor) accessor).getNativeHeader("foo")).containsExactly("bar", "baz");
259+
}
260+
261+
@Test // gh-26155
262+
void copySelf() {
263+
NativeMessageHeaderAccessor accessor = new NativeMessageHeaderAccessor();
264+
accessor.addNativeHeader("foo", "bar");
265+
accessor.setHeader("otherHeader", "otherHeaderValue");
266+
accessor.setLeaveMutable(true);
267+
268+
// Does not fail with ConcurrentModificationException
269+
accessor.copyHeaders(accessor.getMessageHeaders());
270+
}
244271
}

0 commit comments

Comments
 (0)