diff --git a/.gitignore b/.gitignore index c9e0aca55ea..67d798efd45 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,6 @@ jmx.log derby.log spring-test/test-output/ .gradle -.classpath -.project argfile* pom.xml @@ -22,6 +20,11 @@ pom.xml buildSrc/build /spring-*/build +# Eclipse artifacts, including WTP generated manifests +.classpath +.project +spring-*/src/main/java/META-INF/MANIFEST.MF + # IDEA artifacts and output dirs *.iml *.ipr diff --git a/build.gradle b/build.gradle index 724927507fb..19fe164e4dc 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ buildscript { maven { url "http://repo.springsource.org/plugins-release" } } dependencies { - classpath("org.springframework.build.gradle:propdeps-plugin:0.0.1") + classpath("org.springframework.build.gradle:propdeps-plugin:0.0.3") classpath("org.springframework.build.gradle:docbook-reference-plugin:0.2.4") } } @@ -21,8 +21,6 @@ configure(allprojects) { project -> apply plugin: "propdeps" apply plugin: "java" - apply plugin: "propdeps-eclipse" - apply plugin: "propdeps-idea" apply plugin: "test-source-set-dependencies" apply from: "${gradleScriptDir}/ide.gradle" @@ -60,7 +58,7 @@ configure(allprojects) { project -> test { systemProperty("java.awt.headless", "true") - systemProperty("testGroups", properties.get("testGroups")) + systemProperty("testGroups", project.properties.get("testGroups")) } repositories { @@ -235,7 +233,9 @@ project("spring-core") { optional("net.sf.jopt-simple:jopt-simple:3.0") optional("log4j:log4j:1.2.17") testCompile("xmlunit:xmlunit:1.3") - testCompile("org.codehaus.woodstox:wstx-asl:3.2.7") + testCompile("org.codehaus.woodstox:wstx-asl:3.2.7") { + exclude group: "stax", module: "stax-api" + } } jar { @@ -526,8 +526,8 @@ project("spring-orm-hibernate4") { description = "Spring Object/Relational Mapping - Hibernate 4 support" merge.into = project(":spring-orm") dependencies { - compile(project(":spring-tx")) - compile(project(":spring-jdbc")) + provided(project(":spring-tx")) + provided(project(":spring-jdbc")) optional("org.hibernate:hibernate-core:4.1.0.Final") optional("org.hibernate:hibernate-entitymanager:4.1.0.Final") optional(project(":spring-web")) @@ -597,8 +597,8 @@ project("spring-webmvc-tiles3") { description = "Spring Framework Tiles3 Integration" merge.into = project(":spring-webmvc") dependencies { - compile(project(":spring-context")) - compile(project(":spring-web")) + provided(project(":spring-context")) + provided(project(":spring-web")) provided("javax.el:el-api:1.0") provided("javax.servlet:jstl:1.2") provided("javax.servlet.jsp:jsp-api:2.1") @@ -613,6 +613,9 @@ project("spring-webmvc-tiles3") { optional("org.apache.tiles:tiles-jsp:3.0.1") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } + optional("org.apache.tiles:tiles-extras:3.0.1") { + exclude group: "org.slf4j", module: "jcl-over-slf4j" + } optional("org.apache.tiles:tiles-el:3.0.1") { exclude group: "org.slf4j", module: "jcl-over-slf4j" } @@ -640,10 +643,22 @@ project("spring-webmvc-portlet") { project("spring-test") { description = "Spring TestContext Framework" - test { - useJUnit() + + task testNG(type: Test) { useTestNG() + // "TestCase" classes are run by other test classes, not the build. + exclude "**/*TestCase.class" + // Generate TestNG reports alongside JUnit reports. + testReport true } + + test { + dependsOn testNG + useJUnit() + // "TestCase" classes are run by other test classes, not the build. + exclude(["**/*TestCase.class", "**/*TestSuite.class"]) + } + dependencies { compile(project(":spring-core")) optional(project(":spring-beans")) @@ -674,8 +689,8 @@ project("spring-test-mvc") { description = "Spring Test MVC Framework" merge.into = project(":spring-test") dependencies { - optional(project(":spring-context")) - compile(project(":spring-webmvc")) + provided(project(":spring-context")) + provided(project(":spring-webmvc")) provided("javax.servlet:javax.servlet-api:3.0.1") optional("org.hamcrest:hamcrest-core:1.3") optional("com.jayway.jsonpath:json-path:0.8.1") @@ -752,6 +767,7 @@ configure(rootProject) { apply plugin: "docbook-reference" apply plugin: "groovy" + apply plugin: "detect-split-packages" apply from: "${gradleScriptDir}/jdiff.gradle" reference { @@ -759,7 +775,11 @@ configure(rootProject) { pdfFilename = "spring-framework-reference.pdf" } - // don"t publish the default jar for the root project + detectSplitPackages { + projectsToScan -= project(":spring-instrument-tomcat") + } + + // don't publish the default jar for the root project configurations.archives.artifacts.clear() dependencies { // for integration tests @@ -959,6 +979,7 @@ configure(rootProject) { "set GRADLE_OPTS=$gradleBatOpts %GRADLE_OPTS%\nset DEFAULT_JVM_OPTS=") } } + } /* diff --git a/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy b/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy new file mode 100644 index 00000000000..ddafa956ef7 --- /dev/null +++ b/buildSrc/src/main/groovy/org/springframework/build/gradle/DetectSplitPackagesPlugin.groovy @@ -0,0 +1,158 @@ +/* + * Copyright 2002-2013 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.build.gradle + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + + +/** + * Gradle plugin that detects identically named, non-empty packages split across multiple + * subprojects, e.g. "org.springframework.context.annotation" existing in both spring-core + * and spring-aspects. Adds a 'detectSplitPackages' task to the current project's task + * collection. If the project already contains a 'check' task (i.e. is a typical Gradle + * project with the "java" plugin applied), the 'check' task will be updated to depend on + * the execution of 'detectSplitPackages'. + * + * By default, all subprojects will be scanned. Use the 'projectsToScan' task property to + * modify this value. Example usage: + * + * apply plugin: 'detect-split-packages // typically applied to root project + * + * detectSplitPackages { + * packagesToScan -= project(":spring-xyz") // scan every project but spring-xyz + * } + * + * @author Rob Winch + * @author Glyn Normington + * @author Chris Beams + */ +public class DetectSplitPackagesPlugin implements Plugin { + public void apply(Project project) { + def tasks = project.tasks + Task detectSplitPackages = tasks.add("detectSplitPackages", DetectSplitPackagesTask.class) + if (tasks.asMap.containsKey("check")) { + tasks.getByName("check").dependsOn detectSplitPackages + } + } +} + +public class DetectSplitPackagesTask extends DefaultTask { + + private static final String JAVA_FILE_SUFFIX = ".java" + private static final String PACKAGE_SEPARATOR = "." + private static final String HIDDEN_DIRECTORY_PREFIX = "." + + @Input + Set projectsToScan = project.subprojects + + public DetectSplitPackagesTask() { + this.group = "Verification" + this.description = "Detects packages split across two or more subprojects." + } + + @TaskAction + public void detectSplitPackages() { + def splitPackages = doDetectSplitPackages() + if (!splitPackages.isEmpty()) { + def message = "The following split package(s) have been detected:\n" + splitPackages.each { pkg, mod -> + message += " - ${pkg} (split across ${mod[0].name} and ${mod[1].name})\n" + } + throw new GradleException(message) + } + } + + private Map> doDetectSplitPackages() { + def splitPackages = [:] + def mergedProjects = findMergedProjects() + def packagesByProject = mapPackagesByProject() + + def projects = packagesByProject.keySet().toArray() + def nProjects = projects.length + + for (int i = 0; i < nProjects - 1; i++) { + for (int j = i + 1; j < nProjects - 1; j++) { + def prj_i = projects[i] + def prj_j = projects[j] + + def pkgs_i = new HashSet(packagesByProject.get(prj_i)) + def pkgs_j = packagesByProject.get(prj_j) + pkgs_i.retainAll(pkgs_j) + + if (!pkgs_i.isEmpty() + && mergedProjects.get(prj_i) != prj_j + && mergedProjects.get(prj_j) != prj_i) { + pkgs_i.each { pkg -> + def readablePkg = pkg.substring(1).replaceAll(File.separator, PACKAGE_SEPARATOR) + splitPackages[readablePkg] = [prj_i, prj_j] + } + } + } + } + return splitPackages; + } + + private Map> mapPackagesByProject() { + def packagesByProject = [:] + this.projectsToScan.each { Project p -> + def packages = new HashSet() + p.sourceSets.main.java.srcDirs.each { File dir -> + findPackages(packages, dir, "") + } + if (!packages.isEmpty()) { + packagesByProject.put(p, packages) + } + } + return packagesByProject; + } + + private Map findMergedProjects() { + def mergedProjects = [:] + this.projectsToScan.findAll { p -> + p.plugins.findPlugin(MergePlugin) + }.findAll { p -> + p.merge.into + }.each { p -> + mergedProjects.put(p, p.merge.into) + } + return mergedProjects + } + + private static void findPackages(Set packages, File dir, String packagePath) { + def scanDir = new File(dir, packagePath) + def File[] javaFiles = scanDir.listFiles({ file -> + !file.isDirectory() && file.name.endsWith(JAVA_FILE_SUFFIX) + } as FileFilter) + + if (javaFiles != null && javaFiles.length != 0) { + packages.add(packagePath) + } + + scanDir.listFiles({ File file -> + file.isDirectory() && !file.name.startsWith(HIDDEN_DIRECTORY_PREFIX) + } as FileFilter).each { File subDir -> + findPackages(packages, dir, packagePath + File.separator + subDir.name) + } + } +} + diff --git a/buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties b/buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties new file mode 100644 index 00000000000..10b1e4e981d --- /dev/null +++ b/buildSrc/src/main/resources/META-INF/gradle-plugins/detect-split-packages.properties @@ -0,0 +1 @@ +implementation-class=org.springframework.build.gradle.DetectSplitPackagesPlugin diff --git a/gradle.properties b/gradle.properties index 89015ba3ec8..e69ccb3080b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=3.2.1.BUILD-SNAPSHOT +version=3.2.2.BUILD-SNAPSHOT diff --git a/gradle/ide.gradle b/gradle/ide.gradle index e1bb117641e..3f73282254a 100644 --- a/gradle/ide.gradle +++ b/gradle/ide.gradle @@ -1,7 +1,12 @@ import org.gradle.plugins.ide.eclipse.model.ProjectDependency +import org.gradle.plugins.ide.eclipse.model.SourceFolder + +apply plugin: "propdeps-eclipse" +apply plugin: "propdeps-idea" + +// Replace classpath entries with project dependencies (GRADLE-1116) eclipse.classpath.file.whenMerged { classpath -> - // GRADLE-1116 def regexp = /.*?\/([^\/]+)\/build\/[^\/]+\/(?:main|test)/ // only match those that end in main or test (avoids removing necessary entries like build/classes/jaxb) def projectOutputDependencies = classpath.entries.findAll { entry -> entry.path =~ regexp } projectOutputDependencies.each { entry -> @@ -19,3 +24,76 @@ eclipse.classpath.file.whenMerged { classpath -> } classpath.entries.removeAll { entry -> (entry.path =~ /(?!.*?repack.*\.jar).*?\/([^\/]+)\/build\/libs\/[^\/]+\.jar/) } } + + +// Use separate main/test outputs (prevents WTP from packaging test classes) +eclipse.classpath.defaultOutputDir = file(project.name+"/bin/eclipse") +eclipse.classpath.file.beforeMerged { classpath -> + classpath.entries.findAll{ it instanceof SourceFolder }.each { + if(it.output.startsWith("bin/")) { + it.output = null + } + } +} +eclipse.classpath.file.whenMerged { classpath -> + classpath.entries.findAll{ it instanceof SourceFolder }.each { + it.output = "bin/" + it.path.split("/")[1] + } +} + +// Allow projects to be used as WPT modules +eclipse.project.natures "org.eclipse.wst.common.project.facet.core.nature" + + +// Include project specific settings +task eclipseSettings(type: Copy) { + from rootProject.files( + "src/eclipse/org.eclipse.jdt.ui.prefs", + "src/eclipse/org.eclipse.wst.common.project.facet.core.xml") + into project.file('.settings/') + outputs.upToDateWhen { false } +} + +task eclipseWstComponent(type: Copy) { + from rootProject.files( + "src/eclipse/org.eclipse.wst.common.component") + into project.file('.settings/') + expand(deployname: project.name) + outputs.upToDateWhen { false } +} + +task eclipseJdtPrepare(type: Copy) { + from rootProject.file("src/eclipse/org.eclipse.jdt.core.prefs") + into project.file(".settings/") + outputs.upToDateWhen { false } +} + +task cleanEclipseJdtUi(type: Delete) { + delete project.file(".settings/org.eclipse.jdt.ui.prefs") + delete project.file("org.eclipse.jdt.core.prefs") + delete project.file(".settings/org.eclipse.wst.common.component") + delete project.file(".settings/org.eclipse.wst.common.project.facet.core.xml") +} + +tasks["eclipseJdt"].dependsOn(eclipseJdtPrepare) +tasks["cleanEclipse"].dependsOn(cleanEclipseJdtUi) +tasks["eclipse"].dependsOn(eclipseSettings, eclipseWstComponent) + + +// Filter 'build' folder + +eclipse.project.file.withXml { + def node = it.asNode() + + def filteredResources = node.get("filteredResources") + if(filteredResources) { + node.remove(filteredResources) + } + def filterNode = node.appendNode("filteredResources").appendNode("filter") + filterNode.appendNode("id", "1359048889071") + filterNode.appendNode("name", "") + filterNode.appendNode("type", "30") + def matcherNode = filterNode.appendNode("matcher") + matcherNode.appendNode("id", "org.eclipse.ui.ide.multiFilter") + matcherNode.appendNode("arguments", "1.0-projectRelativePath-matches-false-false-build") +} diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java index 9e6baee9327..5d76f6c8a5e 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/AbstractAdvisingBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyConfig */ private int order = Ordered.LOWEST_PRECEDENCE; - private final Map eligibleBeans = new ConcurrentHashMap(64); + private final Map eligibleBeans = new ConcurrentHashMap(64); public void setBeanClassLoader(ClassLoader beanClassLoader) { @@ -94,19 +94,21 @@ public abstract class AbstractAdvisingBeanPostProcessor extends ProxyConfig /** * Check whether the given bean is eligible for advising with this * post-processor's {@link Advisor}. - *

Implements caching of {@code canApply} results per bean name. + *

Implements caching of {@code canApply} results per bean target class. + * Can be overridden e.g. to specifically exclude certain beans by name. * @param bean the bean instance * @param beanName the name of the bean + * @see AopUtils#getTargetClass(Object) * @see AopUtils#canApply(Advisor, Class) */ protected boolean isEligible(Object bean, String beanName) { - Boolean eligible = this.eligibleBeans.get(beanName); + Class targetClass = AopUtils.getTargetClass(bean); + Boolean eligible = this.eligibleBeans.get(targetClass); if (eligible != null) { return eligible; } - Class targetClass = AopUtils.getTargetClass(bean); eligible = AopUtils.canApply(this.advisor, targetClass); - this.eligibleBeans.put(beanName, eligible); + this.eligibleBeans.put(targetClass, eligible); return eligible; } diff --git a/spring-aspects/aspects.gradle b/spring-aspects/aspects.gradle index 0432414467b..81728303163 100644 --- a/spring-aspects/aspects.gradle +++ b/spring-aspects/aspects.gradle @@ -21,6 +21,9 @@ task compileJava(overwrite: true) { inputs.files(project.sourceSets.main.allSource + project.sourceSets.main.compileClasspath) outputs.dir outputDir + ext.sourceCompatibility = project(":spring-core").compileJava.sourceCompatibility + ext.targetCompatibility = project(":spring-core").compileJava.targetCompatibility + doLast{ ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath) @@ -30,7 +33,7 @@ task compileJava(overwrite: true) { destDir: outputDir.absolutePath, aspectPath: configurations.aspects.asPath, inpath: configurations.ajInpath.asPath, - sourceRootCopyFilter: "**/*.java", + sourceRootCopyFilter: "**/*.java,**/*.aj", classpath: (sourceSets.main.runtimeClasspath + configurations.rt).asPath) { sourceroots { sourceSets.main.java.srcDirs.each { @@ -51,6 +54,9 @@ task compileTestJava(overwrite: true) { inputs.files(project.sourceSets.test.allSource + project.sourceSets.test.compileClasspath) outputs.dir outputDir + ext.sourceCompatibility = project(":spring-core").compileTestJava.sourceCompatibility + ext.targetCompatibility = project(":spring-core").compileTestJava.targetCompatibility + doLast{ ant.taskdef(resource: "org/aspectj/tools/ant/taskdefs/aspectjTaskdefs.properties", classpath: configurations.ajc.asPath) diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj index 760cea9dc1e..85342e838b8 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractBeanConfigurerAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package org.springframework.beans.factory.aspectj; import org.aspectj.lang.annotation.SuppressAjWarnings; @@ -23,12 +23,12 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * Abstract superaspect for AspectJ aspects that can perform Dependency * Injection on objects, however they may be created. Define the beanCreation() * pointcut in subaspects. - * + * *

Subaspects may also need a metadata resolution strategy, in the - * {@code BeanWiringInfoResolver} interface. The default implementation + * BeanWiringInfoResolver interface. The default implementation * looks for a bean with the same name as the FQN. This is the default name * of a bean in a Spring container if the id value is not supplied explicitly. - * + * * @author Rob Harrop * @author Rod Johnson * @author Adrian Colyer @@ -62,7 +62,7 @@ public abstract aspect AbstractBeanConfigurerAspect extends BeanConfigurerSuppor /** * The initialization of a new object. - * + * *

WARNING: Although this pointcut is non-abstract for backwards * compatibility reasons, it is meant to be overridden to select * initialization of any configurable bean. diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj index 390e1dc989c..23b012ecc75 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractDependencyInjectionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +21,7 @@ import org.aspectj.lang.annotation.SuppressAjWarnings; /** * Abstract base aspect that can perform Dependency * Injection on objects, however they may be created. - * + * * @author Ramnivas Laddad * @since 2.5.2 */ @@ -29,26 +29,26 @@ public abstract aspect AbstractDependencyInjectionAspect { /** * Select construction join points for objects to inject dependencies */ - public abstract pointcut beanConstruction(Object bean); + public abstract pointcut beanConstruction(Object bean); /** * Select deserialization join points for objects to inject dependencies */ public abstract pointcut beanDeserialization(Object bean); - + /** * Select join points in a configurable bean */ public abstract pointcut inConfigurableBean(); - + /** * Select join points in beans to be configured prior to construction? * By default, use post-construction injection matching the default in the Configurable annotation. */ public pointcut preConstructionConfiguration() : if(false); - + /** - * Select the most-specific initialization join point + * Select the most-specific initialization join point * (most concrete class) for the initialization of an instance. */ public pointcut mostSpecificSubTypeConstruction() : @@ -58,25 +58,25 @@ public abstract aspect AbstractDependencyInjectionAspect { * Select least specific super type that is marked for DI (so that injection occurs only once with pre-construction inejection */ public abstract pointcut leastSpecificSuperTypeConstruction(); - + /** * Configure the bean */ public abstract void configureBean(Object bean); - - private pointcut preConstructionCondition() : + + private pointcut preConstructionCondition() : leastSpecificSuperTypeConstruction() && preConstructionConfiguration(); - + private pointcut postConstructionCondition() : mostSpecificSubTypeConstruction() && !preConstructionConfiguration(); - + /** * Pre-construction configuration. */ @SuppressAjWarnings("adviceDidNotMatch") - before(Object bean) : - beanConstruction(bean) && preConstructionCondition() && inConfigurableBean() { + before(Object bean) : + beanConstruction(bean) && preConstructionCondition() && inConfigurableBean() { configureBean(bean); } @@ -84,18 +84,18 @@ public abstract aspect AbstractDependencyInjectionAspect { * Post-construction configuration. */ @SuppressAjWarnings("adviceDidNotMatch") - after(Object bean) returning : + after(Object bean) returning : beanConstruction(bean) && postConstructionCondition() && inConfigurableBean() { configureBean(bean); } - + /** * Post-deserialization configuration. */ @SuppressAjWarnings("adviceDidNotMatch") - after(Object bean) returning : + after(Object bean) returning : beanDeserialization(bean) && inConfigurableBean() { configureBean(bean); } - + } diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj index 5cd0140d464..8e8b634ef0f 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AbstractInterfaceDrivenDependencyInjectionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,36 +26,36 @@ import java.io.Serializable; * upon deserialization. Subaspects need to simply provide definition for the configureBean() method. This * method may be implemented without relying on Spring container if so desired. *

- *

+ *

* There are two cases that needs to be handled: *

    - *
  1. Normal object creation via the '{@code new}' operator: this is - * taken care of by advising {@code initialization()} join points.
  2. + *
  3. Normal object creation via the 'new' operator: this is + * taken care of by advising initialization() join points.
  4. *
  5. Object creation through deserialization: since no constructor is * invoked during deserialization, the aspect needs to advise a method that a * deserialization mechanism is going to invoke. Ideally, we should not * require user classes to implement any specific method. This implies that * we need to introduce the chosen method. We should also handle the cases * where the chosen method is already implemented in classes (in which case, - * the user's implementation for that method should take precedence over the + * the user's implementation for that method should take precedence over the * introduced implementation). There are a few choices for the chosen method: *
      *
    • readObject(ObjectOutputStream): Java requires that the method must be - * {@code private}. Since aspects cannot introduce a private member, + * private

      . Since aspects cannot introduce a private member, * while preserving its name, this option is ruled out.
    • - *
    • readResolve(): Java doesn't pose any restriction on an access specifier. - * Problem solved! There is one (minor) limitation of this approach in - * that if a user class already has this method, that method must be - * {@code public}. However, this shouldn't be a big burden, since - * use cases that need classes to implement readResolve() (custom enums, + *
    • readResolve(): Java doesn't pose any restriction on an access specifier. + * Problem solved! There is one (minor) limitation of this approach in + * that if a user class already has this method, that method must be + * public. However, this shouldn't be a big burden, since + * use cases that need classes to implement readResolve() (custom enums, * for example) are unlikely to be marked as @Configurable, and - * in any case asking to make that method {@code public} should not + * in any case asking to make that method public should not * pose any undue burden.
    • *
    - * The minor collaboration needed by user classes (i.e., that the - * implementation of {@code readResolve()}, if any, must be - * {@code public}) can be lifted as well if we were to use an - * experimental feature in AspectJ - the {@code hasmethod()} PCD.
  6. + * The minor collaboration needed by user classes (i.e., that the + * implementation of readResolve(), if any, must be + * public) can be lifted as well if we were to use an + * experimental feature in AspectJ - the hasmethod() PCD. *
*

@@ -63,7 +63,7 @@ import java.io.Serializable; * is to use a 'declare parents' statement another aspect (a subaspect of this aspect would be a logical choice) * that declares the classes that need to be configured by supplying the {@link ConfigurableObject} interface. *

- * + * * @author Ramnivas Laddad * @since 2.5.2 */ @@ -71,8 +71,8 @@ public abstract aspect AbstractInterfaceDrivenDependencyInjectionAspect extends /** * Select initialization join point as object construction */ - public pointcut beanConstruction(Object bean) : - initialization(ConfigurableObject+.new(..)) && this(bean); + public pointcut beanConstruction(Object bean) : + initialization(ConfigurableObject+.new(..)) && this(bean); /** * Select deserialization join point made available through ITDs for ConfigurableDeserializationSupport @@ -80,40 +80,40 @@ public abstract aspect AbstractInterfaceDrivenDependencyInjectionAspect extends public pointcut beanDeserialization(Object bean) : execution(Object ConfigurableDeserializationSupport+.readResolve()) && this(bean); - + public pointcut leastSpecificSuperTypeConstruction() : initialization(ConfigurableObject.new(..)); - - - + + + // Implementation to support re-injecting dependencies once an object is deserialized /** - * Declare any class implementing Serializable and ConfigurableObject as also implementing - * ConfigurableDeserializationSupport. This allows us to introduce the readResolve() + * Declare any class implementing Serializable and ConfigurableObject as also implementing + * ConfigurableDeserializationSupport. This allows us to introduce the readResolve() * method and select it with the beanDeserialization() pointcut. - * + * *

Here is an improved version that uses the hasmethod() pointcut and lifts * even the minor requirement on user classes: * *

declare parents: ConfigurableObject+ Serializable+
-	 *		            && !hasmethod(Object readResolve() throws ObjectStreamException)
+	 *		            && !hasmethod(Object readResolve() throws ObjectStreamException) 
 	 *		            implements ConfigurableDeserializationSupport;
 	 * 
*/ - declare parents: + declare parents: ConfigurableObject+ && Serializable+ implements ConfigurableDeserializationSupport; - + /** - * A marker interface to which the {@code readResolve()} is introduced. + * A marker interface to which the readResolve() is introduced. */ static interface ConfigurableDeserializationSupport extends Serializable { } - + /** - * Introduce the {@code readResolve()} method so that we can advise its + * Introduce the readResolve() method so that we can advise its * execution to configure the object. - * + * *

Note if a method with the same signature already exists in a - * {@code Serializable} class of ConfigurableObject type, + * Serializable class of ConfigurableObject type, * that implementation will take precedence (a good thing, since we are * merely interested in an opportunity to detect deserialization.) */ diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj index 5313df9ed16..4cdc292dbbc 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/AnnotationBeanConfigurerAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +32,7 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * annotation to identify which classes need autowiring. * *

The bean name to look up will be taken from the - * {@code @Configurable} annotation if specified, otherwise the + * @Configurable annotation if specified, otherwise the * default bean name to look up will be the FQN of the class being configured. * * @author Rod Johnson @@ -43,7 +43,7 @@ import org.springframework.beans.factory.wiring.BeanConfigurerSupport; * @see org.springframework.beans.factory.annotation.Configurable * @see org.springframework.beans.factory.annotation.AnnotationBeanWiringInfoResolver */ -public aspect AnnotationBeanConfigurerAspect +public aspect AnnotationBeanConfigurerAspect extends AbstractInterfaceDrivenDependencyInjectionAspect implements BeanFactoryAware, InitializingBean, DisposableBean { @@ -51,7 +51,7 @@ public aspect AnnotationBeanConfigurerAspect public pointcut inConfigurableBean() : @this(Configurable); - public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); + public pointcut preConstructionConfiguration() : preConstructionConfigurationSupport(*); declare parents: @Configurable * implements ConfigurableObject; @@ -80,10 +80,10 @@ public aspect AnnotationBeanConfigurerAspect private pointcut preConstructionConfigurationSupport(Configurable c) : @this(c) && if(c.preConstruction()); /* - * This declaration shouldn't be needed, + * This declaration shouldn't be needed, * except for an AspectJ bug (https://bugs.eclipse.org/bugs/show_bug.cgi?id=214559) */ - declare parents: @Configurable Serializable+ + declare parents: @Configurable Serializable+ implements ConfigurableDeserializationSupport; } diff --git a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj index 351c11d7d9e..03f446ca789 100644 --- a/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/beans/factory/aspectj/GenericInterfaceDrivenDependencyInjectionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,38 +17,38 @@ package org.springframework.beans.factory.aspectj; /** * Generic-based dependency injection aspect. - *

- * This aspect allows users to implement efficient, type-safe dependency injection without + *

+ * This aspect allows users to implement efficient, type-safe dependency injection without * the use of the @Configurable annotation. - * - * The subaspect of this aspect doesn't need to include any AOP constructs. - * For example, here is a subaspect that configures the {@code PricingStrategyClient} objects. + * + * The subaspect of this aspect doesn't need to include any AOP constructs. + * For example, here is a subaspect that configures the PricingStrategyClient objects. *

- * aspect PricingStrategyDependencyInjectionAspect
+ * aspect PricingStrategyDependencyInjectionAspect 
  *        extends GenericInterfaceDrivenDependencyInjectionAspect {
  *     private PricingStrategy pricingStrategy;
- *
- *     public void configure(PricingStrategyClient bean) {
- *         bean.setPricingStrategy(pricingStrategy);
- *     }
- *
- *     public void setPricingStrategy(PricingStrategy pricingStrategy) {
- *         this.pricingStrategy = pricingStrategy;
+ *     
+ *     public void configure(PricingStrategyClient bean) { 
+ *         bean.setPricingStrategy(pricingStrategy); 
  *     }
+ *     
+ *     public void setPricingStrategy(PricingStrategy pricingStrategy) { 
+ *         this.pricingStrategy = pricingStrategy; 
+ *     } 
  * }
  * 
* @author Ramnivas Laddad * @since 3.0.0 */ public abstract aspect GenericInterfaceDrivenDependencyInjectionAspect extends AbstractInterfaceDrivenDependencyInjectionAspect { - declare parents: I implements ConfigurableObject; - + declare parents: I implements ConfigurableObject; + public pointcut inConfigurableBean() : within(I+); - + public final void configureBean(Object bean) { configure((I)bean); } - - // Unfortunately, erasure used with generics won't allow to use the same named method + + // Unfortunately, erasure used with generics won't allow to use the same named method protected abstract void configure(I bean); } diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj index a9a6da82e98..dbe15b8fb3a 100644 --- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AbstractCacheAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ public abstract aspect AbstractCacheAspect extends CacheAspectSupport { } }; - return execute(aspectJInvoker, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs()); + return execute(aspectJInvoker, thisJoinPoint.getTarget(), method, thisJoinPoint.getArgs()); } /** diff --git a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj index be3957a3a08..5418488b532 100644 --- a/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/cache/aspectj/AnnotationCacheAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj index e373607d558..7ab3fcd0c34 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AbstractMethodMockingControl.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 +23,9 @@ import java.util.List; /** * Abstract aspect to enable mocking of methods picked out by a pointcut. * Sub-aspects must define the mockStaticsTestMethod() pointcut to - * indicate call stacks when mocking should be triggered, and the + * indicate call stacks when mocking should be triggered, and the * methodToMock() pointcut to pick out a method invocations to mock. - * + * * @author Rod Johnson * @author Ramnivas Laddad */ @@ -42,7 +42,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth // Represents a list of expected calls to static entity methods // Public to allow inserted code to access: is this normal?? public class Expectations { - + // Represents an expected call to a static entity method private class Call { private final String signature; @@ -50,21 +50,21 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth private Object responseObject; // return value or throwable private CallResponse responseType = CallResponse.nothing; - + public Call(String name, Object[] args) { this.signature = name; this.args = args; } - + public boolean hasResponseSpecified() { return responseType != CallResponse.nothing; } - + public void setReturnVal(Object retVal) { this.responseObject = retVal; responseType = CallResponse.return_; } - + public void setThrow(Throwable throwable) { this.responseObject = throwable; responseType = CallResponse.throw_; @@ -89,7 +89,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth } } } - + private List calls = new LinkedList(); // Calls already verified @@ -101,12 +101,12 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth + " calls, received " + verified); } } - + /** * Validate the call and provide the expected return value * @param lastSig * @param args - * @return the return value + * @return */ public Object respond(String lastSig, Object[] args) { Call call = nextCall(); @@ -114,7 +114,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth if (responseType == CallResponse.return_) { return call.returnValue(lastSig, args); } else if(responseType == CallResponse.throw_) { - return call.throwException(lastSig, args); + return (RuntimeException)call.throwException(lastSig, args); } else if(responseType == CallResponse.nothing) { // do nothing } @@ -175,7 +175,7 @@ public abstract aspect AbstractMethodMockingControl percflow(mockStaticsTestMeth return expectations.respond(thisJoinPointStaticPart.toLongString(), thisJoinPoint.getArgs()); } } - + public void expectReturnInternal(Object retVal) { if (!recording) { throw new IllegalStateException("Not recording: Cannot set return value"); diff --git a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj index 1c50c64bbe6..031978ca91f 100644 --- a/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj +++ b/spring-aspects/src/main/java/org/springframework/mock/staticmock/AnnotationDrivenStaticEntityMockingControl.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,16 +18,16 @@ package org.springframework.mock.staticmock; /** * Annotation-based aspect to use in test build to enable mocking static methods - * on JPA-annotated {@code @Entity} classes, as used by Roo for finders. + * on JPA-annotated @Entity classes, as used by Roo for finders. * - *

Mocking will occur in the call stack of any method in a class (typically a test class) - * that is annotated with the @MockStaticEntityMethods annotation. + *

Mocking will occur in the call stack of any method in a class (typically a test class) + * that is annotated with the @MockStaticEntityMethods annotation. * *

Also provides static methods to simplify the programming model for * entering playback mode and setting expected return values. * *

Usage: - *

    + *
      *
    1. Annotate a test class with @MockStaticEntityMethods. *
    2. In each test method, AnnotationDrivenStaticEntityMockingControl will begin in recording mode. * Invoke static methods on Entity classes, with each recording-mode invocation @@ -37,20 +37,20 @@ package org.springframework.mock.staticmock; *
    3. Call the code you wish to test that uses the static methods. Verification will * occur automatically. *
    - * + * * @author Rod Johnson * @author Ramnivas Laddad * @see MockStaticEntityMethods */ public aspect AnnotationDrivenStaticEntityMockingControl extends AbstractMethodMockingControl { - + /** * Stop recording mock calls and enter playback state */ public static void playback() { AnnotationDrivenStaticEntityMockingControl.aspectOf().playbackInternal(); } - + public static void expectReturn(Object retVal) { AnnotationDrivenStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal); } diff --git a/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj b/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj index 3594725aaee..6ff44249d08 100644 --- a/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/orm/jpa/aspectj/JpaExceptionTranslatorAspect.aj @@ -1,19 +1,3 @@ -/* - * Copyright 2002-2012 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.orm.jpa.aspectj; import javax.persistence.EntityManager; @@ -25,14 +9,14 @@ import org.springframework.dao.DataAccessException; import org.springframework.orm.jpa.EntityManagerFactoryUtils; public aspect JpaExceptionTranslatorAspect { - pointcut entityManagerCall(): call(* EntityManager.*(..)) || call(* EntityManagerFactory.*(..)) || call(* EntityTransaction.*(..)) || call(* Query.*(..)); - - after() throwing(RuntimeException re): entityManagerCall() { - DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(re); - if (dex != null) { - throw dex; - } else { - throw re; - } - } -} + pointcut entityManagerCall(): call(* EntityManager.*(..)) || call(* EntityManagerFactory.*(..)) || call(* EntityTransaction.*(..)) || call(* Query.*(..)); + + after() throwing(RuntimeException re): entityManagerCall() { + DataAccessException dex = EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(re); + if (dex != null) { + throw dex; + } else { + throw re; + } + } +} \ No newline at end of file diff --git a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj index a64def7ed11..6cb3ad60e39 100644 --- a/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/scheduling/aspectj/AbstractAsyncExecutionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +28,7 @@ import org.springframework.core.task.AsyncTaskExecutor; /** * Abstract aspect that routes selected methods asynchronously. * - *

    This aspect needs to be injected with an implementation of + *

    This aspect needs to be injected with an implementation of * {@link Executor} to activate it for a specific thread pool. * Otherwise it will simply delegate all calls synchronously. * diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj index ed0956ef9fe..3ce264d9e1a 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AbstractTransactionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 @@ import org.springframework.transaction.interceptor.TransactionAttributeSource; /** * Abstract superaspect for AspectJ transaction aspects. Concrete - * subaspects will implement the {@code transactionalMethodExecution()} + * subaspects will implement the transactionalMethodExecution() * pointcut using a strategy such as Java 5 annotations. * *

    Suitable for use inside or outside the Spring IoC container. @@ -66,7 +66,7 @@ public abstract aspect AbstractTransactionAspect extends TransactionAspectSuppor @SuppressAjWarnings("adviceDidNotMatch") after(Object txObject) throwing(Throwable t) : transactionalMethodExecution(txObject) { try { - completeTransactionAfterThrowing(TransactionAspectSupport.currentTransactionInfo(), t); + completeTransactionAfterThrowing(TransactionAspectSupport.currentTransactionInfo(), t); } catch (Throwable t2) { logger.error("Failed to close transaction after throwing in a transactional method", t2); diff --git a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj index 1fe8de9030b..2ea8f9e3f58 100644 --- a/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj +++ b/spring-aspects/src/main/java/org/springframework/transaction/aspectj/AnnotationTransactionAspect.aj @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,17 +21,17 @@ import org.springframework.transaction.annotation.Transactional; /** * Concrete AspectJ transaction aspect using Spring's @Transactional annotation. - * + * *

    When using this aspect, you must annotate the implementation class * (and/or methods within that class), not the interface (if any) that - * the class implements. AspectJ follows Java's rule that annotations on + * the class implements. AspectJ follows Java's rule that annotations on * interfaces are not inherited. * *

    An @Transactional annotation on a class specifies the default transaction * semantics for the execution of any public operation in the class. * *

    An @Transactional annotation on a method within the class overrides the - * default transaction semantics given by the class annotation (if present). + * default transaction semantics given by the class annotation (if present). * Any method may be annotated (regardless of visibility). * Annotating non-public methods directly is the only way * to get transaction demarcation for the execution of such operations. @@ -57,7 +57,7 @@ public aspect AnnotationTransactionAspect extends AbstractTransactionAspect { execution(public * ((@Transactional *)+).*(..)) && within(@Transactional *); /** - * Matches the execution of any method with the + * Matches the execution of any method with the * Transactional annotation. */ private pointcut executionOfTransactionalMethod() : @@ -66,7 +66,7 @@ public aspect AnnotationTransactionAspect extends AbstractTransactionAspect { /** * Definition of pointcut from super aspect - matched join points * will have Spring transaction management applied. - */ + */ protected pointcut transactionalMethodExecution(Object txObject) : (executionOfAnyPublicMethodInAtTransactionalType() || executionOfTransactionalMethod() ) diff --git a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java index 47ff208dd4e..7c8cd51cece 100644 --- a/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java +++ b/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -107,9 +107,6 @@ public class CachedIntrospectionResults { * @param classLoader the ClassLoader to clear the cache for */ public static void clearClassLoader(ClassLoader classLoader) { - if (classLoader == null) { - return; - } synchronized (classCache) { for (Iterator it = classCache.keySet().iterator(); it.hasNext();) { Class beanClass = it.next(); @@ -199,12 +196,12 @@ public class CachedIntrospectionResults { * @param parent the parent ClassLoader to check for */ private static boolean isUnderneathClassLoader(ClassLoader candidate, ClassLoader parent) { - if (candidate == null) { - return false; - } if (candidate == parent) { return true; } + if (candidate == null) { + return false; + } ClassLoader classLoaderToCheck = candidate; while (classLoaderToCheck != null) { classLoaderToCheck = classLoaderToCheck.getParent(); diff --git a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java index 54467cd3ea8..c3609c10c22 100644 --- a/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java +++ b/spring-beans/src/main/java/org/springframework/beans/ExtendedBeanInfo.java @@ -31,6 +31,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.List; @@ -116,6 +117,14 @@ class ExtendedBeanInfo implements BeanInfo { matches.add(method); } } + // sort non-void returning write methods to guard against the ill effects of + // non-deterministic sorting of methods returned from Class#getDeclaredMethods + // under JDK 7. See http://bugs.sun.com/view_bug.do?bug_id=7023180 + Collections.sort(matches, new Comparator() { + public int compare(Method m1, Method m2) { + return m2.toString().compareTo(m1.toString()); + } + }); return matches; } @@ -264,7 +273,7 @@ class SimpleNonIndexedPropertyDescriptor extends PropertyDescriptor { public SimpleNonIndexedPropertyDescriptor(String propertyName, Method readMethod, Method writeMethod) throws IntrospectionException { - super(propertyName, readMethod, writeMethod); + super(propertyName, null, null); this.setReadMethod(readMethod); this.setWriteMethod(writeMethod); this.propertyType = findPropertyType(readMethod, writeMethod); @@ -353,7 +362,7 @@ class SimpleIndexedPropertyDescriptor extends IndexedPropertyDescriptor { Method indexedReadMethod, Method indexedWriteMethod) throws IntrospectionException { - super(propertyName, readMethod, writeMethod, indexedReadMethod, indexedWriteMethod); + super(propertyName, null, null, null, null); this.setReadMethod(readMethod); this.setWriteMethod(writeMethod); this.propertyType = findPropertyType(readMethod, writeMethod); diff --git a/spring-beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java b/spring-beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java index 1e074dcc338..8ab240836bf 100644 --- a/spring-beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java +++ b/spring-beans/src/main/java/org/springframework/beans/SimpleTypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 @@ package org.springframework.beans; * algorithm (including delegation to {@link java.beans.PropertyEditor} and * {@link org.springframework.core.convert.ConversionService}) underneath. * + *

    Note: Due to its reliance on {@link java.beans.PropertyEditor PropertyEditors}, + * SimpleTypeConverter is not thread-safe. Use a separate instance for each thread. + * * @author Juergen Hoeller * @since 2.0 * @see BeanWrapperImpl diff --git a/spring-beans/src/main/java/org/springframework/beans/TypeConverter.java b/spring-beans/src/main/java/org/springframework/beans/TypeConverter.java index 2efc1edf737..6d69a63ce69 100644 --- a/spring-beans/src/main/java/org/springframework/beans/TypeConverter.java +++ b/spring-beans/src/main/java/org/springframework/beans/TypeConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,10 @@ import org.springframework.core.MethodParameter; * Interface that defines type conversion methods. Typically (but not necessarily) * implemented in conjunction with the {@link PropertyEditorRegistry} interface. * + *

    Note: Since TypeConverter implementations are typically based on + * {@link java.beans.PropertyEditor PropertyEditors} which aren't thread-safe, + * TypeConverters themselves are not to be considered as thread-safe either. + * * @author Juergen Hoeller * @since 2.0 * @see SimpleTypeConverter diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java index 2d18e67b1ac..8c73a16fd7a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,7 +143,7 @@ public interface BeanFactory { * is {@code Object.class}, this method will succeed whatever the class of the * returned instance. * @return an instance of the bean - * @throws NoSuchBeanDefinitionException if there's no such bean definition + * @throws NoSuchBeanDefinitionException if there is no such bean definition * @throws BeanNotOfRequiredTypeException if the bean is not of the required type * @throws BeansException if the bean could not be created */ @@ -158,7 +158,8 @@ public interface BeanFactory { * of the given type. For more extensive retrieval operations across sets of beans, * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}. * @return an instance of the single bean matching the required type - * @throws NoSuchBeanDefinitionException if there is not exactly one matching bean found + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @since 3.0 * @see ListableBeanFactory */ @@ -172,7 +173,7 @@ public interface BeanFactory { * @param args arguments to use if creating a prototype using explicit arguments to a * static factory method. It is invalid to use a non-null args value in any other case. * @return an instance of the bean - * @throws NoSuchBeanDefinitionException if there's no such bean definition + * @throws NoSuchBeanDefinitionException if there is no such bean definition * @throws BeanDefinitionStoreException if arguments have been given but * the affected bean isn't a prototype * @throws BeansException if the bean could not be created diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java index 2a4f577a1c0..657bb1ebd1d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/BeanFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -310,20 +310,15 @@ public abstract class BeanFactoryUtils { * @param lbf the bean factory * @param type type of bean to match * @return the matching bean instance - * @throws NoSuchBeanDefinitionException - * if 0 or more than 1 beans of the given type were found + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created */ public static T beanOfTypeIncludingAncestors(ListableBeanFactory lbf, Class type) throws BeansException { Map beansOfType = beansOfTypeIncludingAncestors(lbf, type); - if (beansOfType.size() == 1) { - return beansOfType.values().iterator().next(); - } - else { - throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); - } + return uniqueBean(type, beansOfType); } /** @@ -351,8 +346,8 @@ public abstract class BeanFactoryUtils { * eagerly initialized to determine their type: So be aware that passing in "true" * for this flag will initialize FactoryBeans and "factory-bean" references. * @return the matching bean instance - * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException - * if 0 or more than 1 beans of the given type were found + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created */ public static T beanOfTypeIncludingAncestors( @@ -360,12 +355,7 @@ public abstract class BeanFactoryUtils { throws BeansException { Map beansOfType = beansOfTypeIncludingAncestors(lbf, type, includeNonSingletons, allowEagerInit); - if (beansOfType.size() == 1) { - return beansOfType.values().iterator().next(); - } - else { - throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); - } + return uniqueBean(type, beansOfType); } /** @@ -380,19 +370,14 @@ public abstract class BeanFactoryUtils { * @param lbf the bean factory * @param type type of bean to match * @return the matching bean instance - * @throws NoSuchBeanDefinitionException - * if 0 or more than 1 beans of the given type were found + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created */ public static T beanOfType(ListableBeanFactory lbf, Class type) throws BeansException { Assert.notNull(lbf, "ListableBeanFactory must not be null"); Map beansOfType = lbf.getBeansOfType(type); - if (beansOfType.size() == 1) { - return beansOfType.values().iterator().next(); - } - else { - throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); - } + return uniqueBean(type, beansOfType); } /** @@ -415,8 +400,8 @@ public abstract class BeanFactoryUtils { * eagerly initialized to determine their type: So be aware that passing in "true" * for this flag will initialize FactoryBeans and "factory-bean" references. * @return the matching bean instance - * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException - * if 0 or more than 1 beans of the given type were found + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found * @throws BeansException if the bean could not be created */ public static T beanOfType( @@ -425,11 +410,27 @@ public abstract class BeanFactoryUtils { Assert.notNull(lbf, "ListableBeanFactory must not be null"); Map beansOfType = lbf.getBeansOfType(type, includeNonSingletons, allowEagerInit); - if (beansOfType.size() == 1) { - return beansOfType.values().iterator().next(); + return uniqueBean(type, beansOfType); + } + + /** + * Extract a unique bean for the given type from the given Map of matching beans. + * @param type type of bean to match + * @param matchingBeans all matching beans found + * @return the unique bean instance + * @throws NoSuchBeanDefinitionException if no bean of the given type was found + * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found + */ + private static T uniqueBean(Class type, Map matchingBeans) { + int nrFound = matchingBeans.size(); + if (nrFound == 1) { + return matchingBeans.values().iterator().next(); + } + else if (nrFound > 1) { + throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } else { - throw new NoSuchBeanDefinitionException(type, "expected single bean but found " + beansOfType.size()); + throw new NoSuchBeanDefinitionException(type); } } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java index 0157d086506..5bd8ea7a9f1 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoSuchBeanDefinitionException.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,11 +20,13 @@ import org.springframework.beans.BeansException; import org.springframework.util.StringUtils; /** - * Exception thrown when a {@code BeanFactory} is asked for a bean - * instance for which it cannot find a definition. + * Exception thrown when a {@code BeanFactory} is asked for a bean instance + * for which it cannot find a definition. * * @author Rod Johnson * @author Juergen Hoeller + * @see BeanFactory#getBean(String) + * @see BeanFactory#getBean(Class) */ @SuppressWarnings("serial") public class NoSuchBeanDefinitionException extends BeansException { @@ -60,7 +62,7 @@ public class NoSuchBeanDefinitionException extends BeansException { * @param type required type of the missing bean */ public NoSuchBeanDefinitionException(Class type) { - super("No unique bean of type [" + type.getName() + "] is defined"); + super("No qualifying bean of type [" + type.getName() + "] is defined"); this.beanType = type; } @@ -70,7 +72,7 @@ public class NoSuchBeanDefinitionException extends BeansException { * @param message detailed message describing the problem */ public NoSuchBeanDefinitionException(Class type, String message) { - super("No unique bean of type [" + type.getName() + "] is defined: " + message); + super("No qualifying bean of type [" + type.getName() + "] is defined: " + message); this.beanType = type; } @@ -81,7 +83,7 @@ public class NoSuchBeanDefinitionException extends BeansException { * @param message detailed message describing the problem */ public NoSuchBeanDefinitionException(Class type, String dependencyDescription, String message) { - super("No matching bean of type [" + type.getName() + "] found for dependency" + + super("No qualifying bean of type [" + type.getName() + "] found for dependency" + (StringUtils.hasLength(dependencyDescription) ? " [" + dependencyDescription + "]" : "") + ": " + message); this.beanType = type; @@ -89,19 +91,26 @@ public class NoSuchBeanDefinitionException extends BeansException { /** - * Return the name of the missing bean, if it was a lookup by name - * that failed. + * Return the name of the missing bean, if it was a lookup by name that failed. */ public String getBeanName() { return this.beanName; } /** - * Return the required type of the missing bean, if it was a lookup - * by type that failed. + * Return the required type of the missing bean, if it was a lookup by type that failed. */ public Class getBeanType() { return this.beanType; } + /** + * Return the number of beans found when only one matching bean was expected. + * For a regular NoSuchBeanDefinitionException, this will always be 0. + * @see NoUniqueBeanDefinitionException + */ + public int getNumberOfBeansFound() { + return 0; + } + } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java new file mode 100644 index 00000000000..7db0bae6e12 --- /dev/null +++ b/spring-beans/src/main/java/org/springframework/beans/factory/NoUniqueBeanDefinitionException.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2013 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.beans.factory; + +import java.util.Arrays; +import java.util.Collection; + +import org.springframework.util.StringUtils; + +/** + * Exception thrown when a {@code BeanFactory} is asked for a bean instance for which + * multiple matching candidates have been found when only one matching bean was expected. + * + * @author Juergen Hoeller + * @since 3.2.1 + * @see BeanFactory#getBean(Class) + */ +@SuppressWarnings("serial") +public class NoUniqueBeanDefinitionException extends NoSuchBeanDefinitionException { + + private int numberOfBeansFound; + + + /** + * Create a new {@code NoUniqueBeanDefinitionException}. + * @param type required type of the non-unique bean + * @param numberOfBeansFound the number of matching beans + * @param message detailed message describing the problem + */ + public NoUniqueBeanDefinitionException(Class type, int numberOfBeansFound, String message) { + super(type, message); + this.numberOfBeansFound = numberOfBeansFound; + } + + /** + * Create a new {@code NoUniqueBeanDefinitionException}. + * @param type required type of the non-unique bean + * @param beanNamesFound the names of all matching beans (as a Collection) + */ + public NoUniqueBeanDefinitionException(Class type, Collection beanNamesFound) { + this(type, beanNamesFound.size(), "expected single matching bean but found " + beanNamesFound.size() + ": " + + StringUtils.collectionToCommaDelimitedString(beanNamesFound)); + } + + /** + * Create a new {@code NoUniqueBeanDefinitionException}. + * @param type required type of the non-unique bean + * @param beanNamesFound the names of all matching beans (as an array) + */ + public NoUniqueBeanDefinitionException(Class type, String... beanNamesFound) { + this(type, Arrays.asList(beanNamesFound)); + } + + + /** + * Return the number of beans found when only one matching bean was expected. + * For a NoUniqueBeanDefinitionException, this will usually be higher than 1. + * @see #getBeanType() + */ + @Override + public int getNumberOfBeansFound() { + return this.numberOfBeansFound; + } + +} diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java b/spring-beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java index 35c3d3f51fa..c2c0a7c6294 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/access/BeanFactoryReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +16,6 @@ package org.springframework.beans.factory.access; -import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.BeanFactory; /** @@ -49,11 +48,10 @@ public interface BeanFactoryReference { *

    In an EJB usage scenario this would normally be called from * {@code ejbRemove()} and {@code ejbPassivate()}. *

    This is safe to call multiple times. - * @throws FatalBeanException if the {@code BeanFactory} cannot be released * @see BeanFactoryLocator * @see org.springframework.context.access.ContextBeanFactoryReference * @see org.springframework.context.ConfigurableApplicationContext#close() */ - void release() throws FatalBeanException; + void release(); } diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java b/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java index e8f2b72b6c7..1ded4154aa2 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/config/ServiceLocatorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import org.springframework.beans.BeansException; import org.springframework.beans.FatalBeanException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.ListableBeanFactory; @@ -364,12 +363,12 @@ public class ServiceLocatorFactoryBean implements FactoryBean, BeanFacto try { String beanName = tryGetBeanName(args); if (StringUtils.hasLength(beanName)) { - // Service locator for a specific bean name. + // Service locator for a specific bean name return beanFactory.getBean(beanName, serviceLocatorMethodReturnType); } else { - // Service locator for a bean type. - return BeanFactoryUtils.beanOfTypeIncludingAncestors(beanFactory, serviceLocatorMethodReturnType); + // Service locator for a bean type + return beanFactory.getBean(serviceLocatorMethodReturnType); } } catch (BeansException ex) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 8db728ae579..6ec60e6e61b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -746,6 +746,9 @@ public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport imp public String resolveEmbeddedValue(String value) { String result = value; for (StringValueResolver resolver : this.embeddedValueResolvers) { + if (result == null) { + return null; + } result = resolver.resolveStringValue(result); } return result; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 824caaaec5e..9e681d0dd69 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -50,6 +50,7 @@ import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.CannotLoadBeanClassException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.ObjectFactory; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.beans.factory.config.BeanDefinition; @@ -89,6 +90,7 @@ import org.springframework.util.StringUtils; * @author Sam Brannen * @author Costin Leau * @author Chris Beams + * @author Phillip Webb * @since 16 April 2001 * @see StaticListableBeanFactory * @see PropertiesBeanDefinitionReader @@ -270,12 +272,28 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (beanNames.length == 1) { return getBean(beanNames[0], requiredType); } - else if (beanNames.length == 0 && getParentBeanFactory() != null) { + else if (beanNames.length > 1) { + T primaryBean = null; + for (String beanName : beanNames) { + T beanInstance = getBean(beanName, requiredType); + if (isPrimary(beanName, beanInstance)) { + if (primaryBean != null) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames.length, + "more than one 'primary' bean found of required type: " + Arrays.asList(beanNames)); + } + primaryBean = beanInstance; + } + } + if (primaryBean != null) { + return primaryBean; + } + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } + else if (getParentBeanFactory() != null) { return getParentBeanFactory().getBean(requiredType); } else { - throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " + - beanNames.length + ": " + StringUtils.arrayToCommaDelimitedString(beanNames)); + throw new NoSuchBeanDefinitionException(requiredType); } } @@ -823,8 +841,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto if (matchingBeans.size() > 1) { String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor); if (primaryBeanName == null) { - throw new NoSuchBeanDefinitionException(type, "expected single matching bean but found " + - matchingBeans.size() + ": " + matchingBeans.keySet()); + throw new NoUniqueBeanDefinitionException(type, matchingBeans.keySet()); } if (autowiredBeanNames != null) { autowiredBeanNames.add(primaryBeanName); @@ -895,7 +912,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto boolean candidateLocal = containsBeanDefinition(candidateBeanName); boolean primaryLocal = containsBeanDefinition(primaryBeanName); if (candidateLocal == primaryLocal) { - throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), + throw new NoUniqueBeanDefinitionException(descriptor.getDependencyType(), candidateBeans.size(), "more than one 'primary' bean found among candidates: " + candidateBeans.keySet()); } else if (candidateLocal && !primaryLocal) { diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java index 990438597f6..6a498344a92 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DisposableBeanAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,6 +61,8 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { private static final String CLOSE_METHOD_NAME = "close"; + private static final String SHUTDOWN_METHOD_NAME = "shutdown"; + private static final Log logger = LogFactory.getLog(DisposableBeanAdapter.class); private static Class closeableInterface; @@ -176,7 +178,12 @@ class DisposableBeanAdapter implements DisposableBean, Runnable, Serializable { return bean.getClass().getMethod(CLOSE_METHOD_NAME).getName(); } catch (NoSuchMethodException ex) { - // no candidate destroy method found + try { + return bean.getClass().getMethod(SHUTDOWN_METHOD_NAME).getName(); + } + catch (NoSuchMethodException ex2) { + // no candidate destroy method found + } } } return null; diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java index 3e8784b1817..4eef1b4751f 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/StaticListableBeanFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 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.beans.factory.BeanNotOfRequiredTypeException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.SmartFactoryBean; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.StringUtils; @@ -117,8 +118,11 @@ public class StaticListableBeanFactory implements ListableBeanFactory { if (beanNames.length == 1) { return getBean(beanNames[0], requiredType); } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(requiredType, beanNames); + } else { - throw new NoSuchBeanDefinitionException(requiredType, "expected single bean but found " + beanNames.length); + throw new NoSuchBeanDefinitionException(requiredType); } } diff --git a/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java b/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java index a4fe92acc96..618ced2e496 100644 --- a/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/CachedIntrospectionResultsTests.java @@ -18,9 +18,8 @@ package org.springframework.beans; import java.beans.BeanInfo; import java.beans.PropertyDescriptor; +import java.util.ArrayList; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; import org.junit.Test; import org.springframework.core.OverridingClassLoader; @@ -37,7 +36,7 @@ import static org.junit.Assert.*; public final class CachedIntrospectionResultsTests { @Test - public void acceptClassLoader() throws Exception { + public void acceptAndClearClassLoader() throws Exception { BeanWrapper bw = new BeanWrapperImpl(TestBean.class); assertTrue(bw.isWritableProperty("name")); assertTrue(bw.isWritableProperty("age")); @@ -57,6 +56,14 @@ public final class CachedIntrospectionResultsTests { assertTrue(CachedIntrospectionResults.classCache.containsKey(TestBean.class)); } + @Test + public void clearClassLoaderForSystemClassLoader() throws Exception { + BeanUtils.getPropertyDescriptors(ArrayList.class); + assertTrue(CachedIntrospectionResults.classCache.containsKey(ArrayList.class)); + CachedIntrospectionResults.clearClassLoader(ArrayList.class.getClassLoader()); + assertFalse(CachedIntrospectionResults.classCache.containsKey(ArrayList.class)); + } + @Test public void shouldUseExtendedBeanInfoWhenApplicable() throws NoSuchMethodException, SecurityException { // given a class with a non-void returning setter method diff --git a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java index aaf6e56092d..ec2ca31e2bb 100644 --- a/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/ExtendedBeanInfoTests.java @@ -23,6 +23,7 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; +import java.math.BigDecimal; import org.junit.Test; @@ -192,7 +193,6 @@ public class ExtendedBeanInfoTests { } } class Child extends Parent { - @Override public Integer getProperty1() { return 2; } @@ -214,7 +214,6 @@ public class ExtendedBeanInfoTests { @Test public void cornerSpr9453() throws IntrospectionException { final class Bean implements Spr9453> { - @Override public Class getProp() { return null; } @@ -583,6 +582,20 @@ public class ExtendedBeanInfoTests { } } + /** + * Prior to SPR-10111 (a follow-up fix for SPR-9702), this method would throw an + * IntrospectionException regarding a "type mismatch between indexed and non-indexed + * methods" intermittently (approximately one out of every four times) under JDK 7 + * due to non-deterministic results from {@link Class#getDeclaredMethods()}. + * See http://bugs.sun.com/view_bug.do?bug_id=7023180 + * @see #cornerSpr9702() + */ + @Test + public void cornerSpr10111() throws Exception { + new ExtendedBeanInfo(Introspector.getBeanInfo(BigDecimal.class)); + } + + @Test public void subclassWriteMethodWithCovariantReturnType() throws IntrospectionException { @SuppressWarnings("unused") class B { @@ -590,9 +603,7 @@ public class ExtendedBeanInfoTests { public Number setFoo(String foo) { return null; } } class C extends B { - @Override public String getFoo() { return null; } - @Override public Integer setFoo(String foo) { return null; } } @@ -695,7 +706,7 @@ public class ExtendedBeanInfoTests { for (PropertyDescriptor pd : ebi.getPropertyDescriptors()) { if (pd.getName().equals("foo")) { - assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", int.class))); + assertThat(pd.getWriteMethod(), is(C.class.getMethod("setFoo", String.class))); return; } } @@ -733,7 +744,7 @@ public class ExtendedBeanInfoTests { assertThat(hasReadMethodForProperty(ebi, "dateFormat"), is(false)); assertThat(hasWriteMethodForProperty(ebi, "dateFormat"), is(true)); assertThat(hasIndexedReadMethodForProperty(ebi, "dateFormat"), is(false)); - assertThat(hasIndexedWriteMethodForProperty(ebi, "dateFormat"), is(true)); + assertThat(hasIndexedWriteMethodForProperty(ebi, "dateFormat"), is(trueUntilJdk17())); } @Test @@ -864,7 +875,6 @@ public class ExtendedBeanInfoTests { } interface TextBookOperations extends BookOperations { - @Override TextBook getBook(); } @@ -874,7 +884,6 @@ public class ExtendedBeanInfoTests { } class LawLibrary extends Library implements TextBookOperations { - @Override public LawBook getBook() { return null; } } @@ -889,7 +898,6 @@ public class ExtendedBeanInfoTests { } class B extends A { - @Override public boolean isTargetMethod() { return false; } diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index acf9f65a05a..fc4ac5d55c6 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -17,6 +17,8 @@ package org.springframework.beans.factory; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -26,6 +28,11 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import java.io.Closeable; import java.lang.reflect.Field; @@ -49,7 +56,9 @@ import javax.security.auth.Subject; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.NotWritablePropertyException; @@ -93,6 +102,7 @@ import org.springframework.tests.sample.beans.SideEffectBean; import org.springframework.tests.sample.beans.TestBean; import org.springframework.tests.sample.beans.factory.DummyFactory; import org.springframework.util.StopWatch; +import org.springframework.util.StringValueResolver; /** * Tests properties population and autowire behavior. @@ -102,11 +112,14 @@ import org.springframework.util.StopWatch; * @author Rick Evans * @author Sam Brannen * @author Chris Beams + * @author Phillip Webb */ public class DefaultListableBeanFactoryTests { private static final Log factoryLog = LogFactory.getLog(DefaultListableBeanFactory.class); + @Rule + public ExpectedException thrown = ExpectedException.none(); @Test public void testUnreferencedSingletonWasInstantiated() { @@ -1270,6 +1283,12 @@ public class DefaultListableBeanFactoryTests { } @Test(expected=NoSuchBeanDefinitionException.class) + public void testGetBeanByTypeWithNoneFound() { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + lbf.getBean(TestBean.class); + } + + @Test(expected=NoUniqueBeanDefinitionException.class) public void testGetBeanByTypeWithAmbiguity() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); @@ -1279,6 +1298,32 @@ public class DefaultListableBeanFactoryTests { lbf.getBean(TestBean.class); } + @Test + public void testGetBeanByTypeWithPrimary() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setPrimary(true); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + TestBean bean = lbf.getBean(TestBean.class); + assertThat(bean.getBeanName(), equalTo("bd2")); + } + + @Test + public void testGetBeanByTypeWithMultiplePrimary() throws Exception { + DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); + RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); + bd1.setPrimary(true); + RootBeanDefinition bd2 = new RootBeanDefinition(TestBean.class); + bd2.setPrimary(true); + lbf.registerBeanDefinition("bd1", bd1); + lbf.registerBeanDefinition("bd2", bd2); + thrown.expect(NoUniqueBeanDefinitionException.class); + thrown.expectMessage(containsString("more than one 'primary'")); + lbf.getBean(TestBean.class); + } + @Test public void testGetBeanByTypeFiltersOutNonAutowireCandidates() { DefaultListableBeanFactory lbf = new DefaultListableBeanFactory(); @@ -1296,7 +1341,8 @@ public class DefaultListableBeanFactoryTests { try { lbf.getBean(TestBean.class); fail("Should have thrown NoSuchBeanDefinitionException"); - } catch (NoSuchBeanDefinitionException ex) { + } + catch (NoSuchBeanDefinitionException ex) { // expected } } @@ -2221,6 +2267,26 @@ public class DefaultListableBeanFactoryTests { assertThat(bf.containsBean("bogus"), is(false)); } + @Test + public void resolveEmbeddedValue() throws Exception { + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + StringValueResolver r1 = mock(StringValueResolver.class); + StringValueResolver r2 = mock(StringValueResolver.class); + StringValueResolver r3 = mock(StringValueResolver.class); + bf.addEmbeddedValueResolver(r1); + bf.addEmbeddedValueResolver(r2); + bf.addEmbeddedValueResolver(r3); + given(r1.resolveStringValue("A")).willReturn("B"); + given(r2.resolveStringValue("B")).willReturn(null); + given(r3.resolveStringValue(isNull(String.class))).willThrow(new IllegalArgumentException()); + + bf.resolveEmbeddedValue("A"); + + verify(r1).resolveStringValue("A"); + verify(r2).resolveStringValue("B"); + verify(r3, never()).resolveStringValue(isNull(String.class)); + } + static class A { } static class B { } diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java index 8ae68321dcd..a170eb84f85 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheManagerFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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,9 +18,12 @@ package org.springframework.cache.ehcache; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Method; import net.sf.ehcache.CacheException; import net.sf.ehcache.CacheManager; +import net.sf.ehcache.config.Configuration; +import net.sf.ehcache.config.ConfigurationFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -28,6 +31,8 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.core.io.Resource; +import org.springframework.util.ClassUtils; +import org.springframework.util.ReflectionUtils; /** * {@link FactoryBean} that exposes an EHCache {@link net.sf.ehcache.CacheManager} @@ -54,6 +59,10 @@ import org.springframework.core.io.Resource; */ public class EhCacheManagerFactoryBean implements FactoryBean, InitializingBean, DisposableBean { + // Check whether EHCache 2.1+ CacheManager.create(Configuration) method is available... + private static final Method createWithConfiguration = + ClassUtils.getMethodIfAvailable(CacheManager.class, "create", Configuration.class); + protected final Log logger = LogFactory.getLog(getClass()); private Resource configLocation; @@ -98,21 +107,42 @@ public class EhCacheManagerFactoryBean implements FactoryBean, Ini public void afterPropertiesSet() throws IOException, CacheException { logger.info("Initializing EHCache CacheManager"); - if (this.configLocation != null) { - InputStream is = this.configLocation.getInputStream(); - try { - this.cacheManager = (this.shared ? CacheManager.create(is) : new CacheManager(is)); + InputStream is = (this.configLocation != null ? this.configLocation.getInputStream() : null); + try { + // A bit convoluted for EHCache 1.x/2.0 compatibility. + // To be much simpler once we require EHCache 2.1+ + if (this.cacheManagerName != null) { + if (this.shared && createWithConfiguration == null) { + // No CacheManager.create(Configuration) method available before EHCache 2.1; + // can only set CacheManager name after creation. + this.cacheManager = (is != null ? CacheManager.create(is) : CacheManager.create()); + this.cacheManager.setName(this.cacheManagerName); + } + else { + Configuration configuration = (is != null ? ConfigurationFactory.parseConfiguration(is) : + ConfigurationFactory.parseConfiguration()); + configuration.setName(this.cacheManagerName); + if (this.shared) { + this.cacheManager = (CacheManager) ReflectionUtils.invokeMethod(createWithConfiguration, null, configuration); + } + else { + this.cacheManager = new CacheManager(configuration); + } + } } - finally { + // For strict backwards compatibility: use simplest possible constructors... + else if (this.shared) { + this.cacheManager = (is != null ? CacheManager.create(is) : CacheManager.create()); + } + else { + this.cacheManager = (is != null ? new CacheManager(is) : new CacheManager()); + } + } + finally { + if (is != null) { is.close(); } } - else { - this.cacheManager = (this.shared ? CacheManager.create() : new CacheManager()); - } - if (this.cacheManagerName != null) { - this.cacheManager.setName(this.cacheManagerName); - } } diff --git a/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java b/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java index 47a115e9805..4184b2f3b41 100644 --- a/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java +++ b/spring-context-support/src/main/java/org/springframework/mail/javamail/MimeMessageHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimePart; +import javax.mail.internet.MimeUtility; import org.springframework.core.io.InputStreamSource; import org.springframework.core.io.Resource; @@ -960,7 +961,7 @@ public class MimeMessageHelper { * @see #addInline(String, javax.activation.DataSource) */ public void addInline(String contentId, InputStreamSource inputStreamSource, String contentType) - throws MessagingException { + throws MessagingException { Assert.notNull(inputStreamSource, "InputStreamSource must not be null"); if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) { @@ -989,11 +990,16 @@ public class MimeMessageHelper { public void addAttachment(String attachmentFilename, DataSource dataSource) throws MessagingException { Assert.notNull(attachmentFilename, "Attachment filename must not be null"); Assert.notNull(dataSource, "DataSource must not be null"); - MimeBodyPart mimeBodyPart = new MimeBodyPart(); - mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT); - mimeBodyPart.setFileName(attachmentFilename); - mimeBodyPart.setDataHandler(new DataHandler(dataSource)); - getRootMimeMultipart().addBodyPart(mimeBodyPart); + try { + MimeBodyPart mimeBodyPart = new MimeBodyPart(); + mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT); + mimeBodyPart.setFileName(MimeUtility.encodeText(attachmentFilename)); + mimeBodyPart.setDataHandler(new DataHandler(dataSource)); + getRootMimeMultipart().addBodyPart(mimeBodyPart); + } + catch (UnsupportedEncodingException ex) { + throw new MessagingException("Failed to encode attachment filename", ex); + } } /** @@ -1035,7 +1041,7 @@ public class MimeMessageHelper { * @see org.springframework.core.io.Resource */ public void addAttachment(String attachmentFilename, InputStreamSource inputStreamSource) - throws MessagingException { + throws MessagingException { String contentType = getFileTypeMap().getContentType(attachmentFilename); addAttachment(attachmentFilename, inputStreamSource, contentType); @@ -1059,7 +1065,7 @@ public class MimeMessageHelper { */ public void addAttachment( String attachmentFilename, InputStreamSource inputStreamSource, String contentType) - throws MessagingException { + throws MessagingException { Assert.notNull(inputStreamSource, "InputStreamSource must not be null"); if (inputStreamSource instanceof Resource && ((Resource) inputStreamSource).isOpen()) { diff --git a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java index 574c7695e7f..027f0f4a6bc 100644 --- a/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java +++ b/spring-context-support/src/main/java/org/springframework/ui/freemarker/FreeMarkerConfigurationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; @@ -147,7 +148,7 @@ public class FreeMarkerConfigurationFactory { * @see #setPostTemplateLoaders */ @Deprecated - public void setTemplateLoaders(TemplateLoader[] templateLoaders) { + public void setTemplateLoaders(TemplateLoader... templateLoaders) { if (templateLoaders != null) { this.templateLoaders.addAll(Arrays.asList(templateLoaders)); } @@ -164,7 +165,7 @@ public class FreeMarkerConfigurationFactory { * @see #setTemplateLoaderPaths * @see #postProcessTemplateLoaders */ - public void setPreTemplateLoaders(TemplateLoader[] preTemplateLoaders) { + public void setPreTemplateLoaders(TemplateLoader... preTemplateLoaders) { this.preTemplateLoaders = Arrays.asList(preTemplateLoaders); } @@ -179,7 +180,7 @@ public class FreeMarkerConfigurationFactory { * @see #setTemplateLoaderPaths * @see #postProcessTemplateLoaders */ - public void setPostTemplateLoaders(TemplateLoader[] postTemplateLoaders) { + public void setPostTemplateLoaders(TemplateLoader... postTemplateLoaders) { this.postTemplateLoaders = Arrays.asList(postTemplateLoaders); } @@ -211,7 +212,7 @@ public class FreeMarkerConfigurationFactory { * @see SpringTemplateLoader * @see #setTemplateLoaders */ - public void setTemplateLoaderPaths(String[] templateLoaderPaths) { + public void setTemplateLoaderPaths(String... templateLoaderPaths) { this.templateLoaderPaths = templateLoaderPaths; } @@ -229,7 +230,7 @@ public class FreeMarkerConfigurationFactory { * Return the Spring ResourceLoader to use for loading FreeMarker template files. */ protected ResourceLoader getResourceLoader() { - return resourceLoader; + return this.resourceLoader; } /** @@ -252,7 +253,7 @@ public class FreeMarkerConfigurationFactory { * Return whether to prefer file system access for template loading. */ protected boolean isPreferFileSystemAccess() { - return preferFileSystemAccess; + return this.preferFileSystemAccess; } @@ -293,25 +294,27 @@ public class FreeMarkerConfigurationFactory { config.setDefaultEncoding(this.defaultEncoding); } + List templateLoaders = new LinkedList(this.templateLoaders); + // Register template loaders that are supposed to kick in early. if (this.preTemplateLoaders != null) { - this.templateLoaders.addAll(this.preTemplateLoaders); + templateLoaders.addAll(this.preTemplateLoaders); } // Register default template loaders. if (this.templateLoaderPaths != null) { for (String path : this.templateLoaderPaths) { - this.templateLoaders.add(getTemplateLoaderForPath(path)); + templateLoaders.add(getTemplateLoaderForPath(path)); } } - postProcessTemplateLoaders(this.templateLoaders); + postProcessTemplateLoaders(templateLoaders); // Register template loaders that are supposed to kick in late. if (this.postTemplateLoaders != null) { - this.templateLoaders.addAll(this.postTemplateLoaders); + templateLoaders.addAll(this.postTemplateLoaders); } - TemplateLoader loader = getAggregateTemplateLoader(this.templateLoaders); + TemplateLoader loader = getAggregateTemplateLoader(templateLoaders); if (loader != null) { config.setTemplateLoader(loader); } diff --git a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java index 646a6797171..fcfe2216425 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheSupportTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,6 +38,7 @@ public class EhCacheSupportTests extends TestCase { public void testLoadingBlankCacheManager() throws Exception { EhCacheManagerFactoryBean cacheManagerFb = new EhCacheManagerFactoryBean(); + cacheManagerFb.setCacheManagerName("myCacheManager"); assertEquals(CacheManager.class, cacheManagerFb.getObjectType()); assertTrue("Singleton property", cacheManagerFb.isSingleton()); cacheManagerFb.afterPropertiesSet(); diff --git a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java index 5d301d85f4c..29a5a98973b 100644 --- a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,6 @@ import java.util.concurrent.ConcurrentMap; import org.springframework.beans.factory.InitializingBean; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.util.Assert; /** * Abstract base class implementing the common {@link CacheManager} methods. @@ -45,10 +44,10 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing public void afterPropertiesSet() { Collection caches = loadCaches(); - Assert.notEmpty(caches, "loadCaches must not return an empty Collection"); - this.cacheMap.clear(); // preserve the initial order of the cache names + this.cacheMap.clear(); + this.cacheNames.clear(); for (Cache cache : caches) { this.cacheMap.put(cache.getName(), decorateCache(cache)); this.cacheNames.add(cache.getName()); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java index f678425ea84..6a7db8bd313 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationScopeMetadataResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 +39,10 @@ import static org.springframework.context.annotation.MetadataUtils.*; */ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver { + private final ScopedProxyMode defaultProxyMode; + protected Class scopeAnnotationType = Scope.class; - private final ScopedProxyMode defaultProxyMode; /** * Create a new instance of the {@code AnnotationScopeMetadataResolver} class. @@ -77,8 +78,7 @@ public class AnnotationScopeMetadataResolver implements ScopeMetadataResolver { ScopeMetadata metadata = new ScopeMetadata(); if (definition instanceof AnnotatedBeanDefinition) { AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition; - AnnotationAttributes attributes = - attributesFor(annDef.getMetadata(), this.scopeAnnotationType); + AnnotationAttributes attributes = attributesFor(annDef.getMetadata(), this.scopeAnnotationType); if (attributes != null) { metadata.setScopeName(attributes.getString("value")); ScopedProxyMode proxyMode = attributes.getEnum("proxyMode"); diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java index 138f45526cd..66aff5979be 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableConfigApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +17,6 @@ package org.springframework.context.support; import org.springframework.beans.factory.BeanNameAware; - import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.util.Assert; @@ -114,12 +113,13 @@ public abstract class AbstractRefreshableConfigApplicationContext extends Abstra /** * Resolve the given path, replacing placeholders with corresponding - * system property values if necessary. Applied to config locations. + * environment property values if necessary. Applied to config locations. * @param path the original file path * @return the resolved file path + * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) */ protected String resolvePath(String path) { - return this.getEnvironment().resolveRequiredPlaceholders(path); + return getEnvironment().resolveRequiredPlaceholders(path); } diff --git a/spring-context/src/main/java/org/springframework/context/support/MessageSourceResourceBundle.java b/spring-context/src/main/java/org/springframework/context/support/MessageSourceResourceBundle.java index 8dc2c1ffb3e..605aa72e871 100644 --- a/spring-context/src/main/java/org/springframework/context/support/MessageSourceResourceBundle.java +++ b/spring-context/src/main/java/org/springframework/context/support/MessageSourceResourceBundle.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 +70,9 @@ public class MessageSourceResourceBundle extends ResourceBundle { * Returns {@code null} if the message could not be resolved. */ @Override - protected Object handleGetObject(String code) { + protected Object handleGetObject(String key) { try { - return this.messageSource.getMessage(code, null, this.locale); + return this.messageSource.getMessage(key, null, this.locale); } catch (NoSuchMessageException ex) { return null; @@ -80,12 +80,29 @@ public class MessageSourceResourceBundle extends ResourceBundle { } /** - * This implementation returns {@code null}, as a MessageSource does - * not allow for enumerating the defined message codes. + * This implementation checks whether the target MessageSource can resolve + * a message for the given key, translating {@code NoSuchMessageException} + * accordingly. In contrast to ResourceBundle's default implementation in + * JDK 1.6, this does not rely on the capability to enumerate message keys. + */ + @Override + public boolean containsKey(String key) { + try { + this.messageSource.getMessage(key, null, this.locale); + return true; + } + catch (NoSuchMessageException ex) { + return false; + } + } + + /** + * This implementation throws {@code UnsupportedOperationException}, + * as a MessageSource does not allow for enumerating the defined message codes. */ @Override public Enumeration getKeys() { - return null; + throw new UnsupportedOperationException("MessageSourceResourceBundle does not support enumerating its keys"); } /** diff --git a/spring-context/src/main/java/org/springframework/ejb/interceptor/SpringBeanAutowiringInterceptor.java b/spring-context/src/main/java/org/springframework/ejb/interceptor/SpringBeanAutowiringInterceptor.java index 89d50674517..33c15966306 100644 --- a/spring-context/src/main/java/org/springframework/ejb/interceptor/SpringBeanAutowiringInterceptor.java +++ b/spring-context/src/main/java/org/springframework/ejb/interceptor/SpringBeanAutowiringInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,8 +71,6 @@ import org.springframework.context.access.ContextSingletonBeanFactoryLocator; * @see org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator * @see #getBeanFactoryLocatorKey - * @see org.springframework.ejb.support.AbstractEnterpriseBean#setBeanFactoryLocator - * @see org.springframework.ejb.support.AbstractEnterpriseBean#setBeanFactoryLocatorKey */ public class SpringBeanAutowiringInterceptor { @@ -99,9 +97,15 @@ public class SpringBeanAutowiringInterceptor { invocationContext.proceed(); } catch (RuntimeException ex) { + doReleaseBean(invocationContext.getTarget()); throw ex; } + catch (Error err) { + doReleaseBean(invocationContext.getTarget()); + throw err; + } catch (Exception ex) { + doReleaseBean(invocationContext.getTarget()); // Cannot declare a checked exception on WebSphere here - so we need to wrap. throw new EJBException(ex); } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java index eb413a6ea7a..aedf1184373 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/DateFormatterRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -57,7 +57,7 @@ public class DateFormatterRegistrar implements FormatterRegistrar { * @param dateFormatter the date formatter */ public void setFormatter(DateFormatter dateFormatter) { - Assert.notNull(dateFormatter,"DateFormatter must not be null"); + Assert.notNull(dateFormatter, "DateFormatter must not be null"); this.dateFormatter = dateFormatter; } diff --git a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java index 6943ad4241f..b49b6072559 100644 --- a/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java +++ b/spring-context/src/main/java/org/springframework/format/datetime/joda/JodaTimeConverters.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.format.datetime.DateFormatterRegistrar; * Installs lower-level type converters required to integrate Joda Time support into Spring's field formatting system. * * @author Keith Donald + * @author Phillip Webb * @since 3.0 */ final class JodaTimeConverters { @@ -55,6 +56,7 @@ final class JodaTimeConverters { registry.addConverter(new DateTimeToCalendarConverter()); registry.addConverter(new DateTimeToLongConverter()); registry.addConverter(new CalendarToReadableInstantConverter()); + registry.addConverter(new DateToReadableInstantConverter()); } @@ -159,4 +161,14 @@ final class JodaTimeConverters { } } + /** + * Used when printing a java.util.Date field with a ReadableInstantPrinter. + * @see MillisecondInstantPrinter + * @see JodaDateTimeFormatAnnotationFormatterFactory + */ + private static class DateToReadableInstantConverter implements Converter { + public ReadableInstant convert(Date source) { + return new DateTime(source); + } + } } diff --git a/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java b/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java index d38e5be9483..f953d9465cf 100644 --- a/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java +++ b/spring-context/src/main/java/org/springframework/jmx/export/assembler/AbstractReflectiveMBeanInfoAssembler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,8 @@ import javax.management.modelmbean.ModelMBeanOperationInfo; import org.springframework.aop.framework.AopProxyUtils; import org.springframework.aop.support.AopUtils; import org.springframework.beans.BeanUtils; +import org.springframework.core.LocalVariableTableParameterNameDiscoverer; +import org.springframework.core.ParameterNameDiscoverer; import org.springframework.jmx.support.JmxUtils; /** @@ -49,6 +51,7 @@ import org.springframework.jmx.support.JmxUtils; * * @author Rob Harrop * @author Juergen Hoeller + * @author David Boden * @since 1.2 * @see #includeOperation * @see #includeReadAttribute @@ -177,6 +180,8 @@ public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBean private boolean exposeClassDescriptor = false; + private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); + /** * Set the default for the JMX field "currencyTimeLimit". @@ -254,6 +259,23 @@ public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBean return this.exposeClassDescriptor; } + /** + * Set the ParameterNameDiscoverer to use for resolving method parameter + * names if needed (e.g. for parameter names of MBean operation methods). + *

    The default is {@link LocalVariableTableParameterNameDiscoverer}. + */ + public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) { + this.parameterNameDiscoverer = parameterNameDiscoverer; + } + + /** + * Return the ParameterNameDiscoverer to use for resolving method parameter + * names if needed (may be {@code null} in order to skip parameter detection). + */ + protected ParameterNameDiscoverer getParameterNameDiscoverer() { + return this.parameterNameDiscoverer; + } + /** * Iterate through all properties on the MBean class and gives subclasses @@ -381,7 +403,8 @@ public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBean * Creates an instance of {@code ModelMBeanOperationInfo} for the * given method. Populates the parameter info for the operation. * @param method the {@code Method} to create a {@code ModelMBeanOperationInfo} for - * @param name the name for the operation info + * @param name the logical name for the operation (method name or property name); + * not used by the default implementation but possibly by subclasses * @param beanKey the key associated with the MBean in the beans map * of the {@code MBeanExporter} * @return the {@code ModelMBeanOperationInfo} @@ -392,7 +415,7 @@ public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBean return new ModelMBeanOperationInfo(getOperationDescription(method, beanKey), method); } else { - return new ModelMBeanOperationInfo(name, + return new ModelMBeanOperationInfo(method.getName(), getOperationDescription(method, beanKey), getOperationParameters(method, beanKey), method.getReturnType().getName(), @@ -476,16 +499,27 @@ public abstract class AbstractReflectiveMBeanInfoAssembler extends AbstractMBean /** * Create parameter info for the given method. - *

    The default implementation returns an empty arry of {@code MBeanParameterInfo}. + *

    The default implementation returns an empty array of {@code MBeanParameterInfo}. * @param method the {@code Method} to get the parameter information for * @param beanKey the key associated with the MBean in the beans map * of the {@code MBeanExporter} * @return the {@code MBeanParameterInfo} array */ protected MBeanParameterInfo[] getOperationParameters(Method method, String beanKey) { - return new MBeanParameterInfo[0]; - } + ParameterNameDiscoverer paramNameDiscoverer = getParameterNameDiscoverer(); + String[] paramNames = (paramNameDiscoverer != null ? paramNameDiscoverer.getParameterNames(method) : null); + if (paramNames == null) { + return new MBeanParameterInfo[0]; + } + MBeanParameterInfo[] info = new MBeanParameterInfo[paramNames.length]; + Class[] typeParameters = method.getParameterTypes(); + for(int i = 0; i < info.length; i++) { + info[i] = new MBeanParameterInfo(paramNames[i], typeParameters[i].getName(), paramNames[i]); + } + + return info; + } /** * Allows subclasses to add extra fields to the {@code Descriptor} for an MBean. diff --git a/spring-context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java b/spring-context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java index 320a64790e1..b75e956dab9 100644 --- a/spring-context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java +++ b/spring-context/src/main/java/org/springframework/jmx/export/assembler/MetadataMBeanInfoAssembler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import org.springframework.jmx.export.metadata.ManagedOperationParameter; import org.springframework.jmx.export.metadata.ManagedResource; import org.springframework.jmx.support.MetricType; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -255,19 +256,17 @@ public class MetadataMBeanInfoAssembler extends AbstractReflectiveMBeanInfoAssem @Override protected MBeanParameterInfo[] getOperationParameters(Method method, String beanKey) { ManagedOperationParameter[] params = this.attributeSource.getManagedOperationParameters(method); - if (params == null || params.length == 0) { - return new MBeanParameterInfo[0]; + if (ObjectUtils.isEmpty(params)) { + return super.getOperationParameters(method, beanKey); } MBeanParameterInfo[] parameterInfo = new MBeanParameterInfo[params.length]; Class[] methodParameters = method.getParameterTypes(); - for (int i = 0; i < params.length; i++) { ManagedOperationParameter param = params[i]; parameterInfo[i] = new MBeanParameterInfo(param.getName(), methodParameters[i].getName(), param.getDescription()); } - return parameterInfo; } diff --git a/spring-context/src/main/java/org/springframework/jmx/support/MBeanRegistrationSupport.java b/spring-context/src/main/java/org/springframework/jmx/support/MBeanRegistrationSupport.java index 74b0a3f3c04..38339d4b7f7 100644 --- a/spring-context/src/main/java/org/springframework/jmx/support/MBeanRegistrationSupport.java +++ b/spring-context/src/main/java/org/springframework/jmx/support/MBeanRegistrationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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.jmx.support; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; import javax.management.InstanceAlreadyExistsException; @@ -112,7 +113,7 @@ public class MBeanRegistrationSupport { /** * The beans that have been registered by this exporter. */ - protected final Set registeredBeans = new LinkedHashSet(); + private final Set registeredBeans = Collections.synchronizedSet(new LinkedHashSet()); /** * The policy used when registering an MBean and finding that it already exists. @@ -228,10 +229,9 @@ public class MBeanRegistrationSupport { * Unregisters all beans that have been registered by an instance of this class. */ protected void unregisterBeans() { - for (ObjectName objectName : this.registeredBeans) { + for (ObjectName objectName : new LinkedHashSet(this.registeredBeans)) { doUnregister(objectName); } - this.registeredBeans.clear(); } /** @@ -257,6 +257,7 @@ public class MBeanRegistrationSupport { logger.error("Could not unregister MBean [" + objectName + "]", ex); } } + this.registeredBeans.remove(objectName); } /** diff --git a/spring-context/src/main/java/org/springframework/remoting/rmi/JndiRmiClientInterceptor.java b/spring-context/src/main/java/org/springframework/remoting/rmi/JndiRmiClientInterceptor.java index 8660e2cf5be..caa7f51452a 100644 --- a/spring-context/src/main/java/org/springframework/remoting/rmi/JndiRmiClientInterceptor.java +++ b/spring-context/src/main/java/org/springframework/remoting/rmi/JndiRmiClientInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package org.springframework.remoting.rmi; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.rmi.RemoteException; - +import javax.naming.Context; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; @@ -88,6 +88,8 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho private boolean refreshStubOnConnectFailure = false; + private boolean exposeAccessContext = false; + private Object cachedStub; private final Object stubMonitor = new Object(); @@ -166,6 +168,18 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho this.refreshStubOnConnectFailure = refreshStubOnConnectFailure; } + /** + * Set whether to expose the JNDI environment context for all access to the target + * RMI stub, i.e. for all method invocations on the exposed object reference. + *

    Default is "false", i.e. to only expose the JNDI context for object lookup. + * Switch this flag to "true" in order to expose the JNDI environment (including + * the authorization context) for each RMI invocation, as needed by WebLogic + * for RMI stubs with authorization requirements. + */ + public void setExposeAccessContext(boolean exposeAccessContext) { + this.exposeAccessContext = exposeAccessContext; + } + @Override public void afterPropertiesSet() throws NamingException { @@ -190,8 +204,8 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho else if (getServiceInterface() != null) { boolean isImpl = getServiceInterface().isInstance(remoteObj); logger.debug("Using service interface [" + getServiceInterface().getName() + - "] for JNDI RMI object [" + getJndiName() + "] - " + - (!isImpl ? "not " : "") + "directly implemented"); + "] for JNDI RMI object [" + getJndiName() + "] - " + + (!isImpl ? "not " : "") + "directly implemented"); } } if (this.cacheStub) { @@ -268,13 +282,15 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho * @see java.rmi.NoSuchObjectException */ public Object invoke(MethodInvocation invocation) throws Throwable { - Object stub = null; + Object stub; try { stub = getStub(); } catch (NamingException ex) { throw new RemoteLookupFailureException("JNDI lookup for RMI service [" + getJndiName() + "] failed", ex); } + + Context ctx = (this.exposeAccessContext ? getJndiTemplate().getContext() : null); try { return doInvoke(invocation, stub); } @@ -297,6 +313,9 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho throw ex; } } + finally { + getJndiTemplate().releaseContext(ctx); + } } /** @@ -354,7 +373,7 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho * @see #invoke */ protected Object refreshAndRetry(MethodInvocation invocation) throws Throwable { - Object freshStub = null; + Object freshStub; synchronized (this.stubMonitor) { this.cachedStub = null; freshStub = lookupStub(); @@ -426,7 +445,7 @@ public class JndiRmiClientInterceptor extends JndiObjectLocator implements Metho * @see org.springframework.remoting.support.RemoteInvocation */ protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler) - throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { + throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { if (AopUtils.isToStringMethod(methodInvocation.getMethod())) { return "RMI invoker proxy for service URL [" + getJndiName() + "]"; diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java index 964872e1d6e..91764b33286 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ExecutorConfigurationSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -54,6 +55,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac private boolean waitForTasksToCompleteOnShutdown = false; + private int awaitTerminationSeconds = 0; + private String beanName; private ExecutorService executor; @@ -85,9 +88,17 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac } /** - * Set whether to wait for scheduled tasks to complete on shutdown. - *

    Default is "false". Switch this to "true" if you prefer - * fully completed tasks at the expense of a longer shutdown phase. + * Set whether to wait for scheduled tasks to complete on shutdown, + * not interrupting running tasks and executing all tasks in the queue. + *

    Default is "false", shutting down immediately through interrupting + * ongoing tasks and clearing the queue. Switch this flag to "true" if you + * prefer fully completed tasks at the expense of a longer shutdown phase. + *

    Note that Spring's container shutdown continues while ongoing tasks + * are being completed. If you want this executor to block and wait for the + * termination of tasks before the rest of the container continues to shut + * down - e.g. in order to keep up other resources that your tasks may need -, + * set the {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} + * property instead of or in addition to this property. * @see java.util.concurrent.ExecutorService#shutdown() * @see java.util.concurrent.ExecutorService#shutdownNow() */ @@ -95,6 +106,33 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac this.waitForTasksToCompleteOnShutdown = waitForJobsToCompleteOnShutdown; } + /** + * Set the maximum number of seconds that this executor is supposed to block + * on shutdown in order to wait for remaining tasks to complete their execution + * before the rest of the container continues to shut down. This is particularly + * useful if your remaining tasks are likely to need access to other resources + * that are also managed by the container. + *

    By default, this executor won't wait for the termination of tasks at all. + * It will either shut down immediately, interrupting ongoing tasks and clearing + * the remaining task queue - or, if the + * {@link #setWaitForTasksToCompleteOnShutdown "waitForTasksToCompleteOnShutdown"} + * flag has been set to {@code true}, it will continue to fully execute all + * ongoing tasks as well as all remaining tasks in the queue, in parallel to + * the rest of the container shutting down. + *

    In either case, if you specify an await-termination period using this property, + * this executor will wait for the given time (max) for the termination of tasks. + * As a rule of thumb, specify a significantly higher timeout here if you set + * "waitForTasksToCompleteOnShutdown" to {@code true} at the same time, + * since all remaining tasks in the queue will still get executed - in contrast + * to the default shutdown behavior where it's just about waiting for currently + * executing tasks that aren't reacting to thread interruption. + * @see java.util.concurrent.ExecutorService#shutdown() + * @see java.util.concurrent.ExecutorService#awaitTermination + */ + public void setAwaitTerminationSeconds(int awaitTerminationSeconds) { + this.awaitTerminationSeconds = awaitTerminationSeconds; + } + public void setBeanName(String name) { this.beanName = name; } @@ -145,6 +183,8 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac /** * Perform a shutdown on the ThreadPoolExecutor. * @see java.util.concurrent.ExecutorService#shutdown() + * @see java.util.concurrent.ExecutorService#shutdownNow() + * @see #awaitTerminationIfNecessary() */ public void shutdown() { if (logger.isInfoEnabled()) { @@ -156,6 +196,31 @@ public abstract class ExecutorConfigurationSupport extends CustomizableThreadFac else { this.executor.shutdownNow(); } + awaitTerminationIfNecessary(); + } + + /** + * Wait for the executor to terminate, according to the value of the + * {@link #setAwaitTerminationSeconds "awaitTerminationSeconds"} property. + */ + private void awaitTerminationIfNecessary() { + if (this.awaitTerminationSeconds > 0) { + try { + if (!this.executor.awaitTermination(this.awaitTerminationSeconds, TimeUnit.SECONDS)) { + if (logger.isWarnEnabled()) { + logger.warn("Timed out while waiting for executor" + + (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate"); + } + } + } + catch (InterruptedException ex) { + if (logger.isWarnEnabled()) { + logger.warn("Interrupted while waiting for executor" + + (this.beanName != null ? " '" + this.beanName + "'" : "") + " to terminate"); + } + Thread.currentThread().interrupt(); + } + } } } diff --git a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java index 19cd880bbb7..3e04248ea86 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java +++ b/spring-context/src/main/java/org/springframework/scheduling/concurrent/ThreadPoolExecutorFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -126,7 +126,7 @@ public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport *

    Default is "false", exposing the raw executor as bean reference. * Switch this flag to "true" to strictly prevent clients from * modifying the executor's configuration. - * @see java.util.concurrent.Executors#unconfigurableScheduledExecutorService + * @see java.util.concurrent.Executors#unconfigurableExecutorService */ public void setExposeUnconfigurableExecutor(boolean exposeUnconfigurableExecutor) { this.exposeUnconfigurableExecutor = exposeUnconfigurableExecutor; @@ -137,9 +137,8 @@ public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { BlockingQueue queue = createQueue(this.queueCapacity); - ThreadPoolExecutor executor = new ThreadPoolExecutor( - this.corePoolSize, this.maxPoolSize, this.keepAliveSeconds, TimeUnit.SECONDS, - queue, threadFactory, rejectedExecutionHandler); + ThreadPoolExecutor executor = createExecutor(this.corePoolSize, this.maxPoolSize, + this.keepAliveSeconds, queue, threadFactory, rejectedExecutionHandler); if (this.allowCoreThreadTimeOut) { executor.allowCoreThreadTimeOut(true); } @@ -151,6 +150,27 @@ public class ThreadPoolExecutorFactoryBean extends ExecutorConfigurationSupport return executor; } + /** + * Create a new instance of {@link ThreadPoolExecutor} or a subclass thereof. + *

    The default implementation creates a standard {@link ThreadPoolExecutor}. + * Can be overridden to provide custom {@link ThreadPoolExecutor} subclasses. + * @param corePoolSize the specified core pool size + * @param maxPoolSize the specified maximum pool size + * @param keepAliveSeconds the specified keep-alive time in seconds + * @param queue the BlockingQueue to use + * @param threadFactory the ThreadFactory to use + * @param rejectedExecutionHandler the RejectedExecutionHandler to use + * @return a new ThreadPoolExecutor instance + * @see #afterPropertiesSet() + */ + protected ThreadPoolExecutor createExecutor( + int corePoolSize, int maxPoolSize, int keepAliveSeconds, BlockingQueue queue, + ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) { + + return new ThreadPoolExecutor(corePoolSize, maxPoolSize, + keepAliveSeconds, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler); + } + /** * Create the BlockingQueue to use for the ThreadPoolExecutor. *

    A LinkedBlockingQueue instance will be created for a positive diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ContextLifecycleScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ContextLifecycleScheduledTaskRegistrar.java new file mode 100644 index 00000000000..8a0fba8c3ba --- /dev/null +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ContextLifecycleScheduledTaskRegistrar.java @@ -0,0 +1,65 @@ +/* + * Copyright 2002-2013 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.scheduling.config; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; + +/** + * {@link ScheduledTaskRegistrar} subclass that redirects the actual scheduling + * of tasks to the {@link ContextRefreshedEvent} callback. Falls back to regular + * {@code ScheduledTaskRegistrar} behavior when not running within an ApplicationContext. + * + * @author Juergen Hoeller + * @since 3.2.1 + */ +public class ContextLifecycleScheduledTaskRegistrar extends ScheduledTaskRegistrar + implements ApplicationContextAware, ApplicationListener { + + private ApplicationContext applicationContext; + + + public void setApplicationContext(ApplicationContext applicationContext) { + this.applicationContext = applicationContext; + } + + + /** + * If we're running within an ApplicationContext, don't schedule the tasks + * right here; wait for this context's ContextRefreshedEvent instead. + */ + @Override + public void afterPropertiesSet() { + if (this.applicationContext == null) { + scheduleTasks(); + } + } + + /** + * Actually schedule the tasks at the right time of the context lifecycle, + * if we're running within an ApplicationContext. + */ + public void onApplicationEvent(ContextRefreshedEvent event) { + if (event.getApplicationContext() != this.applicationContext) { + return; + } + scheduleTasks(); + } + +} diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java index 592fd033c13..2efd26cea9e 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTaskRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -274,11 +274,19 @@ public class ScheduledTaskRegistrar implements InitializingBean, DisposableBean (this.triggerTasks != null && !this.triggerTasks.isEmpty()); } + + /** + * Calls {@link #scheduleTasks()} at bean construction time. + */ + public void afterPropertiesSet() { + scheduleTasks(); + } + /** * Schedule all registered tasks against the underlying {@linkplain * #setTaskScheduler(TaskScheduler) task scheduler}. */ - public void afterPropertiesSet() { + protected void scheduleTasks() { long now = System.currentTimeMillis(); if (this.taskScheduler == null) { diff --git a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java index a892eb414b1..8f6963614f4 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java +++ b/spring-context/src/main/java/org/springframework/scheduling/config/ScheduledTasksBeanDefinitionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,8 +38,10 @@ import org.w3c.dom.NodeList; public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { private static final String ELEMENT_SCHEDULED = "scheduled"; + private static final long ZERO_INITIAL_DELAY = 0; + @Override protected boolean shouldGenerateId() { return true; @@ -47,7 +49,7 @@ public class ScheduledTasksBeanDefinitionParser extends AbstractSingleBeanDefini @Override protected String getBeanClassName(Element element) { - return "org.springframework.scheduling.config.ScheduledTaskRegistrar"; + return "org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar"; } @Override diff --git a/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java b/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java index b494e4c5ff0..e25fdd3f254 100644 --- a/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java +++ b/spring-context/src/main/java/org/springframework/scheduling/support/PeriodicTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 +31,8 @@ import org.springframework.util.Assert; * completion time). To measure the interval between the * scheduled start time of each execution instead, set the * 'fixedRate' property to {@code true}. - *

    - * Note that the TaskScheduler interface already defines methods for scheduling + * + *

    Note that the TaskScheduler interface already defines methods for scheduling * tasks at fixed-rate or with fixed-delay. Both also support an optional value * for the initial delay. Those methods should be used directly whenever * possible. The value of this Trigger implementation is that it can be used @@ -68,7 +68,7 @@ public class PeriodicTrigger implements Trigger { */ public PeriodicTrigger(long period, TimeUnit timeUnit) { Assert.isTrue(period >= 0, "period must not be negative"); - this.timeUnit = (timeUnit != null) ? timeUnit : TimeUnit.MILLISECONDS; + this.timeUnit = (timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS); this.period = this.timeUnit.toMillis(period); } @@ -91,6 +91,7 @@ public class PeriodicTrigger implements Trigger { this.fixedRate = fixedRate; } + /** * Returns the time after which a task should run again. */ @@ -104,6 +105,7 @@ public class PeriodicTrigger implements Trigger { return new Date(triggerContext.lastCompletionTime().getTime() + this.period); } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -113,16 +115,12 @@ public class PeriodicTrigger implements Trigger { return false; } PeriodicTrigger other = (PeriodicTrigger) obj; - return this.fixedRate == other.fixedRate - && this.initialDelay == other.initialDelay - && this.period == other.period; + return (this.fixedRate == other.fixedRate && this.initialDelay == other.initialDelay && this.period == other.period); } @Override public int hashCode() { - return (this.fixedRate ? 17 : 29) + - (int) (37 * this.period) + - (int) (41 * this.initialDelay); + return (this.fixedRate ? 17 : 29) + (int) (37 * this.period) + (int) (41 * this.initialDelay); } } diff --git a/spring-context/src/main/java/org/springframework/validation/DataBinder.java b/spring-context/src/main/java/org/springframework/validation/DataBinder.java index a9d5d835873..35e0ea4a568 100644 --- a/spring-context/src/main/java/org/springframework/validation/DataBinder.java +++ b/spring-context/src/main/java/org/springframework/validation/DataBinder.java @@ -18,7 +18,11 @@ package org.springframework.validation; import java.beans.PropertyEditor; import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; @@ -141,7 +145,7 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor(); - private Validator validator; + private final List validators = new ArrayList(); private ConversionService conversionService; @@ -493,21 +497,58 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { /** * Set the Validator to apply after each binding step. + * @see #addValidators(Validator...) + * @see #replaceValidators(Validator...) */ public void setValidator(Validator validator) { - if (validator != null && (getTarget() != null && !validator.supports(getTarget().getClass()))) { - throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + getTarget()); + assertValidators(validator); + this.validators.clear(); + this.validators.add(validator); + } + + private void assertValidators(Validator... validators) { + Assert.notNull(validators, "Validators required"); + for (Validator validator : validators) { + if (validator != null && (getTarget() != null && !validator.supports(getTarget().getClass()))) { + throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + getTarget()); + } } - this.validator = validator; } /** - * Return the Validator to apply after each binding step, if any. + * Add Validators to apply after each binding step. + * @see #setValidator(Validator) + * @see #replaceValidators(Validator...) */ - public Validator getValidator() { - return this.validator; + public void addValidators(Validator... validators) { + assertValidators(validators); + this.validators.addAll(Arrays.asList(validators)); } + /** + * Replace the Validators to apply after each binding step. + * @see #setValidator(Validator) + * @see #addValidators(Validator...) + */ + public void replaceValidators(Validator... validators) { + assertValidators(validators); + this.validators.clear(); + this.validators.addAll(Arrays.asList(validators)); + } + + /** + * Return the primary Validator to apply after each binding step, if any. + */ + public Validator getValidator() { + return this.validators.size() > 0 ? this.validators.get(0) : null; + } + + /** + * Return the Validators to apply after data binding. + */ + public List getValidators() { + return Collections.unmodifiableList(this.validators); + } //--------------------------------------------------------------------- // Implementation of PropertyEditorRegistry/TypeConverter interface @@ -708,28 +749,31 @@ public class DataBinder implements PropertyEditorRegistry, TypeConverter { /** - * Invoke the specified Validator, if any. + * Invoke the specified Validators, if any. * @see #setValidator(Validator) * @see #getBindingResult() */ public void validate() { - this.validator.validate(getTarget(), getBindingResult()); + for (Validator validator : this.validators) { + validator.validate(getTarget(), getBindingResult()); + } } /** - * Invoke the specified Validator, if any, with the given validation hints. + * Invoke the specified Validators, if any, with the given validation hints. *

    Note: Validation hints may get ignored by the actual target Validator. * @param validationHints one or more hint objects to be passed to a {@link SmartValidator} * @see #setValidator(Validator) * @see SmartValidator#validate(Object, Errors, Object...) */ public void validate(Object... validationHints) { - Validator validator = getValidator(); - if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) { - ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints); - } - else if (validator != null) { - validator.validate(getTarget(), getBindingResult()); + for (Validator validator : getValidators()) { + if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) { + ((SmartValidator) validator).validate(getTarget(), getBindingResult(), validationHints); + } + else if (validator != null) { + validator.validate(getTarget(), getBindingResult()); + } } } diff --git a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java index 1a51c37bb9b..7565e82099c 100644 --- a/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java +++ b/spring-context/src/main/java/org/springframework/validation/beanvalidation/SpringValidatorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -99,7 +99,8 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. } } } - processConstraintViolations(this.targetValidator.validate(target, groups.toArray(new Class[groups.size()])), errors); + processConstraintViolations( + this.targetValidator.validate(target, groups.toArray(new Class[groups.size()])), errors); } /** @@ -114,10 +115,11 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. FieldError fieldError = errors.getFieldError(field); if (fieldError == null || !fieldError.isBindingFailure()) { try { - String errorCode = violation.getConstraintDescriptor().getAnnotation().annotationType().getSimpleName(); - Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, violation.getConstraintDescriptor()); + ConstraintDescriptor cd = violation.getConstraintDescriptor(); + String errorCode = cd.getAnnotation().annotationType().getSimpleName(); + Object[] errorArgs = getArgumentsForConstraint(errors.getObjectName(), field, cd); if (errors instanceof BindingResult) { - // can do custom FieldError registration with invalid value from ConstraintViolation, + // Can do custom FieldError registration with invalid value from ConstraintViolation, // as necessary for Hibernate Validator compatibility (non-indexed set path in field) BindingResult bindingResult = (BindingResult) errors; String nestedField = bindingResult.getNestedPath() + field; @@ -128,8 +130,9 @@ public class SpringValidatorAdapter implements SmartValidator, javax.validation. } else { Object invalidValue = violation.getInvalidValue(); - if (!"".equals(field) && invalidValue == violation.getLeafBean()) { - // bean constraint with property path: retrieve the actual property value + if (field.contains(".") && !field.contains("[]")) { + // Possibly a bean constraint with property path: retrieve the actual property value. + // However, explicitly avoid this for "address[]" style paths that we can't handle. invalidValue = bindingResult.getRawFieldValue(field); } String[] errorCodes = bindingResult.resolveMessageCodes(errorCode, field); diff --git a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.1.xsd b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.1.xsd index 91b49de3275..56680707c22 100644 --- a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.1.xsd +++ b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.1.xsd @@ -52,6 +52,39 @@ ]]> + + + + + + + + + + + + + + + + diff --git a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd index 34b479d12cb..eb23e55017b 100644 --- a/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd +++ b/spring-context/src/main/resources/org/springframework/scheduling/config/spring-task-3.2.xsd @@ -52,6 +52,39 @@ ]]> + + + + + + + + + + + + + + + + diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index 23973400039..5906ea485dc 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 +17,10 @@ package org.springframework.context.annotation; import java.util.Map; +import java.util.regex.Pattern; import org.junit.Test; + import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.annotation.Autowired; @@ -27,7 +29,7 @@ import org.springframework.context.annotation6.ComponentForScanning; import org.springframework.context.annotation6.ConfigForScanning; import org.springframework.context.annotation6.Jsr330NamedForScanning; -import static java.lang.String.*; +import static java.lang.String.format; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.util.StringUtils.*; @@ -120,13 +122,14 @@ public class AnnotationConfigApplicationContextTests { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); // attempt to retrieve a bean that does not exist - Class targetType = java.util.regex.Pattern.class; + Class targetType = Pattern.class; try { Object bean = context.getBean(targetType); fail("should have thrown NoSuchBeanDefinitionException, instead got: " + bean); - } catch (NoSuchBeanDefinitionException ex) { + } + catch (NoSuchBeanDefinitionException ex) { assertThat(ex.getMessage(), containsString( - format("No unique bean of type [%s] is defined", targetType.getName()))); + format("No qualifying bean of type [%s] is defined", targetType.getName()))); } } @@ -137,10 +140,11 @@ public class AnnotationConfigApplicationContextTests { try { context.getBean(TestBean.class); - } catch (RuntimeException ex) { + } + catch (NoSuchBeanDefinitionException ex) { assertThat(ex.getMessage(), allOf( - containsString("No unique bean of type [" + TestBean.class.getName() + "] is defined"), + containsString("No qualifying bean of type [" + TestBean.class.getName() + "] is defined"), containsString("tb1"), containsString("tb2") ) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java b/spring-context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java index 7baf82681eb..87999283a31 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/DestroyMethodInferenceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,21 @@ package org.springframework.context.annotation; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - import java.io.Closeable; import java.io.IOException; import org.junit.Test; + import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.GenericXmlApplicationContext; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +/** + * @author Chris Beams + * @author Juergen Hoeller + */ public class DestroyMethodInferenceTests { @Test @@ -39,6 +44,7 @@ public class DestroyMethodInferenceTests { WithInheritedCloseMethod c4 = ctx.getBean("c4", WithInheritedCloseMethod.class); WithInheritedCloseMethod c5 = ctx.getBean("c5", WithInheritedCloseMethod.class); WithNoCloseMethod c6 = ctx.getBean("c6", WithNoCloseMethod.class); + WithLocalShutdownMethod c7 = ctx.getBean("c7", WithLocalShutdownMethod.class); assertThat(c0.closed, is(false)); assertThat(c1.closed, is(false)); @@ -47,6 +53,7 @@ public class DestroyMethodInferenceTests { assertThat(c4.closed, is(false)); assertThat(c5.closed, is(false)); assertThat(c6.closed, is(false)); + assertThat(c7.closed, is(false)); ctx.close(); assertThat("c0", c0.closed, is(true)); assertThat("c1", c1.closed, is(true)); @@ -55,6 +62,7 @@ public class DestroyMethodInferenceTests { assertThat("c4", c4.closed, is(true)); assertThat("c5", c5.closed, is(true)); assertThat("c6", c6.closed, is(false)); + assertThat("c7", c7.closed, is(true)); } @Test @@ -121,6 +129,11 @@ public class DestroyMethodInferenceTests { public WithNoCloseMethod c6() { return new WithNoCloseMethod(); } + + @Bean + public WithLocalShutdownMethod c7() { + return new WithLocalShutdownMethod(); + } } @@ -149,4 +162,12 @@ public class DestroyMethodInferenceTests { static class WithNoCloseMethod { boolean closed = false; } + + static class WithLocalShutdownMethod { + boolean closed = false; + public void shutdown() { + closed = true; + } + } + } diff --git a/spring-context/src/test/java/org/springframework/context/support/EnvironmentSecurityManagerIntegrationTests.java b/spring-context/src/test/java/org/springframework/context/support/EnvironmentSecurityManagerIntegrationTests.java new file mode 100644 index 00000000000..42647392832 --- /dev/null +++ b/spring-context/src/test/java/org/springframework/context/support/EnvironmentSecurityManagerIntegrationTests.java @@ -0,0 +1,118 @@ +/* + * Copyright 2002-2013 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.context.support; + +import static java.lang.String.format; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.security.AccessControlException; +import java.security.Permission; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.Profile; +import org.springframework.core.env.AbstractEnvironment; +import org.springframework.core.env.StandardEnvironmentTests; +import org.springframework.stereotype.Component; + + +/** + * Tests integration between Environment and SecurityManagers. See SPR-9970. + * + * @author Chris Beams + */ +public class EnvironmentSecurityManagerIntegrationTests { + + private SecurityManager originalSecurityManager; + private Map env; + + @Before + public void setUp() { + originalSecurityManager = System.getSecurityManager(); + env = StandardEnvironmentTests.getModifiableSystemEnvironment(); + env.put(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME, "p1"); + } + + @After + public void tearDown() { + env.remove(AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME); + System.setSecurityManager(originalSecurityManager); + } + + @Test + public void securityManagerDisallowsAccessToSystemEnvironmentButAllowsAccessToIndividualKeys() { + SecurityManager securityManager = new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + // disallowing access to System#getenv means that our + // ReadOnlySystemAttributesMap will come into play. + if ("getenv.*".equals(perm.getName())) { + throw new AccessControlException( + "Accessing the system environment is disallowed"); + } + } + }; + System.setSecurityManager(securityManager); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(bf); + reader.register(C1.class); + assertThat(bf.containsBean("c1"), is(true)); + } + + @Test + public void securityManagerDisallowsAccessToSystemEnvironmentAndDisallowsAccessToIndividualKey() { + SecurityManager securityManager = new SecurityManager() { + @Override + public void checkPermission(Permission perm) { + // disallowing access to System#getenv means that our + // ReadOnlySystemAttributesMap will come into play. + if ("getenv.*".equals(perm.getName())) { + throw new AccessControlException( + "Accessing the system environment is disallowed"); + } + // disallowing access to the spring.profiles.active property means that + // the BeanDefinitionReader won't be able to determine which profiles are + // active. We should see an INFO-level message in the console about this + // and as a result, any components marked with a non-default profile will + // be ignored. + if (("getenv."+AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME).equals(perm.getName())) { + throw new AccessControlException( + format("Accessing system environment variable [%s] is disallowed", + AbstractEnvironment.ACTIVE_PROFILES_PROPERTY_NAME)); + } + } + }; + System.setSecurityManager(securityManager); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + AnnotatedBeanDefinitionReader reader = new AnnotatedBeanDefinitionReader(bf); + reader.register(C1.class); + assertThat(bf.containsBean("c1"), is(false)); + } + + @Component("c1") + @Profile("p1") + static class C1 { + } +} diff --git a/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java b/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java index 7e45ce98985..c0121a4a2dd 100644 --- a/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java +++ b/spring-context/src/test/java/org/springframework/context/support/ResourceBundleMessageSourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -340,6 +340,17 @@ public class ResourceBundleMessageSourceTests extends TestCase { assertEquals(0, filenames.size()); } + public void testMessageSourceResourceBundle() { + ResourceBundleMessageSource ms = new ResourceBundleMessageSource(); + ms.setBasename("org/springframework/context/support/messages"); + MessageSourceResourceBundle rbe = new MessageSourceResourceBundle(ms, Locale.ENGLISH); + assertEquals("message1", rbe.getString("code1")); + assertTrue(rbe.containsKey("code1")); + MessageSourceResourceBundle rbg = new MessageSourceResourceBundle(ms, Locale.GERMAN); + assertEquals("nachricht2", rbg.getString("code2")); + assertTrue(rbg.containsKey("code2")); + } + @Override protected void tearDown() throws Exception { if (JdkVersion.getMajorJavaVersion() >= JdkVersion.JAVA_16) { diff --git a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java index 087e8e95214..5698ed06518 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/DateFormattingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,10 +30,10 @@ import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.MutablePropertyValues; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; -import org.springframework.format.datetime.DateFormatterRegistrar; import org.springframework.format.support.FormattingConversionService; import org.springframework.validation.DataBinder; @@ -186,6 +186,14 @@ public class DateFormattingTests { assertEquals("10/31/09", binder.getBindingResult().getFieldValue("children[0].dateAnnotated")); } + @Test + public void dateToString() throws Exception { + Date date = new Date(); + Object actual = this.conversionService.convert(date, TypeDescriptor.valueOf(Date.class), TypeDescriptor.valueOf(String.class)); + String expected = new DateFormatter().print(date, Locale.US); + assertEquals(expected, actual); + } + @SuppressWarnings("unused") private static class SimpleDateBean { diff --git a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java index 597a3820b50..237df0509f3 100644 --- a/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java +++ b/spring-context/src/test/java/org/springframework/format/datetime/joda/JodaTimeFormattingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.junit.Test; import org.springframework.beans.MutablePropertyValues; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat.ISO; @@ -46,6 +47,7 @@ import static org.junit.Assert.*; /** * @author Keith Donald * @author Juergen Hoeller + * @author Phillip Webb */ public class JodaTimeFormattingTests { @@ -456,6 +458,14 @@ public class JodaTimeFormattingTests { assertEquals("2009-10-31T07:00:00.000-05:00", binder.getBindingResult().getFieldValue("mutableDateTimeAnnotated")); } + @Test + public void dateToString() throws Exception { + Date date = new Date(); + Object actual = this.conversionService.convert(date, TypeDescriptor.valueOf(Date.class), TypeDescriptor.valueOf(String.class)); + String expected = JodaTimeContextHolder.getFormatter(org.joda.time.format.DateTimeFormat.shortDateTime(), Locale.US).print(new DateTime(date)); + assertEquals(expected, actual); + } + @SuppressWarnings("unused") private static class JodaTimeBean { diff --git a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java index 2b0bbaeefcf..72da54f6f36 100644 --- a/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java +++ b/spring-context/src/test/java/org/springframework/jmx/export/assembler/MethodNameBasedMBeanInfoAssemblerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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 @@ -16,11 +16,13 @@ package org.springframework.jmx.export.assembler; +import javax.management.MBeanOperationInfo; import javax.management.modelmbean.ModelMBeanAttributeInfo; import javax.management.modelmbean.ModelMBeanInfo; /** * @author Rob Harrop + * @author David Boden */ public class MethodNameBasedMBeanInfoAssemblerTests extends AbstractJmxAssemblerTests { @@ -56,6 +58,13 @@ public class MethodNameBasedMBeanInfoAssemblerTests extends AbstractJmxAssembler assertFalse(attr.isWritable()); } + public void testSetNameParameterIsNamed() throws Exception { + ModelMBeanInfo info = getMBeanInfoFromAssembler(); + + MBeanOperationInfo operationSetAge = info.getOperation("setName"); + assertEquals("name", operationSetAge.getSignature()[0].getName()); + } + @Override protected String getApplicationContextPath() { return "org/springframework/jmx/export/assembler/methodNameAssembler.xml"; diff --git a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java index 7320cea1d19..ddaa2b0fcea 100644 --- a/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java +++ b/spring-context/src/test/java/org/springframework/scheduling/concurrent/ThreadPoolTaskSchedulerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,6 @@ package org.springframework.scheduling.concurrent; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - import java.util.Date; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; @@ -29,6 +24,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -36,6 +32,8 @@ import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.util.ErrorHandler; +import static org.junit.Assert.*; + /** * @author Mark Fisher * @since 3.0 @@ -44,7 +42,6 @@ public class ThreadPoolTaskSchedulerTests { private static final String THREAD_NAME_PREFIX = "test-"; - private final ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); @@ -54,6 +51,11 @@ public class ThreadPoolTaskSchedulerTests { scheduler.afterPropertiesSet(); } + @After + public void shutdownScheduler() { + scheduler.destroy(); + } + // test methods diff --git a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java index 5c2dad22a4b..3e6cac57598 100644 --- a/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java +++ b/spring-context/src/test/java/org/springframework/validation/beanvalidation/ValidatorFactoryTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintViolation; +import javax.validation.Payload; import javax.validation.Valid; import javax.validation.constraints.NotNull; @@ -37,6 +38,7 @@ import org.hibernate.validator.HibernateValidator; import org.junit.Test; import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.Errors; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; @@ -193,6 +195,18 @@ public class ValidatorFactoryTests { System.out.println(fieldError.getDefaultMessage()); } + @Test + public void testInnerBeanValidation() throws Exception { + LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); + validator.afterPropertiesSet(); + + MainBean mainBean = new MainBean(); + Errors errors = new BeanPropertyBindingResult(mainBean, "mainBean"); + validator.validate(mainBean, errors); + Object rejected = errors.getFieldValue("inner.value"); + assertNull(rejected); + } + @NameAddressValid public static class ValidPerson { @@ -242,7 +256,6 @@ public class ValidatorFactoryTests { } } - public static class ValidAddress { @NotNull @@ -257,7 +270,6 @@ public class ValidatorFactoryTests { } } - @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = NameAddressValidator.class) @@ -270,7 +282,6 @@ public class ValidatorFactoryTests { Class[] payload() default {}; } - public static class NameAddressValidator implements ConstraintValidator { @Override @@ -283,4 +294,53 @@ public class ValidatorFactoryTests { } } + + public static class MainBean { + + @InnerValid + private InnerBean inner = new InnerBean(); + + public InnerBean getInner() { + return inner; + } + } + + public static class InnerBean { + + private String value; + + public String getValue() { + return value; + } + public void setValue(String value) { + this.value = value; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + @Constraint(validatedBy=InnerValidator.class) + public static @interface InnerValid { + String message() default "NOT VALID"; + Class[] groups() default { }; + Class[] payload() default {}; + } + + public static class InnerValidator implements ConstraintValidator { + + @Override + public void initialize(InnerValid constraintAnnotation) { + } + + @Override + public boolean isValid(InnerBean bean, ConstraintValidatorContext context) { + context.disableDefaultConstraintViolation(); + if (bean.getValue() == null) { + context.buildConstraintViolationWithTemplate("NULL"). addNode("value").addConstraintViolation(); + return false; + } + return true; + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java index c16cdb39dc2..1027d4dfc9d 100644 --- a/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java +++ b/spring-core/src/main/java/org/springframework/core/BridgeMethodResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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 java.util.Arrays; import java.util.List; import java.util.Map; -import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ReflectionUtils; @@ -145,7 +144,7 @@ public abstract class BridgeMethodResolver { private static Method findGenericDeclaration(Method bridgeMethod) { // Search parent types for method that has same signature as bridge. Class superclass = bridgeMethod.getDeclaringClass().getSuperclass(); - while (!Object.class.equals(superclass)) { + while (superclass != null && !Object.class.equals(superclass)) { Method method = searchForMatch(superclass, bridgeMethod); if (method != null && !method.isBridge()) { return method; @@ -219,8 +218,6 @@ public abstract class BridgeMethodResolver { * @return whether signatures match as described */ public static boolean isVisibilityBridgeMethodPair(Method bridgeMethod, Method bridgedMethod) { - Assert.isTrue(bridgeMethod != null); - Assert.isTrue(bridgedMethod != null); if (bridgeMethod == bridgedMethod) { return true; } diff --git a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java index 588472532c2..3cab216bf03 100644 --- a/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java +++ b/spring-core/src/main/java/org/springframework/core/LocalVariableTableParameterNameDiscoverer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,15 +65,15 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD public String[] getParameterNames(Method method) { - Class declaringClass = method.getDeclaringClass(); + Method originalMethod = BridgeMethodResolver.findBridgedMethod(method); + Class declaringClass = originalMethod.getDeclaringClass(); Map map = this.parameterNamesCache.get(declaringClass); if (map == null) { - // initialize cache map = inspectClass(declaringClass); this.parameterNamesCache.put(declaringClass, map); } if (map != NO_DEBUG_INFO_MAP) { - return map.get(method); + return map.get(originalMethod); } return null; } @@ -82,14 +82,12 @@ public class LocalVariableTableParameterNameDiscoverer implements ParameterNameD Class declaringClass = ctor.getDeclaringClass(); Map map = this.parameterNamesCache.get(declaringClass); if (map == null) { - // initialize cache map = inspectClass(declaringClass); this.parameterNamesCache.put(declaringClass, map); } if (map != NO_DEBUG_INFO_MAP) { return map.get(ctor); } - return null; } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java index a24f2cde3ac..c9d1fe47c55 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationAwareOrderComparator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,10 @@ package org.springframework.core.annotation; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; @@ -37,7 +41,7 @@ public class AnnotationAwareOrderComparator extends OrderComparator { /** * Shared default instance of AnnotationAwareOrderComparator. */ - public static AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator(); + public static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator(); @Override @@ -46,7 +50,8 @@ public class AnnotationAwareOrderComparator extends OrderComparator { return ((Ordered) obj).getOrder(); } if (obj != null) { - Order order = obj.getClass().getAnnotation(Order.class); + Class clazz = (obj instanceof Class ? (Class) obj : obj.getClass()); + Order order = clazz.getAnnotation(Order.class); if (order != null) { return order.value(); } @@ -54,4 +59,31 @@ public class AnnotationAwareOrderComparator extends OrderComparator { return Ordered.LOWEST_PRECEDENCE; } + + /** + * Sort the given List with a default AnnotationAwareOrderComparator. + *

    Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param list the List to sort + * @see java.util.Collections#sort(java.util.List, java.util.Comparator) + */ + public static void sort(List list) { + if (list.size() > 1) { + Collections.sort(list, INSTANCE); + } + } + + /** + * Sort the given array with a default AnnotationAwareOrderComparator. + *

    Optimized to skip sorting for lists with size 0 or 1, + * in order to avoid unnecessary array extraction. + * @param array the array to sort + * @see java.util.Arrays#sort(Object[], java.util.Comparator) + */ + public static void sort(Object[] array) { + if (array.length > 1) { + Arrays.sort(array, INSTANCE); + } + } + } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index e3dce1b2b4d..a1701c9db13 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ package org.springframework.core.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.Arrays; + import java.util.Map; import java.util.WeakHashMap; @@ -255,8 +255,9 @@ public abstract class AnnotationUtils { * declared locally on the supplied {@code clazz}. The supplied {@link Class} * may represent any type. *

    Note: This method does not determine if the annotation is - * {@link java.lang.annotation.Inherited inherited}. For greater clarity regarding inherited - * annotations, consider using {@link #isAnnotationInherited(Class, Class)} instead. + * {@linkplain java.lang.annotation.Inherited inherited}. For greater clarity + * regarding inherited annotations, consider using + * {@link #isAnnotationInherited(Class, Class)} instead. * @param annotationType the Class object corresponding to the annotation type * @param clazz the Class object corresponding to the class on which to check for the annotation * @return {@code true} if an annotation for the specified {@code annotationType} @@ -268,7 +269,7 @@ public abstract class AnnotationUtils { Assert.notNull(annotationType, "Annotation type must not be null"); Assert.notNull(clazz, "Class must not be null"); boolean declaredLocally = false; - for (Annotation annotation : Arrays.asList(clazz.getDeclaredAnnotations())) { + for (Annotation annotation : clazz.getDeclaredAnnotations()) { if (annotation.annotationType().equals(annotationType)) { declaredLocally = true; break; @@ -279,16 +280,16 @@ public abstract class AnnotationUtils { /** * Determine whether an annotation for the specified {@code annotationType} is present - * on the supplied {@code clazz} and is {@link java.lang.annotation.Inherited inherited} - * i.e., not declared locally for the class). + * on the supplied {@code clazz} and is {@linkplain java.lang.annotation.Inherited inherited} + * (i.e., not declared locally for the class). *

    If the supplied {@code clazz} is an interface, only the interface itself will be checked. * In accordance with standard meta-annotation semantics, the inheritance hierarchy for interfaces - * will not be traversed. See the {@link java.lang.annotation.Inherited JavaDoc} for the - * @Inherited meta-annotation for further details regarding annotation inheritance. + * will not be traversed. See the {@linkplain java.lang.annotation.Inherited Javadoc} for the + * {@code @Inherited} meta-annotation for further details regarding annotation inheritance. * @param annotationType the Class object corresponding to the annotation type * @param clazz the Class object corresponding to the class on which to check for the annotation * @return {@code true} if an annotation for the specified {@code annotationType} is present - * on the supplied {@code clazz} and is {@link java.lang.annotation.Inherited inherited} + * on the supplied {@code clazz} and is inherited * @see Class#isAnnotationPresent(Class) * @see #isAnnotationDeclaredLocally(Class, Class) */ diff --git a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java index f320400e6a8..79c134e7cf0 100644 --- a/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java +++ b/spring-core/src/main/java/org/springframework/core/convert/TypeDescriptor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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.convert; import java.lang.annotation.Annotation; +import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.Collection; import java.util.HashMap; @@ -33,6 +34,8 @@ import org.springframework.util.ObjectUtils; * @author Keith Donald * @author Andy Clement * @author Juergen Hoeller + * @author Phillip Webb + * @author Sam Brannen * @since 3.0 */ public class TypeDescriptor { @@ -75,7 +78,8 @@ public class TypeDescriptor { /** * Create a new type descriptor from a {@link MethodParameter}. - * Use this constructor when a source or target conversion point is a constructor parameter, method parameter, or method return value. + *

    Use this constructor when a source or target conversion point is a + * constructor parameter, method parameter, or method return value. * @param methodParameter the method parameter */ public TypeDescriptor(MethodParameter methodParameter) { @@ -84,7 +88,7 @@ public class TypeDescriptor { /** * Create a new type descriptor from a {@link Field}. - * Use this constructor when source or target conversion point is a field. + *

    Use this constructor when a source or target conversion point is a field. * @param field the field */ public TypeDescriptor(Field field) { @@ -93,7 +97,8 @@ public class TypeDescriptor { /** * Create a new type descriptor from a {@link Property}. - * Use this constructor when a source or target conversion point is a property on a Java class. + *

    Use this constructor when a source or target conversion point is a + * property on a Java class. * @param property the property */ public TypeDescriptor(Property property) { @@ -103,8 +108,11 @@ public class TypeDescriptor { /** * Create a new type descriptor from the given type. - * Use this to instruct the conversion system to convert an object to a specific target type, when no type location such as a method parameter or field is available to provide additional conversion context. - * Generally prefer use of {@link #forObject(Object)} for constructing type descriptors from source objects, as it handles the null object case. + *

    Use this to instruct the conversion system to convert an object to a + * specific target type, when no type location such as a method parameter or + * field is available to provide additional conversion context. + *

    Generally prefer use of {@link #forObject(Object)} for constructing type + * descriptors from source objects, as it handles the {@code null} object case. * @param type the class * @return the type descriptor */ @@ -114,12 +122,15 @@ public class TypeDescriptor { } /** - * Create a new type descriptor from a java.util.Collection type. - * Useful for converting to typed Collections. - * For example, a List<String> could be converted to a List<EmailAddress> by converting to a targetType built with this method. - * The method call to construct such a TypeDescriptor would look something like: collection(List.class, TypeDescriptor.valueOf(EmailAddress.class)); + * Create a new type descriptor from a {@link java.util.Collection} type. + *

    Useful for converting to typed Collections. + *

    For example, a {@code List} could be converted to a + * {@code List} by converting to a targetType built with this method. + * The method call to construct such a {@code TypeDescriptor} would look something + * like: {@code collection(List.class, TypeDescriptor.valueOf(EmailAddress.class));} * @param collectionType the collection type, which must implement {@link Collection}. - * @param elementTypeDescriptor a descriptor for the collection's element type, used to convert collection elements + * @param elementTypeDescriptor a descriptor for the collection's element type, + * used to convert collection elements * @return the collection type descriptor */ public static TypeDescriptor collection(Class collectionType, TypeDescriptor elementTypeDescriptor) { @@ -130,9 +141,9 @@ public class TypeDescriptor { } /** - * Create a new type descriptor from a java.util.Map type. - * Useful for Converting to typed Maps. - * For example, a Map<String, String> could be converted to a Map<Id, EmailAddress> by converting to a targetType built with this method: + * Create a new type descriptor from a {@link java.util.Map} type. + *

    Useful for converting to typed Maps. + *

    For example, a Map<String, String> could be converted to a Map<Id, EmailAddress> by converting to a targetType built with this method: * The method call to construct such a TypeDescriptor would look something like: map(Map.class, TypeDescriptor.valueOf(Id.class), TypeDescriptor.valueOf(EmailAddress.class)); * @param mapType the map type, which must implement {@link Map} * @param keyTypeDescriptor a descriptor for the map's key type, used to convert map keys @@ -146,19 +157,44 @@ public class TypeDescriptor { return new TypeDescriptor(mapType, keyTypeDescriptor, valueTypeDescriptor); } + /** + * Create a new type descriptor as an array of the specified type. + *

    For example to create a {@code Map[]} use + * {@code TypeDescriptor.array(TypeDescriptor.map(Map.class, TypeDescriptor.value(String.class), TypeDescriptor.value(String.class)))}. + * @param elementTypeDescriptor the {@link TypeDescriptor} of the array element or {@code null} + * @return an array {@link TypeDescriptor} or {@code null} if {@code elementTypeDescriptor} is {@code null} + * @since 3.2.1 + */ + public static TypeDescriptor array(TypeDescriptor elementTypeDescriptor) { + if(elementTypeDescriptor == null) { + return null; + } + Class type = Array.newInstance(elementTypeDescriptor.getType(), 0).getClass(); + return new TypeDescriptor(type, elementTypeDescriptor, null, null, elementTypeDescriptor.getAnnotations()); + } + /** * Creates a type descriptor for a nested type declared within the method parameter. - * For example, if the methodParameter is a List<String> and the nestingLevel is 1, the nested type descriptor will be String.class. - * If the methodParameter is a List> and the nestingLevel is 2, the nested type descriptor will also be a String.class. - * If the methodParameter is a Map and the nesting level is 1, the nested type descriptor will be String, derived from the map value. - * If the methodParameter is a List> and the nesting level is 2, the nested type descriptor will be String, derived from the map value. - * Returns null if a nested type cannot be obtained because it was not declared. - * For example, if the method parameter is a List<?>, the nested type descriptor returned will be null. + *

    For example, if the methodParameter is a {@code List} and the + * nesting level is 1, the nested type descriptor will be String.class. + *

    If the methodParameter is a {@code List>} and the nesting + * level is 2, the nested type descriptor will also be a String.class. + *

    If the methodParameter is a {@code Map} and the nesting + * level is 1, the nested type descriptor will be String, derived from the map value. + *

    If the methodParameter is a {@code List>} and the + * nesting level is 2, the nested type descriptor will be String, derived from the map value. + *

    Returns {@code null} if a nested type cannot be obtained because it was not declared. + * For example, if the method parameter is a {@code List}, the nested type + * descriptor returned will be {@code null}. * @param methodParameter the method parameter with a nestingLevel of 1 - * @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the method parameter - * @return the nested type descriptor at the specified nesting level, or null if it could not be obtained - * @throws IllegalArgumentException if the nesting level of the input {@link MethodParameter} argument is not 1 - * @throws IllegalArgumentException if the types up to the specified nesting level are not of collection, array, or map types + * @param nestingLevel the nesting level of the collection/array element or + * map key/value declaration within the method parameter + * @return the nested type descriptor at the specified nesting level, or null + * if it could not be obtained + * @throws IllegalArgumentException if the nesting level of the input + * {@link MethodParameter} argument is not 1 + * @throws IllegalArgumentException if the types up to the specified nesting + * level are not of collection, array, or map types */ public static TypeDescriptor nested(MethodParameter methodParameter, int nestingLevel) { if (methodParameter.getNestingLevel() != 1) { @@ -169,16 +205,23 @@ public class TypeDescriptor { /** * Creates a type descriptor for a nested type declared within the field. - *

    For example, if the field is a {@code List<String>} and the nestingLevel is 1, the nested type descriptor will be {@code String.class}. - * If the field is a {@code List<List<String>>} and the nestingLevel is 2, the nested type descriptor will also be a {@code String.class}. - * If the field is a {@code Map<Integer, String>} and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. - * If the field is a {@code List<Map<Integer, String>>} and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value. - * Returns {@code null} if a nested type cannot be obtained because it was not declared. - * For example, if the field is a {@code List<?>}, the nested type descriptor returned will be {@code null}. + *

    For example, if the field is a {@code List} and the nesting + * level is 1, the nested type descriptor will be {@code String.class}. + *

    If the field is a {@code List>} and the nesting level is + * 2, the nested type descriptor will also be a {@code String.class}. + *

    If the field is a {@code Map} and the nesting level + * is 1, the nested type descriptor will be String, derived from the map value. + *

    If the field is a {@code List>} and the nesting + * level is 2, the nested type descriptor will be String, derived from the map value. + *

    Returns {@code null} if a nested type cannot be obtained because it was not declared. + * For example, if the field is a {@code List}, the nested type descriptor returned will be {@code null}. * @param field the field - * @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the field - * @return the nested type descriptor at the specified nestingLevel, or null if it could not be obtained - * @throws IllegalArgumentException if the types up to the specified nesting level are not of collection, array, or map types + * @param nestingLevel the nesting level of the collection/array element or + * map key/value declaration within the field + * @return the nested type descriptor at the specified nesting level, or null + * if it could not be obtained + * @throws IllegalArgumentException if the types up to the specified nesting + * level are not of collection, array, or map types */ public static TypeDescriptor nested(Field field, int nestingLevel) { return nested(new FieldDescriptor(field), nestingLevel); @@ -186,16 +229,24 @@ public class TypeDescriptor { /** * Creates a type descriptor for a nested type declared within the property. - *

    For example, if the property is a {@code List<String>} and the nestingLevel is 1, the nested type descriptor will be {@code String.class}. - * If the property is a {@code List<List<String>>} and the nestingLevel is 2, the nested type descriptor will also be a {@code String.class}. - * If the property is a {@code Map<Integer, String>} and the nestingLevel is 1, the nested type descriptor will be String, derived from the map value. - * If the property is a {@code List<Map<Integer, String>>} and the nestingLevel is 2, the nested type descriptor will be String, derived from the map value. - * Returns {@code null} if a nested type cannot be obtained because it was not declared. - * For example, if the property is a {@code List<?>}, the nested type descriptor returned will be {@code null}. + *

    For example, if the property is a {@code List} and the nesting + * level is 1, the nested type descriptor will be {@code String.class}. + *

    If the property is a {@code List>} and the nesting level + * is 2, the nested type descriptor will also be a {@code String.class}. + *

    If the property is a {@code Map} and the nesting level + * is 1, the nested type descriptor will be String, derived from the map value. + *

    If the property is a {@code List>} and the nesting + * level is 2, the nested type descriptor will be String, derived from the map value. + *

    Returns {@code null} if a nested type cannot be obtained because it was not declared. + * For example, if the property is a {@code List}, the nested type descriptor + * returned will be {@code null}. * @param property the property - * @param nestingLevel the nesting level of the collection/array element or map key/value declaration within the property - * @return the nested type descriptor at the specified nestingLevel, or {@code null} if it could not be obtained - * @throws IllegalArgumentException if the types up to the specified nesting level are not of collection, array, or map types + * @param nestingLevel the nesting level of the collection/array element or + * map key/value declaration within the property + * @return the nested type descriptor at the specified nesting level, or + * {@code null} if it could not be obtained + * @throws IllegalArgumentException if the types up to the specified nesting + * level are not of collection, array, or map types */ public static TypeDescriptor nested(Property property, int nestingLevel) { return nested(new BeanPropertyDescriptor(property), nestingLevel); @@ -203,8 +254,8 @@ public class TypeDescriptor { /** * Create a new type descriptor for an object. - * Use this factory method to introspect a source object before asking the conversion system to convert it to some another type. - * If the provided object is null, returns null, else calls {@link #valueOf(Class)} to build a TypeDescriptor from the object's class. + *

    Use this factory method to introspect a source object before asking the conversion system to convert it to some another type. + *

    If the provided object is null, returns null, else calls {@link #valueOf(Class)} to build a TypeDescriptor from the object's class. * @param source the source object * @return the type descriptor */ @@ -214,7 +265,7 @@ public class TypeDescriptor { /** * The type of the backing class, method parameter, field, or property described by this TypeDescriptor. - * Returns primitive types as-is. + *

    Returns primitive types as-is. *

    See {@link #getObjectType()} for a variation of this operation that resolves primitive types * to their corresponding Object types if necessary. * @return the type, or {@code null} @@ -235,7 +286,7 @@ public class TypeDescriptor { /** * Narrows this {@link TypeDescriptor} by setting its type to the class of the provided value. - * If the value is {@code null}, no narrowing is performed and this TypeDescriptor is returned unchanged. + *

    If the value is {@code null}, no narrowing is performed and this TypeDescriptor is returned unchanged. *

    Designed to be called by binding frameworks when they read property, field, or method return values. * Allows such frameworks to narrow a TypeDescriptor built from a declared property, field, or method return value type. * For example, a field declared as {@code java.lang.Object} would be narrowed to {@code java.util.HashMap} @@ -255,7 +306,6 @@ public class TypeDescriptor { /** * Cast this {@link TypeDescriptor} to a superclass or implemented interface * preserving annotations and nested type context. - * * @param superType the super type to cast to (can be {@code null} * @return a new TypeDescriptor for the up-cast type * @throws IllegalArgumentException if this type is not assignable to the super-type @@ -324,7 +374,7 @@ public class TypeDescriptor { /** * Returns true if an object of this type descriptor can be assigned to the location described by the given type descriptor. - * For example, valueOf(String.class).isAssignableTo(valueOf(CharSequence.class)) returns true because a String value can be assigned to a CharSequence variable. + *

    For example, valueOf(String.class).isAssignableTo(valueOf(CharSequence.class)) returns true because a String value can be assigned to a CharSequence variable. * On the other hand, valueOf(Number.class).isAssignableTo(valueOf(Integer.class)) returns false because, while all Integers are Numbers, not all Numbers are Integers. *

    * For arrays, collections, and maps, element and key/value types are checked if declared. @@ -382,10 +432,10 @@ public class TypeDescriptor { /** * If this type is a {@link Collection} or an Array, creates a element TypeDescriptor from the provided collection or array element. - * Narrows the {@link #getElementTypeDescriptor() elementType} property to the class of the provided collection or array element. + *

    Narrows the {@link #getElementTypeDescriptor() elementType} property to the class of the provided collection or array element. * For example, if this describes a java.util.List<java.lang.Number< and the element argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer. * If this describes a java.util.List<?> and the element argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well. - * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. + *

    Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. * @param element the collection or array element * @return a element type descriptor, narrowed to the type of the provided element * @throws IllegalStateException if this type is not a java.util.Collection or Array type @@ -417,10 +467,10 @@ public class TypeDescriptor { /** * If this type is a {@link Map}, creates a mapKey {@link TypeDescriptor} from the provided map key. - * Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property to the class of the provided map key. + *

    Narrows the {@link #getMapKeyTypeDescriptor() mapKeyType} property to the class of the provided map key. * For example, if this describes a java.util.Map<java.lang.Number, java.lang.String< and the key argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer. - * If this describes a java.util.Map<?, ?> and the key argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well. - * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. + *

    If this describes a java.util.Map<?, ?> and the key argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well. + *

    Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. * @param mapKey the map key * @return the map key type descriptor * @throws IllegalStateException if this type is not a java.util.Map @@ -432,7 +482,7 @@ public class TypeDescriptor { /** * If this type is a {@link Map} and its value type is parameterized, returns the map's value type. - * If the Map's value type is not parameterized, returns null indicating the value type is not declared. + *

    If the Map's value type is not parameterized, returns null indicating the value type is not declared. * @return the Map value type, or {@code null} if this type is a Map but its value type is not parameterized * @throws IllegalStateException if this type is not a java.util.Map */ @@ -443,10 +493,10 @@ public class TypeDescriptor { /** * If this type is a {@link Map}, creates a mapValue {@link TypeDescriptor} from the provided map value. - * Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property to the class of the provided map value. + *

    Narrows the {@link #getMapValueTypeDescriptor() mapValueType} property to the class of the provided map value. * For example, if this describes a java.util.Map<java.lang.String, java.lang.Number< and the value argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer. * If this describes a java.util.Map<?, ?> and the value argument is a java.lang.Integer, the returned TypeDescriptor will be java.lang.Integer as well. - * Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. + *

    Annotation and nested type context will be preserved in the narrowed TypeDescriptor that is returned. * @param mapValue the map value * @return the map value type descriptor * @throws IllegalStateException if this type is not a java.util.Map diff --git a/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java index 8da8c15d8b5..492d142d372 100644 --- a/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/PropertiesPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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,13 +31,12 @@ import java.util.Properties; * * @author Chris Beams * @since 3.1 - * @see org.springframework.mock.env.MockPropertySource */ public class PropertiesPropertySource extends MapPropertySource { @SuppressWarnings({ "unchecked", "rawtypes" }) public PropertiesPropertySource(String name, Properties source) { - super(name, (Map)source); + super(name, (Map) source); } } diff --git a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java index df310c6002b..5ded8cbbc5e 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java +++ b/spring-core/src/main/java/org/springframework/core/env/ReadOnlySystemAttributesMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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,17 +17,21 @@ package org.springframework.core.env; import java.util.Collection; +import java.util.Collections; import java.util.Map; import java.util.Set; import org.springframework.util.Assert; /** - * Read-only {@code Map} implementation that is backed by system properties or environment - * variables. + * Read-only {@code Map} implementation that is backed by system + * properties or environment variables. * - *

    Used by {@link AbstractApplicationContext} when a {@link SecurityManager} prohibits access to {@link - * System#getProperties()} or {@link System#getenv()}. + *

    Used by {@link AbstractApplicationContext} when a {@link SecurityManager} prohibits + * access to {@link System#getProperties()} or {@link System#getenv()}. It is for this + * reason that the implementations of {@link #keySet()}, {@link #entrySet()}, and + * {@link #values()} always return empty even though {@link #get(Object)} may in fact + * return non-null if the current security manager allows access to individual keys. * * @author Arjen Poutsma * @author Chris Beams @@ -85,7 +89,7 @@ abstract class ReadOnlySystemAttributesMap implements Map { } public Set keySet() { - throw new UnsupportedOperationException(); + return Collections.emptySet(); } public void putAll(Map m) { @@ -93,11 +97,11 @@ abstract class ReadOnlySystemAttributesMap implements Map { } public Collection values() { - throw new UnsupportedOperationException(); + return Collections.emptySet(); } public Set> entrySet() { - throw new UnsupportedOperationException(); + return Collections.emptySet(); } } diff --git a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java index 66eeb1e5bb0..09082aad5c2 100644 --- a/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/env/SystemEnvironmentPropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ public class SystemEnvironmentPropertySource extends MapPropertySource { */ @Override public boolean containsProperty(String name) { - return resolvePropertyName(name) != null; + return getProperty(name) != null; } /** @@ -102,8 +102,8 @@ public class SystemEnvironmentPropertySource extends MapPropertySource { /** * Check to see if this property source contains a property with the given name, or - * any underscore / uppercase variation thereof. Return the resolved name or - * {@code null} if none found. + * any underscore / uppercase variation thereof. Return the resolved name if one is + * found or otherwise the original name. Never returns {@code null}. */ private String resolvePropertyName(String name) { if (super.containsProperty(name)) { @@ -127,6 +127,6 @@ public class SystemEnvironmentPropertySource extends MapPropertySource { } } - return null; + return name; } } diff --git a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java index e881d4034c3..3e8467178d3 100644 --- a/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/ClassPathResource.java @@ -205,18 +205,14 @@ public class ClassPathResource extends AbstractFileResolvingResource { */ public String getDescription() { StringBuilder builder = new StringBuilder("class path resource ["); - String pathToUse = path; - if (this.clazz != null && !pathToUse.startsWith("/")) { builder.append(ClassUtils.classPackageAsResourcePath(this.clazz)); builder.append('/'); } - if (pathToUse.startsWith("/")) { pathToUse = pathToUse.substring(1); } - builder.append(pathToUse); builder.append(']'); return builder.toString(); @@ -232,9 +228,9 @@ public class ClassPathResource extends AbstractFileResolvingResource { } if (obj instanceof ClassPathResource) { ClassPathResource otherRes = (ClassPathResource) obj; - return (this.path.equals(otherRes.path) - && ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) && ObjectUtils.nullSafeEquals( - this.clazz, otherRes.clazz)); + return (this.path.equals(otherRes.path) && + ObjectUtils.nullSafeEquals(this.classLoader, otherRes.classLoader) && + ObjectUtils.nullSafeEquals(this.clazz, otherRes.clazz)); } return false; } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java index 405a16e2a88..04a173836c1 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/EncodedResource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 +17,10 @@ package org.springframework.core.io.support; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.Charset; import org.springframework.core.io.Resource; import org.springframework.util.Assert; @@ -39,7 +41,9 @@ public class EncodedResource { private final Resource resource; - private final String encoding; + private String encoding; + + private Charset charset; /** @@ -48,7 +52,8 @@ public class EncodedResource { * @param resource the Resource to hold */ public EncodedResource(Resource resource) { - this(resource, null); + Assert.notNull(resource, "Resource must not be null"); + this.resource = resource; } /** @@ -63,6 +68,18 @@ public class EncodedResource { this.encoding = encoding; } + /** + * Create a new EncodedResource for the given Resource, + * using the specified encoding. + * @param resource the Resource to hold + * @param charset the charset to use for reading from the resource + */ + public EncodedResource(Resource resource, Charset charset) { + Assert.notNull(resource, "Resource must not be null"); + this.resource = resource; + this.charset = charset; + } + /** * Return the Resource held. @@ -79,13 +96,36 @@ public class EncodedResource { return this.encoding; } + /** + * Return the charset to use for reading from the resource, + * or {@code null} if none specified. + */ + public final Charset getCharset() { + return this.charset; + } + + + /** + * Determine whether a {@link Reader} is required as opposed to an {@link InputStream}, + * i.e. whether an encoding or a charset has been specified. + * @see #getReader() + * @see #getInputStream() + */ + public boolean requiresReader() { + return (this.encoding != null || this.charset != null); + } + /** * Open a {@code java.io.Reader} for the specified resource, * using the specified encoding (if any). * @throws IOException if opening the Reader failed + * @see #requiresReader() */ public Reader getReader() throws IOException { - if (this.encoding != null) { + if (this.charset != null) { + return new InputStreamReader(this.resource.getInputStream(), this.charset); + } + else if (this.encoding != null) { return new InputStreamReader(this.resource.getInputStream(), this.encoding); } else { @@ -93,6 +133,16 @@ public class EncodedResource { } } + /** + * Open an {@code java.io.InputStream} for the specified resource, + * typically assuming that there is no specific encoding to use. + * @throws IOException if opening the InputStream failed + * @see #requiresReader() + */ + public InputStream getInputStream() throws IOException { + return this.resource.getInputStream(); + } + @Override public boolean equals(Object obj) { diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java index d99e69806e6..1e68ba23d88 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 +17,6 @@ package org.springframework.core.io.support; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.Properties; import org.apache.commons.logging.Log; @@ -39,9 +37,6 @@ import org.springframework.util.PropertiesPersister; */ public abstract class PropertiesLoaderSupport { - public static final String XML_FILE_EXTENSION = ".xml"; - - /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @@ -167,7 +162,7 @@ public abstract class PropertiesLoaderSupport { /** * Load properties into the given instance. * @param props the Properties instance to load into - * @throws java.io.IOException in case of I/O errors + * @throws IOException in case of I/O errors * @see #setLocations */ protected void loadProperties(Properties props) throws IOException { @@ -176,21 +171,9 @@ public abstract class PropertiesLoaderSupport { if (logger.isInfoEnabled()) { logger.info("Loading properties file from " + location); } - InputStream is = null; try { - is = location.getInputStream(); - String filename = location.getFilename(); - if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { - this.propertiesPersister.loadFromXml(props, is); - } - else { - if (this.fileEncoding != null) { - this.propertiesPersister.load(props, new InputStreamReader(is, this.fileEncoding)); - } - else { - this.propertiesPersister.load(props, is); - } - } + PropertiesLoaderUtils.fillProperties( + props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister); } catch (IOException ex) { if (this.ignoreResourceNotFound) { @@ -202,11 +185,6 @@ public abstract class PropertiesLoaderSupport { throw ex; } } - finally { - if (is != null) { - is.close(); - } - } } } } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java index 6f3308d8308..503f2bb1bb5 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PropertiesLoaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,6 +18,7 @@ package org.springframework.core.io.support; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; import java.net.URL; import java.net.URLConnection; import java.util.Enumeration; @@ -26,6 +27,8 @@ import java.util.Properties; import org.springframework.core.io.Resource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.DefaultPropertiesPersister; +import org.springframework.util.PropertiesPersister; import org.springframework.util.ResourceUtils; /** @@ -42,11 +45,76 @@ import org.springframework.util.ResourceUtils; */ public abstract class PropertiesLoaderUtils { + private static final String XML_FILE_EXTENSION = ".xml"; + + /** - * Load properties from the given resource. + * Load properties from the given EncodedResource, + * potentially defining a specific encoding for the properties file. + * @see #fillProperties(java.util.Properties, EncodedResource) + */ + public static Properties loadProperties(EncodedResource resource) throws IOException { + Properties props = new Properties(); + fillProperties(props, resource); + return props; + } + + /** + * Fill the given properties from the given EncodedResource, + * potentially defining a specific encoding for the properties file. + * @param props the Properties instance to load into + * @param resource the resource to load from + * @throws IOException in case of I/O errors + */ + public static void fillProperties(Properties props, EncodedResource resource) + throws IOException { + + fillProperties(props, resource, new DefaultPropertiesPersister()); + } + + /** + * Actually load properties from the given EncodedResource into the given Properties instance. + * @param props the Properties instance to load into + * @param resource the resource to load from + * @param persister the PropertiesPersister to use + * @throws IOException in case of I/O errors + */ + static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister) + throws IOException { + + InputStream stream = null; + Reader reader = null; + try { + String filename = resource.getResource().getFilename(); + if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { + stream = resource.getInputStream(); + persister.loadFromXml(props, stream); + } + else if (resource.requiresReader()) { + reader = resource.getReader(); + persister.load(props, reader); + } + else { + stream = resource.getInputStream(); + persister.load(props, stream); + } + } + finally { + if (stream != null) { + stream.close(); + } + if (reader != null) { + reader.close(); + } + } + } + + /** + * Load properties from the given resource (in ISO-8859-1 encoding). * @param resource the resource to load from * @return the populated Properties instance * @throws IOException if loading failed + * @see #fillProperties(java.util.Properties, Resource) */ public static Properties loadProperties(Resource resource) throws IOException { Properties props = new Properties(); @@ -55,7 +123,7 @@ public abstract class PropertiesLoaderUtils { } /** - * Fill the given properties from the given resource. + * Fill the given properties from the given resource (in ISO-8859-1 encoding). * @param props the Properties instance to fill * @param resource the resource to load from * @throws IOException if loading failed @@ -63,7 +131,13 @@ public abstract class PropertiesLoaderUtils { public static void fillProperties(Properties props, Resource resource) throws IOException { InputStream is = resource.getInputStream(); try { - props.load(is); + String filename = resource.getFilename(); + if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) { + props.loadFromXML(is); + } + else { + props.load(is); + } } finally { is.close(); @@ -71,8 +145,8 @@ public abstract class PropertiesLoaderUtils { } /** - * Load all properties from the given class path resource, - * using the default class loader. + * Load all properties from the specified class path resource + * (in ISO-8859-1 encoding), using the default class loader. *

    Merges properties if more than one resource of the same name * found in the class path. * @param resourceName the name of the class path resource @@ -84,8 +158,8 @@ public abstract class PropertiesLoaderUtils { } /** - * Load all properties from the given class path resource, - * using the given class loader. + * Load all properties from the specified class path resource + * (in ISO-8859-1 encoding), using the given class loader. *

    Merges properties if more than one resource of the same name * found in the class path. * @param resourceName the name of the class path resource @@ -100,24 +174,26 @@ public abstract class PropertiesLoaderUtils { if (clToUse == null) { clToUse = ClassUtils.getDefaultClassLoader(); } - Properties properties = new Properties(); + Properties props = new Properties(); Enumeration urls = clToUse.getResources(resourceName); while (urls.hasMoreElements()) { URL url = (URL) urls.nextElement(); - InputStream is = null; + URLConnection con = url.openConnection(); + ResourceUtils.useCachesIfNecessary(con); + InputStream is = con.getInputStream(); try { - URLConnection con = url.openConnection(); - ResourceUtils.useCachesIfNecessary(con); - is = con.getInputStream(); - properties.load(is); - } - finally { - if (is != null) { - is.close(); + if (resourceName != null && resourceName.endsWith(XML_FILE_EXTENSION)) { + props.loadFromXML(is); + } + else { + props.load(is); } } + finally { + is.close(); + } } - return properties; + return props; } } diff --git a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java index f67a4579705..d07955456cc 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/ResourcePropertySource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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,30 +17,51 @@ package org.springframework.core.io.support; import java.io.IOException; -import java.io.InputStream; import java.util.Properties; import org.springframework.core.env.PropertiesPropertySource; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** * Subclass of {@link PropertiesPropertySource} that loads a {@link Properties} * object from a given {@link org.springframework.core.io.Resource} or resource location such as - * {@code "classpath:/com/myco/foo.properties"} or {@code "file:/path/to/file.properties"}. + * {@code "classpath:/com/myco/foo.properties"} or {@code "file:/path/to/file.xml"}. + * Both traditional and XML-based properties file formats are supported, however in order + * for XML processing to take effect, the underlying {@code Resource}'s + * {@link org.springframework.core.io.Resource#getFilename() getFilename()} method must + * return non-{@code null} and end in ".xml". * * @author Chris Beams + * @author Juergen Hoeller * @since 3.1 */ public class ResourcePropertySource extends PropertiesPropertySource { /** * Create a PropertySource having the given name based on Properties - * loaded from the given resource. + * loaded from the given encoded resource. + */ + public ResourcePropertySource(String name, EncodedResource resource) throws IOException { + super(name, PropertiesLoaderUtils.loadProperties(resource)); + } + + /** + * Create a PropertySource based on Properties loaded from the given resource. + * The name of the PropertySource will be generated based on the + * {@link Resource#getDescription() description} of the given resource. + */ + public ResourcePropertySource(EncodedResource resource) throws IOException { + this(getNameForResource(resource.getResource()), resource); + } + + /** + * Create a PropertySource having the given name based on Properties + * loaded from the given encoded resource. */ public ResourcePropertySource(String name, Resource resource) throws IOException { - super(name, loadPropertiesForResource(resource)); + super(name, PropertiesLoaderUtils.loadProperties(new EncodedResource(resource))); } /** @@ -58,17 +79,7 @@ public class ResourcePropertySource extends PropertiesPropertySource { * resource (assuming it is prefixed with {@code classpath:}). */ public ResourcePropertySource(String name, String location, ClassLoader classLoader) throws IOException { - this(name, getResourceForLocation(location, classLoader)); - } - - /** - * Create a PropertySource having the given name based on Properties loaded from - * the given resource location. The default thread context class loader will be - * used to load the resource (assuming the location string is prefixed with - * {@code classpath:}. - */ - public ResourcePropertySource(String name, String location) throws IOException { - this(name, location, ClassUtils.getDefaultClassLoader()); + this(name, new DefaultResourceLoader(classLoader).getResource(location)); } /** @@ -79,7 +90,17 @@ public class ResourcePropertySource extends PropertiesPropertySource { * resource. */ public ResourcePropertySource(String location, ClassLoader classLoader) throws IOException { - this(getResourceForLocation(location, classLoader)); + this(new DefaultResourceLoader(classLoader).getResource(location)); + } + + /** + * Create a PropertySource having the given name based on Properties loaded from + * the given resource location. The default thread context class loader will be + * used to load the resource (assuming the location string is prefixed with + * {@code classpath:}. + */ + public ResourcePropertySource(String name, String location) throws IOException { + this(name, new DefaultResourceLoader().getResource(location)); } /** @@ -88,28 +109,12 @@ public class ResourcePropertySource extends PropertiesPropertySource { * {@link Resource#getDescription() description} of the resource. */ public ResourcePropertySource(String location) throws IOException { - this(getResourceForLocation(location, ClassUtils.getDefaultClassLoader())); + this(new DefaultResourceLoader().getResource(location)); } - private static Resource getResourceForLocation(String location, ClassLoader classLoader) { - return new PathMatchingResourcePatternResolver(classLoader).getResource(location); - } - - private static Properties loadPropertiesForResource(Resource resource) throws IOException { - Properties props = new Properties(); - InputStream is = resource.getInputStream(); - props.load(is); - try { - is.close(); - } catch (IOException ex) { - // ignore - } - return props; - } - /** - * Returns the description string for the resource, and if empty returns + * Return the description string for the resource, and if empty returns * the class name of the resource plus its identity hash code. */ private static String getNameForResource(Resource resource) { diff --git a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java index bbceda14dc5..0ae86e08792 100644 --- a/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java +++ b/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -60,12 +60,18 @@ public class AntPathMatcher implements PathMatcher { private final Map stringMatcherCache = new ConcurrentHashMap(256); + private boolean trimTokens = true; + /** Set the path separator to use for pattern parsing. Default is "/", as in Ant. */ public void setPathSeparator(String pathSeparator) { this.pathSeparator = (pathSeparator != null ? pathSeparator : DEFAULT_PATH_SEPARATOR); } + /** Whether to trim tokenized paths and patterns. */ + public void setTrimTokens(boolean trimTokens) { + this.trimTokens = trimTokens; + } public boolean isPattern(String path) { return (path.indexOf('*') != -1 || path.indexOf('?') != -1); @@ -95,8 +101,8 @@ public class AntPathMatcher implements PathMatcher { return false; } - String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator); - String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator); + String[] pattDirs = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, this.trimTokens, true); + String[] pathDirs = StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); int pattIdxStart = 0; int pattIdxEnd = pattDirs.length - 1; @@ -246,8 +252,8 @@ public class AntPathMatcher implements PathMatcher { * does not enforce this. */ public String extractPathWithinPattern(String pattern, String path) { - String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator); - String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator); + String[] patternParts = StringUtils.tokenizeToStringArray(pattern, this.pathSeparator, this.trimTokens, true); + String[] pathParts = StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); StringBuilder builder = new StringBuilder(); diff --git a/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java index 19f2698c367..1337a5a5440 100644 --- a/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java +++ b/spring-core/src/main/java/org/springframework/util/DefaultPropertiesPersister.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 +33,8 @@ import java.util.Properties; * *

    Allows for reading from any Reader and writing to any Writer, for example * to specify a charset for a properties file. This is a capability that standard - * {@code java.util.Properties} unfortunately lacks up until JDK 1.5: - * You can only load files using the ISO-8859-1 charset there. + * {@code java.util.Properties} unfortunately lacked up until JDK 1.5: + * You were only able to load files using the ISO-8859-1 charset there. * *

    Loading from and storing to a stream delegates to {@code Properties.load} * and {@code Properties.store}, respectively, to be fully compatible with @@ -49,20 +49,11 @@ import java.util.Properties; * an encoding for a Reader/Writer (like ReloadableResourceBundleMessageSource's * "defaultEncoding" and "fileEncodings" properties). * - *

    As of Spring 1.2.2, this implementation also supports properties XML files, - * through the {@code loadFromXml} and {@code storeToXml} methods. - * The default implementations delegate to JDK 1.5's corresponding methods, - * throwing an exception if running on an older JDK. Those implementations - * could be subclassed to apply custom XML handling on JDK 1.4, for example. - * * @author Juergen Hoeller * @since 10.03.2004 * @see java.util.Properties * @see java.util.Properties#load * @see java.util.Properties#store - * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setPropertiesPersister - * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setDefaultEncoding - * @see org.springframework.context.support.ReloadableResourceBundleMessageSource#setFileEncodings */ public class DefaultPropertiesPersister implements PropertiesPersister { @@ -228,30 +219,15 @@ public class DefaultPropertiesPersister implements PropertiesPersister { public void loadFromXml(Properties props, InputStream is) throws IOException { - try { - props.loadFromXML(is); - } - catch (NoSuchMethodError err) { - throw new IOException("Cannot load properties XML file - not running on JDK 1.5+: " + err.getMessage()); - } + props.loadFromXML(is); } public void storeToXml(Properties props, OutputStream os, String header) throws IOException { - try { - props.storeToXML(os, header); - } - catch (NoSuchMethodError err) { - throw new IOException("Cannot store properties XML file - not running on JDK 1.5+: " + err.getMessage()); - } + props.storeToXML(os, header); } public void storeToXml(Properties props, OutputStream os, String header, String encoding) throws IOException { - try { - props.storeToXML(os, header, encoding); - } - catch (NoSuchMethodError err) { - throw new IOException("Cannot store properties XML file - not running on JDK 1.5+: " + err.getMessage()); - } + props.storeToXML(os, header, encoding); } } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java index 9bdd19f54b9..47634bb7604 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/AnnotationAwareOrderComparatorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.springframework.core.annotation; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; -import org.junit.Test; - /** - * Unit tests for {@link AnnotationAwareOrderComparator}. - * + * @author Juergen Hoeller * @author Oliver Gierke */ public class AnnotationAwareOrderComparatorTests { @@ -31,4 +34,34 @@ public class AnnotationAwareOrderComparatorTests { public void instanceVariableIsAnAnnotationAwareOrderComparator() { assertThat(AnnotationAwareOrderComparator.INSTANCE, is(instanceOf(AnnotationAwareOrderComparator.class))); } + + @Test + public void sortInstances() { + List list = new ArrayList<>(); + list.add(new B()); + list.add(new A()); + AnnotationAwareOrderComparator.sort(list); + assertTrue(list.get(0) instanceof A); + assertTrue(list.get(1) instanceof B); + } + + @Test + public void sortClasses() { + List list = new ArrayList<>(); + list.add(B.class); + list.add(A.class); + AnnotationAwareOrderComparator.sort(list); + assertEquals(A.class, list.get(0)); + assertEquals(B.class, list.get(1)); + } + + + @Order(1) + private static class A { + } + + @Order(2) + private static class B { + } + } diff --git a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java index 90e2bf893b6..6597ab46a78 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/TypeDescriptorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -36,6 +37,7 @@ import static org.junit.Assert.*; /** * @author Keith Donald * @author Andy Clement + * @author Phillip Webb */ @SuppressWarnings("rawtypes") public class TypeDescriptorTests { @@ -849,4 +851,23 @@ public class TypeDescriptorTests { assertEquals(TypeDescriptor.forObject(new CustomMap()).getMapValueTypeDescriptor(), TypeDescriptor.valueOf(Integer.class)); } + @Test + public void createMapArray() throws Exception { + TypeDescriptor mapType = TypeDescriptor.map(LinkedHashMap.class, TypeDescriptor.valueOf(String.class), TypeDescriptor.valueOf(Integer.class)); + TypeDescriptor arrayType = TypeDescriptor.array(mapType); + assertEquals(arrayType.getType(), LinkedHashMap[].class); + assertEquals(arrayType.getElementTypeDescriptor(), mapType); + } + + + @Test + public void createStringArray() throws Exception { + TypeDescriptor arrayType = TypeDescriptor.array(TypeDescriptor.valueOf(String.class)); + assertEquals(arrayType, TypeDescriptor.valueOf(String[].class)); + } + + @Test + public void createNullArray() throws Exception { + assertNull(TypeDescriptor.array(null)); + } } diff --git a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java index 3dde4907053..7b2ff0490a1 100644 --- a/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java +++ b/spring-core/src/test/java/org/springframework/core/convert/support/DefaultConversionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +16,14 @@ package org.springframework.core.convert.support; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import java.awt.Color; import java.math.BigDecimal; @@ -773,6 +780,30 @@ public class DefaultConversionTests { assertEquals(new Long(1), e.getId()); } + @Test + public void convertCharArrayToString() throws Exception { + String converted = conversionService.convert(new char[] { 'a', 'b', 'c' }, String.class); + assertThat(converted, equalTo("a,b,c")); + } + + @Test + public void convertStringToCharArray() throws Exception { + char[] converted = conversionService.convert("a,b,c", char[].class); + assertThat(converted, equalTo(new char[] { 'a', 'b', 'c' })); + } + + @Test + public void convertStringToCustomCharArray() throws Exception { + conversionService.addConverter(new Converter() { + @Override + public char[] convert(String source) { + return source.toCharArray(); + } + }); + char[] converted = conversionService.convert("abc", char[].class); + assertThat(converted, equalTo(new char[] { 'a', 'b', 'c' })); + } + public static class TestEntity { private Long id; diff --git a/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java new file mode 100644 index 00000000000..88d9347dfa1 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/env/DummyEnvironment.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2013 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.env; + +public class DummyEnvironment implements Environment { + + public boolean containsProperty(String key) { + return false; + } + + public String getProperty(String key) { + return null; + } + + public String getProperty(String key, String defaultValue) { + return null; + } + + public T getProperty(String key, Class targetType) { + return null; + } + + public T getProperty(String key, Class targetType, T defaultValue) { + return null; + } + + public Class getPropertyAsClass(String key, Class targetType) { + return null; + } + + public String getRequiredProperty(String key) throws IllegalStateException { + return null; + } + + public T getRequiredProperty(String key, Class targetType) + throws IllegalStateException { + return null; + } + + public String resolvePlaceholders(String text) { + return null; + } + + public String resolveRequiredPlaceholders(String text) + throws IllegalArgumentException { + return null; + } + + public String[] getActiveProfiles() { + return null; + } + + public String[] getDefaultProfiles() { + return null; + } + + public boolean acceptsProfiles(String... profiles) { + return false; + } + +} diff --git a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java index 8cec8f813d5..131917c8ccf 100644 --- a/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/StandardEnvironmentTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -456,7 +456,7 @@ public class StandardEnvironmentTests { } @SuppressWarnings("unchecked") - private static Map getModifiableSystemEnvironment() { + public static Map getModifiableSystemEnvironment() { // for os x / linux Class[] classes = Collections.class.getDeclaredClasses(); Map env = System.getenv(); diff --git a/spring-core/src/test/java/org/springframework/core/io/example.xml b/spring-core/src/test/java/org/springframework/core/io/example.xml new file mode 100644 index 00000000000..1d638537e10 --- /dev/null +++ b/spring-core/src/test/java/org/springframework/core/io/example.xml @@ -0,0 +1,6 @@ + + + + bar + + diff --git a/spring-core/src/test/java/org/springframework/core/io/support/ResourcePropertySourceTests.java b/spring-core/src/test/java/org/springframework/core/io/support/ResourcePropertySourceTests.java index 1b77a9f542c..1bbc3cb2a1c 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/ResourcePropertySourceTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/ResourcePropertySourceTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 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,10 @@ public class ResourcePropertySourceTests { private static final String PROPERTIES_LOCATION = "classpath:" + PROPERTIES_PATH; private static final String PROPERTIES_RESOURCE_DESCRIPTION = "class path resource [" + PROPERTIES_PATH + "]"; + private static final String XML_PROPERTIES_PATH = "org/springframework/core/io/example.xml"; + private static final String XML_PROPERTIES_LOCATION = "classpath:" + XML_PROPERTIES_PATH; + private static final String XML_PROPERTIES_RESOURCE_DESCRIPTION = "class path resource [" + XML_PROPERTIES_PATH + "]"; + @Test public void withLocationAndGeneratedName() throws IOException { PropertySource ps = new ResourcePropertySource(PROPERTIES_LOCATION); @@ -46,6 +50,13 @@ public class ResourcePropertySourceTests { assertThat(ps.getName(), is(PROPERTIES_RESOURCE_DESCRIPTION)); } + @Test + public void xmlWithLocationAndGeneratedName() throws IOException { + PropertySource ps = new ResourcePropertySource(XML_PROPERTIES_LOCATION); + assertEquals(ps.getProperty("foo"), "bar"); + assertThat(ps.getName(), is(XML_PROPERTIES_RESOURCE_DESCRIPTION)); + } + @Test public void withLocationAndExplicitName() throws IOException { PropertySource ps = new ResourcePropertySource("ps1", PROPERTIES_LOCATION); diff --git a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java index e960a62ea65..0a34e1c470c 100644 --- a/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java +++ b/spring-core/src/test/java/org/springframework/util/AntPathMatcherTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 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. @@ -542,6 +542,14 @@ public class AntPathMatcherTests { paths.clear(); } + // SPR-8687 + @Test + public void trimTokensOff() { + pathMatcher.setTrimTokens(false); + + assertTrue(pathMatcher.match("/group/{groupName}/members", "/group/sales/members")); + assertTrue(pathMatcher.match("/group/{groupName}/members", "/group/ sales/members")); + } } diff --git a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java index 6b791a154be..7d555076ba4 100644 --- a/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java +++ b/spring-core/src/test/java/org/springframework/util/ReflectionUtilsTests.java @@ -16,15 +16,6 @@ package org.springframework.util; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -33,10 +24,18 @@ import java.rmi.RemoteException; import java.util.LinkedList; import java.util.List; +import org.hamcrest.Matchers; + import org.junit.Ignore; import org.junit.Test; + +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; import org.springframework.tests.sample.objects.TestObject; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.*; + /** * @author Rob Harrop * @author Juergen Hoeller @@ -347,6 +346,43 @@ public class ReflectionUtilsTests { assertFalse(ObjectUtils.containsElement(methods, Parent.class.getMethod("m1"))); } + @Test + public void getUniqueDeclaredMethods_isFastEnough() { + Assume.group(TestGroup.PERFORMANCE); + + @SuppressWarnings("unused") + class C { + void m00() { } void m01() { } void m02() { } void m03() { } void m04() { } + void m05() { } void m06() { } void m07() { } void m08() { } void m09() { } + void m10() { } void m11() { } void m12() { } void m13() { } void m14() { } + void m15() { } void m16() { } void m17() { } void m18() { } void m19() { } + void m20() { } void m21() { } void m22() { } void m23() { } void m24() { } + void m25() { } void m26() { } void m27() { } void m28() { } void m29() { } + void m30() { } void m31() { } void m32() { } void m33() { } void m34() { } + void m35() { } void m36() { } void m37() { } void m38() { } void m39() { } + void m40() { } void m41() { } void m42() { } void m43() { } void m44() { } + void m45() { } void m46() { } void m47() { } void m48() { } void m49() { } + void m50() { } void m51() { } void m52() { } void m53() { } void m54() { } + void m55() { } void m56() { } void m57() { } void m58() { } void m59() { } + void m60() { } void m61() { } void m62() { } void m63() { } void m64() { } + void m65() { } void m66() { } void m67() { } void m68() { } void m69() { } + void m70() { } void m71() { } void m72() { } void m73() { } void m74() { } + void m75() { } void m76() { } void m77() { } void m78() { } void m79() { } + void m80() { } void m81() { } void m82() { } void m83() { } void m84() { } + void m85() { } void m86() { } void m87() { } void m88() { } void m89() { } + void m90() { } void m91() { } void m92() { } void m93() { } void m94() { } + void m95() { } void m96() { } void m97() { } void m98() { } void m99() { } + } + + StopWatch sw = new StopWatch(); + sw.start(); + Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(C.class); + sw.stop(); + long totalMs = sw.getTotalTimeMillis(); + assertThat(methods.length, Matchers.greaterThan(100)); + assertThat(totalMs, Matchers.lessThan(10L)); + } + private static class ListSavingMethodCallback implements ReflectionUtils.MethodCallback { private List methodNames = new LinkedList(); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java index a7f457e147d..d9ae1a2efb1 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/SpelMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -108,7 +108,8 @@ public enum SpelMessage { OPERAND_NOT_INCREMENTABLE(Kind.ERROR,1066,"the expression component ''{0}'' does not support increment"), // OPERAND_NOT_DECREMENTABLE(Kind.ERROR,1067,"the expression component ''{0}'' does not support decrement"), // NOT_ASSIGNABLE(Kind.ERROR,1068,"the expression component ''{0}'' is not assignable"), // - ; + MISSING_CHARACTER(Kind.ERROR,1069,"missing expected character ''{0}''"), + LEFT_OPERAND_PROBLEM(Kind.ERROR,1070, "Problem parsing left operand"); private Kind kind; private int code; diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java index be8e988157b..c243b5d7f4b 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/InternalSpelExpressionParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -37,6 +37,7 @@ import org.springframework.util.StringUtils; * Hand written SpEL parser. Instances are reusable but are not thread safe. * * @author Andy Clement + * @author Phillip Webb * @since 3.0 */ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { @@ -104,8 +105,8 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { Token t = peekToken(); if (t.kind==TokenKind.ASSIGN) { // a=b if (expr==null) { - expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1)); - } + expr = new NullLiteral(toPos(t.startpos-1,t.endpos-1)); + } nextToken(); SpelNodeImpl assignedValue = eatLogicalOrExpression(); return new Assign(toPos(t),expr,assignedValue); @@ -139,7 +140,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { while (peekIdentifierToken("or") || peekToken(TokenKind.SYMBOLIC_OR)) { Token t = nextToken(); //consume OR SpelNodeImpl rhExpr = eatLogicalAndExpression(); - checkRightOperand(t,rhExpr); + checkOperands(t,expr,rhExpr); expr = new OpOr(toPos(t),expr,rhExpr); } return expr; @@ -151,7 +152,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { while (peekIdentifierToken("and") || peekToken(TokenKind.SYMBOLIC_AND)) { Token t = nextToken();// consume 'AND' SpelNodeImpl rhExpr = eatRelationalExpression(); - checkRightOperand(t,rhExpr); + checkOperands(t,expr,rhExpr); expr = new OpAnd(toPos(t),expr,rhExpr); } return expr; @@ -164,7 +165,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { if (relationalOperatorToken != null) { Token t = nextToken(); //consume relational operator token SpelNodeImpl rhExpr = eatSumExpression(); - checkRightOperand(t,rhExpr); + checkOperands(t,expr,rhExpr); TokenKind tk = relationalOperatorToken.kind; if (relationalOperatorToken.isNumericRelationalOperator()) { int pos = toPos(t); @@ -217,7 +218,7 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { while (peekToken(TokenKind.STAR,TokenKind.DIV,TokenKind.MOD)) { Token t = nextToken(); // consume STAR/DIV/MOD SpelNodeImpl rhExpr = eatPowerIncDecExpression(); - checkRightOperand(t,rhExpr); + checkOperands(t,expr,rhExpr); if (t.kind==TokenKind.STAR) { expr = new OpMultiply(toPos(t),expr,rhExpr); } else if (t.kind==TokenKind.DIV) { @@ -836,6 +837,17 @@ class InternalSpelExpressionParser extends TemplateAwareExpressionParser { } } + private void checkOperands(Token token, SpelNodeImpl left, SpelNodeImpl right) { + checkLeftOperand(token, left); + checkRightOperand(token, right); + } + + private void checkLeftOperand(Token token, SpelNodeImpl operandExpression) { + if (operandExpression==null) { + raiseInternalException(token.startpos,SpelMessage.LEFT_OPERAND_PROBLEM); + } + } + private void checkRightOperand(Token token, SpelNodeImpl operandExpression) { if (operandExpression==null) { raiseInternalException(token.startpos,SpelMessage.RIGHT_OPERAND_PROBLEM); diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java index 396c8f17ffc..4223a405424 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/standard/Tokenizer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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 org.springframework.util.Assert; * Lex some input data into a stream of tokens that can then be parsed. * * @author Andy Clement + * @author Phillip Webb * @since 3.0 */ class Tokenizer { @@ -137,14 +138,20 @@ class Tokenizer { } break; case '&': - if (isTwoCharToken(TokenKind.SYMBOLIC_AND)) { - pushPairToken(TokenKind.SYMBOLIC_AND); + if (!isTwoCharToken(TokenKind.SYMBOLIC_AND)) { + throw new InternalParseException(new SpelParseException( + expressionString, pos, + SpelMessage.MISSING_CHARACTER, "&")); } + pushPairToken(TokenKind.SYMBOLIC_AND); break; case '|': - if (isTwoCharToken(TokenKind.SYMBOLIC_OR)) { - pushPairToken(TokenKind.SYMBOLIC_OR); + if (!isTwoCharToken(TokenKind.SYMBOLIC_OR)) { + throw new InternalParseException(new SpelParseException( + expressionString, pos, + SpelMessage.MISSING_CHARACTER, "|")); } + pushPairToken(TokenKind.SYMBOLIC_OR); break; case '?': if (isTwoCharToken(TokenKind.SELECT)) { diff --git a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java index 97d87d50c3b..6c5b249114d 100644 --- a/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java +++ b/spring-expression/src/main/java/org/springframework/expression/spel/support/ReflectivePropertyAccessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,8 @@ import java.lang.reflect.Field; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Comparator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -42,6 +44,7 @@ import org.springframework.util.StringUtils; * * @author Andy Clement * @author Juergen Hoeller + * @author Phillip Webb * @since 3.0 */ public class ReflectivePropertyAccessor implements PropertyAccessor { @@ -284,7 +287,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { private Method findGetterForProperty(String propertyName, Class clazz, Object target) { Method method = findGetterForProperty(propertyName, clazz, target instanceof Class); - if(method == null && target instanceof Class) { + if (method == null && target instanceof Class) { method = findGetterForProperty(propertyName, target.getClass(), false); } return method; @@ -292,7 +295,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { private Method findSetterForProperty(String propertyName, Class clazz, Object target) { Method method = findSetterForProperty(propertyName, clazz, target instanceof Class); - if(method == null && target instanceof Class) { + if (method == null && target instanceof Class) { method = findSetterForProperty(propertyName, target.getClass(), false); } return method; @@ -300,7 +303,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { private Field findField(String name, Class clazz, Object target) { Field field = findField(name, clazz, target instanceof Class); - if(field == null && target instanceof Class) { + if (field == null && target instanceof Class) { field = findField(name, target.getClass(), false); } return field; @@ -310,13 +313,13 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { * Find a getter method for the specified property. */ protected Method findGetterForProperty(String propertyName, Class clazz, boolean mustBeStatic) { - Method[] ms = clazz.getMethods(); + Method[] ms = getSortedClassMethods(clazz); String propertyMethodSuffix = getPropertyMethodSuffix(propertyName); // Try "get*" method... String getterName = "get" + propertyMethodSuffix; for (Method method : ms) { - if (!method.isBridge() && method.getName().equals(getterName) && method.getParameterTypes().length == 0 && + if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 && (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { return method; } @@ -324,7 +327,7 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { // Try "is*" method... getterName = "is" + propertyMethodSuffix; for (Method method : ms) { - if (!method.isBridge() && method.getName().equals(getterName) && method.getParameterTypes().length == 0 && + if (method.getName().equals(getterName) && method.getParameterTypes().length == 0 && (boolean.class.equals(method.getReturnType()) || Boolean.class.equals(method.getReturnType())) && (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { return method; @@ -337,10 +340,10 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { * Find a setter method for the specified property. */ protected Method findSetterForProperty(String propertyName, Class clazz, boolean mustBeStatic) { - Method[] methods = clazz.getMethods(); + Method[] methods = getSortedClassMethods(clazz); String setterName = "set" + getPropertyMethodSuffix(propertyName); for (Method method : methods) { - if (!method.isBridge() && method.getName().equals(setterName) && method.getParameterTypes().length == 1 && + if (method.getName().equals(setterName) && method.getParameterTypes().length == 1 && (!mustBeStatic || Modifier.isStatic(method.getModifiers()))) { return method; } @@ -348,6 +351,19 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { return null; } + /** + * Returns class methods ordered with non bridge methods appearing higher. + */ + private Method[] getSortedClassMethods(Class clazz) { + Method[] methods = clazz.getMethods(); + Arrays.sort(methods, new Comparator() { + public int compare(Method o1, Method o2) { + return (o1.isBridge() == o2.isBridge()) ? 0 : (o1.isBridge() ? 1 : -1); + } + }); + return methods; + } + protected String getPropertyMethodSuffix(String propertyName) { if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) { return propertyName; @@ -367,6 +383,20 @@ public class ReflectivePropertyAccessor implements PropertyAccessor { return field; } } + // We'll search superclasses and implemented interfaces explicitly, + // although it shouldn't be necessary - however, see SPR-10125. + if (clazz.getSuperclass() != null) { + Field field = findField(name, clazz.getSuperclass(), mustBeStatic); + if (field != null) { + return field; + } + } + for (Class implementedInterface : clazz.getInterfaces()) { + Field field = findField(name, implementedInterface, mustBeStatic); + if (field != null) { + return field; + } + } return null; } diff --git a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java index e1122ab04f9..765422302a7 100644 --- a/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java +++ b/spring-expression/src/test/java/org/springframework/expression/spel/SpelReproTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,13 @@ package org.springframework.expression.spel; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; @@ -26,8 +33,9 @@ import java.util.Map; import java.util.Properties; import org.junit.Ignore; +import org.junit.Rule; import org.junit.Test; - +import org.junit.rules.ExpectedException; import org.springframework.core.convert.TypeDescriptor; import org.springframework.expression.AccessException; import org.springframework.expression.BeanResolver; @@ -48,17 +56,19 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardTypeLocator; import org.springframework.expression.spel.testresources.le.div.mod.reserved.Reserver; -import static org.junit.Assert.*; - /** * Reproduction tests cornering various SpEL JIRA issues. * * @author Andy Clement * @author Juergen Hoeller * @author Clark Duplichien + * @author Phillip Webb */ public class SpelReproTests extends ExpressionTestCase { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test public void testNPE_SPR5661() { evaluate("joinThreeStrings('a',null,'c')", "anullc", String.class); @@ -1656,6 +1666,15 @@ public class SpelReproTests extends ExpressionTestCase { assertEquals(Integer.class, value.getTypeDescriptor().getType()); } + @Test + public void SPR_10162_onlyBridgeMethodTest() throws Exception { + ReflectivePropertyAccessor accessor = new ReflectivePropertyAccessor(); + StandardEvaluationContext context = new StandardEvaluationContext(); + Object target = new OnlyBridgeMethod(); + TypedValue value = accessor.read(context, target , "property"); + assertEquals(Integer.class, value.getTypeDescriptor().getType()); + } + @Test public void SPR_10091_simpleTestValueType() { ExpressionParser parser = new SpelExpressionParser(); @@ -1684,6 +1703,33 @@ public class SpelReproTests extends ExpressionTestCase { Object value = parser.parseExpression("primitiveProperty").getValue(evaluationContext); } + @Test + public void SPR_10146_malformedExpressions() throws Exception { + doTestSpr10146("/foo", "EL1070E:(pos 0): Problem parsing left operand"); + doTestSpr10146("*foo", "EL1070E:(pos 0): Problem parsing left operand"); + doTestSpr10146("%foo", "EL1070E:(pos 0): Problem parsing left operand"); + doTestSpr10146("foo", "EL1070E:(pos 0): Problem parsing left operand"); + doTestSpr10146("&&foo", "EL1070E:(pos 0): Problem parsing left operand"); + doTestSpr10146("||foo", "EL1070E:(pos 0): Problem parsing left operand"); + doTestSpr10146("&foo", "EL1069E:(pos 0): missing expected character '&'"); + doTestSpr10146("|foo", "EL1069E:(pos 0): missing expected character '|'"); + } + + private void doTestSpr10146(String expression, String expectedMessage) { + thrown.expect(SpelParseException.class); + thrown.expectMessage(expectedMessage); + new SpelExpressionParser().parseExpression(expression); + } + + @Test + public void SPR_10125() throws Exception { + StandardEvaluationContext context = new StandardEvaluationContext(); + String fromInterface = parser.parseExpression("T("+StaticFinalImpl1.class.getName()+").VALUE").getValue(context, String.class); + assertThat(fromInterface, is("interfaceValue")); + String fromClass = parser.parseExpression("T("+StaticFinalImpl2.class.getName()+").VALUE").getValue(context, String.class); + assertThat(fromClass, is("interfaceValue")); + } public static class BooleanHolder { @@ -1722,4 +1768,27 @@ public class SpelReproTests extends ExpressionTestCase { } } + static class PackagePrivateClassWithGetter { + + public Integer getProperty() { + return null; + } + } + + public static class OnlyBridgeMethod extends PackagePrivateClassWithGetter { + + } + + public static interface StaticFinal { + public static final String VALUE = "interfaceValue"; + } + + public abstract static class AbstractStaticFinal implements StaticFinal { + } + + public static class StaticFinalImpl1 extends AbstractStaticFinal implements StaticFinal { + } + + public static class StaticFinalImpl2 extends AbstractStaticFinal { + } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java index 7efe962c642..6ba84b72b82 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/PreparedStatementCreatorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -146,7 +146,7 @@ public class PreparedStatementCreatorFactory { * Return a new PreparedStatementSetter for the given parameters. * @param params list of parameters (may be {@code null}) */ - public PreparedStatementSetter newPreparedStatementSetter(List params) { + public PreparedStatementSetter newPreparedStatementSetter(List params) { return new PreparedStatementCreatorImpl(params != null ? params : Collections.emptyList()); } @@ -225,7 +225,7 @@ public class PreparedStatementCreatorFactory { } public PreparedStatement createPreparedStatement(Connection con) throws SQLException { - PreparedStatement ps = null; + PreparedStatement ps; if (generatedKeysColumnNames != null || returnGeneratedKeys) { try { if (generatedKeysColumnNames != null) { @@ -235,10 +235,10 @@ public class PreparedStatementCreatorFactory { ps = con.prepareStatement(this.actualSql, PreparedStatement.RETURN_GENERATED_KEYS); } } - catch (AbstractMethodError ex) { + catch (AbstractMethodError err) { throw new InvalidDataAccessResourceUsageException( - "The JDBC driver is not compliant to JDBC 3.0 and thus " + - "does not support retrieval of auto-generated keys", ex); + "Your JDBC driver is not compliant with JDBC 3.0 - " + + "it does not support retrieval of auto-generated keys", err); } } else if (resultSetType == ResultSet.TYPE_FORWARD_ONLY && !updatableResults) { @@ -263,7 +263,7 @@ public class PreparedStatementCreatorFactory { int sqlColIndx = 1; for (int i = 0; i < this.parameters.size(); i++) { Object in = this.parameters.get(i); - SqlParameter declaredParameter = null; + SqlParameter declaredParameter; // SqlParameterValue overrides declared parameter metadata, in particular for // independence from the declared parameter position in case of named parameters. if (in instanceof SqlParameterValue) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java index 1e6a263ca4a..ea8e74501e9 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/core/StatementCreatorUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,6 +22,7 @@ import java.math.BigInteger; import java.sql.Blob; import java.sql.Clob; import java.sql.DatabaseMetaData; +import java.sql.ParameterMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Types; @@ -189,7 +190,7 @@ public abstract class StatementCreatorUtils { if (inValue instanceof SqlParameterValue) { SqlParameterValue parameterValue = (SqlParameterValue) inValue; if (logger.isDebugEnabled()) { - logger.debug("Overriding typeinfo with runtime info from SqlParameterValue: column index " + paramIndex + + logger.debug("Overriding type info with runtime info from SqlParameterValue: column index " + paramIndex + ", SQL type " + parameterValue.getSqlType() + ", Type name " + parameterValue.getTypeName()); } @@ -228,18 +229,31 @@ public abstract class StatementCreatorUtils { boolean useSetObject = false; sqlType = Types.NULL; try { - DatabaseMetaData dbmd = ps.getConnection().getMetaData(); - String databaseProductName = dbmd.getDatabaseProductName(); - String jdbcDriverName = dbmd.getDriverName(); - if (databaseProductName.startsWith("Informix") || - jdbcDriverName.startsWith("Microsoft SQL Server")) { - useSetObject = true; + ParameterMetaData pmd = null; + try { + pmd = ps.getParameterMetaData(); } - else if (databaseProductName.startsWith("DB2") || - jdbcDriverName.startsWith("jConnect") || - jdbcDriverName.startsWith("SQLServer")|| - jdbcDriverName.startsWith("Apache Derby")) { - sqlType = Types.VARCHAR; + catch (Throwable ex) { + // JDBC driver not compliant with JDBC 3.0 + // -> proceed with database-specific checks + } + if (pmd != null) { + sqlType = pmd.getParameterType(paramIndex); + } + else { + DatabaseMetaData dbmd = ps.getConnection().getMetaData(); + String databaseProductName = dbmd.getDatabaseProductName(); + String jdbcDriverName = dbmd.getDriverName(); + if (databaseProductName.startsWith("Informix") || + jdbcDriverName.startsWith("Microsoft SQL Server")) { + useSetObject = true; + } + else if (databaseProductName.startsWith("DB2") || + jdbcDriverName.startsWith("jConnect") || + jdbcDriverName.startsWith("SQLServer")|| + jdbcDriverName.startsWith("Apache Derby")) { + sqlType = Types.VARCHAR; + } } } catch (Throwable ex) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java index 00aedb4c223..c7e7e19918d 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/AbstractDriverBasedDataSource.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -139,14 +139,18 @@ public abstract class AbstractDriverBasedDataSource extends AbstractDataSource { * @see java.sql.Driver#connect(String, java.util.Properties) */ protected Connection getConnectionFromDriver(String username, String password) throws SQLException { - Properties props = new Properties(getConnectionProperties()); + Properties mergedProps = new Properties(); + Properties connProps = getConnectionProperties(); + if (connProps != null) { + mergedProps.putAll(connProps); + } if (username != null) { - props.setProperty("user", username); + mergedProps.setProperty("user", username); } if (password != null) { - props.setProperty("password", password); + mergedProps.setProperty("password", password); } - return getConnectionFromDriver(props); + return getConnectionFromDriver(mergedProps); } /** diff --git a/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml b/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml index 50a5167aedd..8b06d1eb627 100644 --- a/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml +++ b/spring-jdbc/src/main/resources/org/springframework/jdbc/support/sql-error-codes.xml @@ -151,7 +151,7 @@ 1062 - 630,839,840,893,1169,1215,1216,1217,1451,1452,1557 + 630,839,840,893,1169,1215,1216,1217,1364,1451,1452,1557 1 diff --git a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.2.xsd b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.2.xsd index be338f0fc0d..c5ac3bb32d6 100644 --- a/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.2.xsd +++ b/spring-jms/src/main/resources/org/springframework/jms/config/spring-jms-3.2.xsd @@ -225,7 +225,7 @@ ]]> - + - + - + Contains an implementation of Spring's transaction SPI for local Hibernate transactions. * This package is intentionally rather minimal, with no template classes or the like, - * in order to follow native Hibernate recommendations as closely as possible. + * in order to follow Hibernate recommendations as closely as possible. We recommend + * using Hibernate's native sessionFactory.getCurrentSession() style. * *

    This package supports Hibernate 4.x only. * See the {@code org.springframework.orm.hibernate3} package for Hibernate 3.x support. + * Note: Do not use HibernateTemplate or other classes from the hibernate3 package + * with Hibernate 4; this will lead to class definition exceptions at runtime. * */ package org.springframework.orm.hibernate4; diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java index dcf15059d3a..1c7353697ad 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate3/LocalCacheProviderProxy.java @@ -30,7 +30,10 @@ import org.hibernate.cache.CacheProvider; * @author Juergen Hoeller * @since 2.5.1 * @see LocalSessionFactoryBean#setCacheProvider + * @deprecated as of Spring 3.0, following Hibernate 3.3's deprecation + * of the CacheProvider SPI */ +@Deprecated public class LocalCacheProviderProxy implements CacheProvider { private final CacheProvider cacheProvider; diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java index 9419950001e..044cb72be38 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/EntityManagerFactoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,10 +76,12 @@ public abstract class EntityManagerFactoryUtils { * Find an EntityManagerFactory with the given name in the given * Spring application context (represented as ListableBeanFactory). *

    The specified unit name will be matched against the configured - * peristence unit, provided that a discovered EntityManagerFactory + * persistence unit, provided that a discovered EntityManagerFactory * implements the {@link EntityManagerFactoryInfo} interface. If not, * the persistence unit name will be matched against the Spring bean name, * assuming that the EntityManagerFactory bean names follow that convention. + *

    If no unit name has been given, this method will search for a default + * EntityManagerFactory through {@link ListableBeanFactory#getBean(Class)}. * @param beanFactory the ListableBeanFactory to search * @param unitName the name of the persistence unit (may be {@code null} or empty, * in which case a single bean of type EntityManagerFactory will be searched for) @@ -108,7 +110,8 @@ public abstract class EntityManagerFactoryUtils { return beanFactory.getBean(unitName, EntityManagerFactory.class); } else { - return BeanFactoryUtils.beanOfType(beanFactory, EntityManagerFactory.class); + // Find unique EntityManagerFactory bean in the context, falling back to parent contexts. + return beanFactory.getBean(EntityManagerFactory.class); } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java index ac9c408f02c..39c437e8cdf 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/support/PersistenceAnnotationBeanPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.NoUniqueBeanDefinitionException; import org.springframework.beans.factory.annotation.InjectionMetadata; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; @@ -60,7 +61,6 @@ import org.springframework.orm.jpa.ExtendedEntityManagerCreator; import org.springframework.orm.jpa.SharedEntityManagerCreator; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; /** * BeanPostProcessor that processes {@link javax.persistence.PersistenceUnit} @@ -534,10 +534,11 @@ public class PersistenceAnnotationBeanPostProcessor } return emf; } + else if (beanNames.length > 1) { + throw new NoUniqueBeanDefinitionException(EntityManagerFactory.class, beanNames); + } else { - throw new NoSuchBeanDefinitionException( - EntityManagerFactory.class, "expected single bean but found " + beanNames.length + ": " + - StringUtils.arrayToCommaDelimitedString(beanNames)); + throw new NoSuchBeanDefinitionException(EntityManagerFactory.class); } } diff --git a/spring-oxm/src/main/java/org/springframework/oxm/castor/package-info.java b/spring-oxm/src/main/java/org/springframework/oxm/castor/package-info.java index 42f8cdade28..1f1167ab09e 100644 --- a/spring-oxm/src/main/java/org/springframework/oxm/castor/package-info.java +++ b/spring-oxm/src/main/java/org/springframework/oxm/castor/package-info.java @@ -1,7 +1,7 @@ /** * - * Package providing integration of Castor within Springs O/X Mapping + * Package providing integration of Castor within Springs O/X Mapping * support * */ diff --git a/spring-oxm/src/test/resources/org/springframework/oxm/castor/mapping.xml b/spring-oxm/src/test/resources/org/springframework/oxm/castor/mapping.xml index e85971251c2..f54e293fdf6 100644 --- a/spring-oxm/src/test/resources/org/springframework/oxm/castor/mapping.xml +++ b/spring-oxm/src/test/resources/org/springframework/oxm/castor/mapping.xml @@ -1,5 +1,5 @@ - + Castor generated mapping file @@ -28,4 +28,4 @@ xmlns:tns="http://samples.springframework.org/flight"/> - \ No newline at end of file + diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java index 5b89cef1a7a..1bcb49a5b99 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package org.springframework.test.web.client.match; import static org.springframework.test.util.AssertionErrors.assertEquals; -import static org.springframework.test.util.MatcherAssertionErrors.assertThat; import static org.springframework.test.util.AssertionErrors.assertTrue; +import static org.springframework.test.util.MatcherAssertionErrors.assertThat; import java.io.IOException; @@ -72,6 +72,29 @@ public class ContentRequestMatchers { }; } + /** + * Assert the request content type is compatible with the given + * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}. + */ + public RequestMatcher contentTypeCompatibleWith(String contentType) { + return contentTypeCompatibleWith(MediaType.parseMediaType(contentType)); + } + + /** + * Assert the request content type is compatible with the given + * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}. + */ + public RequestMatcher contentTypeCompatibleWith(final MediaType contentType) { + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws IOException, AssertionError { + MediaType actualContentType = request.getHeaders().getContentType(); + assertTrue("Content type not set", actualContentType != null); + assertTrue("Content type [" + actualContentType + "] is not compatible with [" + contentType + "]", + actualContentType.isCompatibleWith(contentType)); + } + }; + } + /** * Get the body of the request as a UTF-8 string and appply the given {@link Matcher}. */ diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java index a074ff6d147..b18fc3554fa 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvc.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ import org.springframework.util.Assert; * * WebApplicationContext wac = ...; * - * MockMvc mockMvc = webAppContextSetup(wac).configureWarRootDir("src/main/webapp", false).build() + * MockMvc mockMvc = webAppContextSetup(wac).build(); * * mockMvc.perform(get("/form")) * .andExpect(status().isOk()) diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java index 75a50db9df7..d2e1054a636 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/MockMvcBuilderSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,11 +43,12 @@ public abstract class MockMvcBuilderSupport { protected final MockMvc createMockMvc(Filter[] filters, MockServletConfig servletConfig, WebApplicationContext webAppContext, RequestBuilder defaultRequestBuilder, - List globalResultMatchers, List globalResultHandlers) { + List globalResultMatchers, List globalResultHandlers, Boolean dispatchOptions) { ServletContext servletContext = webAppContext.getServletContext(); TestDispatcherServlet dispatcherServlet = new TestDispatcherServlet(webAppContext); + dispatcherServlet.setDispatchOptionsRequest(dispatchOptions); try { dispatcherServlet.init(servletConfig); } diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java index da96fd9cd22..bb0c109a099 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -80,6 +80,17 @@ public abstract class MockMvcRequestBuilders { return new MockHttpServletRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVariables); } + /** + * Create a {@link MockHttpServletRequestBuilder} for an OPTIONS request. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables) { + return new MockHttpServletRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVariables); + } + + /** * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP method. * diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java index b39875baaa5..c71dc98b7fd 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/result/ContentResultMatchers.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +54,9 @@ public class ContentResultMatchers { } /** - * Assert the ServletResponse content type. + * Assert the ServletResponse content type. The given content type must + * fully match including type, sub-type, and parameters. For checking + * only the type and sub-type see {@link #contentTypeCompatibleWith(String)}. */ public ResultMatcher contentType(String contentType) { return contentType(MediaType.parseMediaType(contentType)); @@ -62,6 +64,9 @@ public class ContentResultMatchers { /** * Assert the ServletResponse content type after parsing it as a MediaType. + * The given content type must fully match including type, sub-type, and + * parameters. For checking only the type and sub-type see + * {@link #contentTypeCompatibleWith(MediaType)}. */ public ResultMatcher contentType(final MediaType contentType) { return new ResultMatcher() { @@ -73,6 +78,30 @@ public class ContentResultMatchers { }; } + /** + * Assert the ServletResponse content type is compatible with the given + * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}. + */ + public ResultMatcher contentTypeCompatibleWith(String contentType) { + return contentTypeCompatibleWith(MediaType.parseMediaType(contentType)); + } + + /** + * Assert the ServletResponse content type is compatible with the given + * content type as defined by {@link MediaType#isCompatibleWith(MediaType)}. + */ + public ResultMatcher contentTypeCompatibleWith(final MediaType contentType) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String actual = result.getResponse().getContentType(); + assertTrue("Content type not set", actual != null); + MediaType actualContentType = MediaType.parseMediaType(actual); + assertTrue("Content type [" + actual + "] is not compatible with [" + contentType + "]", + actualContentType.isCompatibleWith(contentType)); + } + }; + } + /** * Assert the character encoding in the ServletResponse. * @see HttpServletResponse#getCharacterEncoding() diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java index a97b38aeee2..a44f43a816c 100644 --- a/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/servlet/setup/DefaultMockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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.test.web.servlet.ResultHandler; import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; /** * An concrete implementation of {@link MockMvcBuilder} with methods for @@ -54,6 +55,8 @@ public class DefaultMockMvcBuilder extends MockMvcB private final List globalResultHandlers = new ArrayList(); + private Boolean dispatchOptions = Boolean.FALSE; + /** * Protected constructor. Not intended for direct instantiation. @@ -178,6 +181,17 @@ public class DefaultMockMvcBuilder extends MockMvcB return (T) this; } + /** + * Should the {@link DispatcherServlet} dispatch OPTIONS request to controllers. + * @param dispatchOptions + * @see DispatcherServlet#setDispatchOptionsRequest(boolean) + */ + @SuppressWarnings("unchecked") + public final T dispatchOptions(boolean dispatchOptions) { + this.dispatchOptions = dispatchOptions; + return (T) this; + } + /** * Build a {@link MockMvc} instance. */ @@ -191,7 +205,7 @@ public class DefaultMockMvcBuilder extends MockMvcB Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]); return super.createMockMvc(filterArray, mockServletConfig, this.webAppContext, - this.defaultRequestBuilder, this.globalResultMatchers, this.globalResultHandlers); + this.defaultRequestBuilder, this.globalResultMatchers, this.globalResultHandlers,this.dispatchOptions); } /** diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/Spr10093Tests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/Spr10093Tests.java new file mode 100644 index 00000000000..32713e951c7 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/Spr10093Tests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2013 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.test.web.servlet; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Controller; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +/** + * Tests for SPR-10093 (support for OPTIONS requests). + * @author Arnaud Cogoluègnes + */ +@RunWith(SpringJUnit4ClassRunner.class) +@WebAppConfiguration +@ContextConfiguration +public class Spr10093Tests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = webAppContextSetup(this.wac).dispatchOptions(true).build(); + } + + @Test + public void test() throws Exception { + MyController controller = this.wac.getBean(MyController.class); + int initialCount = controller.counter.get(); + this.mockMvc.perform(options("/myUrl")).andExpect(status().isOk()); + + Assert.assertEquals(initialCount+1,controller.counter.get()); + } + + + @Configuration + @EnableWebMvc + static class WebConfig extends WebMvcConfigurerAdapter { + + @Bean + public MyController myController() { + return new MyController(); + } + } + + @Controller + private static class MyController { + + private AtomicInteger counter = new AtomicInteger(0); + + @RequestMapping(value="/myUrl",method=RequestMethod.OPTIONS) + @ResponseBody + public void handle() { + counter.incrementAndGet(); + } + } + + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java index 80a0d138f34..568f765564a 100644 --- a/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,9 @@ public class ContentAssertionTests { this.mockMvc.perform(get("/handleUtf8")) .andExpect(content().contentType(MediaType.valueOf("text/plain;charset=UTF-8"))) - .andExpect(content().contentType("text/plain;charset=UTF-8")); + .andExpect(content().contentType("text/plain;charset=UTF-8")) + .andExpect(content().contentTypeCompatibleWith("text/plain")) + .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_PLAIN)); } @Test diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java index 441a53e4ce4..d609f39e8fc 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; @@ -97,8 +96,10 @@ public class MockHttpServletRequest implements HttpServletRequest { private static final String CHARSET_PREFIX = "charset="; + private boolean active = true; + // --------------------------------------------------------------------- // ServletRequest properties // --------------------------------------------------------------------- @@ -140,6 +141,7 @@ public class MockHttpServletRequest implements HttpServletRequest { private int localPort = DEFAULT_SERVER_PORT; + // --------------------------------------------------------------------- // HttpServletRequest properties // --------------------------------------------------------------------- @@ -235,6 +237,7 @@ public class MockHttpServletRequest implements HttpServletRequest { this.locales.add(Locale.ENGLISH); } + // --------------------------------------------------------------------- // Lifecycle methods // --------------------------------------------------------------------- @@ -279,6 +282,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } } + // --------------------------------------------------------------------- // ServletRequest interface // --------------------------------------------------------------------- @@ -351,7 +355,7 @@ public class MockHttpServletRequest implements HttpServletRequest { * parameter name, they will be replaced. */ public void setParameter(String name, String value) { - setParameter(name, new String[] { value }); + setParameter(name, new String[] {value}); } /** @@ -373,7 +377,8 @@ public class MockHttpServletRequest implements HttpServletRequest { public void setParameters(Map params) { Assert.notNull(params, "Parameter map must not be null"); for (Object key : params.keySet()) { - Assert.isInstanceOf(String.class, key, "Parameter map key must be of type [" + String.class.getName() + "]"); + Assert.isInstanceOf(String.class, key, + "Parameter map key must be of type [" + String.class.getName() + "]"); Object value = params.get(key); if (value instanceof String) { this.setParameter((String) key, (String) value); @@ -382,8 +387,8 @@ public class MockHttpServletRequest implements HttpServletRequest { this.setParameter((String) key, (String[]) value); } else { - throw new IllegalArgumentException("Parameter map value must be single value " + " or array of type [" - + String.class.getName() + "]"); + throw new IllegalArgumentException( + "Parameter map value must be single value " + " or array of type [" + String.class.getName() + "]"); } } } @@ -394,7 +399,7 @@ public class MockHttpServletRequest implements HttpServletRequest { * parameter name, the given value will be added to the end of the list. */ public void addParameter(String name, String value) { - addParameter(name, new String[] { value }); + addParameter(name, new String[] {value}); } /** @@ -425,7 +430,8 @@ public class MockHttpServletRequest implements HttpServletRequest { public void addParameters(Map params) { Assert.notNull(params, "Parameter map must not be null"); for (Object key : params.keySet()) { - Assert.isInstanceOf(String.class, key, "Parameter map key must be of type [" + String.class.getName() + "]"); + Assert.isInstanceOf(String.class, key, + "Parameter map key must be of type [" + String.class.getName() + "]"); Object value = params.get(key); if (value instanceof String) { this.addParameter((String) key, (String) value); @@ -434,8 +440,8 @@ public class MockHttpServletRequest implements HttpServletRequest { this.addParameter((String) key, (String[]) value); } else { - throw new IllegalArgumentException("Parameter map value must be single value " + " or array of type [" - + String.class.getName() + "]"); + throw new IllegalArgumentException("Parameter map value must be single value " + + " or array of type [" + String.class.getName() + "]"); } } } @@ -456,8 +462,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } public String getParameter(String name) { - Assert.notNull(name, "Parameter name must not be null"); - String[] arr = this.parameters.get(name); + String[] arr = (name != null ? this.parameters.get(name) : null); return (arr != null && arr.length > 0 ? arr[0] : null); } @@ -466,8 +471,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } public String[] getParameterValues(String name) { - Assert.notNull(name, "Parameter name must not be null"); - return this.parameters.get(name); + return (name != null ? this.parameters.get(name) : null); } public Map getParameterMap() { @@ -509,8 +513,8 @@ public class MockHttpServletRequest implements HttpServletRequest { public BufferedReader getReader() throws UnsupportedEncodingException { if (this.content != null) { InputStream sourceStream = new ByteArrayInputStream(this.content); - Reader sourceReader = (this.characterEncoding != null) ? new InputStreamReader(sourceStream, - this.characterEncoding) : new InputStreamReader(sourceStream); + Reader sourceReader = (this.characterEncoding != null) ? + new InputStreamReader(sourceStream, this.characterEncoding) : new InputStreamReader(sourceStream); return new BufferedReader(sourceReader); } else { @@ -574,7 +578,7 @@ public class MockHttpServletRequest implements HttpServletRequest { * @since 3.2 */ public void setPreferredLocales(List locales) { - Assert.notEmpty(locales, "preferred locales list must not be empty"); + Assert.notEmpty(locales, "Locale list must not be empty"); this.locales.clear(); this.locales.addAll(locales); } @@ -635,6 +639,7 @@ public class MockHttpServletRequest implements HttpServletRequest { return this.localPort; } + // --------------------------------------------------------------------- // HttpServletRequest interface // --------------------------------------------------------------------- @@ -797,8 +802,8 @@ public class MockHttpServletRequest implements HttpServletRequest { } public boolean isUserInRole(String role) { - return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext && ((MockServletContext) this.servletContext).getDeclaredRoles().contains( - role))); + return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext && + ((MockServletContext) this.servletContext).getDeclaredRoles().contains(role))); } public void setUserPrincipal(Principal userPrincipal) { diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java index c86e95a93a7..75a7a1fc3ba 100644 --- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java +++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,11 +24,11 @@ import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @@ -56,6 +56,7 @@ public class MockHttpServletResponse implements HttpServletResponse { private static final String LOCATION_HEADER = "Location"; + //--------------------------------------------------------------------- // ServletResponse properties //--------------------------------------------------------------------- @@ -145,7 +146,7 @@ public class MockHttpServletResponse implements HttpServletResponse { private void updateContentTypeHeader() { if (this.contentType != null) { StringBuilder sb = new StringBuilder(this.contentType); - if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) { + if (!this.contentType.toLowerCase().contains(CHARSET_PREFIX) && this.charset) { sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding); } doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true); @@ -299,7 +300,7 @@ public class MockHttpServletResponse implements HttpServletResponse { *

    As of Servlet 3.0, this method is also defined HttpServletResponse. * @return the {@code Set} of header name {@code Strings}, or an empty {@code Set} if none */ - public Set getHeaderNames() { + public Collection getHeaderNames() { return this.headers.keySet(); } diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java index 0928ff4c9ec..0fc6c6012c7 100644 --- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java +++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractContextLoader.java @@ -185,8 +185,8 @@ public abstract class AbstractContextLoader implements SmartContextLoader { * *

    For example, if the supplied class is {@code com.example.MyTest}, * the generated locations will contain a single string with a value of - * "classpath:/com/example/MyTest{@code <suffix>}", - * where {@code <suffix>} is the value of the + * "classpath:/com/example/MyTest{@code }", + * where {@code } is the value of the * {@link #getResourceSuffix() resource suffix} string. * *

    As of Spring 3.1, the implementation of this method adheres to the diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java index 085bbb8b7db..a237bb00d1e 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,6 @@ package org.springframework.mock.web; -import static org.junit.Assert.*; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,6 +27,8 @@ import java.util.Map; import org.junit.Test; +import static org.junit.Assert.*; + /** * Unit tests for {@link MockHttpServletRequest}. * @@ -105,6 +105,12 @@ public class MockHttpServletRequestTests { assertEquals("HTTP header casing not being preserved", headerName, requestHeaders.nextElement()); } + @Test + public void nullParameterName() { + assertNull(request.getParameter(null)); + assertNull(request.getParameterValues(null)); + } + @Test public void setMultipleParameters() { request.setParameter("key1", "value1"); diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java index c16f7cab9e3..867b86ad90a 100644 --- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java +++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,18 +16,17 @@ package org.springframework.mock.web; -import static org.junit.Assert.*; - import java.io.IOException; import java.util.Arrays; -import java.util.Set; - +import java.util.Collection; import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.springframework.web.util.WebUtils; +import static org.junit.Assert.*; + /** * Unit tests for {@link MockHttpServletResponse}. * @@ -127,7 +126,7 @@ public class MockHttpServletResponseTests { public void httpHeaderNameCasingIsPreserved() throws Exception { final String headerName = "Header1"; response.addHeader(headerName, "value1"); - Set responseHeaders = response.getHeaderNames(); + Collection responseHeaders = response.getHeaderNames(); assertNotNull(responseHeaders); assertEquals(1, responseHeaders.size()); assertEquals("HTTP header casing not being preserved", headerName, responseHeaders.iterator().next()); diff --git a/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java b/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java index 3768b03cb63..0be28a871e1 100644 --- a/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/ClassLevelDirtiesContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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 org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -37,7 +38,7 @@ import org.springframework.test.context.support.DependencyInjectionTestExecution import org.springframework.test.context.support.DirtiesContextTestExecutionListener; /** - * JUnit 4 based integration test which verifies correct {@link ContextCache + * JUnit 4 based integration test which verifies correct {@linkplain ContextCache * application context caching} in conjunction with the * {@link SpringJUnit4ClassRunner} and the {@link DirtiesContext * @DirtiesContext} annotation at the class level. @@ -179,9 +180,14 @@ public class ClassLevelDirtiesContextTests { @RunWith(SpringJUnit4ClassRunner.class) @TestExecutionListeners( { DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class }) - @ContextConfiguration("/org/springframework/test/context/junit4/SpringJUnit4ClassRunnerAppCtxTests-context.xml") + @ContextConfiguration public static abstract class BaseTestCase { + @Configuration + static class Config { + /* no beans */ + } + @Autowired protected ApplicationContext applicationContext; diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java index 49d5ad5e7a4..6b7241261be 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/RepeatedSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -156,34 +156,34 @@ public class RepeatedSpringRunnerTests { public static final class TimedRepeatedTestCase extends AbstractRepeatedTestCase { @Test - @Timed(millis = 10000) + @Timed(millis = 1000) @Repeat(5) public void repeatedFiveTimesButDoesNotExceedTimeout() throws Exception { incrementInvocationCount(); } @Test - @Timed(millis = 100) + @Timed(millis = 10) @Repeat(1) public void singleRepetitionExceedsTimeout() throws Exception { incrementInvocationCount(); - Thread.sleep(250); + Thread.sleep(15); } @Test - @Timed(millis = 200) + @Timed(millis = 20) @Repeat(4) public void firstRepetitionOfManyExceedsTimeout() throws Exception { incrementInvocationCount(); - Thread.sleep(250); + Thread.sleep(25); } @Test - @Timed(millis = 1000) + @Timed(millis = 100) @Repeat(10) public void collectiveRepetitionsExceedTimeout() throws Exception { incrementInvocationCount(); - Thread.sleep(150); + Thread.sleep(11); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4SuiteTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java similarity index 95% rename from spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4SuiteTests.java rename to spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java index 2f293d88475..72ddd1b8c4f 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4SuiteTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/SpringJUnit4TestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,15 +42,16 @@ import org.springframework.test.context.junit4.profile.xml.DevProfileXmlConfigTe /** * JUnit test suite for tests involving {@link SpringJUnit4ClassRunner} and the - * Spring TestContext Framework. + * Spring TestContext Framework; only intended to be run manually as a + * convenience. * *

    This test suite serves a dual purpose of verifying that tests run with * {@link SpringJUnit4ClassRunner} can be used in conjunction with JUnit's * {@link Suite} runner. * *

    Note that tests included in this suite will be executed at least twice if - * run from an automated build process, test runner, etc. that is configured to - * run tests based on a "*Tests.class" pattern match. + * run from an automated build process, test runner, etc. that is not configured + * to exclude tests based on a "*TestSuite.class" pattern match. * * @author Sam Brannen * @since 2.5 @@ -104,6 +105,6 @@ StandardJUnit4FeaturesTests.class,// TimedTransactionalSpringRunnerTests.class,// HibernateSessionFlushingTests.class // }) -public class SpringJUnit4SuiteTests { +public class SpringJUnit4TestSuite { /* this test case consists entirely of tests loaded as a suite. */ } diff --git a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java index a3679ba75e1..3877058b00e 100644 --- a/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/junit4/TimedSpringRunnerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,8 @@ import org.junit.runner.notification.RunNotifier; import org.junit.runners.JUnit4; import org.springframework.test.annotation.Timed; import org.springframework.test.context.TestExecutionListeners; +import org.springframework.tests.Assume; +import org.springframework.tests.TestGroup; /** * Verifies proper handling of the following in conjunction with the @@ -42,6 +44,7 @@ public class TimedSpringRunnerTests { @Test public void timedTests() throws Exception { + Assume.group(TestGroup.PERFORMANCE); Class testClass = TimedSpringRunnerTestCase.class; TrackingRunListener listener = new TrackingRunListener(); RunNotifier notifier = new RunNotifier(); @@ -64,34 +67,34 @@ public class TimedSpringRunnerTests { // Should Pass. @Test(timeout = 2000) - public void testJUnitTimeoutWithNoOp() { + public void jUnitTimeoutWithNoOp() { /* no-op */ } // Should Pass. @Test @Timed(millis = 2000) - public void testSpringTimeoutWithNoOp() { + public void springTimeoutWithNoOp() { /* no-op */ } // Should Fail due to timeout. - @Test(timeout = 200) - public void testJUnitTimeoutWithOneSecondWait() throws Exception { - Thread.sleep(1000); + @Test(timeout = 10) + public void jUnitTimeoutWithSleep() throws Exception { + Thread.sleep(20); } // Should Fail due to timeout. @Test - @Timed(millis = 200) - public void testSpringTimeoutWithOneSecondWait() throws Exception { - Thread.sleep(1000); + @Timed(millis = 10) + public void springTimeoutWithSleep() throws Exception { + Thread.sleep(20); } // Should Fail due to duplicate configuration. @Test(timeout = 200) @Timed(millis = 200) - public void testSpringAndJUnitTimeout() { + public void springAndJUnitTimeouts() { /* no-op */ } } diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java index 05ba435f9b3..74e8f2513ad 100644 --- a/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/testng/FailingBeforeAndAfterMethodsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -237,8 +237,7 @@ public class FailingBeforeAndAfterMethodsTests { @BeforeTransaction public void beforeTransaction() { - // See SPR-8116 - //org.testng.Assert.fail("always failing beforeTransaction()"); + org.testng.Assert.fail("always failing beforeTransaction()"); } } @@ -251,8 +250,7 @@ public class FailingBeforeAndAfterMethodsTests { @AfterTransaction public void afterTransaction() { - // See SPR-8116 - //org.testng.Assert.fail("always failing afterTransaction()"); + org.testng.Assert.fail("always failing afterTransaction()"); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/testng/SPR-9398.txt b/spring-test/src/test/java/org/springframework/test/context/testng/SPR-9398.txt deleted file mode 100644 index dfbdc9ad831..00000000000 --- a/spring-test/src/test/java/org/springframework/test/context/testng/SPR-9398.txt +++ /dev/null @@ -1,3 +0,0 @@ -TODO [SPR-9398] re-enable TestNG support for spring-test. - -These TestNG test classes are currently not run at all. diff --git a/spring-tx/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java b/spring-tx/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java index 25bac956685..41bf5447278 100644 --- a/spring-tx/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java +++ b/spring-tx/src/main/java/org/springframework/jca/context/SpringContextResourceAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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 org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.StandardEnvironment; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -228,4 +229,17 @@ public class SpringContextResourceAdapter implements ResourceAdapter { return null; } + + @Override + public boolean equals(Object obj) { + return (obj instanceof SpringContextResourceAdapter && + ObjectUtils.nullSafeEquals(getContextConfigLocation(), + ((SpringContextResourceAdapter) obj).getContextConfigLocation())); + } + + @Override + public int hashCode() { + return ObjectUtils.nullSafeHashCode(getContextConfigLocation()); + } + } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java index 8d15342c02d..9f9b9503ddf 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionAspectSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 +24,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; -import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.annotation.BeanFactoryAnnotationUtils; import org.springframework.core.NamedThreadLocal; import org.springframework.transaction.NoTransactionException; @@ -247,12 +245,8 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init else if (this.transactionManagerBeanName != null) { return this.beanFactory.getBean(this.transactionManagerBeanName, PlatformTransactionManager.class); } - else if (this.beanFactory instanceof ListableBeanFactory) { - return BeanFactoryUtils.beanOfTypeIncludingAncestors(((ListableBeanFactory) this.beanFactory), PlatformTransactionManager.class); - } else { - throw new IllegalStateException( - "Cannot retrieve PlatformTransactionManager beans from non-listable BeanFactory: " + this.beanFactory); + return this.beanFactory.getBean(PlatformTransactionManager.class); } } diff --git a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java index eaf963cc397..62b408e155c 100644 --- a/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java +++ b/spring-tx/src/main/java/org/springframework/transaction/interceptor/TransactionProxyFactoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -176,8 +176,8 @@ public class TransactionProxyFactoryBean extends AbstractSingletonProxyFactoryBe /** * This callback is optional: If running in a BeanFactory and no transaction * manager has been set explicitly, a single matching bean of type - * PlatformTransactionManager will be fetched from the BeanFactory. - * @see org.springframework.beans.factory.BeanFactoryUtils#beanOfTypeIncludingAncestors + * {@link PlatformTransactionManager} will be fetched from the BeanFactory. + * @see org.springframework.beans.factory.BeanFactory#getBean(Class) * @see org.springframework.transaction.PlatformTransactionManager */ public void setBeanFactory(BeanFactory beanFactory) { diff --git a/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java b/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java index b9392d0d95d..74c1772558b 100644 --- a/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java +++ b/spring-web/src/main/java/org/springframework/http/converter/BufferedImageHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2013 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. @@ -132,7 +132,7 @@ public class BufferedImageHttpMessageConverter implements HttpMessageConverter imageWriters = ImageIO.getImageWritersByMIMEType(mediaType.toString()); @@ -191,7 +191,7 @@ public class BufferedImageHttpMessageConverter implements HttpMessageConverter> entry : parts.entrySet()) { String name = entry.getKey(); for (Object part : entry.getValue()) { - writeBoundary(boundary, os); - HttpEntity entity = getEntity(part); - writePart(name, entity, os); - writeNewLine(os); + if (part != null) { + writeBoundary(boundary, os); + HttpEntity entity = getEntity(part); + writePart(name, entity, os); + writeNewLine(os); + } } } } diff --git a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java index 8b805802d20..8cf79f927d4 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBean.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Properties; import javax.servlet.ServletContext; @@ -27,6 +28,7 @@ import javax.servlet.ServletContext; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.http.MediaType; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.web.context.ServletContextAware; @@ -51,11 +53,11 @@ public class ContentNegotiationManagerFactoryBean private boolean ignoreAcceptHeader = false; - private Properties mediaTypes = new Properties(); + private Map mediaTypes = new HashMap(); private Boolean useJaf; - private String parameterName; + private String parameterName = "format"; private MediaType defaultContentType; @@ -63,7 +65,6 @@ public class ContentNegotiationManagerFactoryBean private ServletContext servletContext; - /** * Indicate whether the extension of the request path should be used to determine * the requested media type with the highest priority. @@ -76,21 +77,43 @@ public class ContentNegotiationManagerFactoryBean } /** - * Add mappings from file extensions to media types. - *

    If this property is not set, the Java Action Framework, if available, may - * still be used in conjunction with {@link #setFavorPathExtension(boolean)}. + * Add mappings from file extensions to media types represented as strings. + *

    When this mapping is not set or when an extension is not found, the Java + * Action Framework, if available, may be used if enabled via + * {@link #setFavorPathExtension(boolean)}. + * + * @see #addMediaType(String, MediaType) + * @see #addMediaTypes(Map) */ public void setMediaTypes(Properties mediaTypes) { if (!CollectionUtils.isEmpty(mediaTypes)) { - for (Map.Entry entry : mediaTypes.entrySet()) { - String extension = ((String) entry.getKey()).toLowerCase(Locale.ENGLISH); + for (Entry entry : mediaTypes.entrySet()) { + String extension = ((String)entry.getKey()).toLowerCase(Locale.ENGLISH); this.mediaTypes.put(extension, MediaType.valueOf((String) entry.getValue())); } } } - public Properties getMediaTypes() { - return this.mediaTypes; + /** + * Add a mapping from a file extension to a media type. + *

    If no mapping is added or when an extension is not found, the Java + * Action Framework, if available, may be used if enabled via + * {@link #setFavorPathExtension(boolean)}. + */ + public void addMediaType(String fileExtension, MediaType mediaType) { + this.mediaTypes.put(fileExtension, mediaType); + } + + /** + * Add mappings from file extensions to media types. + *

    If no mappings are added or when an extension is not found, the Java + * Action Framework, if available, may be used if enabled via + * {@link #setFavorPathExtension(boolean)}. + */ + public void addMediaTypes(Map mediaTypes) { + if (mediaTypes != null) { + this.mediaTypes.putAll(mediaTypes); + } } /** @@ -98,6 +121,7 @@ public class ContentNegotiationManagerFactoryBean * to map from file extensions to media types. This is used only when * {@link #setFavorPathExtension(boolean)} is set to {@code true}. *

    The default value is {@code true}. + * * @see #parameterName * @see #setMediaTypes(Properties) */ @@ -114,6 +138,7 @@ public class ContentNegotiationManagerFactoryBean * {@code "application/pdf"} regardless of the {@code Accept} header. *

    To use this option effectively you must also configure the MediaType * type mappings via {@link #setMediaTypes(Properties)}. + * * @see #setParameterName(String) */ public void setFavorParameter(boolean favorParameter) { @@ -126,6 +151,7 @@ public class ContentNegotiationManagerFactoryBean *

    The default parameter name is {@code "format"}. */ public void setParameterName(String parameterName) { + Assert.notNull(parameterName, "parameterName is required"); this.parameterName = parameterName; } @@ -143,8 +169,8 @@ public class ContentNegotiationManagerFactoryBean /** * Set the default content type. *

    This content type will be used when neither the request path extension, - * nor a request parameter, nor the {@code Accept} header could help determine - * the requested content type. + * nor a request parameter, nor the {@code Accept} header could help + * determine the requested content type. */ public void setDefaultContentType(MediaType defaultContentType) { this.defaultContentType = defaultContentType; @@ -157,16 +183,12 @@ public class ContentNegotiationManagerFactoryBean public void afterPropertiesSet() throws Exception { List strategies = new ArrayList(); - Map mediaTypesMap = new HashMap(); - CollectionUtils.mergePropertiesIntoMap(this.mediaTypes, mediaTypesMap); - if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy; if (this.servletContext != null) { - strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, mediaTypesMap); - } - else { - strategy = new PathExtensionContentNegotiationStrategy(mediaTypesMap); + strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes); + } else { + strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); } if (this.useJaf != null) { strategy.setUseJaf(this.useJaf); @@ -175,7 +197,7 @@ public class ContentNegotiationManagerFactoryBean } if (this.favorParameter) { - ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(mediaTypesMap); + ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes); strategy.setParameterName(this.parameterName); strategies.add(strategy); } diff --git a/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java b/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java index 0adaeaab523..c5fe9f1b4f7 100644 --- a/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java +++ b/spring-web/src/main/java/org/springframework/web/accept/ParameterContentNegotiationStrategy.java @@ -44,7 +44,6 @@ public class ParameterContentNegotiationStrategy extends AbstractMappingContentN */ public ParameterContentNegotiationStrategy(Map mediaTypes) { super(mediaTypes); - Assert.notEmpty(mediaTypes, "Cannot look up media types without any mappings"); } /** @@ -52,6 +51,7 @@ public class ParameterContentNegotiationStrategy extends AbstractMappingContentN *

    The default parameter name is {@code format}. */ public void setParameterName(String parameterName) { + Assert.notNull(parameterName, "parameterName is required"); this.parameterName = parameterName; } diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java index 7f469a43185..6af1c621dea 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/CookieValue.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -55,14 +55,14 @@ public @interface CookieValue { * in case the header is missing in the request. Switch this to * {@code false} if you prefer a {@code null} in case of the * missing header. - *

    Alternatively, provide a {@link #defaultValue() defaultValue}, - * which implicitly sets this flag to {@code false}. + *

    Alternatively, provide a {@link #defaultValue}, which implicitly sets + * this flag to {@code false}. */ boolean required() default true; /** * The default value to use as a fallback. Supplying a default value implicitly - * sets {@link #required()} to false. + * sets {@link #required} to {@code false}. */ String defaultValue() default ValueConstants.DEFAULT_NONE; diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java index 9d094d75d32..47d2a567277 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestHeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,14 +49,14 @@ public @interface RequestHeader { *

    Default is {@code true}, leading to an exception thrown in case * of the header missing in the request. Switch this to {@code false} * if you prefer a {@code null} in case of the header missing. - *

    Alternatively, provide a {@link #defaultValue() defaultValue}, - * which implicitely sets this flag to {@code false}. + *

    Alternatively, provide a {@link #defaultValue}, which implicitly sets + * this flag to {@code false}. */ boolean required() default true; /** - * The default value to use as a fallback. Supplying a default value implicitely - * sets {@link #required()} to false. + * The default value to use as a fallback. Supplying a default value implicitly + * sets {@link #required} to {@code false}. */ String defaultValue() default ValueConstants.DEFAULT_NONE; diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java index ff4849e35e2..320bb95d133 100644 --- a/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java +++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java @@ -266,6 +266,7 @@ public @interface RequestMapping { * Ant-style path patterns are also supported (e.g. "/myPath/*.do"). * At the method level, relative paths (e.g. "edit.do") are supported * within the primary mapping expressed at the type level. + * Path mapping URIs may contain placeholders (e.g. "/${connect}") *

    In a Portlet environment: the mapped portlet modes * (i.e. "EDIT", "VIEW", "HELP" or any custom modes). *

    Supported at the type level as well as at the method level! diff --git a/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java index a6185a81e8f..cba5e1dbc30 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/ConfigurableWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,11 +71,6 @@ public interface ConfigurableWebApplicationContext extends WebApplicationContext */ ServletConfig getServletConfig(); - /** - * Return the {@link ConfigurableWebEnvironment} used by this web application context. - */ - ConfigurableWebEnvironment getEnvironment(); - /** * Set the namespace for this web application context, * to be used for building a default context config location. diff --git a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java index 6afd42b16c7..69d812da378 100644 --- a/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java +++ b/spring-web/src/main/java/org/springframework/web/context/ContextLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,11 +23,11 @@ import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; - import javax.servlet.ServletContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.access.BeanFactoryLocator; import org.springframework.beans.factory.access.BeanFactoryReference; @@ -38,6 +38,7 @@ import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.access.ContextSingletonBeanFactoryLocator; import org.springframework.core.GenericTypeResolver; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.util.Assert; @@ -280,7 +281,18 @@ public class ContextLoader { this.context = createWebApplicationContext(servletContext); } if (this.context instanceof ConfigurableWebApplicationContext) { - configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext); + ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; + if (!cwac.isActive()) { + // The context has not yet been refreshed -> provide services such as + // setting the parent context, setting the application context id, etc + if (cwac.getParent() == null) { + // The context instance was injected without an explicit parent -> + // determine parent for root web application context, if any. + ApplicationContext parent = loadParentContext(servletContext); + cwac.setParent(parent); + } + configureAndRefreshWebApplicationContext(cwac, servletContext); + } } servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); @@ -333,9 +345,7 @@ public class ContextLoader { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } - ConfigurableWebApplicationContext wac = - (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); - return wac; + return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } /** @@ -370,10 +380,6 @@ public class ContextLoader { } } - // Determine parent for root web application context, if any. - ApplicationContext parent = loadParentContext(sc); - - wac.setParent(parent); wac.setServletContext(sc); String initParameter = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (initParameter != null) { @@ -472,11 +478,11 @@ public class ContextLoader { Class contextClass = applicationContext.getClass(); ArrayList> initializerInstances = - new ArrayList>(); + new ArrayList>(); for (Class> initializerClass : initializerClasses) { Class initializerContextClass = - GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); + GenericTypeResolver.resolveTypeArgument(initializerClass, ApplicationContextInitializer.class); Assert.isAssignable(initializerContextClass, contextClass, String.format( "Could not add context initializer [%s] as its generic parameter [%s] " + "is not assignable from the type of application context used by this " + @@ -485,7 +491,10 @@ public class ContextLoader { initializerInstances.add(BeanUtils.instantiateClass(initializerClass)); } - applicationContext.getEnvironment().initPropertySources(servletContext, null); + ConfigurableEnvironment env = applicationContext.getEnvironment(); + if (env instanceof ConfigurableWebEnvironment) { + ((ConfigurableWebEnvironment)env).initPropertySources(servletContext, null); + } Collections.sort(initializerInstances, new AnnotationAwareOrderComparator()); for (ApplicationContextInitializer initializer : initializerInstances) { diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java index 4da20dd40b1..22ea83f4585 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/DeferredResult.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,13 +32,13 @@ import org.springframework.web.context.request.NativeWebRequest; *

    Subclasses can extend this class to easily associate additional data or * behavior with the {@link DeferredResult}. For example, one might want to * associate the user used to create the {@link DeferredResult} by extending the - * class and adding an addition property for the user. In this way, the user + * class and adding an additional property for the user. In this way, the user * could easily be accessed later without the need to use a data structure to do * the mapping. * *

    An example of associating additional behavior to this class might be * realized by extending the class to implement an additional interface. For - * example, one might want to implement a {@link Comparable} so that when the + * example, one might want to implement {@link Comparable} so that when the * {@link DeferredResult} is added to a {@link PriorityQueue} it is handled in * the correct order. * diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java index 2e104d8e75b..8c79b4230f2 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -37,9 +37,9 @@ import org.springframework.web.util.UrlPathHelper; * as an SPI and not typically used directly by application classes. * *

    An async scenario starts with request processing as usual in a thread (T1). - * Concurrent request handling can be innitiated by calling - * {@linkplain #startCallableProcessing(Callable, Object...) startCallableProcessing} or - * {@linkplain #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing} + * Concurrent request handling can be initiated by calling + * {@link #startCallableProcessing(Callable, Object...) startCallableProcessing} or + * {@link #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing}, * both of which produce a result in a separate thread (T2). The result is saved * and the request dispatched to the container, to resume processing with the saved * result in a third thread (T3). Within the dispatched thread (T3), the saved @@ -263,7 +263,7 @@ public final class WebAsyncManager { * the timeout value of the {@code AsyncWebRequest} before delegating to * {@link #startCallableProcessing(Callable, Object...)}. * - * @param webAsyncTask an WebAsyncTask containing the target {@code Callable} + * @param webAsyncTask a WebAsyncTask containing the target {@code Callable} * @param processingContext additional context to save that can be accessed * via {@link #getConcurrentResultContext()} * @throws Exception If concurrent processing failed to start diff --git a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncTask.java b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncTask.java index 15cfdc96eeb..59fa985acb7 100644 --- a/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncTask.java +++ b/spring-web/src/main/java/org/springframework/web/context/request/async/WebAsyncTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ public class WebAsyncTask { /** - * Create an {@code WebAsyncTask} wrapping the given {@link Callable}. + * Create a {@code WebAsyncTask} wrapping the given {@link Callable}. * @param callable the callable for concurrent handling */ public WebAsyncTask(Callable callable) { @@ -54,7 +54,7 @@ public class WebAsyncTask { } /** - * Create an {@code WebAsyncTask} with a timeout value and a {@link Callable}. + * Create a {@code WebAsyncTask} with a timeout value and a {@link Callable}. * @param timeout timeout value in milliseconds * @param callable the callable for concurrent handling */ @@ -63,7 +63,7 @@ public class WebAsyncTask { } /** - * Create an {@code WebAsyncTask} with a timeout value, an executor name, and a {@link Callable}. + * Create a {@code WebAsyncTask} with a timeout value, an executor name, and a {@link Callable}. * @param timeout timeout value in milliseconds; ignored if {@code null} * @param callable the callable for concurrent handling */ @@ -73,7 +73,7 @@ public class WebAsyncTask { } /** - * Create an {@code WebAsyncTask} with a timeout value, an executor instance, and a Callable. + * Create a {@code WebAsyncTask} with a timeout value, an executor instance, and a Callable. * @param timeout timeout value in milliseconds; ignored if {@code null} * @param callable the callable for concurrent handling */ @@ -113,7 +113,7 @@ public class WebAsyncTask { return this.executor; } else if (this.executorName != null) { - Assert.state(this.beanFactory != null, "A BeanFactory is required to look up an task executor bean"); + Assert.state(this.beanFactory != null, "A BeanFactory is required to look up a task executor bean"); return this.beanFactory.getBean(this.executorName, AsyncTaskExecutor.class); } else { diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java index f36bddc3168..92b1f7907a7 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.ui.context.Theme; import org.springframework.ui.context.ThemeSource; import org.springframework.ui.context.support.UiApplicationContextUtils; -import org.springframework.util.Assert; import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.ServletConfigAware; @@ -157,15 +156,6 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR return new StandardServletEnvironment(); } - @Override - public ConfigurableWebEnvironment getEnvironment() { - ConfigurableEnvironment env = super.getEnvironment(); - Assert.isInstanceOf(ConfigurableWebEnvironment.class, env, - "ConfigurableWebApplicationContext environment must be of type " + - "ConfigurableWebEnvironment"); - return (ConfigurableWebEnvironment) env; - } - /** * Register request/session scopes, a {@link ServletContextAwareProcessor}, etc. */ @@ -212,7 +202,11 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR @Override protected void initPropertySources() { super.initPropertySources(); - this.getEnvironment().initPropertySources(this.servletContext, this.servletConfig); + ConfigurableEnvironment env = this.getEnvironment(); + if (env instanceof ConfigurableWebEnvironment) { + ((ConfigurableWebEnvironment)env).initPropertySources( + this.servletContext, this.servletConfig); + } } public Theme getTheme(String themeName) { diff --git a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java index ac87960cd00..d30d3aef841 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -147,15 +147,6 @@ public class GenericWebApplicationContext extends GenericApplicationContext return new StandardServletEnvironment(); } - @Override - public ConfigurableWebEnvironment getEnvironment() { - ConfigurableEnvironment env = super.getEnvironment(); - Assert.isInstanceOf(ConfigurableWebEnvironment.class, env, - "ConfigurableWebApplicationContext environment must be of type " + - "ConfigurableWebEnvironment"); - return (ConfigurableWebEnvironment) env; - } - /** * Register ServletContextAwareProcessor. * @see ServletContextAwareProcessor @@ -202,7 +193,11 @@ public class GenericWebApplicationContext extends GenericApplicationContext @Override protected void initPropertySources() { super.initPropertySources(); - this.getEnvironment().initPropertySources(this.servletContext, null); + ConfigurableEnvironment env = this.getEnvironment(); + if (env instanceof ConfigurableWebEnvironment) { + ((ConfigurableWebEnvironment)env).initPropertySources( + this.servletContext, null); + } } public Theme getTheme(String themeName) { diff --git a/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java index 5de71bda608..5d26a434e3c 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/StaticWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,9 +27,7 @@ import org.springframework.core.io.support.ResourcePatternResolver; import org.springframework.ui.context.Theme; import org.springframework.ui.context.ThemeSource; import org.springframework.ui.context.support.UiApplicationContextUtils; -import org.springframework.util.Assert; import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.ServletConfigAware; import org.springframework.web.context.ServletContextAware; @@ -169,15 +167,6 @@ public class StaticWebApplicationContext extends StaticApplicationContext return new StandardServletEnvironment(); } - @Override - public ConfigurableWebEnvironment getEnvironment() { - ConfigurableEnvironment env = super.getEnvironment(); - Assert.isInstanceOf(ConfigurableWebEnvironment.class, env, - "ConfigurableWebApplication environment must be of type " + - "ConfigurableWebEnvironment"); - return (ConfigurableWebEnvironment) env; - } - /** * Initialize the theme capability. */ diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java index f3a5ec9013b..103784e9c29 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java @@ -41,6 +41,7 @@ import org.springframework.util.StringUtils; * @since 3.1.3 * @see Hierarchical URIs */ +@SuppressWarnings("serial") final class HierarchicalUriComponents extends UriComponents { private static final char PATH_DELIMITER = '/'; diff --git a/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java b/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java index b28d398687f..0ee697f6d9a 100644 --- a/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java +++ b/spring-web/src/main/java/org/springframework/web/util/JavaScriptUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2008 the original author or authors. + * Copyright 2002-2013 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,21 +21,21 @@ package org.springframework.web.util; * Escapes based on the JavaScript 1.5 recommendation. * *

    Reference: - * - * Core JavaScript 1.5 Guide - * + * + * JavaScript Guide on Mozilla Developer Network. * * @author Juergen Hoeller * @author Rob Harrop + * @author Rossen Stoyanchev * @since 1.1.1 */ public class JavaScriptUtils { /** - * Turn special characters into escaped characters conforming to JavaScript. - * Handles complete character set defined in HTML 4.01 recommendation. + * Turn JavaScript special characters into escaped characters. + * * @param input the input string - * @return the escaped string + * @return the string with escaped characters */ public static String javaScriptEscape(String input) { if (input == null) { @@ -73,6 +73,13 @@ public class JavaScriptUtils { else if (c == '\f') { filtered.append("\\f"); } + else if (c == '\b') { + filtered.append("\\b"); + } + // No '\v' in Java, use octal value for VT ascii char + else if (c == '\013') { + filtered.append("\\v"); + } else { filtered.append(c); } diff --git a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java index d896518b29a..6e51c77dffd 100644 --- a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java @@ -33,6 +33,7 @@ import org.springframework.util.ObjectUtils; * @since 3.2 * @see Hierarchical vs Opaque URIs */ +@SuppressWarnings("serial") final class OpaqueUriComponents extends UriComponents { private static final MultiValueMap QUERY_PARAMS_NONE = new LinkedMultiValueMap(0); diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java index f04c5f695bd..2a2b300bfa6 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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.web.util; +import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.net.URI; import java.util.Arrays; @@ -38,7 +39,7 @@ import org.springframework.util.MultiValueMap; * @since 3.1 * @see UriComponentsBuilder */ -public abstract class UriComponents { +public abstract class UriComponents implements Serializable { private static final String DEFAULT_ENCODING = "UTF-8"; diff --git a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java index 67b7a4c024f..7f98ee23696 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java +++ b/spring-web/src/main/java/org/springframework/web/util/UrlPathHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; /** @@ -491,6 +493,33 @@ public class UrlPathHelper { } } + /** + * Decode the given matrix variables via + * {@link #decodeRequestString(HttpServletRequest, String)} unless + * {@link #setUrlDecode(boolean)} is set to {@code true} in which case it is + * assumed the URL path from which the variables were extracted is already + * decoded through a call to + * {@link #getLookupPathForRequest(HttpServletRequest)}. + * + * @param request current HTTP request + * @param vars URI variables extracted from the URL path + * @return the same Map or a new Map instance + */ + public MultiValueMap decodeMatrixVariables(HttpServletRequest request, MultiValueMap vars) { + if (this.urlDecode) { + return vars; + } + else { + MultiValueMap decodedVars = new LinkedMultiValueMap (vars.size()); + for (String key : vars.keySet()) { + for (String value : vars.get(key)) { + decodedVars.add(key, decodeInternal(request, value)); + } + } + return decodedVars; + } + } + private boolean shouldRemoveTrailingServletPathSlash(HttpServletRequest request) { if (request.getAttribute(WEBSPHERE_URI_ATTRIBUTE) == null) { // Regular servlet container: behaves as expected in any case, diff --git a/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java index 3fd2d97e2af..0dcb8703bf9 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/BufferedImageHttpMessageConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2013 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,6 +51,7 @@ public class BufferedImageHttpMessageConverterTests { public void canWrite() { assertTrue("Image not supported", converter.canWrite(BufferedImage.class, null)); assertTrue("Image not supported", converter.canWrite(BufferedImage.class, new MediaType("image", "png"))); + assertTrue("Image not supported", converter.canWrite(BufferedImage.class, new MediaType("*", "*"))); } @Test @@ -85,7 +86,7 @@ public class BufferedImageHttpMessageConverterTests { converter.setDefaultContentType(contentType); BufferedImage body = ImageIO.read(logo.getFile()); MockHttpOutputMessage outputMessage = new MockHttpOutputMessage(); - converter.write(body, contentType, outputMessage); + converter.write(body, new MediaType("*", "*"), outputMessage); assertEquals("Invalid content type", contentType, outputMessage.getHeaders().getContentType()); assertTrue("Invalid size", outputMessage.getBodyAsBytes().length > 0); BufferedImage result = ImageIO.read(new ByteArrayInputStream(outputMessage.getBodyAsBytes())); diff --git a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java index 2344269b634..0792d6c6a8e 100644 --- a/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java +++ b/spring-web/src/test/java/org/springframework/http/converter/FormHttpMessageConverterTests.java @@ -112,6 +112,7 @@ public class FormHttpMessageConverterTests { parts.add("name 1", "value 1"); parts.add("name 2", "value 2+1"); parts.add("name 2", "value 2+2"); + parts.add("name 3", null); Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg"); parts.add("logo", logo); diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java index 5694b666105..c5dc6d76010 100644 --- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java +++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletRequest.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; @@ -110,9 +109,9 @@ public class MockHttpServletRequest implements HttpServletRequest { private boolean active = true; - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- // ServletRequest properties - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- private final Map attributes = new LinkedHashMap(); @@ -151,11 +150,12 @@ public class MockHttpServletRequest implements HttpServletRequest { private int localPort = DEFAULT_SERVER_PORT; - private Map parts = new HashMap(); + private final Map parts = new HashMap(); - //--------------------------------------------------------------------- + + // --------------------------------------------------------------------- // HttpServletRequest properties - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- private String authType; @@ -200,9 +200,9 @@ public class MockHttpServletRequest implements HttpServletRequest { private DispatcherType dispatcherType = DispatcherType.REQUEST; - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- // Constructors - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- /** * Create a new {@code MockHttpServletRequest} with a default @@ -256,9 +256,10 @@ public class MockHttpServletRequest implements HttpServletRequest { this.locales.add(Locale.ENGLISH); } - //--------------------------------------------------------------------- + + // --------------------------------------------------------------------- // Lifecycle methods - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- /** * Return the ServletContext that this request is associated with. (Not @@ -302,9 +303,9 @@ public class MockHttpServletRequest implements HttpServletRequest { } - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- // ServletRequest interface - //--------------------------------------------------------------------- + // --------------------------------------------------------------------- @Override public Object getAttribute(String name) { @@ -414,8 +415,7 @@ public class MockHttpServletRequest implements HttpServletRequest { } else { throw new IllegalArgumentException( - "Parameter map value must be single value " + " or array of type [" + String.class.getName() + - "]"); + "Parameter map value must be single value " + " or array of type [" + String.class.getName() + "]"); } } } @@ -490,8 +490,7 @@ public class MockHttpServletRequest implements HttpServletRequest { @Override public String getParameter(String name) { - Assert.notNull(name, "Parameter name must not be null"); - String[] arr = this.parameters.get(name); + String[] arr = (name != null ? this.parameters.get(name) : null); return (arr != null && arr.length > 0 ? arr[0] : null); } @@ -502,8 +501,7 @@ public class MockHttpServletRequest implements HttpServletRequest { @Override public String[] getParameterValues(String name) { - Assert.notNull(name, "Parameter name must not be null"); - return this.parameters.get(name); + return (name != null ? this.parameters.get(name) : null); } @Override @@ -620,7 +618,7 @@ public class MockHttpServletRequest implements HttpServletRequest { * @since 3.2 */ public void setPreferredLocales(List locales) { - Assert.notEmpty(locales, "preferred locales list must not be empty"); + Assert.notEmpty(locales, "Locale list must not be empty"); this.locales.clear(); this.locales.addAll(locales); } @@ -779,7 +777,7 @@ public class MockHttpServletRequest implements HttpServletRequest { @Override public String getHeader(String name) { HeaderValueHolder header = HeaderValueHolder.getByName(this.headers, name); - return (header != null ? header.getValue().toString() : null); + return (header != null ? header.getStringValue() : null); } @Override @@ -867,8 +865,8 @@ public class MockHttpServletRequest implements HttpServletRequest { @Override public boolean isUserInRole(String role) { - return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext && ((MockServletContext) this.servletContext).getDeclaredRoles().contains( - role))); + return (this.userRoles.contains(role) || (this.servletContext instanceof MockServletContext && + ((MockServletContext) this.servletContext).getDeclaredRoles().contains(role))); } public void setUserPrincipal(Principal userPrincipal) { diff --git a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java index 6f52ab8e3e8..990026770db 100644 --- a/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java +++ b/spring-web/src/test/java/org/springframework/mock/web/test/MockHttpServletResponse.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,11 +24,11 @@ import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import javax.servlet.ServletOutputStream; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletResponse; @@ -38,11 +38,9 @@ import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.web.util.WebUtils; /** - * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} - * interface. Supports the Servlet 3.0 API level + * Mock implementation of the {@link javax.servlet.http.HttpServletResponse} interface. * - *

    Used for testing the web framework; also useful for testing - * application controllers. + *

    Compatible with Servlet 2.5 as well as Servlet 3.0. * * @author Juergen Hoeller * @author Rod Johnson @@ -58,6 +56,7 @@ public class MockHttpServletResponse implements HttpServletResponse { private static final String LOCATION_HEADER = "Location"; + //--------------------------------------------------------------------- // ServletResponse properties //--------------------------------------------------------------------- @@ -148,7 +147,7 @@ public class MockHttpServletResponse implements HttpServletResponse { private void updateContentTypeHeader() { if (this.contentType != null) { StringBuilder sb = new StringBuilder(this.contentType); - if (this.contentType.toLowerCase().indexOf(CHARSET_PREFIX) == -1 && this.charset) { + if (!this.contentType.toLowerCase().contains(CHARSET_PREFIX) && this.charset) { sb.append(";").append(CHARSET_PREFIX).append(this.characterEncoding); } doAddHeaderValue(CONTENT_TYPE_HEADER, sb.toString(), true); @@ -319,7 +318,7 @@ public class MockHttpServletResponse implements HttpServletResponse { * @return the {@code Set} of header name {@code Strings}, or an empty {@code Set} if none */ @Override - public Set getHeaderNames() { + public Collection getHeaderNames() { return this.headers.keySet(); } diff --git a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java index 1851879b194..d086093402c 100644 --- a/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java +++ b/spring-web/src/test/java/org/springframework/web/accept/ContentNegotiationManagerFactoryBeanTests.java @@ -19,7 +19,8 @@ import static org.junit.Assert.assertEquals; import java.util.Arrays; import java.util.Collections; -import java.util.Properties; +import java.util.HashMap; +import java.util.Map; import org.junit.Before; import org.junit.Test; @@ -74,9 +75,9 @@ public class ContentNegotiationManagerFactoryBeanTests { @Test public void addMediaTypes() throws Exception { - Properties mediaTypes = new Properties(); - mediaTypes.put("json", MediaType.APPLICATION_JSON_VALUE); - this.factoryBean.setMediaTypes(mediaTypes); + Map mediaTypes = new HashMap(); + mediaTypes.put("json", MediaType.APPLICATION_JSON); + this.factoryBean.addMediaTypes(mediaTypes); this.factoryBean.afterPropertiesSet(); ContentNegotiationManager manager = this.factoryBean.getObject(); @@ -88,17 +89,16 @@ public class ContentNegotiationManagerFactoryBeanTests { @Test public void favorParameter() throws Exception { this.factoryBean.setFavorParameter(true); - this.factoryBean.setParameterName("f"); - Properties mediaTypes = new Properties(); - mediaTypes.put("json", MediaType.APPLICATION_JSON_VALUE); - this.factoryBean.setMediaTypes(mediaTypes); + Map mediaTypes = new HashMap(); + mediaTypes.put("json", MediaType.APPLICATION_JSON); + this.factoryBean.addMediaTypes(mediaTypes); this.factoryBean.afterPropertiesSet(); ContentNegotiationManager manager = this.factoryBean.getObject(); this.servletRequest.setRequestURI("/flower"); - this.servletRequest.addParameter("f", "json"); + this.servletRequest.addParameter("format", "json"); assertEquals(Arrays.asList(MediaType.APPLICATION_JSON), manager.resolveMediaTypes(this.webRequest)); } diff --git a/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesConfigurer.java b/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesConfigurer.java index f01f25dd1ad..d23ac0033b2 100644 --- a/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesConfigurer.java +++ b/spring-webmvc-tiles3/src/main/java/org/springframework/web/servlet/view/tiles3/TilesConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,19 +32,21 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.tiles.TilesContainer; import org.apache.tiles.TilesException; -import org.apache.tiles.access.TilesAccess; import org.apache.tiles.definition.DefinitionsFactory; import org.apache.tiles.definition.DefinitionsReader; import org.apache.tiles.definition.dao.BaseLocaleUrlDefinitionDAO; import org.apache.tiles.definition.dao.CachingLocaleUrlDefinitionDAO; import org.apache.tiles.definition.digester.DigesterDefinitionsReader; import org.apache.tiles.el.ELAttributeEvaluator; +import org.apache.tiles.el.ScopeELResolver; import org.apache.tiles.el.TilesContextBeanELResolver; import org.apache.tiles.el.TilesContextELResolver; import org.apache.tiles.evaluator.AttributeEvaluator; import org.apache.tiles.evaluator.AttributeEvaluatorFactory; import org.apache.tiles.evaluator.BasicAttributeEvaluatorFactory; import org.apache.tiles.evaluator.impl.DirectAttributeEvaluator; +import org.apache.tiles.extras.complete.CompleteAutoloadTilesContainerFactory; +import org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer; import org.apache.tiles.factory.AbstractTilesContainerFactory; import org.apache.tiles.factory.BasicTilesContainerFactory; import org.apache.tiles.impl.BasicTilesContainer; @@ -72,7 +74,9 @@ import org.springframework.web.context.ServletContextAware; *

    The TilesConfigurer simply configures a TilesContainer using a set of files * containing definitions, to be accessed by {@link TilesView} instances. This is a * Spring-based alternative (for usage in Spring configuration) to the Tiles-provided - * {@link org.apache.tiles.web.startup.TilesListener} (for usage in {@code web.xml}). + * {@code ServletContextListener} + * (e.g. {@link org.apache.tiles.extras.complete.CompleteAutoloadTilesListener} + * for usage in {@code web.xml}. * *

    TilesViews can be managed by any {@link org.springframework.web.servlet.ViewResolver}. * For simple convention-based view resolution, consider using {@link TilesViewResolver}. @@ -110,8 +114,6 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D private TilesInitializer tilesInitializer; - private boolean completeAutoload = false; - private String[] definitions; private boolean checkRefresh = false; @@ -154,17 +156,14 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D public void setCompleteAutoload(boolean completeAutoload) { if (completeAutoload) { try { - Class clazz = getClass().getClassLoader().loadClass( - "org.apache.tiles.extras.complete.CompleteAutoloadTilesInitializer"); - this.tilesInitializer = (TilesInitializer) clazz.newInstance(); + this.tilesInitializer = new SpringCompleteAutoloadTilesInitializer(); } catch (Exception ex) { - throw new IllegalStateException("Tiles-Extras 3.0 not available", ex); + throw new IllegalStateException("tiles-extras 3.x not available", ex); } } else { this.tilesInitializer = null; } - this.completeAutoload = completeAutoload; } /** @@ -192,7 +191,7 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D /** * Set the {@link org.apache.tiles.definition.DefinitionsFactory} implementation to use. - * Default is {@link org.apache.tiles.definition.UrlDefinitionsFactory}, + * Default is {@link org.apache.tiles.definition.UnresolvingLocaleDefinitionsFactory}, * operating on definition resource URLs. *

    Specify a custom DefinitionsFactory, e.g. a UrlDefinitionsFactory subclass, * to customize the creation of Tiles Definition objects. Note that such a @@ -204,8 +203,8 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D } /** - * Set the {@link org.apache.tiles.preparer.PreparerFactory} implementation to use. - * Default is {@link org.apache.tiles.preparer.BasicPreparerFactory}, creating + * Set the {@link org.apache.tiles.preparer.factory.PreparerFactory} implementation to use. + * Default is {@link org.apache.tiles.preparer.factory.BasicPreparerFactory}, creating * shared instances for specified preparer classes. *

    Specify {@link SimpleSpringPreparerFactory} to autowire * {@link org.apache.tiles.preparer.ViewPreparer} instances based on specified @@ -230,7 +229,7 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D * Set whether to use a MutableTilesContainer (typically the CachingTilesContainer * implementation) for this application. Default is "false". * @see org.apache.tiles.mgmt.MutableTilesContainer - * @see org.apache.tiles.mgmt.CachingTilesContainer + * @see org.apache.tiles.impl.mgmt.CachingTilesContainer */ public void setUseMutableTilesContainer(boolean useMutableTilesContainer) { this.useMutableTilesContainer = useMutableTilesContainer; @@ -246,26 +245,11 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D * @throws TilesException in case of setup failure */ public void afterPropertiesSet() throws TilesException { - - SpringWildcardServletTilesApplicationContext preliminaryContext = - new SpringWildcardServletTilesApplicationContext(this.servletContext); - + ApplicationContext preliminaryContext = new SpringWildcardServletTilesApplicationContext(this.servletContext); if (this.tilesInitializer == null) { this.tilesInitializer = new SpringTilesInitializer(); } this.tilesInitializer.initialize(preliminaryContext); - - if (this.completeAutoload) { - // We need to do this after initialization simply because we're reusing the - // original CompleteAutoloadTilesInitializer above. We cannot subclass - // CompleteAutoloadTilesInitializer when compiling against Tiles 2.1... - logger.debug("Registering Tiles 3.0 SpringLocaleResolver for complete-autoload setup"); - BasicTilesContainer container = (BasicTilesContainer) TilesAccess.getContainer(preliminaryContext); - BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(container.getDefinitionsFactory()); - if (bw.isWritableProperty("localeResolver")) { - bw.setPropertyValue("localeResolver", new SpringLocaleResolver()); - } - } } /** @@ -332,8 +316,7 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D if (definitionsFactoryClass != null) { DefinitionsFactory factory = BeanUtils.instantiate(definitionsFactoryClass); if (factory instanceof org.apache.tiles.request.ApplicationContextAware) { - ((org.apache.tiles.request.ApplicationContextAware) factory) - .setApplicationContext(applicationContext); + ((org.apache.tiles.request.ApplicationContextAware) factory).setApplicationContext(applicationContext); } BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(factory); if (bw.isWritableProperty("localeResolver")) { @@ -380,6 +363,34 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D } } + private class SpringCompleteAutoloadTilesInitializer extends CompleteAutoloadTilesInitializer { + + @Override + protected AbstractTilesContainerFactory createContainerFactory(ApplicationContext context) { + return new SpringCompleteAutoloadTilesContainerFactory(); + } + } + + private class SpringCompleteAutoloadTilesContainerFactory extends CompleteAutoloadTilesContainerFactory { + + @Override + protected AttributeEvaluatorFactory createAttributeEvaluatorFactory( + ApplicationContext applicationContext, LocaleResolver resolver) { + return new BasicAttributeEvaluatorFactory(new DirectAttributeEvaluator()); + } + + @Override + public TilesContainer createContainer(ApplicationContext applicationContext) { + CachingTilesContainer cachingContainer = (CachingTilesContainer) super.createContainer(applicationContext); + BasicTilesContainer tilesContainer = (BasicTilesContainer) cachingContainer.getWrappedContainer(); + BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(tilesContainer.getDefinitionsFactory()); + if (bw.isWritableProperty("localeResolver")) { + bw.setPropertyValue("localeResolver", new SpringLocaleResolver()); + } + return tilesContainer; + } + } + private class TilesElActivator { @@ -405,6 +416,7 @@ public class TilesConfigurer implements ServletContextAware, InitializingBean, D private static class CompositeELResolverImpl extends CompositeELResolver { public CompositeELResolverImpl() { + add(new ScopeELResolver()); add(new TilesContextELResolver(new TilesContextBeanELResolver())); add(new TilesContextBeanELResolver()); add(new ArrayELResolver(false)); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java index ca2a919043e..3dca4eda942 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/FrameworkServlet.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,11 +39,13 @@ import org.springframework.context.i18n.LocaleContext; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.i18n.SimpleLocaleContext; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.context.ConfigurableWebApplicationContext; +import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; @@ -638,7 +640,10 @@ public abstract class FrameworkServlet extends HttpServletBean { // the context is refreshed; do it eagerly here to ensure servlet property sources // are in place for use in any post-processing or initialization that occurs // below prior to #refresh - wac.getEnvironment().initPropertySources(getServletContext(), getServletConfig()); + ConfigurableEnvironment env = wac.getEnvironment(); + if (env instanceof ConfigurableWebEnvironment) { + ((ConfigurableWebEnvironment)env).initPropertySources(getServletContext(), getServletConfig()); + } postProcessWebApplicationContext(wac); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/HttpServletBean.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/HttpServletBean.java index 044b56d2aea..bcd3a4d1606 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/HttpServletBean.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/HttpServletBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import javax.servlet.http.HttpServlet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - import org.springframework.beans.BeanWrapper; import org.springframework.beans.BeansException; import org.springframework.beans.MutablePropertyValues; @@ -35,6 +34,7 @@ import org.springframework.beans.PropertyAccessorFactory; import org.springframework.beans.PropertyValue; import org.springframework.beans.PropertyValues; import org.springframework.context.EnvironmentAware; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.io.Resource; @@ -42,9 +42,8 @@ import org.springframework.core.io.ResourceEditor; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.StringUtils; -import org.springframework.web.context.ConfigurableWebEnvironment; -import org.springframework.web.context.support.StandardServletEnvironment; import org.springframework.web.context.support.ServletContextResourceLoader; +import org.springframework.web.context.support.StandardServletEnvironment; /** * Simple extension of {@link javax.servlet.http.HttpServlet} which treats @@ -91,7 +90,7 @@ public abstract class HttpServletBean extends HttpServlet */ private final Set requiredProperties = new HashSet(); - private ConfigurableWebEnvironment environment; + private ConfigurableEnvironment environment; /** @@ -187,11 +186,11 @@ public abstract class HttpServletBean extends HttpServlet /** * {@inheritDoc} * @throws IllegalArgumentException if environment is not assignable to - * {@code ConfigurableWebEnvironment}. + * {@code ConfigurableEnvironment}. */ public void setEnvironment(Environment environment) { - Assert.isInstanceOf(ConfigurableWebEnvironment.class, environment); - this.environment = (ConfigurableWebEnvironment)environment; + Assert.isInstanceOf(ConfigurableEnvironment.class, environment); + this.environment = (ConfigurableEnvironment) environment; } /** @@ -199,7 +198,7 @@ public abstract class HttpServletBean extends HttpServlet *

    If {@code null}, a new environment will be initialized via * {@link #createEnvironment()}. */ - public ConfigurableWebEnvironment getEnvironment() { + public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { this.environment = this.createEnvironment(); } @@ -210,7 +209,7 @@ public abstract class HttpServletBean extends HttpServlet * Create and return a new {@link StandardServletEnvironment}. Subclasses may override * in order to configure the environment or specialize the environment type returned. */ - protected ConfigurableWebEnvironment createEnvironment() { + protected ConfigurableEnvironment createEnvironment() { return new StandardServletEnvironment(); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java index 07d2b1a87e5..53e4893f99e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/AsyncSupportConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,7 +29,7 @@ import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; /** - * Helps with configuring a options for asynchronous request processing. + * Helps with configuring options for asynchronous request processing. * * @author Rossen Stoyanchev * @since 3.2 @@ -50,9 +50,9 @@ public class AsyncSupportConfigurer { /** * Set the default {@link AsyncTaskExecutor} to use when a controller method * returns a {@link Callable}. Controller methods can override this default on - * a per-request basis by returning an {@link WebAsyncTask}. + * a per-request basis by returning a {@link WebAsyncTask}. * - *

    By default a {@link SimpleAsyncTaskExecutor} instance is used and it's + *

    By default a {@link SimpleAsyncTaskExecutor} instance is used, and it's * highly recommended to change that default in production since the simple * executor does not re-use threads. * @@ -79,7 +79,7 @@ public class AsyncSupportConfigurer { } /** - * Configure lifecycle intercepters with callbacks around concurrent request + * Configure lifecycle interceptors with callbacks around concurrent request * execution that starts when a controller returns a * {@link java.util.concurrent.Callable}. * @@ -92,7 +92,7 @@ public class AsyncSupportConfigurer { } /** - * Configure lifecycle intercepters with callbacks around concurrent request + * Configure lifecycle interceptors with callbacks around concurrent request * execution that starts when a controller returns a {@link DeferredResult}. * * @param interceptors the interceptors to register diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java index 9900bc32a79..7073e7122a3 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/ContentNegotiationConfigurer.java @@ -15,6 +15,7 @@ */ package org.springframework.web.servlet.config.annotation; +import java.util.HashMap; import java.util.Map; import javax.servlet.ServletContext; @@ -36,7 +37,9 @@ import org.springframework.web.accept.ContentNegotiationManagerFactoryBean; */ public class ContentNegotiationConfigurer { - private ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + private final ContentNegotiationManagerFactoryBean factoryBean = new ContentNegotiationManagerFactoryBean(); + + private final Map mediaTypes = new HashMap(); /** @@ -64,7 +67,7 @@ public class ContentNegotiationConfigurer { * still be used in conjunction with {@link #favorPathExtension(boolean)}. */ public ContentNegotiationConfigurer mediaType(String extension, MediaType mediaType) { - this.factoryBean.getMediaTypes().put(extension, mediaType); + this.mediaTypes.put(extension, mediaType); return this; } @@ -75,7 +78,7 @@ public class ContentNegotiationConfigurer { */ public ContentNegotiationConfigurer mediaTypes(Map mediaTypes) { if (mediaTypes != null) { - this.factoryBean.getMediaTypes().putAll(mediaTypes); + this.mediaTypes.putAll(mediaTypes); } return this; } @@ -86,7 +89,7 @@ public class ContentNegotiationConfigurer { * still be used in conjunction with {@link #favorPathExtension(boolean)}. */ public ContentNegotiationConfigurer replaceMediaTypes(Map mediaTypes) { - this.factoryBean.getMediaTypes().clear(); + this.mediaTypes.clear(); mediaTypes(mediaTypes); return this; } @@ -157,6 +160,9 @@ public class ContentNegotiationConfigurer { * Return the configured {@link ContentNegotiationManager} instance */ protected ContentNegotiationManager getContentNegotiationManager() throws Exception { + if (!this.mediaTypes.isEmpty()) { + this.factoryBean.addMediaTypes(mediaTypes); + } this.factoryBean.afterPropertiesSet(); return this.factoryBean.getObject(); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java index 28fbe1efbc7..c8f11c8175f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMethodMapping.java @@ -165,25 +165,17 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap * under the same mapping */ protected void registerHandlerMethod(Object handler, Method method, T mapping) { - HandlerMethod handlerMethod; - if (handler instanceof String) { - String beanName = (String) handler; - handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); - } - else { - handlerMethod = new HandlerMethod(handler, method); - } - + HandlerMethod newHandlerMethod = createHandlerMethod(handler, method); HandlerMethod oldHandlerMethod = handlerMethods.get(mapping); - if (oldHandlerMethod != null && !oldHandlerMethod.equals(handlerMethod)) { - throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + handlerMethod.getBean() - + "' bean method \n" + handlerMethod + "\nto " + mapping + ": There is already '" + if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) { + throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() + + "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" + oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped."); } - this.handlerMethods.put(mapping, handlerMethod); + this.handlerMethods.put(mapping, newHandlerMethod); if (logger.isInfoEnabled()) { - logger.info("Mapped \"" + mapping + "\" onto " + handlerMethod); + logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod); } Set patterns = getMappingPathPatterns(mapping); @@ -194,6 +186,24 @@ public abstract class AbstractHandlerMethodMapping extends AbstractHandlerMap } } + /** + * Create the HandlerMethod instance. + * @param handler either a bean name or an actual handler instance + * @param method the target method + * @return the created HandlerMethod + */ + protected HandlerMethod createHandlerMethod(Object handler, Method method) { + HandlerMethod handlerMethod; + if (handler instanceof String) { + String beanName = (String) handler; + handlerMethod = new HandlerMethod(beanName, getApplicationContext(), method); + } + else { + handlerMethod = new HandlerMethod(handler, method); + } + return handlerMethod; + } + /** * Extract and return the URL paths contained in a mapping. */ diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java index 0d58e8c134c..3e5fd736592 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 @@ package org.springframework.web.servlet.mvc.annotation; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.ResponseStatus; @@ -32,9 +35,17 @@ import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; *

    This exception resolver is enabled by default in the {@link org.springframework.web.servlet.DispatcherServlet}. * * @author Arjen Poutsma + * @author Rossen Stoyanchev * @since 3.0 */ -public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver { +public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware { + + private MessageSource messageSource; + + + public void setMessageSource(MessageSource messageSource) { + this.messageSource = messageSource; + } @Override protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @@ -69,6 +80,9 @@ public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionRes int statusCode = responseStatus.value().value(); String reason = responseStatus.reason(); + if (this.messageSource != null) { + reason = this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()); + } if (!StringUtils.hasLength(reason)) { response.sendError(statusCode); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java index dfdfc05ed25..cc242bf756a 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,15 +28,19 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.UnsatisfiedServletRequestParameterException; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.handler.AbstractHandlerMethodMapping; +import org.springframework.web.servlet.mvc.condition.NameValueExpression; +import org.springframework.web.servlet.mvc.condition.ParamsRequestCondition; import org.springframework.web.util.WebUtils; /** @@ -100,7 +104,7 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables); if (isMatrixVariableContentAvailable()) { - request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(uriVariables)); + request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, extractMatrixVariables(request, uriVariables)); } if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) { @@ -113,7 +117,9 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe return !getUrlPathHelper().shouldRemoveSemicolonContent(); } - private Map> extractMatrixVariables(Map uriVariables) { + private Map> extractMatrixVariables( + HttpServletRequest request, Map uriVariables) { + Map> result = new LinkedHashMap>(); for (Entry uriVar : uriVariables.entrySet()) { String uriVarValue = uriVar.getValue(); @@ -134,7 +140,8 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe uriVariables.put(uriVar.getKey(), uriVarValue.substring(0, semicolonIndex)); } - result.put(uriVar.getKey(), WebUtils.parseMatrixVariables(matrixVariables)); + MultiValueMap vars = WebUtils.parseMatrixVariables(matrixVariables); + result.put(uriVar.getKey(), getUrlPathHelper().decodeMatrixVariables(request, vars)); } return result; } @@ -182,14 +189,17 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe Set consumableMediaTypes; Set producibleMediaTypes; + Set paramConditions; if (patternAndMethodMatches.isEmpty()) { consumableMediaTypes = getConsumableMediaTypes(request, patternMatches); producibleMediaTypes = getProdicubleMediaTypes(request, patternMatches); + paramConditions = getRequestParams(request, patternMatches); } else { consumableMediaTypes = getConsumableMediaTypes(request, patternAndMethodMatches); producibleMediaTypes = getProdicubleMediaTypes(request, patternAndMethodMatches); + paramConditions = getRequestParams(request, patternAndMethodMatches); } if (!consumableMediaTypes.isEmpty()) { @@ -202,6 +212,10 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe else if (!producibleMediaTypes.isEmpty()) { throw new HttpMediaTypeNotAcceptableException(new ArrayList(producibleMediaTypes)); } + else if (!CollectionUtils.isEmpty(paramConditions)) { + String[] params = paramConditions.toArray(new String[paramConditions.size()]); + throw new UnsatisfiedServletRequestParameterException(params, request.getParameterMap()); + } else { return null; } @@ -227,4 +241,18 @@ public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMe return result; } + private Set getRequestParams(HttpServletRequest request, Set partialMatches) { + for (RequestMappingInfo partialMatch : partialMatches) { + ParamsRequestCondition condition = partialMatch.getParamsCondition(); + if (!CollectionUtils.isEmpty(condition.getExpressions()) && (condition.getMatchingCondition(request) == null)) { + Set expressions = new HashSet(); + for (NameValueExpression expr : condition.getExpressions()) { + expressions.add(expr.toString()); + } + return expressions; + } + } + return null; + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java index ccdf7d3703e..48cff20fe54 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/AbstractMessageConverterMethodProcessor.java @@ -123,7 +123,7 @@ public abstract class AbstractMessageConverterMethodProcessor extends AbstractMe } } if (compatibleMediaTypes.isEmpty()) { - throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes); + throw new HttpMediaTypeNotAcceptableException(producibleMediaTypes); } List mediaTypes = new ArrayList(compatibleMediaTypes); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java index e93029aa640..edb113e6079 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/HttpEntityMethodProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -68,7 +68,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro public boolean supportsReturnType(MethodParameter returnType) { Class parameterType = returnType.getParameterType(); - return HttpEntity.class.equals(parameterType) || ResponseEntity.class.equals(parameterType); + return HttpEntity.class.isAssignableFrom(parameterType) || ResponseEntity.class.isAssignableFrom(parameterType); } public Object resolveArgument( diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 315cf8f68e8..bb9ddd72b1f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,9 +20,11 @@ import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import org.springframework.context.EmbeddedValueResolverAware; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.stereotype.Controller; import org.springframework.util.Assert; +import org.springframework.util.StringValueResolver; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.mvc.condition.AbstractRequestCondition; @@ -46,7 +48,8 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi * @author Rossen Stoyanchev * @since 3.1 */ -public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping { +public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping + implements EmbeddedValueResolverAware { private boolean useSuffixPatternMatch = true; @@ -58,6 +61,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi private final List fileExtensions = new ArrayList(); + private StringValueResolver embeddedValueResolver; + /** * Whether to use suffix pattern match (".*") when matching patterns to @@ -101,6 +106,11 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi this.useTrailingSlashMatch = useTrailingSlashMatch; } + @Override + public void setEmbeddedValueResolver(StringValueResolver resolver) { + this.embeddedValueResolver = resolver; + } + /** * Set the {@link ContentNegotiationManager} to use to determine requested media types. * If not set, the default constructor is used. @@ -142,15 +152,15 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi * Return the file extensions to use for suffix pattern matching. */ public List getFileExtensions() { - return fileExtensions; + return this.fileExtensions; } @Override public void afterPropertiesSet() { - super.afterPropertiesSet(); if (this.useRegisteredSuffixPatternMatch) { this.fileExtensions.addAll(contentNegotiationManager.getAllFileExtensions()); } + super.afterPropertiesSet(); } /** @@ -227,8 +237,9 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi * Created a RequestMappingInfo from a RequestMapping annotation. */ private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition customCondition) { + String[] patterns = resolveEmbeddedValuesInPatterns(annotation.value()); return new RequestMappingInfo( - new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), + new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions), new RequestMethodsRequestCondition(annotation.method()), new ParamsRequestCondition(annotation.params()), @@ -238,4 +249,21 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi customCondition); } + /** + * Resolve placeholder values in the given array of patterns. + * @return a new array with updated patterns + */ + protected String[] resolveEmbeddedValuesInPatterns(String[] patterns) { + if (this.embeddedValueResolver == null) { + return patterns; + } + else { + String[] resolvedPatterns = new String[patterns.length]; + for (int i=0; i < patterns.length; i++) { + resolvedPatterns[i] = this.embeddedValueResolver.resolveStringValue(patterns[i]); + } + return resolvedPatterns; + } + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java index ae16bdb9946..d6e6d010539 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractDispatcherServletInitializer.java @@ -94,6 +94,11 @@ public abstract class AbstractDispatcherServletInitializer ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); + + Assert.notNull(registration, + "Failed to register servlet with name '" + servletName + "'." + + "Check if there is another servlet registered under the same name."); + registration.setLoadOnStartup(1); registration.addMapping(getServletMappings()); registration.setAsyncSupported(isAsyncSupported()); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java index 3f586a94467..cf33995355d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ import org.springframework.ui.context.ThemeSource; import org.springframework.ui.context.support.ResourceBundleThemeSource; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; @@ -61,6 +62,7 @@ import org.springframework.web.util.WebUtils; * appropriate fallback for the locale (the HttpServletRequest's primary locale). * * @author Juergen Hoeller + * @author Rossen Stoyanchev * @since 03.03.2003 * @see org.springframework.web.servlet.DispatcherServlet * @see org.springframework.web.servlet.view.AbstractView#setRequestContextAttribute @@ -233,8 +235,10 @@ public class RequestContext { } /** - * Determine the fallback locale for this context.

    The default implementation checks for a JSTL locale attribute - * in request, session or application scope; if not found, returns the {@code HttpServletRequest.getLocale()}. + * Determine the fallback locale for this context. + *

    The default implementation checks for a JSTL locale attribute in request, + * session or application scope; if not found, returns the + * {@code HttpServletRequest.getLocale()}. * @return the fallback locale (never {@code null}) * @see javax.servlet.http.HttpServletRequest#getLocale() */ @@ -249,8 +253,8 @@ public class RequestContext { } /** - * Determine the fallback theme for this context.

    The default implementation returns the default theme (with name - * "theme"). + * Determine the fallback theme for this context. + *

    The default implementation returns the default theme (with name "theme"). * @return the fallback theme (never {@code null}) */ protected Theme getFallbackTheme() { @@ -309,8 +313,8 @@ public class RequestContext { } /** - * Return the current theme (never {@code null}).

    Resolved lazily for more efficiency when theme support is - * not being used. + * Return the current theme (never {@code null}). + *

    Resolved lazily for more efficiency when theme support is not being used. */ public final Theme getTheme() { if (this.theme == null) { @@ -350,7 +354,8 @@ public class RequestContext { /** * Set the UrlPathHelper to use for context path and request URI decoding. Can be used to pass a shared - * UrlPathHelper instance in.

    A default UrlPathHelper is always available. + * UrlPathHelper instance in. + *

    A default UrlPathHelper is always available. */ public void setUrlPathHelper(UrlPathHelper urlPathHelper) { Assert.notNull(urlPathHelper, "UrlPathHelper must not be null"); @@ -359,7 +364,8 @@ public class RequestContext { /** * Return the UrlPathHelper used for context path and request URI decoding. Can be used to configure the current - * UrlPathHelper.

    A default UrlPathHelper is always available. + * UrlPathHelper. + *

    A default UrlPathHelper is always available. */ public UrlPathHelper getUrlPathHelper() { return this.urlPathHelper; @@ -376,8 +382,8 @@ public class RequestContext { /** * Return the context path of the original request, that is, the path that indicates the current web application. - * This is useful for building links to other resources within the application.

    Delegates to the UrlPathHelper - * for decoding. + * This is useful for building links to other resources within the application. + *

    Delegates to the UrlPathHelper for decoding. * @see javax.servlet.http.HttpServletRequest#getContextPath * @see #getUrlPathHelper */ @@ -422,22 +428,22 @@ public class RequestContext { * context path and the servlet path of the original request. This is useful * for building links to other resources within the application where a * servlet mapping of the style {@code "/main/*"} is used. - *

    Delegates to the UrlPathHelper for decoding the context path. - * @see javax.servlet.http.HttpServletRequest#getContextPath - * @see javax.servlet.http.HttpServletRequest#getServletPath() - * @see #getUrlPathHelper + *

    Delegates to the UrlPathHelper to determine the context and servlet path. */ public String getPathToServlet() { - return this.urlPathHelper.getOriginatingContextPath(this.request) - + this.urlPathHelper.getOriginatingServletPath(this.request); + String path = this.urlPathHelper.getOriginatingContextPath(this.request); + if (StringUtils.hasText(this.urlPathHelper.getPathWithinServletMapping(this.request))) { + path += this.urlPathHelper.getOriginatingServletPath(this.request); + } + return path; } /** * Return the request URI of the original request, that is, the invoked URL without parameters. This is particularly * useful as HTML form action target, possibly in combination with the original query string.

    Note this * implementation will correctly resolve to the URI of any originating root request in the presence of a forwarded - * request. However, this can only work when the Servlet 2.4 'forward' request attributes are present.

    Delegates - * to the UrlPathHelper for decoding. + * request. However, this can only work when the Servlet 2.4 'forward' request attributes are present. + *

    Delegates to the UrlPathHelper for decoding. * @see #getQueryString * @see org.springframework.web.util.UrlPathHelper#getOriginatingRequestUri * @see #getUrlPathHelper @@ -573,8 +579,9 @@ public class RequestContext { } /** - * Retrieve the theme message for the given code.

    Note that theme messages are never HTML-escaped, as they - * typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the theme message for the given code. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param code code of the message * @param defaultMessage String to return if the lookup fails * @return the message @@ -584,8 +591,9 @@ public class RequestContext { } /** - * Retrieve the theme message for the given code.

    Note that theme messages are never HTML-escaped, as they - * typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the theme message for the given code. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param code code of the message * @param args arguments for the message, or {@code null} if none * @param defaultMessage String to return if the lookup fails @@ -596,8 +604,9 @@ public class RequestContext { } /** - * Retrieve the theme message for the given code.

    Note that theme messages are never HTML-escaped, as they - * typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the theme message for the given code. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param code code of the message * @param args arguments for the message as a List, or {@code null} if none * @param defaultMessage String to return if the lookup fails @@ -609,8 +618,9 @@ public class RequestContext { } /** - * Retrieve the theme message for the given code.

    Note that theme messages are never HTML-escaped, as they - * typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the theme message for the given code. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param code code of the message * @return the message * @throws org.springframework.context.NoSuchMessageException if not found @@ -620,8 +630,9 @@ public class RequestContext { } /** - * Retrieve the theme message for the given code.

    Note that theme messages are never HTML-escaped, as they - * typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the theme message for the given code. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param code code of the message * @param args arguments for the message, or {@code null} if none * @return the message @@ -632,8 +643,9 @@ public class RequestContext { } /** - * Retrieve the theme message for the given code.

    Note that theme messages are never HTML-escaped, as they - * typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the theme message for the given code. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param code code of the message * @param args arguments for the message as a List, or {@code null} if none * @return the message @@ -644,8 +656,9 @@ public class RequestContext { } /** - * Retrieve the given MessageSourceResolvable in the current theme.

    Note that theme messages are never - * HTML-escaped, as they typically denote theme-specific resource paths and not client-visible messages. + * Retrieve the given MessageSourceResolvable in the current theme. + *

    Note that theme messages are never HTML-escaped, as they typically denote + * theme-specific resource paths and not client-visible messages. * @param resolvable the MessageSourceResolvable * @return the message * @throws org.springframework.context.NoSuchMessageException if not found diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java index 8aa58643900..7ad0d49b78d 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -95,9 +95,12 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { String scheme = request.getScheme(); int port = request.getServerPort(); + String header = request.getHeader("X-Forwarded-Host"); + String host = StringUtils.hasText(header) ? header: request.getServerName(); + ServletUriComponentsBuilder builder = new ServletUriComponentsBuilder(); builder.scheme(scheme); - builder.host(request.getServerName()); + builder.host(host); if ((scheme.equals("http") && port != 80) || (scheme.equals("https") && port != 443)) { builder.port(port); } @@ -138,7 +141,10 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { return fromRequest(getCurrentRequest()); } - private static HttpServletRequest getCurrentRequest() { + /** + * Obtain the request through {@link RequestContextHolder}. + */ + protected static HttpServletRequest getCurrentRequest() { RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); Assert.state(requestAttributes != null, "Could not find current request via RequestContextHolder"); Assert.isInstanceOf(ServletRequestAttributes.class, requestAttributes); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java index 7f759e8c97f..ec1cf27cc30 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/WebContentGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,9 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.util.StringUtils; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.HttpSessionRequiredException; +import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.support.WebApplicationObjectSupport; +import org.springframework.web.servlet.mvc.LastModified; /** * Convenient superclass for any kind of web content generator, @@ -79,6 +81,8 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { private int cacheSeconds = -1; + private boolean alwaysMustRevalidate = false; + /** * Create a new WebContentGenerator which supports @@ -194,6 +198,25 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { return this.useCacheControlNoStore; } + /** + * An option to add 'must-revalidate' to every Cache-Control header. This + * may be useful with annotated controller methods, which can + * programmatically do a lastModified calculation as described in + * {@link WebRequest#checkNotModified(long)}. Default is "false", + * effectively relying on whether the handler implements + * {@link LastModified} or not. + */ + public void setAlwaysMustRevalidate(boolean mustRevalidate) { + this.alwaysMustRevalidate = mustRevalidate; + } + + /** + * Return whether 'must-revaliate' is added to every Cache-Control header. + */ + public boolean isAlwaysMustRevalidate() { + return alwaysMustRevalidate; + } + /** * Cache content for the given number of seconds. Default is -1, * indicating no generation of cache-related headers. @@ -313,7 +336,7 @@ public abstract class WebContentGenerator extends WebApplicationObjectSupport { if (this.useCacheControlHeader) { // HTTP 1.1 header String headerValue = "max-age=" + seconds; - if (mustRevalidate) { + if (mustRevalidate || this.alwaysMustRevalidate) { headerValue += ", must-revalidate"; } response.setHeader(HEADER_CACHE_CONTROL, headerValue); diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java index f827631d62e..1af5e3972f5 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/tags/form/FormTag.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -50,6 +50,7 @@ import org.springframework.web.util.HtmlUtils; * @author Rob Harrop * @author Juergen Hoeller * @author Scott Andrews + * @author Rossen Stoyanchev * @since 2.0 * @see org.springframework.web.servlet.mvc.SimpleFormController */ @@ -411,6 +412,10 @@ public class FormTag extends AbstractHtmlElementTag { protected String resolveAction() throws JspException { String action = getAction(); if (StringUtils.hasText(action)) { + String pathToServlet = getRequestContext().getPathToServlet(); + if (action.startsWith("/") && !action.startsWith(getRequestContext().getContextPath())) { + action = pathToServlet + action; + } action = getDisplayString(evaluate(ACTION_ATTRIBUTE, action)); return processAction(action); } @@ -469,8 +474,8 @@ public class FormTag extends AbstractHtmlElementTag { if (hiddenFields != null) { for (String name : hiddenFields.keySet()) { this.tagWriter.appendValue(""); - this.tagWriter.appendValue("\n"); + this.tagWriter.appendValue("name=\"" + name + "\" value=\"" + hiddenFields.get(name) + "\" "); + this.tagWriter.appendValue("/>\n"); } } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java index cc497262972..2d543941af6 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java @@ -23,6 +23,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Properties; import java.util.Set; import javax.activation.FileTypeMap; @@ -196,7 +197,9 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport @Deprecated public void setMediaTypes(Map mediaTypes) { if (mediaTypes != null) { - this.cnManagerFactoryBean.getMediaTypes().putAll(mediaTypes); + Properties props = new Properties(); + props.putAll(mediaTypes); + this.cnManagerFactoryBean.setMediaTypes(props); } } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java index 147875e4ca4..b73e69ae6f2 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/DispatcherServletTests.java @@ -16,12 +16,6 @@ package org.springframework.web.servlet; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.sameInstance; -import static org.junit.Assert.assertThat; - import java.io.IOException; import java.util.Locale; @@ -36,15 +30,15 @@ import junit.framework.TestCase; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValue; -import org.springframework.tests.sample.beans.TestBean; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.core.env.ConfigurableEnvironment; -import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.env.DummyEnvironment; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.mock.web.test.MockServletConfig; import org.springframework.mock.web.test.MockServletContext; +import org.springframework.tests.sample.beans.TestBean; import org.springframework.web.bind.EscapedErrors; import org.springframework.web.context.ConfigurableWebEnvironment; import org.springframework.web.context.ServletConfigAwareBean; @@ -65,6 +59,9 @@ import org.springframework.web.servlet.theme.AbstractThemeResolver; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.util.WebUtils; +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + /** * @author Rod Johnson * @author Juergen Hoeller @@ -836,14 +833,14 @@ public class DispatcherServletTests extends TestCase { public void testEnvironmentOperations() { DispatcherServlet servlet = new DispatcherServlet(); - ConfigurableWebEnvironment defaultEnv = servlet.getEnvironment(); + ConfigurableEnvironment defaultEnv = servlet.getEnvironment(); assertThat(defaultEnv, notNullValue()); ConfigurableEnvironment env1 = new StandardServletEnvironment(); servlet.setEnvironment(env1); // should succeed assertThat(servlet.getEnvironment(), sameInstance(env1)); try { - servlet.setEnvironment(new StandardEnvironment()); - fail("expected exception"); + servlet.setEnvironment(new DummyEnvironment()); + fail("expected IllegalArgumentException for non-configurable Environment"); } catch (IllegalArgumentException ex) { } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java index 684550e18d3..25348f53f7a 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/annotation/ResponseStatusExceptionResolverTests.java @@ -1,13 +1,20 @@ package org.springframework.web.servlet.mvc.annotation; import static org.junit.Assert.*; + +import java.util.Locale; + import org.junit.Before; import org.junit.Test; +import org.springframework.context.ApplicationContext; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.support.StaticMessageSource; import org.springframework.http.HttpStatus; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.servlet.ModelAndView; /** @author Arjen Poutsma */ @@ -46,6 +53,24 @@ public class ResponseStatusExceptionResolverTests { assertEquals("Invalid status reason", "You suck!", response.getErrorMessage()); } + @Test + public void statusCodeAndReasonMessage() { + Locale locale = Locale.CHINESE; + LocaleContextHolder.setLocale(locale); + try { + StaticMessageSource messageSource = new StaticMessageSource(); + messageSource.addMessage("gone.reason", locale, "Gone reason message"); + exceptionResolver.setMessageSource(messageSource); + + StatusCodeAndReasonMessageException ex = new StatusCodeAndReasonMessageException(); + ModelAndView mav = exceptionResolver.resolveException(request, response, null, ex); + assertEquals("Invalid status reason", "Gone reason message", response.getErrorMessage()); + } + finally { + LocaleContextHolder.resetLocaleContext(); + } + } + @Test public void notAnnotated() { Exception ex = new Exception(); @@ -65,4 +90,11 @@ public class ResponseStatusExceptionResolverTests { private static class StatusCodeAndReasonException extends Exception { } + + @ResponseStatus(value = HttpStatus.GONE, reason = "gone.reason") + @SuppressWarnings("serial") + private static class StatusCodeAndReasonMessageException extends Exception { + + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java index e0764f7e7bc..2db8412a827 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/RequestMappingInfoHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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,6 +42,7 @@ import org.springframework.util.MultiValueMap; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotSupportedException; import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.UnsatisfiedServletRequestParameterException; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -202,6 +203,19 @@ public class RequestMappingInfoHandlerMappingTests { } } + @Test + public void testUnsatisfiedServletRequestParameterException() throws Exception { + try { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/params"); + this.handlerMapping.getHandler(request); + fail("UnsatisfiedServletRequestParameterException expected"); + } + catch (UnsatisfiedServletRequestParameterException ex) { + assertArrayEquals("Invalid request parameter conditions", + new String[] { "foo=bar" }, ex.getParamConditions()); + } + } + @Test public void uriTemplateVariables() { PatternsRequestCondition patterns = new PatternsRequestCondition("/{path1}/{path2}"); @@ -310,48 +324,63 @@ public class RequestMappingInfoHandlerMappingTests { MultiValueMap matrixVariables; Map uriVariables; - String lookupPath = "/cars;colors=red,blue,green;year=2012"; - - // Pattern "/{cars}" : matrix variables stripped from "cars" variable - request = new MockHttpServletRequest(); - testHandleMatch(request, "/{cars}", lookupPath); + testHandleMatch(request, "/{cars}", "/cars;colors=red,blue,green;year=2012"); matrixVariables = getMatrixVariables(request, "cars"); + uriVariables = getUriTemplateVariables(request); + assertNotNull(matrixVariables); assertEquals(Arrays.asList("red", "blue", "green"), matrixVariables.get("colors")); assertEquals("2012", matrixVariables.getFirst("year")); - - uriVariables = getUriTemplateVariables(request); assertEquals("cars", uriVariables.get("cars")); - // Pattern "/{cars:[^;]+}{params}" : "cars" and "params" variables unchanged - request = new MockHttpServletRequest(); - testHandleMatch(request, "/{cars:[^;]+}{params}", lookupPath); + testHandleMatch(request, "/{cars:[^;]+}{params}", "/cars;colors=red,blue,green;year=2012"); matrixVariables = getMatrixVariables(request, "params"); + uriVariables = getUriTemplateVariables(request); + assertNotNull(matrixVariables); assertEquals(Arrays.asList("red", "blue", "green"), matrixVariables.get("colors")); assertEquals("2012", matrixVariables.getFirst("year")); - - uriVariables = getUriTemplateVariables(request); assertEquals("cars", uriVariables.get("cars")); assertEquals(";colors=red,blue,green;year=2012", uriVariables.get("params")); - // matrix variables not present : "params" variable is empty - request = new MockHttpServletRequest(); testHandleMatch(request, "/{cars:[^;]+}{params}", "/cars"); matrixVariables = getMatrixVariables(request, "params"); - assertNull(matrixVariables); - uriVariables = getUriTemplateVariables(request); + + assertNull(matrixVariables); assertEquals("cars", uriVariables.get("cars")); assertEquals("", uriVariables.get("params")); } + @Test + public void matrixVariablesDecoding() { + + MockHttpServletRequest request; + + UrlPathHelper urlPathHelper = new UrlPathHelper(); + urlPathHelper.setUrlDecode(false); + urlPathHelper.setRemoveSemicolonContent(false); + + this.handlerMapping.setUrlPathHelper(urlPathHelper ); + + request = new MockHttpServletRequest(); + testHandleMatch(request, "/path{filter}", "/path;mvar=a%2fb"); + + MultiValueMap matrixVariables = getMatrixVariables(request, "filter"); + Map uriVariables = getUriTemplateVariables(request); + + assertNotNull(matrixVariables); + assertEquals(Arrays.asList("a/b"), matrixVariables.get("mvar")); + assertEquals(";mvar=a/b", uriVariables.get("filter")); + } + + private void testHandleMatch(MockHttpServletRequest request, String pattern, String lookupPath) { PatternsRequestCondition patterns = new PatternsRequestCondition(pattern); RequestMappingInfo info = new RequestMappingInfo(patterns, null, null, null, null, null, null); @@ -399,6 +428,11 @@ public class RequestMappingInfoHandlerMappingTests { return ""; } + @RequestMapping(value = "/params", params="foo=bar") + public String param() { + return ""; + } + @RequestMapping(value = "/content", produces="application/xml") public String xmlContent() { return ""; diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java index b5a6bd139e5..63fce21fa7d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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. @@ -15,19 +15,28 @@ */ package org.springframework.web.servlet.mvc.method.annotation; -import static org.junit.Assert.*; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import org.junit.Before; import org.junit.Test; import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.util.StringValueResolver; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.support.StaticWebApplicationContext; -import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; /** * Tests for {@link RequestMappingHandlerMapping}. @@ -62,6 +71,35 @@ public class RequestMappingHandlerMappingTests { assertEquals(Arrays.asList("json"), this.handlerMapping.getFileExtensions()); } + @Test + public void useRegsiteredSuffixPatternMatchInitialization() { + + Map fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(fileExtensions); + ContentNegotiationManager manager = new ContentNegotiationManager(strategy); + + final Set extensions = new HashSet(); + + RequestMappingHandlerMapping hm = new RequestMappingHandlerMapping() { + @Override + protected RequestMappingInfo getMappingForMethod(Method method, Class handlerType) { + extensions.addAll(getFileExtensions()); + return super.getMappingForMethod(method, handlerType); + } + }; + + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + wac.registerSingleton("testController", TestController.class); + wac.refresh(); + + hm.setContentNegotiationManager(manager); + hm.setUseRegisteredSuffixPatternMatch(true); + hm.setApplicationContext(wac); + hm.afterPropertiesSet(); + + assertEquals(Collections.singleton("json"), extensions); + } + @Test public void useSuffixPatternMatch() { assertTrue(this.handlerMapping.useSuffixPatternMatch()); @@ -78,4 +116,27 @@ public class RequestMappingHandlerMappingTests { this.handlerMapping.useSuffixPatternMatch()); } + @Test + public void resolveEmbeddedValuesInPatterns() { + this.handlerMapping.setEmbeddedValueResolver(new StringValueResolver() { + public String resolveStringValue(String value) { + return "/${pattern}/bar".equals(value) ? "/foo/bar" : value; + } + }); + + String[] patterns = new String[] { "/foo", "/${pattern}/bar" }; + String[] result = this.handlerMapping.resolveEmbeddedValuesInPatterns(patterns); + + assertArrayEquals(new String[] { "/foo", "/foo/bar" }, result); + } + + + @Controller + static class TestController { + + @RequestMapping + public void handle() { + } + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java index d87212008c9..02ee04662d1 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -83,6 +83,16 @@ public class ServletUriComponentsBuilderTests { assertEquals("http://localhost/mvc-showcase/data/param", result); } + @Test + public void fromRequestWithForwardedHostHeader() { + request.addHeader("X-Forwarded-Host", "anotherHost"); + request.setRequestURI("/mvc-showcase/data/param"); + request.setQueryString("foo=123"); + String result = ServletUriComponentsBuilder.fromRequest(request).build().toUriString(); + + assertEquals("http://anotherHost/mvc-showcase/data/param?foo=123", result); + } + @Test public void fromContextPath() { request.setRequestURI("/mvc-showcase/data/param"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java index 3bd879a1eac..3f4247033b5 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/tags/form/FormTagTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2013 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.web.servlet.support.RequestDataValueProcessor; * @author Juergen Hoeller * @author Scott Andrews * @author Jeremy Grelle + * @author Rossen Stoyanchev */ public class FormTagTests extends AbstractHtmlElementTagTests { @@ -166,6 +167,50 @@ public class FormTagTests extends AbstractHtmlElementTagTests { assertAttributeNotPresent(output, "name"); } + public void testPrependServletPath() throws Exception { + + this.request.setContextPath("/myApp"); + this.request.setServletPath("/main"); + this.request.setPathInfo("/index.html"); + + String commandName = "myCommand"; + String action = "/form.html"; + String enctype = "my/enctype"; + String method = "POST"; + String onsubmit = "onsubmit"; + String onreset = "onreset"; + + this.tag.setCommandName(commandName); + this.tag.setAction(action); + this.tag.setMethod(method); + this.tag.setEnctype(enctype); + this.tag.setOnsubmit(onsubmit); + this.tag.setOnreset(onreset); + + int result = this.tag.doStartTag(); + assertEquals(Tag.EVAL_BODY_INCLUDE, result); + assertEquals("Form attribute not exposed", commandName, + getPageContext().getAttribute(FormTag.MODEL_ATTRIBUTE_VARIABLE_NAME, PageContext.REQUEST_SCOPE)); + + result = this.tag.doEndTag(); + assertEquals(Tag.EVAL_PAGE, result); + + this.tag.doFinally(); + assertNull("Form attribute not cleared after tag ends", + getPageContext().getAttribute(FormTag.MODEL_ATTRIBUTE_VARIABLE_NAME, PageContext.REQUEST_SCOPE)); + + String output = getOutput(); + assertFormTagOpened(output); + assertFormTagClosed(output); + + assertContainsAttribute(output, "action", "/myApp/main/form.html"); + assertContainsAttribute(output, "method", method); + assertContainsAttribute(output, "enctype", enctype); + assertContainsAttribute(output, "onsubmit", onsubmit); + assertContainsAttribute(output, "onreset", onreset); + assertAttributeNotPresent(output, "name"); + } + public void testWithNullResolvedCommand() throws Exception { try { tag.setCommandName("${null}"); @@ -293,7 +338,7 @@ public class FormTagTests extends AbstractHtmlElementTagTests { String output = getOutput(); - assertEquals("", getInputTag(output)); + assertEquals("", getInputTag(output)); assertFormTagOpened(output); assertFormTagClosed(output); diff --git a/src/dist/changelog.txt b/src/dist/changelog.txt index b9726400ae4..25b90985b9b 100644 --- a/src/dist/changelog.txt +++ b/src/dist/changelog.txt @@ -6,11 +6,50 @@ http://www.springsource.org Changes in version 3.2.1 (2013-01-24) -------------------------------------- +* SpEL support for static finals on interfaces (SPR-10125) +* AnnotationAwareOrderComparator is able to sort Class objects as well (SPR-10152) +* added dedicated sort method to AnnotationAwareOrderComparator (SPR-9625) +* BridgeMethodResolver properly handles bridge methods in interfaces (SPR-9330) +* LocalVariableTableParameterNameDiscoverer works for bridge methods as well (SPR-9429) +* added constructor with Charset argument to EncodedResource (SPR-10096) +* ResourcePropertyResource accepts EncodedResource for properties files with a specific encoding (SPR-10096) +* SystemEnvironmentPropertySource properly works with an active JVM SecurityManager (SPR-9970) +* CachedIntrospectionResults.clearClassLoader(null) removes cached classes for the system class loader (SPR-9189) +* DisposableBeanAdapter detects "shutdown" as a destroy method as well (for EHCache CacheManager setup; SPR-9713) +* introduced NoUniqueBeanDefinitionException as a dedicated subclass of NoSuchBeanDefinitionException (SPR-10194) +* DefaultListableBeanFactory's getBean(Class) checks primary marker in case of multiple matches (SPR-7854) * fixed QualifierAnnotationAutowireCandidateResolver's detection of custom qualifier annotations (SPR-10107) * fixed AbstractAutoProxyCreator to accept null bean names again (SPR-10108) +* AbstractAdvisingBeanPostProcessor caches per bean target class, working for null bean names as well (SPR-10144) +* MessageSourceResourceBundle overrides JDK 1.6 containsKey method, avoiding NPE in getKeys (SPR-10136) +* SpringValidationAdapter properly detects invalid value for JSR-303 field-level bean constraints (SPR-9332) +* SpringBeanAutowiringInterceptor eagerly releases BeanFactory if post-construction fails (SPR-10013) +* added "exposeAccessContext" flag JndiRmiClientInterceptor/ProxyFactoryBean (for WebLogic; SPR-9428) +* MBeanExporter does not log warnings for manually unregistered MBeans (SPR-9451) +* MBeanInfoAssembler impls expose actual method parameter names if possible (SPR-9985) +* AbstractCacheManager accepts no caches defined, allowing for EHCache default cache setup (SPR-7955) +* EhCacheManagerFactoryBean applies cacheManagerName ahead of creation (for EHCache 2.5 compatibility; SPR-9171) +* ThreadPoolExecutorFactoryBean exposes "createExecutor" method for custom ThreadPoolExecutor subclasses (SPR-9435) +* added "awaitTerminationSeconds" property to ThreadPoolTaskExecutor/ThreadPoolTaskScheduler (SPR-5387) +* aligned XML scheduled-task elements with @Scheduled in terms of kicking in after context refresh (SPR-9231) +* reintroduced "mode" and "proxy-target-class" attributes in spring-task-3.1/3.2.xsd (SPR-10177) * spring-task-3.2.xsd allows for SpEL expressions in initial-delay attribute (SPR-10102) +* spring-jms-3.2.xsd allows for SpEL expressions in prefetch and receive-timeout attributes (SPR-9553) * JmsTemplate uses configured receiveTimeout if shorter than remaining transaction timeout (SPR-10109) * added MappingJackson2MessageConverter for JMS (SPR-10099) +* JDBC parameter binding uses JDBC 3.0 ParameterMetaData (if available) for type determination (SPR-10084) +* JpaTransactionManager etc finds default EntityManagerFactory in parent context as well (SPR-10160) +* MimeMessageHelper encodes attachment filename if not ASCII compliant (SPR-9258) +* FreeMarkerConfigurationFactory properly supports TemplateLoaders when recreating Configurations (SPR-9389) +* SpringContextResourceAdapter implements equals/hashCode according to the JCA 1.5 contract (SPR-9162) +* ContextLoader properly detects pre-refreshed WebApplicationContext (SPR-9996) +* added support for placeholders in @RequestMapping annotation value (SPR-9935) +* added support for specifying a message code as @ResponseStatus reason (SPR-6044) +* HttpEntityMethodProcessor supports HttpEntity/ResponseEntity subclasses as well (SPR-10207) +* Tiles 3 TilesConfigurer properly works in combination with "completeAutoload" (SPR-10195) +* Spring MVC Test framework supports HTTP OPTIONS method as well (SPR-10093) +* MockHttpServletRequest's getParameter(Values) returns null for null parameter name (SPR-10192) +* MockHttpServletResponse's getHeaderNames declares Collection instead of Set for Servlet 3.0 compatibility (SPR-9885) Changes in version 3.2 GA (2012-12-13) diff --git a/src/dist/readme.txt b/src/dist/readme.txt index e55fefae193..c58486a587d 100644 --- a/src/dist/readme.txt +++ b/src/dist/readme.txt @@ -1,12 +1,11 @@ Spring Framework version ${version} -=============================================================================== +================================================================================ -To find out what has changed since any earlier releases, see -'docs/changelog.txt'. +To find out what has changed since any earlier releases, see 'docs/changelog.txt'. -Please consult the documentation located within the 'docs/reference' directory -of this release and also visit the official Spring Framework home at -http://www.springsource.org/spring-framework +Please consult the documentation located within the +'docs/spring-framework-reference' directory of this release and also visit +the official Spring Framework home at http://www.springsource.org/spring-framework There you will find links to the forum, issue tracker, and other resources. diff --git a/src/eclipse/org.eclipse.jdt.core.prefs b/src/eclipse/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..80a095b1a1a --- /dev/null +++ b/src/eclipse/org.eclipse.jdt.core.prefs @@ -0,0 +1,388 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +org.eclipse.jdt.core.codeComplete.argumentPrefixes= +org.eclipse.jdt.core.codeComplete.argumentSuffixes= +org.eclipse.jdt.core.codeComplete.fieldPrefixes= +org.eclipse.jdt.core.codeComplete.fieldSuffixes= +org.eclipse.jdt.core.codeComplete.localPrefixes= +org.eclipse.jdt.core.codeComplete.localSuffixes= +org.eclipse.jdt.core.codeComplete.staticFieldPrefixes= +org.eclipse.jdt.core.codeComplete.staticFieldSuffixes= +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=error +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=ignore +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=ignore +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning +org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.formatter.align_type_members_on_columns=false +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=80 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16 +org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_assignment=0 +org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 +org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 +org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 +org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 +org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 +org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 +org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 +org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=0 +org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 +org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 +org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_after_package=1 +org.eclipse.jdt.core.formatter.blank_lines_before_field=1 +org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=1 +org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 +org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 +org.eclipse.jdt.core.formatter.blank_lines_before_method=1 +org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 +org.eclipse.jdt.core.formatter.blank_lines_before_package=1 +org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 +org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 +org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line +org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false +org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false +org.eclipse.jdt.core.formatter.comment.format_block_comments=true +org.eclipse.jdt.core.formatter.comment.format_header=false +org.eclipse.jdt.core.formatter.comment.format_html=true +org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true +org.eclipse.jdt.core.formatter.comment.format_line_comments=true +org.eclipse.jdt.core.formatter.comment.format_source_code=true +org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false +org.eclipse.jdt.core.formatter.comment.indent_root_tags=true +org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert +org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert +org.eclipse.jdt.core.formatter.comment.line_length=90 +org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true +org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true +org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false +org.eclipse.jdt.core.formatter.compact_else_if=true +org.eclipse.jdt.core.formatter.continuation_indentation=2 +org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 +org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off +org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on +org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false +org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true +org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true +org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_empty_lines=false +org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true +org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true +org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=true +org.eclipse.jdt.core.formatter.indentation.size=4 +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert +org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert +org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert +org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert +org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert +org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert +org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert +org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert +org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert +org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert +org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert +org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert +org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert +org.eclipse.jdt.core.formatter.join_lines_in_comments=true +org.eclipse.jdt.core.formatter.join_wrapped_lines=true +org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false +org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false +org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false +org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false +org.eclipse.jdt.core.formatter.lineSplit=90 +org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false +org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false +org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 +org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 +org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true +org.eclipse.jdt.core.formatter.tabulation.char=tab +org.eclipse.jdt.core.formatter.tabulation.size=4 +org.eclipse.jdt.core.formatter.use_on_off_tags=false +org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false +org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true +org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true +org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff --git a/src/eclipse/org.eclipse.jdt.ui.prefs b/src/eclipse/org.eclipse.jdt.ui.prefs new file mode 100644 index 00000000000..4aa12d7db50 --- /dev/null +++ b/src/eclipse/org.eclipse.jdt.ui.prefs @@ -0,0 +1,62 @@ +cleanup.add_default_serial_version_id=true +cleanup.add_generated_serial_version_id=false +cleanup.add_missing_annotations=false +cleanup.add_missing_deprecated_annotations=true +cleanup.add_missing_methods=false +cleanup.add_missing_nls_tags=false +cleanup.add_missing_override_annotations=true +cleanup.add_missing_override_annotations_interface_methods=true +cleanup.add_serial_version_id=false +cleanup.always_use_blocks=true +cleanup.always_use_parentheses_in_expressions=false +cleanup.always_use_this_for_non_static_field_access=true +cleanup.always_use_this_for_non_static_method_access=false +cleanup.convert_to_enhanced_for_loop=false +cleanup.correct_indentation=false +cleanup.format_source_code=false +cleanup.format_source_code_changes_only=false +cleanup.make_local_variable_final=false +cleanup.make_parameters_final=false +cleanup.make_private_fields_final=true +cleanup.make_type_abstract_if_missing_method=false +cleanup.make_variable_declarations_final=true +cleanup.never_use_blocks=false +cleanup.never_use_parentheses_in_expressions=true +cleanup.organize_imports=false +cleanup.qualify_static_field_accesses_with_declaring_class=false +cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true +cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true +cleanup.qualify_static_member_accesses_with_declaring_class=true +cleanup.qualify_static_method_accesses_with_declaring_class=false +cleanup.remove_private_constructors=true +cleanup.remove_trailing_whitespaces=true +cleanup.remove_trailing_whitespaces_all=true +cleanup.remove_trailing_whitespaces_ignore_empty=false +cleanup.remove_unnecessary_casts=true +cleanup.remove_unnecessary_nls_tags=true +cleanup.remove_unused_imports=true +cleanup.remove_unused_local_variables=false +cleanup.remove_unused_private_fields=true +cleanup.remove_unused_private_members=false +cleanup.remove_unused_private_methods=true +cleanup.remove_unused_private_types=true +cleanup.sort_members=false +cleanup.sort_members_all=false +cleanup.use_blocks=true +cleanup.use_blocks_only_for_return_and_throw=false +cleanup.use_parentheses_in_expressions=false +cleanup.use_this_for_non_static_field_access=true +cleanup.use_this_for_non_static_field_access_only_if_necessary=false +cleanup.use_this_for_non_static_method_access=false +cleanup.use_this_for_non_static_method_access_only_if_necessary=true +cleanup_profile=_Spring +cleanup_settings_version=2 +eclipse.preferences.version=1 +formatter_profile=_Spring +formatter_settings_version=12 +org.eclipse.jdt.ui.ignorelowercasenames=true +org.eclipse.jdt.ui.importorder=java;javax;org;com; +org.eclipse.jdt.ui.javadoc=true +org.eclipse.jdt.ui.ondemandthreshold=9999 +org.eclipse.jdt.ui.staticondemandthreshold=9999 +org.eclipse.jdt.ui.text.custom_code_templates= diff --git a/src/eclipse/org.eclipse.wst.common.component b/src/eclipse/org.eclipse.wst.common.component new file mode 100644 index 00000000000..ce05d97c85e --- /dev/null +++ b/src/eclipse/org.eclipse.wst.common.component @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/eclipse/org.eclipse.wst.common.project.facet.core.xml b/src/eclipse/org.eclipse.wst.common.project.facet.core.xml new file mode 100644 index 00000000000..e4a5a385a27 --- /dev/null +++ b/src/eclipse/org.eclipse.wst.common.project.facet.core.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/reference/docbook/cache.xml b/src/reference/docbook/cache.xml index b96eaa3c917..08d2f34c0e1 100644 --- a/src/reference/docbook/cache.xml +++ b/src/reference/docbook/cache.xml @@ -570,10 +570,10 @@ public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)]]>< - + ]]> - The CompositeCacheManager above chains multiple CacheManagers and additionally, through the addNoOpManager flag, adds a + The CompositeCacheManager above chains multiple CacheManagers and additionally, through the fallbackToNoOpCache flag, adds a no op cache that for all the definitions not handled by the configured cache managers. That is, every cache definition not found in either jdkCache or gemfireCache (configured above) will be handled by the no op cache, which will not store any information causing the target method to be executed every time. diff --git a/src/reference/docbook/cci.xml b/src/reference/docbook/cci.xml index e5fc3bb82f9..051d587da5b 100644 --- a/src/reference/docbook/cci.xml +++ b/src/reference/docbook/cci.xml @@ -169,9 +169,9 @@ allows for specifying a ConnectionSpec instance to use for all operations on a given factory. If the adapter's connectionSpec property is specified, the adapter - uses the getConnection variant without argument, else - the one with the ConnectionSpec - argument. + uses the getConnection variant with the + ConnectionSpec argument, otherwise + the variant without argument. <bean id="managedConnectionFactory" class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory"> diff --git a/src/reference/docbook/index.xml b/src/reference/docbook/index.xml index 393858cecda..de4af30072f 100644 --- a/src/reference/docbook/index.xml +++ b/src/reference/docbook/index.xml @@ -471,6 +471,9 @@ + + diff --git a/src/reference/docbook/jmx.xml b/src/reference/docbook/jmx.xml index 85ae5d0d271..05e8588ebfa 100644 --- a/src/reference/docbook/jmx.xml +++ b/src/reference/docbook/jmx.xml @@ -1061,7 +1061,7 @@ public class AnnotationTestBean implements IJmxTestBean { + class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/> ]]> diff --git a/src/reference/docbook/migration-3.1.xml b/src/reference/docbook/migration-3.1.xml new file mode 100644 index 00000000000..15ca8d49798 --- /dev/null +++ b/src/reference/docbook/migration-3.1.xml @@ -0,0 +1,37 @@ + + + Migrating to Spring Framework 3.1 + + In this appendix we discuss what users will want to know when upgrading to + Spring Framework 3.1. For a general overview of features, please see + + +

    + Component scanning against the "org" base package + Spring Framework 3.1 introduces a number of @Configuration + classes such as org.springframework.cache.annotation.ProxyCachingConfiguration + and + org.springframework.scheduling.annotation.ProxyAsyncConfiguration. + Because @Configuration is ultimately meta-annotated with Spring's + @Component annotation, these classes will inadvertently be scanned + and processed by the container for any component-scanning directive against the + unqualified "org" package, e.g.: + + <context:component-scan base-package="org"/> + Therefore, in order to avoid errors like the one reported in SPR-9843, + any such directives should be updated to at least one more level of qualification e.g.: + + <context:component-scan base-package="org.xyz"/> + Alternatively, an exclude-filter may be used. See + context:component-scan + documentation for details. +
    + diff --git a/src/reference/docbook/mvc.xml b/src/reference/docbook/mvc.xml index ffe081c1e09..c8aebb4f8a4 100644 --- a/src/reference/docbook/mvc.xml +++ b/src/reference/docbook/mvc.xml @@ -867,7 +867,10 @@ public class ClinicController { @Transactional methods). Usually you will introduce an interface for the controller in order to use JDK dynamic proxies. To make this work you must move the - @RequestMapping annotations to the + @RequestMapping annotations, as well as + any other type and method-level annotations (e.g. + @ModelAttribute, + @InitBinder) to the interface as well as the mapping mechanism can only "see" the interface exposed by the proxy. Alternatively, you could activate proxy-target-class="true" in the configuration for the @@ -876,6 +879,10 @@ public class ClinicController { that CGLIB-based subclass proxies should be used instead of interface-based JDK proxies. For more information on various proxying mechanisms see . + + Note however that method argument annotations, e.g. + @RequestParam, must be present in + the method signatures of the controller class.
    @@ -1073,6 +1080,18 @@ public class RelativePathUriTemplateController { /owners/*/pets/{petId}).
    +
    + Patterns with Placeholders + + Patterns in @RequestMapping annotations + support ${...} placeholders against local properties and/or system properties + and environment variables. This may be useful in cases where the path a + controller is mapped to may need to be customized through configuration. + For more information on placeholders see the Javadoc for + PropertyPlaceholderConfigurer. + +
    +
    Matrix Variables @@ -5091,7 +5110,7 @@ public class WebConfig extends WebMvcConfigurerAdapter { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { - configurer.setFavorPathExtension(false).setFavorParameter(true); + configurer.favorPathExtension(false).favorParameter(true); } } diff --git a/src/reference/docbook/new-in-3.1.xml b/src/reference/docbook/new-in-3.1.xml index ef69b647d06..8a56bfd6472 100644 --- a/src/reference/docbook/new-in-3.1.xml +++ b/src/reference/docbook/new-in-3.1.xml @@ -11,7 +11,8 @@ This is a list of new features for Spring Framework 3.1. A number of features do not have dedicated reference documentation but do have complete Javadoc. In such - cases, fully-qualified class names are given. + cases, fully-qualified class names are given. See also +
    Cache Abstraction diff --git a/src/reference/docbook/overview.xml b/src/reference/docbook/overview.xml index a4de366ef8c..9bef8ee488e 100644 --- a/src/reference/docbook/overview.xml +++ b/src/reference/docbook/overview.xml @@ -362,7 +362,7 @@ TR: OK. Added to diagram.--> places: On the community download site http://www.springsource.org/downloads/community. + xl:href="http://www.springsource.org/download/community">http://www.springsource.org/download/community. Here you find all the Spring jars bundled together into a zip file for easy download. The names of the jars here since version 3.0 are in the form diff --git a/src/reference/docbook/oxm.xml b/src/reference/docbook/oxm.xml index f5797b2c4b8..00ae3966209 100644 --- a/src/reference/docbook/oxm.xml +++ b/src/reference/docbook/oxm.xml @@ -438,7 +438,7 @@ public class Application { though a mapping file can be used to have more control over the behavior of Castor. - For more information on Castor, refer to the + For more information on Castor, refer to the Castor web site. The Spring integration classes reside in the org.springframework.oxm.castor package. @@ -462,7 +462,7 @@ public class Application { Although it is possible to rely on Castor's default marshalling behavior, it might be necessary to have more control over it. This can be accomplished using a Castor mapping file. For more information, refer - to Castor XML Mapping. + to Castor XML Mapping. The mapping can be set using the mappingLocation resource property, indicated diff --git a/src/reference/docbook/remoting.xml b/src/reference/docbook/remoting.xml index 4bd5acc920d..f642f9d653b 100644 --- a/src/reference/docbook/remoting.xml +++ b/src/reference/docbook/remoting.xml @@ -1401,6 +1401,12 @@ String result = can in turn be configured with credentials information or connection pooling functionality. + Note that the java.net implementation for + HTTP requests may raise an exception when accessing the status of a response + that represents an error (e.g. 401). If this is an issue, switch to + HttpComponentsClientHttpRequestFactory instead. + + The previous example using Apache HttpComponents HttpClient directly rewritten to use the RestTemplate is shown below diff --git a/src/reference/docbook/validation.xml b/src/reference/docbook/validation.xml index 5ad3faeee8a..2eb98a76b02 100644 --- a/src/reference/docbook/validation.xml +++ b/src/reference/docbook/validation.xml @@ -12,6 +12,24 @@
    Introduction + + JSR-303 Bean Validation + + The Spring Framework supports JSR-303 Bean Validation adapting + it to Spring's Validator interface. + + An application can choose to enable JSR-303 Bean Validation once globally, + as described in , and use it + exclusively for all validation needs. + + An application can also register + additional Spring Validator instances + per DataBinder instance, as described in + . This may be useful for + plugging in validation logic without the use of annotations. + + + There are pros and cons for considering validation as business logic, and Spring offers a design for validation (and data binding) that does not exclude either one of them. Specifically validation should not be tied to @@ -1778,6 +1796,16 @@ binder.validate(); // get BindingResult that includes any validation errors BindingResult results = binder.getBindingResult(); + + A DataBinder can also be configured with multiple + Validator instances + via dataBinder.addValidators + and dataBinder.replaceValidators. + This is useful when combining globally configured JSR-303 Bean Validation + with a Spring Validator configured + locally on a DataBinder instance. + See . +
    @@ -1847,6 +1875,20 @@ public class MyController { ]]> + + To combine a global and a local validator, configure the + global validator as shown above and then add a local validator: + + +
    diff --git a/src/reference/docbook/xsd-configuration.xml b/src/reference/docbook/xsd-configuration.xml index f57e766e067..a78556a27c8 100644 --- a/src/reference/docbook/xsd-configuration.xml +++ b/src/reference/docbook/xsd-configuration.xml @@ -679,7 +679,7 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schem covered in the chapter entitled . You are strongly encouraged to look at the - 'spring-tx-3.0.xsd' file that ships with the Spring + 'spring-tx.xsd' file that ships with the Spring distribution. This file is (of course), the XML Schema for Spring's transaction configuration, and covers all of the various tags in the tx namespace, including attribute defaults and @@ -812,7 +812,7 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schem Spring as they are currently undergoing review. If you are a third party tool vendor and you would like to contribute to this review process, then do mail the Spring mailing list. The currently supported tool - tags can be found in the file 'spring-tool-3.0.xsd' in the + tags can be found in the file 'spring-tool.xsd' in the 'src/org/springframework/beans/factory/xml' directory of the Spring source distribution.
    @@ -931,8 +931,8 @@ http://www.springframework.org/schema/beans http://www.springframework.org/schem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" -http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd -http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> +http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd +http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> ]]> @@ -1053,8 +1053,8 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" - http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd - http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"> + http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd + http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> ]]> @@ -1138,7 +1138,7 @@ http://www.springframework.org/schema/util http://www.springframework.org/schema relevant XSD file. As can be seen in the following screenshot, the 'http://www.springframework.org/schema/util' namespace is being associated with the file resource - 'C:\bench\spring\src\org\springframework\beans\factory\xml\spring-util-3.0.xsd'. + 'C:\bench\spring\src\org\springframework\beans\factory\xml\spring-util.xsd'.