* Fix Sonar issues for Sec., STOMP, SFTP, WebFlux
This commit is contained in:
committed by
Gary Russell
parent
7790f9e550
commit
761af2730c
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -27,10 +27,12 @@ import org.springframework.aop.TargetSource;
|
||||
import org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator;
|
||||
import org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.integration.security.channel.ChannelAccessPolicy;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityInterceptor;
|
||||
import org.springframework.integration.security.channel.ChannelSecurityMetadataSource;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
@@ -53,6 +55,7 @@ public class ChannelSecurityInterceptorBeanPostProcessor extends AbstractAutoPro
|
||||
|
||||
public ChannelSecurityInterceptorBeanPostProcessor(Map<String, Set<Pattern>> securityInterceptorMappings,
|
||||
Map<String, Map<Pattern, ChannelAccessPolicy>> accessPolicyMapping) {
|
||||
|
||||
this.securityInterceptorMappings = securityInterceptorMappings; //NOSONAR (inconsistent sync)
|
||||
this.accessPolicyMapping = accessPolicyMapping; //NOSONAR (inconsistent sync)
|
||||
}
|
||||
@@ -73,19 +76,24 @@ public class ChannelSecurityInterceptorBeanPostProcessor extends AbstractAutoPro
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
|
||||
TargetSource customTargetSource) throws BeansException {
|
||||
@Nullable TargetSource customTargetSource) throws BeansException {
|
||||
|
||||
if (MessageChannel.class.isAssignableFrom(beanClass)) {
|
||||
List<Advisor> interceptors = new ArrayList<Advisor>();
|
||||
List<Advisor> interceptors = new ArrayList<>();
|
||||
for (Map.Entry<String, Set<Pattern>> entry : this.securityInterceptorMappings.entrySet()) {
|
||||
if (isMatch(beanName, entry.getValue())) {
|
||||
DefaultBeanFactoryPointcutAdvisor channelSecurityInterceptor
|
||||
= new DefaultBeanFactoryPointcutAdvisor();
|
||||
channelSecurityInterceptor.setAdviceBeanName(entry.getKey());
|
||||
channelSecurityInterceptor.setBeanFactory(getBeanFactory());
|
||||
interceptors.add(channelSecurityInterceptor);
|
||||
DefaultBeanFactoryPointcutAdvisor channelSecurityInterceptor =
|
||||
new DefaultBeanFactoryPointcutAdvisor();
|
||||
channelSecurityInterceptor.setAdviceBeanName(entry.getKey());
|
||||
BeanFactory beanFactory = getBeanFactory();
|
||||
if (beanFactory != null) {
|
||||
channelSecurityInterceptor.setBeanFactory(beanFactory);
|
||||
}
|
||||
interceptors.add(channelSecurityInterceptor);
|
||||
}
|
||||
}
|
||||
if (!interceptors.isEmpty()) {
|
||||
return interceptors.toArray();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2016 the original author or authors.
|
||||
* Copyright 2014-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.
|
||||
@@ -18,12 +18,14 @@ package org.springframework.integration.security.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.ManagedMap;
|
||||
@@ -39,6 +41,7 @@ import org.springframework.integration.security.channel.SecuredChannel;
|
||||
* The Integration Security infrastructure {@code beanFactory} initializer.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SecurityIntegrationConfigurationInitializer implements IntegrationConfigurationInitializer {
|
||||
@@ -47,71 +50,30 @@ public class SecurityIntegrationConfigurationInitializer implements IntegrationC
|
||||
ChannelSecurityInterceptorBeanPostProcessor.class.getName();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void initialize(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
|
||||
|
||||
Map<String, ManagedSet<String>> securityInterceptors = new ManagedMap<String, ManagedSet<String>>();
|
||||
Map<String, Map<Pattern, ChannelAccessPolicy>> policies = new HashMap<String, Map<Pattern, ChannelAccessPolicy>>();
|
||||
Map<String, Set<String>> securityInterceptors = new ManagedMap<>();
|
||||
Map<String, Map<Pattern, ChannelAccessPolicy>> policies = new HashMap<>();
|
||||
|
||||
for (String beanName : registry.getBeanDefinitionNames()) {
|
||||
BeanDefinition beanDefinition = registry.getBeanDefinition(beanName);
|
||||
if (ChannelSecurityInterceptor.class.getName().equals(beanDefinition.getBeanClassName())) {
|
||||
BeanDefinition metadataSource = (BeanDefinition) beanDefinition.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, BeanDefinition.class)
|
||||
.getValue();
|
||||
|
||||
Map<String, ?> value = (Map<String, ?>) metadataSource.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, Map.class)
|
||||
.getValue();
|
||||
ManagedSet<String> patterns = new ManagedSet<String>();
|
||||
if (!securityInterceptors.containsKey(beanName)) {
|
||||
securityInterceptors.put(beanName, patterns);
|
||||
}
|
||||
else {
|
||||
patterns = securityInterceptors.get(beanName);
|
||||
}
|
||||
patterns.addAll(value.keySet());
|
||||
collectPatternsFromInterceptor(securityInterceptors, beanName, beanDefinition);
|
||||
}
|
||||
else if (beanDefinition instanceof AnnotatedBeanDefinition) {
|
||||
if (beanDefinition.getSource() instanceof MethodMetadata) {
|
||||
MethodMetadata beanMethod = (MethodMetadata) beanDefinition.getSource();
|
||||
String annotationType = SecuredChannel.class.getName();
|
||||
if (beanMethod.isAnnotated(annotationType)) {
|
||||
Map<String, Object> securedAttributes = beanMethod.getAnnotationAttributes(annotationType);
|
||||
String[] interceptors = (String[]) securedAttributes.get("interceptor");
|
||||
String[] sendAccess = (String[]) securedAttributes.get("sendAccess");
|
||||
String[] receiveAccess = (String[]) securedAttributes.get("receiveAccess");
|
||||
ChannelAccessPolicy accessPolicy = new DefaultChannelAccessPolicy(sendAccess, receiveAccess);
|
||||
for (String interceptor : interceptors) {
|
||||
ManagedSet<String> patterns = new ManagedSet<String>();
|
||||
if (!securityInterceptors.containsKey(interceptor)) {
|
||||
securityInterceptors.put(interceptor, patterns);
|
||||
}
|
||||
else {
|
||||
patterns = securityInterceptors.get(interceptor);
|
||||
}
|
||||
patterns.add(beanName);
|
||||
|
||||
Map<Pattern, ChannelAccessPolicy> mapping = new HashMap<Pattern, ChannelAccessPolicy>();
|
||||
if (!policies.containsKey(interceptor)) {
|
||||
policies.put(interceptor, mapping);
|
||||
}
|
||||
else {
|
||||
mapping = policies.get(interceptor);
|
||||
}
|
||||
mapping.put(Pattern.compile(beanName), accessPolicy);
|
||||
}
|
||||
}
|
||||
Object beanSource = beanDefinition.getSource();
|
||||
if (beanSource instanceof MethodMetadata) {
|
||||
collectInterceptorsAndPoliciesBySecuredChannel(securityInterceptors, policies, beanName,
|
||||
(MethodMetadata) beanSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!securityInterceptors.isEmpty()) {
|
||||
|
||||
BeanDefinitionBuilder builder =
|
||||
BeanDefinitionBuilder.rootBeanDefinition(ChannelSecurityInterceptorBeanPostProcessor.class)
|
||||
.addConstructorArgValue(securityInterceptors);
|
||||
.addConstructorArgValue(securityInterceptors);
|
||||
if (!policies.isEmpty()) {
|
||||
builder.addConstructorArgValue(policies);
|
||||
}
|
||||
@@ -119,4 +81,67 @@ public class SecurityIntegrationConfigurationInitializer implements IntegrationC
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void collectPatternsFromInterceptor(Map<String, Set<String>> securityInterceptors, String beanName,
|
||||
BeanDefinition beanDefinition) {
|
||||
|
||||
ConstructorArgumentValues.ValueHolder metadataSourceValueHolder =
|
||||
beanDefinition
|
||||
.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, BeanDefinition.class);
|
||||
if (metadataSourceValueHolder != null) {
|
||||
BeanDefinition metadataSource = (BeanDefinition) metadataSourceValueHolder.getValue();
|
||||
if (metadataSource != null) {
|
||||
ConstructorArgumentValues.ValueHolder patternMappingsValueHolder =
|
||||
metadataSource
|
||||
.getConstructorArgumentValues()
|
||||
.getIndexedArgumentValue(0, Map.class);
|
||||
if (patternMappingsValueHolder != null) {
|
||||
Map<String, ?> patternsToAdd = (Map<String, ?>) patternMappingsValueHolder.getValue();
|
||||
Set<String> patterns = new ManagedSet<>();
|
||||
if (!securityInterceptors.containsKey(beanName)) {
|
||||
securityInterceptors.put(beanName, patterns);
|
||||
}
|
||||
else {
|
||||
patterns = securityInterceptors.get(beanName);
|
||||
}
|
||||
if (patternsToAdd != null) {
|
||||
patterns.addAll(patternsToAdd.keySet());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectInterceptorsAndPoliciesBySecuredChannel(Map<String, Set<String>> securityInterceptors,
|
||||
Map<String, Map<Pattern, ChannelAccessPolicy>> policies, String beanName, MethodMetadata beanMethod) {
|
||||
|
||||
Map<String, Object> securedAttributes = beanMethod.getAnnotationAttributes(SecuredChannel.class.getName());
|
||||
if (securedAttributes != null) {
|
||||
String[] interceptors = (String[]) securedAttributes.get("interceptor");
|
||||
String[] sendAccess = (String[]) securedAttributes.get("sendAccess");
|
||||
String[] receiveAccess = (String[]) securedAttributes.get("receiveAccess");
|
||||
ChannelAccessPolicy accessPolicy = new DefaultChannelAccessPolicy(sendAccess, receiveAccess);
|
||||
for (String interceptor : interceptors) {
|
||||
Set<String> patterns = new ManagedSet<>();
|
||||
if (!securityInterceptors.containsKey(interceptor)) {
|
||||
securityInterceptors.put(interceptor, patterns);
|
||||
}
|
||||
else {
|
||||
patterns = securityInterceptors.get(interceptor);
|
||||
}
|
||||
patterns.add(beanName);
|
||||
|
||||
Map<Pattern, ChannelAccessPolicy> mapping = new HashMap<>();
|
||||
if (!policies.containsKey(interceptor)) {
|
||||
policies.put(interceptor, mapping);
|
||||
}
|
||||
else {
|
||||
mapping = policies.get(interceptor);
|
||||
}
|
||||
mapping.put(Pattern.compile(beanName), accessPolicy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-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.
|
||||
@@ -17,9 +17,9 @@
|
||||
package org.springframework.integration.sftp.gateway;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
@@ -42,6 +42,7 @@ import com.jcraft.jsch.SftpException;
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEntry> {
|
||||
@@ -49,8 +50,10 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
private static final Method LS_ENTRY_SET_FILENAME_METHOD;
|
||||
|
||||
static {
|
||||
LS_ENTRY_SET_FILENAME_METHOD = ReflectionUtils.findMethod(LsEntry.class, "setFilename", String.class);
|
||||
LS_ENTRY_SET_FILENAME_METHOD.setAccessible(true);
|
||||
LS_ENTRY_SET_FILENAME_METHOD = ReflectionUtils.findMethod(LsEntry.class, "setFilename", String.class);
|
||||
if (LS_ENTRY_SET_FILENAME_METHOD != null) {
|
||||
ReflectionUtils.makeAccessible(LS_ENTRY_SET_FILENAME_METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,6 +64,7 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory<LsEntry> sessionFactory,
|
||||
MessageSessionCallback<LsEntry, ?> messageSessionCallback) {
|
||||
|
||||
this(new SftpRemoteFileTemplate(sessionFactory), messageSessionCallback);
|
||||
}
|
||||
|
||||
@@ -72,6 +76,7 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
*/
|
||||
public SftpOutboundGateway(RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
MessageSessionCallback<LsEntry, ?> messageSessionCallback) {
|
||||
|
||||
super(remoteFileTemplate, messageSessionCallback);
|
||||
}
|
||||
|
||||
@@ -119,11 +124,9 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
|
||||
List<AbstractFileInfo<LsEntry>> canonicalFiles = new ArrayList<AbstractFileInfo<LsEntry>>();
|
||||
for (LsEntry file : files) {
|
||||
canonicalFiles.add(new SftpFileInfo(file));
|
||||
}
|
||||
return canonicalFiles;
|
||||
return files.stream()
|
||||
.map(SftpFileInfo::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -149,14 +152,15 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileOperations<LsEntry> remoteFileOperations, final String path, final int chmod) {
|
||||
remoteFileOperations.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException("Failed to execute chmod", e);
|
||||
}
|
||||
});
|
||||
remoteFileOperations
|
||||
.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException("Failed to execute chmod", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.reactivestreams.Publisher;
|
||||
import org.springframework.core.ReactiveAdapter;
|
||||
import org.springframework.core.ReactiveAdapterRegistry;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@@ -147,6 +148,7 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
private Mono<Void> doHandle(ServerWebExchange exchange) {
|
||||
return extractRequestBody(exchange)
|
||||
.doOnSubscribe(s -> this.activeCount.incrementAndGet())
|
||||
.cast(Object.class)
|
||||
.switchIfEmpty(Mono.just(exchange.getRequest().getQueryParams()))
|
||||
.map(body ->
|
||||
new RequestEntity<>(body, exchange.getRequest().getHeaders(),
|
||||
@@ -166,71 +168,77 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings(UNCHECKED)
|
||||
private <T> Mono<T> extractRequestBody(ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
|
||||
if (isReadable(request)) {
|
||||
MediaType contentType;
|
||||
if (request.getHeaders().getContentType() == null) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
else {
|
||||
contentType = request.getHeaders().getContentType();
|
||||
}
|
||||
|
||||
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
|
||||
return (Mono<T>) exchange.getFormData();
|
||||
}
|
||||
else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
|
||||
return (Mono<T>) exchange.getMultipartData();
|
||||
}
|
||||
else {
|
||||
ResolvableType bodyType = getRequestPayloadType();
|
||||
if (bodyType == null) {
|
||||
bodyType =
|
||||
"text".equals(contentType.getType())
|
||||
? ResolvableType.forClass(String.class)
|
||||
: ResolvableType.forClass(byte[].class);
|
||||
}
|
||||
|
||||
Class<?> resolvedType = bodyType.resolve();
|
||||
|
||||
ReactiveAdapter adapter = (resolvedType != null ? this.adapterRegistry.getAdapter(resolvedType) :
|
||||
null);
|
||||
ResolvableType elementType = (adapter != null ? bodyType.getGeneric() : bodyType);
|
||||
|
||||
HttpMessageReader<?> httpMessageReader = this.codecConfigurer
|
||||
.getReaders()
|
||||
.stream()
|
||||
.filter(reader -> reader.canRead(elementType, contentType))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new UnsupportedMediaTypeStatusException(
|
||||
"Could not convert request: no suitable HttpMessageReader found for expected type ["
|
||||
+ elementType + "] and content type [" + contentType + "]"));
|
||||
|
||||
|
||||
Map<String, Object> readHints = Collections.emptyMap();
|
||||
if (adapter != null && adapter.isMultiValue()) {
|
||||
Flux<?> flux = httpMessageReader.read(bodyType, elementType, request, response, readHints);
|
||||
|
||||
return (Mono<T>) Mono.just(adapter.fromPublisher(flux));
|
||||
}
|
||||
else {
|
||||
Mono<?> mono = httpMessageReader.readMono(bodyType, elementType, request, response, readHints);
|
||||
|
||||
if (adapter != null) {
|
||||
return (Mono<T>) Mono.just(adapter.fromPublisher(mono));
|
||||
}
|
||||
else {
|
||||
return (Mono<T>) mono;
|
||||
}
|
||||
}
|
||||
}
|
||||
private Mono<?> extractRequestBody(ServerWebExchange exchange) {
|
||||
if (isReadable(exchange.getRequest())) {
|
||||
return extractReadableRequestBody(exchange);
|
||||
}
|
||||
else {
|
||||
return (Mono<T>) Mono.just(exchange.getRequest().getQueryParams());
|
||||
return Mono.just(exchange.getRequest().getQueryParams());
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<?> extractReadableRequestBody(ServerWebExchange exchange) {
|
||||
MediaType contentType =
|
||||
exchange.getRequest()
|
||||
.getHeaders()
|
||||
.getContentType();
|
||||
if (contentType == null) {
|
||||
contentType = MediaType.APPLICATION_OCTET_STREAM;
|
||||
}
|
||||
|
||||
if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(contentType)) {
|
||||
return exchange.getFormData();
|
||||
}
|
||||
else if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(contentType)) {
|
||||
return exchange.getMultipartData();
|
||||
}
|
||||
else {
|
||||
return readRequestBody(exchange, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
private Mono<?> readRequestBody(ServerWebExchange exchange, MediaType contentType) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
ServerHttpResponse response = exchange.getResponse();
|
||||
ResolvableType bodyType = getRequestPayloadType();
|
||||
if (bodyType == null) {
|
||||
bodyType =
|
||||
"text".equals(contentType.getType())
|
||||
? ResolvableType.forClass(String.class)
|
||||
: ResolvableType.forClass(byte[].class);
|
||||
}
|
||||
|
||||
Class<?> resolvedType = bodyType.resolve();
|
||||
|
||||
ReactiveAdapter adapter =
|
||||
resolvedType != null
|
||||
? this.adapterRegistry.getAdapter(resolvedType)
|
||||
: null;
|
||||
ResolvableType elementType = (adapter != null ? bodyType.getGeneric() : bodyType);
|
||||
|
||||
HttpMessageReader<?> httpMessageReader = this.codecConfigurer
|
||||
.getReaders()
|
||||
.stream()
|
||||
.filter(reader -> reader.canRead(elementType, contentType))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new UnsupportedMediaTypeStatusException(
|
||||
"Could not convert request: no suitable HttpMessageReader found for expected type ["
|
||||
+ elementType + "] and content type [" + contentType + "]"));
|
||||
|
||||
|
||||
Map<String, Object> readHints = Collections.emptyMap();
|
||||
if (adapter != null && adapter.isMultiValue()) {
|
||||
Flux<?> flux = httpMessageReader.read(bodyType, elementType, request, response, readHints);
|
||||
return Mono.just(adapter.fromPublisher(flux));
|
||||
}
|
||||
else {
|
||||
Mono<?> mono = httpMessageReader.readMono(bodyType, elementType, request, response, readHints);
|
||||
if (adapter != null) {
|
||||
return Mono.just(adapter.fromPublisher(mono));
|
||||
}
|
||||
else {
|
||||
return mono;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +249,7 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
MultiValueMap<String, String> requestParams = request.getQueryParams();
|
||||
|
||||
StandardEvaluationContext evaluationContext = buildEvaluationContext(httpEntity, exchange);
|
||||
EvaluationContext evaluationContext = buildEvaluationContext(httpEntity, exchange);
|
||||
Object payload;
|
||||
if (getPayloadExpression() != null) {
|
||||
payload = getPayloadExpression().getValue(evaluationContext);
|
||||
@@ -299,7 +307,7 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
}
|
||||
|
||||
@SuppressWarnings(UNCHECKED)
|
||||
private StandardEvaluationContext buildEvaluationContext(RequestEntity<?> httpEntity, ServerWebExchange exchange) {
|
||||
private EvaluationContext buildEvaluationContext(RequestEntity<?> httpEntity, ServerWebExchange exchange) {
|
||||
ServerHttpRequest request = exchange.getRequest();
|
||||
HttpHeaders requestHeaders = request.getHeaders();
|
||||
MultiValueMap<String, String> requestParams = request.getQueryParams();
|
||||
@@ -482,7 +490,6 @@ public class WebFluxInboundEndpoint extends BaseHttpInboundEndpoint implements W
|
||||
return (mediaTypes.isEmpty() ? Collections.singletonList(MediaType.ALL) : mediaTypes);
|
||||
}
|
||||
|
||||
@SuppressWarnings(UNCHECKED)
|
||||
private List<MediaType> getProducibleTypes(ServerWebExchange exchange,
|
||||
Supplier<List<MediaType>> producibleTypesSupplier) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user