mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Compare commits
197 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cde41b8b5 | |||
| 4b861aeae6 | |||
| aee8a9c97b | |||
| 91e6274b7d | |||
| 61579ffe7b | |||
| b47491695e | |||
| 9d0e62ef68 | |||
| 3dd6c19c6d | |||
| 0befc60c8f | |||
| 3f3141cdda | |||
| 646fcc5c2f | |||
| b2ce98e881 | |||
| 395c9bd723 | |||
| 142f1ab42f | |||
| 0ca4cd1cf8 | |||
| 36d42bcda4 | |||
| 283811b16b | |||
| aea6bb6357 | |||
| 0fb31c5e36 | |||
| 17f9b61249 | |||
| afd248da8a | |||
| 572c668726 | |||
| 5520e730f1 | |||
| 09f1f727a7 | |||
| 40127bd9ad | |||
| 4c7414833b | |||
| 357fb48d13 | |||
| 513461d4f1 | |||
| c53c8bfc5a | |||
| c211e3998b | |||
| 61d330f805 | |||
| ea5f8f5a7c | |||
| 0c289283ff | |||
| 7ab4d0ca08 | |||
| 214576673a | |||
| 32b4279929 | |||
| 817254f2eb | |||
| fed81c6f83 | |||
| dfe4e01f51 | |||
| df9761cb3f | |||
| 12baafd4f0 | |||
| 1148b61dfa | |||
| bb7152d6e7 | |||
| 61fc321554 | |||
| 4f2e54fc2d | |||
| b97fa4a5ee | |||
| 65a167f7fd | |||
| 0270808b3c | |||
| 823a16ce17 | |||
| 7b4ec72378 | |||
| 437c33ba42 | |||
| 43d3abdfd5 | |||
| 3ae776b1fa | |||
| 61352fe85b | |||
| 6375cc55a3 | |||
| dd09c08cdf | |||
| d6591a6329 | |||
| 42dfa40151 | |||
| 89b81b0080 | |||
| d31767eab8 | |||
| b160f93495 | |||
| 44fd2d96cc | |||
| 9623cdec48 | |||
| 91e39d558f | |||
| c88f11f958 | |||
| 1dbcd66091 | |||
| 4c9ed0d87e | |||
| 06e6386dc9 | |||
| 13a8f90e08 | |||
| a15975d94d | |||
| 93f645800b | |||
| 347c2daa2c | |||
| e39bf87b07 | |||
| 67e7c784e8 | |||
| 384a399fd2 | |||
| c60313de3f | |||
| 84b8ceca0f | |||
| 72e3c43375 | |||
| 8f6d3feaa0 | |||
| 121f9e3734 | |||
| ea73ec5c41 | |||
| cdf2ab9737 | |||
| 19a1477228 | |||
| 5adaa1030c | |||
| f7bf6460aa | |||
| b36af8a11f | |||
| 8a129ef3da | |||
| 69ccba30e9 | |||
| e6893da971 | |||
| 5ed0cf9027 | |||
| 537f79f680 | |||
| 018469286d | |||
| 30e40210a8 | |||
| c8bdb3c602 | |||
| e43439c6c3 | |||
| b2322e58d9 | |||
| d9af4d6599 | |||
| f72318a0ae | |||
| 50b46c9e35 | |||
| 9c7de232b8 | |||
| c326e44488 | |||
| 30c0616384 | |||
| 74b5c371ba | |||
| 846636f2b9 | |||
| cb8ad46564 | |||
| 45828cb934 | |||
| 446e7ed25c | |||
| 73cad470e5 | |||
| 4adc820714 | |||
| 0d151b0212 | |||
| 72590dbb21 | |||
| 7a55d932f9 | |||
| b9e03cb06b | |||
| 4a57e26d76 | |||
| b6191f6057 | |||
| d187cbce73 | |||
| 6a1fe0b1d0 | |||
| 0a06bce3a6 | |||
| 977550f956 | |||
| 18d90ec140 | |||
| d3e0f4dd91 | |||
| 9beb97880f | |||
| 7ad69bfc20 | |||
| 1a154c3e4c | |||
| 37f0e8c6e5 | |||
| 93a522f336 | |||
| 542de82c5f | |||
| 7bf9b767fd | |||
| 0f1f95e090 | |||
| a49123a72b | |||
| dd4f915234 | |||
| d2616b7996 | |||
| deac8e556e | |||
| 8fc16fc937 | |||
| ec6eed6c63 | |||
| 3aee3182ac | |||
| f389f66270 | |||
| 6c3a64578c | |||
| 6e587d5c57 | |||
| 7035ee7ebb | |||
| f23612c3a3 | |||
| 9d27e86951 | |||
| 4577a19190 | |||
| 0075f13126 | |||
| 425a999d5e | |||
| a8cf275aed | |||
| 6df1a7874a | |||
| 99cbfd32c3 | |||
| e3e4f37222 | |||
| 3a51b5a9d2 | |||
| b8201f48fd | |||
| f4d8c7cc2b | |||
| 8e253a316d | |||
| 51aa16f85f | |||
| 8c2e5dcb4a | |||
| 05988193b7 | |||
| 6a7b197af7 | |||
| 73a81f98d4 | |||
| 583201b02c | |||
| f05175586e | |||
| 9d421841d5 | |||
| 260ebeca3a | |||
| f736b665bd | |||
| 76dcde9e31 | |||
| 554662ebab | |||
| ac00d8a215 | |||
| 769ea1b106 | |||
| 206a5e02dd | |||
| 591429e538 | |||
| d198626618 | |||
| 767ea9db83 | |||
| 9f36d170be | |||
| f9689dfe48 | |||
| bf8831157d | |||
| f4e9fb52a8 | |||
| 04ab93ceb4 | |||
| 0ef117ff11 | |||
| fda08852ba | |||
| 203370a810 | |||
| b9a1168580 | |||
| e99b0038a0 | |||
| 91d3e4402f | |||
| 44c95a4736 | |||
| 7368f58518 | |||
| 63c2c08e01 | |||
| dd0d270ba2 | |||
| 873cb4e58b | |||
| b55a2631ba | |||
| 6f73b8b200 | |||
| 2b68212c20 | |||
| 3650ecc3bb | |||
| bec63fbb33 | |||
| 375a63a041 | |||
| 773cd3241b | |||
| f2e8171bfb | |||
| dc3d834026 | |||
| e30f1fbe89 |
@@ -22,8 +22,9 @@ wiki page.
|
||||
|
||||
## Documentation
|
||||
|
||||
The Spring Frameworks maintains
|
||||
[reference documentation](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/),
|
||||
The Spring Frameworks maintains reference documentation
|
||||
([published](http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/) and
|
||||
[source](src/docs/asciidoc)),
|
||||
Github [wiki pages](https://github.com/spring-projects/spring-framework/wiki), and an
|
||||
[API reference](http://docs.spring.io/spring-framework/docs/current/javadoc-api/).
|
||||
There are also [guides and tutorials](https://spring.io/guides) across Spring projects.
|
||||
|
||||
+23
-17
@@ -14,7 +14,7 @@ buildscript {
|
||||
plugins {
|
||||
id "com.gradle.build-scan" version "1.8"
|
||||
id "io.spring.dependency-management" version "1.0.3.RELEASE" apply false
|
||||
id "org.jetbrains.kotlin.jvm" version "1.1.61" apply false
|
||||
id "org.jetbrains.kotlin.jvm" version "1.2.20" apply false
|
||||
id "org.jetbrains.dokka" version "0.9.15"
|
||||
id "org.asciidoctor.convert" version "1.5.6"
|
||||
}
|
||||
@@ -45,22 +45,22 @@ configure(allprojects) { project ->
|
||||
ext.freemarkerVersion = "2.3.27-incubating"
|
||||
ext.groovyVersion = "2.4.13"
|
||||
ext.hsqldbVersion = "2.4.0"
|
||||
ext.jackson2Version = "2.9.2"
|
||||
ext.jettyVersion = "9.4.7.v20170914"
|
||||
ext.junitJupiterVersion = "5.0.2"
|
||||
ext.junitPlatformVersion = "1.0.2"
|
||||
ext.junitVintageVersion = "4.12.2"
|
||||
ext.kotlinVersion = "1.1.61"
|
||||
ext.jackson2Version = "2.9.3"
|
||||
ext.jettyVersion = "9.4.8.v20171121"
|
||||
ext.junitJupiterVersion = "5.0.3"
|
||||
ext.junitPlatformVersion = "1.0.3"
|
||||
ext.junitVintageVersion = "4.12.3"
|
||||
ext.kotlinVersion = "1.2.20"
|
||||
ext.log4jVersion = "2.10.0"
|
||||
ext.nettyVersion = "4.1.17.Final"
|
||||
ext.reactorVersion = "Bismuth-SR4"
|
||||
ext.nettyVersion = "4.1.20.Final"
|
||||
ext.reactorVersion = "Bismuth-SR5"
|
||||
ext.rxjavaVersion = "1.3.4"
|
||||
ext.rxjavaAdapterVersion = "1.2.1"
|
||||
ext.rxjava2Version = "2.1.6"
|
||||
ext.rxjava2Version = "2.1.8"
|
||||
ext.slf4jVersion = "1.7.25"
|
||||
ext.tiles3Version = "3.0.8"
|
||||
ext.tomcatVersion = "8.5.23"
|
||||
ext.undertowVersion = "1.4.21.Final"
|
||||
ext.tomcatVersion = "8.5.27"
|
||||
ext.undertowVersion = "1.4.22.Final"
|
||||
|
||||
ext.gradleScriptDir = "${rootProject.projectDir}/gradle"
|
||||
|
||||
@@ -71,13 +71,19 @@ configure(allprojects) { project ->
|
||||
|
||||
apply plugin: "kotlin"
|
||||
compileKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
kotlinOptions.freeCompilerArgs = ["-Xskip-runtime-version-check", "-Xjsr305=strict"]
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = ["-Xjsr305=strict"]
|
||||
apiVersion = "1.1"
|
||||
languageVersion = "1.1"
|
||||
}
|
||||
|
||||
}
|
||||
compileTestKotlin {
|
||||
kotlinOptions.jvmTarget = "1.8"
|
||||
kotlinOptions.freeCompilerArgs = ["-Xskip-runtime-version-check", "-Xjsr305=strict"]
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs = ["-Xjsr305=strict"]
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@@ -268,7 +274,7 @@ configure(rootProject) {
|
||||
|
||||
task wrapper(type: Wrapper) {
|
||||
description = "Generates gradlew[.bat] scripts"
|
||||
gradleVersion = '4.3.1'
|
||||
gradleVersion = '4.4.1'
|
||||
|
||||
doLast() {
|
||||
def gradleOpts = "-XX:MaxMetaspaceSize=1024m -Xmx1024m"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
version=5.0.2.BUILD-SNAPSHOT
|
||||
version=5.0.3.RELEASE
|
||||
|
||||
+2
-1
@@ -66,7 +66,8 @@ dokka {
|
||||
outputDirectory = "$buildDir/docs/kdoc"
|
||||
|
||||
sourceDirs = files(subprojects.collect { project ->
|
||||
project.sourceSets.main.kotlin.srcDirs
|
||||
def kotlinDirs = project.sourceSets.main.kotlin.srcDirs.collect()
|
||||
kotlinDirs -= project.sourceSets.main.java.srcDirs
|
||||
})
|
||||
externalDocumentationLink {
|
||||
url = new URL("http://docs.spring.io/spring-framework/docs/${version}/javadoc-api/")
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+2
-2
@@ -1,5 +1,5 @@
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-bin.zip
|
||||
|
||||
@@ -9,6 +9,14 @@ pluginManagement {
|
||||
}
|
||||
*/
|
||||
|
||||
// Workaround for https://github.com/Kotlin/dokka/issues/146
|
||||
pluginManagement {
|
||||
repositories {
|
||||
jcenter()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
include "spring-aop"
|
||||
include "spring-aspects"
|
||||
include "spring-beans"
|
||||
|
||||
@@ -4,6 +4,6 @@ dependencies {
|
||||
compile(project(":spring-beans"))
|
||||
compile(project(':spring-core'))
|
||||
optional("org.aspectj:aspectjweaver:${aspectjVersion}")
|
||||
optional("org.apache.commons:commons-pool2:2.4.3")
|
||||
optional("org.apache.commons:commons-pool2:2.5.0")
|
||||
optional("com.jamonapi:jamon:2.81")
|
||||
}
|
||||
|
||||
+1
-1
@@ -148,7 +148,7 @@ public class AspectMetadata implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the aspect class.
|
||||
* Return the aspect name.
|
||||
*/
|
||||
public String getAspectName() {
|
||||
return this.aspectName;
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -544,13 +544,13 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
|
||||
* @see #setInterceptorNames
|
||||
*/
|
||||
private Advisor[] resolveInterceptorNames() {
|
||||
Assert.state(this.beanFactory != null, "BeanFactory required for resolving interceptor names");
|
||||
ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory ?
|
||||
(ConfigurableBeanFactory) this.beanFactory : null);
|
||||
BeanFactory bf = this.beanFactory;
|
||||
ConfigurableBeanFactory cbf = (bf instanceof ConfigurableBeanFactory ? (ConfigurableBeanFactory) bf : null);
|
||||
List<Advisor> advisors = new ArrayList<>();
|
||||
for (String beanName : this.interceptorNames) {
|
||||
if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {
|
||||
Object next = this.beanFactory.getBean(beanName);
|
||||
Assert.state(bf != null, "BeanFactory required for resolving interceptor names");
|
||||
Object next = bf.getBean(beanName);
|
||||
advisors.add(this.advisorAdapterRegistry.wrap(next));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ dependencies {
|
||||
optional(project(":spring-context-support")) // for JavaMail and JSR-107 support
|
||||
optional(project(":spring-orm")) // for JPA exception translation support
|
||||
optional(project(":spring-tx")) // for JPA, @Transactional support
|
||||
optional("javax.cache:cache-api:1.0.0") // for JCache aspect
|
||||
optional("javax.cache:cache-api:1.1.0") // for JCache aspect
|
||||
optional("javax.transaction:javax.transaction-api:1.2") // for @javax.transaction.Transactional support
|
||||
testCompile(project(":spring-core")) // for CodeStyleAspect
|
||||
testCompile(project(":spring-test"))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -98,12 +98,19 @@ public abstract class BeanUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a class using its no-arg constructor.
|
||||
* Instantiate a class using its 'primary' constructor (for Kotlin classes,
|
||||
* potentially having default arguments declared) or its default constructor
|
||||
* (for regular Java classes, expecting a standard no-arg setup).
|
||||
* <p>Note that this method tries to set the constructor accessible
|
||||
* if given a non-accessible (that is, non-public) constructor.
|
||||
* @param clazz class to instantiate
|
||||
* @param clazz the class to instantiate
|
||||
* @return the new instance
|
||||
* @throws BeanInstantiationException if the bean cannot be instantiated
|
||||
* @throws BeanInstantiationException if the bean cannot be instantiated.
|
||||
* The cause may notably indicate a {@link NoSuchMethodException} if no
|
||||
* primary/default constructor was found, a {@link NoClassDefFoundError}
|
||||
* or other {@link LinkageError} in case of an unresolvable class definition
|
||||
* (e.g. due to a missing dependency at runtime), or an exception thrown
|
||||
* from the constructor invocation itself.
|
||||
* @see Constructor#newInstance
|
||||
*/
|
||||
public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
|
||||
@@ -113,15 +120,15 @@ public abstract class BeanUtils {
|
||||
}
|
||||
try {
|
||||
Constructor<T> ctor = (KotlinDetector.isKotlinType(clazz) ?
|
||||
KotlinDelegate.findPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
|
||||
if (ctor == null) {
|
||||
throw new BeanInstantiationException(clazz, "No default constructor found");
|
||||
}
|
||||
KotlinDelegate.getPrimaryConstructor(clazz) : clazz.getDeclaredConstructor());
|
||||
return instantiateClass(ctor);
|
||||
}
|
||||
catch (NoSuchMethodException ex) {
|
||||
throw new BeanInstantiationException(clazz, "No default constructor found", ex);
|
||||
}
|
||||
catch (LinkageError err) {
|
||||
throw new BeanInstantiationException(clazz, "Unresolvable class definition", err);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -561,13 +568,14 @@ public abstract class BeanUtils {
|
||||
|
||||
/**
|
||||
* Check if the given type represents a "simple" value type:
|
||||
* a primitive, a String or other CharSequence, a Number, a Date,
|
||||
* a primitive, an enum, a String or other CharSequence, a Number, a Date,
|
||||
* a URI, a URL, a Locale or a Class.
|
||||
* @param clazz the type to check
|
||||
* @return whether the given type represents a "simple" value type
|
||||
*/
|
||||
public static boolean isSimpleValueType(Class<?> clazz) {
|
||||
return (ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() ||
|
||||
return (ClassUtils.isPrimitiveOrWrapper(clazz) ||
|
||||
Enum.class.isAssignableFrom(clazz) ||
|
||||
CharSequence.class.isAssignableFrom(clazz) ||
|
||||
Number.class.isAssignableFrom(clazz) ||
|
||||
Date.class.isAssignableFrom(clazz) ||
|
||||
@@ -692,10 +700,26 @@ public abstract class BeanUtils {
|
||||
private static class KotlinDelegate {
|
||||
|
||||
/**
|
||||
* Return the Java constructor corresponding to the Kotlin primary constructor if any.
|
||||
* Determine the Java constructor corresponding to the Kotlin primary constructor.
|
||||
* @param clazz the {@link Class} of the Kotlin class
|
||||
* @throws NoSuchMethodException if no such constructor found
|
||||
* @since 5.0.3
|
||||
* @see #findPrimaryConstructor
|
||||
* @see Class#getDeclaredConstructor
|
||||
*/
|
||||
public static <T> Constructor<T> getPrimaryConstructor(Class<T> clazz) throws NoSuchMethodException {
|
||||
Constructor<T> ctor = findPrimaryConstructor(clazz);
|
||||
if (ctor == null) {
|
||||
throw new NoSuchMethodException();
|
||||
}
|
||||
return ctor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Java constructor corresponding to the Kotlin primary constructor, if any.
|
||||
* @param clazz the {@link Class} of the Kotlin class
|
||||
* @see <a href="http://kotlinlang.org/docs/reference/classes.html#constructors">
|
||||
* http://kotlinlang.org/docs/reference/classes.html#constructors</a>
|
||||
* http://kotlinlang.org/docs/reference/classes.html#constructors</a>
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> Constructor<T> findPrimaryConstructor(Class<T> clazz) {
|
||||
@@ -705,8 +729,10 @@ public abstract class BeanUtils {
|
||||
return null;
|
||||
}
|
||||
Constructor<T> constructor = ReflectJvmMapping.getJavaConstructor(primaryCtor);
|
||||
Assert.notNull(constructor,
|
||||
() -> "Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
|
||||
if (constructor == null) {
|
||||
throw new IllegalStateException(
|
||||
"Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
catch (UnsupportedOperationException ex) {
|
||||
@@ -717,7 +743,8 @@ public abstract class BeanUtils {
|
||||
/**
|
||||
* Instantiate a Kotlin class using the provided constructor.
|
||||
* @param ctor the constructor of the Kotlin class to instantiate
|
||||
* @param args the constructor arguments to apply (use null for unspecified parameter if needed)
|
||||
* @param args the constructor arguments to apply
|
||||
* (use {@code null} for unspecified parameter if needed)
|
||||
*/
|
||||
public static <T> T instantiateClass(Constructor<T> ctor, Object... args)
|
||||
throws IllegalAccessException, InvocationTargetException, InstantiationException {
|
||||
|
||||
+36
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -244,6 +244,27 @@ public class CachedIntrospectionResults {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link BeanInfo} descriptor for the given target class.
|
||||
* @param beanClass the target class to introspect
|
||||
* @param ignoreBeaninfoClasses whether to apply {@link Introspector#IGNORE_ALL_BEANINFO} mode
|
||||
* @return the resulting {@code BeanInfo} descriptor (never {@code null})
|
||||
* @throws IntrospectionException from the underlying {@link Introspector}
|
||||
*/
|
||||
private static BeanInfo getBeanInfo(Class<?> beanClass, boolean ignoreBeaninfoClasses)
|
||||
throws IntrospectionException {
|
||||
|
||||
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
|
||||
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanClass);
|
||||
if (beanInfo != null) {
|
||||
return beanInfo;
|
||||
}
|
||||
}
|
||||
return (ignoreBeaninfoClasses ?
|
||||
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
|
||||
Introspector.getBeanInfo(beanClass));
|
||||
}
|
||||
|
||||
|
||||
/** The BeanInfo object for the introspected bean class */
|
||||
private final BeanInfo beanInfo;
|
||||
@@ -265,21 +286,7 @@ public class CachedIntrospectionResults {
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Getting BeanInfo for class [" + beanClass.getName() + "]");
|
||||
}
|
||||
|
||||
BeanInfo beanInfo = null;
|
||||
for (BeanInfoFactory beanInfoFactory : beanInfoFactories) {
|
||||
beanInfo = beanInfoFactory.getBeanInfo(beanClass);
|
||||
if (beanInfo != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (beanInfo == null) {
|
||||
// If none of the factories supported the class, fall back to the default
|
||||
beanInfo = (shouldIntrospectorIgnoreBeaninfoClasses ?
|
||||
Introspector.getBeanInfo(beanClass, Introspector.IGNORE_ALL_BEANINFO) :
|
||||
Introspector.getBeanInfo(beanClass));
|
||||
}
|
||||
this.beanInfo = beanInfo;
|
||||
this.beanInfo = getBeanInfo(beanClass, shouldIntrospectorIgnoreBeaninfoClasses);
|
||||
|
||||
if (logger.isTraceEnabled()) {
|
||||
logger.trace("Caching PropertyDescriptors for class [" + beanClass.getName() + "]");
|
||||
@@ -307,15 +314,17 @@ public class CachedIntrospectionResults {
|
||||
// Explicitly check implemented interfaces for setter/getter methods as well,
|
||||
// in particular for Java 8 default methods...
|
||||
Class<?> clazz = beanClass;
|
||||
while (clazz != null) {
|
||||
while (clazz != null && clazz != Object.class) {
|
||||
Class<?>[] ifcs = clazz.getInterfaces();
|
||||
for (Class<?> ifc : ifcs) {
|
||||
BeanInfo ifcInfo = Introspector.getBeanInfo(ifc, Introspector.IGNORE_ALL_BEANINFO);
|
||||
PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors();
|
||||
for (PropertyDescriptor pd : ifcPds) {
|
||||
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
|
||||
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
|
||||
this.propertyDescriptorCache.put(pd.getName(), pd);
|
||||
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
|
||||
BeanInfo ifcInfo = getBeanInfo(ifc, true);
|
||||
PropertyDescriptor[] ifcPds = ifcInfo.getPropertyDescriptors();
|
||||
for (PropertyDescriptor pd : ifcPds) {
|
||||
if (!this.propertyDescriptorCache.containsKey(pd.getName())) {
|
||||
pd = buildGenericTypeAwarePropertyDescriptor(beanClass, pd);
|
||||
this.propertyDescriptorCache.put(pd.getName(), pd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -329,6 +338,7 @@ public class CachedIntrospectionResults {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BeanInfo getBeanInfo() {
|
||||
return this.beanInfo;
|
||||
}
|
||||
@@ -341,10 +351,10 @@ public class CachedIntrospectionResults {
|
||||
PropertyDescriptor getPropertyDescriptor(String name) {
|
||||
PropertyDescriptor pd = this.propertyDescriptorCache.get(name);
|
||||
if (pd == null && StringUtils.hasLength(name)) {
|
||||
// Same lenient fallback checking as in PropertyTypeDescriptor...
|
||||
pd = this.propertyDescriptorCache.get(name.substring(0, 1).toLowerCase() + name.substring(1));
|
||||
// Same lenient fallback checking as in Property...
|
||||
pd = this.propertyDescriptorCache.get(StringUtils.uncapitalize(name));
|
||||
if (pd == null) {
|
||||
pd = this.propertyDescriptorCache.get(name.substring(0, 1).toUpperCase() + name.substring(1));
|
||||
pd = this.propertyDescriptorCache.get(StringUtils.capitalize(name));
|
||||
}
|
||||
}
|
||||
return (pd == null || pd instanceof GenericTypeAwarePropertyDescriptor ? pd :
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -346,6 +346,7 @@ class TypeConverterDelegate {
|
||||
// to be checked, hence we don't return it right away.
|
||||
try {
|
||||
Field enumField = requiredType.getField(trimmedValue);
|
||||
ReflectionUtils.makeAccessible(enumField);
|
||||
convertedValue = enumField.get(null);
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -280,7 +280,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
"Resolution of declared constructors on bean Class [" + beanClass.getName() +
|
||||
"] from ClassLoader [" + beanClass.getClassLoader() + "] failed", ex);
|
||||
}
|
||||
List<Constructor<?>> candidates = new ArrayList<Constructor<?>>(rawCandidates.length);
|
||||
List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
|
||||
Constructor<?> requiredConstructor = null;
|
||||
Constructor<?> defaultConstructor = null;
|
||||
Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
|
||||
@@ -347,7 +347,8 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
||||
else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
|
||||
candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
|
||||
}
|
||||
else if (nonSyntheticConstructors == 2 && primaryConstructor != null && defaultConstructor != null) {
|
||||
else if (nonSyntheticConstructors == 2 && primaryConstructor != null
|
||||
&& defaultConstructor != null && !primaryConstructor.equals(defaultConstructor)) {
|
||||
candidateConstructors = new Constructor<?>[] {primaryConstructor, defaultConstructor};
|
||||
}
|
||||
else if (nonSyntheticConstructors == 1 && primaryConstructor != null) {
|
||||
|
||||
+11
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -552,7 +552,7 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
|
||||
// Check bean class whether we're dealing with a FactoryBean.
|
||||
if (FactoryBean.class.isAssignableFrom(beanType)) {
|
||||
if (!BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
if (!BeanFactoryUtils.isFactoryDereference(name) && beanInstance == null) {
|
||||
// If it's a FactoryBean, we want to look at what it creates, not the factory class.
|
||||
beanType = getTypeForFactoryBean(beanName, mbd);
|
||||
if (beanType == null) {
|
||||
@@ -1379,8 +1379,8 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
|
||||
}
|
||||
catch (LinkageError ex) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), ex);
|
||||
catch (LinkageError err) {
|
||||
throw new CannotLoadBeanClassException(mbd.getResourceDescription(), beanName, mbd.getBeanClassName(), err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1614,8 +1614,13 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp
|
||||
Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
|
||||
|
||||
// Don't let calling code try to dereference the factory if the bean isn't a factory.
|
||||
if (BeanFactoryUtils.isFactoryDereference(name) && !(beanInstance instanceof FactoryBean)) {
|
||||
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
|
||||
if (BeanFactoryUtils.isFactoryDereference(name)) {
|
||||
if (beanInstance instanceof NullBean) {
|
||||
return beanInstance;
|
||||
}
|
||||
if (!(beanInstance instanceof FactoryBean)) {
|
||||
throw new BeanIsNotAFactoryException(transformedBeanName(name), beanInstance.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have the bean instance, which may be a normal bean or a FactoryBean.
|
||||
|
||||
+17
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -739,19 +739,22 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
|
||||
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
|
||||
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
|
||||
if (isFactoryBean(beanName)) {
|
||||
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
|
||||
boolean isEagerInit;
|
||||
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
|
||||
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
|
||||
((SmartFactoryBean<?>) factory).isEagerInit(),
|
||||
getAccessControlContext());
|
||||
}
|
||||
else {
|
||||
isEagerInit = (factory instanceof SmartFactoryBean &&
|
||||
((SmartFactoryBean<?>) factory).isEagerInit());
|
||||
}
|
||||
if (isEagerInit) {
|
||||
getBean(beanName);
|
||||
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
|
||||
if (bean instanceof FactoryBean) {
|
||||
final FactoryBean<?> factory = (FactoryBean<?>) bean;
|
||||
boolean isEagerInit;
|
||||
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
|
||||
isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
|
||||
((SmartFactoryBean<?>) factory).isEagerInit(),
|
||||
getAccessControlContext());
|
||||
}
|
||||
else {
|
||||
isEagerInit = (factory instanceof SmartFactoryBean &&
|
||||
((SmartFactoryBean<?>) factory).isEagerInit());
|
||||
}
|
||||
if (isEagerInit) {
|
||||
getBean(beanName);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
+5
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -137,12 +137,12 @@ public class DefaultNamespaceHandlerResolver implements NamespaceHandlerResolver
|
||||
return namespaceHandler;
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
|
||||
namespaceUri + "] not found", ex);
|
||||
throw new FatalBeanException("Could not find NamespaceHandler class [" + className +
|
||||
"] for namespace [" + namespaceUri + "]", ex);
|
||||
}
|
||||
catch (LinkageError err) {
|
||||
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
|
||||
namespaceUri + "]: problem with handler class file or dependent class", err);
|
||||
throw new FatalBeanException("Unresolvable class definition for NamespaceHandler class [" +
|
||||
className + "] for namespace [" + namespaceUri + "]", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -173,4 +173,32 @@ public class BeanWrapperEnumTests {
|
||||
assertEquals(new Integer(1), gb.getStandardEnumMap().get(CustomEnum.VALUE_1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonPublicEnum() {
|
||||
NonPublicEnumHolder holder = new NonPublicEnumHolder();
|
||||
BeanWrapper bw = new BeanWrapperImpl(holder);
|
||||
bw.setPropertyValue("nonPublicEnum", "VALUE_1");
|
||||
assertEquals(NonPublicEnum.VALUE_1, holder.getNonPublicEnum());
|
||||
}
|
||||
|
||||
|
||||
enum NonPublicEnum {
|
||||
|
||||
VALUE_1, VALUE_2;
|
||||
}
|
||||
|
||||
|
||||
static class NonPublicEnumHolder {
|
||||
|
||||
private NonPublicEnum nonPublicEnum;
|
||||
|
||||
public NonPublicEnum getNonPublicEnum() {
|
||||
return nonPublicEnum;
|
||||
}
|
||||
|
||||
public void setNonPublicEnum(NonPublicEnum nonPublicEnum) {
|
||||
this.nonPublicEnum = nonPublicEnum;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+31
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -2736,6 +2736,16 @@ public class DefaultListableBeanFactoryTests {
|
||||
assertSame(Optional.empty(), bf.getBean(Optional.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNonPublicEnum() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
RootBeanDefinition bd = new RootBeanDefinition(NonPublicEnumHolder.class);
|
||||
bd.getConstructorArgumentValues().addGenericArgumentValue("VALUE_1");
|
||||
bf.registerBeanDefinition("holderBean", bd);
|
||||
NonPublicEnumHolder holder = (NonPublicEnumHolder) bf.getBean("holderBean");
|
||||
assertEquals(NonPublicEnum.VALUE_1, holder.getNonPublicEnum());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that by-type bean lookup caching is working effectively by searching for a
|
||||
* bean of type B 10K times within a container having 1K additional beans of type A.
|
||||
@@ -3263,4 +3273,24 @@ public class DefaultListableBeanFactoryTests {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum NonPublicEnum {
|
||||
|
||||
VALUE_1, VALUE_2;
|
||||
}
|
||||
|
||||
|
||||
static class NonPublicEnumHolder {
|
||||
|
||||
final NonPublicEnum nonPublicEnum;
|
||||
|
||||
public NonPublicEnumHolder(NonPublicEnum nonPublicEnum) {
|
||||
this.nonPublicEnum = nonPublicEnum;
|
||||
}
|
||||
|
||||
public NonPublicEnum getNonPublicEnum() {
|
||||
return nonPublicEnum;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+43
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,6 +43,7 @@ import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
|
||||
@@ -152,6 +153,35 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
assertEquals("nestedTestBean", depBeans[1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedResourceInjectionWithDestruction() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
bf.registerResolvableDependency(BeanFactory.class, bf);
|
||||
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
|
||||
bpp.setBeanFactory(bf);
|
||||
bf.addBeanPostProcessor(bpp);
|
||||
bf.registerBeanDefinition("annotatedBean", new RootBeanDefinition(TypedExtendedResourceInjectionBean.class));
|
||||
bf.registerBeanDefinition("testBean", new RootBeanDefinition(TestBean.class));
|
||||
NestedTestBean ntb = new NestedTestBean();
|
||||
bf.registerSingleton("nestedTestBean", ntb);
|
||||
|
||||
TestBean tb = bf.getBean("testBean", TestBean.class);
|
||||
TypedExtendedResourceInjectionBean bean = (TypedExtendedResourceInjectionBean) bf.getBean("annotatedBean");
|
||||
assertSame(tb, bean.getTestBean());
|
||||
assertSame(tb, bean.getTestBean2());
|
||||
assertSame(tb, bean.getTestBean3());
|
||||
assertSame(tb, bean.getTestBean4());
|
||||
assertSame(ntb, bean.getNestedTestBean());
|
||||
assertSame(bf, bean.getBeanFactory());
|
||||
|
||||
assertArrayEquals(new String[] {"testBean", "nestedTestBean"}, bf.getDependenciesForBean("annotatedBean"));
|
||||
bf.destroySingleton("testBean");
|
||||
assertFalse(bf.containsSingleton("testBean"));
|
||||
assertFalse(bf.containsSingleton("annotatedBean"));
|
||||
assertTrue(bean.destroyed);
|
||||
assertSame(0, bf.getDependenciesForBean("annotatedBean").length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtendedResourceInjectionWithOverriding() {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
@@ -1286,9 +1316,6 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
bf.destroySingletons();
|
||||
}
|
||||
|
||||
@Qualifier("testBean")
|
||||
private void testBeanQualifierProvider() {}
|
||||
|
||||
@Test
|
||||
public void testObjectFactorySerialization() throws Exception {
|
||||
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
|
||||
@@ -2494,6 +2521,9 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
|
||||
|
||||
@Qualifier("testBean")
|
||||
private void testBeanQualifierProvider() {}
|
||||
|
||||
@Qualifier("integerRepo")
|
||||
private Repository<?> integerRepositoryQualifierProvider;
|
||||
|
||||
@@ -2579,7 +2609,15 @@ public class AutowiredAnnotationBeanPostProcessorTests {
|
||||
}
|
||||
|
||||
|
||||
public static class TypedExtendedResourceInjectionBean extends NonPublicResourceInjectionBean<NestedTestBean> {
|
||||
public static class TypedExtendedResourceInjectionBean extends NonPublicResourceInjectionBean<NestedTestBean>
|
||||
implements DisposableBean {
|
||||
|
||||
public boolean destroyed = false;
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
this.destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+3
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -66,10 +66,9 @@ public class CustomProblemReporterTests {
|
||||
|
||||
private static class CollatingProblemReporter implements ProblemReporter {
|
||||
|
||||
private List<Problem> errors = new ArrayList<>();
|
||||
|
||||
private List<Problem> warnings = new ArrayList<>();
|
||||
private final List<Problem> errors = new ArrayList<>();
|
||||
|
||||
private final List<Problem> warnings = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void fatal(Problem problem) {
|
||||
|
||||
+5
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -49,8 +49,7 @@ public class XmlBeanDefinitionReaderTests {
|
||||
@Test(expected = BeanDefinitionStoreException.class)
|
||||
public void withOpenInputStream() {
|
||||
SimpleBeanDefinitionRegistry registry = new SimpleBeanDefinitionRegistry();
|
||||
Resource resource = new InputStreamResource(getClass().getResourceAsStream(
|
||||
"test.xml"));
|
||||
Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml"));
|
||||
new XmlBeanDefinitionReader(registry).loadBeanDefinitions(resource);
|
||||
}
|
||||
|
||||
@@ -122,16 +121,16 @@ public class XmlBeanDefinitionReaderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dtdValidationAutodetect() throws Exception {
|
||||
public void dtdValidationAutodetect() {
|
||||
doTestValidation("validateWithDtd.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void xsdValidationAutodetect() throws Exception {
|
||||
public void xsdValidationAutodetect() {
|
||||
doTestValidation("validateWithXsd.xml");
|
||||
}
|
||||
|
||||
private void doTestValidation(String resourceName) throws Exception {
|
||||
private void doTestValidation(String resourceName) {
|
||||
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
|
||||
Resource resource = new ClassPathResource(resourceName, getClass());
|
||||
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(resource);
|
||||
|
||||
+20
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -169,6 +169,20 @@ class KotlinAutowiredTests {
|
||||
assertEquals(tb, kb.testBean)
|
||||
}
|
||||
|
||||
@Test // SPR-16289
|
||||
fun `Instantiation via secondary constructor when a default primary is defined`() {
|
||||
val bf = DefaultListableBeanFactory()
|
||||
val bpp = AutowiredAnnotationBeanPostProcessor()
|
||||
bpp.setBeanFactory(bf)
|
||||
bf.addBeanPostProcessor(bpp)
|
||||
val bd = RootBeanDefinition(KotlinBeanWithPrimaryAndSecondaryConstructors::class.java)
|
||||
bd.scope = RootBeanDefinition.SCOPE_PROTOTYPE
|
||||
bf.registerBeanDefinition("bean", bd)
|
||||
|
||||
bf.getBean(KotlinBeanWithPrimaryAndSecondaryConstructors::class.java, "foo")
|
||||
bf.getBean(KotlinBeanWithPrimaryAndSecondaryConstructors::class.java)
|
||||
}
|
||||
|
||||
@Test(expected = BeanCreationException::class) // SPR-16022
|
||||
fun `No autowiring with primary and secondary non annotated constructors`() {
|
||||
val bf = DefaultListableBeanFactory()
|
||||
@@ -244,6 +258,11 @@ class KotlinAutowiredTests {
|
||||
constructor() : this(TestBean())
|
||||
}
|
||||
|
||||
@Suppress("unused", "UNUSED_PARAMETER")
|
||||
class KotlinBeanWithPrimaryAndSecondaryConstructors() {
|
||||
constructor(p: String) : this()
|
||||
}
|
||||
|
||||
class KotlinBeanWithSecondaryConstructor(
|
||||
val optional: String = "foo",
|
||||
val injectedFromConstructor: TestBean
|
||||
|
||||
@@ -8,8 +8,8 @@ dependencies {
|
||||
optional(project(":spring-tx")) // for Quartz support
|
||||
optional("javax.activation:activation:1.1.1")
|
||||
optional("javax.mail:javax.mail-api:1.6.0")
|
||||
optional("javax.cache:cache-api:1.0.0")
|
||||
optional("com.github.ben-manes.caffeine:caffeine:2.6.0")
|
||||
optional("javax.cache:cache-api:1.1.0")
|
||||
optional("com.github.ben-manes.caffeine:caffeine:2.6.1")
|
||||
optional("net.sf.ehcache:ehcache:2.10.4")
|
||||
optional("org.quartz-scheduler:quartz:2.3.0")
|
||||
optional("org.codehaus.fabric3.api:commonj:1.1.0")
|
||||
@@ -17,7 +17,7 @@ dependencies {
|
||||
testCompile(project(":spring-context"))
|
||||
testCompile("org.hsqldb:hsqldb:${hsqldbVersion}")
|
||||
testCompile("org.slf4j:slf4j-api:${slf4jVersion}")
|
||||
testCompile("org.hibernate:hibernate-validator:6.0.5.Final")
|
||||
testCompile("org.hibernate:hibernate-validator:6.0.7.Final")
|
||||
testRuntime("org.ehcache:jcache:1.0.1")
|
||||
testRuntime("org.ehcache:ehcache:3.4.0")
|
||||
testRuntime("org.glassfish:javax.el:3.0.1-b08")
|
||||
|
||||
@@ -23,7 +23,7 @@ dependencies {
|
||||
optional("org.hibernate:hibernate-validator:5.4.2.Final")
|
||||
optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
|
||||
optional("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
|
||||
testCompile("org.apache.commons:commons-pool2:2.4.3")
|
||||
testCompile("org.apache.commons:commons-pool2:2.5.0")
|
||||
testCompile("org.slf4j:slf4j-api:${slf4jVersion}")
|
||||
testCompile("javax.inject:javax.inject-tck:1")
|
||||
testRuntime("javax.xml.bind:jaxb-api:2.3.0")
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,6 +34,7 @@ import org.springframework.lang.Nullable;
|
||||
* for detailed instructions.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.1
|
||||
* @see EnableCaching
|
||||
* @see CachingConfigurerSupport
|
||||
@@ -69,8 +70,8 @@ public interface CachingConfigurer {
|
||||
* Return the {@link CacheResolver} bean to use to resolve regular caches for
|
||||
* annotation-driven cache management. This is an alternative and more powerful
|
||||
* option of specifying the {@link CacheManager} to use.
|
||||
* <p>If both a {@link #cacheManager()} and {@link #cacheResolver()} are set, the
|
||||
* cache manager is ignored.
|
||||
* <p>If both a {@link #cacheManager()} and {@code #cacheResolver()} are set,
|
||||
* the cache manager is ignored.
|
||||
* <p>Implementations must explicitly declare
|
||||
* {@link org.springframework.context.annotation.Bean @Bean}, e.g.
|
||||
* <pre class="code">
|
||||
|
||||
+23
-25
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -51,22 +51,20 @@ import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base class for caching aspects, such as the {@link CacheInterceptor}
|
||||
* or an AspectJ aspect.
|
||||
* Base class for caching aspects, such as the {@link CacheInterceptor} or an
|
||||
* AspectJ aspect.
|
||||
*
|
||||
* <p>This enables the underlying Spring caching infrastructure to be
|
||||
* used easily to implement an aspect for any aspect system.
|
||||
* <p>This enables the underlying Spring caching infrastructure to be used easily
|
||||
* to implement an aspect for any aspect system.
|
||||
*
|
||||
* <p>Subclasses are responsible for calling methods in this class in
|
||||
* the correct order.
|
||||
* <p>Subclasses are responsible for calling relevant methods in the correct order.
|
||||
*
|
||||
* <p>Uses the <b>Strategy</b> design pattern. A {@link CacheResolver}
|
||||
* implementation will resolve the actual cache(s) to use, and a
|
||||
* {@link CacheOperationSource} is used for determining caching
|
||||
* operations.
|
||||
* <p>Uses the <b>Strategy</b> design pattern. A {@link CacheOperationSource} is
|
||||
* used for determining caching operations, a {@link KeyGenerator} will build the
|
||||
* cache keys, and a {@link CacheResolver} will resolve the actual cache(s) to use.
|
||||
*
|
||||
* <p>A cache aspect is serializable if its {@code CacheResolver} and
|
||||
* {@code CacheOperationSource} are serializable.
|
||||
* <p>Note: A cache aspect is serializable but does not perform any actual caching
|
||||
* after deserialization.
|
||||
*
|
||||
* @author Costin Leau
|
||||
* @author Juergen Hoeller
|
||||
@@ -121,7 +119,7 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
/**
|
||||
* Set the default {@link KeyGenerator} that this cache aspect should delegate to
|
||||
* if no specific key generator has been set for the operation.
|
||||
* <p>The default is a {@link SimpleKeyGenerator}
|
||||
* <p>The default is a {@link SimpleKeyGenerator}.
|
||||
*/
|
||||
public void setKeyGenerator(KeyGenerator keyGenerator) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
@@ -134,22 +132,12 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
return this.keyGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link CacheManager} to use to create a default {@link CacheResolver}.
|
||||
* Replace the current {@link CacheResolver}, if any.
|
||||
* @see #setCacheResolver(CacheResolver)
|
||||
* @see SimpleCacheResolver
|
||||
*/
|
||||
public void setCacheManager(CacheManager cacheManager) {
|
||||
this.cacheResolver = new SimpleCacheResolver(cacheManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link CacheResolver} that this cache aspect should delegate
|
||||
* to if no specific cache resolver has been set for the operation.
|
||||
* <p>The default resolver resolves the caches against their names and the
|
||||
* default cache manager.
|
||||
* @see #setCacheManager(org.springframework.cache.CacheManager)
|
||||
* @see #setCacheManager
|
||||
* @see SimpleCacheResolver
|
||||
*/
|
||||
public void setCacheResolver(@Nullable CacheResolver cacheResolver) {
|
||||
@@ -164,6 +152,16 @@ public abstract class CacheAspectSupport extends AbstractCacheInvoker
|
||||
return this.cacheResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link CacheManager} to use to create a default {@link CacheResolver}.
|
||||
* Replace the current {@link CacheResolver}, if any.
|
||||
* @see #setCacheResolver
|
||||
* @see SimpleCacheResolver
|
||||
*/
|
||||
public void setCacheManager(CacheManager cacheManager) {
|
||||
this.cacheResolver = new SimpleCacheResolver(cacheManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the containing {@link BeanFactory} for {@link CacheManager} and other
|
||||
* service lookups.
|
||||
|
||||
Vendored
+59
-10
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,10 @@ package org.springframework.cache.interceptor;
|
||||
import org.springframework.aop.Pointcut;
|
||||
import org.springframework.aop.framework.AbstractSingletonProxyFactoryBean;
|
||||
import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
/**
|
||||
* Proxy factory bean for simplified declarative caching handling.
|
||||
@@ -41,24 +45,59 @@ import org.springframework.aop.support.DefaultPointcutAdvisor;
|
||||
* @see CacheInterceptor
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
|
||||
public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean
|
||||
implements BeanFactoryAware, SmartInitializingSingleton {
|
||||
|
||||
private final CacheInterceptor cachingInterceptor = new CacheInterceptor();
|
||||
private final CacheInterceptor cacheInterceptor = new CacheInterceptor();
|
||||
|
||||
private Pointcut pointcut = Pointcut.TRUE;
|
||||
|
||||
|
||||
/**
|
||||
* Set the sources used to find cache operations.
|
||||
* Set one or more sources to find cache operations.
|
||||
* @see CacheInterceptor#setCacheOperationSources
|
||||
*/
|
||||
public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) {
|
||||
this.cachingInterceptor.setCacheOperationSources(cacheOperationSources);
|
||||
this.cacheInterceptor.setCacheOperationSources(cacheOperationSources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a pointcut, i.e a bean that can cause conditional invocation
|
||||
* of the CacheInterceptor depending on method and attributes passed.
|
||||
* Note: Additional interceptors are always invoked.
|
||||
* Set the default {@link KeyGenerator} that this cache aspect should delegate to
|
||||
* if no specific key generator has been set for the operation.
|
||||
* <p>The default is a {@link SimpleKeyGenerator}.
|
||||
* @since 5.0.3
|
||||
* @see CacheInterceptor#setKeyGenerator
|
||||
*/
|
||||
public void setKeyGenerator(KeyGenerator keyGenerator) {
|
||||
this.cacheInterceptor.setKeyGenerator(keyGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default {@link CacheResolver} that this cache aspect should delegate
|
||||
* to if no specific cache resolver has been set for the operation.
|
||||
* <p>The default resolver resolves the caches against their names and the
|
||||
* default cache manager.
|
||||
* @since 5.0.3
|
||||
* @see CacheInterceptor#setCacheResolver
|
||||
*/
|
||||
public void setCacheResolver(CacheResolver cacheResolver) {
|
||||
this.cacheInterceptor.setCacheResolver(cacheResolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link CacheManager} to use to create a default {@link CacheResolver}.
|
||||
* Replace the current {@link CacheResolver}, if any.
|
||||
* @since 5.0.3
|
||||
* @see CacheInterceptor#setCacheManager
|
||||
*/
|
||||
public void setCacheManager(CacheManager cacheManager) {
|
||||
this.cacheInterceptor.setCacheManager(cacheManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a pointcut, i.e. a bean that triggers conditional invocation of the
|
||||
* {@link CacheInterceptor} depending on the method and attributes passed.
|
||||
* <p>Note: Additional interceptors are always invoked.
|
||||
* @see #setPreInterceptors
|
||||
* @see #setPostInterceptors
|
||||
*/
|
||||
@@ -66,11 +105,21 @@ public class CacheProxyFactoryBean extends AbstractSingletonProxyFactoryBean {
|
||||
this.pointcut = pointcut;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanFactory(BeanFactory beanFactory) {
|
||||
this.cacheInterceptor.setBeanFactory(beanFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterSingletonsInstantiated() {
|
||||
this.cacheInterceptor.afterSingletonsInstantiated();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Object createMainInterceptor() {
|
||||
this.cachingInterceptor.afterPropertiesSet();
|
||||
return new DefaultPointcutAdvisor(this.pointcut, this.cachingInterceptor);
|
||||
this.cacheInterceptor.afterPropertiesSet();
|
||||
return new DefaultPointcutAdvisor(this.pointcut, this.cacheInterceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* </pre>
|
||||
*
|
||||
* The semantics of the above-mentioned annotations match their use at the component
|
||||
* class level: {@code Profile} allows for selective inclusion of certain beans.
|
||||
* class level: {@code @Profile} allows for selective inclusion of certain beans.
|
||||
* {@code @Scope} changes the bean's scope from singleton to the specified scope.
|
||||
* {@code @Lazy} only has an actual effect in case of the default singleton scope.
|
||||
* {@code @DependsOn} enforces the creation of specific other beans before this
|
||||
@@ -96,12 +96,12 @@ import org.springframework.core.annotation.AliasFor;
|
||||
* order values determine the order of resolved elements in case of collection
|
||||
* injection points (with several target beans matching by type and qualifier).
|
||||
*
|
||||
* <p><b>NOTE:</b> {@code @Order} values may influence priorities at injection points
|
||||
* <p><b>NOTE:</b> {@code @Order} values may influence priorities at injection points,
|
||||
* but please be aware that they do not influence singleton startup order which is an
|
||||
* orthogonal concern determined by dependency relationships and {@code @DependsOn}
|
||||
* declarations as mentioned above. Also, {@link javax.annotation.Priority} is not
|
||||
* available at this level since it cannot be declared on methods; its semantics can
|
||||
* be modelled through {@code @Order} values in combination with {@code @Primary} on
|
||||
* be modeled through {@code @Order} values in combination with {@code @Primary} on
|
||||
* a single bean per type.
|
||||
*
|
||||
* <h3>{@code @Bean} Methods in {@code @Configuration} Classes</h3>
|
||||
|
||||
+28
-30
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,6 @@ import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.FatalBeanException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanDefinitionHolder;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
@@ -216,6 +215,10 @@ public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
|
||||
scanner.addExcludeFilter(typeFilter);
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
parserContext.getReaderContext().warning(
|
||||
"Ignoring non-present type filter class: " + ex, parserContext.extractSource(element));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
parserContext.getReaderContext().error(
|
||||
ex.getMessage(), parserContext.extractSource(element), ex.getCause());
|
||||
@@ -225,39 +228,34 @@ public class ComponentScanBeanDefinitionParser implements BeanDefinitionParser {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected TypeFilter createTypeFilter(
|
||||
Element element, @Nullable ClassLoader classLoader, ParserContext parserContext) {
|
||||
protected TypeFilter createTypeFilter(Element element, @Nullable ClassLoader classLoader,
|
||||
ParserContext parserContext) throws ClassNotFoundException {
|
||||
|
||||
String filterType = element.getAttribute(FILTER_TYPE_ATTRIBUTE);
|
||||
String expression = element.getAttribute(FILTER_EXPRESSION_ATTRIBUTE);
|
||||
expression = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(expression);
|
||||
try {
|
||||
if ("annotation".equals(filterType)) {
|
||||
return new AnnotationTypeFilter((Class<Annotation>) ClassUtils.forName(expression, classLoader));
|
||||
}
|
||||
else if ("assignable".equals(filterType)) {
|
||||
return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
|
||||
}
|
||||
else if ("aspectj".equals(filterType)) {
|
||||
return new AspectJTypeFilter(expression, classLoader);
|
||||
}
|
||||
else if ("regex".equals(filterType)) {
|
||||
return new RegexPatternTypeFilter(Pattern.compile(expression));
|
||||
}
|
||||
else if ("custom".equals(filterType)) {
|
||||
Class<?> filterClass = ClassUtils.forName(expression, classLoader);
|
||||
if (!TypeFilter.class.isAssignableFrom(filterClass)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
|
||||
}
|
||||
return (TypeFilter) BeanUtils.instantiateClass(filterClass);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported filter type: " + filterType);
|
||||
}
|
||||
if ("annotation".equals(filterType)) {
|
||||
return new AnnotationTypeFilter((Class<Annotation>) ClassUtils.forName(expression, classLoader));
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new FatalBeanException("Type filter class not found: " + expression, ex);
|
||||
else if ("assignable".equals(filterType)) {
|
||||
return new AssignableTypeFilter(ClassUtils.forName(expression, classLoader));
|
||||
}
|
||||
else if ("aspectj".equals(filterType)) {
|
||||
return new AspectJTypeFilter(expression, classLoader);
|
||||
}
|
||||
else if ("regex".equals(filterType)) {
|
||||
return new RegexPatternTypeFilter(Pattern.compile(expression));
|
||||
}
|
||||
else if ("custom".equals(filterType)) {
|
||||
Class<?> filterClass = ClassUtils.forName(expression, classLoader);
|
||||
if (!TypeFilter.class.isAssignableFrom(filterClass)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Class is not assignable to [" + TypeFilter.class.getName() + "]: " + expression);
|
||||
}
|
||||
return (TypeFilter) BeanUtils.instantiateClass(filterClass);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unsupported filter type: " + filterType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -712,8 +712,7 @@ class ConfigurationClassParser {
|
||||
@Override
|
||||
@Nullable
|
||||
public AnnotationMetadata getImportingClassFor(String importedClass) {
|
||||
List<AnnotationMetadata> list = this.imports.get(importedClass);
|
||||
return (!CollectionUtils.isEmpty(list) ? list.get(list.size() - 1) : null);
|
||||
return CollectionUtils.lastElement(this.imports.get(importedClass));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+4
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -285,9 +285,9 @@ public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactor
|
||||
if ((getBeanFactory().containsSingleton(beanNameToRegister) &&
|
||||
(!isFactoryBean || matchesBeanType(Lifecycle.class, beanNameToCheck))) ||
|
||||
matchesBeanType(SmartLifecycle.class, beanNameToCheck)) {
|
||||
Lifecycle bean = getBeanFactory().getBean(beanNameToCheck, Lifecycle.class);
|
||||
if (bean != this) {
|
||||
beans.put(beanNameToRegister, bean);
|
||||
Object bean = getBeanFactory().getBean(beanNameToCheck);
|
||||
if (bean != this && bean instanceof Lifecycle) {
|
||||
beans.put(beanNameToRegister, (Lifecycle) bean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -185,7 +185,7 @@ public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerS
|
||||
/**
|
||||
* Implemented for compatibility with {@link org.springframework.beans.factory.config.PlaceholderConfigurerSupport}.
|
||||
* @deprecated in favor of {@link #processProperties(ConfigurableListableBeanFactory, ConfigurablePropertyResolver)}
|
||||
* @throws UnsupportedOperationException
|
||||
* @throws UnsupportedOperationException in this implementation
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
|
||||
+9
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -85,7 +85,8 @@ import org.springframework.util.StringUtils;
|
||||
* @see ResourceBundleMessageSource
|
||||
* @see java.util.ResourceBundle
|
||||
*/
|
||||
public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource implements ResourceLoaderAware {
|
||||
public class ReloadableResourceBundleMessageSource extends AbstractResourceBasedMessageSource
|
||||
implements ResourceLoaderAware {
|
||||
|
||||
private static final String PROPERTIES_SUFFIX = ".properties";
|
||||
|
||||
@@ -101,17 +102,14 @@ public class ReloadableResourceBundleMessageSource extends AbstractResourceBased
|
||||
|
||||
private ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||
|
||||
/** Cache to hold filename lists per Locale */
|
||||
private final ConcurrentMap<String, Map<Locale, List<String>>> cachedFilenames =
|
||||
new ConcurrentHashMap<>();
|
||||
// Cache to hold filename lists per Locale
|
||||
private final ConcurrentMap<String, Map<Locale, List<String>>> cachedFilenames = new ConcurrentHashMap<>();
|
||||
|
||||
/** Cache to hold already loaded properties per filename */
|
||||
private final ConcurrentMap<String, PropertiesHolder> cachedProperties =
|
||||
new ConcurrentHashMap<>();
|
||||
// Cache to hold already loaded properties per filename
|
||||
private final ConcurrentMap<String, PropertiesHolder> cachedProperties = new ConcurrentHashMap<>();
|
||||
|
||||
/** Cache to hold merged loaded properties per locale */
|
||||
private final ConcurrentMap<Locale, PropertiesHolder> cachedMergedProperties =
|
||||
new ConcurrentHashMap<>();
|
||||
// Cache to hold already loaded properties per filename
|
||||
private final ConcurrentMap<Locale, PropertiesHolder> cachedMergedProperties = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+60
-52
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,7 @@ import java.util.MissingResourceException;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -80,7 +81,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
* This allows for very efficient hash lookups, significantly faster
|
||||
* than the ResourceBundle class's own cache.
|
||||
*/
|
||||
private final Map<String, Map<Locale, ResourceBundle>> cachedResourceBundles = new HashMap<>();
|
||||
private final Map<String, Map<Locale, ResourceBundle>> cachedResourceBundles = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Cache to hold already generated MessageFormats.
|
||||
@@ -90,7 +91,7 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
* very efficient hash lookups without concatenated keys.
|
||||
* @see #getMessageFormat
|
||||
*/
|
||||
private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats = new HashMap<>();
|
||||
private final Map<ResourceBundle, Map<String, Map<Locale, MessageFormat>>> cachedBundleMessageFormats = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
@@ -178,31 +179,32 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
}
|
||||
else {
|
||||
// Cache forever: prefer locale cache over repeated getBundle calls.
|
||||
synchronized (this.cachedResourceBundles) {
|
||||
Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
|
||||
if (localeMap != null) {
|
||||
ResourceBundle bundle = localeMap.get(locale);
|
||||
if (bundle != null) {
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
try {
|
||||
ResourceBundle bundle = doGetBundle(basename, locale);
|
||||
if (localeMap == null) {
|
||||
localeMap = new HashMap<>();
|
||||
this.cachedResourceBundles.put(basename, localeMap);
|
||||
}
|
||||
localeMap.put(locale, bundle);
|
||||
Map<Locale, ResourceBundle> localeMap = this.cachedResourceBundles.get(basename);
|
||||
if (localeMap != null) {
|
||||
ResourceBundle bundle = localeMap.get(locale);
|
||||
if (bundle != null) {
|
||||
return bundle;
|
||||
}
|
||||
catch (MissingResourceException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
|
||||
}
|
||||
try {
|
||||
ResourceBundle bundle = doGetBundle(basename, locale);
|
||||
if (localeMap == null) {
|
||||
localeMap = new ConcurrentHashMap<>();
|
||||
Map<Locale, ResourceBundle> existing = this.cachedResourceBundles.putIfAbsent(basename, localeMap);
|
||||
if (existing != null) {
|
||||
localeMap = existing;
|
||||
}
|
||||
// Assume bundle not found
|
||||
// -> do NOT throw the exception to allow for checking parent message source.
|
||||
return null;
|
||||
}
|
||||
localeMap.put(locale, bundle);
|
||||
return bundle;
|
||||
}
|
||||
catch (MissingResourceException ex) {
|
||||
if (logger.isWarnEnabled()) {
|
||||
logger.warn("ResourceBundle [" + basename + "] not found for MessageSource: " + ex.getMessage());
|
||||
}
|
||||
// Assume bundle not found
|
||||
// -> do NOT throw the exception to allow for checking parent message source.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,36 +251,40 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
protected MessageFormat getMessageFormat(ResourceBundle bundle, String code, Locale locale)
|
||||
throws MissingResourceException {
|
||||
|
||||
synchronized (this.cachedBundleMessageFormats) {
|
||||
Map<String, Map<Locale, MessageFormat>> codeMap = this.cachedBundleMessageFormats.get(bundle);
|
||||
Map<Locale, MessageFormat> localeMap = null;
|
||||
if (codeMap != null) {
|
||||
localeMap = codeMap.get(code);
|
||||
if (localeMap != null) {
|
||||
MessageFormat result = localeMap.get(locale);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
Map<String, Map<Locale, MessageFormat>> codeMap = this.cachedBundleMessageFormats.get(bundle);
|
||||
Map<Locale, MessageFormat> localeMap = null;
|
||||
if (codeMap != null) {
|
||||
localeMap = codeMap.get(code);
|
||||
if (localeMap != null) {
|
||||
MessageFormat result = localeMap.get(locale);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
String msg = getStringOrNull(bundle, code);
|
||||
if (msg != null) {
|
||||
if (codeMap == null) {
|
||||
codeMap = new HashMap<>();
|
||||
this.cachedBundleMessageFormats.put(bundle, codeMap);
|
||||
}
|
||||
if (localeMap == null) {
|
||||
localeMap = new HashMap<>();
|
||||
codeMap.put(code, localeMap);
|
||||
}
|
||||
MessageFormat result = createMessageFormat(msg, locale);
|
||||
localeMap.put(locale, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String msg = getStringOrNull(bundle, code);
|
||||
if (msg != null) {
|
||||
if (codeMap == null) {
|
||||
codeMap = new ConcurrentHashMap<>();
|
||||
Map<String, Map<Locale, MessageFormat>> existing = this.cachedBundleMessageFormats.putIfAbsent(bundle, codeMap);
|
||||
if (existing != null) {
|
||||
codeMap = existing;
|
||||
}
|
||||
}
|
||||
if (localeMap == null) {
|
||||
localeMap = new ConcurrentHashMap<>();
|
||||
Map<Locale, MessageFormat> existing = codeMap.putIfAbsent(code, localeMap);
|
||||
if (existing != null) {
|
||||
localeMap = existing;
|
||||
}
|
||||
}
|
||||
MessageFormat result = createMessageFormat(msg, locale);
|
||||
localeMap.put(locale, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,7 +400,9 @@ public class ResourceBundleMessageSource extends AbstractResourceBasedMessageSou
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsReload(String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) {
|
||||
public boolean needsReload(
|
||||
String baseName, Locale locale, String format, ClassLoader loader, ResourceBundle bundle, long loadTime) {
|
||||
|
||||
if (super.needsReload(baseName, locale, format, loader, bundle, loadTime)) {
|
||||
cachedBundleMessageFormats.remove(bundle);
|
||||
return true;
|
||||
|
||||
+11
-14
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,7 +47,7 @@ import org.springframework.util.Assert;
|
||||
* appropriate weaver implementation: As of Spring Framework 5.0, it detects
|
||||
* Oracle WebLogic 10+, GlassFish 4+, Tomcat 8+, WildFly 8+, IBM WebSphere 8.5+,
|
||||
* {@link InstrumentationSavingAgent Spring's VM agent}, and any {@link ClassLoader}
|
||||
* supported by Spring's {@link ReflectiveLoadTimeWeaver}.
|
||||
* supported by Spring's {@link ReflectiveLoadTimeWeaver} (such as Liberty's).
|
||||
*
|
||||
* @author Juergen Hoeller
|
||||
* @author Ramnivas Laddad
|
||||
@@ -104,32 +104,29 @@ public class DefaultContextLoadTimeWeaver implements LoadTimeWeaver, BeanClassLo
|
||||
* This method never fails, allowing to try other possible ways to use an
|
||||
* server-agnostic weaver. This non-failure logic is required since
|
||||
* determining a load-time weaver based on the ClassLoader name alone may
|
||||
* legitimately fail due to other mismatches. Specific case in point: the
|
||||
* use of WebLogicLoadTimeWeaver works for WLS 10 but fails due to the lack
|
||||
* of a specific method (addInstanceClassPreProcessor) for any earlier
|
||||
* versions even though the ClassLoader name is the same.
|
||||
* legitimately fail due to other mismatches.
|
||||
*/
|
||||
@Nullable
|
||||
protected LoadTimeWeaver createServerSpecificLoadTimeWeaver(ClassLoader classLoader) {
|
||||
String name = classLoader.getClass().getName();
|
||||
try {
|
||||
if (name.startsWith("weblogic")) {
|
||||
return new WebLogicLoadTimeWeaver(classLoader);
|
||||
if (name.startsWith("org.apache.catalina")) {
|
||||
return new TomcatLoadTimeWeaver(classLoader);
|
||||
}
|
||||
else if (name.startsWith("org.glassfish")) {
|
||||
return new GlassFishLoadTimeWeaver(classLoader);
|
||||
}
|
||||
else if (name.startsWith("org.apache.catalina")) {
|
||||
return new TomcatLoadTimeWeaver(classLoader);
|
||||
}
|
||||
else if (name.startsWith("org.jboss")) {
|
||||
else if (name.startsWith("org.jboss.modules")) {
|
||||
return new JBossLoadTimeWeaver(classLoader);
|
||||
}
|
||||
else if (name.startsWith("com.ibm")) {
|
||||
else if (name.startsWith("com.ibm.ws.classloader")) {
|
||||
return new WebSphereLoadTimeWeaver(classLoader);
|
||||
}
|
||||
else if (name.startsWith("weblogic")) {
|
||||
return new WebLogicLoadTimeWeaver(classLoader);
|
||||
}
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
catch (Exception ex) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Could not obtain server-specific LoadTimeWeaver: " + ex.getMessage());
|
||||
}
|
||||
|
||||
+2
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -70,6 +70,7 @@ class WebLogicClassLoaderAdapter {
|
||||
throw new IllegalStateException(
|
||||
"Could not initialize WebLogic LoadTimeWeaver because WebLogic 10 API classes are not available", ex);
|
||||
}
|
||||
|
||||
if (!wlGenericClassLoaderClass.isInstance(classLoader)) {
|
||||
throw new IllegalArgumentException(
|
||||
"ClassLoader must be an instance of [" + wlGenericClassLoaderClass.getName() + "]: " + classLoader);
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -74,7 +74,8 @@ class WebSphereClassLoaderAdapter {
|
||||
}
|
||||
|
||||
if (!wsCompoundClassLoaderClass.isInstance(classLoader)) {
|
||||
throw new IllegalArgumentException("ClassLoader must be instance of " + COMPOUND_CLASS_LOADER_NAME);
|
||||
throw new IllegalArgumentException(
|
||||
"ClassLoader must be an instance of [" + COMPOUND_CLASS_LOADER_NAME + "]: " + classLoader);
|
||||
}
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
+5
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -32,6 +32,7 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.aop.support.AopUtils;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
@@ -311,7 +312,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(final Object bean, String beanName) {
|
||||
Class<?> targetClass = AopUtils.getTargetClass(bean);
|
||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);
|
||||
if (!this.nonAnnotatedClasses.contains(targetClass)) {
|
||||
Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
|
||||
(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {
|
||||
@@ -365,7 +366,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
|
||||
"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -443,7 +444,7 @@ public class ScheduledAnnotationBeanPostProcessor
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
|
||||
"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into long");
|
||||
}
|
||||
tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));
|
||||
}
|
||||
|
||||
Vendored
+129
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.cache.interceptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CacheProxyFactoryBean}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @author Juergen Hoeller
|
||||
*/
|
||||
public class CacheProxyFactoryBeanTests {
|
||||
|
||||
@Test
|
||||
public void configurationClassWithCacheProxyFactoryBean() {
|
||||
try (AnnotationConfigApplicationContext applicationContext =
|
||||
new AnnotationConfigApplicationContext(CacheProxyFactoryBeanConfiguration.class)) {
|
||||
Greeter greeter = applicationContext.getBean("greeter", Greeter.class);
|
||||
assertNotNull(greeter);
|
||||
assertFalse(greeter.isCacheMiss());
|
||||
assertEquals("Hello John!", greeter.greet("John"));
|
||||
assertTrue(greeter.isCacheMiss());
|
||||
assertEquals("Hello Jon!", greeter.greet("Jon"));
|
||||
assertTrue(greeter.isCacheMiss());
|
||||
assertEquals("Hello John!", greeter.greet("John"));
|
||||
assertFalse(greeter.isCacheMiss());
|
||||
assertEquals("Hello World!", greeter.greet());
|
||||
assertTrue(greeter.isCacheMiss());
|
||||
assertEquals("Hello World!", greeter.greet());
|
||||
assertFalse(greeter.isCacheMiss());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@EnableCaching
|
||||
static class CacheProxyFactoryBeanConfiguration {
|
||||
|
||||
@Bean
|
||||
ConcurrentMapCacheManager cacheManager() {
|
||||
return new ConcurrentMapCacheManager("Greetings");
|
||||
}
|
||||
|
||||
@Bean
|
||||
CacheProxyFactoryBean greeter() {
|
||||
CacheProxyFactoryBean factoryBean = new CacheProxyFactoryBean();
|
||||
factoryBean.setCacheOperationSources(newCacheOperationSource("greet", newCacheOperation("Greetings")));
|
||||
factoryBean.setTarget(new SimpleGreeter());
|
||||
return factoryBean;
|
||||
}
|
||||
|
||||
CacheOperationSource newCacheOperationSource(String methodName, CacheOperation... cacheOperations) {
|
||||
NameMatchCacheOperationSource cacheOperationSource = new NameMatchCacheOperationSource();
|
||||
cacheOperationSource.addCacheMethod(methodName, Arrays.asList(cacheOperations));
|
||||
return cacheOperationSource;
|
||||
}
|
||||
|
||||
CacheableOperation newCacheOperation(String cacheName) {
|
||||
CacheableOperation.Builder builder = new CacheableOperation.Builder();
|
||||
builder.setCacheManager("cacheManager");
|
||||
builder.setCacheName(cacheName);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
interface Greeter {
|
||||
|
||||
default boolean isCacheHit() {
|
||||
return !isCacheMiss();
|
||||
}
|
||||
|
||||
boolean isCacheMiss();
|
||||
|
||||
void setCacheMiss();
|
||||
|
||||
default String greet() {
|
||||
return greet("World");
|
||||
}
|
||||
|
||||
default String greet(String name) {
|
||||
setCacheMiss();
|
||||
return String.format("Hello %s!", name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class SimpleGreeter implements Greeter {
|
||||
|
||||
private final AtomicBoolean cacheMiss = new AtomicBoolean(false);
|
||||
|
||||
@Override
|
||||
public boolean isCacheMiss() {
|
||||
return this.cacheMiss.getAndSet(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheMiss() {
|
||||
this.cacheMiss.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -1132,6 +1132,11 @@ public class ConfigurationClassPostProcessorTests {
|
||||
public RepositoryFactoryBean<Object> repoFactoryBean() {
|
||||
return new RepositoryFactoryBean<>();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FactoryBean<Object> nullFactoryBean() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
+18
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,7 @@ import java.util.TimeZone;
|
||||
import org.junit.After;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
@@ -179,6 +180,14 @@ public class ScheduledAnnotationBeanPostProcessorTests {
|
||||
severalFixedRates(context, processorDefinition, targetDefinition);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void severalFixedRatesAgainstNestedCglibProxy() {
|
||||
BeanDefinition processorDefinition = new RootBeanDefinition(ScheduledAnnotationBeanPostProcessor.class);
|
||||
BeanDefinition targetDefinition = new RootBeanDefinition(SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean.class);
|
||||
targetDefinition.setFactoryMethodName("nestedProxy");
|
||||
severalFixedRates(context, processorDefinition, targetDefinition);
|
||||
}
|
||||
|
||||
private void severalFixedRates(StaticApplicationContext context,
|
||||
BeanDefinition processorDefinition, BeanDefinition targetDefinition) {
|
||||
|
||||
@@ -631,6 +640,14 @@ public class ScheduledAnnotationBeanPostProcessorTests {
|
||||
@Scheduled(fixedRate = 4000, initialDelay = 2000)
|
||||
public void fixedRate() {
|
||||
}
|
||||
|
||||
static SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean nestedProxy() {
|
||||
ProxyFactory pf1 = new ProxyFactory(new SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean());
|
||||
pf1.setProxyTargetClass(true);
|
||||
ProxyFactory pf2 = new ProxyFactory(pf1.getProxy());
|
||||
pf2.setProxyTargetClass(true);
|
||||
return (SeveralFixedRatesWithRepeatedScheduledAnnotationTestBean) pf2.getProxy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ dependencyManagement {
|
||||
// both into the spring-core jar. cglib 3.2 itself depends on asm 5.x and is therefore
|
||||
// further transformed by the JarJar task to depend on org.springframework.asm; this
|
||||
// avoids including two different copies of asm unnecessarily.
|
||||
def cglibVersion = "3.2.5"
|
||||
def cglibVersion = "3.2.6"
|
||||
def objenesisVersion = "2.6"
|
||||
|
||||
configurations {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -196,7 +196,10 @@ public abstract class BridgeMethodResolver {
|
||||
return method;
|
||||
}
|
||||
else {
|
||||
return searchInterfaces(ifc.getInterfaces(), bridgeMethod);
|
||||
method = searchInterfaces(ifc.getInterfaces(), bridgeMethod);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,16 +16,10 @@
|
||||
|
||||
package org.springframework.core;
|
||||
|
||||
import java.io.Externalizable;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -47,19 +41,7 @@ public abstract class Conventions {
|
||||
*/
|
||||
private static final String PLURAL_SUFFIX = "List";
|
||||
|
||||
/**
|
||||
* Set of interfaces that are supposed to be ignored
|
||||
* when searching for the 'primary' interface of a proxy.
|
||||
*/
|
||||
private static final Set<Class<?>> IGNORED_INTERFACES;
|
||||
|
||||
static {
|
||||
IGNORED_INTERFACES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
Serializable.class, Externalizable.class, Cloneable.class, Comparable.class)));
|
||||
}
|
||||
|
||||
private static final ReactiveAdapterRegistry reactiveAdapterRegistry =
|
||||
ReactiveAdapterRegistry.getSharedInstance();
|
||||
private static final ReactiveAdapterRegistry reactiveAdapterRegistry = ReactiveAdapterRegistry.getSharedInstance();
|
||||
|
||||
|
||||
/**
|
||||
@@ -67,16 +49,13 @@ public abstract class Conventions {
|
||||
* based on its concrete type. The convention used is to return the
|
||||
* un-capitalized short name of the {@code Class}, according to JavaBeans
|
||||
* property naming rules.
|
||||
*
|
||||
* <p>For example:<br>
|
||||
* {@code com.myapp.Product} becomes {@code "product"}<br>
|
||||
* {@code com.myapp.MyProduct} becomes {@code "myProduct"}<br>
|
||||
* {@code com.myapp.UKProduct} becomes {@code "UKProduct"}<br>
|
||||
*
|
||||
* <p>For arrays the pluralized version of the array component type is used.
|
||||
* For {@code Collection}s an attempt is made to 'peek ahead' to determine
|
||||
* the component type and return its pluralized version.
|
||||
*
|
||||
* @param value the value to generate a variable name for
|
||||
* @return the generated variable name
|
||||
*/
|
||||
@@ -110,12 +89,10 @@ public abstract class Conventions {
|
||||
/**
|
||||
* Determine the conventional variable name for the given parameter taking
|
||||
* the generic collection type, if any, into account.
|
||||
*
|
||||
* <p>As of 5.0 this method supports reactive types:<br>
|
||||
* {@code Mono<com.myapp.Product>} becomes {@code "productMono"}<br>
|
||||
* {@code Flux<com.myapp.MyProduct>} becomes {@code "myProductFlux"}<br>
|
||||
* {@code Observable<com.myapp.MyProduct>} becomes {@code "myProductObservable"}<br>
|
||||
*
|
||||
* @param parameter the method or constructor parameter
|
||||
* @return the generated variable name
|
||||
*/
|
||||
@@ -295,7 +272,7 @@ public abstract class Conventions {
|
||||
if (Proxy.isProxyClass(valueClass)) {
|
||||
Class<?>[] ifcs = valueClass.getInterfaces();
|
||||
for (Class<?> ifc : ifcs) {
|
||||
if (!IGNORED_INTERFACES.contains(ifc)) {
|
||||
if (!ClassUtils.isJavaLanguageInterface(ifc)) {
|
||||
return ifc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -117,36 +117,35 @@ public class ResolvableType implements Serializable {
|
||||
@Nullable
|
||||
private final ResolvableType componentType;
|
||||
|
||||
/**
|
||||
* Copy of the resolved value.
|
||||
*/
|
||||
@Nullable
|
||||
private final Class<?> resolved;
|
||||
|
||||
@Nullable
|
||||
private final Integer hash;
|
||||
|
||||
@Nullable
|
||||
private ResolvableType superType;
|
||||
private Class<?> resolved;
|
||||
|
||||
@Nullable
|
||||
private ResolvableType[] interfaces;
|
||||
private volatile ResolvableType superType;
|
||||
|
||||
@Nullable
|
||||
private ResolvableType[] generics;
|
||||
private volatile ResolvableType[] interfaces;
|
||||
|
||||
@Nullable
|
||||
private volatile ResolvableType[] generics;
|
||||
|
||||
|
||||
/**
|
||||
* Private constructor used to create a new {@link ResolvableType} for cache key purposes,
|
||||
* with no upfront resolution.
|
||||
*/
|
||||
private ResolvableType(Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {
|
||||
private ResolvableType(
|
||||
Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {
|
||||
|
||||
this.type = type;
|
||||
this.typeProvider = typeProvider;
|
||||
this.variableResolver = variableResolver;
|
||||
this.componentType = null;
|
||||
this.resolved = null;
|
||||
this.hash = calculateHashCode();
|
||||
this.resolved = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,8 +160,8 @@ public class ResolvableType implements Serializable {
|
||||
this.typeProvider = typeProvider;
|
||||
this.variableResolver = variableResolver;
|
||||
this.componentType = null;
|
||||
this.resolved = resolveClass();
|
||||
this.hash = hash;
|
||||
this.resolved = resolveClass();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,8 +175,8 @@ public class ResolvableType implements Serializable {
|
||||
this.typeProvider = typeProvider;
|
||||
this.variableResolver = variableResolver;
|
||||
this.componentType = componentType;
|
||||
this.resolved = resolveClass();
|
||||
this.hash = null;
|
||||
this.resolved = resolveClass();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -453,10 +452,12 @@ public class ResolvableType implements Serializable {
|
||||
if (resolved == null || resolved.getGenericSuperclass() == null) {
|
||||
return NONE;
|
||||
}
|
||||
if (this.superType == null) {
|
||||
this.superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver());
|
||||
ResolvableType superType = this.superType;
|
||||
if (superType == null) {
|
||||
superType = forType(SerializableTypeWrapper.forGenericSuperclass(resolved), asVariableResolver());
|
||||
this.superType = superType;
|
||||
}
|
||||
return this.superType;
|
||||
return superType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,10 +471,12 @@ public class ResolvableType implements Serializable {
|
||||
if (resolved == null || ObjectUtils.isEmpty(resolved.getGenericInterfaces())) {
|
||||
return EMPTY_TYPES_ARRAY;
|
||||
}
|
||||
if (this.interfaces == null) {
|
||||
this.interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver());
|
||||
ResolvableType[] interfaces = this.interfaces;
|
||||
if (interfaces == null) {
|
||||
interfaces = forTypes(SerializableTypeWrapper.forGenericInterfaces(resolved), asVariableResolver());
|
||||
this.interfaces = interfaces;
|
||||
}
|
||||
return this.interfaces;
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -667,24 +670,25 @@ public class ResolvableType implements Serializable {
|
||||
if (this == NONE) {
|
||||
return EMPTY_TYPES_ARRAY;
|
||||
}
|
||||
if (this.generics == null) {
|
||||
ResolvableType[] generics = this.generics;
|
||||
if (generics == null) {
|
||||
if (this.type instanceof Class) {
|
||||
Class<?> typeClass = (Class<?>) this.type;
|
||||
this.generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
|
||||
generics = forTypes(SerializableTypeWrapper.forTypeParameters(typeClass), this.variableResolver);
|
||||
}
|
||||
else if (this.type instanceof ParameterizedType) {
|
||||
Type[] actualTypeArguments = ((ParameterizedType) this.type).getActualTypeArguments();
|
||||
ResolvableType[] generics = new ResolvableType[actualTypeArguments.length];
|
||||
generics = new ResolvableType[actualTypeArguments.length];
|
||||
for (int i = 0; i < actualTypeArguments.length; i++) {
|
||||
generics[i] = forType(actualTypeArguments[i], this.variableResolver);
|
||||
}
|
||||
this.generics = generics;
|
||||
}
|
||||
else {
|
||||
this.generics = resolveType().getGenerics();
|
||||
generics = resolveType().getGenerics();
|
||||
}
|
||||
this.generics = generics;
|
||||
}
|
||||
return this.generics;
|
||||
return generics;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -748,7 +752,7 @@ public class ResolvableType implements Serializable {
|
||||
*/
|
||||
@Nullable
|
||||
public Class<?> resolve() {
|
||||
return (this.resolved != null ? this.resolved : null);
|
||||
return this.resolved;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1372,7 +1376,9 @@ public class ResolvableType implements Serializable {
|
||||
* @param variableResolver the variable resolver or {@code null}
|
||||
* @return a {@link ResolvableType} for the specified {@link Type} and {@link VariableResolver}
|
||||
*/
|
||||
static ResolvableType forType(@Nullable Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {
|
||||
static ResolvableType forType(
|
||||
@Nullable Type type, @Nullable TypeProvider typeProvider, @Nullable VariableResolver variableResolver) {
|
||||
|
||||
if (type == null && typeProvider != null) {
|
||||
type = SerializableTypeWrapper.forTypeProvider(typeProvider);
|
||||
}
|
||||
@@ -1390,13 +1396,14 @@ public class ResolvableType implements Serializable {
|
||||
cache.purgeUnreferencedEntries();
|
||||
|
||||
// Check the cache - we may have a ResolvableType which has been resolved before...
|
||||
ResolvableType key = new ResolvableType(type, typeProvider, variableResolver);
|
||||
ResolvableType resolvableType = cache.get(key);
|
||||
if (resolvableType == null) {
|
||||
resolvableType = new ResolvableType(type, typeProvider, variableResolver, key.hash);
|
||||
cache.put(resolvableType, resolvableType);
|
||||
ResolvableType resultType = new ResolvableType(type, typeProvider, variableResolver);
|
||||
ResolvableType cachedType = cache.get(resultType);
|
||||
if (cachedType == null) {
|
||||
cachedType = new ResolvableType(type, typeProvider, variableResolver, resultType.hash);
|
||||
cache.put(cachedType, cachedType);
|
||||
}
|
||||
return resolvableType;
|
||||
resultType.resolved = cachedType.resolved;
|
||||
return resultType;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,7 +42,7 @@ import org.springframework.core.Ordered;
|
||||
*
|
||||
* <p>Since Spring 4.1, the standard {@link javax.annotation.Priority} annotation
|
||||
* can be used as a drop-in replacement for this annotation in ordering scenarios.
|
||||
* Note that {@code Priority} may have additional semantics when a single element
|
||||
* Note that {@code @Priority} may have additional semantics when a single element
|
||||
* has to be picked (see {@link AnnotationAwareOrderComparator#getPriority}).
|
||||
*
|
||||
* <p>Alternatively, order values may also be determined on a per-instance basis
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MimeType;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@code Decoder} implementations that can decode
|
||||
* a {@code DataBuffer} directly to the target element type.
|
||||
*
|
||||
* <p>Sub-classes must implement {@link #decodeDataBuffer} to provide a way to
|
||||
* transform a {@code DataBuffer} to the target data type. The default
|
||||
* {@link #decode} implementation transforms each individual data buffer while
|
||||
* {@link #decodeToMono} applies "reduce" and transforms the aggregated buffer.
|
||||
*
|
||||
* <p>Sub-classes can override {@link #decode} in order to split the input stream
|
||||
* along different boundaries (e.g. on new line characters for {@code String})
|
||||
* or always reduce to a single data buffer (e.g. {@code Resource}).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public abstract class AbstractDataBufferDecoder<T> extends AbstractDecoder<T> {
|
||||
|
||||
|
||||
protected AbstractDataBufferDecoder(MimeType... supportedMimeTypes) {
|
||||
super(supportedMimeTypes);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Flux<T> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream).map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return DataBufferUtils.join(inputStream)
|
||||
.map(buffer -> decodeDataBuffer(buffer, elementType, mimeType, hints));
|
||||
}
|
||||
|
||||
/**
|
||||
* How to decode a {@code DataBuffer} to the target element type.
|
||||
*/
|
||||
protected abstract T decodeDataBuffer(DataBuffer buffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints);
|
||||
|
||||
}
|
||||
@@ -18,9 +18,6 @@ package org.springframework.core.codec;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
@@ -32,9 +29,11 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* Decoder for {@code byte} arrays.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ByteArrayDecoder extends AbstractDecoder<byte[]> {
|
||||
public class ByteArrayDecoder extends AbstractDataBufferDecoder<byte[]> {
|
||||
|
||||
|
||||
public ByteArrayDecoder() {
|
||||
super(MimeTypeUtils.ALL);
|
||||
@@ -48,16 +47,13 @@ public class ByteArrayDecoder extends AbstractDecoder<byte[]> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<byte[]> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
protected byte[] decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream).map((dataBuffer) -> {
|
||||
byte[] result = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(result);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return result ;
|
||||
});
|
||||
byte[] result = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(result);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,6 @@ package org.springframework.core.codec;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
@@ -34,9 +31,11 @@ import org.springframework.util.MimeTypeUtils;
|
||||
*
|
||||
* @author Sebastien Deleuze
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ByteBufferDecoder extends AbstractDecoder<ByteBuffer> {
|
||||
public class ByteBufferDecoder extends AbstractDataBufferDecoder<ByteBuffer> {
|
||||
|
||||
|
||||
public ByteBufferDecoder() {
|
||||
super(MimeTypeUtils.ALL);
|
||||
@@ -50,16 +49,14 @@ public class ByteBufferDecoder extends AbstractDecoder<ByteBuffer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<ByteBuffer> decode(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
protected ByteBuffer decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream).map((dataBuffer) -> {
|
||||
ByteBuffer copy = ByteBuffer.allocate(dataBuffer.readableByteCount());
|
||||
copy.put(dataBuffer.asByteBuffer());
|
||||
copy.flip();
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return copy;
|
||||
});
|
||||
ByteBuffer copy = ByteBuffer.allocate(dataBuffer.readableByteCount());
|
||||
copy.put(dataBuffer.asByteBuffer());
|
||||
copy.flip();
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,9 +34,11 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* {@link org.springframework.core.io.buffer.DataBufferUtils#release(DataBuffer)}.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class DataBufferDecoder extends AbstractDecoder<DataBuffer> {
|
||||
public class DataBufferDecoder extends AbstractDataBufferDecoder<DataBuffer> {
|
||||
|
||||
|
||||
public DataBufferDecoder() {
|
||||
super(MimeTypeUtils.ALL);
|
||||
@@ -56,4 +58,11 @@ public class DataBufferDecoder extends AbstractDecoder<DataBuffer> {
|
||||
return Flux.from(inputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DataBuffer decodeDataBuffer(DataBuffer buffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.util.Map;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
@@ -38,9 +37,11 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* Decoder for {@link Resource}s.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 5.0
|
||||
*/
|
||||
public class ResourceDecoder extends AbstractDecoder<Resource> {
|
||||
public class ResourceDecoder extends AbstractDataBufferDecoder<Resource> {
|
||||
|
||||
|
||||
public ResourceDecoder() {
|
||||
super(MimeTypeUtils.ALL);
|
||||
@@ -63,30 +64,24 @@ public class ResourceDecoder extends AbstractDecoder<Resource> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Resource> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
protected Resource decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
|
||||
Class<?> clazz = elementType.getRawClass();
|
||||
Assert.state(clazz != null, "No resource class");
|
||||
|
||||
Mono<byte[]> byteArray = Flux.from(inputStream).
|
||||
reduce(DataBuffer::write).
|
||||
map(dataBuffer -> {
|
||||
byte[] bytes = new byte[dataBuffer.readableByteCount()];
|
||||
dataBuffer.read(bytes);
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
return bytes;
|
||||
});
|
||||
|
||||
|
||||
if (InputStreamResource.class == clazz) {
|
||||
return Mono.from(byteArray.map(ByteArrayInputStream::new).map(InputStreamResource::new));
|
||||
return new InputStreamResource(new ByteArrayInputStream(bytes));
|
||||
}
|
||||
else if (clazz.isAssignableFrom(ByteArrayResource.class)) {
|
||||
return Mono.from(byteArray.map(ByteArrayResource::new));
|
||||
return new ByteArrayResource(bytes);
|
||||
}
|
||||
else {
|
||||
return Mono.error(new IllegalStateException("Unsupported resource class: " + clazz));
|
||||
throw new IllegalStateException("Unsupported resource class: " + clazz);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.function.IntPredicate;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
@@ -49,7 +48,7 @@ import org.springframework.util.MimeTypeUtils;
|
||||
* @since 5.0
|
||||
* @see CharSequenceEncoder
|
||||
*/
|
||||
public class StringDecoder extends AbstractDecoder<String> {
|
||||
public class StringDecoder extends AbstractDataBufferDecoder<String> {
|
||||
|
||||
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
@@ -84,16 +83,7 @@ public class StringDecoder extends AbstractDecoder<String> {
|
||||
if (this.splitOnNewline) {
|
||||
inputFlux = Flux.from(inputStream).flatMap(StringDecoder::splitOnNewline);
|
||||
}
|
||||
return inputFlux.map(buffer -> decodeDataBuffer(buffer, mimeType));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<String> decodeToMono(Publisher<DataBuffer> inputStream, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
return Flux.from(inputStream)
|
||||
.reduce(DataBuffer::write)
|
||||
.map(buffer -> decodeDataBuffer(buffer, mimeType));
|
||||
return super.decode(inputFlux, elementType, mimeType, hints);
|
||||
}
|
||||
|
||||
private static Flux<DataBuffer> splitOnNewline(DataBuffer dataBuffer) {
|
||||
@@ -113,7 +103,10 @@ public class StringDecoder extends AbstractDecoder<String> {
|
||||
return Flux.fromIterable(results);
|
||||
}
|
||||
|
||||
private String decodeDataBuffer(DataBuffer dataBuffer, @Nullable MimeType mimeType) {
|
||||
@Override
|
||||
protected String decodeDataBuffer(DataBuffer dataBuffer, ResolvableType elementType,
|
||||
@Nullable MimeType mimeType, @Nullable Map<String, Object> hints) {
|
||||
|
||||
Charset charset = getCharset(mimeType);
|
||||
CharBuffer charBuffer = charset.decode(dataBuffer.asByteBuffer());
|
||||
DataBufferUtils.release(dataBuffer);
|
||||
|
||||
@@ -239,11 +239,9 @@ public final class Property {
|
||||
field = ReflectionUtils.findField(declaringClass, name);
|
||||
if (field == null) {
|
||||
// Same lenient fallback checking as in CachedIntrospectionResults...
|
||||
field = ReflectionUtils.findField(declaringClass,
|
||||
name.substring(0, 1).toLowerCase() + name.substring(1));
|
||||
field = ReflectionUtils.findField(declaringClass, StringUtils.uncapitalize(name));
|
||||
if (field == null) {
|
||||
field = ReflectionUtils.findField(declaringClass,
|
||||
name.substring(0, 1).toUpperCase() + name.substring(1));
|
||||
field = ReflectionUtils.findField(declaringClass, StringUtils.capitalize(name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,8 @@ import joptsimple.OptionSet;
|
||||
import joptsimple.OptionSpec;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link CommandLinePropertySource} implementation backed by a JOpt {@link OptionSet}.
|
||||
@@ -86,13 +88,13 @@ public class JOptCommandLinePropertySource extends CommandLinePropertySource<Opt
|
||||
public String[] getPropertyNames() {
|
||||
List<String> names = new ArrayList<>();
|
||||
for (OptionSpec<?> spec : this.source.specs()) {
|
||||
List<String> aliases = new ArrayList<>(spec.options());
|
||||
if (!aliases.isEmpty()) {
|
||||
String lastOption = CollectionUtils.lastElement(spec.options());
|
||||
if (lastOption != null) {
|
||||
// Only the longest name is used for enumerating
|
||||
names.add(aliases.get(aliases.size() - 1));
|
||||
names.add(lastOption);
|
||||
}
|
||||
}
|
||||
return names.toArray(new String[names.size()]);
|
||||
return StringUtils.toStringArray(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+69
-68
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import org.springframework.util.ResourceUtils;
|
||||
@@ -42,6 +43,68 @@ import org.springframework.util.ResourceUtils;
|
||||
*/
|
||||
public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try {
|
||||
URL url = getURL();
|
||||
if (ResourceUtils.isFileURL(url)) {
|
||||
// Proceed with file system resolution
|
||||
return getFile().exists();
|
||||
}
|
||||
else {
|
||||
// Try a URL connection content-length header
|
||||
URLConnection con = url.openConnection();
|
||||
customizeConnection(con);
|
||||
HttpURLConnection httpCon =
|
||||
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
|
||||
if (httpCon != null) {
|
||||
int code = httpCon.getResponseCode();
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
return true;
|
||||
}
|
||||
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (con.getContentLength() >= 0) {
|
||||
return true;
|
||||
}
|
||||
if (httpCon != null) {
|
||||
// no HTTP OK status, and no content-length header: give up
|
||||
httpCon.disconnect();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// Fall back to stream existence: can we open the stream?
|
||||
InputStream is = getInputStream();
|
||||
is.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable() {
|
||||
try {
|
||||
URL url = getURL();
|
||||
if (ResourceUtils.isFileURL(url)) {
|
||||
// Proceed with file system resolution
|
||||
File file = getFile();
|
||||
return (file.canRead() && !file.isDirectory());
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
try {
|
||||
@@ -123,81 +186,20 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
* This implementation returns a FileChannel for the given URI-identified
|
||||
* resource, provided that it refers to a file in the file system.
|
||||
* @since 5.0
|
||||
* @see #getFile(URI)
|
||||
* @see #getFile()
|
||||
*/
|
||||
@Override
|
||||
public ReadableByteChannel readableChannel() throws IOException {
|
||||
if (isFile()) {
|
||||
try {
|
||||
// Try file system channel
|
||||
return FileChannel.open(getFile().toPath(), StandardOpenOption.READ);
|
||||
}
|
||||
else {
|
||||
catch (FileNotFoundException | NoSuchFileException ex) {
|
||||
// Fall back to InputStream adaptation in superclass
|
||||
return super.readableChannel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
try {
|
||||
URL url = getURL();
|
||||
if (ResourceUtils.isFileURL(url)) {
|
||||
// Proceed with file system resolution
|
||||
return getFile().exists();
|
||||
}
|
||||
else {
|
||||
// Try a URL connection content-length header
|
||||
URLConnection con = url.openConnection();
|
||||
customizeConnection(con);
|
||||
HttpURLConnection httpCon =
|
||||
(con instanceof HttpURLConnection ? (HttpURLConnection) con : null);
|
||||
if (httpCon != null) {
|
||||
int code = httpCon.getResponseCode();
|
||||
if (code == HttpURLConnection.HTTP_OK) {
|
||||
return true;
|
||||
}
|
||||
else if (code == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (con.getContentLength() >= 0) {
|
||||
return true;
|
||||
}
|
||||
if (httpCon != null) {
|
||||
// no HTTP OK status, and no content-length header: give up
|
||||
httpCon.disconnect();
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// Fall back to stream existence: can we open the stream?
|
||||
InputStream is = getInputStream();
|
||||
is.close();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadable() {
|
||||
try {
|
||||
URL url = getURL();
|
||||
if (ResourceUtils.isFileURL(url)) {
|
||||
// Proceed with file system resolution
|
||||
File file = getFile();
|
||||
return (file.canRead() && !file.isDirectory());
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
URL url = getURL();
|
||||
@@ -231,7 +233,6 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
|
||||
return con.getLastModified();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Customize the given {@link URLConnection}, obtained in the course of an
|
||||
* {@link #exists()}, {@link #contentLength()} or {@link #lastModified()} call.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.core.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -26,6 +27,7 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
@@ -115,12 +117,17 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation opens a FileInputStream for the underlying file.
|
||||
* This implementation opens a NIO file stream for the underlying file.
|
||||
* @see java.io.FileInputStream
|
||||
*/
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return Files.newInputStream(this.file.toPath());
|
||||
try {
|
||||
return Files.newInputStream(this.file.toPath());
|
||||
}
|
||||
catch (NoSuchFileException ex) {
|
||||
throw new FileNotFoundException(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +190,12 @@ public class FileSystemResource extends AbstractResource implements WritableReso
|
||||
*/
|
||||
@Override
|
||||
public ReadableByteChannel readableChannel() throws IOException {
|
||||
return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
|
||||
try {
|
||||
return FileChannel.open(this.file.toPath(), StandardOpenOption.READ);
|
||||
}
|
||||
catch (NoSuchFileException ex) {
|
||||
throw new FileNotFoundException(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,11 +26,13 @@ import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
|
||||
/**
|
||||
* Subclass of {@link UrlResource} which assumes file resolution, to the degree
|
||||
* of implementing the {@link WritableResource} interface for it.
|
||||
* of implementing the {@link WritableResource} interface for it. This resource
|
||||
* variant also caches resolved {@link File} handles from {@link #getFile()}.
|
||||
*
|
||||
* <p>This is the class resolved by {@link DefaultResourceLoader} for a "file:..."
|
||||
* URL location, allowing a downcast to {@link WritableResource} for it.
|
||||
@@ -44,6 +46,10 @@ import org.springframework.util.ResourceUtils;
|
||||
*/
|
||||
public class FileUrlResource extends UrlResource implements WritableResource {
|
||||
|
||||
@Nullable
|
||||
private volatile File file;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code FileUrlResource} based on the given URL object.
|
||||
* <p>Note that this does not enforce "file" as URL protocol. If a protocol
|
||||
@@ -71,11 +77,14 @@ public class FileUrlResource extends UrlResource implements WritableResource {
|
||||
|
||||
|
||||
@Override
|
||||
public Resource createRelative(String relativePath) throws MalformedURLException {
|
||||
if (relativePath.startsWith("/")) {
|
||||
relativePath = relativePath.substring(1);
|
||||
public File getFile() throws IOException {
|
||||
File file = this.file;
|
||||
if (file != null) {
|
||||
return file;
|
||||
}
|
||||
return new FileUrlResource(new URL(getURL(), relativePath));
|
||||
file = super.getFile();
|
||||
this.file = file;
|
||||
return file;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,4 +115,12 @@ public class FileUrlResource extends UrlResource implements WritableResource {
|
||||
return FileChannel.open(getFile().toPath(), StandardOpenOption.WRITE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Resource createRelative(String relativePath) throws MalformedURLException {
|
||||
if (relativePath.startsWith("/")) {
|
||||
relativePath = relativePath.substring(1);
|
||||
}
|
||||
return new FileUrlResource(new URL(getURL(), relativePath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import java.net.URL;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.OpenOption;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -202,7 +203,12 @@ public class PathResource extends AbstractResource implements WritableResource {
|
||||
*/
|
||||
@Override
|
||||
public ReadableByteChannel readableChannel() throws IOException {
|
||||
return Files.newByteChannel(this.path, StandardOpenOption.READ);
|
||||
try {
|
||||
return Files.newByteChannel(this.path, StandardOpenOption.READ);
|
||||
}
|
||||
catch (NoSuchFileException ex) {
|
||||
throw new FileNotFoundException(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,7 +221,7 @@ public class PathResource extends AbstractResource implements WritableResource {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns the underlying File's length.
|
||||
* This implementation returns the underlying file's length.
|
||||
*/
|
||||
@Override
|
||||
public long contentLength() throws IOException {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -190,7 +190,8 @@ public interface DataBuffer {
|
||||
|
||||
/**
|
||||
* Write one or more {@code DataBuffer}s to this buffer, starting at the current
|
||||
* writing position.
|
||||
* writing position. It is the responsibility of the caller to
|
||||
* {@linkplain DataBufferUtils#release(DataBuffer) release} the given data buffers.
|
||||
* @param buffers the byte buffers to write into this buffer
|
||||
* @return this buffer
|
||||
*/
|
||||
|
||||
+14
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A factory for {@link DataBuffer}s, allowing for allocation and wrapping of
|
||||
@@ -61,4 +62,16 @@ public interface DataBufferFactory {
|
||||
*/
|
||||
DataBuffer wrap(byte[] bytes);
|
||||
|
||||
/**
|
||||
* Return a new {@code DataBuffer} composed of the {@code dataBuffers} elements joined together.
|
||||
* Depending on the implementation, the returned buffer may be a single buffer containing all
|
||||
* data of the provided buffers, or it may be a true composite that contains references to the
|
||||
* buffers.
|
||||
* <p>Note that the given data buffers do <strong>not</strong> have to be released, as they are
|
||||
* released as part of the returned composite.
|
||||
* @param dataBuffers the data buffers to be composed
|
||||
* @return a buffer that is composed from the {@code dataBuffers} argument
|
||||
* @since 5.0.3
|
||||
*/
|
||||
DataBuffer join(List<? extends DataBuffer> dataBuffers);
|
||||
}
|
||||
|
||||
+196
-70
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -28,9 +28,10 @@ import java.nio.channels.CompletionHandler;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
@@ -38,6 +39,7 @@ import org.reactivestreams.Subscription;
|
||||
import reactor.core.publisher.BaseSubscriber;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.FluxSink;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.publisher.SynchronousSink;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -55,74 +57,170 @@ public abstract class DataBufferUtils {
|
||||
|
||||
private static final Consumer<DataBuffer> RELEASE_CONSUMER = DataBufferUtils::release;
|
||||
|
||||
private static final BinaryOperator<DataBuffer> WRITE_AGGREGATOR =
|
||||
(dataBuffer1, dataBuffer2) -> {
|
||||
DataBuffer result = dataBuffer1.write(dataBuffer2);
|
||||
release(dataBuffer2);
|
||||
return result;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Reading
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Read the given {@code InputStream} into a {@code Flux} of
|
||||
* Read the given {@code InputStream} into a <strong>read-once</strong> {@code Flux} of
|
||||
* {@code DataBuffer}s. Closes the input stream when the flux is terminated.
|
||||
* <p>The resulting {@code Flux} can only be subscribed to once. See
|
||||
* {@link #readInputStream(Callable, DataBufferFactory, int)} for a variant that supports
|
||||
* multiple subscriptions.
|
||||
* @param inputStream the input stream to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
* @deprecated as of Spring 5.0.3, in favor of
|
||||
* {@link #readInputStream(Callable, DataBufferFactory, int)}, to be removed in Spring 5.1
|
||||
*/
|
||||
@Deprecated
|
||||
public static Flux<DataBuffer> read(InputStream inputStream,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
Assert.notNull(inputStream, "InputStream must not be null");
|
||||
|
||||
ReadableByteChannel channel = Channels.newChannel(inputStream);
|
||||
return read(channel, dataBufferFactory, bufferSize);
|
||||
return readInputStream(() -> inputStream, dataBufferFactory, bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given {@code ReadableByteChannel} into a {@code Flux} of
|
||||
* {@code DataBuffer}s. Closes the channel when the flux is terminated.
|
||||
* @param channel the channel to read from
|
||||
* Obtain a {@link InputStream} from the given supplier, and read it into a {@code Flux} of
|
||||
* {@code DataBuffer}s. Closes the input stream when the flux is terminated.
|
||||
* @param inputStreamSupplier the supplier for the input stream to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
*/
|
||||
public static Flux<DataBuffer> readInputStream(Callable<InputStream> inputStreamSupplier,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
Assert.notNull(inputStreamSupplier, "'inputStreamSupplier' must not be null");
|
||||
|
||||
return readByteChannel(() -> Channels.newChannel(inputStreamSupplier.call()),
|
||||
dataBufferFactory, bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given {@code ReadableByteChannel} into a <strong>read-once</strong> {@code Flux} of
|
||||
* {@code DataBuffer}s. Closes the channel when the flux is terminated.
|
||||
* <p>The resulting {@code Flux} can only be subscribed to once. See
|
||||
* {@link #readByteChannel(Callable, DataBufferFactory, int)} for a variant that supports
|
||||
* multiple subscriptions.
|
||||
* @param channel the channel to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
* @deprecated as of Spring 5.0.3, in favor of
|
||||
* {@link #readByteChannel(Callable, DataBufferFactory, int)}, to be removed in Spring 5.1
|
||||
*/
|
||||
@Deprecated
|
||||
public static Flux<DataBuffer> read(ReadableByteChannel channel,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
Assert.notNull(channel, "ReadableByteChannel must not be null");
|
||||
Assert.notNull(dataBufferFactory, "DataBufferFactory must not be null");
|
||||
Assert.isTrue(bufferSize > 0, "'bufferSize' must be > 0");
|
||||
|
||||
return Flux.generate(() -> channel,
|
||||
new ReadableByteChannelGenerator(dataBufferFactory, bufferSize),
|
||||
DataBufferUtils::closeChannel);
|
||||
return readByteChannel(() -> channel, dataBufferFactory, bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given {@code AsynchronousFileChannel} into a {@code Flux} of
|
||||
* {@code DataBuffer}s. Closes the channel when the flux is terminated.
|
||||
* @param channel the channel to read from
|
||||
* Obtain a {@link ReadableByteChannel} from the given supplier, and read it into a
|
||||
* {@code Flux} of {@code DataBuffer}s. Closes the channel when the flux is terminated.
|
||||
* @param channelSupplier the supplier for the channel to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
*/
|
||||
public static Flux<DataBuffer> read(AsynchronousFileChannel channel,
|
||||
public static Flux<DataBuffer> readByteChannel(Callable<ReadableByteChannel> channelSupplier,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
return read(channel, 0, dataBufferFactory, bufferSize);
|
||||
|
||||
Assert.notNull(channelSupplier, "'channelSupplier' must not be null");
|
||||
Assert.notNull(dataBufferFactory, "'dataBufferFactory' must not be null");
|
||||
Assert.isTrue(bufferSize > 0, "'bufferSize' must be > 0");
|
||||
|
||||
return Flux.using(channelSupplier,
|
||||
channel -> {
|
||||
ReadableByteChannelGenerator generator =
|
||||
new ReadableByteChannelGenerator(channel, dataBufferFactory,
|
||||
bufferSize);
|
||||
return Flux.generate(generator);
|
||||
},
|
||||
DataBufferUtils::closeChannel
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given {@code AsynchronousFileChannel} into a {@code Flux} of
|
||||
* {@code DataBuffer}s, starting at the given position. Closes the channel when the flux is
|
||||
* Read the given {@code AsynchronousFileChannel} into a <strong>read-once</strong> {@code Flux}
|
||||
* of {@code DataBuffer}s. Closes the channel when the flux is terminated.
|
||||
* <p>The resulting {@code Flux} can only be subscribed to once. See
|
||||
* {@link #readAsynchronousFileChannel(Callable, DataBufferFactory, int)} for a variant that
|
||||
* supports multiple subscriptions.
|
||||
* @param channel the channel to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
* @deprecated as of Spring 5.0.3, in favor of
|
||||
* {@link #readAsynchronousFileChannel(Callable, DataBufferFactory, int)}, to be removed in
|
||||
* Spring 5.1
|
||||
*/
|
||||
@Deprecated
|
||||
public static Flux<DataBuffer> read(AsynchronousFileChannel channel,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
return readAsynchronousFileChannel(() -> channel, dataBufferFactory, bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given {@code AsynchronousFileChannel} into a <strong>read-once</strong> {@code Flux}
|
||||
* of {@code DataBuffer}s, starting at the given position. Closes the channel when the flux is
|
||||
* terminated.
|
||||
* <p>The resulting {@code Flux} can only be subscribed to once. See
|
||||
* {@link #readAsynchronousFileChannel(Callable, long, DataBufferFactory, int)} for a variant
|
||||
* that supports multiple subscriptions.
|
||||
* @param channel the channel to read from
|
||||
* @param position the position to start reading from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
* @deprecated as of Spring 5.0.3, in favor of
|
||||
* {@link #readAsynchronousFileChannel(Callable, long, DataBufferFactory, int)}, to be removed
|
||||
* in Spring 5.1
|
||||
*/
|
||||
@Deprecated
|
||||
public static Flux<DataBuffer> read(AsynchronousFileChannel channel,
|
||||
long position, DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
return readAsynchronousFileChannel(() -> channel, position, dataBufferFactory, bufferSize);
|
||||
}
|
||||
|
||||
Assert.notNull(channel, "'channel' must not be null");
|
||||
/**
|
||||
* Obtain a {@code AsynchronousFileChannel} from the given supplier, and read it into a
|
||||
* {@code Flux} of {@code DataBuffer}s. Closes the channel when the flux is terminated.
|
||||
* @param channelSupplier the supplier for the channel to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
*/
|
||||
public static Flux<DataBuffer> readAsynchronousFileChannel(
|
||||
Callable<AsynchronousFileChannel> channelSupplier,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
return readAsynchronousFileChannel(channelSupplier, 0, dataBufferFactory, bufferSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a {@code AsynchronousFileChannel} from the given supplier, and read it into a
|
||||
* {@code Flux} of {@code DataBuffer}s, starting at the given position. Closes the channel when
|
||||
* the flux is terminated.
|
||||
* @param channelSupplier the supplier for the channel to read from
|
||||
* @param position the position to start reading from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
* @return a flux of data buffers read from the given channel
|
||||
*/
|
||||
public static Flux<DataBuffer> readAsynchronousFileChannel(
|
||||
Callable<AsynchronousFileChannel> channelSupplier,
|
||||
long position, DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
Assert.notNull(channelSupplier, "'channelSupplier' must not be null");
|
||||
Assert.notNull(dataBufferFactory, "'dataBufferFactory' must not be null");
|
||||
Assert.isTrue(position >= 0, "'position' must be >= 0");
|
||||
Assert.isTrue(bufferSize > 0, "'bufferSize' must be > 0");
|
||||
@@ -130,22 +228,23 @@ public abstract class DataBufferUtils {
|
||||
DataBuffer dataBuffer = dataBufferFactory.allocateBuffer(bufferSize);
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, bufferSize);
|
||||
|
||||
return Flux.create(sink -> {
|
||||
sink.onDispose(() -> closeChannel(channel));
|
||||
CompletionHandler<Integer, DataBuffer> completionHandler =
|
||||
new AsynchronousFileChannelReadCompletionHandler(channel, sink, position,
|
||||
dataBufferFactory, bufferSize);
|
||||
channel.read(byteBuffer, position, dataBuffer, completionHandler);
|
||||
});
|
||||
return Flux.using(channelSupplier,
|
||||
channel -> Flux.create(sink -> {
|
||||
CompletionHandler<Integer, DataBuffer> completionHandler =
|
||||
new AsynchronousFileChannelReadCompletionHandler(channel,
|
||||
sink, position, dataBufferFactory, bufferSize);
|
||||
channel.read(byteBuffer, position, dataBuffer, completionHandler);
|
||||
}),
|
||||
DataBufferUtils::closeChannel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the given {@code Resource} into a {@code Flux} of {@code DataBuffer}s.
|
||||
* <p>If the resource is a file, it is read into an
|
||||
* {@code AsynchronousFileChannel} and turned to {@code Flux} via
|
||||
* {@link #read(AsynchronousFileChannel, DataBufferFactory, int)} or else
|
||||
* fall back on {@link #read(InputStream, DataBufferFactory, int)} closes
|
||||
* the channel when the flux is terminated.
|
||||
* {@link #readAsynchronousFileChannel(Callable, DataBufferFactory, int)} or else
|
||||
* fall back to {@link #readByteChannel(Callable, DataBufferFactory, int)}.
|
||||
* Closes the channel when the flux is terminated.
|
||||
* @param resource the resource to read from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
* @param bufferSize the maximum size of the data buffers
|
||||
@@ -162,9 +261,9 @@ public abstract class DataBufferUtils {
|
||||
* starting at the given position.
|
||||
* <p>If the resource is a file, it is read into an
|
||||
* {@code AsynchronousFileChannel} and turned to {@code Flux} via
|
||||
* {@link #read(AsynchronousFileChannel, DataBufferFactory, int)} or else
|
||||
* fall back on {@link #read(InputStream, DataBufferFactory, int)}. Closes
|
||||
* the channel when the flux is terminated.
|
||||
* {@link #readAsynchronousFileChannel(Callable, DataBufferFactory, int)} or else
|
||||
* fall back on {@link #readByteChannel(Callable, DataBufferFactory, int)}.
|
||||
* Closes the channel when the flux is terminated.
|
||||
* @param resource the resource to read from
|
||||
* @param position the position to start reading from
|
||||
* @param dataBufferFactory the factory to create data buffers with
|
||||
@@ -177,26 +276,23 @@ public abstract class DataBufferUtils {
|
||||
try {
|
||||
if (resource.isFile()) {
|
||||
File file = resource.getFile();
|
||||
AsynchronousFileChannel channel =
|
||||
AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ);
|
||||
return DataBufferUtils.read(channel, position, dataBufferFactory, bufferSize);
|
||||
|
||||
return readAsynchronousFileChannel(
|
||||
() -> AsynchronousFileChannel.open(file.toPath(), StandardOpenOption.READ),
|
||||
position, dataBufferFactory, bufferSize);
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// fallback to resource.readableChannel(), below
|
||||
}
|
||||
|
||||
try {
|
||||
ReadableByteChannel channel = resource.readableChannel();
|
||||
Flux<DataBuffer> in = DataBufferUtils.read(channel, dataBufferFactory, bufferSize);
|
||||
return DataBufferUtils.skipUntilByteCount(in, position);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Flux.error(ex);
|
||||
}
|
||||
Flux<DataBuffer> result = readByteChannel(resource::readableChannel, dataBufferFactory, bufferSize);
|
||||
return position == 0 ? result : skipUntilByteCount(result, position);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Writing
|
||||
//---------------------------------------------------------------------
|
||||
@@ -295,7 +391,7 @@ public abstract class DataBufferUtils {
|
||||
|
||||
private static void closeChannel(@Nullable Channel channel) {
|
||||
try {
|
||||
if (channel != null) {
|
||||
if (channel != null && channel.isOpen()) {
|
||||
channel.close();
|
||||
}
|
||||
}
|
||||
@@ -303,6 +399,10 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Various
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Relay buffers from the given {@link Publisher} until the total
|
||||
* {@linkplain DataBuffer#readableByteCount() byte count} reaches
|
||||
@@ -335,10 +435,6 @@ public abstract class DataBufferUtils {
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Various
|
||||
//---------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Skip buffers from the given {@link Publisher} until the total
|
||||
* {@linkplain DataBuffer#readableByteCount() byte count} reaches
|
||||
@@ -403,55 +499,82 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a consumer that calls {@link #release(DataBuffer)} on all
|
||||
* Return a consumer that calls {@link #release(DataBuffer)} on all
|
||||
* passed data buffers.
|
||||
*/
|
||||
public static Consumer<DataBuffer> releaseConsumer() {
|
||||
return RELEASE_CONSUMER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@code DataBuffer} composed of the {@code dataBuffers} elements joined together.
|
||||
* Depending on the {@link DataBuffer} implementation, the returned buffer may be a single
|
||||
* buffer containing all data of the provided buffers, or it may be a true composite that
|
||||
* contains references to the buffers.
|
||||
* @param dataBuffers the data buffers that are to be composed
|
||||
* @return a buffer that is composed from the {@code dataBuffers} argument
|
||||
* @since 5.0.3
|
||||
*/
|
||||
public static Mono<DataBuffer> join(Publisher<? extends DataBuffer> dataBuffers) {
|
||||
Assert.notNull(dataBuffers, "'dataBuffers' must not be null");
|
||||
|
||||
return Flux.from(dataBuffers)
|
||||
.collectList()
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(list -> {
|
||||
DataBufferFactory bufferFactory = list.get(0).factory();
|
||||
return bufferFactory.join(list);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private static class ReadableByteChannelGenerator
|
||||
implements BiFunction<ReadableByteChannel, SynchronousSink<DataBuffer>, ReadableByteChannel> {
|
||||
implements Consumer<SynchronousSink<DataBuffer>> {
|
||||
|
||||
private final ReadableByteChannel channel;
|
||||
|
||||
private final DataBufferFactory dataBufferFactory;
|
||||
|
||||
private final int bufferSize;
|
||||
|
||||
public ReadableByteChannelGenerator(DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
public ReadableByteChannelGenerator(ReadableByteChannel channel,
|
||||
DataBufferFactory dataBufferFactory, int bufferSize) {
|
||||
|
||||
this.channel = channel;
|
||||
this.dataBufferFactory = dataBufferFactory;
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReadableByteChannel apply(ReadableByteChannel channel,
|
||||
SynchronousSink<DataBuffer> sub) {
|
||||
public void accept(SynchronousSink<DataBuffer> sink) {
|
||||
boolean release = true;
|
||||
DataBuffer dataBuffer = this.dataBufferFactory.allocateBuffer(this.bufferSize);
|
||||
try {
|
||||
int read;
|
||||
ByteBuffer byteBuffer = dataBuffer.asByteBuffer(0, dataBuffer.capacity());
|
||||
if ((read = channel.read(byteBuffer)) >= 0) {
|
||||
if ((read = this.channel.read(byteBuffer)) >= 0) {
|
||||
dataBuffer.writePosition(read);
|
||||
release = false;
|
||||
sub.next(dataBuffer);
|
||||
sink.next(dataBuffer);
|
||||
}
|
||||
else {
|
||||
sub.complete();
|
||||
sink.complete();
|
||||
}
|
||||
}
|
||||
catch (IOException ex) {
|
||||
sub.error(ex);
|
||||
sink.error(ex);
|
||||
}
|
||||
finally {
|
||||
if (release) {
|
||||
release(dataBuffer);
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class AsynchronousFileChannelReadCompletionHandler
|
||||
implements CompletionHandler<Integer, DataBuffer> {
|
||||
|
||||
@@ -463,7 +586,10 @@ public abstract class DataBufferUtils {
|
||||
|
||||
private final int bufferSize;
|
||||
|
||||
private AtomicLong position;
|
||||
private final AtomicLong position;
|
||||
|
||||
private final AtomicBoolean disposed = new AtomicBoolean();
|
||||
|
||||
|
||||
private AsynchronousFileChannelReadCompletionHandler(
|
||||
AsynchronousFileChannel channel, FluxSink<DataBuffer> sink,
|
||||
@@ -482,7 +608,7 @@ public abstract class DataBufferUtils {
|
||||
dataBuffer.writePosition(read);
|
||||
this.sink.next(dataBuffer);
|
||||
|
||||
if (!this.sink.isCancelled()) {
|
||||
if (!this.disposed.get()) {
|
||||
DataBuffer newDataBuffer =
|
||||
this.dataBufferFactory.allocateBuffer(this.bufferSize);
|
||||
ByteBuffer newByteBuffer = newDataBuffer.asByteBuffer(0, this.bufferSize);
|
||||
@@ -491,7 +617,6 @@ public abstract class DataBufferUtils {
|
||||
}
|
||||
else {
|
||||
release(dataBuffer);
|
||||
closeChannel(this.channel);
|
||||
this.sink.complete();
|
||||
}
|
||||
}
|
||||
@@ -499,11 +624,12 @@ public abstract class DataBufferUtils {
|
||||
@Override
|
||||
public void failed(Throwable exc, DataBuffer dataBuffer) {
|
||||
release(dataBuffer);
|
||||
closeChannel(this.channel);
|
||||
this.sink.error(exc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class AsynchronousFileChannelWriteCompletionHandler
|
||||
extends BaseSubscriber<DataBuffer>
|
||||
implements CompletionHandler<Integer, ByteBuffer> {
|
||||
|
||||
+22
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -102,6 +103,26 @@ public class DefaultDataBufferFactory implements DataBufferFactory {
|
||||
return DefaultDataBuffer.fromFilledByteBuffer(this, wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation creates a single {@link DefaultDataBuffer} to contain the data
|
||||
* in {@code dataBuffers}.
|
||||
*/
|
||||
@Override
|
||||
public DataBuffer join(List<? extends DataBuffer> dataBuffers) {
|
||||
Assert.notEmpty(dataBuffers, "'dataBuffers' must not be empty");
|
||||
|
||||
int capacity = dataBuffers.stream()
|
||||
.mapToInt(DataBuffer::readableByteCount)
|
||||
.sum();
|
||||
DefaultDataBuffer dataBuffer = allocateBuffer(capacity);
|
||||
DataBuffer result = dataBuffers.stream()
|
||||
.map(o -> (DataBuffer) o)
|
||||
.reduce(dataBuffer, DataBuffer::write);
|
||||
dataBuffers.forEach(DataBufferUtils::release);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultDataBufferFactory (preferDirect=" + this.preferDirect + ")";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,11 +25,8 @@ import java.util.function.IntPredicate;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* Implementation of the {@code DataBuffer} interface that wraps a Netty
|
||||
@@ -42,7 +39,7 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
|
||||
private final NettyDataBufferFactory dataBufferFactory;
|
||||
|
||||
private ByteBuf byteBuf;
|
||||
private final ByteBuf byteBuf;
|
||||
|
||||
|
||||
/**
|
||||
@@ -174,8 +171,10 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
|
||||
@Override
|
||||
public NettyDataBuffer write(DataBuffer... buffers) {
|
||||
if (!ObjectUtils.isEmpty(buffers)) {
|
||||
if (buffers[0] instanceof NettyDataBuffer) {
|
||||
Assert.notNull(buffers, "'buffers' must not be null");
|
||||
|
||||
if (buffers.length > 0) {
|
||||
if (hasNettyDataBuffers(buffers)) {
|
||||
ByteBuf[] nativeBuffers = Arrays.stream(buffers)
|
||||
.map(b -> ((NettyDataBuffer) b).getNativeBuffer())
|
||||
.toArray(ByteBuf[]::new);
|
||||
@@ -191,12 +190,23 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static boolean hasNettyDataBuffers(DataBuffer[] dataBuffers) {
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
if (!(dataBuffer instanceof NettyDataBuffer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NettyDataBuffer write(ByteBuffer... buffers) {
|
||||
Assert.notNull(buffers, "'buffers' must not be null");
|
||||
ByteBuf[] wrappedBuffers = Arrays.stream(buffers).map(Unpooled::wrappedBuffer)
|
||||
.toArray(ByteBuf[]::new);
|
||||
return write(wrappedBuffers);
|
||||
|
||||
for (ByteBuffer buffer : buffers) {
|
||||
this.byteBuf.writeBytes(buffer);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,17 +218,8 @@ public class NettyDataBuffer implements PooledDataBuffer {
|
||||
public NettyDataBuffer write(ByteBuf... byteBufs) {
|
||||
Assert.notNull(byteBufs, "'byteBufs' must not be null");
|
||||
|
||||
if (this.byteBuf instanceof CompositeByteBuf) {
|
||||
CompositeByteBuf composite = (CompositeByteBuf) this.byteBuf;
|
||||
composite.addComponents(true, byteBufs);
|
||||
}
|
||||
else {
|
||||
ByteBuf oldByteBuf = this.byteBuf;
|
||||
CompositeByteBuf composite = oldByteBuf.alloc().compositeBuffer(byteBufs.length + 1);
|
||||
composite.addComponent(true, oldByteBuf);
|
||||
composite.addComponents(true, byteBufs);
|
||||
|
||||
this.byteBuf = composite;
|
||||
for (ByteBuf byteBuf : byteBufs) {
|
||||
this.byteBuf.writeBytes(byteBuf);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
+19
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,9 +17,11 @@
|
||||
package org.springframework.core.io.buffer;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
@@ -80,6 +82,22 @@ public class NettyDataBufferFactory implements DataBufferFactory {
|
||||
return new NettyDataBuffer(byteBuf, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* <p>This implementation uses Netty's {@link CompositeByteBuf}.
|
||||
*/
|
||||
@Override
|
||||
public DataBuffer join(List<? extends DataBuffer> dataBuffers) {
|
||||
Assert.notNull(dataBuffers, "'dataBuffers' must not be null");
|
||||
CompositeByteBuf composite = this.byteBufAllocator.compositeBuffer(dataBuffers.size());
|
||||
for (DataBuffer dataBuffer : dataBuffers) {
|
||||
Assert.isInstanceOf(NettyDataBuffer.class, dataBuffer);
|
||||
NettyDataBuffer nettyDataBuffer = (NettyDataBuffer) dataBuffer;
|
||||
composite.addComponent(true, nettyDataBuffer.getNativeBuffer());
|
||||
}
|
||||
return new NettyDataBuffer(composite, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the given Netty {@link ByteBuf} in a {@code NettyDataBuffer}.
|
||||
* @param byteBuf the Netty byte buffer to wrap
|
||||
|
||||
+1
-1
@@ -427,7 +427,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
|
||||
int prefixIndex = filePath.indexOf(':');
|
||||
if (prefixIndex == 1) {
|
||||
// Possibly "c:" drive prefix on Windows, to be upper-cased for proper duplicate detection
|
||||
filePath = filePath.substring(0, 1).toUpperCase() + filePath.substring(1);
|
||||
filePath = StringUtils.capitalize(filePath);
|
||||
}
|
||||
UrlResource jarResource = new UrlResource(ResourceUtils.JAR_URL_PREFIX +
|
||||
ResourceUtils.FILE_URL_PREFIX + filePath + ResourceUtils.JAR_URL_SEPARATOR);
|
||||
|
||||
+11
-7
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.core.type.classreading;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.core.io.DefaultResourceLoader;
|
||||
@@ -73,10 +74,13 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
|
||||
|
||||
@Override
|
||||
public MetadataReader getMetadataReader(String className) throws IOException {
|
||||
String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
|
||||
ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
|
||||
Resource resource = this.resourceLoader.getResource(resourcePath);
|
||||
if (!resource.exists()) {
|
||||
try {
|
||||
String resourcePath = ResourceLoader.CLASSPATH_URL_PREFIX +
|
||||
ClassUtils.convertClassNameToResourcePath(className) + ClassUtils.CLASS_FILE_SUFFIX;
|
||||
Resource resource = this.resourceLoader.getResource(resourcePath);
|
||||
return getMetadataReader(resource);
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
// Maybe an inner class name using the dot name syntax? Need to use the dollar syntax here...
|
||||
// ClassUtils.forName has an equivalent check for resolution into Class references later on.
|
||||
int lastDotIndex = className.lastIndexOf('.');
|
||||
@@ -87,11 +91,11 @@ public class SimpleMetadataReaderFactory implements MetadataReaderFactory {
|
||||
ClassUtils.convertClassNameToResourcePath(innerClassName) + ClassUtils.CLASS_FILE_SUFFIX;
|
||||
Resource innerClassResource = this.resourceLoader.getResource(innerClassResourcePath);
|
||||
if (innerClassResource.exists()) {
|
||||
resource = innerClassResource;
|
||||
return getMetadataReader(innerClassResource);
|
||||
}
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
return getMetadataReader(resource);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,8 @@
|
||||
package org.springframework.util;
|
||||
|
||||
import java.beans.Introspector;
|
||||
import java.io.Externalizable;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -74,6 +76,13 @@ public abstract class ClassUtils {
|
||||
public static final String CLASS_FILE_SUFFIX = ".class";
|
||||
|
||||
|
||||
/**
|
||||
* Common Java language interfaces which are supposed to be ignored
|
||||
* when searching for 'primary' user-level interfaces.
|
||||
*/
|
||||
private static final Set<Class<?>> javaLanguageInterfaces = new HashSet<>(
|
||||
Arrays.asList(Serializable.class, Externalizable.class, Cloneable.class, Comparable.class));
|
||||
|
||||
/**
|
||||
* Map with primitive wrapper type as key and corresponding primitive
|
||||
* type as value, for example: Integer.class -> int.class.
|
||||
@@ -289,11 +298,10 @@ public abstract class ClassUtils {
|
||||
return forName(className, classLoader);
|
||||
}
|
||||
catch (ClassNotFoundException ex) {
|
||||
throw new IllegalArgumentException("Cannot find class [" + className + "]", ex);
|
||||
throw new IllegalArgumentException("Could not find class [" + className + "]", ex);
|
||||
}
|
||||
catch (LinkageError ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Error loading class [" + className + "]: problem with class file or dependent class.", ex);
|
||||
catch (LinkageError err) {
|
||||
throw new IllegalArgumentException("Unresolvable class definition for class [" + className + "]", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1218,6 +1226,19 @@ public abstract class ClassUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given interface is a common Java language interface:
|
||||
* {@link Serializable}, {@link Externalizable}, {@link Cloneable}, {@link Comparable}
|
||||
* - all of which can be ignored when looking for 'primary' user-level interfaces.
|
||||
* Common characteristics: no service-level operations, no bean property methods,
|
||||
* no default methods.
|
||||
* @param ifc the interface to check
|
||||
* @since 5.0.3
|
||||
*/
|
||||
public static boolean isJavaLanguageInterface(Class<?> ifc) {
|
||||
return javaLanguageInterfaces.contains(ifc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given object is a CGLIB proxy.
|
||||
* @param object the object to check
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,6 +29,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
@@ -312,6 +313,48 @@ public abstract class CollectionUtils {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last element of the given Set, using {@link SortedSet#last()}
|
||||
* or otherwise iterating over all elements (assuming a linked set).
|
||||
* @param set the Set to check (may be {@code null} or empty)
|
||||
* @return the last element, or {@code null} if none
|
||||
* @since 5.0.3
|
||||
* @see SortedSet
|
||||
* @see LinkedHashMap#keySet()
|
||||
* @see java.util.LinkedHashSet
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T lastElement(@Nullable Set<T> set) {
|
||||
if (isEmpty(set)) {
|
||||
return null;
|
||||
}
|
||||
if (set instanceof SortedSet) {
|
||||
return ((SortedSet<T>) set).last();
|
||||
}
|
||||
|
||||
// Full iteration necessary...
|
||||
Iterator<T> it = set.iterator();
|
||||
T last = null;
|
||||
while (it.hasNext()) {
|
||||
last = it.next();
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the last element of the given List, accessing the highest index.
|
||||
* @param list the List to check (may be {@code null} or empty)
|
||||
* @return the last element, or {@code null} if none
|
||||
* @since 5.0.3
|
||||
*/
|
||||
@Nullable
|
||||
public static <T> T lastElement(@Nullable List<T> list) {
|
||||
if (isEmpty(list)) {
|
||||
return null;
|
||||
}
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marshal the elements from the given enumeration into an array of the given type.
|
||||
* Enumeration elements must be assignable to the type of the given array. The array
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -251,19 +251,20 @@ public class SocketUtils {
|
||||
*/
|
||||
int findAvailablePort(int minPort, int maxPort) {
|
||||
Assert.isTrue(minPort > 0, "'minPort' must be greater than 0");
|
||||
Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equals 'minPort'");
|
||||
Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'");
|
||||
Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to " + PORT_RANGE_MAX);
|
||||
|
||||
int portRange = maxPort - minPort;
|
||||
int candidatePort;
|
||||
int searchCounter = 0;
|
||||
do {
|
||||
if (++searchCounter > portRange) {
|
||||
if (searchCounter > portRange) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Could not find an available %s port in the range [%d, %d] after %d attempts",
|
||||
name(), minPort, maxPort, searchCounter));
|
||||
}
|
||||
candidatePort = findRandomPort(minPort, maxPort);
|
||||
searchCounter++;
|
||||
}
|
||||
while (!isPortAvailable(candidatePort));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -741,7 +741,7 @@ public class BridgeMethodResolverTests {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings({ "unused", "unchecked" })
|
||||
@SuppressWarnings({"unused", "unchecked"})
|
||||
public static abstract class GenericEventBroadcasterImpl<T extends Event>
|
||||
extends GenericBroadcasterImpl implements EventBroadcaster {
|
||||
|
||||
@@ -806,10 +806,10 @@ public class BridgeMethodResolverTests {
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static class MessageBroadcasterImpl extends
|
||||
GenericEventBroadcasterImpl<MessageEvent>
|
||||
implements MessageBroadcaster {
|
||||
@SuppressWarnings({"serial", "unchecked"})
|
||||
public static class MessageBroadcasterImpl extends GenericEventBroadcasterImpl<MessageEvent>
|
||||
implements Serializable, // implement an unrelated interface first (SPR-16288)
|
||||
MessageBroadcaster {
|
||||
|
||||
public MessageBroadcasterImpl() {
|
||||
super(NewMessageEvent.class);
|
||||
@@ -864,7 +864,7 @@ public class BridgeMethodResolverTests {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static class SettableRepositoryRegistry<R extends SimpleGenericRepository<?>>
|
||||
implements RepositoryRegistry {
|
||||
implements RepositoryRegistry {
|
||||
|
||||
protected void injectInto(R rep) {
|
||||
}
|
||||
@@ -902,7 +902,7 @@ public class BridgeMethodResolverTests {
|
||||
|
||||
|
||||
public static class GenericHibernateRepository<T, ID extends Serializable>
|
||||
implements ConvenientGenericRepository<T, ID> {
|
||||
implements ConvenientGenericRepository<T, ID> {
|
||||
|
||||
/**
|
||||
* @param c Mandatory. The domain class this repository is responsible for.
|
||||
@@ -964,8 +964,8 @@ public class BridgeMethodResolverTests {
|
||||
}
|
||||
|
||||
|
||||
public static class HibernateRepositoryRegistry extends
|
||||
SettableRepositoryRegistry<GenericHibernateRepository<?, ?>> {
|
||||
public static class HibernateRepositoryRegistry
|
||||
extends SettableRepositoryRegistry<GenericHibernateRepository<?, ?>> {
|
||||
|
||||
@Override
|
||||
public void injectInto(GenericHibernateRepository<?, ?> rep) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -64,6 +64,7 @@ import static org.mockito.BDDMockito.*;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Juergen Hoeller
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
@@ -176,11 +177,13 @@ public class ResolvableTypeTests {
|
||||
ResolvableType type = ResolvableType.forField(field);
|
||||
assertThat(type.getType(), equalTo(field.getGenericType()));
|
||||
assertThat(type.resolve(), equalTo((Class) List.class));
|
||||
assertThat(type.getSource(), sameInstance(field));
|
||||
|
||||
Field field2 = Fields.class.getDeclaredField("otherPrivateField");
|
||||
ResolvableType type2 = ResolvableType.forField(field2);
|
||||
assertThat(type2.getType(), equalTo(field2.getGenericType()));
|
||||
assertThat(type2.resolve(), equalTo((Class) List.class));
|
||||
assertThat(type2.getSource(), sameInstance(field2));
|
||||
|
||||
assertEquals(type, type2);
|
||||
assertEquals(type.hashCode(), type2.hashCode());
|
||||
@@ -259,6 +262,19 @@ public class ResolvableTypeTests {
|
||||
ResolvableType.forMethodParameter(null);
|
||||
}
|
||||
|
||||
@Test // SPR-16210
|
||||
public void forMethodParameterWithSameSignatureAndGenerics() throws Exception {
|
||||
Method method = Methods.class.getMethod("list1");
|
||||
MethodParameter methodParameter = MethodParameter.forExecutable(method, -1);
|
||||
ResolvableType type = ResolvableType.forMethodParameter(methodParameter);
|
||||
assertThat(((MethodParameter)type.getSource()).getMethod(), equalTo(method));
|
||||
|
||||
method = Methods.class.getMethod("list2");
|
||||
methodParameter = MethodParameter.forExecutable(method, -1);
|
||||
type = ResolvableType.forMethodParameter(methodParameter);
|
||||
assertThat(((MethodParameter)type.getSource()).getMethod(), equalTo(method));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forMethodReturn() throws Exception {
|
||||
Method method = Methods.class.getMethod("charSequenceReturn");
|
||||
@@ -1437,6 +1453,10 @@ public class ResolvableTypeTests {
|
||||
T typedReturn();
|
||||
|
||||
Set<?> wildcardSet();
|
||||
|
||||
List<String> list1();
|
||||
|
||||
List<String> list2();
|
||||
}
|
||||
|
||||
|
||||
|
||||
+18
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.util.Collections;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -39,6 +40,7 @@ public class ByteArrayDecoderTests extends AbstractDataBufferAllocatingTestCase
|
||||
|
||||
private final ByteArrayDecoder decoder = new ByteArrayDecoder();
|
||||
|
||||
|
||||
@Test
|
||||
public void canDecode() {
|
||||
assertTrue(this.decoder.canDecode(ResolvableType.forClass(byte[].class),
|
||||
@@ -65,4 +67,19 @@ public class ByteArrayDecoderTests extends AbstractDataBufferAllocatingTestCase
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeToMono() {
|
||||
DataBuffer fooBuffer = stringBuffer("foo");
|
||||
DataBuffer barBuffer = stringBuffer("bar");
|
||||
Flux<DataBuffer> source = Flux.just(fooBuffer, barBuffer);
|
||||
Mono<byte[]> output = this.decoder.decodeToMono(source,
|
||||
ResolvableType.forClassWithGenerics(Publisher.class, byte[].class),
|
||||
null, Collections.emptyMap());
|
||||
|
||||
StepVerifier.create(output)
|
||||
.consumeNextWith(bytes -> assertArrayEquals("foobar".getBytes(), bytes))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -63,4 +64,19 @@ public class ByteBufferDecoderTests extends AbstractDataBufferAllocatingTestCase
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeToMono() {
|
||||
DataBuffer fooBuffer = stringBuffer("foo");
|
||||
DataBuffer barBuffer = stringBuffer("bar");
|
||||
Flux<DataBuffer> source = Flux.just(fooBuffer, barBuffer);
|
||||
Mono<ByteBuffer> output = this.decoder.decodeToMono(source,
|
||||
ResolvableType.forClassWithGenerics(Publisher.class, ByteBuffer.class),
|
||||
null, Collections.emptyMap());
|
||||
|
||||
StepVerifier.create(output)
|
||||
.expectNext(ByteBuffer.wrap("foobar".getBytes()))
|
||||
.expectComplete()
|
||||
.verify();
|
||||
}
|
||||
}
|
||||
|
||||
+20
-2
@@ -17,15 +17,19 @@
|
||||
package org.springframework.core.codec;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
@@ -53,11 +57,25 @@ public class DataBufferDecoderTests extends AbstractDataBufferAllocatingTestCase
|
||||
DataBuffer barBuffer = stringBuffer("bar");
|
||||
Flux<DataBuffer> source = Flux.just(fooBuffer, barBuffer);
|
||||
Flux<DataBuffer> output = this.decoder.decode(source,
|
||||
ResolvableType.forClassWithGenerics(Publisher.class, ByteBuffer.class),
|
||||
ResolvableType.forClassWithGenerics(Publisher.class, DataBuffer.class),
|
||||
null, Collections.emptyMap());
|
||||
|
||||
assertSame(source, output);
|
||||
|
||||
release(fooBuffer, barBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void decodeToMono() {
|
||||
DataBuffer fooBuffer = stringBuffer("foo");
|
||||
DataBuffer barBuffer = stringBuffer("bar");
|
||||
Flux<DataBuffer> source = Flux.just(fooBuffer, barBuffer);
|
||||
Mono<DataBuffer> output = this.decoder.decodeToMono(source,
|
||||
ResolvableType.forClassWithGenerics(Publisher.class, DataBuffer.class),
|
||||
null, Collections.emptyMap());
|
||||
|
||||
DataBuffer outputBuffer = output.block(Duration.ofSeconds(5));
|
||||
assertEquals("foobar", DataBufferTestUtils.dumpString(outputBuffer, StandardCharsets.UTF_8));
|
||||
|
||||
release(outputBuffer);
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,13 +18,13 @@ package org.springframework.core.io;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@@ -75,136 +75,136 @@ public class PathResourceTests {
|
||||
|
||||
|
||||
@Test
|
||||
public void nullPath() throws Exception {
|
||||
public void nullPath() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Path must not be null");
|
||||
new PathResource((Path) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullPathString() throws Exception {
|
||||
public void nullPathString() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("Path must not be null");
|
||||
new PathResource((String) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nullUri() throws Exception {
|
||||
public void nullUri() {
|
||||
thrown.expect(IllegalArgumentException.class);
|
||||
thrown.expectMessage("URI must not be null");
|
||||
new PathResource((URI) null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromPath() throws Exception {
|
||||
public void createFromPath() {
|
||||
Path path = Paths.get(TEST_FILE);
|
||||
PathResource resource = new PathResource(path);
|
||||
assertThat(resource.getPath(), equalTo(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromString() throws Exception {
|
||||
public void createFromString() {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getPath(), equalTo(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createFromUri() throws Exception {
|
||||
public void createFromUri() {
|
||||
File file = new File(TEST_FILE);
|
||||
PathResource resource = new PathResource(file.toURI());
|
||||
assertThat(resource.getPath(), equalTo(file.getAbsoluteFile().toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPathForFile() throws Exception {
|
||||
public void getPathForFile() {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getPath(), equalTo(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPathForDir() throws Exception {
|
||||
public void getPathForDir() {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.getPath(), equalTo(TEST_DIR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileExists() throws Exception {
|
||||
public void fileExists() {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.exists(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dirExists() throws Exception {
|
||||
public void dirExists() {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.exists(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileDoesNotExist() throws Exception {
|
||||
public void fileDoesNotExist() {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
assertThat(resource.exists(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileIsReadable() throws Exception {
|
||||
public void fileIsReadable() {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.isReadable(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotExistIsNotReadable() throws Exception {
|
||||
public void doesNotExistIsNotReadable() {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
assertThat(resource.isReadable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryIsNotReadable() throws Exception {
|
||||
public void directoryIsNotReadable() {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.isReadable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStream() throws Exception {
|
||||
public void getInputStream() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(resource.getInputStream());
|
||||
assertThat(bytes.length, greaterThan(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStreamForDir() throws Exception {
|
||||
public void getInputStreamForDir() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getInputStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getInputStreamDoesNotExist() throws Exception {
|
||||
public void getInputStreamDoesNotExist() throws IOException {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getInputStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUrl() throws Exception {
|
||||
public void getUrl() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getURL().toString(), Matchers.endsWith("core/io/example.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getUri() throws Exception {
|
||||
public void getUri() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getURI().toString(), Matchers.endsWith("core/io/example.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFile() throws Exception {
|
||||
public void getFile() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.getFile().getAbsoluteFile(), equalTo(file.getAbsoluteFile()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFileUnsupported() throws Exception {
|
||||
public void getFileUnsupported() throws IOException {
|
||||
Path path = mock(Path.class);
|
||||
given(path.normalize()).willReturn(path);
|
||||
given(path.toFile()).willThrow(new UnsupportedOperationException());
|
||||
@@ -214,72 +214,72 @@ public class PathResourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentLength() throws Exception {
|
||||
public void contentLength() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.contentLength(), equalTo(file.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void contentLengthForDirectory() throws Exception {
|
||||
public void contentLengthForDirectory() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
File file = new File(TEST_DIR);
|
||||
assertThat(resource.contentLength(), equalTo(file.length()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lastModified() throws Exception {
|
||||
public void lastModified() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
File file = new File(TEST_FILE);
|
||||
assertThat(resource.lastModified() / 1000, equalTo(file.lastModified() / 1000));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRelativeFromDir() throws Exception {
|
||||
public void createRelativeFromDir() throws IOException {
|
||||
Resource resource = new PathResource(TEST_DIR).createRelative("example.properties");
|
||||
assertThat(resource, equalTo((Resource) new PathResource(TEST_FILE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createRelativeFromFile() throws Exception {
|
||||
public void createRelativeFromFile() throws IOException {
|
||||
Resource resource = new PathResource(TEST_FILE).createRelative("../example.properties");
|
||||
assertThat(resource, equalTo((Resource) new PathResource(TEST_FILE)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void filename() throws Exception {
|
||||
public void filename() {
|
||||
Resource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getFilename(), equalTo("example.properties"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void description() throws Exception {
|
||||
public void description() {
|
||||
Resource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.getDescription(), containsString("path ["));
|
||||
assertThat(resource.getDescription(), containsString(TEST_FILE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fileIsWritable() throws Exception {
|
||||
public void fileIsWritable() {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
assertThat(resource.isWritable(), equalTo(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryIsNotWritable() throws Exception {
|
||||
public void directoryIsNotWritable() {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
assertThat(resource.isWritable(), equalTo(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void outputStream() throws Exception {
|
||||
public void outputStream() throws IOException {
|
||||
PathResource resource = new PathResource(temporaryFolder.newFile("test").toPath());
|
||||
FileCopyUtils.copy("test".getBytes(StandardCharsets.UTF_8), resource.getOutputStream());
|
||||
assertThat(resource.contentLength(), equalTo(4L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotExistOutputStream() throws Exception {
|
||||
public void doesNotExistOutputStream() throws IOException {
|
||||
File file = temporaryFolder.newFile("test");
|
||||
file.delete();
|
||||
PathResource resource = new PathResource(file.toPath());
|
||||
@@ -288,14 +288,14 @@ public class PathResourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryOutputStream() throws Exception {
|
||||
public void directoryOutputStream() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.getOutputStream();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReadableByteChannel() throws Exception {
|
||||
public void getReadableByteChannel() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_FILE);
|
||||
ReadableByteChannel channel = null;
|
||||
try {
|
||||
@@ -313,7 +313,7 @@ public class PathResourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReadableByteChannelForDir() throws Exception {
|
||||
public void getReadableByteChannelForDir() throws IOException {
|
||||
PathResource resource = new PathResource(TEST_DIR);
|
||||
try {
|
||||
resource.readableChannel();
|
||||
@@ -324,14 +324,14 @@ public class PathResourceTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getReadableByteChannelDoesNotExist() throws Exception {
|
||||
public void getReadableByteChannelDoesNotExist() throws IOException {
|
||||
PathResource resource = new PathResource(NON_EXISTING_FILE);
|
||||
thrown.expect(NoSuchFileException.class);
|
||||
thrown.expect(FileNotFoundException.class);
|
||||
resource.readableChannel();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWritableChannel() throws Exception {
|
||||
public void getWritableChannel() throws IOException {
|
||||
PathResource resource = new PathResource(temporaryFolder.newFile("test").toPath());
|
||||
ByteBuffer buffer = ByteBuffer.wrap("test".getBytes(StandardCharsets.UTF_8));
|
||||
WritableByteChannel channel = null;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -201,8 +201,8 @@ public class ResourceTests {
|
||||
return name;
|
||||
}
|
||||
@Override
|
||||
public InputStream getInputStream() {
|
||||
return null;
|
||||
public InputStream getInputStream() throws IOException {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -211,21 +211,21 @@ public class ResourceTests {
|
||||
fail("FileNotFoundException should have been thrown");
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
assertTrue(ex.getMessage().indexOf(name) != -1);
|
||||
assertTrue(ex.getMessage().contains(name));
|
||||
}
|
||||
try {
|
||||
resource.getFile();
|
||||
fail("FileNotFoundException should have been thrown");
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
assertTrue(ex.getMessage().indexOf(name) != -1);
|
||||
assertTrue(ex.getMessage().contains(name));
|
||||
}
|
||||
try {
|
||||
resource.createRelative("/testing");
|
||||
fail("FileNotFoundException should have been thrown");
|
||||
}
|
||||
catch (FileNotFoundException ex) {
|
||||
assertTrue(ex.getMessage().indexOf(name) != -1);
|
||||
assertTrue(ex.getMessage().contains(name));
|
||||
}
|
||||
|
||||
assertThat(resource.getFilename(), nullValue());
|
||||
@@ -235,19 +235,19 @@ public class ResourceTests {
|
||||
public void testContentLength() throws IOException {
|
||||
AbstractResource resource = new AbstractResource() {
|
||||
@Override
|
||||
public InputStream getInputStream() throws IOException {
|
||||
public InputStream getInputStream() {
|
||||
return new ByteArrayInputStream(new byte[] { 'a', 'b', 'c' });
|
||||
}
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
};
|
||||
assertThat(resource.contentLength(), is(3L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetReadableByteChannel() throws IOException {
|
||||
public void testReadableChannel() throws IOException {
|
||||
Resource resource = new FileSystemResource(getClass().getResource("Resource.class").getFile());
|
||||
ReadableByteChannel channel = null;
|
||||
try {
|
||||
@@ -264,4 +264,24 @@ public class ResourceTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = FileNotFoundException.class)
|
||||
public void testInputStreamNotFoundOnFileSystemResource() throws IOException {
|
||||
new FileSystemResource(getClass().getResource("Resource.class").getFile()).createRelative("X").getInputStream();
|
||||
}
|
||||
|
||||
@Test(expected = FileNotFoundException.class)
|
||||
public void testReadableChannelNotFoundOnFileSystemResource() throws IOException {
|
||||
new FileSystemResource(getClass().getResource("Resource.class").getFile()).createRelative("X").readableChannel();
|
||||
}
|
||||
|
||||
@Test(expected = FileNotFoundException.class)
|
||||
public void testInputStreamNotFoundOnClassPathResource() throws IOException {
|
||||
new ClassPathResource("Resource.class", getClass()).createRelative("X").getInputStream();
|
||||
}
|
||||
|
||||
@Test(expected = FileNotFoundException.class)
|
||||
public void testReadableChannelNotFoundOnClassPathResource() throws IOException {
|
||||
new ClassPathResource("Resource.class", getClass()).createRelative("X").readableChannel();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -303,7 +303,7 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
assertArrayEquals(new byte[]{'a', 'b', 'c', 'd'}, result);
|
||||
|
||||
release(buffer1);
|
||||
release(buffer1, buffer2, buffer3);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -461,5 +461,36 @@ public class DataBufferTests extends AbstractDataBufferAllocatingTestCase {
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spr16351() {
|
||||
DataBuffer buffer = createDataBuffer(6);
|
||||
byte[] bytes = {'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
buffer.write(bytes);
|
||||
DataBuffer slice = buffer.slice(3, 3);
|
||||
buffer.writePosition(3);
|
||||
buffer.write(slice);
|
||||
|
||||
assertEquals(6, buffer.readableByteCount());
|
||||
byte[] result = new byte[6];
|
||||
buffer.read(result);
|
||||
|
||||
assertArrayEquals(bytes, result);
|
||||
|
||||
release(buffer);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void join() {
|
||||
DataBuffer composite = this.bufferFactory.join(Arrays.asList(stringBuffer("a"),
|
||||
stringBuffer("b"), stringBuffer("c")));
|
||||
assertEquals(3, composite.readableByteCount());
|
||||
byte[] bytes = new byte[3];
|
||||
composite.read(bytes);
|
||||
|
||||
assertArrayEquals(new byte[] {'a','b','c'}, bytes);
|
||||
|
||||
release(composite);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+48
-60
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,6 +24,7 @@ import java.nio.channels.AsynchronousFileChannel;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@@ -31,6 +32,7 @@ import java.nio.file.StandardOpenOption;
|
||||
import java.time.Duration;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import org.junit.Test;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import reactor.core.publisher.Flux;
|
||||
@@ -38,6 +40,7 @@ import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -49,10 +52,11 @@ import static org.mockito.Mockito.*;
|
||||
public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
@Test
|
||||
public void readReadableByteChannel() throws Exception {
|
||||
public void readByteChannel() throws Exception {
|
||||
URI uri = DataBufferUtilsTests.class.getResource("DataBufferUtilsTests.txt").toURI();
|
||||
FileChannel channel = FileChannel.open(Paths.get(uri), StandardOpenOption.READ);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.read(channel, this.bufferFactory, 3);
|
||||
Flux<DataBuffer> flux =
|
||||
DataBufferUtils.readByteChannel(() -> FileChannel.open(Paths.get(uri), StandardOpenOption.READ),
|
||||
this.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(flux)
|
||||
.consumeNextWith(stringConsumer("foo"))
|
||||
@@ -61,16 +65,14 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
.consumeNextWith(stringConsumer("qux"))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(5));
|
||||
|
||||
assertFalse(channel.isOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readAsynchronousFileChannel() throws Exception {
|
||||
URI uri = DataBufferUtilsTests.class.getResource("DataBufferUtilsTests.txt").toURI();
|
||||
AsynchronousFileChannel
|
||||
channel = AsynchronousFileChannel.open(Paths.get(uri), StandardOpenOption.READ);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.read(channel, this.bufferFactory, 3);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.readAsynchronousFileChannel(
|
||||
() -> AsynchronousFileChannel.open(Paths.get(uri), StandardOpenOption.READ),
|
||||
this.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(flux)
|
||||
.consumeNextWith(stringConsumer("foo"))
|
||||
@@ -84,9 +86,9 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
@Test
|
||||
public void readAsynchronousFileChannelPosition() throws Exception {
|
||||
URI uri = DataBufferUtilsTests.class.getResource("DataBufferUtilsTests.txt").toURI();
|
||||
AsynchronousFileChannel
|
||||
channel = AsynchronousFileChannel.open(Paths.get(uri), StandardOpenOption.READ);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.read(channel, 3, this.bufferFactory, 3);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.readAsynchronousFileChannel(
|
||||
() -> AsynchronousFileChannel.open(Paths.get(uri), StandardOpenOption.READ),
|
||||
3, this.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(flux)
|
||||
.consumeNextWith(stringConsumer("bar"))
|
||||
@@ -96,26 +98,12 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
.verify(Duration.ofSeconds(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readUnalignedChannel() throws Exception {
|
||||
URI uri = DataBufferUtilsTests.class.getResource("DataBufferUtilsTests.txt").toURI();
|
||||
FileChannel channel = FileChannel.open(Paths.get(uri), StandardOpenOption.READ);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.read(channel, this.bufferFactory, 5);
|
||||
|
||||
StepVerifier.create(flux)
|
||||
.consumeNextWith(stringConsumer("fooba"))
|
||||
.consumeNextWith(stringConsumer("rbazq"))
|
||||
.consumeNextWith(stringConsumer("ux"))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(5));
|
||||
|
||||
assertFalse(channel.isOpen());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void readInputStream() throws Exception {
|
||||
InputStream is = DataBufferUtilsTests.class.getResourceAsStream("DataBufferUtilsTests.txt");
|
||||
Flux<DataBuffer> flux = DataBufferUtils.read(is, this.bufferFactory, 3);
|
||||
Flux<DataBuffer> flux = DataBufferUtils.readInputStream(
|
||||
() -> DataBufferUtilsTests.class.getResourceAsStream("DataBufferUtilsTests.txt"),
|
||||
this.bufferFactory, 3);
|
||||
|
||||
StepVerifier.create(flux)
|
||||
.consumeNextWith(stringConsumer("foo"))
|
||||
@@ -293,27 +281,35 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
|
||||
flux.subscribe(DataBufferUtils.releaseConsumer());
|
||||
|
||||
// AbstractDataBufferAllocatingTestCase.LeakDetector will assert the release of the buffers
|
||||
assertReleased(foo);
|
||||
assertReleased(bar);
|
||||
assertReleased(baz);
|
||||
}
|
||||
|
||||
private static void assertReleased(DataBuffer dataBuffer) {
|
||||
if (dataBuffer instanceof NettyDataBuffer) {
|
||||
ByteBuf byteBuf = ((NettyDataBuffer) dataBuffer).getNativeBuffer();
|
||||
assertEquals(0, byteBuf.refCnt());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void SPR16070() throws Exception {
|
||||
ReadableByteChannel channel = mock(ReadableByteChannel.class);
|
||||
when(channel.read(any()))
|
||||
.thenAnswer(putByte(1))
|
||||
.thenAnswer(putByte(2))
|
||||
.thenAnswer(putByte(3))
|
||||
.thenAnswer(putByte('a'))
|
||||
.thenAnswer(putByte('b'))
|
||||
.thenAnswer(putByte('c'))
|
||||
.thenReturn(-1);
|
||||
|
||||
Flux<DataBuffer> read = DataBufferUtils.read(channel, this.bufferFactory, 1);
|
||||
Flux<DataBuffer> read = DataBufferUtils.readByteChannel(() -> channel, this.bufferFactory, 1);
|
||||
|
||||
StepVerifier.create(
|
||||
read.reduce(DataBuffer::write)
|
||||
.map(this::dataBufferToBytes)
|
||||
.map(this::encodeHexString)
|
||||
)
|
||||
.expectNext("010203")
|
||||
.verifyComplete();
|
||||
StepVerifier.create(read)
|
||||
.consumeNextWith(stringConsumer("a"))
|
||||
.consumeNextWith(stringConsumer("b"))
|
||||
.consumeNextWith(stringConsumer("c"))
|
||||
.expectComplete()
|
||||
.verify(Duration.ofSeconds(5));
|
||||
|
||||
}
|
||||
|
||||
@@ -325,27 +321,19 @@ public class DataBufferUtilsTests extends AbstractDataBufferAllocatingTestCase {
|
||||
};
|
||||
}
|
||||
|
||||
private byte[] dataBufferToBytes(DataBuffer buffer) {
|
||||
try {
|
||||
int byteCount = buffer.readableByteCount();
|
||||
byte[] bytes = new byte[byteCount];
|
||||
buffer.read(bytes);
|
||||
return bytes;
|
||||
}
|
||||
finally {
|
||||
release(buffer);
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void join() {
|
||||
DataBuffer foo = stringBuffer("foo");
|
||||
DataBuffer bar = stringBuffer("bar");
|
||||
DataBuffer baz = stringBuffer("baz");
|
||||
Flux<DataBuffer> flux = Flux.just(foo, bar, baz);
|
||||
|
||||
private String encodeHexString(byte[] data) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
builder.append((0xF0 & b) >>> 4);
|
||||
builder.append(0x0F & b);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
DataBuffer result = DataBufferUtils.join(flux).block(Duration.ofSeconds(5));
|
||||
|
||||
assertEquals("foobarbaz", DataBufferTestUtils.dumpString(result, StandardCharsets.UTF_8));
|
||||
|
||||
release(result);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,10 +22,14 @@ import java.net.ServerSocket;
|
||||
import java.util.SortedSet;
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.util.SocketUtils.*;
|
||||
import static org.springframework.util.SocketUtils.PORT_RANGE_MIN;
|
||||
import static org.springframework.util.SocketUtils.PORT_RANGE_MAX;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SocketUtils}.
|
||||
@@ -35,15 +39,20 @@ import static org.springframework.util.SocketUtils.*;
|
||||
*/
|
||||
public class SocketUtilsTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException exception = ExpectedException.none();
|
||||
|
||||
// TCP
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void findAvailableTcpPortWithZeroMinPort() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
SocketUtils.findAvailableTcpPort(0);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void findAvailableTcpPortWithNegativeMinPort() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
SocketUtils.findAvailableTcpPort(-500);
|
||||
}
|
||||
|
||||
@@ -53,11 +62,22 @@ public class SocketUtilsTests {
|
||||
assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@Test
|
||||
public void findAvailableTcpPortWithMinPortEqualToMaxPort() {
|
||||
int minMaxPort = SocketUtils.findAvailableTcpPort();
|
||||
int port = SocketUtils.findAvailableTcpPort(minMaxPort, minMaxPort);
|
||||
assertEquals(minMaxPort, port);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findAvailableTcpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception {
|
||||
int port = SocketUtils.findAvailableTcpPort();
|
||||
ServerSocket socket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"));
|
||||
try {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectMessage(startsWith("Could not find an available TCP port"));
|
||||
exception.expectMessage(endsWith("after 1 attempts"));
|
||||
// will only look for the exact port
|
||||
SocketUtils.findAvailableTcpPort(port, port);
|
||||
}
|
||||
finally {
|
||||
@@ -99,21 +119,24 @@ public class SocketUtilsTests {
|
||||
findAvailableTcpPorts(50, 40000, 45000);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void findAvailableTcpPortsWithRequestedNumberGreaterThanSizeOfRange() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
findAvailableTcpPorts(50, 45000, 45010);
|
||||
}
|
||||
|
||||
|
||||
// UDP
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void findAvailableUdpPortWithZeroMinPort() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
SocketUtils.findAvailableUdpPort(0);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void findAvailableUdpPortWithNegativeMinPort() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
SocketUtils.findAvailableUdpPort(-500);
|
||||
}
|
||||
|
||||
@@ -123,11 +146,14 @@ public class SocketUtilsTests {
|
||||
assertPortInRange(port, PORT_RANGE_MIN, PORT_RANGE_MAX);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@Test
|
||||
public void findAvailableUdpPortWhenPortOnLoopbackInterfaceIsNotAvailable() throws Exception {
|
||||
int port = SocketUtils.findAvailableUdpPort();
|
||||
DatagramSocket socket = new DatagramSocket(port, InetAddress.getByName("localhost"));
|
||||
try {
|
||||
exception.expect(IllegalStateException.class);
|
||||
exception.expectMessage(startsWith("Could not find an available UDP port"));
|
||||
exception.expectMessage(endsWith("after 1 attempts"));
|
||||
// will only look for the exact port
|
||||
SocketUtils.findAvailableUdpPort(port, port);
|
||||
}
|
||||
@@ -170,8 +196,9 @@ public class SocketUtilsTests {
|
||||
findAvailableUdpPorts(50, 40000, 45000);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test
|
||||
public void findAvailableUdpPortsWithRequestedNumberGreaterThanSizeOfRange() {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
findAvailableUdpPorts(50, 45000, 45010);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -26,6 +26,7 @@ import org.springframework.asm.ClassWriter;
|
||||
import org.springframework.asm.MethodVisitor;
|
||||
import org.springframework.asm.Opcodes;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Manages the class being generated by the compilation process.
|
||||
@@ -153,10 +154,7 @@ public class CodeFlow implements Opcodes {
|
||||
*/
|
||||
@Nullable
|
||||
public String lastDescriptor() {
|
||||
if (this.compilationScopes.peek().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return this.compilationScopes.peek().get(this.compilationScopes.peek().size() - 1);
|
||||
return CollectionUtils.lastElement(this.compilationScopes.peek());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -458,8 +458,8 @@ public class ConstructorReference extends SpelNodeImpl {
|
||||
mv.visitInsn(DUP);
|
||||
|
||||
// children[0] is the type of the constructor, don't want to include that in argument processing
|
||||
SpelNodeImpl[] arguments = new SpelNodeImpl[children.length - 1];
|
||||
System.arraycopy(children, 1, arguments, 0, children.length - 1);
|
||||
SpelNodeImpl[] arguments = new SpelNodeImpl[this.children.length - 1];
|
||||
System.arraycopy(this.children, 1, arguments, 0, this.children.length - 1);
|
||||
generateCodeForArguments(mv, cf, constructor, arguments);
|
||||
mv.visitMethodInsn(INVOKESPECIAL, classDesc, "<init>", CodeFlow.createSignatureDescriptor(constructor), false);
|
||||
cf.pushDescriptor(this.exitTypeDescriptor);
|
||||
|
||||
+35
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -47,6 +47,7 @@ import org.springframework.util.ReflectionUtils;
|
||||
* (right now), so the names must be unique.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public class FunctionReference extends SpelNodeImpl {
|
||||
@@ -56,9 +57,7 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
// Captures the most recently used method for the function invocation *if* the method
|
||||
// can safely be used for compilation (i.e. no argument conversion is going on)
|
||||
@Nullable
|
||||
private Method method;
|
||||
|
||||
private boolean argumentConversionOccurred;
|
||||
private volatile Method method;
|
||||
|
||||
|
||||
public FunctionReference(String functionName, int pos, SpelNodeImpl... arguments) {
|
||||
@@ -73,9 +72,8 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
if (value == TypedValue.NULL) {
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.FUNCTION_NOT_DEFINED, this.name);
|
||||
}
|
||||
|
||||
// Two possibilities: a lambda function or a Java static method registered as a function
|
||||
if (!(value.getValue() instanceof Method)) {
|
||||
// Two possibilities: a lambda function or a Java static method registered as a function
|
||||
throw new SpelEvaluationException(
|
||||
SpelMessage.FUNCTION_REFERENCE_CANNOT_BE_INVOKED, this.name, value.getClass());
|
||||
}
|
||||
@@ -90,21 +88,22 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a function represented as a java.lang.reflect.Method.
|
||||
* Execute a function represented as a {@code java.lang.reflect.Method}.
|
||||
* @param state the expression evaluation state
|
||||
* @param method the method to invoke
|
||||
* @return the return value of the invoked Java method
|
||||
* @throws EvaluationException if there is any problem invoking the method
|
||||
*/
|
||||
private TypedValue executeFunctionJLRMethod(ExpressionState state, Method method) throws EvaluationException {
|
||||
this.method = null;
|
||||
Object[] functionArgs = getArguments(state);
|
||||
|
||||
if (!method.isVarArgs() && method.getParameterCount() != functionArgs.length) {
|
||||
throw new SpelEvaluationException(SpelMessage.INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
|
||||
functionArgs.length, method.getParameterCount());
|
||||
if (!method.isVarArgs()) {
|
||||
int declaredParamCount = method.getParameterCount();
|
||||
if (declaredParamCount != functionArgs.length) {
|
||||
throw new SpelEvaluationException(SpelMessage.INCORRECT_NUMBER_OF_ARGUMENTS_TO_FUNCTION,
|
||||
functionArgs.length, declaredParamCount);
|
||||
}
|
||||
}
|
||||
// Only static methods can be called in this way
|
||||
if (!Modifier.isStatic(method.getModifiers())) {
|
||||
throw new SpelEvaluationException(getStartPosition(),
|
||||
SpelMessage.FUNCTION_MUST_BE_STATIC, ClassUtils.getQualifiedMethodName(method), this.name);
|
||||
@@ -112,25 +111,33 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
|
||||
// Convert arguments if necessary and remap them for varargs if required
|
||||
TypeConverter converter = state.getEvaluationContext().getTypeConverter();
|
||||
argumentConversionOccurred = ReflectionHelper.convertAllArguments(converter, functionArgs, method);
|
||||
boolean argumentConversionOccurred = ReflectionHelper.convertAllArguments(converter, functionArgs, method);
|
||||
if (method.isVarArgs()) {
|
||||
functionArgs = ReflectionHelper.setupArgumentsForVarargsInvocation(
|
||||
method.getParameterTypes(), functionArgs);
|
||||
}
|
||||
boolean compilable = false;
|
||||
|
||||
try {
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
Object result = method.invoke(method.getClass(), functionArgs);
|
||||
if (!argumentConversionOccurred) {
|
||||
this.method = method;
|
||||
this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
|
||||
}
|
||||
compilable = !argumentConversionOccurred;
|
||||
return new TypedValue(result, new TypeDescriptor(new MethodParameter(method, -1)).narrow(result));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new SpelEvaluationException(getStartPosition(), ex, SpelMessage.EXCEPTION_DURING_FUNCTION_CALL,
|
||||
this.name, ex.getMessage());
|
||||
}
|
||||
finally {
|
||||
if (compilable) {
|
||||
this.exitTypeDescriptor = CodeFlow.toDescriptor(method.getReturnType());
|
||||
this.method = method;
|
||||
}
|
||||
else {
|
||||
this.exitTypeDescriptor = null;
|
||||
this.method = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -162,12 +169,13 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
|
||||
@Override
|
||||
public boolean isCompilable() {
|
||||
if (this.method == null || this.argumentConversionOccurred) {
|
||||
Method method = this.method;
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
int methodModifiers = this.method.getModifiers();
|
||||
int methodModifiers = method.getModifiers();
|
||||
if (!Modifier.isStatic(methodModifiers) || !Modifier.isPublic(methodModifiers) ||
|
||||
!Modifier.isPublic(this.method.getDeclaringClass().getModifiers())) {
|
||||
!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
for (SpelNodeImpl child : this.children) {
|
||||
@@ -179,12 +187,13 @@ public class FunctionReference extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateCode(MethodVisitor mv,CodeFlow cf) {
|
||||
Assert.state(this.method != null, "No method handle");
|
||||
String classDesc = this.method.getDeclaringClass().getName().replace('.', '/');
|
||||
generateCodeForArguments(mv, cf, this.method, this.children);
|
||||
mv.visitMethodInsn(INVOKESTATIC, classDesc, this.method.getName(),
|
||||
CodeFlow.createSignatureDescriptor(this.method), false);
|
||||
public void generateCode(MethodVisitor mv, CodeFlow cf) {
|
||||
Method method = this.method;
|
||||
Assert.state(method != null, "No method handle");
|
||||
String classDesc = method.getDeclaringClass().getName().replace('.', '/');
|
||||
generateCodeForArguments(mv, cf, method, this.children);
|
||||
mv.visitMethodInsn(INVOKESTATIC, classDesc, method.getName(),
|
||||
CodeFlow.createSignatureDescriptor(method), false);
|
||||
cf.pushDescriptor(this.exitTypeDescriptor);
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -31,6 +31,7 @@ import org.springframework.expression.spel.SpelEvaluationException;
|
||||
import org.springframework.expression.spel.SpelMessage;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
@@ -160,7 +161,7 @@ public class Selection extends SpelNodeImpl {
|
||||
}
|
||||
|
||||
if (this.variant == LAST) {
|
||||
return new ValueRef.TypedValueHolderValueRef(new TypedValue(result.get(result.size() - 1)), this);
|
||||
return new ValueRef.TypedValueHolderValueRef(new TypedValue(CollectionUtils.lastElement(result)), this);
|
||||
}
|
||||
|
||||
if (operand instanceof Iterable) {
|
||||
|
||||
+6
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.util.ObjectUtils;
|
||||
* format expression.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @author Juergen Hoeller
|
||||
* @since 3.0
|
||||
*/
|
||||
public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
@@ -46,7 +47,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
private static SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0];
|
||||
|
||||
|
||||
protected int pos; // start = top 16bits, end = bottom 16bits
|
||||
protected int pos; // start = top 16bits, end = bottom 16bits
|
||||
|
||||
protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
|
||||
|
||||
@@ -81,7 +82,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the next child is one of the specified classes
|
||||
* Return {@code true} if the next child is one of the specified classes.
|
||||
*/
|
||||
protected boolean nextChildIs(Class<?>... clazzes) {
|
||||
if (this.parent != null) {
|
||||
@@ -123,8 +124,7 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
|
||||
@Override
|
||||
public void setValue(ExpressionState expressionState, @Nullable Object newValue) throws EvaluationException {
|
||||
throw new SpelEvaluationException(getStartPosition(),
|
||||
SpelMessage.SETVALUE_NOT_SUPPORTED, getClass());
|
||||
throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -281,4 +281,5 @@ public abstract class SpelNodeImpl implements SpelNode, Opcodes {
|
||||
}
|
||||
cf.exitCompilationScope();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -4988,7 +4988,7 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateCode(String propertyName, MethodVisitor mv,CodeFlow cf) {
|
||||
public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
|
||||
if (method == null) {
|
||||
try {
|
||||
method = Payload2.class.getDeclaredMethod("getField", String.class);
|
||||
@@ -5005,7 +5005,8 @@ public class SpelCompilationCoverageTests extends AbstractExpressionTests {
|
||||
mv.visitTypeInsn(CHECKCAST, memberDeclaringClassSlashedDescriptor);
|
||||
}
|
||||
mv.visitLdcInsn(propertyName);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, method.getName(),CodeFlow.createSignatureDescriptor(method),false);
|
||||
mv.visitMethodInsn(INVOKEVIRTUAL, memberDeclaringClassSlashedDescriptor, method.getName(),
|
||||
CodeFlow.createSignatureDescriptor(method), false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
This meta-project is used to generate a bill-of-materials POM that contains the other
|
||||
projects in a dependencyManagement section.
|
||||
|
||||
<http://docs.spring.io/spring-framework/docs/current/spring-framework-reference/html/overview.html#overview-maven-bom>
|
||||
<https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/overview.html#overview-getting-started>
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -58,7 +58,7 @@ public class ArgumentPreparedStatementSetter implements PreparedStatementSetter,
|
||||
* @param ps the PreparedStatement
|
||||
* @param parameterPosition index of the parameter position
|
||||
* @param argValue the value to set
|
||||
* @throws SQLException
|
||||
* @throws SQLException if thrown by PreparedStatement methods
|
||||
*/
|
||||
protected void doSetValue(PreparedStatement ps, int parameterPosition, Object argValue) throws SQLException {
|
||||
if (argValue instanceof SqlParameterValue) {
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -92,7 +92,7 @@ public class ArgumentTypePreparedStatementSetter implements PreparedStatementSet
|
||||
* @param parameterPosition index of the parameter position
|
||||
* @param argType the argument type
|
||||
* @param argValue the argument value
|
||||
* @throws SQLException
|
||||
* @throws SQLException if thrown by PreparedStatement methods
|
||||
*/
|
||||
protected void doSetValue(PreparedStatement ps, int parameterPosition, int argType, Object argValue)
|
||||
throws SQLException {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.jdbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -35,18 +36,18 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class SqlParameter {
|
||||
|
||||
/** The name of the parameter, if any */
|
||||
// The name of the parameter, if any
|
||||
@Nullable
|
||||
private String name;
|
||||
|
||||
/** SQL type constant from {@code java.sql.Types} */
|
||||
// SQL type constant from {@code java.sql.Types}
|
||||
private final int sqlType;
|
||||
|
||||
/** Used for types that are user-named like: STRUCT, DISTINCT, JAVA_OBJECT, named array types */
|
||||
// Used for types that are user-named like: STRUCT, DISTINCT, JAVA_OBJECT, named array types
|
||||
@Nullable
|
||||
private String typeName;
|
||||
|
||||
/** The scale to apply in case of a NUMERIC or DECIMAL type, if any */
|
||||
// The scale to apply in case of a NUMERIC or DECIMAL type, if any
|
||||
@Nullable
|
||||
private Integer scale;
|
||||
|
||||
@@ -184,12 +185,16 @@ public class SqlParameter {
|
||||
* to a List of SqlParameter objects as used in this package.
|
||||
*/
|
||||
public static List<SqlParameter> sqlTypesToAnonymousParameterList(@Nullable int... types) {
|
||||
List<SqlParameter> result = new LinkedList<>();
|
||||
List<SqlParameter> result;
|
||||
if (types != null) {
|
||||
result = new ArrayList<>(types.length);
|
||||
for (int type : types) {
|
||||
result.add(new SqlParameter(type));
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = new LinkedList<>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+32
-30
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -53,47 +53,47 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public class CallMetaDataContext {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
// Logger available to subclasses
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/** name of procedure to call **/
|
||||
// Name of procedure to call
|
||||
@Nullable
|
||||
private String procedureName;
|
||||
|
||||
/** name of catalog for call **/
|
||||
// Name of catalog for call
|
||||
@Nullable
|
||||
private String catalogName;
|
||||
|
||||
/** name of schema for call **/
|
||||
// Name of schema for call
|
||||
@Nullable
|
||||
private String schemaName;
|
||||
|
||||
/** List of SqlParameter objects to be used in call execution */
|
||||
// List of SqlParameter objects to be used in call execution
|
||||
private List<SqlParameter> callParameters = new ArrayList<>();
|
||||
|
||||
/** Actual name to use for the return value in the output map */
|
||||
// Actual name to use for the return value in the output map
|
||||
@Nullable
|
||||
private String actualFunctionReturnName;
|
||||
|
||||
/** Set of in parameter names to exclude use for any not listed */
|
||||
// Set of in parameter names to exclude use for any not listed
|
||||
private Set<String> limitedInParameterNames = new HashSet<>();
|
||||
|
||||
/** List of SqlParameter names for out parameters */
|
||||
// List of SqlParameter names for out parameters
|
||||
private List<String> outParameterNames = new ArrayList<>();
|
||||
|
||||
/** Indicates whether this is a procedure or a function **/
|
||||
// Indicates whether this is a procedure or a function
|
||||
private boolean function = false;
|
||||
|
||||
/** Indicates whether this procedure's return value should be included **/
|
||||
// Indicates whether this procedure's return value should be included
|
||||
private boolean returnValueRequired = false;
|
||||
|
||||
/** Should we access call parameter meta data info or not */
|
||||
// Should we access call parameter meta data info or not
|
||||
private boolean accessCallParameterMetaData = true;
|
||||
|
||||
/** Should we bind parameter by name **/
|
||||
// Should we bind parameter by name
|
||||
private boolean namedBinding;
|
||||
|
||||
/** The provider of call meta data */
|
||||
// The provider of call meta data
|
||||
@Nullable
|
||||
private CallMetaDataProvider metaDataProvider;
|
||||
|
||||
@@ -274,7 +274,8 @@ public class CallMetaDataContext {
|
||||
return new SqlOutParameter(parameterName, provider.getRefCursorSqlType(), rowMapper);
|
||||
}
|
||||
else {
|
||||
throw new InvalidDataAccessApiUsageException("Return of a ResultSet from a stored procedure is not supported.");
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Return of a ResultSet from a stored procedure is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -493,31 +494,37 @@ public class CallMetaDataContext {
|
||||
}
|
||||
if (parameterName != null) {
|
||||
if (parameterSource.hasValue(parameterName)) {
|
||||
matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, parameterName));
|
||||
matchedParameters.put(parameterName,
|
||||
SqlParameterSourceUtils.getTypedValue(parameterSource, parameterName));
|
||||
}
|
||||
else {
|
||||
String lowerCaseName = parameterName.toLowerCase();
|
||||
if (parameterSource.hasValue(lowerCaseName)) {
|
||||
matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, lowerCaseName));
|
||||
matchedParameters.put(parameterName,
|
||||
SqlParameterSourceUtils.getTypedValue(parameterSource, lowerCaseName));
|
||||
}
|
||||
else {
|
||||
String englishLowerCaseName = parameterName.toLowerCase(Locale.ENGLISH);
|
||||
if (parameterSource.hasValue(englishLowerCaseName)) {
|
||||
matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, englishLowerCaseName));
|
||||
matchedParameters.put(parameterName,
|
||||
SqlParameterSourceUtils.getTypedValue(parameterSource, englishLowerCaseName));
|
||||
}
|
||||
else {
|
||||
String propertyName = JdbcUtils.convertUnderscoreNameToPropertyName(parameterName);
|
||||
if (parameterSource.hasValue(propertyName)) {
|
||||
matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, propertyName));
|
||||
matchedParameters.put(parameterName,
|
||||
SqlParameterSourceUtils.getTypedValue(parameterSource, propertyName));
|
||||
}
|
||||
else {
|
||||
if (caseInsensitiveParameterNames.containsKey(lowerCaseName)) {
|
||||
String sourceName = caseInsensitiveParameterNames.get(lowerCaseName);
|
||||
matchedParameters.put(parameterName, SqlParameterSourceUtils.getTypedValue(parameterSource, sourceName));
|
||||
matchedParameters.put(parameterName,
|
||||
SqlParameterSourceUtils.getTypedValue(parameterSource, sourceName));
|
||||
}
|
||||
else {
|
||||
logger.warn("Unable to locate the corresponding parameter value for '" + parameterName +
|
||||
"' within the parameter values provided: " + caseInsensitiveParameterNames.values());
|
||||
logger.warn("Unable to locate the corresponding parameter value for '" +
|
||||
parameterName + "' within the parameter values provided: " +
|
||||
caseInsensitiveParameterNames.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -567,8 +574,8 @@ public class CallMetaDataContext {
|
||||
value = ((SqlParameterValue)value).getValue();
|
||||
}
|
||||
if (value != null) {
|
||||
logger.debug("Unable to locate the corresponding IN or IN-OUT parameter for \"" + parameterName +
|
||||
"\" in the parameters used: " + callParameterNames.keySet());
|
||||
logger.debug("Unable to locate the corresponding IN or IN-OUT parameter for \"" +
|
||||
parameterName + "\" in the parameters used: " + callParameterNames.keySet());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -669,12 +676,7 @@ public class CallMetaDataContext {
|
||||
* @since 4.2
|
||||
*/
|
||||
protected String createParameterBinding(SqlParameter parameter) {
|
||||
if (isNamedBinding()) {
|
||||
return parameter.getName() + " => ?";
|
||||
}
|
||||
else {
|
||||
return "?";
|
||||
}
|
||||
return (isNamedBinding() ? parameter.getName() + " => ?" : "?");
|
||||
}
|
||||
|
||||
private static String lowerCase(@Nullable String paramName) {
|
||||
|
||||
+14
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -45,35 +45,35 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public class TableMetaDataContext {
|
||||
|
||||
/** Logger available to subclasses */
|
||||
// Logger available to subclasses
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/** Name of table for this context */
|
||||
// Name of table for this context
|
||||
@Nullable
|
||||
private String tableName;
|
||||
|
||||
/** Name of catalog for this context */
|
||||
// Name of catalog for this context
|
||||
@Nullable
|
||||
private String catalogName;
|
||||
|
||||
/** Name of schema for this context */
|
||||
// Name of schema for this context
|
||||
@Nullable
|
||||
private String schemaName;
|
||||
|
||||
/** List of columns objects to be used in this context */
|
||||
// List of columns objects to be used in this context
|
||||
private List<String> tableColumns = new ArrayList<>();
|
||||
|
||||
/** should we access insert parameter meta data info or not */
|
||||
// Should we access insert parameter meta data info or not
|
||||
private boolean accessTableColumnMetaData = true;
|
||||
|
||||
/** should we override default for including synonyms for meta data lookups */
|
||||
// Should we override default for including synonyms for meta data lookups
|
||||
private boolean overrideIncludeSynonymsDefault = false;
|
||||
|
||||
/** the provider of table meta data */
|
||||
// The provider of table meta data
|
||||
@Nullable
|
||||
private TableMetaDataProvider metaDataProvider;
|
||||
|
||||
/** are we using generated key columns */
|
||||
// Are we using generated key columns
|
||||
private boolean generatedKeyColumnsUsed = false;
|
||||
|
||||
|
||||
@@ -206,8 +206,8 @@ public class TableMetaDataContext {
|
||||
*/
|
||||
public List<Object> matchInParameterValuesWithInsertColumns(SqlParameterSource parameterSource) {
|
||||
List<Object> values = new ArrayList<>();
|
||||
// for parameter source lookups we need to provide caseinsensitive lookup support since the
|
||||
// database metadata is not necessarily providing case sensitive column names
|
||||
// For parameter source lookups we need to provide case-insensitive lookup support since the
|
||||
// database metadata is not necessarily providing case-sensitive column names
|
||||
Map<String, String> caseInsensitiveParameterNames =
|
||||
SqlParameterSourceUtils.extractCaseInsensitiveParameterNames(parameterSource);
|
||||
for (String column : this.tableColumns) {
|
||||
@@ -226,9 +226,8 @@ public class TableMetaDataContext {
|
||||
}
|
||||
else {
|
||||
if (caseInsensitiveParameterNames.containsKey(lowerCaseName)) {
|
||||
values.add(
|
||||
SqlParameterSourceUtils.getTypedValue(parameterSource,
|
||||
caseInsensitiveParameterNames.get(lowerCaseName)));
|
||||
values.add(SqlParameterSourceUtils.getTypedValue(
|
||||
parameterSource, caseInsensitiveParameterNames.get(lowerCaseName)));
|
||||
}
|
||||
else {
|
||||
values.add(null);
|
||||
|
||||
+21
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -73,6 +73,26 @@ public class BeanPropertySqlParameterSource extends AbstractSqlParameterSource {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a default SQL type from the corresponding property type.
|
||||
* @see org.springframework.jdbc.core.StatementCreatorUtils#javaTypeToSqlParameterType
|
||||
*/
|
||||
@Override
|
||||
public int getSqlType(String paramName) {
|
||||
int sqlType = super.getSqlType(paramName);
|
||||
if (sqlType != TYPE_UNKNOWN) {
|
||||
return sqlType;
|
||||
}
|
||||
Class<?> propType = this.beanWrapper.getPropertyType(paramName);
|
||||
return StatementCreatorUtils.javaTypeToSqlParameterType(propType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames() {
|
||||
return getReadablePropertyNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide access to the property names of the wrapped bean.
|
||||
* Uses support provided in the {@link PropertyAccessor} interface.
|
||||
@@ -92,18 +112,4 @@ public class BeanPropertySqlParameterSource extends AbstractSqlParameterSource {
|
||||
return this.propertyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives a default SQL type from the corresponding property type.
|
||||
* @see org.springframework.jdbc.core.StatementCreatorUtils#javaTypeToSqlParameterType
|
||||
*/
|
||||
@Override
|
||||
public int getSqlType(String paramName) {
|
||||
int sqlType = super.getSqlType(paramName);
|
||||
if (sqlType != TYPE_UNKNOWN) {
|
||||
return sqlType;
|
||||
}
|
||||
Class<?> propType = this.beanWrapper.getPropertyType(paramName);
|
||||
return StatementCreatorUtils.javaTypeToSqlParameterType(propType);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -54,4 +54,10 @@ public class EmptySqlParameterSource implements SqlParameterSource {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.util.Map;
|
||||
import org.springframework.jdbc.core.SqlParameterValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link SqlParameterSource} implementation that holds a given Map of parameters.
|
||||
@@ -163,4 +164,10 @@ public class MapSqlParameterSource extends AbstractSqlParameterSource {
|
||||
return this.values.get(paramName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String[] getParameterNames() {
|
||||
return StringUtils.toStringArray(this.values.keySet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
-2
@@ -105,14 +105,26 @@ public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations
|
||||
|
||||
|
||||
/**
|
||||
* Expose the classic Spring JdbcTemplate to allow invocation of
|
||||
* less commonly used methods.
|
||||
* Expose the classic Spring JdbcTemplate operations to allow invocation
|
||||
* of less commonly used methods.
|
||||
*/
|
||||
@Override
|
||||
public JdbcOperations getJdbcOperations() {
|
||||
return this.classicJdbcTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose the classic Spring {@link JdbcTemplate} itself, if available,
|
||||
* in particular for passing it on to other {@code JdbcTemplate} consumers.
|
||||
* <p>If sufficient for the purposes at hand, {@link #getJdbcOperations()}
|
||||
* is recommended over this variant.
|
||||
* @since 5.0.3
|
||||
*/
|
||||
public JdbcTemplate getJdbcTemplate() {
|
||||
Assert.state(this.classicJdbcTemplate instanceof JdbcTemplate, "No JdbcTemplate available");
|
||||
return (JdbcTemplate) this.classicJdbcTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the maximum number of entries for this template's SQL cache.
|
||||
* Default is 256.
|
||||
|
||||
+25
-26
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,6 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -42,24 +41,33 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class NamedParameterUtils {
|
||||
|
||||
/**
|
||||
* Set of characters that qualify as parameter separators,
|
||||
* indicating that a parameter name in a SQL String has ended.
|
||||
*/
|
||||
private static final char[] PARAMETER_SEPARATORS =
|
||||
new char[] {'"', '\'', ':', '&', ',', ';', '(', ')', '|', '=', '+', '-', '*', '%', '/', '\\', '<', '>', '^'};
|
||||
|
||||
/**
|
||||
* Set of characters that qualify as comment or quotes starting characters.
|
||||
*/
|
||||
private static final String[] START_SKIP =
|
||||
new String[] {"'", "\"", "--", "/*"};
|
||||
private static final String[] START_SKIP = new String[] {"'", "\"", "--", "/*"};
|
||||
|
||||
/**
|
||||
* Set of characters that at are the corresponding comment or quotes ending characters.
|
||||
*/
|
||||
private static final String[] STOP_SKIP =
|
||||
new String[] {"'", "\"", "\n", "*/"};
|
||||
private static final String[] STOP_SKIP = new String[] {"'", "\"", "\n", "*/"};
|
||||
|
||||
/**
|
||||
* Set of characters that qualify as parameter separators,
|
||||
* indicating that a parameter name in a SQL String has ended.
|
||||
*/
|
||||
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^";
|
||||
|
||||
/**
|
||||
* An index with separator flags per character code.
|
||||
* Technically only needed between 34 and 124 at this point.
|
||||
*/
|
||||
private static final boolean[] separatorIndex = new boolean[128];
|
||||
|
||||
static {
|
||||
for (char c : PARAMETER_SEPARATORS.toCharArray()) {
|
||||
separatorIndex[c] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
@@ -233,7 +241,6 @@ public abstract class NamedParameterUtils {
|
||||
// character sequence ending comment or quote not found
|
||||
return statement.length;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return position;
|
||||
@@ -384,15 +391,7 @@ public abstract class NamedParameterUtils {
|
||||
* that is, whether the given character qualifies as a separator.
|
||||
*/
|
||||
private static boolean isParameterSeparator(char c) {
|
||||
if (Character.isWhitespace(c)) {
|
||||
return true;
|
||||
}
|
||||
for (char separator : PARAMETER_SEPARATORS) {
|
||||
if (c == separator) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return (separatorIndex[c] || Character.isWhitespace(c));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -423,10 +422,10 @@ public abstract class NamedParameterUtils {
|
||||
*/
|
||||
public static List<SqlParameter> buildSqlParameterList(ParsedSql parsedSql, SqlParameterSource paramSource) {
|
||||
List<String> paramNames = parsedSql.getParameterNames();
|
||||
List<SqlParameter> params = new LinkedList<>();
|
||||
List<SqlParameter> params = new ArrayList<>(paramNames.size());
|
||||
for (String paramName : paramNames) {
|
||||
params.add(
|
||||
new SqlParameter(paramName, paramSource.getSqlType(paramName), paramSource.getTypeName(paramName)));
|
||||
params.add(new SqlParameter(
|
||||
paramName, paramSource.getSqlType(paramName), paramSource.getTypeName(paramName)));
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
+21
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -73,7 +73,9 @@ public interface SqlParameterSource {
|
||||
* or {@code TYPE_UNKNOWN} if not known
|
||||
* @see #TYPE_UNKNOWN
|
||||
*/
|
||||
int getSqlType(String paramName);
|
||||
default int getSqlType(String paramName) {
|
||||
return TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the type name for the specified named parameter.
|
||||
@@ -82,6 +84,22 @@ public interface SqlParameterSource {
|
||||
* or {@code null} if not known
|
||||
*/
|
||||
@Nullable
|
||||
String getTypeName(String paramName);
|
||||
default String getTypeName(String paramName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all available parameter names if possible.
|
||||
* <p>This is an optional operation, primarily for use with
|
||||
* {@link org.springframework.jdbc.core.simple.SimpleJdbcInsert}
|
||||
* and {@link org.springframework.jdbc.core.simple.SimpleJdbcCall}.
|
||||
* @return the array of parameter names, or {@code null} if not determinable
|
||||
* @since 5.0.3
|
||||
* @see SqlParameterSourceUtils#extractCaseInsensitiveParameterNames
|
||||
*/
|
||||
@Nullable
|
||||
default String[] getParameterNames() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2017 the original author or authors.
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,7 +42,7 @@ public abstract class SqlParameterSourceUtils {
|
||||
* @return an array of {@link SqlParameterSource}
|
||||
* @see MapSqlParameterSource
|
||||
* @see BeanPropertySqlParameterSource
|
||||
* @see NamedParameterJdbcTemplate#batchUpdate(String, SqlParameterSource[]))
|
||||
* @see NamedParameterJdbcTemplate#batchUpdate(String, SqlParameterSource[])
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static SqlParameterSource[] createBatch(Object... candidates) {
|
||||
@@ -58,7 +58,7 @@ public abstract class SqlParameterSourceUtils {
|
||||
* @since 5.0.2
|
||||
* @see MapSqlParameterSource
|
||||
* @see BeanPropertySqlParameterSource
|
||||
* @see NamedParameterJdbcTemplate#batchUpdate(String, SqlParameterSource[]))
|
||||
* @see NamedParameterJdbcTemplate#batchUpdate(String, SqlParameterSource[])
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static SqlParameterSource[] createBatch(Collection<?> candidates) {
|
||||
@@ -117,14 +117,9 @@ public abstract class SqlParameterSourceUtils {
|
||||
*/
|
||||
public static Map<String, String> extractCaseInsensitiveParameterNames(SqlParameterSource parameterSource) {
|
||||
Map<String, String> caseInsensitiveParameterNames = new HashMap<>();
|
||||
if (parameterSource instanceof BeanPropertySqlParameterSource) {
|
||||
String[] propertyNames = ((BeanPropertySqlParameterSource) parameterSource).getReadablePropertyNames();
|
||||
for (String name : propertyNames) {
|
||||
caseInsensitiveParameterNames.put(name.toLowerCase(), name);
|
||||
}
|
||||
}
|
||||
else if (parameterSource instanceof MapSqlParameterSource) {
|
||||
for (String name : ((MapSqlParameterSource) parameterSource).getValues().keySet()) {
|
||||
String[] paramNames = parameterSource.getParameterNames();
|
||||
if (paramNames != null) {
|
||||
for (String name : paramNames) {
|
||||
caseInsensitiveParameterNames.put(name.toLowerCase(), name);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user