INT-4072 Fix applySequence with State Propagation
JIRA: https://jira.spring.io/browse/INT-4072 When `publishSubscribeChannel` is with `applySequence = true`, a `messageToSend` is overridden with `sequenceDetails` using `MessageBuilder`, therefore a new fresh `Message`. In case of state propagation, e.g. `SecurityContextPropagationChannelInterceptor`, we just lost the state from the `ThreadStatePropagationChannelInterceptor.MessageWithThreadState` because of new `Message<?>` * Add into `BroadcastingDispatcher` the logic to delegate `pushSequenceDetails` into `MessageWithThreadState` directly do not lose the `state` * Make `ThreadStatePropagationChannelInterceptor` as `MessageBuilderFactory`-aware and use it to rebuild an `original` `Message<?>` in the `MessageWithThreadState` to populate `SequenceDetails` **Cherry-pick to 4.2.x** Provide an explicit order for `publishSubscribeChannel` subscribers Fixes GH-1847 (https://github.com/spring-projects/spring-integration/issues/1847) Fix mutation in the `ThreadStatePropagationChannelInterceptor` Since `BroadcastingDispatcher` invokes `pushSequenceDetails` for each subscribed handler, make `MessageWithThreadState` as immutable and return a new instance via `cloneWithSequenceDetails()` method with particular `sequenceDetails`. Previous mutable solution ended up with the issue of concurrent modification. * Introduce `CloneableMessage` abstraction to let any custom `Message` to return `MessageBuilder` with desired context. * Introduce `DelegatingMessageBuilder` as an extension of the `MessageBuilder` to let custom `CloneableMessage` to return desired customization. * Add into `MessageBuilder#fromMessage()` `if` for the `CloneableMessage` * Add into `MutableMessageBuilder` a `warn` about `CloneableMessage` * Revert changes in the `BroadcastingDispatcher` in favor of `CloneableMessage` in the `MessageBuilder` * Redo `ThreadStatePropagationChannelInterceptor#MessageWithThreadState` logic to be based on the `CloneableMessage` and `DelegatingMessageBuilder` extension. Introduce `MessageDecorator` contract Remove `CloneableMessage` aspect and everything around `MessageWithThreadState` is now `MessageDecorator` and `BroadcastingDispatcher` check if incoming `message` is `MessageDecorator` and performs its `decorateMessage` after `builder`
This commit is contained in:
committed by
Gary Russell
parent
bce851576e
commit
999644a530
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.integration.channel.interceptor;
|
||||
|
||||
import org.springframework.integration.support.MessageDecorator;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
@@ -91,14 +92,15 @@ public abstract class ThreadStatePropagationChannelInterceptor<S>
|
||||
protected abstract void populatePropagatedContext(S state, Message<?> message, MessageChannel channel);
|
||||
|
||||
|
||||
private static final class MessageWithThreadState<S> implements Message<Object> {
|
||||
private static final class MessageWithThreadState<S> implements Message<Object>, MessageDecorator {
|
||||
|
||||
private final Message<?> message;
|
||||
private final Message<Object> message;
|
||||
|
||||
private final S state;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private MessageWithThreadState(Message<?> message, S state) {
|
||||
this.message = message;
|
||||
this.message = (Message<Object>) message;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@@ -112,6 +114,11 @@ public abstract class ThreadStatePropagationChannelInterceptor<S>
|
||||
return this.message.getHeaders();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message<?> decorateMessage(Message<?> message) {
|
||||
return new MessageWithThreadState<S>(message, this.state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MessageWithThreadState{" +
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.dispatcher;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -25,6 +26,7 @@ import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.integration.MessageDispatchingException;
|
||||
import org.springframework.integration.support.DefaultMessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageBuilderFactory;
|
||||
import org.springframework.integration.support.MessageDecorator;
|
||||
import org.springframework.integration.support.utils.IntegrationUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
@@ -158,13 +160,20 @@ public class BroadcastingDispatcher extends AbstractDispatcher implements BeanFa
|
||||
throw new MessageDispatchingException(message, "Dispatcher has no subscribers");
|
||||
}
|
||||
int sequenceSize = handlers.size();
|
||||
Message<?> messageToSend = message;
|
||||
UUID sequenceId = null;
|
||||
if (this.applySequence) {
|
||||
sequenceId = message.getHeaders().getId();
|
||||
}
|
||||
for (MessageHandler handler : handlers) {
|
||||
Message<?> messageToSend = message;
|
||||
if (this.applySequence) {
|
||||
messageToSend = getMessageBuilderFactory()
|
||||
.fromMessage(message)
|
||||
.pushSequenceDetails(message.getHeaders().getId(), sequenceNumber++, sequenceSize)
|
||||
.pushSequenceDetails(sequenceId, sequenceNumber++, sequenceSize)
|
||||
.build();
|
||||
if (message instanceof MessageDecorator) {
|
||||
messageToSend = ((MessageDecorator) message).decorateMessage(messageToSend);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.executor != null) {
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2016 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
|
||||
*
|
||||
* http://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.integration.support;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
/**
|
||||
* The {@link Message} decoration contract.
|
||||
* An implementation may decide to return any {@link Message} instance
|
||||
* and even a different {@link Message} implementation. Usually used to
|
||||
* wrap a message in another.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.2.9
|
||||
*/
|
||||
public interface MessageDecorator {
|
||||
|
||||
Message<?> decorateMessage(Message<?> message);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user