INT-3882: StompSession Reconnection Logic
JIRA: https://jira.spring.io/browse/INT-3882 Add reconnect support for the StompSession: * Introduce `recoveryInterval` for the `AbstractStompSessionManager` * Add reconnect scheduled task * Add handling of the `ConnectionLostException` into the `AbstractStompSessionManager`, as well as for the `StompInboundChannelAdapter` and `StompMessageHandler` * Cover adapters reconnection feature with tests * Documentation polishing Rework logic according PR comments * Make `StompMessageHandler` as a "lazy-load" for the connection * Add "direct" connect interaction for the `AbstractStompSessionManager` * Polishing tests Polishing Fix `StompAdaptersParserTests#testStompSessionManagerReconnect()` to use "fake" server port Address PR comments The further polishing Some further polishing * `AbstractStompSessionManager`: `reconnectFuture.cancel(true)` on each `scheduleReconnect()` to avoid something like "DDoS attack" * Add reconnect feature test for the `StompInboundChannelAdapterWebSocketIntegrationTests`, closing and then refreshing again the `serverContext`
This commit is contained in:
committed by
Gary Russell
parent
f1bd6e3bac
commit
b9730cd183
@@ -19,16 +19,19 @@ package org.springframework.integration.stomp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.integration.stomp.event.StompExceptionEvent;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.integration.stomp.event.StompConnectionFailedEvent;
|
||||
import org.springframework.integration.stomp.event.StompSessionConnectedEvent;
|
||||
import org.springframework.messaging.simp.stomp.ConnectionLostException;
|
||||
import org.springframework.messaging.simp.stomp.StompClientSupport;
|
||||
import org.springframework.messaging.simp.stomp.StompCommand;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||
@@ -43,9 +46,9 @@ import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
* Base {@link StompSessionManager} implementation to manage a single {@link StompSession}
|
||||
* over its {@link ListenableFuture} from the target implementation of this class.
|
||||
* <p>
|
||||
* The connection to the {@link StompSession} is made during {@link #afterPropertiesSet()}.
|
||||
* The connection to the {@link StompSession} is made during {@link #start()}.
|
||||
* <p>
|
||||
* The {@link #destroy()} lifecycle method manages {@link StompSession#disconnect()}.
|
||||
* The {@link #stop()} lifecycle method manages {@link StompSession#disconnect()}.
|
||||
* <p>
|
||||
* The {@link #connect(StompSessionHandler)} and {@link #disconnect(StompSessionHandler)} method
|
||||
* implementations populate/remove the provided {@link StompSessionHandler} to/from an internal
|
||||
@@ -53,19 +56,28 @@ import org.springframework.util.concurrent.ListenableFutureCallback;
|
||||
* to the provided {@link StompSessionHandler}s.
|
||||
* This {@link AbstractStompSessionManager.CompositeStompSessionHandler} is used for the
|
||||
* {@link StompSession} connection.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.2
|
||||
*/
|
||||
public abstract class AbstractStompSessionManager implements StompSessionManager, ApplicationEventPublisherAware,
|
||||
InitializingBean, DisposableBean, BeanNameAware {
|
||||
SmartLifecycle, DisposableBean, BeanNameAware {
|
||||
|
||||
private static final long DEFAULT_RECOVERY_INTERVAL = 10000;
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final CompositeStompSessionHandler compositeStompSessionHandler = new CompositeStompSessionHandler();
|
||||
|
||||
private final Object lifecycleMonitor = new Object();
|
||||
|
||||
protected final StompClientSupport stompClient;
|
||||
|
||||
private boolean autoStartup = false;
|
||||
|
||||
private boolean running = false;
|
||||
|
||||
private int phase = Integer.MAX_VALUE / 2;
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private volatile StompHeaders connectHeaders;
|
||||
@@ -74,8 +86,14 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
|
||||
|
||||
private volatile boolean autoReceipt;
|
||||
|
||||
private volatile boolean connecting;
|
||||
|
||||
private volatile boolean connected;
|
||||
|
||||
private volatile long recoveryInterval = DEFAULT_RECOVERY_INTERVAL;
|
||||
|
||||
private volatile ScheduledFuture<?> reconnectFuture;
|
||||
|
||||
private String name;
|
||||
|
||||
public AbstractStompSessionManager(StompClientSupport stompClient) {
|
||||
@@ -111,50 +129,160 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param recoveryInterval the reconnect interval in milliseconds in case of lost connection.
|
||||
* @since 4.2.2
|
||||
*/
|
||||
public void setRecoveryInterval(int recoveryInterval) {
|
||||
this.recoveryInterval = recoveryInterval;
|
||||
}
|
||||
|
||||
public void setAutoStartup(boolean autoStartup) {
|
||||
this.autoStartup = autoStartup;
|
||||
}
|
||||
|
||||
public void setPhase(int phase) {
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
public long getRecoveryInterval() {
|
||||
return recoveryInterval;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
public boolean isAutoStartup() {
|
||||
return this.autoStartup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return this.running;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPhase() {
|
||||
return this.phase;
|
||||
}
|
||||
|
||||
private void connect() {
|
||||
this.connecting = true;
|
||||
this.stompSessionListenableFuture = doConnect(this.compositeStompSessionHandler);
|
||||
this.stompSessionListenableFuture.addCallback(new ListenableFutureCallback<StompSession>() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
logger.error("STOMP connect error.", e);
|
||||
if (applicationEventPublisher != null) {
|
||||
applicationEventPublisher.publishEvent(
|
||||
new StompExceptionEvent(AbstractStompSessionManager.this, e));
|
||||
}
|
||||
scheduleReconnect(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(StompSession stompSession) {
|
||||
stompSession.setAutoReceipt(autoReceipt);
|
||||
AbstractStompSessionManager.this.connected = true;
|
||||
AbstractStompSessionManager.this.connecting = false;
|
||||
stompSession.setAutoReceipt(isAutoReceiptEnabled());
|
||||
if (AbstractStompSessionManager.this.applicationEventPublisher != null) {
|
||||
AbstractStompSessionManager.this.applicationEventPublisher.publishEvent(
|
||||
new StompSessionConnectedEvent(this));
|
||||
}
|
||||
AbstractStompSessionManager.this.reconnectFuture = null;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleReconnect(Throwable e) {
|
||||
if (this.reconnectFuture != null) {
|
||||
this.reconnectFuture.cancel(true);
|
||||
this.reconnectFuture = null;
|
||||
}
|
||||
this.connecting = this.connected = false;
|
||||
logger.error("STOMP connect error.", e);
|
||||
if (this.applicationEventPublisher != null) {
|
||||
this.applicationEventPublisher.publishEvent(
|
||||
new StompConnectionFailedEvent(this, e));
|
||||
}
|
||||
|
||||
this.reconnectFuture = this.stompClient.getTaskScheduler()
|
||||
.scheduleWithFixedDelay(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
connect();
|
||||
}
|
||||
|
||||
}, this.recoveryInterval);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.stompSessionListenableFuture.addCallback(new ListenableFutureCallback<StompSession>() {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
AbstractStompSessionManager.this.connected = false;
|
||||
public void destroy() {
|
||||
if (this.stompSessionListenableFuture != null) {
|
||||
if (this.reconnectFuture != null) {
|
||||
this.reconnectFuture.cancel(false);
|
||||
this.reconnectFuture = null;
|
||||
}
|
||||
this.stompSessionListenableFuture.addCallback(new ListenableFutureCallback<StompSession>() {
|
||||
|
||||
@Override
|
||||
public void onSuccess(StompSession session) {
|
||||
session.disconnect();
|
||||
AbstractStompSessionManager.this.connected = false;
|
||||
@Override
|
||||
public void onFailure(Throwable ex) {
|
||||
AbstractStompSessionManager.this.connected = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(StompSession session) {
|
||||
session.disconnect();
|
||||
AbstractStompSessionManager.this.connected = false;
|
||||
}
|
||||
|
||||
});
|
||||
this.stompSessionListenableFuture = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (!isRunning()) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Starting " + getClass().getSimpleName());
|
||||
}
|
||||
connect();
|
||||
this.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@Override
|
||||
public void stop(Runnable callback) {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
stop();
|
||||
if (callback != null) {
|
||||
callback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
synchronized (this.lifecycleMonitor) {
|
||||
if (isRunning()) {
|
||||
this.running = false;
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Stopping " + getClass().getSimpleName());
|
||||
}
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(StompSessionHandler handler) {
|
||||
this.compositeStompSessionHandler.addHandler(handler);
|
||||
if (!isConnected() && !this.connecting) {
|
||||
if (this.reconnectFuture != null) {
|
||||
this.reconnectFuture.cancel(true);
|
||||
this.reconnectFuture = null;
|
||||
}
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,18 +305,16 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
|
||||
protected abstract ListenableFuture<StompSession> doConnect(StompSessionHandler handler);
|
||||
|
||||
|
||||
private static class CompositeStompSessionHandler extends StompSessionHandlerAdapter {
|
||||
private class CompositeStompSessionHandler extends StompSessionHandlerAdapter {
|
||||
|
||||
private final List<StompSessionHandler> delegates =
|
||||
Collections.synchronizedList(new ArrayList<StompSessionHandler>());
|
||||
|
||||
private volatile StompSession session;
|
||||
|
||||
private volatile StompHeaders connectedHeaders;
|
||||
|
||||
void addHandler(StompSessionHandler delegate) {
|
||||
if (this.session != null) {
|
||||
delegate.afterConnected(this.session, this.connectedHeaders);
|
||||
delegate.afterConnected(this.session, getConnectHeaders());
|
||||
}
|
||||
synchronized (this.delegates) {
|
||||
this.delegates.add(delegate);
|
||||
@@ -204,7 +330,6 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
|
||||
@Override
|
||||
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
|
||||
this.session = session;
|
||||
this.connectedHeaders = connectedHeaders;
|
||||
synchronized (this.delegates) {
|
||||
for (StompSessionHandler delegate : this.delegates) {
|
||||
delegate.afterConnected(session, connectedHeaders);
|
||||
@@ -214,7 +339,7 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
|
||||
|
||||
@Override
|
||||
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
|
||||
Throwable exception) {
|
||||
Throwable exception) {
|
||||
synchronized (this.delegates) {
|
||||
for (StompSessionHandler delegate : this.delegates) {
|
||||
delegate.handleException(session, command, headers, payload, exception);
|
||||
@@ -224,6 +349,10 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
|
||||
|
||||
@Override
|
||||
public void handleTransportError(StompSession session, Throwable exception) {
|
||||
if (exception instanceof ConnectionLostException) {
|
||||
this.session = null;
|
||||
scheduleReconnect(exception);
|
||||
}
|
||||
synchronized (this.delegates) {
|
||||
for (StompSessionHandler delegate : this.delegates) {
|
||||
delegate.handleTransportError(session, exception);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 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.stomp.event;
|
||||
|
||||
/**
|
||||
* The {@link StompIntegrationEvent} implementation for the failed connection exceptions.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.2.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class StompConnectionFailedEvent extends StompIntegrationEvent {
|
||||
|
||||
public StompConnectionFailedEvent(Object source, Throwable cause) {
|
||||
super(source, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2015 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.stomp.event;
|
||||
|
||||
/**
|
||||
* The {@link StompIntegrationEvent} indicating the STOMP session establishment.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.2.2
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class StompSessionConnectedEvent extends StompIntegrationEvent {
|
||||
|
||||
public StompSessionConnectedEvent(Object source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.integration.endpoint.MessageProducerSupport;
|
||||
import org.springframework.integration.mapping.HeaderMapper;
|
||||
import org.springframework.integration.stomp.StompSessionManager;
|
||||
@@ -38,6 +39,7 @@ import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandlingException;
|
||||
import org.springframework.messaging.simp.stomp.ConnectionLostException;
|
||||
import org.springframework.messaging.simp.stomp.StompCommand;
|
||||
import org.springframework.messaging.simp.stomp.StompFrameHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
||||
@@ -190,16 +192,24 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
|
||||
|
||||
@Override
|
||||
protected void doStart() {
|
||||
if (this.stompSessionManager instanceof Lifecycle) {
|
||||
((Lifecycle) this.stompSessionManager).start();
|
||||
}
|
||||
this.stompSessionManager.connect(this.stompSessionHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() {
|
||||
for (StompSession.Subscription subscription : this.subscriptions.values()) {
|
||||
subscription.unsubscribe();
|
||||
this.stompSessionManager.disconnect(this.stompSessionHandler);
|
||||
try {
|
||||
for (StompSession.Subscription subscription : this.subscriptions.values()) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.warn("The exception during unsubscribtion.", e);
|
||||
}
|
||||
this.subscriptions.clear();
|
||||
this.stompSessionManager.disconnect(this.stompSessionHandler);
|
||||
}
|
||||
|
||||
private void subscribeDestination(final String destination) {
|
||||
@@ -262,7 +272,7 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
|
||||
}
|
||||
else {
|
||||
logger.warn("The StompInboundChannelAdapter [" + getComponentName() +
|
||||
"] hasn't been connected to StompSession. Check the state of [" + this.stompSessionManager + "]");
|
||||
"] ins't connected to StompSession. Check the state of [" + this.stompSessionManager + "]");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +304,9 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
|
||||
|
||||
@Override
|
||||
public void handleTransportError(StompSession session, Throwable exception) {
|
||||
if (exception instanceof ConnectionLostException) {
|
||||
StompInboundChannelAdapter.this.stompSession = null;
|
||||
}
|
||||
logger.error("STOMP transport error for session: [" + session + "]", exception);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.integration.stomp.outbound;
|
||||
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.ApplicationEventPublisherAware;
|
||||
import org.springframework.context.Lifecycle;
|
||||
@@ -32,28 +35,35 @@ import org.springframework.integration.stomp.support.StompHeaderMapper;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageDeliveryException;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.messaging.simp.stomp.ConnectionLostException;
|
||||
import org.springframework.messaging.simp.stomp.StompCommand;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
|
||||
import org.springframework.messaging.simp.stomp.StompHeaders;
|
||||
import org.springframework.messaging.simp.stomp.StompSession;
|
||||
import org.springframework.messaging.simp.stomp.StompSessionHandler;
|
||||
import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link AbstractMessageHandler} implementation to send messages to STOMP destinations.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
* @since 4.2
|
||||
*/
|
||||
public class StompMessageHandler extends AbstractMessageHandler implements ApplicationEventPublisherAware, Lifecycle {
|
||||
|
||||
private static final int DEFAULT_CONNECT_TIMEOUT = 3000;
|
||||
|
||||
private final StompSessionHandler sessionHandler = new IntegrationOutboundStompSessionHandler();
|
||||
|
||||
private final StompSessionManager stompSessionManager;
|
||||
|
||||
private final Semaphore connectSemaphore = new Semaphore(0);
|
||||
|
||||
private volatile StompSession stompSession;
|
||||
|
||||
private volatile Throwable transportError;
|
||||
|
||||
private volatile boolean running;
|
||||
|
||||
private volatile HeaderMapper<StompHeaders> headerMapper = new StompHeaderMapper();
|
||||
@@ -64,6 +74,8 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
|
||||
private ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
private volatile long connectTimeout = DEFAULT_CONNECT_TIMEOUT;
|
||||
|
||||
public StompMessageHandler(StompSessionManager stompSessionManager) {
|
||||
Assert.notNull(stompSessionManager, "'stompSessionManager' is required.");
|
||||
this.stompSessionManager = stompSessionManager;
|
||||
@@ -84,6 +96,17 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
this.headerMapper = headerMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the the timeout in milliseconds to wait for the STOMP session establishment.
|
||||
* Must be greater than
|
||||
* {@link org.springframework.integration.stomp.AbstractStompSessionManager#setRecoveryInterval(int)}.
|
||||
* @param connectTimeout the timeout to use.
|
||||
* @since 4.2.2
|
||||
*/
|
||||
public void setConnectTimeout(long connectTimeout) {
|
||||
this.connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
|
||||
this.evaluationContext = evaluationContext;
|
||||
}
|
||||
@@ -104,10 +127,13 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
|
||||
@Override
|
||||
protected void handleMessageInternal(final Message<?> message) throws Exception {
|
||||
if (!this.isRunning()) {
|
||||
throw new MessageDeliveryException(message, "The StompMessageHandler [" + getComponentName() +
|
||||
"] hasn't been connected to StompSession. Check the state of [" + this.stompSessionManager + "]");
|
||||
try {
|
||||
connectIfNecessary();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new MessageDeliveryException(message, "The [" + this + "] could not deliver message." , e);
|
||||
}
|
||||
StompSession stompSession = this.stompSession;
|
||||
|
||||
StompHeaders stompHeaders = new StompHeaders();
|
||||
this.headerMapper.fromHeaders(message.getHeaders(), stompHeaders);
|
||||
@@ -118,7 +144,7 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
stompHeaders.setDestination(destination);
|
||||
}
|
||||
|
||||
final StompSession.Receiptable receiptable = this.stompSession.send(stompHeaders, message.getPayload());
|
||||
final StompSession.Receiptable receiptable = stompSession.send(stompHeaders, message.getPayload());
|
||||
if (receiptable.getReceiptId() != null) {
|
||||
final String destination = stompHeaders.getDestination();
|
||||
if (this.applicationEventPublisher != null) {
|
||||
@@ -154,15 +180,40 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
}
|
||||
}
|
||||
|
||||
private StompSession connectIfNecessary() throws Exception {
|
||||
synchronized (this.sessionHandler) {
|
||||
if (this.stompSession == null || !this.stompSessionManager.isConnected()) {
|
||||
this.stompSessionManager.disconnect(this.sessionHandler);
|
||||
this.stompSessionManager.connect(this.sessionHandler);
|
||||
if (!this.connectSemaphore.tryAcquire(this.connectTimeout, TimeUnit.MILLISECONDS)
|
||||
|| this.stompSession == null) {
|
||||
if (this.transportError != null) {
|
||||
if (this.transportError instanceof ConnectionLostException) {
|
||||
throw (ConnectionLostException) this.transportError;
|
||||
}
|
||||
else {
|
||||
throw new ConnectionLostException(this.transportError.getMessage());
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new ConnectionLostException("Failed to obtain StompSession during timeout: "
|
||||
+ this.connectTimeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.stompSession;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
this.stompSessionManager.connect(this.sessionHandler);
|
||||
this.running = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
this.stompSessionManager.disconnect(this.sessionHandler);
|
||||
this.running = false;
|
||||
this.stompSessionManager.disconnect(this.sessionHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,13 +221,13 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
return this.running;
|
||||
}
|
||||
|
||||
|
||||
private class IntegrationOutboundStompSessionHandler extends StompSessionHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
|
||||
StompMessageHandler.this.transportError = null;
|
||||
StompMessageHandler.this.stompSession = session;
|
||||
StompMessageHandler.this.running = true;
|
||||
StompMessageHandler.this.connectSemaphore.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,8 +251,22 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportError(StompSession session, Throwable exception) {
|
||||
logger.error("STOMP transport error for session: [" + session + "]", exception);
|
||||
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
|
||||
Throwable exception) {
|
||||
Message<byte[]> message = MessageBuilder.createMessage(payload,
|
||||
StompHeaderAccessor.create(command, headers).getMessageHeaders());
|
||||
logger.error("The exception for session [" + session + "] on message [" + message + "]", exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void handleTransportError(StompSession session, Throwable exception) {
|
||||
StompMessageHandler.this.transportError = exception;
|
||||
if (exception instanceof ConnectionLostException) {
|
||||
StompMessageHandler.this.stompSession = null;
|
||||
}
|
||||
else {
|
||||
logger.error("STOMP transport error for session: [" + session + "]", exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user