* Fix Sonar issues for Sec., STOMP, SFTP, WebFlux

This commit is contained in:
Artem Bilan
2018-12-19 15:25:27 -05:00
committed by Gary Russell
parent 7790f9e550
commit 761af2730c
8 changed files with 342 additions and 260 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2017 the original author or authors.
* Copyright 2015-2018 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.
@@ -35,12 +35,14 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.integration.stomp.event.StompConnectionFailedEvent;
import org.springframework.integration.stomp.event.StompSessionConnectedEvent;
import org.springframework.lang.Nullable;
import org.springframework.messaging.simp.stomp.StompClientSupport;
import org.springframework.messaging.simp.stomp.StompCommand;
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.scheduling.TaskScheduler;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.concurrent.ListenableFuture;
@@ -73,12 +75,12 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
protected final Log logger = LogFactory.getLog(getClass());
protected final StompClientSupport stompClient;
private final CompositeStompSessionHandler compositeStompSessionHandler = new CompositeStompSessionHandler();
private final Object lifecycleMonitor = new Object();
protected final StompClientSupport stompClient;
private final AtomicInteger epoch = new AtomicInteger();
private boolean autoStartup = false;
@@ -176,9 +178,7 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
private synchronized void connect() {
if (this.connecting || this.connected) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Aborting connect; another thread is connecting.");
}
this.logger.debug("Aborting connect; another thread is connecting.");
return;
}
final int epoch = this.epoch.get();
@@ -201,12 +201,12 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
final CountDownLatch connectLatch = new CountDownLatch(1);
this.stompSessionListenableFuture.addCallback(
stompSession -> {
if (AbstractStompSessionManager.this.logger.isDebugEnabled()) {
AbstractStompSessionManager.this.logger.debug("onSuccess");
}
AbstractStompSessionManager.this.logger.debug("onSuccess");
AbstractStompSessionManager.this.connected = true;
AbstractStompSessionManager.this.connecting = false;
stompSession.setAutoReceipt(isAutoReceiptEnabled());
if (stompSession != null) {
stompSession.setAutoReceipt(isAutoReceiptEnabled());
}
if (AbstractStompSessionManager.this.applicationEventPublisher != null) {
AbstractStompSessionManager.this.applicationEventPublisher.publishEvent(
new StompSessionConnectedEvent(this));
@@ -216,9 +216,7 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
},
e -> {
if (AbstractStompSessionManager.this.logger.isDebugEnabled()) {
AbstractStompSessionManager.this.logger.debug("onFailure", e);
}
AbstractStompSessionManager.this.logger.debug("onFailure", e);
connectLatch.countDown();
if (epoch == AbstractStompSessionManager.this.epoch.get()) {
scheduleReconnect(e);
@@ -255,12 +253,14 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
this.reconnectFuture = null;
}
if (this.stompClient.getTaskScheduler() != null) {
this.reconnectFuture = this.stompClient.getTaskScheduler()
.schedule(this::connect, new Date(System.currentTimeMillis() + this.recoveryInterval));
TaskScheduler taskScheduler = this.stompClient.getTaskScheduler();
if (taskScheduler != null) {
this.reconnectFuture =
taskScheduler.schedule(this::connect,
new Date(System.currentTimeMillis() + this.recoveryInterval));
}
else {
this.logger.info("For automatic reconnection the 'stompClient' should be configured with a TaskScheduler.");
this.logger.info("For automatic reconnection the stompClient should be configured with a TaskScheduler.");
}
}
@@ -271,20 +271,23 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
this.reconnectFuture.cancel(false);
this.reconnectFuture = null;
}
this.stompSessionListenableFuture.addCallback(new ListenableFutureCallback<StompSession>() {
this.stompSessionListenableFuture.addCallback(
new ListenableFutureCallback<StompSession>() {
@Override
public void onFailure(Throwable ex) {
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;
}
@Override
public void onSuccess(StompSession session) {
if (session != null) {
session.disconnect();
}
AbstractStompSessionManager.this.connected = false;
}
});
});
this.stompSessionListenableFuture = null;
}
}
@@ -294,7 +297,7 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
synchronized (this.lifecycleMonitor) {
if (!isRunning()) {
if (this.logger.isInfoEnabled()) {
this.logger.info("Starting " + getClass().getSimpleName());
this.logger.info("Starting " + this);
}
connect();
this.running = true;
@@ -318,7 +321,7 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
if (isRunning()) {
this.running = false;
if (this.logger.isInfoEnabled()) {
this.logger.info("Stopping " + getClass().getSimpleName());
this.logger.info("Stopping " + this);
}
destroy();
}
@@ -360,8 +363,7 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
private class CompositeStompSessionHandler extends StompSessionHandlerAdapter {
private final List<StompSessionHandler> delegates =
Collections.synchronizedList(new ArrayList<StompSessionHandler>());
private final List<StompSessionHandler> delegates = Collections.synchronizedList(new ArrayList<>());
private volatile StompSession session;
@@ -393,8 +395,9 @@ public abstract class AbstractStompSessionManager implements StompSessionManager
}
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
Throwable exception) {
public void handleException(StompSession session, @Nullable StompCommand command, StompHeaders headers,
byte[] payload, Throwable exception) {
synchronized (this.delegates) {
for (StompSessionHandler delegate : this.delegates) {
delegate.handleException(session, command, headers, payload, exception);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2018 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.
@@ -36,6 +36,7 @@ import org.springframework.integration.support.management.IntegrationManagedReso
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandlingException;
@@ -60,6 +61,7 @@ import org.springframework.util.Assert;
* if provided {@link StompSessionManager} supports {@code autoReceiptEnabled}.
*
* @author Artem Bilan
*
* @since 4.2
*/
@ManagedResource
@@ -68,25 +70,22 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
private final StompSessionHandler stompSessionHandler = new IntegrationInboundStompSessionHandler();
private final Set<String> destinations = new LinkedHashSet<String>();
private final Set<String> destinations = new LinkedHashSet<>();
private final StompSessionManager stompSessionManager;
private final Map<String, StompSession.Subscription> subscriptions =
new HashMap<String, StompSession.Subscription>();
private final Map<String, StompSession.Subscription> subscriptions = new HashMap<>();
private final Lock destinationLock = new ReentrantLock();
private ApplicationEventPublisher applicationEventPublisher;
private Class<?> payloadType = String.class;
private HeaderMapper<StompHeaders> headerMapper = new StompHeaderMapper();
private volatile StompSession stompSession;
private volatile Class<?> payloadType = String.class;
private volatile HeaderMapper<StompHeaders> headerMapper = new StompHeaderMapper();
private volatile MessageChannel errorChannel;
public StompInboundChannelAdapter(StompSessionManager stompSessionManager, String... destinations) {
Assert.notNull(stompSessionManager, "'stompSessionManager' is required.");
if (destinations != null) {
@@ -103,12 +102,6 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
this.payloadType = payloadType;
}
@Override
public void setErrorChannel(MessageChannel errorChannel) {
super.setErrorChannel(errorChannel);
this.errorChannel = errorChannel;
}
public void setHeaderMapper(HeaderMapper<StompHeaders> headerMapper) {
Assert.notNull(headerMapper, "'headerMapper' must not be null.");
this.headerMapper = headerMapper;
@@ -123,7 +116,7 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
public String[] getDestinations() {
this.destinationLock.lock();
try {
return this.destinations.toArray(new String[this.destinations.size()]);
return this.destinations.toArray(new String[0]);
}
finally {
this.destinationLock.unlock();
@@ -206,7 +199,7 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
}
}
catch (Exception e) {
logger.warn("The exception during unsubscription.", e);
logger.warn("The exception during unsubscribing.", e);
}
this.subscriptions.clear();
}
@@ -222,15 +215,24 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
}
@Override
public void handleFrame(StompHeaders headers, Object body) {
public void handleFrame(StompHeaders headers, @Nullable Object body) {
Message<?> message;
if (body instanceof Message) {
if (body == null) {
logger.info("No body in STOMP frame: nothing to produce.");
return;
}
else if (body instanceof Message) {
message = (Message<?>) body;
}
else {
message = getMessageBuilderFactory().withPayload(body)
.copyHeaders(StompInboundChannelAdapter.this.headerMapper.toHeaders(headers))
.build();
Map<String, Object> headersToCopy =
StompInboundChannelAdapter.this.headerMapper.toHeaders(headers);
message =
getMessageBuilderFactory()
.withPayload(body)
.copyHeaders(headersToCopy)
.build();
}
sendMessage(message);
}
@@ -260,7 +262,7 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
}
this.subscriptions.put(destination, subscription);
}
else {
else if (logger.isWarnEnabled()) {
logger.warn("The StompInboundChannelAdapter [" + getComponentName() +
"] ins't connected to StompSession. Check the state of [" + this.stompSessionManager + "]");
}
@@ -277,15 +279,27 @@ public class StompInboundChannelAdapter extends MessageProducerSupport implement
}
@Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload,
Throwable exception) {
if (StompInboundChannelAdapter.this.errorChannel != null) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(command);
headerAccessor.copyHeaders(StompInboundChannelAdapter.this.headerMapper.toHeaders(headers));
Message<byte[]> failedMessage = MessageBuilder.createMessage(payload,
headerAccessor.getMessageHeaders());
getMessagingTemplate().send(StompInboundChannelAdapter.this.errorChannel,
new ErrorMessage(new MessageHandlingException(failedMessage, exception)));
public void handleException(StompSession session, @Nullable StompCommand command, StompHeaders headers,
byte[] payload, Throwable exception) {
MessageChannel errorChannel = getErrorChannel();
if (errorChannel != null) {
Message<byte[]> failedMessage;
// TODO 5.2 Copy all the STOMP headers for error message without any mapping
Map<String, Object> headersToCopy = StompInboundChannelAdapter.this.headerMapper.toHeaders(headers);
if (command != null) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(command);
headerAccessor.copyHeaders(headersToCopy);
failedMessage = MessageBuilder.createMessage(payload, headerAccessor.getMessageHeaders());
}
else {
failedMessage =
MessageBuilder.withPayload(payload)
.copyHeaders(headersToCopy)
.build();
}
getMessagingTemplate()
.send(errorChannel, new ErrorMessage(new MessageHandlingException(failedMessage, exception)));
}
else {
logger.error("STOMP Frame handling error.", exception);

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2018 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.
@@ -32,6 +32,7 @@ import org.springframework.integration.stomp.StompSessionManager;
import org.springframework.integration.stomp.event.StompExceptionEvent;
import org.springframework.integration.stomp.event.StompReceiptEvent;
import org.springframework.integration.stomp.support.StompHeaderMapper;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageDeliveryException;
import org.springframework.messaging.MessagingException;
@@ -47,7 +48,9 @@ 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 {
@@ -60,13 +63,7 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
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();
private HeaderMapper<StompHeaders> headerMapper = new StompHeaderMapper();
private Expression destinationExpression;
@@ -74,7 +71,13 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
private ApplicationEventPublisher applicationEventPublisher;
private volatile long connectTimeout = DEFAULT_CONNECT_TIMEOUT;
private long connectTimeout = DEFAULT_CONNECT_TIMEOUT;
private volatile StompSession stompSession;
private volatile Throwable transportError;
private volatile boolean running;
public StompMessageHandler(StompSessionManager stompSessionManager) {
Assert.notNull(stompSessionManager, "'stompSessionManager' is required.");
@@ -126,19 +129,20 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
}
@Override
protected void handleMessageInternal(final Message<?> message) throws Exception {
protected void handleMessageInternal(final Message<?> message) {
try {
connectIfNecessary();
}
catch (Exception e) {
throw new MessageDeliveryException(message, "The [" + this + "] could not deliver message.", 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);
if (stompHeaders.getDestination() == null) {
Assert.state(this.destinationExpression != null, "One of 'destination' or 'destinationExpression' must be" +
Assert.state(this.destinationExpression != null, "One of 'destination' or 'destinationExpression' must " +
"be" +
" provided, if message header doesn't supply 'destination' STOMP header.");
String destination = this.destinationExpression.getValue(this.evaluationContext, message, String.class);
stompHeaders.setDestination(destination);
@@ -171,7 +175,7 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
}
}
private StompSession connectIfNecessary() throws Exception {
private void connectIfNecessary() throws InterruptedException {
synchronized (this.connectSemaphore) {
if (this.stompSession == null || !this.stompSessionManager.isConnected()) {
this.stompSessionManager.disconnect(this.sessionHandler);
@@ -192,7 +196,6 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
}
}
}
return this.stompSession;
}
}
@@ -235,22 +238,35 @@ public class StompMessageHandler extends AbstractMessageHandler implements Appli
Message<?> failedMessage = getMessageBuilderFactory().withPayload(thePayload)
.copyHeaders(StompMessageHandler.this.headerMapper.toHeaders(headers))
.build();
MessagingException exception = new MessageDeliveryException(failedMessage,
"STOMP frame handling error.");
logger.error("STOMP frame handling error.", exception);
MessagingException exception =
new MessageDeliveryException(failedMessage, "STOMP frame handling error.");
if (StompMessageHandler.this.applicationEventPublisher != null) {
StompMessageHandler.this.applicationEventPublisher.publishEvent(
new StompExceptionEvent(StompMessageHandler.this, exception));
}
else {
logger.error(exception);
}
}
}
@Override
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);
public void handleException(StompSession session, @Nullable StompCommand command,
StompHeaders headers, byte[] payload, Throwable exception) {
Message<byte[]> failedMessage;
if (command != null) {
StompHeaderAccessor stompHeaderAccessor = StompHeaderAccessor.create(command, headers);
failedMessage = MessageBuilder.createMessage(payload, stompHeaderAccessor.getMessageHeaders());
}
else {
failedMessage =
MessageBuilder.withPayload(payload)
.copyHeaders(headers)
.build();
}
logger.error("The exception for session [" + session + "] on message [" + failedMessage + "]", exception);
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015-2016 the original author or authors.
* Copyright 2015-2018 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.
@@ -40,7 +40,9 @@ import org.springframework.util.StringUtils;
* The STOMP {@link HeaderMapper} implementation.
*
* @author Artem Bilan
*
* @since 4.2
*
* @see StompHeaders
*/
public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
@@ -51,32 +53,32 @@ public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
public static final String STOMP_OUTBOUND_HEADER_NAME_PATTERN = "STOMP_OUTBOUND_HEADERS";
private static final String[] STOMP_INBOUND_HEADER_NAMES = new String[] {
StompHeaders.CONTENT_LENGTH,
StompHeaders.CONTENT_TYPE,
StompHeaders.MESSAGE_ID,
StompHeaders.RECEIPT_ID,
StompHeaders.SUBSCRIPTION,
};
private static final String[] STOMP_INBOUND_HEADER_NAMES =
new String[] {
StompHeaders.CONTENT_LENGTH,
StompHeaders.CONTENT_TYPE,
StompHeaders.MESSAGE_ID,
StompHeaders.RECEIPT_ID,
StompHeaders.SUBSCRIPTION,
};
private final static List<String> STOMP_INBOUND_HEADER_NAMES_LIST =
Arrays.<String>asList(STOMP_INBOUND_HEADER_NAMES);
private static final List<String> STOMP_INBOUND_HEADER_NAMES_LIST = Arrays.asList(STOMP_INBOUND_HEADER_NAMES);
private static final String[] STOMP_OUTBOUND_HEADER_NAMES = new String[] {
StompHeaders.CONTENT_LENGTH,
StompHeaders.CONTENT_TYPE,
StompHeaders.DESTINATION,
StompHeaders.RECEIPT,
IntegrationStompHeaders.DESTINATION,
IntegrationStompHeaders.RECEIPT
};
private static final String[] STOMP_OUTBOUND_HEADER_NAMES =
new String[] {
StompHeaders.CONTENT_LENGTH,
StompHeaders.CONTENT_TYPE,
StompHeaders.DESTINATION,
StompHeaders.RECEIPT,
IntegrationStompHeaders.DESTINATION,
IntegrationStompHeaders.RECEIPT
};
private final static List<String> STOMP_OUTBOUND_HEADER_NAMES_LIST =
Arrays.<String>asList(STOMP_OUTBOUND_HEADER_NAMES);
private static final List<String> STOMP_OUTBOUND_HEADER_NAMES_LIST = Arrays.asList(STOMP_OUTBOUND_HEADER_NAMES);
private volatile String[] inboundHeaderNames = STOMP_INBOUND_HEADER_NAMES;
private String[] inboundHeaderNames = STOMP_INBOUND_HEADER_NAMES;
private volatile String[] outboundHeaderNames = STOMP_OUTBOUND_HEADER_NAMES;
private String[] outboundHeaderNames = STOMP_OUTBOUND_HEADER_NAMES;
public void setInboundHeaderNames(String[] inboundHeaderNames) { //NOSONAR - false positive
Assert.notNull(inboundHeaderNames, "'inboundHeaderNames' must not be null.");
@@ -110,12 +112,14 @@ public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
else if (StompHeaderAccessor.NATIVE_HEADERS.equals(name)) {
MultiValueMap<String, String> multiValueMap =
headers.get(StompHeaderAccessor.NATIVE_HEADERS, MultiValueMap.class);
for (Map.Entry<String, List<String>> entry1 : multiValueMap.entrySet()) {
name = entry1.getKey();
if (shouldMapHeader(name, this.outboundHeaderNames)) {
String value = entry1.getValue().get(0);
if (StringUtils.hasText(value)) {
setStompHeader(target, name, value);
if (multiValueMap != null) {
for (Map.Entry<String, List<String>> entry1 : multiValueMap.entrySet()) {
name = entry1.getKey();
if (shouldMapHeader(name, this.outboundHeaderNames)) {
String value = entry1.getValue().get(0);
if (StringUtils.hasText(value)) {
setStompHeader(target, name, value);
}
}
}
}
@@ -149,7 +153,8 @@ public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
else {
Class<?> clazz = (value != null) ? value.getClass() : null;
throw new IllegalArgumentException(
"Expected MediaType or String value for 'content-type' header value, but received: " + clazz);
"Expected MediaType or String value for 'content-type' header value, but received: "
+ clazz);
}
}
}
@@ -187,7 +192,7 @@ public class StompHeaderMapper implements HeaderMapper<StompHeaders> {
@Override
public Map<String, Object> toHeaders(StompHeaders source) {
Map<String, Object> target = new HashMap<String, Object>();
Map<String, Object> target = new HashMap<>();
for (String name : source.keySet()) {
if (shouldMapHeader(name, this.inboundHeaderNames)) {
if (StompHeaders.CONTENT_TYPE.equals(name)) {