Compare commits

...

50 Commits

Author SHA1 Message Date
Spring Builds 925b078260 Release v5.2.19.RELEASE 2021-12-16 09:25:18 +00:00
Stephane Nicoll 1a03ffef62 Upgrade to Log4j2 2.16.0 2021-12-15 09:49:30 +01:00
Juergen Hoeller e954d7fc20 Upgrade to AspectJ 1.9.7, Groovy 2.5.15, BlockHound 1.0.6, SLF4J 1.7.32 2021-12-14 18:19:49 +01:00
Juergen Hoeller ec13a3f1ba Backported clarifications for SchedulerFactoryBean and cache annotations
See gh-27709
See gh-27726
2021-12-14 18:19:17 +01:00
Juergen Hoeller 842f5b354d Declare serialVersionUID on DefaultAopProxyFactory
Closes gh-27784
2021-12-14 18:17:03 +01:00
Juergen Hoeller 67a4b63d42 Avoid NPE against null value from toString call
Closes gh-27782
2021-12-14 18:16:52 +01:00
Juergen Hoeller 354dad615b Avoid early initialization of empty interceptor names
Closes gh-12238
2021-12-14 18:16:35 +01:00
Stephane Nicoll 0668323176 Upgrade to Log4j2 2.15.0 2021-12-10 15:12:57 +01:00
Sam Brannen e03e62c76a Test status quo for null in varargs in SpEL expressions
This commit also points out that `null` supplied as a single value for
a varargs array of type Optional will be kept as `null` instead of being
converted to Optional.empty(); whereas, if more than one value is passed
to such a varargs array a null value will be properly converted to
Optional.empty().

See gh-27719
2021-12-10 11:18:10 +01:00
Rossen Stoyanchev 96a7fc693b Apply LogFormatUtils in more places 2021-12-09 16:07:20 +00:00
Rossen Stoyanchev 50f6db2704 Replace both EOL and control characters 2021-12-08 13:41:45 +00:00
Arjen Poutsma 24ebb5ecd5 Use ByteArrayDecoder in DefaultClientResponse::createException
This commit changes DefaultClientResponse::createException to use
the ByteArrayDecoder, instead of converting to DataBuffers and
turning these into a byte array.

Closes gh-27666
2021-11-10 17:09:55 +01:00
Juergen Hoeller a439a83f34 Polishing 2021-11-09 23:01:46 +01:00
Juergen Hoeller f0de3a9924 Recommend ObjectProvider as alternative to @Lazy for optional dependencies
Closes gh-27649
2021-11-09 23:01:31 +01:00
Stephane Nicoll 17c3976bca Upgrade to Reactor Dysprosium-SR25
Closes gh-27635
2021-11-09 19:40:59 +01:00
Sam Brannen 9c4a33e64c Increase the likelihood that timed tests pass on CI server 2021-11-05 13:29:06 +01:00
Stephane Nicoll dd3ae4e63d Start building against Reactor Dysprosium-SR25 snapshots
See gh-27635
2021-11-04 14:51:52 +01:00
Sam Brannen dfc435d045 Return previous value in UndertowHeadersAdapter's remove() method
Prior to this commit, UndertowHeadersAdapter's remove() method violated
the java.util.Map contract by always returning null.

This commit fixes this by returning the previous list stored under the
specified key, and otherwise returning null if no previous value was
present.

Closes gh-27592
2021-10-22 14:59:57 +02:00
Sam Brannen 78a24c0dd1 Update "Reporting a Vulnerability" link 2021-10-20 14:50:19 +02:00
Sam Brannen 2bec54e7b3 Fix Javadoc in [NamedParameter]JdbcOperations.queryForObject methods
This commit fixes the Javadoc in all queryForObject(...) methods in
JdbcOperations and NamedParameterJdbcOperations regarding what kinds of
exceptions are thrown under which conditions.

Closes gh-27559

(cherry picked from commit 0853baaa3f)
2021-10-19 18:38:17 +02:00
Rossen Stoyanchev 52e2953de9 Fix checkstyle warning
See gh-27569
2021-10-18 17:09:46 +01:00
Rossen Stoyanchev 7b149a68c6 Fix assertion message in DefaultDataBuffer
Closes gh-27567
2021-10-18 16:57:12 +01:00
Smile d1b3b8eaf6 Update Javadoc in DefaultResponseErrorHandler
Closes gh-27569
2021-10-18 16:28:19 +01:00
Spring Builds f94794e5c0 Next development version (v5.2.19.BUILD-SNAPSHOT) 2021-10-14 10:25:19 +00:00
Rossen Stoyanchev 26af506441 DefaultResponseErrorHandler shows full error details
Closes gh-27552
2021-10-13 21:21:38 +01:00
Juergen Hoeller b14a940716 Polishing 2021-10-13 13:00:53 +02:00
Juergen Hoeller 4ecbf6511d Remove dead fallback code 2021-10-13 12:52:38 +02:00
Juergen Hoeller 5b6a32d1b9 Polishing 2021-10-12 16:18:41 +02:00
Juergen Hoeller 263244f637 Avoid expensive isReadable() check during classpath scan
Closes gh-27541
See gh-21372
2021-10-12 16:18:21 +02:00
Juergen Hoeller 3a166ea742 Add efficient existence check to ClassPathResource.isReadable()
Includes reduced isReadable() check in PathResourceLookupFunction, aligned with PathResourceResolver.

Closes gh-27538
See gh-21372
2021-10-12 16:18:09 +02:00
Sam Brannen 6d4dfed772 Introduce test for gh-27499 and polish contribution 2021-10-12 13:19:13 +02:00
Nick 09ef597836 Avoid duplicate JCacheOperationSource bean registration in <cache:annotation-driven />
In our application we use XML context and <cache:annotation-driven />
declaration. Also we disable bean definition duplication by setting
GenericApplicationContext.setAllowBeanDefinitionOverriding(false) in an
ApplicationContextInitializer. This combination leads to a
BeanDefinitionOverrideException because the
DefaultJCacheOperationSource bean is registered twice.

 - once for: parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
 - once for: parserContext.registerBeanComponent(new BeanComponentDefinition(sourceDef, sourceName));

This commit refactors JCacheCachingConfigurer.registerCacheAspect(...)
so that the JCacheOperationSource bean is registered only once.

Closes gh-27499
2021-10-12 13:19:13 +02:00
Sam Brannen 50920e1a96 Upgrade to Reactor Dysprosium-SR24
Closes gh-27526
2021-10-12 13:19:13 +02:00
Stephane Nicoll 8c4d1d4d29 Upgrade CI to github-release resource 1.5.5
Closes gh-27460
2021-10-11 16:20:35 +02:00
Rossen Stoyanchev 82f8d0dfd7 Apply value formatting to resolved exceptions 2021-10-11 11:15:59 +01:00
Stephane Nicoll 38f616a8e8 Start building against Reactor Dysprosium-SR23 snapshots
See gh-27526
2021-10-07 08:52:40 +02:00
Rossen Stoyanchev 441c4d5d5c Generalize formatValue
Provide an overload for additional control and compact output.
2021-10-06 21:31:02 +01:00
Sam Brannen 66f4d20fd4 Comment out unused fudgeFactor 2021-10-05 14:22:51 +02:00
Rossen Stoyanchev 2fa44d62b9 ResponseStatusExceptionHandler logs exception message only
No need to log the full exception for a resolved exception, and as the
comment suggests that it mirrors Spring MVC equivalent, which also
logs the message only.
2021-10-05 12:13:39 +01:00
Stephane Nicoll c86ac97267 Upgrade to GitHub Changelog Generator 0.0.7 in CI
Closes gh-27513
2021-10-03 07:10:06 +02:00
Juergen Hoeller fd916baf79 Skip all flaky StopWatch time assertions 2021-10-02 12:09:01 +02:00
Juergen Hoeller 790d56dd18 Backported tests for bean-derived (Auto)Closeable.close() method
See gh-27504
2021-10-01 13:47:58 +02:00
Sam Brannen 5a5f118efc Escape closing curly braces in regular expressions for Android support
PR gh-24470 introduced a regression for Android users by no longer
escaping closing curly braces in regular expressions.

This commit therefore partially reverts the changes made in 273812f9c5
for closing curly braces (`}`).

Closes gh27467
2021-10-01 10:56:00 +02:00
Juergen Hoeller edf664c1cf Defensively handle fast class generation failure for individual methods
Includes rethrowing of last actual defineClass exception encountered.

Closes gh-27490
2021-09-30 18:00:10 +02:00
Juergen Hoeller 137bca129f Skip flaky StopWatch time assertions 2021-09-27 17:43:25 +02:00
Juergen Hoeller b01a3aeb6b Fix invalid characters in source files
Closes gh-27475
2021-09-27 17:42:27 +02:00
Juergen Hoeller 195d375964 Polishing 2021-09-24 13:49:48 +02:00
Juergen Hoeller ebd351753f Fix contract violations in ConcurrentReferenceHashMap's EntrySet/Iterator
Closes gh-27454

