Compare commits

..

1 Commits

Author SHA1 Message Date
Spring Buildmaster 5acffaa72d Release v5.3.0 2020-10-27 14:44:42 +00:00
88 changed files with 787 additions and 1573 deletions
+5 -5
View File
@@ -30,9 +30,9 @@ configure(allprojects) { project ->
mavenBom "io.projectreactor:reactor-bom:2020.0.0"
mavenBom "io.r2dbc:r2dbc-bom:Arabba-SR8"
mavenBom "io.rsocket:rsocket-bom:1.1.0"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.34.v20201102"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.33.v20201020"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.4.10"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.1"
mavenBom "org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.4.0"
mavenBom "org.junit:junit-bom:5.7.0"
}
dependencies {
@@ -123,7 +123,7 @@ configure(allprojects) { project ->
dependency "net.sf.ehcache:ehcache:2.10.6"
dependency "org.ehcache:jcache:1.0.1"
dependency "org.ehcache:ehcache:3.4.0"
dependency "org.hibernate:hibernate-core:5.4.23.Final"
dependency "org.hibernate:hibernate-core:5.4.22.Final"
dependency "org.hibernate:hibernate-validator:6.1.6.Final"
dependency "org.webjars:webjars-locator-core:0.46"
dependency "org.webjars:underscorejs:1.8.3"
@@ -190,14 +190,14 @@ configure(allprojects) { project ->
dependency "org.testng:testng:7.3.0"
dependency "org.hamcrest:hamcrest:2.1"
dependency "org.awaitility:awaitility:3.1.6"
dependency "org.assertj:assertj-core:3.18.0"
dependency "org.assertj:assertj-core:3.17.2"
dependencySet(group: 'org.xmlunit', version: '2.6.2') {
entry 'xmlunit-assertj'
entry('xmlunit-matchers') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
}
dependencySet(group: 'org.mockito', version: '3.6.0') {
dependencySet(group: 'org.mockito', version: '3.5.15') {
entry('mockito-core') {
exclude group: "org.hamcrest", name: "hamcrest-core"
}
-6
View File
@@ -1,9 +1,3 @@
logging:
level:
io.spring.concourse: DEBUG
distribute:
optional-deployments:
- '.*\\.zip'
spring:
main:
banner-mode: off
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.3.1
version=5.3.0
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
@@ -221,12 +221,10 @@ public class MethodInvocationProceedingJoinPoint implements ProceedingJoinPoint,
@Override
@Nullable
public String[] getParameterNames() {
String[] parameterNames = this.parameterNames;
if (parameterNames == null) {
parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
this.parameterNames = parameterNames;
if (this.parameterNames == null) {
this.parameterNames = parameterNameDiscoverer.getParameterNames(getMethod());
}
return parameterNames;
return this.parameterNames;
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -160,12 +160,23 @@ public class AspectJProxyFactory extends ProxyCreatorSupport {
}
/**
* Get the singleton aspect instance for the supplied aspect type.
* An instance is created if one cannot be found in the instance cache.
* Get the singleton aspect instance for the supplied aspect type. An instance
* is created if one cannot be found in the instance cache.
*/
private Object getSingletonAspectInstance(Class<?> aspectClass) {
return aspectCache.computeIfAbsent(aspectClass,
clazz -> new SimpleAspectInstanceFactory(clazz).getAspectInstance());
// Quick check without a lock...
Object instance = aspectCache.get(aspectClass);
if (instance == null) {
synchronized (aspectCache) {
// To be safe, check within full lock now...
instance = aspectCache.get(aspectClass);
if (instance == null) {
instance = new SimpleAspectInstanceFactory(aspectClass).getAspectInstance();
aspectCache.put(aspectClass, instance);
}
}
}
return instance;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -114,15 +114,6 @@ public interface Advised extends TargetClassAware {
*/
Advisor[] getAdvisors();
/**
* Return the number of advisors applying to this proxy.
* <p>The default implementation delegates to {@code getAdvisors().length}.
* @since 5.3.1
*/
default int getAdvisorCount() {
return getAdvisors().length;
}
/**
* Add an advisor at the end of the advisor chain.
* <p>The Advisor may be an {@link org.springframework.aop.IntroductionAdvisor},
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -95,6 +95,12 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
*/
private List<Advisor> advisors = new ArrayList<>();
/**
* Array updated on changes to the advisors list, which is easier
* to manipulate internally.
*/
private Advisor[] advisorArray = new Advisor[0];
/**
* No-arg constructor for use as a JavaBean.
@@ -238,12 +244,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
@Override
public final Advisor[] getAdvisors() {
return this.advisors.toArray(new Advisor[0]);
}
@Override
public int getAdvisorCount() {
return this.advisors.size();
return this.advisorArray;
}
@Override
@@ -291,6 +292,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
}
}
updateAdvisorArray();
adviceChanged();
}
@@ -337,6 +339,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
Assert.notNull(advisor, "Advisor must not be null");
this.advisors.add(advisor);
}
updateAdvisorArray();
adviceChanged();
}
}
@@ -360,18 +363,27 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
"Illegal position " + pos + " in advisor list with size " + this.advisors.size());
}
this.advisors.add(pos, advisor);
updateAdvisorArray();
adviceChanged();
}
/**
* Bring the array up to date with the list.
*/
protected final void updateAdvisorArray() {
this.advisorArray = this.advisors.toArray(new Advisor[0]);
}
/**
* Allows uncontrolled access to the {@link List} of {@link Advisor Advisors}.
* <p>Use with care, and remember to {@link #adviceChanged() fire advice changed events}
* when making any modifications.
* <p>Use with care, and remember to {@link #updateAdvisorArray() refresh the advisor array}
* and {@link #adviceChanged() fire advice changed events} when making any modifications.
*/
protected final List<Advisor> getAdvisorsInternal() {
return this.advisors;
}
@Override
public void addAdvice(Advice advice) throws AopConfigException {
int pos = this.advisors.size();
@@ -509,6 +521,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
Assert.notNull(advisor, "Advisor must not be null");
this.advisors.add(advisor);
}
updateAdvisorArray();
adviceChanged();
}
@@ -523,6 +536,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
copy.advisorChainFactory = this.advisorChainFactory;
copy.interfaces = this.interfaces;
copy.advisors = this.advisors;
copy.updateAdvisorArray();
return copy;
}
@@ -539,6 +553,7 @@ public class AdvisedSupport extends ProxyConfig implements Advised {
this.methodCache = new ConcurrentHashMap<>(32);
}
@Override
public String toProxyConfigString() {
return toString();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -206,7 +206,7 @@ public abstract class AopProxyUtils {
* Check equality of the advisors behind the given AdvisedSupport objects.
*/
public static boolean equalsAdvisors(AdvisedSupport a, AdvisedSupport b) {
return a.getAdvisorCount() == b.getAdvisorCount() && Arrays.equals(a.getAdvisors(), b.getAdvisors());
return Arrays.equals(a.getAdvisors(), b.getAdvisors());
}
@@ -125,7 +125,7 @@ class CglibAopProxy implements AopProxy, Serializable {
*/
public CglibAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
@@ -942,11 +942,11 @@ class CglibAopProxy implements AopProxy, Serializable {
}
// Advice instance identity is unimportant to the proxy class:
// All that matters is type and ordering.
if (this.advised.getAdvisorCount() != otherAdvised.getAdvisorCount()) {
return false;
}
Advisor[] thisAdvisors = this.advised.getAdvisors();
Advisor[] thatAdvisors = otherAdvised.getAdvisors();
if (thisAdvisors.length != thatAdvisors.length) {
return false;
}
for (int i = 0; i < thisAdvisors.length; i++) {
Advisor thisAdvisor = thisAdvisors[i];
Advisor thatAdvisor = thatAdvisors[i];
@@ -104,7 +104,7 @@ final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializa
*/
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
if (config.getAdvisors().length == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -422,12 +422,9 @@ public abstract class AbstractNestablePropertyAccessor extends AbstractPropertyA
}
return;
}
if (this.suppressNotWritablePropertyException) {
// Optimization for common ignoreUnknown=true scenario since the
// exception would be caught and swallowed higher up anyway...
return;
else {
throw createNotWritablePropertyException(tokens.canonicalName);
}
throw createNotWritablePropertyException(tokens.canonicalName);
}
Object oldValue = null;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -40,8 +40,6 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
private boolean autoGrowNestedPaths = false;
boolean suppressNotWritablePropertyException = false;
@Override
public void setExtractOldValueForEditor(boolean extractOldValueForEditor) {
@@ -91,41 +89,30 @@ public abstract class AbstractPropertyAccessor extends TypeConverterSupport impl
List<PropertyAccessException> propertyAccessExceptions = null;
List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues ?
((MutablePropertyValues) pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = true;
}
try {
for (PropertyValue pv : propertyValues) {
// setPropertyValue may throw any BeansException, which won't be caught
for (PropertyValue pv : propertyValues) {
try {
// This method may throw any BeansException, which won't be caught
// here, if there is a critical failure such as no matching field.
// We can attempt to deal only with less serious exceptions.
try {
setPropertyValue(pv);
}
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
}
propertyAccessExceptions.add(ex);
}
setPropertyValue(pv);
}
}
finally {
if (ignoreUnknown) {
this.suppressNotWritablePropertyException = false;
catch (NotWritablePropertyException ex) {
if (!ignoreUnknown) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (NullValueInNestedPathException ex) {
if (!ignoreInvalid) {
throw ex;
}
// Otherwise, just ignore it and continue...
}
catch (PropertyAccessException ex) {
if (propertyAccessExceptions == null) {
propertyAccessExceptions = new ArrayList<>();
}
propertyAccessExceptions.add(ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -92,7 +92,8 @@ public class DirectFieldAccessor extends AbstractNestablePropertyAccessor {
@Override
protected NotWritablePropertyException createNotWritablePropertyException(String propertyName) {
PropertyMatches matches = PropertyMatches.forField(propertyName, getRootClass());
throw new NotWritablePropertyException(getRootClass(), getNestedPath() + propertyName,
throw new NotWritablePropertyException(
getRootClass(), getNestedPath() + propertyName,
matches.buildErrorMessage(), matches.getPossibleMatches());
}
@@ -644,20 +644,21 @@ public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationA
}
synchronized (this) {
if (!this.cached) {
Object cachedFieldValue = null;
if (value != null || this.required) {
cachedFieldValue = desc;
this.cachedFieldValue = desc;
registerDependentBeans(beanName, autowiredBeanNames);
if (autowiredBeanNames.size() == 1) {
String autowiredBeanName = autowiredBeanNames.iterator().next();
if (beanFactory.containsBean(autowiredBeanName) &&
beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
cachedFieldValue = new ShortcutDependencyDescriptor(
this.cachedFieldValue = new ShortcutDependencyDescriptor(
desc, autowiredBeanName, field.getType());
}
}
}
this.cachedFieldValue = cachedFieldValue;
else {
this.cachedFieldValue = null;
}
this.cached = true;
}
}
@@ -26,6 +26,9 @@ import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -66,6 +69,8 @@ public class InjectionMetadata {
};
private static final Log logger = LogFactory.getLog(InjectionMetadata.class);
private final Class<?> targetClass;
private final Collection<InjectedElement> injectedElements;
@@ -105,6 +110,9 @@ public class InjectionMetadata {
if (!beanDefinition.isExternallyManagedConfigMember(member)) {
beanDefinition.registerExternallyManagedConfigMember(member);
checkedElements.add(element);
if (logger.isTraceEnabled()) {
logger.trace("Registered injected element on class [" + this.targetClass.getName() + "]: " + element);
}
}
}
this.checkedElements = checkedElements;
@@ -116,6 +124,9 @@ public class InjectionMetadata {
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
if (logger.isTraceEnabled()) {
logger.trace("Processing injected element of bean '" + beanName + "': " + element);
}
element.inject(target, beanName, pvs);
}
}
@@ -146,8 +157,7 @@ public class InjectionMetadata {
* @since 5.2
*/
public static InjectionMetadata forElements(Collection<InjectedElement> elements, Class<?> clazz) {
return (elements.isEmpty() ? new InjectionMetadata(clazz, Collections.emptyList()) :
new InjectionMetadata(clazz, elements));
return (elements.isEmpty() ? InjectionMetadata.EMPTY : new InjectionMetadata(clazz, elements));
}
/**
@@ -1288,8 +1288,10 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
* @param registry the PropertyEditorRegistry to initialize
*/
protected void registerCustomEditors(PropertyEditorRegistry registry) {
if (registry instanceof PropertyEditorRegistrySupport) {
((PropertyEditorRegistrySupport) registry).useConfigValueEditors();
PropertyEditorRegistrySupport registrySupport =
(registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null);
if (registrySupport != null) {
registrySupport.useConfigValueEditors();
}
if (!this.propertyEditorRegistrars.isEmpty()) {
for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
@@ -155,7 +155,8 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing
* @param name the name of the cache to be added
*/
private void updateCacheNames(String name) {
Set<String> cacheNames = new LinkedHashSet<>(this.cacheNames);
Set<String> cacheNames = new LinkedHashSet<>(this.cacheNames.size() + 1);
cacheNames.addAll(this.cacheNames);
cacheNames.add(name);
this.cacheNames = Collections.unmodifiableSet(cacheNames);
}
@@ -99,7 +99,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
* @see #setBeanNameGenerator
*/
public static final AnnotationBeanNameGenerator IMPORT_BEAN_NAME_GENERATOR =
FullyQualifiedAnnotationBeanNameGenerator.INSTANCE;
new FullyQualifiedAnnotationBeanNameGenerator();
private static final String IMPORT_REGISTRY_BEAN_NAME =
ConfigurationClassPostProcessor.class.getName() + ".importRegistry";
@@ -43,15 +43,6 @@ import org.springframework.util.Assert;
*/
public class FullyQualifiedAnnotationBeanNameGenerator extends AnnotationBeanNameGenerator {
/**
* A convenient constant for a default {@code FullyQualifiedAnnotationBeanNameGenerator}
* instance, as used for configuration-level import purposes.
* @since 5.2.11
*/
public static final FullyQualifiedAnnotationBeanNameGenerator INSTANCE =
new FullyQualifiedAnnotationBeanNameGenerator();
@Override
protected String buildDefaultBeanName(BeanDefinition definition) {
String beanClassName = definition.getBeanClassName();
@@ -307,23 +307,6 @@ public class ConfigurationClassProcessingTests {
assertThat(tb.getLawyer()).isEqualTo(ctx.getBean(NestedTestBean.class));
}
@Test // gh-26019
public void autowiringWithDynamicPrototypeBeanClass() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
ConfigWithDynamicPrototype.class, PrototypeDependency.class);
PrototypeInterface p1 = ctx.getBean(PrototypeInterface.class, 1);
assertThat(p1).isInstanceOf(PrototypeOne.class);
assertThat(((PrototypeOne) p1).prototypeDependency).isNotNull();
PrototypeInterface p2 = ctx.getBean(PrototypeInterface.class, 2);
assertThat(p2).isInstanceOf(PrototypeTwo.class);
PrototypeInterface p3 = ctx.getBean(PrototypeInterface.class, 1);
assertThat(p3).isInstanceOf(PrototypeOne.class);
assertThat(((PrototypeOne) p3).prototypeDependency).isNotNull();
}
/**
* Creates a new {@link BeanFactory}, populates it with a {@link BeanDefinition}
@@ -649,42 +632,4 @@ public class ConfigurationClassProcessingTests {
}
}
static class PrototypeDependency {
}
interface PrototypeInterface {
}
static class PrototypeOne extends AbstractPrototype {
@Autowired
PrototypeDependency prototypeDependency;
}
static class PrototypeTwo extends AbstractPrototype {
// no autowired dependency here, in contrast to above
}
static class AbstractPrototype implements PrototypeInterface {
}
@Configuration
static class ConfigWithDynamicPrototype {
@Bean
@Scope(value = "prototype")
public PrototypeInterface getDemoBean( int i) {
switch ( i) {
case 1: return new PrototypeOne();
case 2:
default:
return new PrototypeTwo();
}
}
}
}
@@ -264,12 +264,14 @@ public class ReactiveAdapterRegistry {
registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(io.reactivex.Observable.class, io.reactivex.Observable::empty),
source -> ((io.reactivex.Observable<?>) source).toFlowable(io.reactivex.BackpressureStrategy.BUFFER),
io.reactivex.Observable::fromPublisher
source -> io.reactivex.Flowable.fromPublisher(source)
.toObservable()
);
registry.registerReactiveType(
ReactiveTypeDescriptor.singleRequiredValue(io.reactivex.Single.class),
source -> ((io.reactivex.Single<?>) source).toFlowable(),
io.reactivex.Single::fromPublisher
source -> io.reactivex.Flowable.fromPublisher(source)
.toObservable().singleElement().toSingle()
);
registry.registerReactiveType(
ReactiveTypeDescriptor.singleOptionalValue(io.reactivex.Maybe.class, io.reactivex.Maybe::empty),
@@ -280,7 +282,8 @@ public class ReactiveAdapterRegistry {
registry.registerReactiveType(
ReactiveTypeDescriptor.noValue(io.reactivex.Completable.class, io.reactivex.Completable::complete),
source -> ((io.reactivex.Completable) source).toFlowable(),
io.reactivex.Completable::fromPublisher
source -> io.reactivex.Flowable.fromPublisher(source)
.toObservable().ignoreElements()
);
}
}
@@ -301,26 +304,30 @@ public class ReactiveAdapterRegistry {
io.reactivex.rxjava3.core.Observable::empty),
source -> ((io.reactivex.rxjava3.core.Observable<?>) source).toFlowable(
io.reactivex.rxjava3.core.BackpressureStrategy.BUFFER),
io.reactivex.rxjava3.core.Observable::fromPublisher
source -> io.reactivex.rxjava3.core.Flowable.fromPublisher(source)
.toObservable()
);
registry.registerReactiveType(
ReactiveTypeDescriptor.singleRequiredValue(io.reactivex.rxjava3.core.Single.class),
source -> ((io.reactivex.rxjava3.core.Single<?>) source).toFlowable(),
io.reactivex.rxjava3.core.Single::fromPublisher
source -> io.reactivex.rxjava3.core.Flowable.fromPublisher(source)
.toObservable().singleElement().toSingle()
);
registry.registerReactiveType(
ReactiveTypeDescriptor.singleOptionalValue(
io.reactivex.rxjava3.core.Maybe.class,
io.reactivex.rxjava3.core.Maybe::empty),
source -> ((io.reactivex.rxjava3.core.Maybe<?>) source).toFlowable(),
io.reactivex.rxjava3.core.Maybe::fromPublisher
source -> io.reactivex.rxjava3.core.Flowable.fromPublisher(source)
.toObservable().singleElement()
);
registry.registerReactiveType(
ReactiveTypeDescriptor.noValue(
io.reactivex.rxjava3.core.Completable.class,
io.reactivex.rxjava3.core.Completable::complete),
source -> ((io.reactivex.rxjava3.core.Completable) source).toFlowable(),
io.reactivex.rxjava3.core.Completable::fromPublisher
source -> io.reactivex.rxjava3.core.Flowable.fromPublisher(source)
.toObservable().ignoreElements()
);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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,8 +18,8 @@ package org.springframework.core.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.function.IntFunction;
@@ -31,11 +31,10 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* {@link Collector} implementations that provide various reduction operations for
* Collector implementations that provide various reduction operations for
* {@link MergedAnnotation} instances.
*
* @author Phillip Webb
* @author Sam Brannen
* @since 5.2
*/
public abstract class MergedAnnotationCollectors {
@@ -53,16 +52,13 @@ public abstract class MergedAnnotationCollectors {
* Create a new {@link Collector} that accumulates merged annotations to a
* {@link LinkedHashSet} containing {@linkplain MergedAnnotation#synthesize()
* synthesized} versions.
* <p>The collector returned by this method is effectively equivalent to
* {@code Collectors.mapping(MergedAnnotation::synthesize, Collectors.toCollection(LinkedHashSet::new))}
* but avoids the creation of a composite collector.
* @param <A> the annotation type
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link Set}
*/
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, Set<A>> toAnnotationSet() {
return Collector.of(LinkedHashSet::new, (set, annotation) -> set.add(annotation.synthesize()),
MergedAnnotationCollectors::combiner);
return Collector.of(ArrayList<A>::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::addAll, LinkedHashSet::new);
}
/**
@@ -94,14 +90,14 @@ public abstract class MergedAnnotationCollectors {
IntFunction<R[]> generator) {
return Collector.of(ArrayList::new, (list, annotation) -> list.add(annotation.synthesize()),
MergedAnnotationCollectors::combiner, list -> list.toArray(generator.apply(list.size())));
MergedAnnotationCollectors::addAll, list -> list.toArray(generator.apply(list.size())));
}
/**
* Create a new {@link Collector} that accumulates merged annotations to a
* Create a new {@link Collector} that accumulates merged annotations to an
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
* @param <A> the annotation type
* @param adaptations the adaptations that should be applied to the annotation values
* @return a {@link Collector} which collects and synthesizes the
@@ -115,13 +111,13 @@ public abstract class MergedAnnotationCollectors {
}
/**
* Create a new {@link Collector} that accumulates merged annotations to a
* Create a new {@link Collector} that accumulates merged annotations to an
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
* added} from each merged annotation
* {@linkplain MergedAnnotation#asMap(Adapt...) as a map}.
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
* @param <A> the annotation type
* @param finisher the finisher function for the new {@link MultiValueMap}
* @param adaptations the adaptations that should be applied to the annotation values
* @param finisher the finisher function for the new {@link MultiValueMap}
* @return a {@link Collector} which collects and synthesizes the
* annotations into a {@link LinkedMultiValueMap}
* @see #toMultiValueMap(MergedAnnotation.Adapt...)
@@ -134,7 +130,7 @@ public abstract class MergedAnnotationCollectors {
IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS);
return Collector.of(LinkedMultiValueMap::new,
(map, annotation) -> annotation.asMap(adaptations).forEach(map::add),
MergedAnnotationCollectors::combiner, finisher, characteristics);
MergedAnnotationCollectors::merge, finisher, characteristics);
}
@@ -142,22 +138,13 @@ public abstract class MergedAnnotationCollectors {
return instance == candidate;
}
/**
* {@link Collector#combiner() Combiner} for collections.
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
*/
private static <E, C extends Collection<E>> C combiner(C collection, C additions) {
collection.addAll(additions);
return collection;
private static <E, L extends List<E>> L addAll(L list, L additions) {
list.addAll(additions);
return list;
}
/**
* {@link Collector#combiner() Combiner} for multi-value maps.
* <p>This method is only invoked if the {@link java.util.stream.Stream} is
* processed in {@linkplain java.util.stream.Stream#parallel() parallel}.
*/
private static <K, V> MultiValueMap<K, V> combiner(MultiValueMap<K, V> map, MultiValueMap<K, V> additions) {
private static <K, V> MultiValueMap<K, V> merge(MultiValueMap<K, V> map,
MultiValueMap<K, V> additions) {
map.addAll(additions);
return map;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -118,7 +118,7 @@ public final class Property {
}
// Package private
// package private
MethodParameter getMethodParameter() {
return this.methodParameter;
@@ -132,7 +132,7 @@ public final class Property {
}
// Internal helpers
// internal helpers
private String resolveName() {
if (this.readMethod != null) {
@@ -142,13 +142,10 @@ public final class Property {
}
else {
index = this.readMethod.getName().indexOf("is");
if (index != -1) {
index += 2;
}
else {
// Record-style plain accessor method, e.g. name()
index = 0;
if (index == -1) {
throw new IllegalArgumentException("Not a getter method");
}
index += 2;
}
return StringUtils.uncapitalize(this.readMethod.getName().substring(index));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -63,36 +63,34 @@ import org.springframework.util.StringUtils;
* <p>Individual expressions can be compiled by calling {@code SpelCompiler.compile(expression)}.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 4.1
*/
public final class SpelCompiler implements Opcodes {
private static final int CLASSES_DEFINED_LIMIT = 100;
private static final Log logger = LogFactory.getLog(SpelCompiler.class);
private static final int CLASSES_DEFINED_LIMIT = 100;
// A compiler is created for each classloader, it manages a child class loader of that
// classloader and the child is used to load the compiled expressions.
private static final Map<ClassLoader, SpelCompiler> compilers = new ConcurrentReferenceHashMap<>();
// The child ClassLoader used to load the compiled expression classes
private volatile ChildClassLoader childClassLoader;
private ChildClassLoader ccl;
// Counter suffix for generated classes within this SpelCompiler instance
private final AtomicInteger suffixId = new AtomicInteger(1);
private SpelCompiler(@Nullable ClassLoader classloader) {
this.childClassLoader = new ChildClassLoader(classloader);
this.ccl = new ChildClassLoader(classloader);
}
/**
* Attempt compilation of the supplied expression. A check is made to see
* if it is compilable before compilation proceeds. The check involves
* visiting all the nodes in the expression AST and ensuring enough state
* visiting all the nodes in the expression Ast and ensuring enough state
* is known about them that bytecode can be generated for them.
* @param expression the expression to compile
* @return an instance of the class implementing the compiled expression,
@@ -127,7 +125,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Generate the class that encapsulates the compiled expression and define it.
* The generated class will be a subtype of CompiledExpression.
* The generated class will be a subtype of CompiledExpression.
* @param expressionToCompile the expression to be compiled
* @return the expression call, or {@code null} if the decision was to opt out of
* compilation during code generation
@@ -137,7 +135,7 @@ public final class SpelCompiler implements Opcodes {
// Create class outline 'spel/ExNNN extends org.springframework.expression.spel.CompiledExpression'
String className = "spel/Ex" + getNextSuffix();
ClassWriter cw = new ExpressionClassWriter();
cw.visit(V1_8, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
cw.visit(V1_5, ACC_PUBLIC, className, null, "org/springframework/expression/spel/CompiledExpression", null);
// Create default constructor
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
@@ -152,7 +150,7 @@ public final class SpelCompiler implements Opcodes {
// Create getValue() method
mv = cw.visitMethod(ACC_PUBLIC, "getValue",
"(Ljava/lang/Object;Lorg/springframework/expression/EvaluationContext;)Ljava/lang/Object;", null,
new String[] {"org/springframework/expression/EvaluationException"});
new String[ ]{"org/springframework/expression/EvaluationException"});
mv.visitCode();
CodeFlow cf = new CodeFlow(className, cw);
@@ -189,7 +187,7 @@ public final class SpelCompiler implements Opcodes {
/**
* Load a compiled expression class. Makes sure the classloaders aren't used too much
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* because they anchor compiled classes in memory and prevent GC. If you have expressions
* continually recompiling over time then by replacing the classloader periodically
* at least some of the older variants can be garbage collected.
* @param name the name of the class
@@ -198,25 +196,12 @@ public final class SpelCompiler implements Opcodes {
*/
@SuppressWarnings("unchecked")
private Class<? extends CompiledExpression> loadClass(String name, byte[] bytes) {
ChildClassLoader ccl = this.childClassLoader;
if (ccl.getClassesDefinedCount() >= CLASSES_DEFINED_LIMIT) {
synchronized (this) {
ChildClassLoader currentCcl = this.childClassLoader;
if (ccl == currentCcl) {
// Still the same ClassLoader that needs to be replaced...
ccl = new ChildClassLoader(ccl.getParent());
this.childClassLoader = ccl;
}
else {
// Already replaced by some other thread, let's pick it up.
ccl = currentCcl;
}
}
if (this.ccl.getClassesDefinedCount() > CLASSES_DEFINED_LIMIT) {
this.ccl = new ChildClassLoader(this.ccl.getParent());
}
return (Class<? extends CompiledExpression>) ccl.defineClass(name, bytes);
return (Class<? extends CompiledExpression>) this.ccl.defineClass(name, bytes);
}
/**
* Factory method for compiler instances. The returned SpelCompiler will
* attach a class loader as the child of the given class loader and this
@@ -226,28 +211,21 @@ public final class SpelCompiler implements Opcodes {
*/
public static SpelCompiler getCompiler(@Nullable ClassLoader classLoader) {
ClassLoader clToUse = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
// Quick check for existing compiler without lock contention
SpelCompiler compiler = compilers.get(clToUse);
if (compiler == null) {
// Full lock now since we're creating a child ClassLoader
synchronized (compilers) {
compiler = compilers.get(clToUse);
if (compiler == null) {
compiler = new SpelCompiler(clToUse);
compilers.put(clToUse, compiler);
}
synchronized (compilers) {
SpelCompiler compiler = compilers.get(clToUse);
if (compiler == null) {
compiler = new SpelCompiler(clToUse);
compilers.put(clToUse, compiler);
}
return compiler;
}
return compiler;
}
/**
* Request that an attempt is made to compile the specified expression.
* It may fail if components of the expression are not suitable for compilation
* or the data types involved are not suitable for compilation. Used for testing.
* @param expression the expression to compile
* @return {@code true} if the expression was successfully compiled,
* {@code false} otherwise
* Request that an attempt is made to compile the specified expression. It may fail if
* components of the expression are not suitable for compilation or the data types
* involved are not suitable for compilation. Used for testing.
* @return true if the expression was successfully compiled
*/
public static boolean compile(Expression expression) {
return (expression instanceof SpelExpression && ((SpelExpression) expression).compileExpression());
@@ -272,27 +250,24 @@ public final class SpelCompiler implements Opcodes {
private static final URL[] NO_URLS = new URL[0];
private final AtomicInteger classesDefinedCount = new AtomicInteger(0);
private int classesDefinedCount = 0;
public ChildClassLoader(@Nullable ClassLoader classLoader) {
super(NO_URLS, classLoader);
}
public Class<?> defineClass(String name, byte[] bytes) {
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
this.classesDefinedCount.incrementAndGet();
return clazz;
int getClassesDefinedCount() {
return this.classesDefinedCount;
}
public int getClassesDefinedCount() {
return this.classesDefinedCount.get();
public Class<?> defineClass(String name, byte[] bytes) {
Class<?> clazz = super.defineClass(name, bytes, 0, bytes.length);
this.classesDefinedCount++;
return clazz;
}
}
/**
* An ASM ClassWriter extension bound to the SpelCompiler's ClassLoader.
*/
private class ExpressionClassWriter extends ClassWriter {
public ExpressionClassWriter() {
@@ -301,7 +276,7 @@ public final class SpelCompiler implements Opcodes {
@Override
protected ClassLoader getClassLoader() {
return childClassLoader;
return ccl;
}
}
@@ -395,11 +395,6 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
if (method == null) {
method = findMethodForProperty(getPropertyMethodSuffixes(propertyName),
"is", clazz, mustBeStatic, 0, BOOLEAN_TYPES);
if (method == null) {
// Record-style plain accessor method, e.g. name()
method = findMethodForProperty(new String[] {propertyName},
"", clazz, mustBeStatic, 0, ANY_TYPES);
}
}
return method;
}
@@ -688,11 +683,12 @@ public class ReflectivePropertyAccessor implements PropertyAccessor {
return true;
}
getterName = "is" + StringUtils.capitalize(name);
if (getterName.equals(method.getName())) {
return true;
}
return getterName.equals(method.getName());
}
else {
Field field = (Field) this.member;
return field.getName().equals(name);
}
return this.member.getName().equals(name);
}
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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,7 +36,6 @@ import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.testresources.Inventor;
import org.springframework.expression.spel.testresources.Person;
import org.springframework.expression.spel.testresources.RecordPerson;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -192,20 +191,6 @@ public class PropertyAccessTests extends AbstractExpressionTests {
parser.parseExpression("name='p3'").getValue(context, target));
}
@Test
public void propertyReadOnlyWithRecordStyle() {
EvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Expression expr = parser.parseExpression("name");
RecordPerson target1 = new RecordPerson("p1");
assertThat(expr.getValue(context, target1)).isEqualTo("p1");
RecordPerson target2 = new RecordPerson("p2");
assertThat(expr.getValue(context, target2)).isEqualTo("p2");
assertThatExceptionOfType(SpelEvaluationException.class).isThrownBy(() ->
parser.parseExpression("name='p3'").getValue(context, target2));
}
@Test
public void propertyReadWrite() {
EvaluationContext context = SimpleEvaluationContext.forReadWriteDataBinding().build();
@@ -4180,13 +4180,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(expression.getValue(tc)).isEqualTo("value4");
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value4");
// record-style accessor
expression = parser.parseExpression("strawberry");
assertCantCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value5");
assertCanCompile(expression);
assertThat(expression.getValue(tc)).isEqualTo("value5");
}
@Test
@@ -4560,9 +4553,23 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
Object v = expression.getValue(ctx,holder);
assertThat(v).isEqualTo("abc");
// // time it interpreted
// long stime = System.currentTimeMillis();
// for (int i = 0; i < 100000; i++) {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis() - stime));
assertCanCompile(expression);
v = expression.getValue(ctx,holder);
assertThat(v).isEqualTo("abc");
// // time it compiled
// stime = System.currentTimeMillis();
// for (int i = 0; i < 100000; i++) {
// v = expression.getValue(ctx,holder);
// }
// System.out.println((System.currentTimeMillis() - stime));
}
@Test
@@ -4978,12 +4985,13 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(fast.compileExpression()).isTrue();
r.setValue2(null);
// try the numbers 0,1,2,null
for (int i = 0; i < 4; i++) {
r.setValue(i < 3 ? i : null);
for (int i=0;i<4;i++) {
r.setValue(i<3?i:null);
boolean slowResult = (Boolean)slow.getValue(ctx);
boolean fastResult = (Boolean)fast.getValue(ctx);
assertThat(fastResult).as("Differing results: expression=" + expressionText +
" value=" + r.getValue() + " slow=" + slowResult + " fast="+fastResult).isEqualTo(slowResult);
// System.out.println("Trying "+expressionText+" with value="+r.getValue()+" result is "+slowResult);
assertThat(fastResult).as(" Differing results: expression="+expressionText+
" value="+r.getValue()+" slow="+slowResult+" fast="+fastResult).isEqualTo(slowResult);
}
}
@@ -4994,12 +5002,13 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
assertThat(fast.compileExpression()).isTrue();
Reg r = (Reg)ctx.getRootObject().getValue();
// try the numbers 0,1,2,null
for (int i = 0; i < 4; i++) {
r.setValue(i < 3 ? i : null);
for (int i=0;i<4;i++) {
r.setValue(i<3?i:null);
boolean slowResult = (Boolean)slow.getValue(ctx);
boolean fastResult = (Boolean)fast.getValue(ctx);
assertThat(fastResult).as("Differing results: expression=" + expressionText +
" value=" + r.getValue() + " slow=" + slowResult + " fast="+fastResult).isEqualTo(slowResult);
// System.out.println("Trying "+expressionText+" with value="+r.getValue()+" result is "+slowResult);
assertThat(fastResult).as(" Differing results: expression="+expressionText+
" value="+r.getValue()+" slow="+slowResult+" fast="+fastResult).isEqualTo(slowResult);
}
}
@@ -5830,6 +5839,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public String orange = "value1";
public static String apple = "value2";
public long peach = 34L;
public String getBanana() {
@@ -5839,10 +5849,6 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
public static String getPlum() {
return "value4";
}
public String strawberry() {
return "value5";
}
}
@@ -76,6 +76,7 @@ import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
*/
public class SpelReproTests extends AbstractExpressionTests {
@Test
public void NPE_SPR5661() {
evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -16,10 +16,9 @@
package org.springframework.expression.spel.testresources;
///CLOVER:OFF
public class Person {
private String privateName;
Company company;
public Person(String name) {
@@ -42,5 +41,4 @@ public class Person {
public Company getCompany() {
return company;
}
}
@@ -1,42 +0,0 @@
/*
* Copyright 2002-2020 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
*
* https://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.expression.spel.testresources;
public class RecordPerson {
private String name;
private Company company;
public RecordPerson(String name) {
this.name = name;
}
public RecordPerson(String name, Company company) {
this.name = name;
this.company = company;
}
public String name() {
return name;
}
public Company company() {
return company;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -19,25 +19,19 @@ package org.springframework.expression.spel.testresources;
import java.util.List;
public class TestAddress{
private String street;
private List<String> crossStreets;
private String street;
private List<String> crossStreets;
public String getStreet() {
return street;
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public List<String> getCrossStreets() {
return crossStreets;
}
public void setCrossStreets(List<String> crossStreets) {
this.crossStreets = crossStreets;
}
}
public void setStreet(String street) {
this.street = street;
}
public List<String> getCrossStreets() {
return crossStreets;
}
public void setCrossStreets(List<String> crossStreets) {
this.crossStreets = crossStreets;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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,25 +17,19 @@
package org.springframework.expression.spel.testresources;
public class TestPerson {
private String name;
private TestAddress address;
private String name;
private TestAddress address;
public String getName() {
return name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TestAddress getAddress() {
return address;
}
public void setAddress(TestAddress address) {
this.address = address;
}
}
public void setName(String name) {
this.name = name;
}
public TestAddress getAddress() {
return address;
}
public void setAddress(TestAddress address) {
this.address = address;
}
}
@@ -637,23 +637,21 @@ public class CallMetaDataContext {
schemaNameToUse = this.metaDataProvider.schemaNameToUse(getSchemaName());
}
String procedureNameToUse = this.metaDataProvider.procedureNameToUse(getProcedureName());
if (isFunction() || isReturnValueRequired()) {
callString = new StringBuilder("{? = call ");
callString = new StringBuilder().append("{? = call ").
append(StringUtils.hasLength(catalogNameToUse) ? catalogNameToUse + "." : "").
append(StringUtils.hasLength(schemaNameToUse) ? schemaNameToUse + "." : "").
append(procedureNameToUse).append("(");
parameterCount = -1;
}
else {
callString = new StringBuilder("{call ");
callString = new StringBuilder().append("{call ").
append(StringUtils.hasLength(catalogNameToUse) ? catalogNameToUse + "." : "").
append(StringUtils.hasLength(schemaNameToUse) ? schemaNameToUse + "." : "").
append(procedureNameToUse).append("(");
}
if (StringUtils.hasLength(catalogNameToUse)) {
callString.append(catalogNameToUse).append(".");
}
if (StringUtils.hasLength(schemaNameToUse)) {
callString.append(schemaNameToUse).append(".");
}
callString.append(this.metaDataProvider.procedureNameToUse(getProcedureName()));
callString.append("(");
for (SqlParameter parameter : this.callParameters) {
if (!parameter.isResultsParameter()) {
if (parameterCount > 0) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -83,7 +83,7 @@ public abstract class NamedParameterUtils {
Assert.notNull(sql, "SQL must not be null");
Set<String> namedParameters = new HashSet<>();
StringBuilder sqlToUse = new StringBuilder(sql);
String sqlToUse = sql;
List<ParameterHolder> parameterList = new ArrayList<>();
char[] statement = sql.toCharArray();
@@ -155,7 +155,7 @@ public abstract class NamedParameterUtils {
int j = i + 1;
if (j < statement.length && statement[j] == ':') {
// escaped ":" should be skipped
sqlToUse.deleteCharAt(i - escapes);
sqlToUse = sqlToUse.substring(0, i - escapes) + sqlToUse.substring(i - escapes + 1);
escapes++;
i = i + 2;
continue;
@@ -174,7 +174,7 @@ public abstract class NamedParameterUtils {
}
i++;
}
ParsedSql parsedSql = new ParsedSql(sqlToUse.toString());
ParsedSql parsedSql = new ParsedSql(sqlToUse);
for (ParameterHolder ph : parameterList) {
parsedSql.addNamedParameter(ph.getParameterName(), ph.getStartIndex(), ph.getEndIndex());
}
@@ -56,9 +56,6 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
@Nullable
private TcpOperations<byte[]> tcpClient;
@Nullable
private TaskScheduler taskScheduler;
private boolean autoStartup = true;
@Nullable
@@ -67,6 +64,8 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
@Nullable
private String userRegistryBroadcast;
@Nullable
private TaskScheduler taskScheduler;
public StompBrokerRelayRegistration(SubscribableChannel clientInboundChannel,
MessageChannel clientOutboundChannel, String[] destinationPrefixes) {
@@ -182,27 +181,8 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
* specified are effectively ignored.
* @since 4.3.15
*/
public StompBrokerRelayRegistration setTcpClient(TcpOperations<byte[]> tcpClient) {
public void setTcpClient(TcpOperations<byte[]> tcpClient) {
this.tcpClient = tcpClient;
return this;
}
/**
* Some STOMP clients (e.g. stomp-js) always send heartbeats at a fixed rate
* but others (Spring STOMP client) do so only when no other messages are
* sent. However messages with a non-broker {@link #getDestinationPrefixes()
* destination prefix} aren't forwarded and as a result the broker may deem
* the connection inactive.
* <p>When this {@link TaskScheduler} is set, it is used to reset a count of
* the number of messages sent from client to broker since the beginning of
* the current heartbeat period. This is then used to decide whether to send
* a heartbeat to the broker when ignoring a message with a non-broker
* destination prefix.
* @since 5.3
*/
public StompBrokerRelayRegistration setTaskScheduler(@Nullable TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
return this;
}
/**
@@ -248,6 +228,26 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
return this;
}
/**
* Some STOMP clients (e.g. stomp-js) always send heartbeats at a fixed rate
* but others (Spring STOMP client) do so only when no other messages are
* sent. However messages with a non-broker {@link #getDestinationPrefixes()
* destination prefix} aren't forwarded and as a result the broker may deem
* the connection inactive.
*
* <p>When this {@link TaskScheduler} is set, it is used to reset a count of
* the number of messages sent from client to broker since the beginning of
* the current heartbeat period. This is then used to decide whether to send
* a heartbeat to the broker when ignoring a message with a non-broker
* destination prefix.
*
* @param taskScheduler the scheduler to use
* @since 5.3
*/
public void setTaskScheduler(@Nullable TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
@Nullable
protected String getUserRegistryBroadcast() {
return this.userRegistryBroadcast;
@@ -256,6 +256,7 @@ public class StompBrokerRelayRegistration extends AbstractBrokerRegistration {
@Override
protected StompBrokerRelayMessageHandler getMessageHandler(SubscribableChannel brokerChannel) {
StompBrokerRelayMessageHandler handler = new StompBrokerRelayMessageHandler(
getClientInboundChannel(), getClientOutboundChannel(),
brokerChannel, getDestinationPrefixes());
@@ -95,7 +95,7 @@ abstract class NamedParameterUtils {
Assert.notNull(sql, "SQL must not be null");
Set<String> namedParameters = new HashSet<>();
StringBuilder sqlToUse = new StringBuilder(sql);
String sqlToUse = sql;
List<ParameterHolder> parameterList = new ArrayList<>();
char[] statement = sql.toCharArray();
@@ -171,7 +171,8 @@ abstract class NamedParameterUtils {
int j = i + 1;
if (j < statement.length && statement[j] == ':') {
// escaped ":" should be skipped
sqlToUse.deleteCharAt(i - escapes);
sqlToUse = sqlToUse.substring(0, i - escapes)
+ sqlToUse.substring(i - escapes + 1);
escapes++;
i = i + 2;
continue;
@@ -180,7 +181,7 @@ abstract class NamedParameterUtils {
}
i++;
}
ParsedSql parsedSql = new ParsedSql(sqlToUse.toString());
ParsedSql parsedSql = new ParsedSql(sqlToUse);
for (ParameterHolder ph : parameterList) {
parsedSql.addNamedParameter(ph.getParameterName(), ph.getStartIndex(), ph.getEndIndex());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -83,7 +83,7 @@ public @interface ActiveProfiles {
* <p>The default value is {@code true}, which means that a test
* class will <em>inherit</em> bean definition profiles defined by a
* test superclass. Specifically, the bean definition profiles for a test
* class will be appended to the list of bean definition profiles
* class will be added to the list of bean definition profiles
* defined by a test superclass. Thus, subclasses have the option of
* <em>extending</em> the list of bean definition profiles.
* <p>If {@code inheritProfiles} is set to {@code false}, the bean
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2020 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.
@@ -19,8 +19,8 @@ package org.springframework.test.context;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
@@ -533,8 +533,8 @@ public class MergedContextConfiguration implements Serializable {
return EMPTY_STRING_ARRAY;
}
// Active profiles must be unique
Set<String> profilesSet = new LinkedHashSet<>(Arrays.asList(activeProfiles));
// Active profiles must be unique and sorted
Set<String> profilesSet = new TreeSet<>(Arrays.asList(activeProfiles));
return StringUtils.toStringArray(profilesSet);
}
@@ -492,11 +492,10 @@ public abstract class TestContextAnnotationUtils {
Assert.notNull(annotation, "Annotation must not be null");
this.rootDeclaringClass = rootDeclaringClass;
this.declaringClass = declaringClass;
T mergedAnnotation = (T) AnnotatedElementUtils.findMergedAnnotation(
this.annotation = (T) AnnotatedElementUtils.findMergedAnnotation(
rootDeclaringClass, annotation.annotationType());
Assert.state(mergedAnnotation != null,
Assert.state(this.annotation != null,
() -> "Failed to find merged annotation for " + annotation);
this.annotation = mergedAnnotation;
}
public Class<?> getRootDeclaringClass() {
@@ -546,13 +545,15 @@ public abstract class TestContextAnnotationUtils {
/**
* Find <strong>all</strong> annotations of the specified annotation type
* that are present or meta-present on the {@linkplain #getRootDeclaringClass()
* root declaring class} of this descriptor or on any interfaces that the
* root declaring class implements.
* root declaring class} of this descriptor.
* @return the set of all merged, synthesized {@code Annotations} found,
* or an empty set if none were found
*/
public Set<T> findAllLocalMergedAnnotations() {
SearchStrategy searchStrategy = SearchStrategy.TYPE_HIERARCHY;
SearchStrategy searchStrategy =
(getEnclosingConfiguration(getRootDeclaringClass()) == EnclosingConfiguration.INHERIT ?
SearchStrategy.TYPE_HIERARCHY_AND_ENCLOSING_CLASSES :
SearchStrategy.TYPE_HIERARCHY);
return MergedAnnotations.from(getRootDeclaringClass(), searchStrategy, RepeatableContainers.none())
.stream(getAnnotationType())
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
@@ -16,10 +16,8 @@
package org.springframework.test.context.support;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -72,8 +70,8 @@ abstract class ActiveProfilesUtils {
static String[] resolveActiveProfiles(Class<?> testClass) {
Assert.notNull(testClass, "Class must not be null");
Set<String> activeProfiles = new TreeSet<>();
AnnotationDescriptor<ActiveProfiles> descriptor = findAnnotationDescriptor(testClass, ActiveProfiles.class);
List<String[]> profileArrays = new ArrayList<>();
if (descriptor == null && logger.isDebugEnabled()) {
logger.debug(String.format(
@@ -109,24 +107,16 @@ abstract class ActiveProfilesUtils {
String[] profiles = resolver.resolve(rootDeclaringClass);
if (!ObjectUtils.isEmpty(profiles)) {
// Prepend to the list so that we can later traverse "down" the hierarchy
// to ensure that we retain the top-down profile registration order
// within a test class hierarchy.
profileArrays.add(0, profiles);
for (String profile : profiles) {
if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim());
}
}
}
descriptor = (annotation.inheritProfiles() ? descriptor.next() : null);
}
Set<String> activeProfiles = new LinkedHashSet<>();
for (String[] profiles : profileArrays) {
for (String profile : profiles) {
if (StringUtils.hasText(profile)) {
activeProfiles.add(profile.trim());
}
}
}
return StringUtils.toStringArray(activeProfiles);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -143,7 +143,7 @@ class MergedContextConfigurationTests {
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertThat(mergedConfig2.hashCode()).isNotEqualTo(mergedConfig1.hashCode());
assertThat(mergedConfig2).hasSameHashCodeAs(mergedConfig1);
}
@Test
@@ -339,13 +339,13 @@ class MergedContextConfigurationTests {
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertThat(mergedConfig2).isNotEqualTo(mergedConfig1);
assertThat(mergedConfig2).isEqualTo(mergedConfig1);
}
@Test
void equalsWithSameDuplicateProfiles() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "catbert", "dogbert", "catbert", "dogbert", "catbert" };
String[] activeProfiles2 = new String[] { "dogbert", "catbert", "dogbert", "catbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2020 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.
@@ -90,8 +90,8 @@ class ContextCacheTests {
int size = 0, hit = 0, miss = 0;
loadCtxAndAssertStats(FooBarProfilesTestCase.class, ++size, hit, ++miss);
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
// Profiles {foo, bar} should not hash to the same as {bar,foo}
loadCtxAndAssertStats(BarFooProfilesTestCase.class, ++size, hit, ++miss);
// Profiles {foo, bar} MUST hash to the same as {bar, foo}
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
loadCtxAndAssertStats(FooBarProfilesTestCase.class, size, ++hit, miss);
loadCtxAndAssertStats(BarFooProfilesTestCase.class, size, ++hit, miss);
@@ -67,12 +67,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
@Test
void resolveActiveProfilesWithDuplicatedProfiles() {
assertResolvedProfiles(DuplicatedProfiles.class, "foo", "bar", "baz");
assertResolvedProfiles(DuplicatedProfiles.class, "bar", "baz", "foo");
}
@Test
void resolveActiveProfilesWithLocalAndInheritedDuplicatedProfiles() {
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "foo", "bar", "baz", "cat", "dog");
assertResolvedProfiles(ExtendedDuplicatedProfiles.class, "bar", "baz", "cat", "dog", "foo");
}
@Test
@@ -92,12 +92,12 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
@Test
void resolveActiveProfilesWithLocalAndInheritedAnnotations() {
assertResolvedProfiles(LocationsBar.class, "foo", "bar");
assertResolvedProfiles(LocationsBar.class, "bar", "foo");
}
@Test
void resolveActiveProfilesWithOverriddenAnnotation() {
assertResolvedProfiles(Animals.class, "dog", "cat");
assertResolvedProfiles(Animals.class, "cat", "dog");
}
/**
@@ -129,7 +129,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
*/
@Test
void resolveActiveProfilesWithLocalAndInheritedMetaAnnotations() {
assertResolvedProfiles(MetaLocationsBar.class, "foo", "bar");
assertResolvedProfiles(MetaLocationsBar.class, "bar", "foo");
}
/**
@@ -137,7 +137,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
*/
@Test
void resolveActiveProfilesWithOverriddenMetaAnnotation() {
assertResolvedProfiles(MetaAnimals.class, "dog", "cat");
assertResolvedProfiles(MetaAnimals.class, "cat", "dog");
}
/**
@@ -161,7 +161,7 @@ class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsTests {
*/
@Test
void resolveActiveProfilesWithMergedInheritedResolver() {
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "foo", "bar");
assertResolvedProfiles(MergedInheritedFooActiveProfilesResolverTestCase.class, "bar", "foo");
}
/**
@@ -373,8 +373,6 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt
void buildMergedConfigWithDuplicateConfigurationOnEnclosingClassAndNestedClass() {
compareApplesToApples(AppleConfigTestCase.class, AppleConfigTestCase.Nested.class);
compareApplesToApples(AppleConfigTestCase.Nested.class, AppleConfigTestCase.Nested.DoubleNested.class);
compareApplesToOranges(ApplesAndOrangesConfigTestCase.class, ApplesAndOrangesConfigTestCase.Nested.class);
compareApplesToOranges(ApplesAndOrangesConfigTestCase.Nested.class, ApplesAndOrangesConfigTestCase.Nested.DoubleNested.class);
}
private void compareApplesToApples(Class<?> parent, Class<?> child) {
@@ -402,7 +400,7 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt
DelegatingSmartContextLoader.class);
assertThat(parentMergedConfig.getActiveProfiles()).as("active profiles")
.containsExactly("oranges", "apples")
.containsExactly("apples", "oranges")
.isEqualTo(childMergedConfig.getActiveProfiles());
assertThat(parentMergedConfig).isEqualTo(childMergedConfig);
}
@@ -533,7 +531,7 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt
}
@ContextConfiguration(classes = AppleConfig.class)
@ActiveProfiles({"oranges", "apples"})
@ActiveProfiles({"apples", "oranges"})
static class ApplesAndOrangesConfigTestCase {
@ContextConfiguration(classes = AppleConfig.class)
@@ -541,19 +539,19 @@ class BootstrapTestUtilsMergedConfigTests extends AbstractContextConfigurationUt
class Nested {
@ContextConfiguration(classes = AppleConfig.class)
@ActiveProfiles(profiles = {"oranges", "apples", "oranges"}, inheritProfiles = false)
@ActiveProfiles(profiles = {"apples", "oranges", "apples"}, inheritProfiles = false)
class DoubleNested {
}
}
}
@ContextConfiguration(classes = AppleConfig.class)
@ActiveProfiles(profiles = {"oranges", "apples", "oranges"}, inheritProfiles = false)
@ActiveProfiles(profiles = {"oranges", "apples"}, inheritProfiles = false)
static class DuplicateConfigApplesAndOrangesConfigTestCase extends ApplesAndOrangesConfigTestCase {
}
@ContextConfiguration(classes = AppleConfig.class)
@ActiveProfiles(profiles = {"oranges", "apples", "oranges"}, inheritProfiles = false)
@ActiveProfiles(profiles = {"apples", "oranges", "apples"}, inheritProfiles = false)
static class SubDuplicateConfigApplesAndOrangesConfigTestCase extends DuplicateConfigApplesAndOrangesConfigTestCase {
}
@@ -352,9 +352,9 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
}
return new ReactiveTransactionSupport(adapter);
});
Object result = txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
return (isSuspendingFunction ? (hasSuspendingFlowReturnType ? KotlinDelegate.asFlow((Publisher<?>) result) :
KotlinDelegate.awaitSingleOrNull((Publisher<?>) result, ((CoroutinesInvocationCallback) invocation).getContinuation())) : result);
Publisher<?> publisher = (Publisher<?>) txSupport.invokeWithinTransaction(method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
return (isSuspendingFunction ? (hasSuspendingFlowReturnType ? KotlinDelegate.asFlow(publisher) :
KotlinDelegate.awaitSingleOrNull(publisher, ((CoroutinesInvocationCallback) invocation).getContinuation())) : publisher);
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
@@ -875,8 +875,8 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// For Mono and suspending functions not returning kotlinx.coroutines.flow.Flow
if (Mono.class.isAssignableFrom(method.getReturnType()) || (KotlinDetector.isSuspendingFunction(method) && !COROUTINES_FLOW_CLASS_NAME.equals(new MethodParameter(method, -1).getParameterType().getName()))) {
// Optimize for Mono
if (Mono.class.isAssignableFrom(method.getReturnType())) {
return TransactionContextManager.currentContext().flatMap(context ->
createTransactionIfNecessary(rtm, txAttr, joinpointIdentification).flatMap(it -> {
try {
@@ -1,170 +0,0 @@
/*
* Copyright 2002-2020 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
*
* https://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.transaction.annotation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.runBlocking
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.aop.framework.ProxyFactory
import org.springframework.transaction.interceptor.TransactionInterceptor
import org.springframework.transaction.testfixture.ReactiveCallCountingTransactionManager
/**
* @author Sebastien Deleuze
*/
class CoroutinesAnnotationTransactionInterceptorTests {
private val rtm = ReactiveCallCountingTransactionManager()
private val source = AnnotationTransactionAttributeSource()
@Test
fun suspendingNoValueSuccess() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(TestWithCoroutines())
proxyFactory.addAdvice(TransactionInterceptor(rtm, source))
val proxy = proxyFactory.proxy as TestWithCoroutines
runBlocking {
proxy.suspendingNoValueSuccess()
}
assertReactiveGetTransactionAndCommitCount(1)
}
@Test
fun suspendingNoValueFailure() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(TestWithCoroutines())
proxyFactory.addAdvice(TransactionInterceptor(rtm, source))
val proxy = proxyFactory.proxy as TestWithCoroutines
runBlocking {
try {
proxy.suspendingNoValueFailure()
}
catch (ex: IllegalStateException) {
}
}
assertReactiveGetTransactionAndRollbackCount(1)
}
@Test
fun suspendingValueSuccess() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(TestWithCoroutines())
proxyFactory.addAdvice(TransactionInterceptor(rtm, source))
val proxy = proxyFactory.proxy as TestWithCoroutines
runBlocking {
Assertions.assertThat(proxy.suspendingValueSuccess()).isEqualTo("foo")
}
assertReactiveGetTransactionAndCommitCount(1)
}
@Test
fun suspendingValueFailure() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(TestWithCoroutines())
proxyFactory.addAdvice(TransactionInterceptor(rtm, source))
val proxy = proxyFactory.proxy as TestWithCoroutines
runBlocking {
try {
proxy.suspendingValueFailure()
Assertions.fail("No exception thrown as expected")
}
catch (ex: IllegalStateException) {
}
}
assertReactiveGetTransactionAndRollbackCount(1)
}
@Test
fun suspendingFlowSuccess() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(TestWithCoroutines())
proxyFactory.addAdvice(TransactionInterceptor(rtm, source))
val proxy = proxyFactory.proxy as TestWithCoroutines
runBlocking {
Assertions.assertThat(proxy.suspendingFlowSuccess().toList()).containsExactly("foo", "foo")
}
assertReactiveGetTransactionAndCommitCount(1)
}
@Test
fun flowSuccess() {
val proxyFactory = ProxyFactory()
proxyFactory.setTarget(TestWithCoroutines())
proxyFactory.addAdvice(TransactionInterceptor(rtm, source))
val proxy = proxyFactory.proxy as TestWithCoroutines
runBlocking {
Assertions.assertThat(proxy.flowSuccess().toList()).containsExactly("foo", "foo")
}
assertReactiveGetTransactionAndCommitCount(1)
}
private fun assertReactiveGetTransactionAndCommitCount(expectedCount: Int) {
Assertions.assertThat(rtm.begun).isEqualTo(expectedCount)
Assertions.assertThat(rtm.commits).isEqualTo(expectedCount)
}
private fun assertReactiveGetTransactionAndRollbackCount(expectedCount: Int) {
Assertions.assertThat(rtm.begun).isEqualTo(expectedCount)
Assertions.assertThat(rtm.rollbacks).isEqualTo(expectedCount)
}
@Transactional
open class TestWithCoroutines {
open suspend fun suspendingNoValueSuccess() {
delay(10)
}
open suspend fun suspendingNoValueFailure() {
delay(10)
throw IllegalStateException()
}
open suspend fun suspendingValueSuccess(): String {
delay(10)
return "foo"
}
open suspend fun suspendingValueFailure(): String {
delay(10)
throw IllegalStateException()
}
open fun flowSuccess(): Flow<String> {
return flow {
emit("foo")
delay(10)
emit("foo")
}
}
open suspend fun suspendingFlowSuccess(): Flow<String> {
delay(10)
return flow {
emit("foo")
delay(10)
emit("foo")
}
}
}
}
@@ -21,7 +21,6 @@ import java.lang.annotation.Annotation;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
@@ -100,20 +99,8 @@ public abstract class AbstractJackson2Decoder extends Jackson2CodecSupport imple
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
JavaType javaType = getObjectMapper().constructType(elementType.getType());
// Skip String: CharSequenceDecoder + "*/*" comes after
if (CharSequence.class.isAssignableFrom(elementType.toClass()) || !supportsMimeType(mimeType)) {
return false;
}
if (!logger.isDebugEnabled()) {
return getObjectMapper().canDeserialize(javaType);
}
else {
AtomicReference<Throwable> causeRef = new AtomicReference<>();
if (getObjectMapper().canDeserialize(javaType, causeRef)) {
return true;
}
logWarningIfNecessary(javaType, causeRef.get());
return false;
}
return (!CharSequence.class.isAssignableFrom(elementType.toClass()) &&
getObjectMapper().canDeserialize(javaType) && supportsMimeType(mimeType));
}
@Override
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -34,7 +33,6 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SequenceWriter;
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException;
import com.fasterxml.jackson.databind.ser.FilterProvider;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -49,7 +47,6 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageEncoder;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.lang.Nullable;
@@ -114,23 +111,8 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
return false;
}
}
if (String.class.isAssignableFrom(elementType.resolve(clazz))) {
return false;
}
if (Object.class == clazz) {
return true;
}
if (!logger.isDebugEnabled()) {
return getObjectMapper().canSerialize(clazz);
}
else {
AtomicReference<Throwable> causeRef = new AtomicReference<>();
if (getObjectMapper().canSerialize(clazz, causeRef)) {
return true;
}
logWarningIfNecessary(clazz, causeRef.get());
return false;
}
return (Object.class == clazz ||
(!String.class.isAssignableFrom(elementType.resolve(clazz)) && getObjectMapper().canSerialize(clazz)));
}
@Override
@@ -150,7 +132,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
byte[] separator = getStreamingMediaTypeSeparator(mimeType);
if (separator != null) { // streaming
try {
ObjectWriter writer = createObjectWriter(elementType, mimeType, null, hints);
ObjectWriter writer = createObjectWriter(elementType, mimeType, hints);
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.getFactory()._getBufferRecycler());
JsonEncoding encoding = getJsonEncoding(mimeType);
JsonGenerator generator = getObjectMapper().getFactory().createGenerator(byteBuilder, encoding);
@@ -188,18 +170,7 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
public DataBuffer encodeValue(Object value, DataBufferFactory bufferFactory,
ResolvableType valueType, @Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
Class<?> jsonView = null;
FilterProvider filters = null;
if (value instanceof MappingJacksonValue) {
MappingJacksonValue container = (MappingJacksonValue) value;
value = container.getValue();
jsonView = container.getSerializationView();
filters = container.getFilters();
}
ObjectWriter writer = createObjectWriter(valueType, mimeType, jsonView, hints);
if (filters != null) {
writer = writer.with(filters);
}
ObjectWriter writer = createObjectWriter(valueType, mimeType, hints);
ByteArrayBuilder byteBuilder = new ByteArrayBuilder(writer.getFactory()._getBufferRecycler());
try {
JsonEncoding encoding = getJsonEncoding(mimeType);
@@ -281,12 +252,10 @@ public abstract class AbstractJackson2Encoder extends Jackson2CodecSupport imple
}
private ObjectWriter createObjectWriter(ResolvableType valueType, @Nullable MimeType mimeType,
@Nullable Class<?> jsonView, @Nullable Map<String, Object> hints) {
@Nullable Map<String, Object> hints) {
JavaType javaType = getJavaType(valueType.getType(), null);
if (jsonView == null && hints != null) {
jsonView = (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT);
}
Class<?> jsonView = (hints != null ? (Class<?>) hints.get(Jackson2CodecSupport.JSON_VIEW_HINT) : null);
ObjectWriter writer = (jsonView != null ?
getObjectMapper().writerWithView(jsonView) : getObjectMapper().writer());
@@ -26,7 +26,6 @@ import java.util.Map;
import com.fasterxml.jackson.annotation.JsonView;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.logging.Log;
@@ -109,34 +108,7 @@ public abstract class Jackson2CodecSupport {
protected boolean supportsMimeType(@Nullable MimeType mimeType) {
if (mimeType == null) {
return true;
}
for (MimeType supportedMimeType : this.mimeTypes) {
if (supportedMimeType.isCompatibleWith(mimeType)) {
return true;
}
}
return false;
}
/**
* Determine whether to log the given exception coming from a
* {@link ObjectMapper#canDeserialize} / {@link ObjectMapper#canSerialize} check.
* @param type the class that Jackson tested for (de-)serializability
* @param cause the Jackson-thrown exception to evaluate
* (typically a {@link JsonMappingException})
* @since 5.3.1
*/
protected void logWarningIfNecessary(Type type, @Nullable Throwable cause) {
if (cause == null) {
return;
}
if (logger.isDebugEnabled()) {
String msg = "Failed to evaluate Jackson " + (type instanceof JavaType ? "de" : "") +
"serialization for type [" + type + "]";
logger.debug(msg, cause);
}
return (mimeType == null || this.mimeTypes.stream().anyMatch(m -> m.isCompatibleWith(mimeType)));
}
protected JavaType getJavaType(Type type, @Nullable Class<?> contextClass) {
@@ -18,7 +18,6 @@ package org.springframework.http.converter.json;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
@@ -56,7 +55,6 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.TypeUtils;
/**
@@ -310,8 +308,7 @@ public abstract class AbstractJackson2HttpMessageConverter extends AbstractGener
MediaType contentType = outputMessage.getHeaders().getContentType();
JsonEncoding encoding = getJsonEncoding(contentType);
OutputStream outputStream = StreamUtils.nonClosing(outputMessage.getBody());
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputStream, encoding)) {
try (JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding)) {
writePrefix(generator, object);
Object value = object;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -42,8 +42,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0);
return Map.class.isAssignableFrom(parameter.getParameterType()) &&
parameter.getParameterAnnotations().length == 0;
}
@Override
@@ -70,8 +70,8 @@ public class MapMethodProcessor implements HandlerMethodArgumentResolver, Handle
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type [" +
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2017 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.
@@ -70,8 +70,8 @@ public class ModelMethodProcessor implements HandlerMethodArgumentResolver, Hand
}
else {
// should not happen
throw new UnsupportedOperationException("Unexpected return type [" +
returnType.getParameterType().getName() + "] in method: " + returnType.getMethod());
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
@@ -1023,21 +1023,15 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
if (this.path.length() == 0) {
return null;
}
String sanitized = getSanitizedPath(this.path);
return new HierarchicalUriComponents.FullPathComponent(sanitized);
}
private static String getSanitizedPath(final StringBuilder path) {
int index = path.indexOf("//");
if (index >= 0) {
StringBuilder sanitized = new StringBuilder(path);
while (index != -1) {
sanitized.deleteCharAt(index);
index = sanitized.indexOf("//", index);
String path = this.path.toString();
while (true) {
int index = path.indexOf("//");
if (index == -1) {
break;
}
return sanitized.toString();
path = path.substring(0, index) + path.substring(index + 1);
}
return path.toString();
return new HierarchicalUriComponents.FullPathComponent(path);
}
public void removeTrailingSlash() {
@@ -401,17 +401,18 @@ public class UrlPathHelper {
* <li>replace all "//" by "/"</li>
* </ul>
*/
private static String getSanitizedPath(final String path) {
int index = path.indexOf("//");
if (index >= 0) {
StringBuilder sanitized = new StringBuilder(path);
while (index != -1) {
sanitized.deleteCharAt(index);
index = sanitized.indexOf("//", index);
private String getSanitizedPath(final String path) {
String sanitized = path;
while (true) {
int index = sanitized.indexOf("//");
if (index < 0) {
break;
}
else {
sanitized = sanitized.substring(0, index) + sanitized.substring(index + 1);
}
return sanitized.toString();
}
return path;
return sanitized;
}
/**
@@ -611,21 +612,15 @@ public class UrlPathHelper {
removeSemicolonContentInternal(requestUri) : removeJsessionid(requestUri));
}
private static String removeSemicolonContentInternal(String requestUri) {
private String removeSemicolonContentInternal(String requestUri) {
int semicolonIndex = requestUri.indexOf(';');
if (semicolonIndex == -1) {
return requestUri;
}
StringBuilder sb = new StringBuilder(requestUri);
while (semicolonIndex != -1) {
int slashIndex = sb.indexOf("/", semicolonIndex + 1);
if (slashIndex == -1) {
return sb.substring(0, semicolonIndex);
}
sb.delete(semicolonIndex, slashIndex);
semicolonIndex = sb.indexOf(";", semicolonIndex);
int slashIndex = requestUri.indexOf('/', semicolonIndex);
String start = requestUri.substring(0, semicolonIndex);
requestUri = (slashIndex != -1) ? start + requestUri.substring(slashIndex) : start;
semicolonIndex = requestUri.indexOf(';', semicolonIndex);
}
return sb.toString();
return requestUri;
}
private String removeJsessionid(String requestUri) {
@@ -39,7 +39,6 @@ import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.http.codec.json.JacksonViewBean.MyJacksonView1;
import org.springframework.http.codec.json.JacksonViewBean.MyJacksonView3;
import org.springframework.http.converter.json.MappingJacksonValue;
import org.springframework.util.MimeType;
import org.springframework.util.MimeTypeUtils;
import org.springframework.web.testfixture.xml.Pojo;
@@ -215,25 +214,6 @@ public class Jackson2JsonEncoderTests extends AbstractEncoderTests<Jackson2JsonE
null, hints);
}
@Test
public void jacksonValue() {
JacksonViewBean bean = new JacksonViewBean();
bean.setWithView1("with");
bean.setWithView2("with");
bean.setWithoutView("without");
MappingJacksonValue jacksonValue = new MappingJacksonValue(bean);
jacksonValue.setSerializationView(MyJacksonView1.class);
ResolvableType type = ResolvableType.forClass(MappingJacksonValue.class);
testEncode(Mono.just(jacksonValue), type, step -> step
.consumeNextWith(expectString("{\"withView1\":\"with\"}")
.andThen(DataBufferUtils::release))
.verifyComplete(),
null, Collections.emptyMap());
}
@Test // gh-22771
public void encodeWithFlushAfterWriteOff() {
ObjectMapper mapper = new ObjectMapper();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -47,8 +47,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.entry;
import static org.assertj.core.api.Assertions.within;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* Jackson 2.x converter tests.
@@ -151,7 +149,6 @@ public class MappingJackson2HttpMessageConverterTests {
assertThat(result.contains("\"bool\":true")).isTrue();
assertThat(result.contains("\"bytes\":\"AQI=\"")).isTrue();
assertThat(outputMessage.getHeaders().getContentType()).as("Invalid content-type").isEqualTo(MediaType.APPLICATION_JSON);
verify(outputMessage.getBody(), never()).close();
}
@Test
@@ -55,8 +55,8 @@ class UriComponentsBuilderTests {
void plain() throws URISyntaxException {
UriComponentsBuilder builder = UriComponentsBuilder.newInstance();
UriComponents result = builder.scheme("https").host("example.com")
.path("foo").queryParam("bar").fragment("baz").build();
.path("foo").queryParam("bar").fragment("baz")
.build();
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("example.com");
assertThat(result.getPath()).isEqualTo("foo");
@@ -93,10 +93,10 @@ class UriComponentsBuilderTests {
@Test
void fromPath() throws URISyntaxException {
UriComponents result = UriComponentsBuilder.fromPath("foo").queryParam("bar").fragment("baz").build();
assertThat(result.getPath()).isEqualTo("foo");
assertThat(result.getQuery()).isEqualTo("bar");
assertThat(result.getFragment()).isEqualTo("baz");
assertThat(result.toUriString()).as("Invalid result URI String").isEqualTo("foo?bar#baz");
URI expected = new URI("foo?bar#baz");
@@ -113,12 +113,12 @@ class UriComponentsBuilderTests {
void fromHierarchicalUri() throws URISyntaxException {
URI uri = new URI("https://example.com/foo?bar#baz");
UriComponents result = UriComponentsBuilder.fromUri(uri).build();
assertThat(result.getScheme()).isEqualTo("https");
assertThat(result.getHost()).isEqualTo("example.com");
assertThat(result.getPath()).isEqualTo("/foo");
assertThat(result.getQuery()).isEqualTo("bar");
assertThat(result.getFragment()).isEqualTo("baz");
assertThat(result.toUri()).as("Invalid result URI").isEqualTo(uri);
}
@@ -126,10 +126,10 @@ class UriComponentsBuilderTests {
void fromOpaqueUri() throws URISyntaxException {
URI uri = new URI("mailto:foo@bar.com#baz");
UriComponents result = UriComponentsBuilder.fromUri(uri).build();
assertThat(result.getScheme()).isEqualTo("mailto");
assertThat(result.getSchemeSpecificPart()).isEqualTo("foo@bar.com");
assertThat(result.getFragment()).isEqualTo("baz");
assertThat(result.toUri()).as("Invalid result URI").isEqualTo(uri);
}
@@ -614,7 +614,7 @@ class UriComponentsBuilderTests {
assertThat(after.getPath()).isEqualTo("/foo/");
}
@Test // gh-19890
@Test // gh-19890
void fromHttpRequestWithEmptyScheme() {
HttpRequest request = new HttpRequest() {
@Override
@@ -896,7 +896,7 @@ class UriComponentsBuilderTests {
assertThat(uriComponents.getQueryParams().get("bar").get(0)).isNull();
}
@Test // gh-24444
@Test // gh-24444
void opaqueUriDoesNotResetOnNullInput() throws URISyntaxException {
URI uri = new URI("urn:ietf:wg:oauth:2.0:oob");
UriComponents result = UriComponentsBuilder.fromUri(uri)
@@ -1156,7 +1156,7 @@ class UriComponentsBuilderTests {
assertThat(result.toUriString()).isEqualTo("https://example.com/rest/mobile/users/1");
}
@Test // gh-25737
@Test // gh-25737
void fromHttpRequestForwardedHeaderComma() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader("Forwarded", "for=192.0.2.0,for=192.0.2.1;proto=https;host=192.0.2.3:9090");
@@ -1195,15 +1195,9 @@ class UriComponentsBuilderTests {
assertThat(uri).isEqualTo("http://localhost:8081/{path}?sort={sort}&sort=another_value");
}
@Test // SPR-17630
@Test // SPR-17630
void toUriStringWithCurlyBraces() {
assertThat(UriComponentsBuilder.fromUriString("/path?q={asa}asa").toUriString()).isEqualTo("/path?q=%7Basa%7Dasa");
}
@Test // gh-26012
void verifyDoubleSlashReplacedWithSingleOne() {
String path = UriComponentsBuilder.fromPath("/home/").path("/path").build().getPath();
assertThat(path).isEqualTo("/home/path");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -43,6 +43,7 @@ public class UriComponentsTests {
@Test
public void expandAndEncode() {
UriComponents uri = UriComponentsBuilder
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").build()
.expand("Z\u00fcrich", "a+b").encode();
@@ -52,6 +53,7 @@ public class UriComponentsTests {
@Test
public void encodeAndExpand() {
UriComponents uri = UriComponentsBuilder
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode().build()
.expand("Z\u00fcrich", "a+b");
@@ -61,14 +63,16 @@ public class UriComponentsTests {
@Test
public void encodeAndExpandPartially() {
UriComponents uri = UriComponentsBuilder
.fromPath("/hotel list/{city} specials").queryParam("q", "{value}").encode()
.uriVariables(Collections.singletonMap("city", "Z\u00fcrich")).build();
.uriVariables(Collections.singletonMap("city", "Z\u00fcrich"))
.build();
assertThat(uri.expand("a+b").toString()).isEqualTo("/hotel%20list/Z%C3%BCrich%20specials?q=a%2Bb");
}
@Test // SPR-17168
@Test // SPR-17168
public void encodeAndExpandWithDollarSign() {
UriComponents uri = UriComponentsBuilder.fromPath("/path").queryParam("q", "{value}").encode().build();
assertThat(uri.expand("JavaClass$1.class").toString()).isEqualTo("/path?q=JavaClass%241.class");
@@ -76,71 +80,71 @@ public class UriComponentsTests {
@Test
public void toUriEncoded() throws URISyntaxException {
UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/hotel list/Z\u00fcrich").build();
assertThat(uri.encode().toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z%C3%BCrich"));
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"https://example.com/hotel list/Z\u00fcrich").build();
assertThat(uriComponents.encode().toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z%C3%BCrich"));
}
@Test
public void toUriNotEncoded() throws URISyntaxException {
UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/hotel list/Z\u00fcrich").build();
assertThat(uri.toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z\u00fcrich"));
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"https://example.com/hotel list/Z\u00fcrich").build();
assertThat(uriComponents.toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z\u00fcrich"));
}
@Test
public void toUriAlreadyEncoded() throws URISyntaxException {
UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/hotel%20list/Z%C3%BCrich").build(true);
assertThat(uri.encode().toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z%C3%BCrich"));
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"https://example.com/hotel%20list/Z%C3%BCrich").build(true);
UriComponents encoded = uriComponents.encode();
assertThat(encoded.toUri()).isEqualTo(new URI("https://example.com/hotel%20list/Z%C3%BCrich"));
}
@Test
public void toUriWithIpv6HostAlreadyEncoded() throws URISyntaxException {
UriComponents uri = UriComponentsBuilder.fromUriString(
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://[1abc:2abc:3abc::5ABC:6abc]:8080/hotel%20list/Z%C3%BCrich").build(true);
assertThat(uri.encode().toUri()).isEqualTo(
new URI("http://[1abc:2abc:3abc::5ABC:6abc]:8080/hotel%20list/Z%C3%BCrich"));
UriComponents encoded = uriComponents.encode();
assertThat(encoded.toUri()).isEqualTo(new URI("http://[1abc:2abc:3abc::5ABC:6abc]:8080/hotel%20list/Z%C3%BCrich"));
}
@Test
public void expand() {
UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com").path("/{foo} {bar}").build();
uri = uri.expand("1 2", "3 4");
assertThat(uri.getPath()).isEqualTo("/1 2 3 4");
assertThat(uri.toUriString()).isEqualTo("https://example.com/1 2 3 4");
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"https://example.com").path("/{foo} {bar}").build();
uriComponents = uriComponents.expand("1 2", "3 4");
assertThat(uriComponents.getPath()).isEqualTo("/1 2 3 4");
assertThat(uriComponents.toUriString()).isEqualTo("https://example.com/1 2 3 4");
}
@Test // SPR-13311
@Test // SPR-13311
public void expandWithRegexVar() {
String template = "/myurl/{name:[a-z]{1,5}}/show";
UriComponents uri = UriComponentsBuilder.fromUriString(template).build();
uri = uri.expand(Collections.singletonMap("name", "test"));
assertThat(uri.getPath()).isEqualTo("/myurl/test/show");
UriComponents uriComponents = UriComponentsBuilder.fromUriString(template).build();
uriComponents = uriComponents.expand(Collections.singletonMap("name", "test"));
assertThat(uriComponents.getPath()).isEqualTo("/myurl/test/show");
}
@Test // SPR-17630
@Test // SPR-17630
public void uirTemplateExpandWithMismatchedCurlyBraces() {
UriComponents uri = UriComponentsBuilder.fromUriString("/myurl/?q={{{{").encode().build();
assertThat(uri.toUriString()).isEqualTo("/myurl/?q=%7B%7B%7B%7B");
assertThat(UriComponentsBuilder.fromUriString("/myurl/?q={{{{").encode().build().toUriString()).isEqualTo("/myurl/?q=%7B%7B%7B%7B");
}
@Test // gh-22447
@Test // gh-22447
public void expandWithFragmentOrder() {
UriComponents uri = UriComponentsBuilder
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://{host}/{path}#{fragment}").build()
.expand("example.com", "foo", "bar");
assertThat(uri.toUriString()).isEqualTo("https://example.com/foo#bar");
assertThat(uriComponents.toUriString()).isEqualTo("https://example.com/foo#bar");
}
@Test // SPR-12123
@Test // SPR-12123
public void port() {
UriComponents uri1 = fromUriString("https://example.com:8080/bar").build();
UriComponents uri2 = fromUriString("https://example.com/bar").port(8080).build();
UriComponents uri3 = fromUriString("https://example.com/bar").port("{port}").build().expand(8080);
UriComponents uri4 = fromUriString("https://example.com/bar").port("808{digit}").build().expand(0);
assertThat(uri1.getPort()).isEqualTo(8080);
assertThat(uri1.toUriString()).isEqualTo("https://example.com:8080/bar");
assertThat(uri2.getPort()).isEqualTo(8080);
@@ -171,22 +175,20 @@ public class UriComponentsTests {
@Test
public void normalize() {
UriComponents uri = UriComponentsBuilder.fromUriString("https://example.com/foo/../bar").build();
assertThat(uri.normalize().toString()).isEqualTo("https://example.com/bar");
UriComponents uriComponents = UriComponentsBuilder.fromUriString("https://example.com/foo/../bar").build();
assertThat(uriComponents.normalize().toString()).isEqualTo("https://example.com/bar");
}
@Test
public void serializable() throws Exception {
UriComponents uri = UriComponentsBuilder.fromUriString(
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"https://example.com").path("/{foo}").query("bar={baz}").build();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(uri);
oos.writeObject(uriComponents);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
UriComponents readObject = (UriComponents) ois.readObject();
assertThat(uri.toString()).isEqualTo(readObject.toString());
assertThat(uriComponents.toString()).isEqualTo(readObject.toString());
}
@Test
@@ -195,7 +197,6 @@ public class UriComponentsTests {
UriComponentsBuilder targetBuilder = UriComponentsBuilder.newInstance();
source.copyToUriComponentsBuilder(targetBuilder);
UriComponents result = targetBuilder.build().encode();
assertThat(result.getPath()).isEqualTo("/foo/bar/ba%2Fz");
assertThat(result.getPathSegments()).isEqualTo(Arrays.asList("foo", "bar", "ba%2Fz"));
}
@@ -206,7 +207,6 @@ public class UriComponentsTests {
UriComponents uric1 = UriComponentsBuilder.fromUriString(url).path("/{foo}").query("bar={baz}").build();
UriComponents uric2 = UriComponentsBuilder.fromUriString(url).path("/{foo}").query("bar={baz}").build();
UriComponents uric3 = UriComponentsBuilder.fromUriString(url).path("/{foo}").query("bin={baz}").build();
assertThat(uric1).isInstanceOf(HierarchicalUriComponents.class);
assertThat(uric1).isEqualTo(uric1);
assertThat(uric1).isEqualTo(uric2);
@@ -219,7 +219,6 @@ public class UriComponentsTests {
UriComponents uric1 = UriComponentsBuilder.fromUriString(baseUrl + "/foo/bar").build();
UriComponents uric2 = UriComponentsBuilder.fromUriString(baseUrl + "/foo/bar").build();
UriComponents uric3 = UriComponentsBuilder.fromUriString(baseUrl + "/foo/bin").build();
assertThat(uric1).isInstanceOf(OpaqueUriComponents.class);
assertThat(uric1).isEqualTo(uric1);
assertThat(uric1).isEqualTo(uric2);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -49,7 +49,9 @@ public class UriTemplateTests {
assertThat(result).as("Invalid expanded template").isEqualTo(new URI("/hotels/1/bookings/42"));
}
@Test // SPR-9712
// SPR-9712
@Test
public void expandVarArgsWithArrayValue() throws Exception {
UriTemplate template = new UriTemplate("/sum?numbers={numbers}");
URI result = template.expand(new int[] {1, 2, 3});
@@ -59,7 +61,8 @@ public class UriTemplateTests {
@Test
public void expandVarArgsNotEnoughVariables() throws Exception {
UriTemplate template = new UriTemplate("/hotels/{hotel}/bookings/{booking}");
assertThatIllegalArgumentException().isThrownBy(() -> template.expand("1"));
assertThatIllegalArgumentException().isThrownBy(() ->
template.expand("1"));
}
@Test
@@ -153,7 +156,7 @@ public class UriTemplateTests {
assertThat(result).as("Invalid match").isEqualTo(expected);
}
@Test // SPR-13627
@Test // SPR-13627
public void matchCustomRegexWithNestedCurlyBraces() throws Exception {
UriTemplate template = new UriTemplate("/site.{domain:co.[a-z]{2}}");
Map<String, String> result = template.match("/site.co.eu");
@@ -178,8 +181,8 @@ public class UriTemplateTests {
assertThat(result).as("Invalid match").isEqualTo(expected);
}
@Test // SPR-16169
public void matchWithMultipleSegmentsAtTheEnd() throws Exception {
@Test // SPR-16169
public void matchWithMultipleSegmentsAtTheEnd() {
UriTemplate template = new UriTemplate("/account/{accountId}");
assertThat(template.matches("/account/15/alias/5")).isFalse();
}
@@ -199,20 +202,21 @@ public class UriTemplateTests {
assertThat(template.matches("/search?query=foo#bar")).isTrue();
}
@Test // SPR-13705
public void matchesWithSlashAtTheEnd() throws Exception {
assertThat(new UriTemplate("/test/").matches("/test/")).isTrue();
@Test // SPR-13705
public void matchesWithSlashAtTheEnd() {
UriTemplate uriTemplate = new UriTemplate("/test/");
assertThat(uriTemplate.matches("/test/")).isTrue();
}
@Test
public void expandWithDollar() throws Exception {
public void expandWithDollar() {
UriTemplate template = new UriTemplate("/{a}");
URI uri = template.expand("$replacement");
assertThat(uri.toString()).isEqualTo("/$replacement");
}
@Test
public void expandWithAtSign() throws Exception {
public void expandWithAtSign() {
UriTemplate template = new UriTemplate("http://localhost/query={query}");
URI uri = template.expand("foo@bar");
assertThat(uri.toString()).isEqualTo("http://localhost/query=foo@bar");
@@ -104,9 +104,6 @@ public class UrlPathHelperTests {
request.setRequestURI("/foo+bar");
assertThat(helper.getRequestUri(request)).isEqualTo("/foo+bar");
request.setRequestURI("/home/" + "/path");
assertThat(helper.getRequestUri(request)).isEqualTo("/home/path");
}
@Test
@@ -115,9 +112,6 @@ public class UrlPathHelperTests {
request.setRequestURI("/foo;f=F;o=O;o=O/bar;b=B;a=A;r=R");
assertThat(helper.getRequestUri(request)).isEqualTo("/foo/bar");
request.setRequestURI("/foo;f=F;o=O;o=O/bar;b=B;a=A;r=R/baz;test");
assertThat(helper.getRequestUri(request)).isEqualTo("/foo/bar/baz");
// SPR-13455
request.setRequestURI("/foo/;test/1");
request.setServletPath("/foo/1");
@@ -33,7 +33,6 @@ import java.util.function.Supplier;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
@@ -174,9 +173,6 @@ class DefaultWebClient implements WebClient {
private final Map<String, Object> attributes = new LinkedHashMap<>(4);
@Nullable
private Function<Context, Context> contextModifier;
@Nullable
private Consumer<ClientHttpRequest> httpRequestConsumer;
@@ -185,7 +181,6 @@ class DefaultWebClient implements WebClient {
this.httpMethod = httpMethod;
}
@Override
public RequestBodySpec uri(String uriTemplate, Object... uriVariables) {
attribute(URI_TEMPLATE_ATTRIBUTE, uriTemplate);
@@ -243,6 +238,18 @@ class DefaultWebClient implements WebClient {
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
@Override
public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
attributesConsumer.accept(this.attributes);
return this;
}
@Override
public DefaultRequestBodyUriSpec accept(MediaType... acceptableMediaTypes) {
getHeaders().setAccept(Arrays.asList(acceptableMediaTypes));
@@ -291,25 +298,6 @@ class DefaultWebClient implements WebClient {
return this;
}
@Override
public RequestBodySpec attribute(String name, Object value) {
this.attributes.put(name, value);
return this;
}
@Override
public RequestBodySpec attributes(Consumer<Map<String, Object>> attributesConsumer) {
attributesConsumer.accept(this.attributes);
return this;
}
@Override
public RequestBodySpec context(Function<Context, Context> contextModifier) {
this.contextModifier = (this.contextModifier != null ?
this.contextModifier.andThen(contextModifier) : contextModifier);
return this;
}
@Override
public RequestBodySpec httpRequest(Consumer<ClientHttpRequest> requestConsumer) {
this.httpRequestConsumer = (this.httpRequestConsumer != null ?
@@ -424,15 +412,9 @@ class DefaultWebClient implements WebClient {
ClientRequest request = (this.inserter != null ?
initRequestBuilder().body(this.inserter).build() :
initRequestBuilder().build());
return Mono.defer(() -> {
Mono<ClientResponse> responseMono = exchangeFunction.exchange(request)
.checkpoint("Request to " + this.httpMethod.name() + " " + this.uri + " [DefaultWebClient]")
.switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR);
if (this.contextModifier != null) {
responseMono = responseMono.contextWrite(this.contextModifier);
}
return responseMono;
});
return Mono.defer(() -> exchangeFunction.exchange(request)
.checkpoint("Request to " + this.httpMethod.name() + " " + this.uri + " [DefaultWebClient]")
.switchIfEmpty(NO_HTTP_CLIENT_RESPONSE_ERROR));
}
private ClientRequest.Builder initRequestBuilder() {
@@ -498,14 +480,12 @@ class DefaultWebClient implements WebClient {
private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
DefaultResponseSpec(Mono<ClientResponse> responseMono, Supplier<HttpRequest> requestSupplier) {
this.responseMono = responseMono;
this.requestSupplier = requestSupplier;
this.statusHandlers.add(DEFAULT_STATUS_HANDLER);
}
@Override
public ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate,
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
@@ -642,22 +622,6 @@ class DefaultWebClient implements WebClient {
handleBodyFlux(response, response.bodyToFlux(elementTypeRef))));
}
@Override
public <T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(Class<T> elementType) {
return this.responseMono.map(response ->
ResponseEntity.status(response.rawStatusCode())
.headers(response.headers().asHttpHeaders())
.body(response.bodyToFlux(elementType)));
}
@Override
public <T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(ParameterizedTypeReference<T> elementTypeReference) {
return this.responseMono.map(response ->
ResponseEntity.status(response.rawStatusCode())
.headers(response.headers().asHttpHeaders())
.body(response.bodyToFlux(elementTypeReference)));
}
@Override
public Mono<ResponseEntity<Void>> toBodilessEntity() {
return this.responseMono.flatMap(response ->
@@ -274,13 +274,9 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
.map(filter -> filter.apply(exchange))
.orElse(exchange) : exchange);
HttpHeaders defaultHeaders = copyDefaultHeaders();
MultiValueMap<String, String> defaultCookies = copyDefaultCookies();
return new DefaultWebClient(filteredExchange, initUriBuilderFactory(),
defaultHeaders,
defaultCookies,
this.defaultHeaders != null ? HttpHeaders.readOnlyHttpHeaders(this.defaultHeaders) : null,
this.defaultCookies != null ? CollectionUtils.unmodifiableMultiValueMap(this.defaultCookies) : null,
this.defaultRequest, new DefaultWebClientBuilder(this));
}
@@ -317,28 +313,4 @@ final class DefaultWebClientBuilder implements WebClient.Builder {
return factory;
}
@Nullable
private HttpHeaders copyDefaultHeaders() {
if (this.defaultHeaders != null) {
HttpHeaders copy = new HttpHeaders();
this.defaultHeaders.forEach((key, values) -> copy.put(key, new ArrayList<>(values)));
return HttpHeaders.readOnlyHttpHeaders(copy);
}
else {
return null;
}
}
@Nullable
private MultiValueMap<String, String> copyDefaultCookies() {
if (this.defaultCookies != null) {
MultiValueMap<String, String> copy = new LinkedMultiValueMap<>(this.defaultCookies.size());
this.defaultCookies.forEach((key, values) -> copy.put(key, new ArrayList<>(values)));
return CollectionUtils.unmodifiableMultiValueMap(copy);
}
else {
return null;
}
}
}
@@ -29,7 +29,6 @@ import java.util.function.Predicate;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.ReactiveAdapterRegistry;
@@ -471,17 +470,6 @@ public interface WebClient {
*/
S attributes(Consumer<Map<String, Object>> attributesConsumer);
/**
* Provide a function to populate the Reactor {@code Context}. In contrast
* to {@link #attribute(String, Object) attributes} which apply only to
* the current request, the Reactor {@code Context} transparently propagates
* to the downstream processing chain which may include other nested or
* successive calls over HTTP or via other reactive clients.
* @param contextModifier the function to modify the context with
* @since 5.3.1
*/
S context(Function<Context, Context> contextModifier);
/**
* Callback for access to the {@link ClientHttpRequest} that in turn
* provides access to the native request of the underlying HTTP library.
@@ -793,116 +781,103 @@ public interface WebClient {
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction);
/**
* Decode the body to the given target type. For an error response (status
* code of 4xx or 5xx), the {@code Mono} emits a {@link WebClientException}.
* Use {@link #onStatus(Predicate, Function)} to customize error response
* handling.
* @param elementClass the type to decode to
* @param <T> the target body type
* @return the decoded body
* Extract the body to a {@code Mono}. By default, if the response has status code 4xx or
* 5xx, the {@code Mono} will contain a {@link WebClientException}. This can be overridden
* with {@link #onStatus(Predicate, Function)}.
* @param elementClass the expected response body element class
* @param <T> response body type
* @return a mono containing the body, or a {@link WebClientResponseException} if the
* status code is 4xx or 5xx
*/
<T> Mono<T> bodyToMono(Class<T> elementClass);
/**
* Variant of {@link #bodyToMono(Class)} with a {@link ParameterizedTypeReference}.
* @param elementTypeRef the type to decode to
* @param <T> the target body type
* @return the decoded body
* Extract the body to a {@code Mono}. By default, if the response has status code 4xx or
* 5xx, the {@code Mono} will contain a {@link WebClientException}. This can be overridden
* with {@link #onStatus(Predicate, Function)}.
* @param elementTypeRef a type reference describing the expected response body element type
* @param <T> response body type
* @return a mono containing the body, or a {@link WebClientResponseException} if the
* status code is 4xx or 5xx
*/
<T> Mono<T> bodyToMono(ParameterizedTypeReference<T> elementTypeRef);
/**
* Decode the body to a {@link Flux} with elements of the given type.
* For an error response (status code of 4xx or 5xx), the {@code Mono}
* emits a {@link WebClientException}. Use {@link #onStatus(Predicate, Function)}
* to customize error response handling.
* @param elementClass the type of element to decode to
* @param <T> the body element type
* @return the decoded body
* Extract the body to a {@code Flux}. By default, if the response has status code 4xx or
* 5xx, the {@code Flux} will contain a {@link WebClientException}. This can be overridden
* with {@link #onStatus(Predicate, Function)}.
* @param elementClass the class of elements in the response
* @param <T> the type of elements in the response
* @return a flux containing the body, or a {@link WebClientResponseException} if the
* status code is 4xx or 5xx
*/
<T> Flux<T> bodyToFlux(Class<T> elementClass);
/**
* Variant of {@link #bodyToMono(Class)} with a {@link ParameterizedTypeReference}.
* @param elementTypeRef the type of element to decode to
* @param <T> the body element type
* @return the decoded body
* Extract the body to a {@code Flux}. By default, if the response has status code 4xx or
* 5xx, the {@code Flux} will contain a {@link WebClientException}. This can be overridden
* with {@link #onStatus(Predicate, Function)}.
* @param elementTypeRef a type reference describing the expected response body element type
* @param <T> the type of elements in the response
* @return a flux containing the body, or a {@link WebClientResponseException} if the
* status code is 4xx or 5xx
*/
<T> Flux<T> bodyToFlux(ParameterizedTypeReference<T> elementTypeRef);
/**
* Return a {@code ResponseEntity} with the body decoded to an Object of
* the given type. For an error response (status code of 4xx or 5xx), the
* {@code Mono} emits a {@link WebClientException}. Use
* {@link #onStatus(Predicate, Function)} to customize error response handling.
* Return the response as a delayed {@code ResponseEntity}. By default, if the response has
* status code 4xx or 5xx, the {@code Mono} will contain a {@link WebClientException}. This
* can be overridden with {@link #onStatus(Predicate, Function)}.
* @param bodyClass the expected response body type
* @param <T> response body type
* @return the {@code ResponseEntity} with the decoded body
* @return {@code Mono} with the {@code ResponseEntity}
* @since 5.2
*/
<T> Mono<ResponseEntity<T>> toEntity(Class<T> bodyClass);
/**
* Variant of {@link #bodyToMono(Class)} with a {@link ParameterizedTypeReference}.
* @param bodyTypeReference the expected response body type
* @param <T> the response body type
* @return the {@code ResponseEntity} with the decoded body
* Return the response as a delayed {@code ResponseEntity}. By default, if the response has
* status code 4xx or 5xx, the {@code Mono} will contain a {@link WebClientException}. This
* can be overridden with {@link #onStatus(Predicate, Function)}.
* @param bodyTypeReference a type reference describing the expected response body type
* @param <T> response body type
* @return {@code Mono} with the {@code ResponseEntity}
* @since 5.2
*/
<T> Mono<ResponseEntity<T>> toEntity(ParameterizedTypeReference<T> bodyTypeReference);
/**
* Return a {@code ResponseEntity} with the body decoded to a {@code List}
* of elements of the given type. For an error response (status code of
* 4xx or 5xx), the {@code Mono} emits a {@link WebClientException}.
* Use {@link #onStatus(Predicate, Function)} to customize error response
* handling.
* @param elementClass the type of element to decode the target Flux to
* @param <T> the body element type
* @return the {@code ResponseEntity}
* Return the response as a delayed list of {@code ResponseEntity}s. By default, if the
* response has status code 4xx or 5xx, the {@code Mono} will contain a
* {@link WebClientException}. This can be overridden with
* {@link #onStatus(Predicate, Function)}.
* @param elementClass the expected response body list element class
* @param <T> the type of elements in the list
* @return {@code Mono} with the list of {@code ResponseEntity}s
* @since 5.2
*/
<T> Mono<ResponseEntity<List<T>>> toEntityList(Class<T> elementClass);
/**
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
* @param elementTypeRef the type of element to decode the target Flux to
* @param <T> the body element type
* @return the {@code ResponseEntity}
* Return the response as a delayed list of {@code ResponseEntity}s. By default, if the
* response has status code 4xx or 5xx, the {@code Mono} will contain a
* {@link WebClientException}. This can be overridden with
* {@link #onStatus(Predicate, Function)}.
* @param elementTypeRef the expected response body list element reference type
* @param <T> the type of elements in the list
* @return {@code Mono} with the list of {@code ResponseEntity}s
* @since 5.2
*/
<T> Mono<ResponseEntity<List<T>>> toEntityList(ParameterizedTypeReference<T> elementTypeRef);
/**
* Return a {@code ResponseEntity} with the body decoded to a {@code Flux}
* of elements of the given type. For an error response (status code of
* 4xx or 5xx), the {@code Mono} emits a {@link WebClientException}.
* Use {@link #onStatus(Predicate, Function)} to customize error response
* handling.
* <p><strong>Note:</strong> The {@code Flux} representing the body must
* be subscribed to or else associated resources will not be released.
* @param elementType the type of element to decode the target Flux to
* @param <T> the body element type
* @return the {@code ResponseEntity}
* @since 5.3.1
*/
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(Class<T> elementType);
/**
* Variant of {@link #toEntity(Class)} with a {@link ParameterizedTypeReference}.
* @param elementTypeReference the type of element to decode the target Flux to
* @param <T> the body element type
* @return the {@code ResponseEntity}
* @since 5.3.1
*/
<T> Mono<ResponseEntity<Flux<T>>> toEntityFlux(ParameterizedTypeReference<T> elementTypeReference);
/**
* Return a {@code ResponseEntity} without a body. For an error response
* (status code of 4xx or 5xx), the {@code Mono} emits a
* {@link WebClientException}. Use {@link #onStatus(Predicate, Function)}
* to customize error response handling.
* @return the {@code ResponseEntity}
* Return the response as a delayed {@code ResponseEntity} containing status and headers,
* but no body. By default, if the response has status code 4xx or 5xx, the {@code Mono}
* will contain a {@link WebClientException}. This can be overridden with
* {@link #onStatus(Predicate, Function)}.
* Calling this method will {@linkplain ClientResponse#releaseBody() release} the body of
* the response.
* @return {@code Mono} with the bodiless {@code ResponseEntity}
* @since 5.2
*/
Mono<ResponseEntity<Void>> toBodilessEntity();
@@ -66,6 +66,7 @@ public interface WebSocketSession {
* is closed. In a typical {@link WebSocketHandler} implementation this
* stream is composed into the overall processing flow, so that when the
* connection is closed, handling will end.
*
* <p>See the class-level doc of {@link WebSocketHandler} and the reference
* for more details and examples of how to handle the session.
*/
@@ -75,17 +76,12 @@ public interface WebSocketSession {
* Give a source of outgoing messages, write the messages and return a
* {@code Mono<Void>} that completes when the source completes and writing
* is done.
*
* <p>See the class-level doc of {@link WebSocketHandler} and the reference
* for more details and examples of how to handle the session.
*/
Mono<Void> send(Publisher<WebSocketMessage> messages);
/**
* Whether the underlying connection is open.
* @since 5.3.1
*/
boolean isOpen();
/**
* Close the WebSocket session with {@link CloseStatus#NORMAL}.
*/
@@ -113,11 +113,6 @@ public class JettyWebSocketSession extends AbstractListenerWebSocketSession<Sess
return true;
}
@Override
public boolean isOpen() {
return getDelegate().isOpen();
}
@Override
public Mono<Void> close(CloseStatus status) {
getDelegate().close(status.getCode(), status.getReason());
@@ -15,13 +15,10 @@
*/
package org.springframework.web.reactive.socket.adapter;
import java.util.function.Consumer;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.Connection;
import reactor.netty.NettyInbound;
import reactor.netty.NettyOutbound;
import reactor.netty.http.websocket.WebsocketInbound;
@@ -96,13 +93,6 @@ public class ReactorNettyWebSocketSession
.then();
}
@Override
public boolean isOpen() {
DisposedCallback callback = new DisposedCallback();
getDelegate().getInbound().withConnection(callback);
return callback.isDisposed();
}
@Override
public Mono<Void> close(CloseStatus status) {
// this will notify WebSocketInbound.receiveCloseStatus()
@@ -139,19 +129,4 @@ public class ReactorNettyWebSocketSession
}
}
private static class DisposedCallback implements Consumer<Connection> {
private boolean disposed;
public boolean isDisposed() {
return this.disposed;
}
@Override
public void accept(Connection connection) {
this.disposed = connection.isDisposed();
}
}
}
@@ -103,11 +103,6 @@ public class StandardWebSocketSession extends AbstractListenerWebSocketSession<S
return true;
}
@Override
public boolean isOpen() {
return getDelegate().isOpen();
}
@Override
public Mono<Void> close(CloseStatus status) {
try {
@@ -108,11 +108,6 @@ public class UndertowWebSocketSession extends AbstractListenerWebSocketSession<W
return true;
}
@Override
public boolean isOpen() {
return getDelegate().isOpen();
}
@Override
public Mono<Void> close(CloseStatus status) {
CloseMessage cm = new CloseMessage(status.getCode(), status.getReason());
@@ -129,34 +129,6 @@ public class DefaultWebClientTests {
assertThat(request.cookies().getFirst("id")).isEqualTo("123");
}
@Test
public void contextFromThreadLocal() {
WebClient client = this.builder
.filter((request, next) ->
// Async, continue on different thread
Mono.delay(Duration.ofMillis(10)).then(next.exchange(request)))
.filter((request, next) ->
Mono.deferContextual(contextView -> {
String fooValue = contextView.get("foo");
return next.exchange(ClientRequest.from(request).header("foo", fooValue).build());
}))
.build();
ThreadLocal<String> fooHolder = new ThreadLocal<>();
fooHolder.set("bar");
try {
client.get().uri("/path")
.context(context -> context.put("foo", fooHolder.get()))
.retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10));
}
finally {
fooHolder.remove();
}
ClientRequest request = verifyAndGetRequest();
assertThat(request.headers().getFirst("foo")).isEqualTo("bar");
}
@Test
public void httpRequest() {
this.builder.build().get().uri("/path")
@@ -170,8 +142,7 @@ public class DefaultWebClientTests {
@Test
public void defaultHeaderAndCookie() {
WebClient client = this.builder
.defaultHeader("Accept", "application/json")
.defaultCookie("id", "123")
.defaultHeader("Accept", "application/json").defaultCookie("id", "123")
.build();
client.get().uri("/path")
@@ -199,33 +170,6 @@ public class DefaultWebClientTests {
assertThat(request.cookies().getFirst("id")).isEqualTo("456");
}
@Test
public void defaultHeaderAndCookieCopies() {
WebClient client1 = this.builder
.defaultHeader("Accept", "application/json")
.defaultCookie("id", "123")
.build();
WebClient client2 = this.builder
.defaultHeader("Accept", "application/xml")
.defaultCookies(cookies -> cookies.set("id", "456"))
.build();
client1.get().uri("/path")
.retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10));
ClientRequest request = verifyAndGetRequest();
assertThat(request.headers().getFirst("Accept")).isEqualTo("application/json");
assertThat(request.cookies().getFirst("id")).isEqualTo("123");
client2.get().uri("/path")
.retrieve().bodyToMono(Void.class).block(Duration.ofSeconds(10));
request = verifyAndGetRequest();
assertThat(request.headers().getFirst("Accept")).isEqualTo("application/xml");
assertThat(request.cookies().getFirst("id")).isEqualTo("456");
}
@Test
public void defaultRequest() {
ThreadLocal<String> context = new NamedThreadLocal<>("foo");
@@ -279,7 +279,7 @@ class WebClientIntegrationTests {
}
@ParameterizedWebClientTest
void retrieveJsonArrayAsResponseEntityList(ClientHttpConnector connector) {
void retrieveJsonArrayAsResponseEntity(ClientHttpConnector connector) {
startServer(connector);
String content = "[{\"bar\":\"bar1\",\"foo\":\"foo1\"}, {\"bar\":\"bar2\",\"foo\":\"foo2\"}]";
@@ -309,39 +309,6 @@ class WebClientIntegrationTests {
});
}
@ParameterizedWebClientTest
void retrieveJsonArrayAsResponseEntityFlux(ClientHttpConnector connector) {
startServer(connector);
String content = "[{\"bar\":\"bar1\",\"foo\":\"foo1\"}, {\"bar\":\"bar2\",\"foo\":\"foo2\"}]";
prepareResponse(response -> response
.setHeader("Content-Type", "application/json").setBody(content));
ResponseEntity<Flux<Pojo>> entity = this.webClient.get()
.uri("/json").accept(MediaType.APPLICATION_JSON)
.retrieve()
.toEntityFlux(Pojo.class)
.block(Duration.ofSeconds(3));
assertThat(entity).isNotNull();
assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(entity.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_JSON);
assertThat(entity.getHeaders().getContentLength()).isEqualTo(58);
assertThat(entity.getBody()).isNotNull();
StepVerifier.create(entity.getBody())
.expectNext(new Pojo("foo1", "bar1"))
.expectNext(new Pojo("foo2", "bar2"))
.expectComplete()
.verify(Duration.ofSeconds(3));
expectRequestCount(1);
expectRequest(request -> {
assertThat(request.getPath()).isEqualTo("/json");
assertThat(request.getHeader(HttpHeaders.ACCEPT)).isEqualTo("application/json");
});
}
@Test // gh-24788
void retrieveJsonArrayAsBodilessEntityShouldReleasesConnection() {
@@ -0,0 +1,157 @@
/*
* Copyright 2002-2020 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
*
* https://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.web.reactive.function.server;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
/**
* @author Arjen Poutsma
*/
public class CustomRouteBuilder {
public static final String OPERATION_ATTRIBUTE = CustomRouteBuilder.class.getName() + ".operation";
private final RouterFunctions.Builder delegate = RouterFunctions.route();
private CustomRouteBuilder() {
}
public static CustomRouteBuilder route() {
return new CustomRouteBuilder();
}
public CustomRouteBuilder GET(String pattern, HandlerFunction<ServerResponse> handlerFunction,
Consumer<OperationBuilder> operationsConsumer) {
OperationBuilder builder = new OperationBuilder();
operationsConsumer.accept(builder);
this.delegate.GET(pattern, handlerFunction)
.withAttribute(OPERATION_ATTRIBUTE, builder.operation);
return this;
}
public RouterFunction<ServerResponse> build() {
return this.delegate.build();
}
public static void main(String[] args) {
RouterFunction<ServerResponse> routerFunction =
route()
.GET("/foo", request -> ServerResponse.ok().build(), ops -> ops
.parameter("key1", "My key1 description")
.parameter("key1", "My key1 description")
.response(200, "This is normal response description")
.response(404, "This is response description")
)
.build();
AttributesVisitor visitor = new AttributesVisitor();
routerFunction.accept(visitor);
}
public static class OperationBuilder {
private final Operation operation = new Operation();
public OperationBuilder parameter(String name, String description) {
this.operation.parameter(name, description);
return this;
}
public OperationBuilder response(int statusCode, String description) {
this.operation.response(statusCode, description);
return this;
}
}
static class Operation {
private final Map<String, String> parameters = new LinkedHashMap<>();
private final Map<Integer, String > responses = new LinkedHashMap<>();
public void parameter(String name, String description) {
this.parameters.put(name, description);
}
public void response(int status, String description) {
this.responses.put(status, description);
}
@Override
public String toString() {
return "parameters=" + parameters +
", responses=" + responses;
}
}
static class AttributesVisitor implements RouterFunctions.Visitor {
@Nullable
private Map<String, Object> attributes;
@Override
public void attributes(Map<String, Object> attributes) {
this.attributes = attributes;
}
@Override
public void route(RequestPredicate predicate, HandlerFunction<?> handlerFunction) {
System.out.printf("Route predicate %s->%s%nhas attributes %s", predicate, handlerFunction, this.attributes);
this.attributes = null;
}
@Override
public void startNested(RequestPredicate predicate) {
// TODO
}
@Override
public void endNested(RequestPredicate predicate) {
// TODO
}
@Override
public void resources(Function<ServerRequest, Mono<Resource>> lookupFunction) {
// TODO
}
@Override
public void unknown(RouterFunction<?> routerFunction) {
// TODO
}
}
}
@@ -162,7 +162,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
private static final boolean javaxValidationPresent;
private static final boolean romePresent;
private static boolean romePresent;
private static final boolean jaxb2Present;
@@ -19,11 +19,9 @@ package org.springframework.web.servlet.config;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.lang.Nullable;
@@ -45,7 +43,6 @@ import org.springframework.web.util.UrlPathHelper;
* Convenience methods for use in MVC namespace BeanDefinitionParsers.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Brian Clozel
* @author Marten Deinum
* @since 3.1
@@ -70,15 +67,15 @@ public abstract class MvcNamespaceUtils {
private static final String HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME = "mvcHandlerMappingIntrospector";
public static void registerDefaultComponents(ParserContext context, @Nullable Object source) {
registerBeanNameUrlHandlerMapping(context, source);
registerHttpRequestHandlerAdapter(context, source);
registerSimpleControllerHandlerAdapter(context, source);
registerHandlerMappingIntrospector(context, source);
registerLocaleResolver(context, source);
registerThemeResolver(context, source);
registerViewNameTranslator(context, source);
registerFlashMapManager(context, source);
public static void registerDefaultComponents(ParserContext parserContext, @Nullable Object source) {
registerBeanNameUrlHandlerMapping(parserContext, source);
registerHttpRequestHandlerAdapter(parserContext, source);
registerSimpleControllerHandlerAdapter(parserContext, source);
registerHandlerMappingIntrospector(parserContext, source);
registerThemeResolver(parserContext, source);
registerLocaleResolver(parserContext, source);
registerFlashMapManager(parserContext, source);
registerViewNameTranslator(parserContext, source);
}
/**
@@ -87,21 +84,21 @@ public abstract class MvcNamespaceUtils {
* @return a RuntimeBeanReference to this {@link UrlPathHelper} instance
*/
public static RuntimeBeanReference registerUrlPathHelper(
@Nullable RuntimeBeanReference urlPathHelperRef, ParserContext context, @Nullable Object source) {
@Nullable RuntimeBeanReference urlPathHelperRef, ParserContext parserContext, @Nullable Object source) {
if (urlPathHelperRef != null) {
if (context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) {
context.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME);
if (parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME)) {
parserContext.getRegistry().removeAlias(URL_PATH_HELPER_BEAN_NAME);
}
context.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME);
parserContext.getRegistry().registerAlias(urlPathHelperRef.getBeanName(), URL_PATH_HELPER_BEAN_NAME);
}
else if (!context.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
!context.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) {
else if (!parserContext.getRegistry().isAlias(URL_PATH_HELPER_BEAN_NAME) &&
!parserContext.getRegistry().containsBeanDefinition(URL_PATH_HELPER_BEAN_NAME)) {
RootBeanDefinition urlPathHelperDef = new RootBeanDefinition(UrlPathHelper.class);
urlPathHelperDef.setSource(source);
urlPathHelperDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef);
context.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(URL_PATH_HELPER_BEAN_NAME, urlPathHelperDef);
parserContext.registerComponent(new BeanComponentDefinition(urlPathHelperDef, URL_PATH_HELPER_BEAN_NAME));
}
return new RuntimeBeanReference(URL_PATH_HELPER_BEAN_NAME);
}
@@ -112,21 +109,21 @@ public abstract class MvcNamespaceUtils {
* @return a RuntimeBeanReference to this {@link PathMatcher} instance
*/
public static RuntimeBeanReference registerPathMatcher(@Nullable RuntimeBeanReference pathMatcherRef,
ParserContext context, @Nullable Object source) {
ParserContext parserContext, @Nullable Object source) {
if (pathMatcherRef != null) {
if (context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
context.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
if (parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME)) {
parserContext.getRegistry().removeAlias(PATH_MATCHER_BEAN_NAME);
}
context.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
parserContext.getRegistry().registerAlias(pathMatcherRef.getBeanName(), PATH_MATCHER_BEAN_NAME);
}
else if (!context.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
!context.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
else if (!parserContext.getRegistry().isAlias(PATH_MATCHER_BEAN_NAME) &&
!parserContext.getRegistry().containsBeanDefinition(PATH_MATCHER_BEAN_NAME)) {
RootBeanDefinition pathMatcherDef = new RootBeanDefinition(AntPathMatcher.class);
pathMatcherDef.setSource(source);
pathMatcherDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef);
context.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(PATH_MATCHER_BEAN_NAME, pathMatcherDef);
parserContext.registerComponent(new BeanComponentDefinition(pathMatcherDef, PATH_MATCHER_BEAN_NAME));
}
return new RuntimeBeanReference(PATH_MATCHER_BEAN_NAME);
}
@@ -207,28 +204,14 @@ public abstract class MvcNamespaceUtils {
* Registers an {@link HandlerMappingIntrospector} under a well-known name
* unless already registered.
*/
private static void registerHandlerMappingIntrospector(ParserContext context, @Nullable Object source) {
if (!context.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
private static void registerHandlerMappingIntrospector(ParserContext parserContext, @Nullable Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(HandlerMappingIntrospector.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
beanDef.setLazyInit(true);
context.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef);
context.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME));
}
}
/**
* Registers an {@link AcceptHeaderLocaleResolver} under a well-known name
* unless already registered.
*/
private static void registerLocaleResolver(ParserContext context, @Nullable Object source) {
if (!containsBeanInHierarchy(context, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(AcceptHeaderLocaleResolver.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, beanDef);
context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME, beanDef);
parserContext.registerComponent(new BeanComponentDefinition(beanDef, HANDLER_MAPPING_INTROSPECTOR_BEAN_NAME));
}
}
@@ -236,29 +219,27 @@ public abstract class MvcNamespaceUtils {
* Registers an {@link FixedThemeResolver} under a well-known name
* unless already registered.
*/
private static void registerThemeResolver(ParserContext context, @Nullable Object source) {
if (!containsBeanInHierarchy(context, DispatcherServlet.THEME_RESOLVER_BEAN_NAME)) {
private static void registerThemeResolver(ParserContext parserContext, @Nullable Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(FixedThemeResolver.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, beanDef);
context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.THEME_RESOLVER_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, beanDef);
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.THEME_RESOLVER_BEAN_NAME));
}
}
/**
* Registers an {@link DefaultRequestToViewNameTranslator} under a well-known name
* Registers an {@link AcceptHeaderLocaleResolver} under a well-known name
* unless already registered.
*/
private static void registerViewNameTranslator(ParserContext context, @Nullable Object source) {
if (!containsBeanInHierarchy(context, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(DefaultRequestToViewNameTranslator.class);
private static void registerLocaleResolver(ParserContext parserContext, @Nullable Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(AcceptHeaderLocaleResolver.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(
DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, beanDef);
context.registerComponent(
new BeanComponentDefinition(beanDef, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, beanDef);
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME));
}
}
@@ -266,13 +247,27 @@ public abstract class MvcNamespaceUtils {
* Registers an {@link SessionFlashMapManager} under a well-known name
* unless already registered.
*/
private static void registerFlashMapManager(ParserContext context, @Nullable Object source) {
if (!containsBeanInHierarchy(context, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)) {
private static void registerFlashMapManager(ParserContext parserContext, @Nullable Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(SessionFlashMapManager.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
context.getRegistry().registerBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, beanDef);
context.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME));
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, beanDef);
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME));
}
}
/**
* Registers an {@link DefaultRequestToViewNameTranslator} under a well-known name
* unless already registered.
*/
private static void registerViewNameTranslator(ParserContext parserContext, @Nullable Object source) {
if (!parserContext.getRegistry().containsBeanDefinition(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME)) {
RootBeanDefinition beanDef = new RootBeanDefinition(DefaultRequestToViewNameTranslator.class);
beanDef.setSource(source);
beanDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
parserContext.getRegistry().registerBeanDefinition(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, beanDef);
parserContext.registerComponent(new BeanComponentDefinition(beanDef, DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME));
}
}
@@ -295,16 +290,4 @@ public abstract class MvcNamespaceUtils {
return null;
}
/**
* Check for an existing bean of the given name, ideally in the entire
* context hierarchy (through a {@code containsBean} call) since this
* is also what {@code DispatcherServlet} does, or otherwise just in
* the local context (through {@code containsBeanDefinition}).
*/
private static boolean containsBeanInHierarchy(ParserContext context, String beanName) {
BeanDefinitionRegistry registry = context.getRegistry();
return (registry instanceof BeanFactory ? ((BeanFactory) registry).containsBean(beanName) :
registry.containsBeanDefinition(beanName));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -81,7 +81,7 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
private static final String RESOURCE_URL_PROVIDER = "mvcResourceUrlProvider";
private static final boolean webJarsPresent = ClassUtils.isPresent(
private static final boolean isWebJarsAssetLocatorPresent = ClassUtils.isPresent(
"org.webjars.WebJarAssetLocator", ResourcesBeanDefinitionParser.class.getClassLoader());
@@ -331,7 +331,7 @@ class ResourcesBeanDefinitionParser implements BeanDefinitionParser {
}
if (isAutoRegistration) {
if (webJarsPresent) {
if (isWebJarsAssetLocatorPresent) {
RootBeanDefinition webJarsResolverDef = new RootBeanDefinition(WebJarsResourceResolver.class);
webJarsResolverDef.setSource(source);
webJarsResolverDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 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.
@@ -284,7 +284,7 @@ public class ViewResolverRegistry {
protected List<ViewResolver> getViewResolvers() {
if (this.contentNegotiatingResolver != null) {
return Collections.singletonList(this.contentNegotiatingResolver);
return Collections.<ViewResolver>singletonList(this.contentNegotiatingResolver);
}
else {
return this.viewResolvers;
@@ -1,64 +0,0 @@
/*
* Copyright 2002-2020 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
*
* https://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.web.servlet.mvc.method.annotation;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
/**
* Resolves an argument of type {@link Principal}, similar to
* {@link ServletRequestMethodArgumentResolver} but irrespective of whether the
* argument is annotated or not. This is done to enable custom argument
* resolution of a {@link Principal} argument (with a custom annotation).
*
* @author Rossen Stoyanchev
* @since 5.3.1
*/
public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter parameter) {
return Principal.class.isAssignableFrom(parameter.getParameterType());
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request == null) {
throw new IllegalStateException("Current request is not of type HttpServletRequest: " + webRequest);
}
Principal principal = request.getUserPrincipal();
if (principal != null && !parameter.getParameterType().isInstance(principal)) {
throw new IllegalStateException("Current user principal is not of type [" +
parameter.getParameterType().getName() + "]: " + principal);
}
return principal;
}
}
@@ -715,7 +715,6 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
}
// Catch-all
resolvers.add(new PrincipalMethodArgumentResolver());
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
return resolvers;
@@ -50,9 +50,7 @@ import org.springframework.web.servlet.support.RequestContextUtils;
* <li>{@link MultipartRequest}
* <li>{@link HttpSession}
* <li>{@link PushBuilder} (as of Spring 5.0 on Servlet 4.0)
* <li>{@link Principal} but only if not annotated in order to allow custom
* resolvers to resolve it, and the falling back on
* {@link PrincipalMethodArgumentResolver}.
* <li>{@link Principal}
* <li>{@link InputStream}
* <li>{@link Reader}
* <li>{@link HttpMethod} (as of Spring 4.0)
@@ -398,7 +398,7 @@ public class RedirectView extends AbstractUrlBasedView implements SmartView {
private Map<String, String> getCurrentRequestUriVariables(HttpServletRequest request) {
String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
Map<String, String> uriVars = (Map<String, String>) request.getAttribute(name);
return (uriVars != null) ? uriVars : Collections.emptyMap();
return (uriVars != null) ? uriVars : Collections.<String, String> emptyMap();
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2019 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.
@@ -84,7 +84,7 @@ public class DispatcherServletTests {
@BeforeEach
public void setup() throws ServletException {
public void setUp() throws ServletException {
MockServletConfig complexConfig = new MockServletConfig(getServletContext(), "complex");
complexConfig.addInitParameter("publishContext", "false");
complexConfig.addInitParameter("class", "notWritable");
@@ -105,7 +105,6 @@ public class DispatcherServletTests {
return servletConfig.getServletContext();
}
@Test
public void configuredDispatcherServlets() {
assertThat(("simple" + FrameworkServlet.DEFAULT_NAMESPACE_SUFFIX).equals(simpleDispatcherServlet.getNamespace())).as("Correct namespace").isTrue();
@@ -50,7 +50,6 @@ import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.Resource;
@@ -101,7 +100,6 @@ import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import org.springframework.web.servlet.handler.MappedInterceptor;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
@@ -124,12 +122,9 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
import org.springframework.web.servlet.resource.ResourceUrlProviderExposingInterceptor;
import org.springframework.web.servlet.resource.VersionResourceResolver;
import org.springframework.web.servlet.resource.WebJarsResourceResolver;
import org.springframework.web.servlet.support.SessionFlashMapManager;
import org.springframework.web.servlet.theme.CookieThemeResolver;
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
import org.springframework.web.servlet.view.BeanNameViewResolver;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;
import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.RedirectView;
@@ -230,10 +225,10 @@ public class MvcNamespaceTests {
assertThat(appContext.getBean(ConversionService.class)).isNotNull();
assertThat(appContext.getBean(LocalValidatorFactoryBean.class)).isNotNull();
assertThat(appContext.getBean(Validator.class)).isNotNull();
assertThat(appContext.getBean("localeResolver", LocaleResolver.class)).isNotNull();
assertThat(appContext.getBean("themeResolver", ThemeResolver.class)).isNotNull();
assertThat(appContext.getBean("viewNameTranslator", RequestToViewNameTranslator.class)).isNotNull();
assertThat(appContext.getBean("localeResolver", LocaleResolver.class)).isNotNull();
assertThat(appContext.getBean("flashMapManager", FlashMapManager.class)).isNotNull();
assertThat(appContext.getBean("viewNameTranslator", RequestToViewNameTranslator.class)).isNotNull();
// default web binding initializer behavior test
request = new MockHttpServletRequest("GET", "/");
@@ -267,23 +262,6 @@ public class MvcNamespaceTests {
assertThat(introspector.getHandlerMappings().get(1).getClass()).isEqualTo(BeanNameUrlHandlerMapping.class);
}
@Test // gh-25290
public void testDefaultConfigWithBeansInParentContext() throws Exception {
StaticApplicationContext parent = new StaticApplicationContext();
parent.registerSingleton("localeResolver", CookieLocaleResolver.class);
parent.registerSingleton("themeResolver", CookieThemeResolver.class);
parent.registerSingleton("viewNameTranslator", DefaultRequestToViewNameTranslator.class);
parent.registerSingleton("flashMapManager", SessionFlashMapManager.class);
parent.refresh();
appContext.setParent(parent);
loadBeanDefinitions("mvc-config.xml");
assertThat(appContext.getBean("localeResolver")).isSameAs(parent.getBean("localeResolver"));
assertThat(appContext.getBean("themeResolver")).isSameAs(parent.getBean("themeResolver"));
assertThat(appContext.getBean("viewNameTranslator")).isSameAs(parent.getBean("viewNameTranslator"));
assertThat(appContext.getBean("flashMapManager")).isSameAs(parent.getBean("flashMapManager"));
}
@Test
public void testCustomConversionService() throws Exception {
loadBeanDefinitions("mvc-config-custom-conversion-service.xml");
@@ -1,110 +0,0 @@
/*
* Copyright 2002-2020 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
*
* https://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.web.servlet.mvc.method.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.security.Principal;
import javax.servlet.ServletRequest;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.testfixture.servlet.MockHttpServletRequest;
import org.springframework.web.testfixture.servlet.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Unit tests for {@link PrincipalMethodArgumentResolver}.
*
* @author Rossen Stoyanchev
*/
public class PrincipalMethodArgumentResolverTests {
private PrincipalMethodArgumentResolver resolver;
private ModelAndViewContainer mavContainer;
private MockHttpServletRequest servletRequest;
private ServletWebRequest webRequest;
private Method method;
@BeforeEach
public void setup() throws Exception {
resolver = new PrincipalMethodArgumentResolver();
mavContainer = new ModelAndViewContainer();
servletRequest = new MockHttpServletRequest("GET", "");
webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse());
method = getClass().getMethod("supportedParams", ServletRequest.class, Principal.class);
}
@Test
public void principal() throws Exception {
Principal principal = () -> "Foo";
servletRequest.setUserPrincipal(principal);
MethodParameter principalParameter = new MethodParameter(method, 1);
assertThat(resolver.supportsParameter(principalParameter)).as("Principal not supported").isTrue();
Object result = resolver.resolveArgument(principalParameter, null, webRequest, null);
assertThat(result).as("Invalid result").isSameAs(principal);
}
@Test
public void principalAsNull() throws Exception {
MethodParameter principalParameter = new MethodParameter(method, 1);
assertThat(resolver.supportsParameter(principalParameter)).as("Principal not supported").isTrue();
Object result = resolver.resolveArgument(principalParameter, null, webRequest, null);
assertThat(result).as("Invalid result").isNull();
}
@Test // gh-25780
public void annotatedPrincipal() throws Exception {
Principal principal = () -> "Foo";
servletRequest.setUserPrincipal(principal);
Method principalMethod = getClass().getMethod("supportedParamsWithAnnotatedPrincipal", Principal.class);
MethodParameter principalParameter = new MethodParameter(principalMethod, 0);
assertThat(resolver.supportsParameter(principalParameter)).isTrue();
}
@SuppressWarnings("unused")
public void supportedParams(ServletRequest p0, Principal p1) {}
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthenticationPrincipal {}
@SuppressWarnings("unused")
public void supportedParamsWithAnnotatedPrincipal(@AuthenticationPrincipal Principal p) {}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2017 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.
@@ -120,6 +120,7 @@ public interface WebSocketSession extends Closeable {
/**
* Send a WebSocket message: either {@link TextMessage} or {@link BinaryMessage}.
*
* <p><strong>Note:</strong> The underlying standard WebSocket session (JSR-356) does
* not allow concurrent sending. Therefore sending must be synchronized. To ensure
* that, one option is to wrap the {@code WebSocketSession} with the
@@ -130,7 +131,7 @@ public interface WebSocketSession extends Closeable {
void sendMessage(WebSocketMessage<?> message) throws IOException;
/**
* Whether the underlying connection is open.
* Return whether the connection is still open.
*/
boolean isOpen();
@@ -27,7 +27,6 @@ import org.springframework.context.event.SmartApplicationListener;
import org.springframework.core.Ordered;
import org.springframework.lang.Nullable;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.user.DestinationUserNameProvider;
import org.springframework.messaging.simp.user.SimpSession;
@@ -35,6 +34,7 @@ import org.springframework.messaging.simp.user.SimpSubscription;
import org.springframework.messaging.simp.user.SimpSubscriptionMatcher;
import org.springframework.messaging.simp.user.SimpUser;
import org.springframework.messaging.simp.user.SimpUserRegistry;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.util.Assert;
/**
@@ -84,16 +84,19 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
public void onApplicationEvent(ApplicationEvent event) {
AbstractSubProtocolEvent subProtocolEvent = (AbstractSubProtocolEvent) event;
Message<?> message = subProtocolEvent.getMessage();
MessageHeaders headers = message.getHeaders();
String sessionId = SimpMessageHeaderAccessor.getSessionId(headers);
SimpMessageHeaderAccessor accessor =
MessageHeaderAccessor.getAccessor(message, SimpMessageHeaderAccessor.class);
Assert.state(accessor != null, "No SimpMessageHeaderAccessor");
String sessionId = accessor.getSessionId();
Assert.state(sessionId != null, "No session id");
if (event instanceof SessionSubscribeEvent) {
LocalSimpSession session = this.sessions.get(sessionId);
if (session != null) {
String id = SimpMessageHeaderAccessor.getSubscriptionId(headers);
String destination = SimpMessageHeaderAccessor.getDestination(headers);
String id = accessor.getSubscriptionId();
String destination = accessor.getDestination();
if (id != null && destination != null) {
session.addSubscription(id, destination);
}
@@ -134,7 +137,7 @@ public class DefaultSimpUserRegistry implements SimpUserRegistry, SmartApplicati
else if (event instanceof SessionUnsubscribeEvent) {
LocalSimpSession session = this.sessions.get(sessionId);
if (session != null) {
String subscriptionId = SimpMessageHeaderAccessor.getSubscriptionId(headers);
String subscriptionId = accessor.getSubscriptionId();
if (subscriptionId != null) {
session.removeSubscription(subscriptionId);
}
@@ -269,15 +269,13 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE
}
for (Message<byte[]> message : messages) {
StompHeaderAccessor headerAccessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
Assert.state(headerAccessor != null, "No StompHeaderAccessor");
StompCommand command = headerAccessor.getCommand();
boolean isConnect = StompCommand.CONNECT.equals(command) || StompCommand.STOMP.equals(command);
boolean sent = false;
try {
StompHeaderAccessor headerAccessor =
MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
Assert.state(headerAccessor != null, "No StompHeaderAccessor");
StompCommand command = headerAccessor.getCommand();
boolean isConnect = StompCommand.CONNECT.equals(command) || StompCommand.STOMP.equals(command);
headerAccessor.setSessionId(session.getId());
headerAccessor.setSessionAttributes(session.getAttributes());
@@ -307,7 +305,7 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE
try {
SimpAttributesContextHolder.setAttributesFromMessage(message);
sent = outputChannel.send(message);
boolean sent = outputChannel.send(message);
if (sent) {
if (this.eventPublisher != null) {
@@ -329,15 +327,9 @@ public class StompSubProtocolHandler implements SubProtocolHandler, ApplicationE
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to send message to MessageChannel in session " + session.getId(), ex);
}
else if (logger.isErrorEnabled()) {
// Skip unsent CONNECT messages (likely auth issues)
if (!isConnect || sent) {
logger.error("Failed to send message to MessageChannel in session " + session.getId() +
":" + ex.getMessage());
}
if (logger.isErrorEnabled()) {
logger.error("Failed to send client message to application via MessageChannel" +
" in session " + session.getId() + ". Sending STOMP ERROR to client.", ex);
}
handleError(session, ex, message);
}
+1 -1
View File
@@ -563,7 +563,7 @@ files named `services.xml` and `daos.xml` (which are on the classpath) can be in
val ctx = ClassPathXmlApplicationContext(arrayOf("services.xml", "daos.xml"), MessengerService::class.java)
----
See the {api-spring-framework}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
See the api-spring-framework}/context/support/ClassPathXmlApplicationContext.html[`ClassPathXmlApplicationContext`]
javadoc for details on the various constructors.
+1 -1
View File
@@ -6805,7 +6805,7 @@ The following example extracts the `id` column and emits its value:
.Kotlin
----
val names = client.sql("SELECT name FROM person")
.map{ row: Row -> row.get("id", String.class) }
.map{ it.get("id", String.class) }
.flow()
----
+25 -68
View File
@@ -102,7 +102,7 @@ To change the limit for default codecs, use the following:
.Java
----
WebClient webClient = WebClient.builder()
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024))
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(2 * 1024 * 1024));
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@@ -831,7 +831,7 @@ inline-style, through the built-in `BodyInserters`, as the following example sho
[[webflux-client-filter]]
== Filters
== Client Filters
You can register a client filter (`ExchangeFilterFunction`) through the `WebClient.Builder`
in order to intercept and modify requests, as the following example shows:
@@ -887,36 +887,9 @@ a filter for basic authentication through a static factory method:
.build()
----
You can create a new `WebClient` instance by using another as a starting point. This allows
insert or removing filters without affecting the original `WebClient`. Below is an example
that inserts a basic authentication filter at index 0:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val client = webClient.mutate()
.filters { it.add(0, basicAuthentication("user", "password")) }
.build()
----
[[webflux-client-attributes]]
== Attributes
You can add attributes to a request. This is convenient if you want to pass information
through the filter chain and influence the behavior of filters for a given request.
For example:
Filters apply globally to every request. To change a filter's behavior for a specific
request, you can add request attributes to the `ClientRequest` that can then be accessed
by all filters in the chain, as the following example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
@@ -939,11 +912,10 @@ For example:
.Kotlin
----
val client = WebClient.builder()
.filter { request, _ ->
val usr = request.attributes()["myAttribute"];
// ...
}
.build()
.filter { request, _ ->
val usr = request.attributes()["myAttribute"];
// ...
}.build()
client.get().uri("https://example.org/")
.attribute("myAttribute", "...")
@@ -951,44 +923,29 @@ For example:
.awaitBody<Unit>()
----
[[webflux-client-context]]
== Context
<<webflux-client-attributes>> provide a convenient way to pass information to the filter
chain but they only influence the current request. If you want to pass information that
propagates to additional requests that are nested, e.g. via `flatMap`, or executed after,
e.g. via `concatMap`, then you'll need to use the Reactor `Context`.
`WebClient` exposes a method to populate the Reactor `Context` for a given request.
This information is available to filters for the current request and it also propagates
to subsequent requests or other reactive clients participating in the downstream
processing chain. For example:
You can also replicate an existing `WebClient`, insert new filters, or remove already
registered filters. The following example, inserts a basic authentication filter at
index 0:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
WebClient client = WebClient.builder()
.filter((request, next) ->
Mono.deferContextual(contextView -> {
String value = contextView.get("foo");
// ...
}))
.build();
import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
client.get().uri("https://example.org/")
.context(context -> context.put("foo", ...))
.retrieve()
.bodyToMono(String.class)
.flatMap(body -> {
// perform nested request (context propagates automatically)...
});
WebClient client = webClient.mutate()
.filters(filterList -> {
filterList.add(0, basicAuthentication("user", "password"));
})
.build();
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
----
val client = webClient.mutate()
.filters { it.add(0, basicAuthentication("user", "password")) }
.build()
----
Note that you can also specify how to populate the context through the `defaultRequest`
method at the level of the `WebClient.Builder` and that applies to all requests.
This could be used for to example to pass information from `ThreadLocal` storage onto
a Reactor processing chain in a Spring MVC application.
[[webflux-client-synchronous]]