(cherry picked from commit 208fafa4a3)
2021-09-24 13:41:35 +02:00
Juergen Hoeller 9524ecd5b0 Log rejected listener container tasks at warn level
Closes gh-27451
2021-09-24 13:30:50 +02:00
Spring Builds 9053613a65 Next development version (v5.2.18.BUILD-SNAPSHOT) 2021-09-15 08:10:53 +00:00
92 changed files with 946 additions and 699 deletions
+1 -1
View File
@@ -8,4 +8,4 @@ wiki page.
## Reporting a Vulnerability
Please see https://pivotal.io/security.
Please see https://spring.io/security-policy.
+9 -9
View File
@@ -2,14 +2,14 @@ plugins {
id 'io.spring.dependency-management' version '1.0.9.RELEASE' apply false
id 'io.spring.ge.conventions' version '0.0.7'
id 'io.spring.nohttp' version '0.0.5.RELEASE'
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
id "io.freefair.aspectj" version '4.1.6' apply false
id 'org.jetbrains.dokka' version '0.10.1' apply false
id 'org.jetbrains.kotlin.jvm' version '1.3.72' apply false
id 'org.asciidoctor.jvm.convert' version '2.4.0'
id 'org.asciidoctor.jvm.pdf' version '2.4.0'
id 'de.undercouch.download' version '4.1.1'
id "io.freefair.aspectj" version '4.1.6' apply false
id "com.github.ben-manes.versions" version '0.28.0'
id 'com.gradle.build-scan' version '3.2'
id 'de.undercouch.download' version '4.1.1'
}
apply from: "$rootDir/gradle/build-scan-user-data.gradle"
@@ -28,7 +28,7 @@ configure(allprojects) { project ->
imports {
mavenBom "com.fasterxml.jackson:jackson-bom:2.10.5"
mavenBom "io.netty:netty-bom:4.1.51.Final"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR23"
mavenBom "io.projectreactor:reactor-bom:Dysprosium-SR25"
mavenBom "io.rsocket:rsocket-bom:1.0.4"
mavenBom "org.eclipse.jetty:jetty-bom:9.4.31.v20200723"
mavenBom "org.jetbrains.kotlin:kotlin-bom:1.3.72"
@@ -36,21 +36,21 @@ configure(allprojects) { project ->
mavenBom "org.junit:junit-bom:5.6.3"
}
dependencies {
dependencySet(group: 'org.apache.logging.log4j', version: '2.13.3') {
dependencySet(group: 'org.apache.logging.log4j', version: '2.16.0') {
entry 'log4j-api'
entry 'log4j-core'
entry 'log4j-jul'
entry 'log4j-slf4j-impl'
}
dependency "org.slf4j:slf4j-api:1.7.30"
dependency "org.slf4j:slf4j-api:1.7.32"
dependency "com.google.code.findbugs:jsr305:3.0.2"
dependencySet(group: 'org.aspectj', version: '1.9.6') {
dependencySet(group: 'org.aspectj', version: '1.9.7') {
entry 'aspectjrt'
entry 'aspectjtools'
entry 'aspectjweaver'
}
dependencySet(group: 'org.codehaus.groovy', version: '2.5.13') {
dependencySet(group: 'org.codehaus.groovy', version: '2.5.15') {
entry 'groovy'
entry 'groovy-jsr223'
entry 'groovy-templates'
@@ -61,7 +61,7 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava:1.3.8"
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.21"
dependency "io.projectreactor.tools:blockhound:1.0.4.RELEASE"
dependency "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
dependency "com.caucho:hessian:4.0.63"
dependency "com.fasterxml:aalto-xml:1.2.2"
+5
View File
@@ -49,6 +49,11 @@ resource_types:
source:
repository: springio/artifactory-resource
tag: 0.0.13
- name: github-release
type: registry-image
source:
repository: concourse/github-release-resource
tag: 1.5.5
- name: github-status-resource
type: registry-image
source:
+1 -1
View File
@@ -4,7 +4,7 @@ image_resource:
type: registry-image
source:
repository: springio/github-changelog-generator
tag: '0.0.6'
tag: '0.0.7'
inputs:
- name: git-repo
- name: artifactory-repo
+1 -1
View File
@@ -1,4 +1,4 @@
version=5.2.17.BUILD-SNAPSHOT
version=5.2.19.RELEASE
org.gradle.jvmargs=-Xmx1536M
org.gradle.caching=true
org.gradle.parallel=true
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -237,7 +237,7 @@ class CglibAopProxy implements AopProxy, Serializable {
* validates it if not.
*/
private void validateClassIfNecessary(Class<?> proxySuperClass, @Nullable ClassLoader proxyClassLoader) {
if (logger.isWarnEnabled()) {
if (logger.isInfoEnabled()) {
synchronized (validatedClasses) {
if (!validatedClasses.containsKey(proxySuperClass)) {
doValidateClass(proxySuperClass, proxyClassLoader,
@@ -679,13 +679,19 @@ class CglibAopProxy implements AopProxy, Serializable {
Object retVal;
// Check whether we only have one InvokerInterceptor: that is,
// no real advice, but just reflective invocation of the target.
if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) {
// We can skip creating a MethodInvocation: just invoke the target directly.
// Note that the final invoker must be an InvokerInterceptor, so we know
// it does nothing but a reflective operation on the target, and no hot
// swapping or fancy proxying.
Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
retVal = methodProxy.invoke(target, argsToUse);
try {
retVal = methodProxy.invoke(target, argsToUse);
}
catch (CodeGenerationException ex) {
CglibMethodInvocation.logFastClassGenerationFailure(method);
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
}
}
else {
// We need to create a method invocation...
@@ -737,10 +743,7 @@ class CglibAopProxy implements AopProxy, Serializable {
super(proxy, target, method, arguments, targetClass, interceptorsAndDynamicMethodMatchers);
// Only use method proxy for public methods not derived from java.lang.Object
this.methodProxy = (Modifier.isPublic(method.getModifiers()) &&
method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method) ?
methodProxy : null);
this.methodProxy = (isMethodProxyCompatible(method) ? methodProxy : null);
}
@Override
@@ -776,10 +779,25 @@ class CglibAopProxy implements AopProxy, Serializable {
@Override
protected Object invokeJoinpoint() throws Throwable {
if (this.methodProxy != null) {
return this.methodProxy.invoke(this.target, this.arguments);
try {
return this.methodProxy.invoke(this.target, this.arguments);
}
catch (CodeGenerationException ex) {
logFastClassGenerationFailure(this.method);
}
}
else {
return super.invokeJoinpoint();
return super.invokeJoinpoint();
}
static boolean isMethodProxyCompatible(Method method) {
return (Modifier.isPublic(method.getModifiers()) &&
method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) &&
!AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method));
}
static void logFastClassGenerationFailure(Method method) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to generate CGLIB fast class for method: " + method);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2021 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,9 +43,11 @@ import org.springframework.aop.SpringProxy;
* @see AdvisedSupport#setProxyTargetClass
* @see AdvisedSupport#setInterfaces
*/
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
private static final long serialVersionUID = 7930414337282325166L;
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -421,11 +421,7 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
* are unaffected by such changes.
*/
private synchronized void initializeAdvisorChain() throws AopConfigException, BeansException {
if (this.advisorChainInitialized) {
return;
}
if (!ObjectUtils.isEmpty(this.interceptorNames)) {
if (!this.advisorChainInitialized && !ObjectUtils.isEmpty(this.interceptorNames)) {
if (this.beanFactory == null) {
throw new IllegalStateException("No BeanFactory available anymore (probably due to serialization) " +
"- cannot resolve interceptor names " + Arrays.asList(this.interceptorNames));
@@ -464,9 +460,9 @@ public class ProxyFactoryBean extends ProxyCreatorSupport
addAdvisorOnChainCreation(advice);
}
}
}
this.advisorChainInitialized = true;
this.advisorChainInitialized = true;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2021 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.
@@ -22,13 +22,18 @@ import org.springframework.contextsupport.testfixture.jcache.AbstractJCacheAnnot
/**
* @author Stephane Nicoll
* @author Sam Brannen
*/
public class JCacheAspectJNamespaceConfigTests extends AbstractJCacheAnnotationTests {
@Override
protected ApplicationContext getApplicationContext() {
return new GenericXmlApplicationContext(
"/org/springframework/cache/config/annotation-jcache-aspectj.xml");
GenericXmlApplicationContext context = new GenericXmlApplicationContext();
// Disallow bean definition overriding to test https://github.com/spring-projects/spring-framework/pull/27499
context.setAllowBeanDefinitionOverriding(false);
context.load("/org/springframework/cache/config/annotation-jcache-aspectj.xml");
context.refresh();
return context;
}
}
@@ -459,7 +459,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
return metadata;
}
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -350,7 +350,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
else {
String scopeName = mbd.getScope();
if (!StringUtils.hasLength(scopeName)) {
throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");
throw new IllegalStateException("No scope name defined for bean '" + beanName + "'");
}
Scope scope = this.scopes.get(scopeName);
if (scope == null) {
@@ -62,6 +62,8 @@ import org.springframework.util.StringUtils;
@SuppressWarnings("serial")
class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
private static final String DESTROY_METHOD_NAME = "destroy";
private static final String CLOSE_METHOD_NAME = "close";
private static final String SHUTDOWN_METHOD_NAME = "shutdown";
@@ -104,13 +106,16 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
Assert.notNull(bean, "Disposable bean must not be null");
this.bean = bean;
this.beanName = beanName;
this.invokeDisposableBean =
(this.bean instanceof DisposableBean && !beanDefinition.isExternallyManagedDestroyMethod("destroy"));
this.invokeDisposableBean = (bean instanceof DisposableBean &&
!beanDefinition.isExternallyManagedDestroyMethod(DESTROY_METHOD_NAME));
this.nonPublicAccessAllowed = beanDefinition.isNonPublicAccessAllowed();
this.acc = acc;
String destroyMethodName = inferDestroyMethodIfNecessary(bean, beanDefinition);
if (destroyMethodName != null && !(this.invokeDisposableBean && "destroy".equals(destroyMethodName)) &&
if (destroyMethodName != null &&
!(this.invokeDisposableBean && DESTROY_METHOD_NAME.equals(destroyMethodName)) &&
!beanDefinition.isExternallyManagedDestroyMethod(destroyMethodName)) {
this.destroyMethodName = destroyMethodName;
Method destroyMethod = determineDestroyMethod(destroyMethodName);
if (destroyMethod == null) {
@@ -135,6 +140,7 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
}
this.destroyMethod = destroyMethod;
}
this.beanPostProcessors = filterPostProcessors(postProcessors, bean);
}
@@ -349,8 +355,8 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable {
destroyMethodName = beanDefinition.getDestroyMethodName();
if (AbstractBeanDefinition.INFER_METHOD.equals(destroyMethodName) ||
(destroyMethodName == null && bean instanceof AutoCloseable)) {
// Only perform destroy method inference or Closeable detection
// in case of the bean not explicitly implementing DisposableBean
// Only perform destroy method inference in case of the bean
// not explicitly implementing the DisposableBean interface
destroyMethodName = null;
if (!(bean instanceof DisposableBean)) {
try {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -64,24 +64,24 @@ public final class ParserContext {
}
public final XmlReaderContext getReaderContext() {
public XmlReaderContext getReaderContext() {
return this.readerContext;
}
public final BeanDefinitionRegistry getRegistry() {
public BeanDefinitionRegistry getRegistry() {
return this.readerContext.getRegistry();
}
public final BeanDefinitionParserDelegate getDelegate() {
public BeanDefinitionParserDelegate getDelegate() {
return this.delegate;
}
@Nullable
public final BeanDefinition getContainingBeanDefinition() {
public BeanDefinition getContainingBeanDefinition() {
return this.containingBeanDefinition;
}
public final boolean isNested() {
public boolean isNested() {
return (this.containingBeanDefinition != null);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -58,6 +58,8 @@ import org.springframework.lang.Nullable;
* @since 1.1
* @see SchedulerFactoryBean#setDataSource
* @see SchedulerFactoryBean#setNonTransactionalDataSource
* @see SchedulerFactoryBean#getConfigTimeDataSource()
* @see SchedulerFactoryBean#getConfigTimeNonTransactionalDataSource()
* @see org.springframework.jdbc.datasource.DataSourceUtils#doGetConnection
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -313,6 +313,7 @@ public class SchedulerFactoryBean extends SchedulerAccessor implements FactoryBe
* If set, this will override corresponding settings in Quartz properties.
* <p>Note: If this is set, the Quartz settings should not define
* a job store "dataSource" to avoid meaningless double configuration.
* Also, do not define a "org.quartz.jobStore.class" property at all.
* <p>A Spring-specific subclass of Quartz' JobStoreCMT will be used.
* It is therefore strongly recommended to perform all operations on
* the Scheduler within Spring-managed (or plain JTA) transactions.
+1 -1
View File
@@ -34,8 +34,8 @@ dependencies {
testCompile("org.codehaus.groovy:groovy-test")
testCompile("org.codehaus.groovy:groovy-xml")
testCompile("org.apache.commons:commons-pool2")
testCompile("javax.inject:javax.inject-tck")
testCompile("org.awaitility:awaitility")
testCompile("javax.inject:javax.inject-tck")
testRuntime("javax.xml.bind:jaxb-api")
testRuntime("org.glassfish:javax.el")
// Substitute for javax.management:jmxremote_optional:1.0.1_04 (not available on Maven Central)
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -244,17 +244,21 @@ class AnnotationDrivenCacheBeanDefinitionParser implements BeanDefinitionParser
private static void registerCacheAspect(Element element, ParserContext parserContext) {
if (!parserContext.getRegistry().containsBeanDefinition(CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME)) {
Object eleSource = parserContext.extractSource(element);
RootBeanDefinition def = new RootBeanDefinition();
def.setBeanClassName(JCACHE_ASPECT_CLASS_NAME);
def.setFactoryMethodName("aspectOf");
BeanDefinition sourceDef = createJCacheOperationSourceBeanDefinition(element, eleSource);
String sourceName =
parserContext.getReaderContext().registerWithGeneratedName(sourceDef);
def.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(sourceName));
Object source = parserContext.extractSource(element);
parserContext.registerBeanComponent(new BeanComponentDefinition(sourceDef, sourceName));
parserContext.registerBeanComponent(new BeanComponentDefinition(def, CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME));
BeanDefinition cacheOperationSourceDef = createJCacheOperationSourceBeanDefinition(element, source);
String cacheOperationSourceName = parserContext.getReaderContext().registerWithGeneratedName(cacheOperationSourceDef);
RootBeanDefinition jcacheAspectDef = new RootBeanDefinition();
jcacheAspectDef.setBeanClassName(JCACHE_ASPECT_CLASS_NAME);
jcacheAspectDef.setFactoryMethodName("aspectOf");
jcacheAspectDef.getPropertyValues().add("cacheOperationSource", new RuntimeBeanReference(cacheOperationSourceName));
parserContext.getRegistry().registerBeanDefinition(CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME, jcacheAspectDef);
CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), source);
compositeDef.addNestedComponent(new BeanComponentDefinition(cacheOperationSourceDef, cacheOperationSourceName));
compositeDef.addNestedComponent(new BeanComponentDefinition(jcacheAspectDef, CacheManagementConfigUtils.JCACHE_ASPECT_BEAN_NAME));
parserContext.registerComponent(compositeDef);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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,11 +16,12 @@
package org.springframework.context.annotation;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@@ -93,9 +94,9 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
private String resourcePattern = DEFAULT_RESOURCE_PATTERN;
private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> includeFilters = new ArrayList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new ArrayList<>();
@Nullable
private Environment environment;
@@ -424,40 +425,38 @@ public class ClassPathScanningCandidateComponentProvider implements EnvironmentC
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
else {
catch (FileNotFoundException ex) {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
logger.trace("Ignored non-readable " + resource + ": " + ex.getMessage());
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
}
catch (IOException ex) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -332,7 +332,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
}
private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
@@ -352,7 +352,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
return metadata;
}
private InjectionMetadata buildResourceMetadata(final Class<?> clazz) {
private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
@@ -464,6 +464,7 @@ public class CommonAnnotationBeanPostProcessor extends InitDestroyAnnotationBean
public void releaseTarget(Object target) {
}
};
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
if (element.lookupType.isInterface()) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -73,7 +73,7 @@ class ComponentScanAnnotationParser {
}
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -453,8 +453,8 @@ class ConfigurationClassEnhancer {
* instance directly. If a FactoryBean instance is fetched through the container via &-dereferencing,
* it will not be proxied. This too is aligned with the way XML configuration works.
*/
private Object enhanceFactoryBean(final Object factoryBean, Class<?> exposedType,
final ConfigurableBeanFactory beanFactory, final String beanName) {
private Object enhanceFactoryBean(Object factoryBean, Class<?> exposedType,
ConfigurableBeanFactory beanFactory, String beanName) {
try {
Class<?> clazz = factoryBean.getClass();
@@ -489,8 +489,8 @@ class ConfigurationClassEnhancer {
return createCglibProxyForFactoryBean(factoryBean, beanFactory, beanName);
}
private Object createInterfaceProxyForFactoryBean(final Object factoryBean, Class<?> interfaceType,
final ConfigurableBeanFactory beanFactory, final String beanName) {
private Object createInterfaceProxyForFactoryBean(Object factoryBean, Class<?> interfaceType,
ConfigurableBeanFactory beanFactory, String beanName) {
return Proxy.newProxyInstance(
factoryBean.getClass().getClassLoader(), new Class<?>[] {interfaceType},
@@ -502,8 +502,8 @@ class ConfigurationClassEnhancer {
});
}
private Object createCglibProxyForFactoryBean(final Object factoryBean,
final ConfigurableBeanFactory beanFactory, final String beanName) {
private Object createCglibProxyForFactoryBean(Object factoryBean,
ConfigurableBeanFactory beanFactory, String beanName) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(factoryBean.getClass());
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2021 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,6 +47,11 @@ import java.lang.annotation.Target;
* or {@link javax.inject.Inject}: In that context, it leads to the creation of a
* lazy-resolution proxy for all affected dependencies, as an alternative to using
* {@link org.springframework.beans.factory.ObjectFactory} or {@link javax.inject.Provider}.
* Please note that such a lazy-resolution proxy will always be injected; if the target
* dependency does not exist, you will only be able to find out through an exception on
* invocation. As a consequence, such an injection point results in unintuitive behavior
* for optional dependencies. For a programmatic equivalent, allowing for lazy references
* with more sophistication, consider {@link org.springframework.beans.factory.ObjectProvider}.
*
* @author Chris Beams
* @author Juergen Hoeller
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -65,10 +65,10 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIOException;
/**
* @since 13.03.2003
* @author Rod Johnson
* @author Juergen Hoeller
* @author Chris Beams
* @since 13.03.2003
*/
public class ProxyFactoryBeanTests {
@@ -631,20 +631,50 @@ public class ProxyFactoryBeanTests {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
new XmlBeanDefinitionReader(bf).loadBeanDefinitions(new ClassPathResource(FROZEN_CONTEXT, CLASS));
Advised advised = (Advised)bf.getBean("frozen");
Advised advised = (Advised) bf.getBean("frozen");
assertThat(advised.isFrozen()).as("The proxy should be frozen").isTrue();
}
@Test
public void testDetectsInterfaces() throws Exception {
public void testDetectsInterfaces() {
ProxyFactoryBean fb = new ProxyFactoryBean();
fb.setTarget(new TestBean());
fb.addAdvice(new DebugInterceptor());
fb.setBeanFactory(new DefaultListableBeanFactory());
ITestBean proxy = (ITestBean) fb.getObject();
assertThat(AopUtils.isJdkDynamicProxy(proxy)).isTrue();
}
@Test
public void testWithInterceptorNames() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton("debug", new DebugInterceptor());
ProxyFactoryBean fb = new ProxyFactoryBean();
fb.setTarget(new TestBean());
fb.setInterceptorNames("debug");
fb.setBeanFactory(bf);
Advised proxy = (Advised) fb.getObject();
assertThat(proxy.getAdvisors().length).isEqualTo(1);
}
@Test
public void testWithLateInterceptorNames() {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton("debug", new DebugInterceptor());
ProxyFactoryBean fb = new ProxyFactoryBean();
fb.setTarget(new TestBean());
fb.setBeanFactory(bf);
fb.getObject();
fb.setInterceptorNames("debug");
Advised proxy = (Advised) fb.getObject();
assertThat(proxy.getAdvisors().length).isEqualTo(1);
}
/**
* Fires only on void methods. Saves list of methods intercepted.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -25,7 +25,6 @@ import org.springframework.beans.testfixture.beans.TestBean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests cornering the issue reported in SPR-8080. If the product of a @Bean method
* was @Autowired into a configuration class while at the same time the declaring
@@ -34,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* 'currently in creation' status of the autowired bean and result in creating multiple
* instances of the given @Bean, violating container scoping / singleton semantics.
*
* This is resolved through no longer relying on 'currently in creation' status, but
* <p>This is resolved through no longer relying on 'currently in creation' status, but
* rather on a thread local that informs the enhanced bean method implementation whether
* the factory is the caller or not.
*
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -26,7 +26,6 @@ import org.springframework.context.support.GenericXmlApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Chris Beams
* @author Juergen Hoeller
@@ -47,6 +46,7 @@ public class DestroyMethodInferenceTests {
WithLocalShutdownMethod c7 = ctx.getBean("c7", WithLocalShutdownMethod.class);
WithInheritedCloseMethod c8 = ctx.getBean("c8", WithInheritedCloseMethod.class);
WithDisposableBean c9 = ctx.getBean("c9", WithDisposableBean.class);
WithAutoCloseable c10 = ctx.getBean("c10", WithAutoCloseable.class);
assertThat(c0.closed).as("c0").isFalse();
assertThat(c1.closed).as("c1").isFalse();
@@ -58,6 +58,8 @@ public class DestroyMethodInferenceTests {
assertThat(c7.closed).as("c7").isFalse();
assertThat(c8.closed).as("c8").isFalse();
assertThat(c9.closed).as("c9").isFalse();
assertThat(c10.closed).as("c10").isFalse();
ctx.close();
assertThat(c0.closed).as("c0").isTrue();
assertThat(c1.closed).as("c1").isTrue();
@@ -69,6 +71,7 @@ public class DestroyMethodInferenceTests {
assertThat(c7.closed).as("c7").isTrue();
assertThat(c8.closed).as("c8").isFalse();
assertThat(c9.closed).as("c9").isTrue();
assertThat(c10.closed).as("c10").isTrue();
}
@Test
@@ -80,21 +83,29 @@ public class DestroyMethodInferenceTests {
WithLocalCloseMethod x3 = ctx.getBean("x3", WithLocalCloseMethod.class);
WithNoCloseMethod x4 = ctx.getBean("x4", WithNoCloseMethod.class);
WithInheritedCloseMethod x8 = ctx.getBean("x8", WithInheritedCloseMethod.class);
WithDisposableBean x9 = ctx.getBean("x9", WithDisposableBean.class);
WithAutoCloseable x10 = ctx.getBean("x10", WithAutoCloseable.class);
assertThat(x1.closed).isFalse();
assertThat(x2.closed).isFalse();
assertThat(x3.closed).isFalse();
assertThat(x4.closed).isFalse();
assertThat(x8.closed).isFalse();
assertThat(x9.closed).isFalse();
assertThat(x10.closed).isFalse();
ctx.close();
assertThat(x1.closed).isFalse();
assertThat(x2.closed).isTrue();
assertThat(x3.closed).isTrue();
assertThat(x4.closed).isFalse();
assertThat(x8.closed).isFalse();
assertThat(x9.closed).isTrue();
assertThat(x10.closed).isTrue();
}
@Configuration
@Configuration(proxyBeanMethods = false)
static class Config {
@Bean(destroyMethod = "explicitClose")
@@ -155,6 +166,11 @@ public class DestroyMethodInferenceTests {
public WithDisposableBean c9() {
return new WithDisposableBean();
}
@Bean
public WithAutoCloseable c10() {
return new WithAutoCloseable();
}
}
@@ -189,17 +205,6 @@ public class DestroyMethodInferenceTests {
}
static class WithDisposableBean implements DisposableBean {
boolean closed = false;
@Override
public void destroy() {
closed = true;
}
}
static class WithNoCloseMethod {
boolean closed = false;
@@ -215,4 +220,26 @@ public class DestroyMethodInferenceTests {
}
}
static class WithDisposableBean implements DisposableBean {
boolean closed = false;
@Override
public void destroy() {
closed = true;
}
}
static class WithAutoCloseable implements AutoCloseable {
boolean closed = false;
@Override
public void close() {
closed = true;
}
}
}
@@ -15,10 +15,16 @@
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithInheritedCloseMethod"
destroy-method=""/>
<bean id="x9"
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithDisposableBean"
destroy-method=""/>
<bean id="x10"
class="org.springframework.context.annotation.DestroyMethodInferenceTests.WithAutoCloseable"/>
<beans default-destroy-method="(inferred)">
<bean id="x3"
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithLocalCloseMethod"/>
<bean id="x4"
class="org.springframework.context.annotation.DestroyMethodInferenceTests$WithNoCloseMethod"/>
</beans>
+4 -4
View File
@@ -53,22 +53,22 @@ dependencies {
optional("io.reactivex:rxjava-reactive-streams")
optional("io.reactivex.rxjava2:rxjava")
optional("io.netty:netty-buffer")
testCompile("io.projectreactor:reactor-test")
testCompile("com.google.code.findbugs:jsr305")
testCompile(project(":kotlin-coroutines"))
testCompile("javax.annotation:javax.annotation-api")
testCompile("javax.xml.bind:jaxb-api")
testCompile("com.google.code.findbugs:jsr305")
testCompile("com.fasterxml.woodstox:woodstox-core")
testCompile("org.xmlunit:xmlunit-assertj")
testCompile("org.xmlunit:xmlunit-matchers")
testCompile(project(":kotlin-coroutines"))
testCompile("io.projectreactor:reactor-test")
testCompile("io.projectreactor.tools:blockhound")
testFixturesImplementation("io.projectreactor:reactor-test")
testFixturesImplementation("com.google.code.findbugs:jsr305")
testFixturesImplementation("org.junit.platform:junit-platform-launcher")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params")
testFixturesImplementation("org.assertj:assertj-core")
testFixturesImplementation("org.xmlunit:xmlunit-assertj")
testFixturesImplementation("io.projectreactor:reactor-test")
}
jar {
@@ -499,6 +499,7 @@ public class ReflectUtils {
ProtectionDomain protectionDomain, Class<?> contextClass) throws Exception {
Class c = null;
Throwable t = THROWABLE;
// Preferred option: JDK 9+ Lookup.defineClass API if ClassLoader matches
if (contextClass != null && contextClass.getClassLoader() == loader &&
@@ -516,6 +517,7 @@ public class ReflectUtils {
// in case of plain LinkageError (class already defined)
// or IllegalArgumentException (class in different package):
// fall through to traditional ClassLoader.defineClass below
t = target;
}
catch (Throwable ex) {
throw new CodeGenerationException(ex);
@@ -543,6 +545,7 @@ public class ReflectUtils {
if (!ex.getClass().getName().endsWith("InaccessibleObjectException")) {
throw new CodeGenerationException(ex);
}
t = ex;
}
}
@@ -564,7 +567,7 @@ public class ReflectUtils {
// No defineClass variant available at all?
if (c == null) {
throw new CodeGenerationException(THROWABLE);
throw new CodeGenerationException(t);
}
// Force static initializers to run.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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,7 +47,7 @@ import org.springframework.util.ReflectionUtils;
*
* <p>By default, depending on classpath availability, adapters are registered
* for Reactor, RxJava 1, RxJava 2 types, {@link CompletableFuture}, Java 9+
* {@code Flow.Publisher} and Kotlin Coroutines {@code Deferred} and {@code Flow}.
* {@code Flow.Publisher}, and Kotlin Coroutines {@code Deferred} and {@code Flow}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
@@ -74,6 +74,10 @@ public class ReactiveAdapterRegistry {
boolean reactorRegistered = false;
if (ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader)) {
new ReactorRegistrar().registerAdapters(this);
if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) {
// Java 9+ Flow.Publisher
new ReactorJdkFlowAdapterRegistrar().registerAdapter(this);
}
reactorRegistered = true;
}
this.reactorPresent = reactorRegistered;
@@ -83,20 +87,12 @@ public class ReactiveAdapterRegistry {
ClassUtils.isPresent("rx.RxReactiveStreams", classLoader)) {
new RxJava1Registrar().registerAdapters(this);
}
// RxJava2
if (ClassUtils.isPresent("io.reactivex.Flowable", classLoader)) {
new RxJava2Registrar().registerAdapters(this);
}
// Java 9+ Flow.Publisher
if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) {
new ReactorJdkFlowAdapterRegistrar().registerAdapter(this);
}
// If not present, do nothing for the time being...
// We can fall back on "reactive-streams-flow-bridge" (once released)
// Coroutines
// Kotlin Coroutines
if (this.reactorPresent && ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader)) {
new CoroutinesRegistrar().registerAdapters(this);
}
@@ -226,6 +222,35 @@ public class ReactiveAdapterRegistry {
}
private static class ReactorJdkFlowAdapterRegistrar {
void registerAdapter(ReactiveAdapterRegistry registry) {
// Reflectively access optional JDK 9+ API (for runtime compatibility with JDK 8)
try {
String publisherName = "java.util.concurrent.Flow.Publisher";
Class<?> publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader());
String adapterName = "reactor.adapter.JdkFlowAdapter";
Class<?> flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader());
Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass);
Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class);
Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty());
registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow),
source -> (Publisher<?>) ReflectionUtils.invokeMethod(toFluxMethod, null, source),
publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher)
);
}
catch (Throwable ex) {
// Ignore
}
}
}
private static class RxJava1Registrar {
void registerAdapters(ReactiveAdapterRegistry registry) {
@@ -280,35 +305,6 @@ public class ReactiveAdapterRegistry {
}
private static class ReactorJdkFlowAdapterRegistrar {
void registerAdapter(ReactiveAdapterRegistry registry) {
// TODO: remove reflection when build requires JDK 9+
try {
String publisherName = "java.util.concurrent.Flow.Publisher";
Class<?> publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader());
String adapterName = "reactor.adapter.JdkFlowAdapter";
Class<?> flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader());
Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass);
Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class);
Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty());
registry.registerReactiveType(
ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow),
source -> (Publisher<?>) ReflectionUtils.invokeMethod(toFluxMethod, null, source),
publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher)
);
}
catch (Throwable ex) {
// Ignore
}
}
}
/**
* ReactiveAdapter variant that wraps adapted Publishers as {@link Flux} or
* {@link Mono} depending on {@link ReactiveTypeDescriptor#isMultiValue()}.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -88,7 +88,15 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
@Override
public boolean isReadable() {
try {
URL url = getURL();
return checkReadable(getURL());
}
catch (IOException ex) {
return false;
}
}
boolean checkReadable(URL url) {
try {
if (ResourceUtils.isFileURL(url)) {
// Proceed with file system resolution
File file = getFile();
@@ -142,6 +142,18 @@ public class ClassPathResource extends AbstractFileResolvingResource {
return (resolveURL() != null);
}
/**
* This implementation checks for the resolution of a resource URL upfront,
* then proceeding with {@link AbstractFileResolvingResource}'s length check.
* @see java.lang.ClassLoader#getResource(String)
* @see java.lang.Class#getResource(String)
*/
@Override
public boolean isReadable() {
URL url = resolveURL();
return (url != null && checkReadable(url));
}
/**
* Resolves a URL for the underlying class path resource.
* @return the resolved URL, or {@code null} if not resolvable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 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.
@@ -48,8 +48,9 @@ public interface InputStreamSource {
* creating mail attachments. For such a use case, it is <i>required</i>
* that each {@code getInputStream()} call returns a fresh stream.
* @return the input stream for the underlying resource (must not be {@code null})
* @throws java.io.FileNotFoundException if the underlying resource doesn't exist
* @throws java.io.FileNotFoundException if the underlying resource does not exist
* @throws IOException if the content stream could not be opened
* @see Resource#isReadable()
*/
InputStream getInputStream() throws IOException;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -462,9 +462,9 @@ public class DefaultDataBuffer implements DataBuffer {
private void checkIndex(int index, int length) {
assertIndex(index >= 0, "index %d must be >= 0", index);
assertIndex(length >= 0, "length %d must be >= 0", index);
assertIndex(length >= 0, "length %d must be >= 0", length);
assertIndex(index <= this.capacity, "index %d must be <= %d", index, this.capacity);
assertIndex(length <= this.capacity, "length %d must be <= %d", index, this.capacity);
assertIndex(length <= this.capacity, "length %d must be <= %d", length, this.capacity);
}
private void assertIndex(boolean expression, String format, Object... args) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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,10 +17,12 @@
package org.springframework.core.log;
import java.util.function.Function;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.springframework.lang.Nullable;
import org.springframework.util.ObjectUtils;
/**
* Utility methods for formatting and logging messages.
@@ -35,31 +37,57 @@ import org.springframework.lang.Nullable;
*/
public abstract class LogFormatUtils {
private static final Pattern NEWLINE_PATTERN = Pattern.compile("[\n\r]");
private static final Pattern CONTROL_CHARACTER_PATTERN = Pattern.compile("\\p{Cc}");
/**
* Format the given value via {@code toString()}, quoting it if it is a
* {@link CharSequence}, and possibly truncating at 100 if limitLength is
* set to true.
* Convenience variant of {@link #formatValue(Object, int, boolean)} that
* limits the length of a log message to 100 characters and also replaces
* newline and control characters if {@code limitLength} is set to "true".
* @param value the value to format
* @param limitLength whether to truncate large formatted values (over 100)
* @param limitLength whether to truncate the value at a length of 100
* @return the formatted value
*/
public static String formatValue(@Nullable Object value, boolean limitLength) {
return formatValue(value, (limitLength ? 100 : -1), limitLength);
}
/**
* Format the given value via {@code toString()}, quoting it if it is a
* {@link CharSequence}, truncating at the specified {@code maxLength}, and
* compacting it into a single line when {@code replaceNewLines} is set.
* @param value the value to be formatted
* @param maxLength the max length, after which to truncate, or -1 for unlimited
* @param replaceNewlinesAndControlCharacters whether to replace newline and
* control characters with placeholders
* @return the formatted value
*/
public static String formatValue(
@Nullable Object value, int maxLength, boolean replaceNewlinesAndControlCharacters) {
if (value == null) {
return "";
}
String str;
String result;
try {
result = ObjectUtils.nullSafeToString(value);
}
catch (Throwable ex) {
result = ObjectUtils.nullSafeToString(ex);
}
if (maxLength != -1) {
result = (result.length() > maxLength ? result.substring(0, maxLength) + " (truncated)..." : result);
}
if (replaceNewlinesAndControlCharacters) {
result = NEWLINE_PATTERN.matcher(result).replaceAll("<EOL>");
result = CONTROL_CHARACTER_PATTERN.matcher(result).replaceAll("?");
}
if (value instanceof CharSequence) {
str = "\"" + value + "\"";
result = "\"" + result + "\"";
}
else {
try {
str = value.toString();
}
catch (Throwable ex) {
str = ex.toString();
}
}
return (limitLength && str.length() > 100 ? str.substring(0, 100) + " (truncated)..." : str);
return result;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -77,7 +77,7 @@ public class AntPathMatcher implements PathMatcher {
private static final int CACHE_TURNOFF_THRESHOLD = 65536;
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?}");
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
private static final char[] WILDCARD_CHARS = { '*', '?', '{' };
@@ -641,7 +641,7 @@ public class AntPathMatcher implements PathMatcher {
*/
protected static class AntPathStringMatcher {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}");
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
@@ -858,7 +858,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
Reference<K, V> ref = ConcurrentReferenceHashMap.this.getReference(entry.getKey(), Restructure.NEVER);
Entry<K, V> otherEntry = (ref != null ? ref.get() : null);
if (otherEntry != null) {
return ObjectUtils.nullSafeEquals(otherEntry.getValue(), otherEntry.getValue());
return ObjectUtils.nullSafeEquals(entry.getValue(), otherEntry.getValue());
}
}
return false;
@@ -966,6 +966,7 @@ public class ConcurrentReferenceHashMap<K, V> extends AbstractMap<K, V> implemen
public void remove() {
Assert.state(this.last != null, "No element to remove");
ConcurrentReferenceHashMap.this.remove(this.last.getKey());
this.last = null;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -41,11 +41,13 @@ import org.springframework.util.comparator.NullSafeComparator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
/**
* Tests for {@link ConcurrentReferenceHashMap}.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
class ConcurrentReferenceHashMapTests {
@@ -466,6 +468,7 @@ class ConcurrentReferenceHashMapTests {
iterator.next();
iterator.next();
iterator.remove();
assertThatIllegalStateException().isThrownBy(iterator::remove);
iterator.next();
assertThat(iterator.hasNext()).isFalse();
assertThat(this.map).hasSize(2);
@@ -486,6 +489,26 @@ class ConcurrentReferenceHashMapTests {
assertThat(this.map.get(2)).isEqualTo("2b");
}
@Test
void containsViaEntrySet() {
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
Set<Map.Entry<Integer, String>> entrySet = this.map.entrySet();
Set<Map.Entry<Integer, String>> copy = new HashMap<>(this.map).entrySet();
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isTrue());
this.map.put(1, "A");
this.map.put(2, "B");
this.map.put(3, "C");
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isFalse());
this.map.put(1, "1");
this.map.put(2, "2");
this.map.put(3, "3");
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isTrue());
entrySet.clear();
copy.forEach(entry -> assertThat(entrySet.contains(entry)).isFalse());
}
@Test
@Disabled("Intended for use during development only")
void shouldBeFasterThanSynchronizedMap() throws InterruptedException {
@@ -583,7 +606,7 @@ class ConcurrentReferenceHashMapTests {
}
// For testing we want more control of the hash
this.supplementalHash = super.getHash(o);
return o == null ? 0 : o.hashCode();
return (o != null ? o.hashCode() : 0);
}
public int getSupplementalHash() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.util.StopWatch.TaskInfo;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
@@ -41,7 +40,7 @@ class StopWatchTests {
private static final long duration1 = 200;
private static final long duration2 = 100;
private static final long fudgeFactor = 50;
// private static final long fudgeFactor = 100;
private final StopWatch stopWatch = new StopWatch(ID);
@@ -78,18 +77,21 @@ class StopWatchTests {
assertThat(stopWatch.currentTaskName()).isEqualTo(name1);
stopWatch.stop();
assertThat(stopWatch.isRunning()).isFalse();
/* Flaky StopWatch time assertions...
assertThat(stopWatch.getLastTaskTimeNanos())
.as("last task time in nanoseconds for task #1")
.isGreaterThanOrEqualTo(millisToNanos(duration1 - fudgeFactor))
.isLessThanOrEqualTo(millisToNanos(duration1 + fudgeFactor));
.as("last task time in nanoseconds for task #1")
.isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration1 - fudgeFactor))
.isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration1 + fudgeFactor));
assertThat(stopWatch.getTotalTimeMillis())
.as("total time in milliseconds for task #1")
.isGreaterThanOrEqualTo(duration1 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + fudgeFactor);
.as("total time in milliseconds for task #1")
.isGreaterThanOrEqualTo(duration1 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + fudgeFactor);
assertThat(stopWatch.getTotalTimeSeconds())
.as("total time in seconds for task #1")
.isGreaterThanOrEqualTo((duration1 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + fudgeFactor) / 1000.0);
.as("total time in seconds for task #1")
.isGreaterThanOrEqualTo((duration1 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + fudgeFactor) / 1000.0);
*/
stopWatch.start(name2);
Thread.sleep(duration2);
@@ -97,18 +99,21 @@ class StopWatchTests {
assertThat(stopWatch.currentTaskName()).isEqualTo(name2);
stopWatch.stop();
assertThat(stopWatch.isRunning()).isFalse();
/* Flaky StopWatch time assertions...
assertThat(stopWatch.getLastTaskTimeNanos())
.as("last task time in nanoseconds for task #2")
.isGreaterThanOrEqualTo(millisToNanos(duration2))
.isLessThanOrEqualTo(millisToNanos(duration2 + fudgeFactor));
.as("last task time in nanoseconds for task #2")
.isGreaterThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration2))
.isLessThanOrEqualTo(TimeUnit.MILLISECONDS.toNanos(duration2 + fudgeFactor));
assertThat(stopWatch.getTotalTimeMillis())
.as("total time in milliseconds for tasks #1 and #2")
.isGreaterThanOrEqualTo(duration1 + duration2 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + duration2 + fudgeFactor);
.as("total time in milliseconds for tasks #1 and #2")
.isGreaterThanOrEqualTo(duration1 + duration2 - fudgeFactor)
.isLessThanOrEqualTo(duration1 + duration2 + fudgeFactor);
assertThat(stopWatch.getTotalTimeSeconds())
.as("total time in seconds for task #2")
.isGreaterThanOrEqualTo((duration1 + duration2 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + duration2 + fudgeFactor) / 1000.0);
.as("total time in seconds for task #2")
.isGreaterThanOrEqualTo((duration1 + duration2 - fudgeFactor) / 1000.0)
.isLessThanOrEqualTo((duration1 + duration2 + fudgeFactor) / 1000.0);
*/
assertThat(stopWatch.getTaskCount()).isEqualTo(2);
assertThat(stopWatch.prettyPrint()).contains(name1, name2);
@@ -135,12 +140,8 @@ class StopWatchTests {
assertThat(stopWatch.prettyPrint()).contains("No task info kept");
assertThat(stopWatch.toString()).doesNotContain(name1, name2);
assertThatExceptionOfType(UnsupportedOperationException.class)
.isThrownBy(stopWatch::getTaskInfo)
.withMessage("Task info is not being kept!");
}
private static long millisToNanos(long duration) {
return MILLISECONDS.toNanos(duration);
.isThrownBy(stopWatch::getTaskInfo)
.withMessage("Task info is not being kept!");
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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,7 +19,7 @@ package org.springframework.expression;
/**
* A bean resolver can be registered with the evaluation context and will kick in
* for bean references: {@code @myBeanName} and {@code &myBeanName} expressions.
* The <tt>&</tt> variant syntax allows access to the factory bean where relevant.
* The {@code &} variant syntax allows access to the factory bean where relevant.
*
* @author Andy Clement
* @since 3.0.3
@@ -28,7 +28,7 @@ public interface BeanResolver {
/**
* Look up a bean by the given name and return a corresponding instance for it.
* For attempting access to a factory bean, the name needs a <tt>&</tt> prefix.
* For attempting access to a factory bean, the name needs a {@code &} prefix.
* @param context the current evaluation context
* @param beanName the name of the bean to look up
* @return an object representing the bean
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 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,10 +40,11 @@ public interface TypeComparator {
* Compare two given objects.
* @param firstObject the first object
* @param secondObject the second object
* @return 0 if they are equal, <0 if the first is smaller than the second,
* or >0 if the first is larger than the second
* @return 0 if they are equal, a negative integer if the first is smaller than
* the second, or a positive integer if the first is larger than the second
* @throws EvaluationException if a problem occurs during comparison
* (or if they are not comparable in the first place)
* @see Comparable#compareTo
*/
int compare(@Nullable Object firstObject, @Nullable Object secondObject) throws EvaluationException;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -25,8 +25,8 @@ import org.springframework.expression.spel.SpelEvaluationException;
import org.springframework.expression.spel.SpelMessage;
/**
* Represents a bean reference to a type, for example <tt>@foo</tt> or <tt>@'foo.bar'</tt>.
* For a FactoryBean the syntax <tt>&foo</tt> can be used to access the factory itself.
* Represents a bean reference to a type, for example {@code @foo} or {@code @'foo.bar'}.
* For a FactoryBean the syntax {@code &foo} can be used to access the factory itself.
*
* @author Andy Clement
*/
@@ -64,13 +64,13 @@ public class BeanReference extends SpelNodeImpl {
public String toStringAST() {
StringBuilder sb = new StringBuilder();
if (!this.beanName.startsWith(FACTORY_BEAN_PREFIX)) {
sb.append("@");
sb.append('@');
}
if (!this.beanName.contains(".")) {
sb.append(this.beanName);
}
else {
sb.append("'").append(this.beanName).append("'");
sb.append('\'').append(this.beanName).append('\'');
}
return sb.toString();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -46,6 +46,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*
* @author Andy Clement
* @author Phillip Webb
* @author Sam Brannen
*/
public class MethodInvocationTests extends AbstractExpressionTests {
@@ -232,26 +233,54 @@ public class MethodInvocationTests extends AbstractExpressionTests {
@Test
public void testVarargsInvocation01() {
// Calling 'public int aVarargsMethod(String... strings)'
//evaluate("aVarargsMethod('a','b','c')", 3, Integer.class);
//evaluate("aVarargsMethod('a')", 1, Integer.class);
evaluate("aVarargsMethod()", 0, Integer.class);
evaluate("aVarargsMethod(1,2,3)", 3, Integer.class); // all need converting to strings
evaluate("aVarargsMethod(1)", 1, Integer.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)", 3, Integer.class); // first and last need conversion
// evaluate("aVarargsMethod(new String[]{'a','b','c'})", 3, Integer.class);
// Calling 'public String aVarargsMethod(String... strings)'
evaluate("aVarargsMethod('a','b','c')", "[a, b, c]", String.class);
evaluate("aVarargsMethod('a')", "[a]", String.class);
evaluate("aVarargsMethod()", "[]", String.class);
evaluate("aVarargsMethod(1,2,3)", "[1, 2, 3]", String.class); // all need converting to strings
evaluate("aVarargsMethod(1)", "[1]", String.class); // needs string conversion
evaluate("aVarargsMethod(1,'a',3.0d)", "[1, a, 3.0]", String.class); // first and last need conversion
evaluate("aVarargsMethod(new String[]{'a','b','c'})", "[a, b, c]", String.class);
evaluate("aVarargsMethod(new String[]{})", "[]", String.class);
evaluate("aVarargsMethod(null)", "[null]", String.class);
evaluate("aVarargsMethod(null,'a')", "[null, a]", String.class);
evaluate("aVarargsMethod('a',null,'b')", "[a, null, b]", String.class);
}
@Test
public void testVarargsInvocation02() {
// Calling 'public int aVarargsMethod2(int i, String... strings)' - returns int+length_of_strings
evaluate("aVarargsMethod2(5,'a','b','c')", 8, Integer.class);
evaluate("aVarargsMethod2(2,'a')", 3, Integer.class);
evaluate("aVarargsMethod2(4)", 4, Integer.class);
evaluate("aVarargsMethod2(8,2,3)", 10, Integer.class);
evaluate("aVarargsMethod2(9)", 9, Integer.class);
evaluate("aVarargsMethod2(2,'a',3.0d)", 4, Integer.class);
// evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", 11, Integer.class);
// Calling 'public String aVarargsMethod2(int i, String... strings)'
evaluate("aVarargsMethod2(5,'a','b','c')", "5-[a, b, c]", String.class);
evaluate("aVarargsMethod2(2,'a')", "2-[a]", String.class);
evaluate("aVarargsMethod2(4)", "4-[]", String.class);
evaluate("aVarargsMethod2(8,2,3)", "8-[2, 3]", String.class);
evaluate("aVarargsMethod2(2,'a',3.0d)", "2-[a, 3.0]", String.class);
evaluate("aVarargsMethod2(8,new String[]{'a','b','c'})", "8-[a, b, c]", String.class);
evaluate("aVarargsMethod2(8,new String[]{})", "8-[]", String.class);
evaluate("aVarargsMethod2(8,null)", "8-[null]", String.class);
evaluate("aVarargsMethod2(8,null,'a')", "8-[null, a]", String.class);
evaluate("aVarargsMethod2(8,'a',null,'b')", "8-[a, null, b]", String.class);
}
@Test
public void testVarargsOptionalInvocation() {
// Calling 'public String optionalVarargsMethod(Optional<String>... values)'
evaluate("optionalVarargsMethod()", "[]", String.class);
evaluate("optionalVarargsMethod(new String[0])", "[]", String.class);
evaluate("optionalVarargsMethod('a')", "[Optional[a]]", String.class);
evaluate("optionalVarargsMethod('a','b','c')", "[Optional[a], Optional[b], Optional[c]]", String.class);
evaluate("optionalVarargsMethod(9)", "[Optional[9]]", String.class);
evaluate("optionalVarargsMethod(2,3)", "[Optional[2], Optional[3]]", String.class);
evaluate("optionalVarargsMethod('a',3.0d)", "[Optional[a], Optional[3.0]]", String.class);
evaluate("optionalVarargsMethod(new String[]{'a','b','c'})", "[Optional[a], Optional[b], Optional[c]]", String.class);
// The following should actually evaluate to [Optional.empty] instead of [null],
// but ReflectionHelper.convertArguments() passes the array type instead of
// the array's component type as the target type to the ConversionService, and
// GenericConversionService.convertNullSource() therefore fails to convert null
// to Optional.empty().
evaluate("optionalVarargsMethod(null)", "[null]", String.class);
evaluate("optionalVarargsMethod(null,'a')", "[Optional.empty, Optional[a]]", String.class);
evaluate("optionalVarargsMethod('a',null,'b')", "[Optional[a], Optional.empty, Optional[b]]", String.class);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2021 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,6 +16,7 @@
package org.springframework.expression.spel;
import java.util.Arrays;
import java.util.GregorianCalendar;
import org.springframework.expression.spel.support.StandardEvaluationContext;
@@ -51,10 +52,10 @@ public class TestScenarioCreator {
TestScenarioCreator.class.getDeclaredMethod("reverseInt", Integer.TYPE, Integer.TYPE, Integer.TYPE));
testContext.registerFunction("reverseString",
TestScenarioCreator.class.getDeclaredMethod("reverseString", String.class));
testContext.registerFunction("varargsFunctionReverseStringsAndMerge",
TestScenarioCreator.class.getDeclaredMethod("varargsFunctionReverseStringsAndMerge", String[].class));
testContext.registerFunction("varargsFunctionReverseStringsAndMerge2",
TestScenarioCreator.class.getDeclaredMethod("varargsFunctionReverseStringsAndMerge2", Integer.TYPE, String[].class));
testContext.registerFunction("varargsFunction",
TestScenarioCreator.class.getDeclaredMethod("varargsFunction", String[].class));
testContext.registerFunction("varargsFunction2",
TestScenarioCreator.class.getDeclaredMethod("varargsFunction2", Integer.TYPE, String[].class));
}
catch (Exception ex) {
throw new IllegalStateException(ex);
@@ -108,25 +109,12 @@ public class TestScenarioCreator {
return backwards.toString();
}
public static String varargsFunctionReverseStringsAndMerge(String... strings) {
StringBuilder sb = new StringBuilder();
if (strings != null) {
for (int i = strings.length - 1; i >= 0; i--) {
sb.append(strings[i]);
}
}
return sb.toString();
public static String varargsFunction(String... strings) {
return Arrays.toString(strings);
}
public static String varargsFunctionReverseStringsAndMerge2(int j, String... strings) {
StringBuilder sb = new StringBuilder();
sb.append(j);
if (strings != null) {
for (int i = strings.length - 1; i >= 0; i--) {
sb.append(strings[i]);
}
}
return sb.toString();
public static String varargsFunction2(int i, String... strings) {
return String.valueOf(i) + "-" + Arrays.toString(strings);
}
}
@@ -58,16 +58,23 @@ public class VariableAndFunctionTests extends AbstractExpressionTests {
@Test
public void testCallVarargsFunction() {
evaluate("#varargsFunctionReverseStringsAndMerge('a','b','c')", "cba", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge('a')", "a", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge()", "", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge('b',25)", "25b", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge(25)", "25", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(1,'a','b','c')", "1cba", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(2,'a')", "2a", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(3)", "3", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(4,'b',25)", "425b", String.class);
evaluate("#varargsFunctionReverseStringsAndMerge2(5,25)", "525", String.class);
evaluate("#varargsFunction()", "[]", String.class);
evaluate("#varargsFunction(new String[0])", "[]", String.class);
evaluate("#varargsFunction('a','b','c')", "[a, b, c]", String.class);
evaluate("#varargsFunction('a')", "[a]", String.class);
evaluate("#varargsFunction('b',25)", "[b, 25]", String.class);
evaluate("#varargsFunction(25)", "[25]", String.class);
evaluate("#varargsFunction(null)", "[null]", String.class);
evaluate("#varargsFunction('a',null,'b')", "[a, null, b]", String.class);
evaluate("#varargsFunction2(9)", "9-[]", String.class);
evaluate("#varargsFunction2(9, new String[0])", "9-[]", String.class);
evaluate("#varargsFunction2(9,'a','b','c')", "9-[a, b, c]", String.class);
evaluate("#varargsFunction2(9,'a')", "9-[a]", String.class);
evaluate("#varargsFunction2(9,'b',25)", "9-[b, 25]", String.class);
evaluate("#varargsFunction2(9,25)", "9-[25]", String.class);
evaluate("#varargsFunction2(9,null)", "9-[null]", String.class);
evaluate("#varargsFunction2(9,'a',null,'b')", "9-[a, null, b]", String.class);
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2021 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,11 +17,13 @@
package org.springframework.expression.spel.testresources;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.springframework.util.ObjectUtils;
@@ -190,16 +192,17 @@ public class Inventor {
return a + b + c;
}
public int aVarargsMethod(String... strings) {
if (strings == null)
return 0;
return strings.length;
public String aVarargsMethod(String... strings) {
return Arrays.toString(strings);
}
public int aVarargsMethod2(int i, String... strings) {
if (strings == null)
return i;
return strings.length + i;
public String aVarargsMethod2(int i, String... strings) {
return String.valueOf(i) + "-" + Arrays.toString(strings);
}
@SuppressWarnings("unchecked")
public String optionalVarargsMethod(Optional<String>... values) {
return Arrays.toString(values);
}
public Inventor(String... strings) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -21,22 +21,21 @@ import java.util.List;
import java.util.Map;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.jdbc.support.rowset.SqlRowSet;
import org.springframework.lang.Nullable;
/**
* Interface specifying a basic set of JDBC operations.
* Implemented by {@link JdbcTemplate}. Not often used directly, but a useful
*
* <p>Implemented by {@link JdbcTemplate}. Not often used directly, but a useful
* option to enhance testability, as it can easily be mocked or stubbed.
*
* <p>Alternatively, the standard JDBC infrastructure can be mocked.
* However, mocking this interface constitutes significantly less work.
* As an alternative to a mock objects approach to testing data access code,
* consider the powerful integration testing support provided in the
* {@code org.springframework.test} package, shipped in
* {@code spring-test.jar}.
* consider the powerful integration testing support provided via the <em>Spring
* TestContext Framework</em>, in the {@code spring-test} artifact.
*
* @author Rod Johnson
* @author Juergen Hoeller
@@ -143,8 +142,8 @@ public interface JdbcOperations {
* @param rowMapper a callback that will map one object per row
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if there is any problem executing the query
* @see #queryForObject(String, RowMapper, Object...)
*/
@@ -163,8 +162,10 @@ public interface JdbcOperations {
* @param sql the SQL query to execute
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if there is any problem executing the query
* @see #queryForObject(String, Class, Object...)
*/
@@ -181,8 +182,8 @@ public interface JdbcOperations {
* mapped to a Map (one entry for each column, using the column name as the key).
* @param sql the SQL query to execute
* @return the result Map (one entry per column, with column name as key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if there is any problem executing the query
* @see #queryForMap(String, Object...)
* @see ColumnMapRowMapper
@@ -528,8 +529,8 @@ public interface JdbcOperations {
* @param rowMapper a callback that will map one object per row
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -548,8 +549,8 @@ public interface JdbcOperations {
* @param rowMapper a callback that will map one object per row
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -567,8 +568,8 @@ public interface JdbcOperations {
* only the argument value but also the SQL type and optionally the scale
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
* @since 3.0.1
*/
@@ -586,8 +587,10 @@ public interface JdbcOperations {
* (constants from {@code java.sql.Types})
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see #queryForObject(String, Class)
* @see java.sql.Types
@@ -608,8 +611,10 @@ public interface JdbcOperations {
* only the argument value but also the SQL type and optionally the scale
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see #queryForObject(String, Class)
*/
@@ -628,8 +633,10 @@ public interface JdbcOperations {
* may also contain {@link SqlParameterValue} objects which indicate not
* only the argument value but also the SQL type and optionally the scale
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws IncorrectResultSizeDataAccessException if the query does not return
* exactly one row, or does not return exactly one column in that row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @since 3.0.1
* @see #queryForObject(String, Class)
@@ -647,8 +654,8 @@ public interface JdbcOperations {
* @param argTypes the SQL types of the arguments
* (constants from {@code java.sql.Types})
* @return the result Map (one entry per column, with column name as key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
* @see #queryForMap(String)
* @see ColumnMapRowMapper
@@ -671,8 +678,8 @@ public interface JdbcOperations {
* only the argument value but also the SQL type and optionally the scale
* @return the result Map (one entry for each column, using the
* column name as the key)
* @throws IncorrectResultSizeDataAccessException if the query does not
* return exactly one row
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
* @see #queryForMap(String)
* @see ColumnMapRowMapper
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -236,8 +236,7 @@ public interface NamedParameterJdbcOperations {
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -255,8 +254,7 @@ public interface NamedParameterJdbcOperations {
* @return the single mapped object (may be {@code null} if the given
* {@link RowMapper} returned {@code} null)
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws DataAccessException if the query fails
*/
@Nullable
@@ -273,10 +271,12 @@ public interface NamedParameterJdbcOperations {
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see org.springframework.jdbc.core.JdbcTemplate#queryForObject(String, Class)
* @see org.springframework.jdbc.core.SingleColumnRowMapper
*/
@Nullable
<T> T queryForObject(String sql, SqlParameterSource paramSource, Class<T> requiredType)
@@ -293,8 +293,9 @@ public interface NamedParameterJdbcOperations {
* @param requiredType the type that the result object is expected to match
* @return the result object of the required type, or {@code null} in case of SQL NULL
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException
* if the query does not return exactly one row, or does not return exactly
* one column in that row
* if the query does not return exactly one row
* @throws org.springframework.jdbc.IncorrectResultSetColumnCountException
* if the query does not return a row containing a single column
* @throws DataAccessException if the query fails
* @see org.springframework.jdbc.core.JdbcTemplate#queryForObject(String, Class)
*/
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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,8 +16,8 @@
package org.springframework.jms.listener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.jms.Connection;
@@ -81,9 +81,9 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
private boolean active = false;
private volatile boolean running = false;
private volatile boolean running;
private final List<Object> pausedTasks = new LinkedList<>();
private final List<Object> pausedTasks = new ArrayList<>();
protected final Object lifecycleMonitor = new Object();
@@ -582,13 +582,13 @@ public abstract class AbstractJmsListeningContainer extends JmsDestinationAccess
/**
* Log a task that has been rejected by {@link #doRescheduleTask}.
* <p>The default implementation simply logs a corresponding message
* at debug level.
* at warn level.
* @param task the rejected task object
* @param ex the exception thrown from {@link #doRescheduleTask}
*/
protected void logRejectedTask(Object task, RuntimeException ex) {
if (logger.isDebugEnabled()) {
logger.debug("Listener container task [" + task + "] has been rejected and paused: " + ex);
if (logger.isWarnEnabled()) {
logger.warn("Listener container task [" + task + "] has been rejected and paused: " + ex);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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,6 +16,7 @@
package org.springframework.orm.hibernate5;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
@@ -349,7 +350,7 @@ public class LocalSessionFactoryBuilder extends Configuration {
Resource[] resources = this.resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String className = reader.getClassMetadata().getClassName();
if (matchesEntityTypeFilter(reader, readerFactory)) {
@@ -362,6 +363,9 @@ public class LocalSessionFactoryBuilder extends Configuration {
packageNames.add(className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()));
}
}
catch (FileNotFoundException ex) {
// Ignore non-readable resource
}
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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,12 +16,13 @@
package org.springframework.orm.jpa.persistenceunit;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -498,7 +499,7 @@ public class DefaultPersistenceUnitManager
* as defined in the JPA specification.
*/
private List<SpringPersistenceUnitInfo> readPersistenceUnitInfos() {
List<SpringPersistenceUnitInfo> infos = new LinkedList<>();
List<SpringPersistenceUnitInfo> infos = new ArrayList<>(1);
String defaultName = this.defaultPersistenceUnitName;
boolean buildDefaultUnit = (this.packagesToScan != null || this.mappingResources != null);
boolean foundDefaultUnit = false;
@@ -585,7 +586,7 @@ public class DefaultPersistenceUnitManager
Resource[] resources = this.resourcePatternResolver.getResources(pattern);
MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(this.resourcePatternResolver);
for (Resource resource : resources) {
if (resource.isReadable()) {
try {
MetadataReader reader = readerFactory.getMetadataReader(resource);
String className = reader.getClassMetadata().getClassName();
if (matchesFilter(reader, readerFactory)) {
@@ -602,6 +603,9 @@ public class DefaultPersistenceUnitManager
className.substring(0, className.length() - PACKAGE_INFO_SUFFIX.length()));
}
}
catch (FileNotFoundException ex) {
// Ignore non-readable resource
}
}
}
catch (IOException ex) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -393,7 +393,7 @@ public class PersistenceAnnotationBeanPostProcessor
}
private InjectionMetadata findPersistenceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
private InjectionMetadata findPersistenceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
@@ -413,7 +413,7 @@ public class PersistenceAnnotationBeanPostProcessor
return metadata;
}
private InjectionMetadata buildPersistenceMetadata(final Class<?> clazz) {
private InjectionMetadata buildPersistenceMetadata(Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, Arrays.asList(PersistenceContext.class, PersistenceUnit.class))) {
return InjectionMetadata.EMPTY;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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,7 +47,7 @@ public abstract class TestContextResourceUtils {
private static final String SLASH = "/";
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(".*\\$\\{[^}]+}.*");
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile(".*\\$\\{[^}]+\\}.*");
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -67,7 +67,7 @@ class TimedTransactionalSpringExtensionTests {
event(test("WithExceededJUnitJupiterTimeout"),
finishedWithFailure(
instanceOf(TimeoutException.class),
message(msg -> msg.endsWith("timed out after 50 milliseconds")))));
message(msg -> msg.endsWith("timed out after 10 milliseconds")))));
}
@@ -83,10 +83,10 @@ class TimedTransactionalSpringExtensionTests {
}
@Test
@Timeout(value = 50, unit = TimeUnit.MILLISECONDS)
@Timeout(value = 10, unit = TimeUnit.MILLISECONDS)
void transactionalWithExceededJUnitJupiterTimeout() throws Exception {
assertThatTransaction().isActive();
Thread.sleep(100);
Thread.sleep(200);
}
@Test
@@ -97,11 +97,11 @@ class TimedTransactionalSpringExtensionTests {
}
@Test
@Timeout(value = 50, unit = TimeUnit.MILLISECONDS)
@Timeout(value = 10, unit = TimeUnit.MILLISECONDS)
@Transactional(propagation = Propagation.NOT_SUPPORTED)
void notTransactionalWithExceededJUnitJupiterTimeout() throws Exception {
assertThatTransaction().isNotActive();
Thread.sleep(100);
Thread.sleep(200);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -76,14 +76,14 @@ public class TimedSpringRunnerTests {
}
// Should Fail due to timeout.
@Test(timeout = 100)
@Test(timeout = 10)
public void jUnitTimeoutWithSleep() throws Exception {
Thread.sleep(200);
}
// Should Fail due to timeout.
@Test
@Timed(millis = 100)
@Timed(millis = 10)
public void springTimeoutWithSleep() throws Exception {
Thread.sleep(200);
}
@@ -97,7 +97,7 @@ public class TimedSpringRunnerTests {
// Should Fail due to timeout.
@Test
@MetaTimedWithOverride(millis = 100)
@MetaTimedWithOverride(millis = 10)
public void springTimeoutWithSleepAndMetaAnnotationAndOverride() throws Exception {
Thread.sleep(200);
}
@@ -110,7 +110,7 @@ public class TimedSpringRunnerTests {
}
}
@Timed(millis = 100)
@Timed(millis = 10)
@Retention(RetentionPolicy.RUNTIME)
private static @interface MetaTimed {
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2021 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.
@@ -38,7 +38,6 @@ import org.springframework.util.Assert;
* PersistenceExceptionTranslator} interface, which are subsequently asked to translate
* candidate exceptions.
*
* <p>All of Spring's applicable resource factories (e.g.
* {@link org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean})
* implement the {@code PersistenceExceptionTranslator} interface out of the box.
@@ -17,6 +17,7 @@
package org.springframework.http.server.reactive;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
@@ -36,6 +37,7 @@ import org.springframework.util.MultiValueMap;
* {@code MultiValueMap} implementation for wrapping Undertow HTTP headers.
*
* @author Brian Clozel
* @author Sam Brannen
* @since 5.1.1
*/
class UndertowHeadersAdapter implements MultiValueMap<String, String> {
@@ -131,7 +133,10 @@ class UndertowHeadersAdapter implements MultiValueMap<String, String> {
@Nullable
public List<String> remove(Object key) {
if (key instanceof String) {
this.headers.remove((String) key);
Collection<String> removed = this.headers.remove((String) key);
if (removed != null) {
return new ArrayList<>(removed);
}
}
return null;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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,14 +16,11 @@
package org.springframework.web.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -126,38 +123,26 @@ public class DefaultResponseErrorHandler implements ResponseErrorHandler {
}
/**
* Return error message with details from the response body, possibly truncated:
* Return error message with details from the response body. For example:
* <pre>
* 404 Not Found: [{'id': 123, 'message': 'my very long... (500 bytes)]
* 404 Not Found: [{'id': 123, 'message': 'my message'}]
* </pre>
*/
private String getErrorMessage(
int rawStatusCode, String statusText, @Nullable byte[] responseBody, @Nullable Charset charset) {
String preface = rawStatusCode + " " + statusText + ": ";
if (ObjectUtils.isEmpty(responseBody)) {
return preface + "[no body]";
}
charset = charset == null ? StandardCharsets.UTF_8 : charset;
int maxChars = 200;
charset = (charset != null ? charset : StandardCharsets.UTF_8);
if (responseBody.length < maxChars * 2) {
return preface + "[" + new String(responseBody, charset) + "]";
}
String bodyText = new String(responseBody, charset);
bodyText = LogFormatUtils.formatValue(bodyText, -1, true);
try {
Reader reader = new InputStreamReader(new ByteArrayInputStream(responseBody), charset);
CharBuffer buffer = CharBuffer.allocate(maxChars);
reader.read(buffer);
reader.close();
buffer.flip();
return preface + "[" + buffer.toString() + "... (" + responseBody.length + " bytes)]";
}
catch (IOException ex) {
// should never happen
throw new IllegalStateException(ex);
}
return preface + bodyText;
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -20,6 +20,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -70,7 +71,7 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
// Mirrors AbstractHandlerExceptionResolver in spring-webmvc...
String logPrefix = exchange.getLogPrefix();
if (this.warnLogger != null && this.warnLogger.isWarnEnabled()) {
this.warnLogger.warn(logPrefix + formatError(ex, exchange.getRequest()), ex);
this.warnLogger.warn(logPrefix + formatError(ex, exchange.getRequest()));
}
else if (logger.isDebugEnabled()) {
logger.debug(logPrefix + formatError(ex, exchange.getRequest()));
@@ -81,9 +82,10 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
private String formatError(Throwable ex, ServerHttpRequest request) {
String reason = ex.getClass().getSimpleName() + ": " + ex.getMessage();
String className = ex.getClass().getSimpleName();
String message = LogFormatUtils.formatValue(ex.getMessage(), -1, true);
String path = request.getURI().getRawPath();
return "Resolved [" + reason + "] for HTTP " + request.getMethod() + " " + path;
return "Resolved [" + className + ": " + message + "] for HTTP " + request.getMethod() + " " + path;
}
private boolean updateResponse(ServerHttpResponse response, Throwable ex) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -48,7 +48,7 @@ import org.springframework.util.MultiValueMap;
public abstract class UriComponents implements Serializable {
/** Captures URI template variable names. */
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)}");
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -82,7 +82,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
private static final String HOST_PATTERN = "(" + HOST_IPV6_PATTERN + "|" + HOST_IPV4_PATTERN + ")";
private static final String PORT_PATTERN = "(\\d*(?:\\{[^/]+?})?)";
private static final String PORT_PATTERN = "(\\d*(?:\\{[^/]+?\\})?)";
private static final String PATH_PATTERN = "([^?#]*)";
@@ -521,8 +521,8 @@ public class UrlPathHelper {
return UriUtils.decode(source, enc);
}
catch (UnsupportedCharsetException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode request string [" + source + "] with encoding '" + enc +
if (logger.isDebugEnabled()) {
logger.debug("Could not decode request string [" + source + "] with encoding '" + enc +
"': falling back to platform default encoding; exception message: " + ex.getMessage());
}
return URLDecoder.decode(source);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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,17 +83,17 @@ class CaptureTheRestPathElement extends PathElement {
}
private String pathToString(int fromSegment, List<Element> pathElements) {
StringBuilder buf = new StringBuilder();
StringBuilder sb = new StringBuilder();
for (int i = fromSegment, max = pathElements.size(); i < max; i++) {
Element element = pathElements.get(i);
if (element instanceof PathSegment) {
buf.append(((PathSegment)element).valueToMatch());
sb.append(((PathSegment)element).valueToMatch());
}
else {
buf.append(element.value());
sb.append(element.value());
}
}
return buf.toString();
return sb.toString();
}
@Override
@@ -101,6 +101,11 @@ class CaptureTheRestPathElement extends PathElement {
return 1;
}
@Override
public char[] getChars() {
return ("/{*" + this.variableName + "}").toCharArray();
}
@Override
public int getWildcardCount() {
return 0;
@@ -117,8 +122,4 @@ class CaptureTheRestPathElement extends PathElement {
return "CaptureTheRest(/{*" + this.variableName + "})";
}
@Override
public char[] getChars() {
return ("/{*"+this.variableName+"}").toCharArray();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ class CaptureVariablePathElement extends PathElement {
private final String variableName;
@Nullable
private Pattern constraintPattern;
private final Pattern constraintPattern;
/**
@@ -55,6 +55,7 @@ class CaptureVariablePathElement extends PathElement {
if (colon == -1) {
// no constraint
this.variableName = new String(captureDescriptor, 1, captureDescriptor.length - 2);
this.constraintPattern = null;
}
else {
this.variableName = new String(captureDescriptor, 1, colon - 1);
@@ -134,6 +135,18 @@ class CaptureVariablePathElement extends PathElement {
return 1;
}
@Override
public char[] getChars() {
StringBuilder sb = new StringBuilder();
sb.append('{');
sb.append(this.variableName);
if (this.constraintPattern != null) {
sb.append(':').append(this.constraintPattern.pattern());
}
sb.append('}');
return sb.toString().toCharArray();
}
@Override
public int getWildcardCount() {
return 0;
@@ -156,16 +169,4 @@ class CaptureVariablePathElement extends PathElement {
(this.constraintPattern != null ? ":" + this.constraintPattern.pattern() : "") + "})";
}
@Override
public char[] getChars() {
StringBuilder b = new StringBuilder();
b.append("{");
b.append(this.variableName);
if (this.constraintPattern != null) {
b.append(":").append(this.constraintPattern.pattern());
}
b.append("}");
return b.toString().toCharArray();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -26,14 +26,15 @@ import org.springframework.web.util.pattern.PathPattern.MatchingContext;
* literal path elements 'foo', 'bar' and 'goo'.
*
* @author Andy Clement
* @since 5.0
*/
class LiteralPathElement extends PathElement {
private char[] text;
private final char[] text;
private int len;
private final int len;
private boolean caseSensitive;
private final boolean caseSensitive;
public LiteralPathElement(int pos, char[] literalText, boolean caseSensitive, char separator) {
@@ -69,7 +70,7 @@ class LiteralPathElement extends PathElement {
return false;
}
char[] data = ((PathContainer.PathSegment)element).valueToMatchAsChars();
char[] data = ((PathContainer.PathSegment) element).valueToMatchAsChars();
if (this.caseSensitive) {
for (int i = 0; i < this.len; i++) {
if (data[i] != this.text[i]) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -332,18 +332,18 @@ public class PathPattern implements Comparable<PathPattern> {
PathContainer resultPath = null;
if (multipleAdjacentSeparators) {
// Need to rebuild the path without the duplicate adjacent separators
StringBuilder buf = new StringBuilder();
StringBuilder sb = new StringBuilder();
int i = startIndex;
while (i < endIndex) {
Element e = pathElements.get(i++);
buf.append(e.value());
sb.append(e.value());
if (e instanceof Separator) {
while (i < endIndex && (pathElements.get(i) instanceof Separator)) {
i++;
}
}
}
resultPath = PathContainer.parsePath(buf.toString(), this.pathOptions);
resultPath = PathContainer.parsePath(sb.toString(), this.pathOptions);
}
else if (startIndex >= endIndex) {
resultPath = PathContainer.parsePath("");
@@ -487,13 +487,13 @@ public class PathPattern implements Comparable<PathPattern> {
* @return the string form of the pattern
*/
String computePatternString() {
StringBuilder buf = new StringBuilder();
StringBuilder sb = new StringBuilder();
PathElement pe = this.head;
while (pe != null) {
buf.append(pe.getChars());
sb.append(pe.getChars());
pe = pe.next;
}
return buf.toString();
return sb.toString();
}
@Nullable
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 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.
@@ -66,14 +66,14 @@ public class PatternParseException extends IllegalArgumentException {
* with a pointer to the error position, as well as the error message.
*/
public String toDetailedString() {
StringBuilder buf = new StringBuilder();
buf.append(this.pattern).append('\n');
StringBuilder sb = new StringBuilder();
sb.append(this.pattern).append('\n');
for (int i = 0; i < this.position; i++) {
buf.append(' ');
sb.append(' ');
}
buf.append("^\n");
buf.append(getMessage());
return buf.toString();
sb.append("^\n");
sb.append(getMessage());
return sb.toString();
}
public int getPosition() {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -35,7 +35,7 @@ import org.springframework.web.util.pattern.PathPattern.MatchingContext;
*/
class RegexPathElement extends PathElement {
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}");
private static final Pattern GLOB_PATTERN = Pattern.compile("\\?|\\*|\\{((?:\\{[^/]+?\\}|[^/{}]|\\\\[{}])+?)\\}");
private static final String DEFAULT_VARIABLE_PATTERN = "(.*)";
@@ -136,20 +136,19 @@ class RegexPathElement extends PathElement {
if (matches) {
if (isNoMorePattern()) {
if (matchingContext.determineRemainingPath &&
(this.variableNames.isEmpty() || textToMatch.length() > 0)) {
(this.variableNames.isEmpty() || textToMatch.length() > 0)) {
matchingContext.remainingPathIndex = pathIndex + 1;
matches = true;
}
else {
// No more pattern, is there more data?
// If pattern is capturing variables there must be some actual data to bind to them
matches = (pathIndex + 1) >= matchingContext.pathLength
&& (this.variableNames.isEmpty() || textToMatch.length() > 0);
matches = (pathIndex + 1 >= matchingContext.pathLength) &&
(this.variableNames.isEmpty() || textToMatch.length() > 0);
if (!matches && matchingContext.isMatchOptionalTrailingSeparator()) {
matches = (this.variableNames.isEmpty()
|| textToMatch.length() > 0)
&& (pathIndex + 2) >= matchingContext.pathLength
&& matchingContext.isSeparator(pathIndex + 1);
matches = (this.variableNames.isEmpty() || textToMatch.length() > 0) &&
(pathIndex + 2 >= matchingContext.pathLength) &&
matchingContext.isSeparator(pathIndex + 1);
}
}
}
@@ -160,11 +159,11 @@ class RegexPathElement extends PathElement {
if (matches && matchingContext.extractingVariables) {
// Process captures
if (this.variableNames.size() != matcher.groupCount()) { // SPR-8455
throw new IllegalArgumentException("The number of capturing groups in the pattern segment "
+ this.pattern + " does not match the number of URI template variables it defines, "
+ "which can occur if capturing groups are used in a URI template regex. "
+ "Use non-capturing groups instead.");
if (this.variableNames.size() != matcher.groupCount()) { // SPR-8455
throw new IllegalArgumentException("The number of capturing groups in the pattern segment " +
this.pattern + " does not match the number of URI template variables it defines, " +
"which can occur if capturing groups are used in a URI template regex. " +
"Use non-capturing groups instead.");
}
for (int i = 1; i <= matcher.groupCount(); i++) {
String name = this.variableNames.get(i - 1);
@@ -187,6 +186,11 @@ class RegexPathElement extends PathElement {
return (this.regex.length - varsLength - this.variableNames.size());
}
@Override
public char[] getChars() {
return this.regex;
}
@Override
public int getCaptureCount() {
return this.variableNames.size();
@@ -208,8 +212,4 @@ class RegexPathElement extends PathElement {
return "Regex(" + String.valueOf(this.regex) + ")";
}
@Override
public char[] getChars() {
return this.regex;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -62,14 +62,15 @@ class SeparatorPathElement extends PathElement {
return 1;
}
@Override
public String toString() {
return "Separator(" + this.separator + ")";
}
@Override
public char[] getChars() {
return new char[] {this.separator};
}
@Override
public String toString() {
return "Separator(" + this.separator + ")";
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -74,7 +74,7 @@ class SingleCharWildcardedPathElement extends PathElement {
return false;
}
char[] data = ((PathSegment)element).valueToMatchAsChars();
char[] data = ((PathSegment) element).valueToMatchAsChars();
if (this.caseSensitive) {
for (int i = 0; i < this.len; i++) {
char ch = this.text[i];
@@ -125,15 +125,15 @@ class SingleCharWildcardedPathElement extends PathElement {
return this.len;
}
@Override
public char[] getChars() {
return this.text;
}
@Override
public String toString() {
return "SingleCharWildcarded(" + String.valueOf(this.text) + ")";
}
@Override
public char[] getChars() {
return this.text;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -86,6 +86,11 @@ class WildcardPathElement extends PathElement {
return 1;
}
@Override
public char[] getChars() {
return new char[] {'*'};
}
@Override
public int getWildcardCount() {
return 1;
@@ -102,8 +107,4 @@ class WildcardPathElement extends PathElement {
return "Wildcard(*)";
}
@Override
public char[] getChars() {
return new char[] {'*'};
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 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,6 +47,11 @@ class WildcardTheRestPathElement extends PathElement {
return 1;
}
@Override
public char[] getChars() {
return (this.separator + "**").toCharArray();
}
@Override
public int getWildcardCount() {
return 1;
@@ -58,8 +63,4 @@ class WildcardTheRestPathElement extends PathElement {
return "WildcardTheRest(" + this.separator + "**)";
}
@Override
public char[] getChars() {
return (this.separator+"**").toCharArray();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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,10 +19,8 @@ package org.springframework.web.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -75,25 +73,7 @@ public class DefaultResponseErrorHandlerTests {
assertThatExceptionOfType(HttpClientErrorException.class)
.isThrownBy(() -> handler.handleError(response))
.satisfies(ex -> assertThat(ex.getResponseHeaders()).isSameAs(headers))
.satisfies(ex -> assertThat(ex.getMessage()).isEqualTo("404 Not Found: [Hello World]"));
}
@Test
public void handleErrorWithLongBody() throws Exception {
Function<Integer, String> bodyGenerator =
size -> Flux.just("a").repeat(size-1).reduce((s, s2) -> s + s2).block();
given(response.getRawStatusCode()).willReturn(HttpStatus.NOT_FOUND.value());
given(response.getStatusText()).willReturn("Not Found");
given(response.getHeaders()).willReturn(new HttpHeaders());
given(response.getBody()).willReturn(
new ByteArrayInputStream(bodyGenerator.apply(500).getBytes(StandardCharsets.UTF_8)));
assertThatExceptionOfType(HttpClientErrorException.class)
.isThrownBy(() -> handler.handleError(response))
.satisfies(ex -> assertThat(ex.getMessage()).isEqualTo(
"404 Not Found: [" + bodyGenerator.apply(200) + "... (500 bytes)]"));
.withMessage("404 Not Found: \"Hello World\"");
}
@Test
@@ -187,13 +187,7 @@ class DefaultClientResponse implements ClientResponse {
@Override
public Mono<WebClientResponseException> createException() {
return DataBufferUtils.join(body(BodyExtractors.toDataBuffers()))
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
})
return bodyToMono(byte[].class)
.defaultIfEmpty(new byte[0])
.map(bodyBytes -> {
HttpRequest request = this.requestSupplier.get();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -72,7 +72,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Mono<Resourc
try {
Resource resource = this.location.createRelative(path);
if (resource.exists() && resource.isReadable() && isResourceUnderLocation(resource)) {
if (resource.isReadable() && isResourceUnderLocation(resource)) {
return Mono.just(resource);
}
else {
@@ -29,6 +29,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
@@ -119,11 +120,12 @@ public class PathResourceResolver extends AbstractResourceResolver {
return Mono.just(resource);
}
else if (logger.isWarnEnabled()) {
Resource[] allowedLocations = getAllowedLocations();
logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " +
"but resource \"" + resource.getURL() + "\" is neither under the " +
"current location \"" + location.getURL() + "\" nor under any of the " +
"allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
Resource[] allowed = getAllowedLocations();
logger.warn(LogFormatUtils.formatValue(
"Resource path \"" + resourcePath + "\" was successfully resolved " +
"but resource \"" + resource.getURL() + "\" is neither under the " +
"current location \"" + location.getURL() + "\" nor under any of the " +
"allowed locations " + (allowed != null ? Arrays.asList(allowed) : "[]"), -1, true));
}
}
return Mono.empty();
@@ -199,7 +201,8 @@ public class PathResourceResolver extends AbstractResourceResolver {
try {
String decodedPath = URLDecoder.decode(resourcePath, "UTF-8");
if (decodedPath.contains("../") || decodedPath.contains("..\\")) {
logger.warn("Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath);
logger.warn(LogFormatUtils.formatValue(
"Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath, -1, true));
return true;
}
}
@@ -36,6 +36,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.core.codec.Hints;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
@@ -490,7 +491,8 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
protected boolean isInvalidPath(String path) {
if (path.contains("WEB-INF") || path.contains("META-INF")) {
if (logger.isWarnEnabled()) {
logger.warn("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]");
logger.warn(LogFormatUtils.formatValue(
"Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true));
}
return true;
}
@@ -498,14 +500,16 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);
if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
if (logger.isWarnEnabled()) {
logger.warn("Path represents URL or has \"url:\" prefix: [" + path + "]");
logger.warn(LogFormatUtils.formatValue(
"Path represents URL or has \"url:\" prefix: [" + path + "]", -1, true));
}
return true;
}
}
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
if (logger.isWarnEnabled()) {
logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]");
logger.warn(LogFormatUtils.formatValue(
"Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true));
}
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -53,7 +53,7 @@ import org.springframework.web.util.UriUtils;
*/
public class RedirectView extends AbstractUrlBasedView {
private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)}");
private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
private HttpStatus statusCode = HttpStatus.SEE_OTHER;
@@ -30,6 +30,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.codec.ByteArrayDecoder;
import org.springframework.core.codec.StringDecoder;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DefaultDataBuffer;
@@ -48,6 +49,7 @@ import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.api.Assertions.entry;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.springframework.web.reactive.function.BodyExtractors.toMono;
@@ -340,6 +342,33 @@ public class DefaultClientResponseTests {
assertThat(result.getHeaders().getContentType()).isEqualTo(MediaType.TEXT_PLAIN);
}
@Test
public void createException() {
DefaultDataBufferFactory factory = new DefaultDataBufferFactory();
byte[] bytes = "foo".getBytes(StandardCharsets.UTF_8);
DefaultDataBuffer dataBuffer = factory.wrap(ByteBuffer.wrap(bytes));
Flux<DataBuffer> body = Flux.just(dataBuffer);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_PLAIN);
given(mockResponse.getHeaders()).willReturn(httpHeaders);
given(mockResponse.getStatusCode()).willReturn(HttpStatus.NOT_FOUND);
given(mockResponse.getRawStatusCode()).willReturn(HttpStatus.NOT_FOUND.value());
given(mockResponse.getBody()).willReturn(body);
List<HttpMessageReader<?>> messageReaders = Collections.singletonList(
new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
given(mockExchangeStrategies.messageReaders()).willReturn(messageReaders);
Mono<WebClientResponseException> resultMono = defaultClientResponse.createException();
WebClientResponseException exception = resultMono.block();
assertThat(exception.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
assertThat(exception.getMessage()).isEqualTo("404 Not Found");
assertThat(exception.getHeaders()).containsExactly(entry("Content-Type",
Collections.singletonList("text/plain")));
assertThat(exception.getResponseBodyAsByteArray()).isEqualTo(bytes);
}
private void mockTextPlainResponse(Flux<DataBuffer> body) {
HttpHeaders httpHeaders = new HttpHeaders();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 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.
@@ -61,9 +61,6 @@ public class FreeMarkerConfigurerBeanDefinitionParser extends AbstractSingleBean
for (Element childElement : childElements) {
locations.add(childElement.getAttribute("location"));
}
if (locations.isEmpty()) {
locations.add("/WEB-INF/");
}
builder.addPropertyValue("templateLoaderPaths", StringUtils.toStringArray(locations));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -71,7 +71,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
try {
Resource resource = this.location.createRelative(path);
if (resource.exists() && resource.isReadable() && isResourceUnderLocation(resource)) {
if (resource.isReadable() && isResourceUnderLocation(resource)) {
return Optional.of(resource);
}
else {
@@ -110,10 +110,7 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
return true;
}
}
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
return true;
}
return false;
return path.contains("..") && StringUtils.cleanPath(path).contains("../");
}
private boolean isResourceUnderLocation(Resource resource) throws IOException {
@@ -144,10 +141,8 @@ class PathResourceLookupFunction implements Function<ServerRequest, Optional<Res
if (!resourcePath.startsWith(locationPath)) {
return false;
}
if (resourcePath.contains("%") && StringUtils.uriDecode(resourcePath, StandardCharsets.UTF_8).contains("../")) {
return false;
}
return true;
return !resourcePath.contains("%") ||
!StringUtils.uriDecode(resourcePath, StandardCharsets.UTF_8).contains("../");
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -25,6 +25,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerExceptionResolver;
@@ -142,7 +143,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
if (result != null) {
// Print debug message when warn logger is not enabled.
if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {
logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result));
logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));
}
// Explicitly configured warn logger in logException method.
logException(ex, request);
@@ -207,7 +208,7 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti
* @return the log message to use
*/
protected String buildLogMessage(Exception ex, HttpServletRequest request) {
return "Resolved [" + ex + "]";
return "Resolved [" + LogFormatUtils.formatValue(ex, -1, true) + "]";
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2021 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.
@@ -67,9 +67,8 @@ public class ExtendedServletRequestDataBinder extends ServletRequestDataBinder {
if (uriVars != null) {
uriVars.forEach((name, value) -> {
if (mpvs.contains(name)) {
if (logger.isWarnEnabled()) {
logger.warn("Skipping URI variable '" + name +
"' because request contains bind value with same name.");
if (logger.isDebugEnabled()) {
logger.debug("URI variable '" + name + "' overridden by request bind value.");
}
}
else {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -193,7 +193,7 @@ class ReactiveTypeHandler {
"-------------------------------\n" +
"Controller:\t" + returnType.getContainingClass().getName() + "\n" +
"Method:\t\t" + returnType.getMethod().getName() + "\n" +
"Returning:\t" + ResolvableType.forMethodParameter(returnType).toString() + "\n" +
"Returning:\t" + ResolvableType.forMethodParameter(returnType) + "\n" +
"!!!");
this.taskExecutorWarning = false;
}
@@ -33,6 +33,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ServletContextResource;
@@ -188,11 +189,12 @@ public class PathResourceResolver extends AbstractResourceResolver {
return resource;
}
else if (logger.isWarnEnabled()) {
Resource[] allowedLocations = getAllowedLocations();
logger.warn("Resource path \"" + resourcePath + "\" was successfully resolved " +
"but resource \"" + resource.getURL() + "\" is neither under the " +
"current location \"" + location.getURL() + "\" nor under any of the " +
"allowed locations " + (allowedLocations != null ? Arrays.asList(allowedLocations) : "[]"));
Resource[] allowed = getAllowedLocations();
logger.warn(LogFormatUtils.formatValue(
"Resource path \"" + resourcePath + "\" was successfully resolved " +
"but resource \"" + resource.getURL() + "\" is neither under " +
"the current location \"" + location.getURL() + "\" nor under any of " +
"the allowed locations " + (allowed != null ? Arrays.asList(allowed) : "[]"), -1, true));
}
}
return null;
@@ -285,7 +287,8 @@ public class PathResourceResolver extends AbstractResourceResolver {
try {
String decodedPath = URLDecoder.decode(resourcePath, "UTF-8");
if (decodedPath.contains("../") || decodedPath.contains("..\\")) {
logger.warn("Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath);
logger.warn(LogFormatUtils.formatValue(
"Resolved resource path contains encoded \"../\" or \"..\\\": " + resourcePath, -1, true));
return true;
}
}
@@ -40,6 +40,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRange;
@@ -662,7 +663,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
protected boolean isInvalidPath(String path) {
if (path.contains("WEB-INF") || path.contains("META-INF")) {
if (logger.isWarnEnabled()) {
logger.warn("Path with \"WEB-INF\" or \"META-INF\": [" + path + "]");
logger.warn(LogFormatUtils.formatValue(
"Path with \"WEB-INF\" or \"META-INF\": [" + path + "]", -1, true));
}
return true;
}
@@ -670,14 +672,16 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
String relativePath = (path.charAt(0) == '/' ? path.substring(1) : path);
if (ResourceUtils.isUrl(relativePath) || relativePath.startsWith("url:")) {
if (logger.isWarnEnabled()) {
logger.warn("Path represents URL or has \"url:\" prefix: [" + path + "]");
logger.warn(LogFormatUtils.formatValue(
"Path represents URL or has \"url:\" prefix: [" + path + "]", -1, true));
}
return true;
}
}
if (path.contains("..") && StringUtils.cleanPath(path).contains("../")) {
if (logger.isWarnEnabled()) {
logger.warn("Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]");
logger.warn(LogFormatUtils.formatValue(
"Path contains \"../\" after call to StringUtils#cleanPath: [" + path + "]", -1, true));
}
return true;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 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.
@@ -65,7 +65,7 @@ public class ResourceHandlerRegistryTests {
@BeforeEach
public void setUp() {
public void setup() {
GenericWebApplicationContext appContext = new GenericWebApplicationContext();
appContext.refresh();
@@ -77,8 +77,14 @@ public class ResourceHandlerRegistryTests {
this.response = new MockHttpServletResponse();
}
private ResourceHttpRequestHandler getHandler(String pathPattern) {
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
return (ResourceHttpRequestHandler) hm.getUrlMap().get(pathPattern);
}
@Test
public void noResourceHandlers() throws Exception {
public void noResourceHandlers() {
this.registry = new ResourceHandlerRegistry(new GenericWebApplicationContext(), new MockServletContext());
assertThat((Object) this.registry.getHandlerMapping()).isNull();
}
@@ -127,7 +133,7 @@ public class ResourceHandlerRegistryTests {
}
@Test
public void resourceChain() throws Exception {
public void resourceChain() {
ResourceResolver mockResolver = Mockito.mock(ResourceResolver.class);
ResourceTransformer mockTransformer = Mockito.mock(ResourceTransformer.class);
this.registration.resourceChain(true).addResolver(mockResolver).addTransformer(mockTransformer);
@@ -149,7 +155,7 @@ public class ResourceHandlerRegistryTests {
}
@Test
public void resourceChainWithoutCaching() throws Exception {
public void resourceChainWithoutCaching() {
this.registration.resourceChain(false);
ResourceHttpRequestHandler handler = getHandler("/resources/**");
@@ -163,7 +169,7 @@ public class ResourceHandlerRegistryTests {
}
@Test
public void resourceChainWithVersionResolver() throws Exception {
public void resourceChainWithVersionResolver() {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addFixedVersionStrategy("fixed", "/**/*.js")
.addContentVersionStrategy("/**");
@@ -187,7 +193,7 @@ public class ResourceHandlerRegistryTests {
}
@Test
public void resourceChainWithOverrides() throws Exception {
public void resourceChainWithOverrides() {
CachingResourceResolver cachingResolver = Mockito.mock(CachingResourceResolver.class);
VersionResourceResolver versionResolver = Mockito.mock(VersionResourceResolver.class);
WebJarsResourceResolver webjarsResolver = Mockito.mock(WebJarsResourceResolver.class);
@@ -222,7 +228,7 @@ public class ResourceHandlerRegistryTests {
}
@Test
public void urlResourceWithCharset() throws Exception {
public void urlResourceWithCharset() {
this.registration.addResourceLocations("[charset=ISO-8859-1]file:///tmp");
this.registration.resourceChain(true);
@@ -238,9 +244,4 @@ public class ResourceHandlerRegistryTests {
assertThat(locationCharsets.values().iterator().next()).isEqualTo(StandardCharsets.ISO_8859_1);
}
private ResourceHttpRequestHandler getHandler(String pathPattern) {
SimpleUrlHandlerMapping hm = (SimpleUrlHandlerMapping) this.registry.getHandlerMapping();
return (ResourceHttpRequestHandler) hm.getUrlMap().get(pathPattern);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -286,7 +286,6 @@ public class ResourceHttpRequestHandlerTests {
@Test // SPR-14368
public void getResourceWithMediaTypeResolvedThroughServletContext() throws Exception {
MockServletContext servletContext = new MockServletContext() {
@Override
public String getMimeType(String filePath) {
@@ -310,7 +309,6 @@ public class ResourceHttpRequestHandlerTests {
@Test
public void testInvalidPath() throws Exception {
// Use mock ResourceResolver: i.e. we're only testing upfront validations...
Resource resource = mock(Resource.class);
@@ -656,7 +654,7 @@ public class ResourceHttpRequestHandlerTests {
assertThat(ranges[11]).isEqualTo("t.");
}
@Test // gh-25976
@Test // gh-25976
public void partialContentByteRangeWithEncodedResource(GzipSupport.GzippedFiles gzippedFiles) throws Exception {
String path = "js/foo.js";
gzippedFiles.create(path);
@@ -685,7 +683,7 @@ public class ResourceHttpRequestHandlerTests {
assertThat(this.response.getHeaderValues("Vary")).containsExactly("Accept-Encoding");
}
@Test // gh-25976
@Test // gh-25976
public void partialContentWithHttpHead() throws Exception {
this.request.setMethod("HEAD");
this.request.addHeader("Range", "bytes=0-1");
@@ -29,6 +29,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.Lifecycle;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
@@ -293,7 +294,8 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
protected void handleInvalidUpgradeHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (logger.isErrorEnabled()) {
logger.error("Handshake failed due to invalid Upgrade header: " + request.getHeaders().getUpgrade());
logger.error(LogFormatUtils.formatValue(
"Handshake failed due to invalid Upgrade header: " + request.getHeaders().getUpgrade(), -1, true));
}
response.setStatusCode(HttpStatus.BAD_REQUEST);
response.getBody().write("Can \"Upgrade\" only to \"WebSocket\".".getBytes(StandardCharsets.UTF_8));
@@ -301,7 +303,8 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
protected void handleInvalidConnectHeader(ServerHttpRequest request, ServerHttpResponse response) throws IOException {
if (logger.isErrorEnabled()) {
logger.error("Handshake failed due to invalid Connection header " + request.getHeaders().getConnection());
logger.error(LogFormatUtils.formatValue(
"Handshake failed due to invalid Connection header" + request.getHeaders().getConnection(), -1, true));
}
response.setStatusCode(HttpStatus.BAD_REQUEST);
response.getBody().write("\"Connection\" must be \"upgrade\".".getBytes(StandardCharsets.UTF_8));
@@ -325,8 +328,9 @@ public abstract class AbstractHandshakeHandler implements HandshakeHandler, Life
protected void handleWebSocketVersionNotSupported(ServerHttpRequest request, ServerHttpResponse response) {
if (logger.isErrorEnabled()) {
String version = request.getHeaders().getFirst("Sec-WebSocket-Version");
logger.error("Handshake failed due to unsupported WebSocket version: " + version +
". Supported versions: " + Arrays.toString(getSupportedVersions()));
logger.error(LogFormatUtils.formatValue(
"Handshake failed due to unsupported WebSocket version: " + version +
". Supported versions: " + Arrays.toString(getSupportedVersions()), -1, true));
}
response.setStatusCode(HttpStatus.UPGRADE_REQUIRED);
response.getHeaders().set(WebSocketHttpHeaders.SEC_WEBSOCKET_VERSION,
@@ -34,6 +34,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
@@ -340,7 +341,8 @@ public abstract class AbstractSockJsService implements SockJsService, CorsConfig
if (sockJsPath == null) {
if (logger.isWarnEnabled()) {
logger.warn("Expected SockJS path. Failing request: " + request.getURI());
logger.warn(LogFormatUtils.formatValue(
"Expected SockJS path. Failing request: " + request.getURI(), -1, true));
}
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
@@ -405,7 +407,8 @@ public abstract class AbstractSockJsService implements SockJsService, CorsConfig
String[] pathSegments = StringUtils.tokenizeToStringArray(sockJsPath.substring(1), "/");
if (pathSegments.length != 3) {
if (logger.isWarnEnabled()) {
logger.warn("Invalid SockJS path '" + sockJsPath + "' - required to have 3 path segments");
logger.warn(LogFormatUtils.formatValue("Invalid SockJS path '" + sockJsPath + "' - " +
"required to have 3 path segments", -1, true));
}
if (requestInfo != null) {
logger.debug("Ignoring transport request: " + requestInfo);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 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.
@@ -30,6 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import org.springframework.context.Lifecycle;
import org.springframework.core.log.LogFormatUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServerHttpRequest;
@@ -234,7 +235,7 @@ public class TransportHandlingSockJsService extends AbstractSockJsService implem
TransportType transportType = TransportType.fromValue(transport);
if (transportType == null) {
if (logger.isWarnEnabled()) {
logger.warn("Unknown transport type for " + request.getURI());
logger.warn(LogFormatUtils.formatValue("Unknown transport type for " + request.getURI(), -1, true));
}
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
@@ -243,7 +244,7 @@ public class TransportHandlingSockJsService extends AbstractSockJsService implem
TransportHandler transportHandler = this.handlers.get(transportType);
if (transportHandler == null) {
if (logger.isWarnEnabled()) {
logger.warn("No TransportHandler for " + request.getURI());
logger.warn(LogFormatUtils.formatValue("No TransportHandler for " + request.getURI(), -1, true));
}
response.setStatusCode(HttpStatus.NOT_FOUND);
return;
+80 -68
View File
@@ -1024,7 +1024,7 @@ by type without help. Consider the following class:
class ExampleBean(
private val years: Int, // Number of years to calculate the Ultimate Answer
private val ultimateAnswer: String// The Answer to Life, the Universe, and Everything
private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)
----
@@ -1608,7 +1608,7 @@ listings shows how to use the `parent` attribute:
----
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required as here -->
<!-- insert dependencies as required here -->
</bean>
----
@@ -1690,7 +1690,7 @@ respectively. The following example shows how to use them:
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key ="a ref" value-ref="myDataSource"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
@@ -1834,7 +1834,7 @@ class SomeClass {
When the `accounts` property of the `something` bean is prepared for injection, the generics
information about the element type of the strongly-typed `Map<String, Float>` is
available by reflection. Thus, Spring's type conversion infrastructure recognizes the
various value elements as being of type `Float`, and the string values (`9.99, 2.75`, and
various value elements as being of type `Float`, and the string values (`9.99`, `2.75`, and
`3.99`) are converted into an actual `Float` type.
@@ -2028,7 +2028,7 @@ not commonly used since the plain order of declaration is usually sufficient the
In practice, the constructor resolution
<<beans-factory-ctor-arguments-resolution,mechanism>> is quite efficient in matching
arguments, so unless you really need to, we recommend using the name notation
through-out your configuration.
throughout your configuration.
[[beans-compound-property-names]]
@@ -2503,13 +2503,13 @@ declared return type of the lookup method:
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
protected abstract Command createCommand();
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@@ -2604,9 +2604,9 @@ interface provides the new method definition, as the following example shows:
.Kotlin
----
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
class ReplacementComputeValue : MethodReplacer {
override fun reimplement(obj: Any, method: Method, args: Array<out Any>): Any {
@@ -2806,7 +2806,7 @@ prototype-scoped bean repeatedly at runtime. You cannot dependency-inject a
prototype-scoped bean into your singleton bean, because that injection occurs only
once, when the Spring container instantiates the singleton bean and resolves
and injects its dependencies. If you need a new instance of a prototype bean at
runtime more than once, see <<beans-factory-method-injection>>
runtime more than once, see <<beans-factory-method-injection>>.
@@ -2984,7 +2984,7 @@ The Spring container creates a new instance of the `AppPreferences` bean by usin
`appPreferences` bean is scoped at the `ServletContext` level and stored as a regular
`ServletContext` attribute. This is somewhat similar to a Spring singleton bean but
differs in two important ways: It is a singleton per `ServletContext`, not per Spring
'ApplicationContext' (for which there may be several in any given web application),
`ApplicationContext` (for which there may be several in any given web application),
and it is actually exposed and therefore visible as a `ServletContext` attribute.
When using annotation-driven components or Java configuration, you can use the
@@ -3039,7 +3039,7 @@ constructor or setter argument or autowired field) as `ObjectFactory<MyTargetBea
allowing for a `getObject()` call to retrieve the current instance on demand every
time it is needed -- without holding on to the instance or storing it separately.
As an extended variant, you may declare `ObjectProvider<MyTargetBean>`, which delivers
As an extended variant, you may declare `ObjectProvider<MyTargetBean>` which delivers
several additional access variants, including `getIfAvailable` and `getIfUnique`.
The JSR-330 variant of this is called `Provider` and is used with a `Provider<MyTargetBean>`
@@ -3100,7 +3100,7 @@ to the HTTP `Session`-scoped bean (`userPreferences`). The salient point here is
`userManager` bean is a singleton: it is instantiated exactly once per
container, and its dependencies (in this case only one, the `userPreferences` bean) are
also injected only once. This means that the `userManager` bean operates only on the
exact same `userPreferences` object (that is, the one with which it was originally injected.
exact same `userPreferences` object (that is, the one with which it was originally injected).
This is not the behavior you want when injecting a shorter-lived scoped bean into a
longer-lived scoped bean (for example, injecting an HTTP `Session`-scoped collaborating
@@ -3350,8 +3350,9 @@ of the scope. You can also do the `Scope` registration declaratively, by using t
</beans>
----
NOTE: When you place `<aop:scoped-proxy/>` in a `FactoryBean` implementation, it is the factory
bean itself that is scoped, not the object returned from `getObject()`.
NOTE: When you place `<aop:scoped-proxy/>` within a `<bean>` declaration for a
`FactoryBean` implementation, it is the factory bean itself that is scoped, not the object
returned from `getObject()`.
@@ -3927,7 +3928,7 @@ shows the definition of the BeanNameAware interface:
----
The callback is invoked after population of normal bean properties but before an
initialization callback such as `InitializingBean`, `afterPropertiesSet`, or a custom
initialization callback such as `InitializingBean.afterPropertiesSet()` or a custom
init-method.
@@ -3959,7 +3960,7 @@ dependency type. The following table summarizes the most important `Aware` inter
| `BeanFactoryAware`
| Declaring `BeanFactory`.
| <<beans-factory-aware>>
| <<beans-beanfactory>>
| `BeanNameAware`
| Name of the declaring bean.
@@ -4146,7 +4147,7 @@ or it may wrap a bean with a proxy. Some Spring AOP infrastructure classes are
implemented as bean post-processors in order to provide proxy-wrapping logic.
An `ApplicationContext` automatically detects any beans that are defined in the
configuration metadata that implements the `BeanPostProcessor` interface. The
configuration metadata that implement the `BeanPostProcessor` interface. The
`ApplicationContext` registers these beans as post-processors so that they can be called
later, upon bean creation. Bean post-processors can be deployed in the container in the
same fashion as any other beans.
@@ -4544,22 +4545,22 @@ Java as opposed to a (potentially) verbose amount of XML, you can create your ow
`FactoryBean`, write the complex initialization inside that class, and then plug your
custom `FactoryBean` into the container.
The `FactoryBean` interface provides three methods:
The `FactoryBean<T>` interface provides three methods:
* `Object getObject()`: Returns an instance of the object this factory creates. The
* `T getObject()`: Returns an instance of the object this factory creates. The
instance can possibly be shared, depending on whether this factory returns singletons
or prototypes.
* `boolean isSingleton()`: Returns `true` if this `FactoryBean` returns singletons or
`false` otherwise.
* `Class getObjectType()`: Returns the object type returned by the `getObject()` method
`false` otherwise. The default implementation of this method returns `true`.
* `Class<?> getObjectType()`: Returns the object type returned by the `getObject()` method
or `null` if the type is not known in advance.
The `FactoryBean` concept and interface is used in a number of places within the Spring
The `FactoryBean` concept and interface are used in a number of places within the Spring
Framework. More than 50 implementations of the `FactoryBean` interface ship with Spring
itself.
When you need to ask a container for an actual `FactoryBean` instance itself instead of
the bean it produces, preface the bean's `id` with the ampersand symbol (`&`) when
the bean it produces, prefix the bean's `id` with the ampersand symbol (`&`) when
calling the `getBean()` method of the `ApplicationContext`. So, for a given `FactoryBean`
with an `id` of `myBean`, invoking `getBean("myBean")` on the container returns the
product of the `FactoryBean`, whereas invoking `getBean("&myBean")` returns the
@@ -4677,11 +4678,11 @@ example:
----
class SimpleMovieLister {
@Required
lateinit var movieFinder: MovieFinder
@Required
lateinit var movieFinder: MovieFinder
// ...
}
// ...
}
----
This annotation indicates that the affected bean property must be populated at
@@ -5377,7 +5378,7 @@ Letting qualifier values select against target bean names, within the type-match
candidates, does not require a `@Qualifier` annotation at the injection point.
If there is no other resolution indicator (such as a qualifier or a primary marker),
for a non-unique dependency situation, Spring matches the injection point name
(that is, the field name or parameter name) against the target bean names and choose the
(that is, the field name or parameter name) against the target bean names and chooses the
same-named candidate, if any.
====
@@ -5392,7 +5393,7 @@ matching an `account` qualifier against beans marked with the same qualifier lab
For beans that are themselves defined as a collection, `Map`, or array type, `@Resource`
is a fine solution, referring to the specific collection or array bean by unique name.
That said, as of 4.3, collection, you can match `Map`, and array types through Spring's
That said, as of 4.3, you can match collection, `Map`, and array types through Spring's
`@Autowired` type matching algorithm as well, as long as the element type information
is preserved in `@Bean` return type signatures or collection inheritance hierarchies.
In this case, you can use qualifier values to select among same-typed collections,
@@ -6046,14 +6047,14 @@ example shows:
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
.Java
----
@Configuration
public class AppConfig {
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -6079,7 +6080,7 @@ will get properties from `application.properties` and `application.yml` files.
Built-in converter support provided by Spring allows simple type conversion (to `Integer`
or `int` for example) to be automatically handled. Multiple comma-separated values can be
automatically converted to String array without extra effort.
automatically converted to `String` array without extra effort.
It is possible to provide a default value as following:
@@ -6103,8 +6104,8 @@ It is possible to provide a default value as following:
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
----
A Spring `BeanPostProcessor` uses a `ConversionService` behind the scene to handle the
process for converting the String value in `@Value` to the target type. If you want to
A Spring `BeanPostProcessor` uses a `ConversionService` behind the scenes to handle the
process for converting the `String` value in `@Value` to the target type. If you want to
provide conversion support for your own custom type, you can provide your own
`ConversionService` bean instance as the following example shows:
@@ -6130,7 +6131,7 @@ provide conversion support for your own custom type, you can provide your own
@Bean
fun conversionService(): ConversionService {
return DefaultFormattingConversionService().apply {
return DefaultFormattingConversionService().apply {
addConverter(MyCustomConverter())
}
}
@@ -6319,7 +6320,7 @@ is meta-annotated with `@Component`, as the following example shows:
// ...
}
----
<1> The `Component` causes `@Service` to be treated in the same way as `@Component`.
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
.Kotlin
@@ -6333,7 +6334,7 @@ is meta-annotated with `@Component`, as the following example shows:
// ...
}
----
<1> The `Component` causes `@Service` to be treated in the same way as `@Component`.
<1> The `@Component` causes `@Service` to be treated in the same way as `@Component`.
You can also combine meta-annotations to create "`composed annotations`". For example,
the `@RestController` annotation from Spring MVC is composed of `@Controller` and
@@ -6595,7 +6596,7 @@ and using "`stub`" repositories instead:
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
// ...
}
----
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
@@ -6679,9 +6680,11 @@ factory method and other bean definition properties, such as a qualifier value t
the `@Qualifier` annotation. Other method-level annotations that can be specified are
`@Scope`, `@Lazy`, and custom qualifier annotations.
TIP: In addition to its role for component initialization, you can also place the `@Lazy` annotation
on injection points marked with `@Autowired` or `@Inject`. In this context, it
leads to the injection of a lazy-resolution proxy.
TIP: In addition to its role for component initialization, you can also place the `@Lazy`
annotation on injection points marked with `@Autowired` or `@Inject`. In this context,
it leads to the injection of a lazy-resolution proxy. However, such a proxy approach
is rather limited. For sophisticated lazy interactions, in particular in combination
with optional dependencies, we recommend `ObjectProvider<MyTargetBean>` instead.
Autowired fields and methods are supported, as previously discussed, with additional
support for autowiring of `@Bean` methods. The following example shows how to do so:
@@ -7161,7 +7164,7 @@ configuration, as shown in the following example:
With Gradle 4.6 and later, the dependency should be declared in the `annotationProcessor`
configuration, as shown in the following example:
[source,groovy,indent=0subs="verbatim,quotes,attributes"]
[source,groovy,indent=0,subs="verbatim,quotes,attributes"]
----
dependencies {
annotationProcessor "org.springframework:spring-context-indexer:{spring-version}"
@@ -7179,8 +7182,8 @@ TIP: The index is enabled automatically when a `META-INF/spring.components` file
on the classpath. If an index is partially available for some libraries (or use cases)
but could not be built for the whole application, you can fall back to a regular classpath
arrangement (as though no index were present at all) by setting `spring.index.ignore` to
`true`, either as a JVM system property or in a `spring.properties` file at the root of
the classpath.
`true`, either as a JVM system property or via the
<<appendix.adoc#appendix-spring-properties,`SpringProperties`>> mechanism.
@@ -7635,7 +7638,7 @@ to reduce subtle bugs that can be hard to track down when operating in "`lite`"
****
The `@Bean` and `@Configuration` annotations are discussed in depth in the following sections.
First, however, we cover the various ways of creating a spring container using by
First, however, we cover the various ways of creating a spring container by using
Java-based configuration.
@@ -7762,7 +7765,7 @@ To enable component scanning, you can annotate your `@Configuration` class as fo
@Configuration
@ComponentScan(basePackages = "com.acme") // <1>
public class AppConfig {
...
// ...
}
----
<1> This annotation enables component scanning.
@@ -7896,6 +7899,7 @@ init-param):
`@Bean` is a method-level annotation and a direct analog of the XML `<bean/>` element.
The annotation supports some of the attributes offered by `<bean/>`, such as:
* <<beans-factory-lifecycle-initializingbean, init-method>>
* <<beans-factory-lifecycle-disposablebean, destroy-method>>
* <<beans-factory-autowire,autowiring>>
@@ -7984,7 +7988,7 @@ return type, as the following example shows:
However, this limits the visibility for advance type prediction to the specified
interface type (`TransferService`). Then, with the full type (`TransferServiceImpl`)
known to the container only once, the affected singleton bean has been instantiated.
known to the container only once the affected singleton bean has been instantiated.
Non-lazy singleton beans get instantiated according to their declaration order,
so you may see different type matching results depending on when another component
tries to match by a non-declared type (such as `@Autowired TransferServiceImpl`,
@@ -8242,8 +8246,10 @@ Spring offers a convenient way of working with scoped dependencies through
<<beans-factory-scopes-other-injection, scoped proxies>>. The easiest way to create
such a proxy when using the XML configuration is the `<aop:scoped-proxy/>` element.
Configuring your beans in Java with a `@Scope` annotation offers equivalent support
with the `proxyMode` attribute. The default is no proxy (`ScopedProxyMode.NO`),
but you can specify `ScopedProxyMode.TARGET_CLASS` or `ScopedProxyMode.INTERFACES`.
with the `proxyMode` attribute. The default is `ScopedProxyMode.DEFAULT`, which
typically indicates that no scoped proxy should be created unless a different default
has been configured at the component-scan instruction level. You can specify
`ScopedProxyMode.TARGET_CLASS`, `ScopedProxyMode.INTERFACES` or `ScopedProxyMode.NO`.
If you port the scoped proxy example from the XML reference documentation (see
<<beans-factory-scopes-other-injection, scoped proxies>>) to our `@Bean` using Java,
@@ -8297,7 +8303,7 @@ as the following example shows:
@Configuration
public class AppConfig {
@Bean(name = "myThing")
@Bean("myThing")
public Thing thing() {
return new Thing();
}
@@ -8390,7 +8396,7 @@ annotation, as the following example shows:
=== Using the `@Configuration` annotation
`@Configuration` is a class-level annotation indicating that an object is a source of
bean definitions. `@Configuration` classes declare beans through public `@Bean` annotated
bean definitions. `@Configuration` classes declare beans through `@Bean`-annotated
methods. Calls to `@Bean` methods on `@Configuration` classes can also be used to define
inter-bean dependencies. See <<beans-java-basic-concepts>> for a general introduction.
@@ -9139,7 +9145,7 @@ method that returns `true` or `false`. For example, the following listing shows
val attrs = metadata.getAllAnnotationAttributes(Profile::class.java.name)
if (attrs != null) {
for (value in attrs["value"]!!) {
if (context.environment.acceptsProfiles(Profiles .of(*value as Array<String>))) {
if (context.environment.acceptsProfiles(Profiles.of(*value as Array<String>))) {
return true
}
}
@@ -9903,7 +9909,7 @@ as a way to provide a default definition for one or more beans. If any
profile is enabled, the default profile does not apply.
You can change the name of the default profile by using `setDefaultProfiles()` on
the `Environment` or ,declaratively, by using the `spring.profiles.default` property.
the `Environment` or, declaratively, by using the `spring.profiles.default` property.
@@ -10222,8 +10228,8 @@ bean with the same name. If it does, it uses that bean as the `MessageSource`. I
`DelegatingMessageSource` is instantiated in order to be able to accept calls to the
methods defined above.
Spring provides two `MessageSource` implementations, `ResourceBundleMessageSource` and
`StaticMessageSource`. Both implement `HierarchicalMessageSource` in order to do nested
Spring provides three `MessageSource` implementations, `ResourceBundleMessageSource`, `ReloadableResourceBundleMessageSource`
and `StaticMessageSource`. All of them implement `HierarchicalMessageSource` in order to do nested
messaging. The `StaticMessageSource` is rarely used but provides programmatic ways to
add messages to the source. The following example shows `ResourceBundleMessageSource`:
@@ -10408,6 +10414,10 @@ You can also use the `MessageSourceAware` interface to acquire a reference to an
`ApplicationContext` that implements the `MessageSourceAware` interface is injected with
the application context's `MessageSource` when the bean is created and configured.
NOTE: Because Spring's `MessageSource` is based on Java's `ResourceBundle`, it does not merge
bundles with the same base name, but will only use the first bundle found.
Subsequent message bundles with the same base name are ignored.
NOTE: As an alternative to `ResourceBundleMessageSource`, Spring provides a
`ReloadableResourceBundleMessageSource` class. This variant supports the same bundle
file format but is more flexible than the standard JDK based
@@ -10718,7 +10728,7 @@ following example shows how to do so:
----
It is also possible to add additional runtime filtering by using the `condition` attribute
of the annotation that defines a <<expressions, `SpEL` expression>> , which should match
of the annotation that defines a <<expressions, `SpEL` expression>>, which should match
to actually invoke the method for a particular event.
The following example shows how our notifier can be rewritten to be invoked only if the
@@ -10830,10 +10840,12 @@ The following example shows how to do so:
Be aware of the following limitations when using asynchronous events:
* If an asynchronous event listener throws an `Exception`, it is not propagated to the
caller. See `AsyncUncaughtExceptionHandler` for more details.
caller. See
{api-spring-framework}/aop/interceptor/AsyncUncaughtExceptionHandler.html[`AsyncUncaughtExceptionHandler`]
for more details.
* Asynchronous event listener methods cannot publish a subsequent event by returning a
value. If you need to publish another event as the result of the processing, inject an
{api-spring-framework}/aop/interceptor/AsyncUncaughtExceptionHandler.html[`ApplicationEventPublisher`]
{api-spring-framework}/context/ApplicationEventPublisher.html[`ApplicationEventPublisher`]
to publish the event manually.
@@ -11021,8 +11033,8 @@ For a simple deployment of a Spring ApplicationContext as a Java EE RAR file:
. Package
all application classes into a RAR file (which is a standard JAR file with a different
file extension).
.Add all required library JARs into the root of the RAR archive.
.Add a
. Add all required library JARs into the root of the RAR archive.
. Add a
`META-INF/ra.xml` deployment descriptor (as shown in the {api-spring-framework}/jca/context/SpringContextResourceAdapter.html[javadoc for `SpringContextResourceAdapter`])
and the corresponding Spring XML bean definition file(s) (typically
`META-INF/applicationContext.xml`).
@@ -11116,7 +11128,7 @@ The following table lists features provided by the `BeanFactory` and
| No
| Yes
| Convenient `MessageSource` access (for internalization)
| Convenient `MessageSource` access (for internationalization)
| No
| Yes
+47 -48
View File
@@ -6615,7 +6615,6 @@ has it applied automatically:
protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {
// do the actual work
}
}
----
@@ -6740,11 +6739,17 @@ seconds and one running every morning at 6 AM. To finalize everything, we need t
</bean>
----
More properties are available for the `SchedulerFactoryBean`, such as the calendars
used by the job details, properties to customize Quartz with, and others. See the
{api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`]
More properties are available for the `SchedulerFactoryBean`, such as the calendars used by the
job details, properties to customize Quartz with, and a Spring-provided JDBC DataSource. See
the {api-spring-framework}/scheduling/quartz/SchedulerFactoryBean.html[`SchedulerFactoryBean`]
javadoc for more information.
NOTE: `SchedulerFactoryBean` also recognizes a `quartz.properties` file in the classpath,
based on Quartz property keys, as with regular Quartz configuration. Please note that many
`SchedulerFactoryBean` settings interact with common Quartz settings in the properties file;
it is therefore not recommended to specify values at both levels. For example, do not set
an "org.quartz.jobStore.class" property if you mean to rely on a Spring-provided DataSource.
@@ -6756,8 +6761,8 @@ an existing Spring application. Similar to the <<data-access.adoc#transaction, t
support, the caching abstraction allows consistent use of various caching solutions with
minimal impact on the code.
As from Spring 4.1, the cache abstraction has been significantly extended with the
support of <<cache-jsr-107,JSR-107 annotations>> and more customization options.
In Spring Framework 4.1, the cache abstraction was significantly extended with support
for <<cache-jsr-107,JSR-107 annotations>> and more customization options.
@@ -6815,15 +6820,16 @@ compliant caches (such as Ehcache 3.x). See <<cache-plug>> for more information
plugging in other cache stores and providers.
IMPORTANT: The caching abstraction has no special handling for multi-threaded and
multi-process environments, as such features are handled by the cache implementation. .
multi-process environments, as such features are handled by the cache implementation.
If you have a multi-process environment (that is, an application deployed on several nodes),
you need to configure your cache provider accordingly. Depending on your use cases, a copy
of the same data on several nodes can be enough. However, if you change the data during
the course of the application, you may need to enable other propagation mechanisms.
Caching a particular item is a direct equivalent of the typical get-if-not-found-then-
proceed-and-put-eventually code blocks found with programmatic cache interaction.
Caching a particular item is a direct equivalent of the typical
get-if-not-found-then-proceed-and-put-eventually code blocks
found with programmatic cache interaction.
No locks are applied, and several threads may try to load the same item concurrently.
The same applies to eviction. If several threads are trying to update or evict data
concurrently, you may use stale data. Certain cache providers offer advanced features
@@ -6875,7 +6881,7 @@ method -- if at least one cache is hit, the associated value is returned.
NOTE: All the other caches that do not contain the value are also updated, even though
the cached method was not actually invoked.
The following example uses `@Cacheable` on the `findBook` method:
The following example uses `@Cacheable` on the `findBook` method with multiple caches:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -7028,7 +7034,7 @@ regardless of the content of the annotation.
Similarly to `key` and `keyGenerator`, the `cacheManager` and `cacheResolver`
parameters are mutually exclusive, and an operation specifying both
results in an exception. as a custom `CacheManager` is ignored by the
results in an exception, as a custom `CacheManager` is ignored by the
`CacheResolver` implementation. This is probably not what you expect.
====
@@ -7053,7 +7059,6 @@ is updated in the cache. The following example shows how to use the `sync` attri
----
<1> Using the `sync` attribute.
NOTE: This is an optional feature, and your favorite cache library may not support it.
All `CacheManager` implementations provided by the core framework support it. See the
documentation of your cache provider for more details.
@@ -7092,9 +7097,11 @@ want to cache paperback books, as the following example does:
<1> Using the `unless` attribute to block hardbacks.
The cache abstraction supports `java.util.Optional`, using its content as the cached value
only if it is present. `#result` always refers to the business entity and never a
supported wrapper, so the previous example can be rewritten as follows:
The cache abstraction supports `java.util.Optional` return types. If an `Optional` value
is _present_, it will be stored in the associated cache. If an `Optional` value is not
present, `null` will be stored in the associated cache. `#result` always refers to the
business entity and never a supported wrapper, so the previous example can be rewritten
as follows:
[source,java,indent=0]
[subs="verbatim,quotes"]
@@ -7103,8 +7110,8 @@ supported wrapper, so the previous example can be rewritten as follows:
public Optional<Book> findBook(String name)
----
Note that `result` still refers to `Book` and not `Optional`. As it might be `null`, we
should use the safe navigation operator.
Note that `#result` still refers to `Book` and not `Optional<Book>`. Since it might be
`null`, we use SpEL's <<core.adoc#expressions-operator-safe-navigation, safe navigation operator>>.
[[cache-spel-context]]
===== Available Caching SpEL Evaluation Context
@@ -7214,7 +7221,6 @@ all entries from the `books` cache:
----
<1> Using the `allEntries` attribute to evict all entries from the cache.
This option comes in handy when an entire cache region needs to be cleared out.
Rather than evicting each entry (which would take a long time, since it is inefficient),
all the entries are removed in one operation, as the preceding example shows.
@@ -7275,7 +7281,6 @@ comes into play. The following examples uses `@CacheConfig` to set the name of t
----
<1> Using `@CacheConfig` to set the name of the cache.
`@CacheConfig` is a class-level annotation that allows sharing the cache names,
the custom `KeyGenerator`, the custom `CacheManager`, and the custom `CacheResolver`.
Placing this annotation on the class does not turn on any caching operation.
@@ -7418,13 +7423,11 @@ if you need to annotate non-public methods, as it changes the bytecode itself.
****
TIP: Spring recommends that you only annotate concrete classes (and methods of concrete
classes) with the `@Cache{asterisk}` annotation, as opposed to annotating interfaces.
You certainly can place the `@Cache{asterisk}` annotation on an interface (or an interface
method), but this works only as you would expect it to if you use interface-based proxies.
The fact that Java annotations are not inherited from interfaces means that, if you use
class-based proxies (`proxy-target-class="true"`) or the weaving-based aspect
(`mode="aspectj"`), the caching settings are not recognized by the proxying and weaving
infrastructure, and the object is not wrapped in a caching proxy.
classes) with the `@Cache{asterisk}` annotations, as opposed to annotating interfaces.
You certainly can place an `@Cache{asterisk}` annotation on an interface (or an interface
method), but this works only if you use the proxy mode (`mode="proxy"`). If you use the
weaving-based aspect (`mode="aspectj"`), the caching settings are not recognized on
interface-level declarations by the weaving infrastructure.
NOTE: In proxy mode (the default), only external method calls coming in through the
proxy are intercepted. This means that self-invocation (in effect, a method within the
@@ -7499,7 +7502,7 @@ up its declaration at runtime and understands its meaning. Note that, as mention
=== JCache (JSR-107) Annotations
Since version 4.1, Spring's caching abstraction fully supports the JCache standard
annotations: `@CacheResult`, `@CachePut`, `@CacheRemove`, and `@CacheRemoveAll`
(JSR-107) annotations: `@CacheResult`, `@CachePut`, `@CacheRemove`, and `@CacheRemoveAll`
as well as the `@CacheDefaults`, `@CacheKey`, and `@CacheValue` companions.
You can use these annotations even without migrating your cache store to JSR-107.
The internal implementation uses Spring's caching abstraction and provides default
@@ -7513,8 +7516,8 @@ you can switch to these standard annotations without changing your cache storage
==== Feature Summary
For those who are familiar with Spring's caching annotations, the following table
describes the main differences between the Spring annotations and the JSR-107
counterpart:
describes the main differences between the Spring annotations and their JSR-107
counterparts:
.Spring vs. JSR-107 caching annotations
[cols="1,1,3"]
@@ -7565,7 +7568,6 @@ to customize the factory for each cache operation, as the following example show
----
<1> Customizing the factory for this operation.
NOTE: For all referenced classes, Spring tries to locate a bean with the given type.
If more than one match exists, a new instance is created and can use the regular
bean lifecycle callbacks, such as dependency injection.
@@ -7609,16 +7611,15 @@ invoking the method again:
==== Enabling JSR-107 Support
You need do nothing specific to enable the JSR-107 support alongside Spring's
declarative annotation support. Both `@EnableCaching` and the
`cache:annotation-driven` element automatically enable the JCache support
if both the JSR-107 API and the `spring-context-support` module are present
in the classpath.
You do not need to do anything specific to enable the JSR-107 support alongside Spring's
declarative annotation support. Both `@EnableCaching` and the `cache:annotation-driven`
XML element automatically enable the JCache support if both the JSR-107 API and the
`spring-context-support` module are present in the classpath.
NOTE: Depending on your use case, the choice is basically yours. You can even
mix and match services by using the JSR-107 API on some and using Spring's own
annotations on others. However, if these services impact the same caches,
you should use a consistent and identical key generation implementation.
NOTE: Depending on your use case, the choice is basically yours. You can even mix and
match services by using the JSR-107 API on some and using Spring's own annotations on
others. However, if these services impact the same caches, you should use a consistent
and identical key generation implementation.
@@ -7848,15 +7849,13 @@ invoked every time.
=== Plugging-in Different Back-end Caches
Clearly, there are plenty of caching products out there that you can use as a backing
store. To plug them in, you need to provide a `CacheManager` and a `Cache` implementation,
since, unfortunately, there is no available standard that we can use instead.
This may sound harder than it is, since, in practice, the classes tend to be simple
https://en.wikipedia.org/wiki/Adapter_pattern[adapters] that map the caching abstraction
framework on top of the storage API, as the `ehcache` classes do. Most `CacheManager`
classes can use the classes in the `org.springframework.cache.support` package
(such as `AbstractCacheManager` which takes care of the boiler-plate code,
leaving only the actual mapping to be completed). We hope that, in time, the libraries
that provide integration with Spring can fill in this small configuration gap.
store. For those that do not support JSR-107 you need to provide a `CacheManager` and a
`Cache` implementation. This may sound harder than it is, since, in practice, the classes
tend to be simple https://en.wikipedia.org/wiki/Adapter_pattern[adapters] that map the
caching abstraction framework on top of the storage API, as the `ehcache` classes do.
Most `CacheManager` classes can use the classes in the
`org.springframework.cache.support` package (such as `AbstractCacheManager` which takes
care of the boiler-plate code, leaving only the actual mapping to be completed).