Compare commits

...

71 Commits

Author SHA1 Message Date
Spring Buildmaster 2239ddf6f4 Release version 4.1.5.RELEASE 2015-02-20 03:09:01 -08:00
Juergen Hoeller d77af71e9c Revised common validation methods in AbstractMessageConverterMethodArgumentResolver
The protected validation methods are analogous to ModelAttributeMethodProcessor now.

Issue: SPR-12655
(cherry picked from commit 7191050)
2015-02-19 23:53:16 +01:00
Sebastien Deleuze 5db4e4bab0 Adjust log level for invalid SockJS or Websocket requests 2015-02-19 16:06:06 +01:00
Sebastien Deleuze 7cc56e1630 Improve error handling in WebUtils.isValidOrigin()
With this commit, WebUtils.isValidOrigin() logs an error message instead
of throwing an IllegalArgumentException when Origin header value is
invalid (for example when it does not contain the scheme).

Issue: SPR-12697
2015-02-19 14:12:10 +01:00
Juergen Hoeller b5e80390de Correct reference to executeAndReturnKey method
Issue: SPR-12639
(cherry picked from commit f20a624)
2015-02-18 22:30:20 +01:00
Juergen Hoeller 55a14eb684 MessageHeaderAccessor defensively checks id, timestamp and contentType values
Issue: SPR-12730
(cherry picked from commit dbd353b)
2015-02-18 22:30:04 +01:00
Sam Brannen d8269dd9fe Polish reference manual regarding WebSocket
- fixed typos
 - improved grammar and punctuation
 - fixed class names

(cherry picked from commit c7a456c0bf)
2015-02-18 20:33:34 +01:00
Juergen Hoeller aae7583141 Latest dependency updates (Groovy 2.3.10, SLF4J 1.7.10) 2015-02-18 18:54:38 +01:00
Rossen Stoyanchev 6fafe63311 Ignore Pong messages in StompSubProtocolHandler
Issue: SPR-12728
2015-02-18 12:27:13 -05:00
Rossen Stoyanchev f1e406c63b Fix handling of empty payload Pong message on Jetty
Issue: SPR-12727
2015-02-18 12:27:13 -05:00
Juergen Hoeller c8a4d1649f Latest dependency updates (AspectJ 1.8.5, Joda-Time 2.7, H2 1.4.185) 2015-02-18 17:54:43 +01:00
Juergen Hoeller 8dbe753963 Polishing 2015-02-18 17:54:16 +01:00
Sebastien Deleuze 23fa37b08b Change SockJS and Websocket default allowedOrigins to same origin
This commit adds support for a same origin check that compares
Origin header to Host header. It also changes the default setting
from all origins allowed to only same origin allowed.

Issues: SPR-12697, SPR-12685
(cherry picked from commit 6062e15)
2015-02-18 17:26:55 +01:00
Sebastien Deleuze cc78d40c6b Fix SockJS origin check
This commit introduces the following changes:
 - Requests without Origin header are not rejected anymore
 - Disable Iframe when allowedOrigins is not empty and not equals to *
 - The Iframe is not cached anymore in order to have a reliable origin check
 - allowedOrigins must not be null or empty
 - allowedOrigins format is now validated (should be * or start by http(s)://)

Issue: SPR-12660
(cherry picked from commit 9b3319b)
2015-02-18 17:26:44 +01:00
Sebastien Deleuze 1dc39324b9 Add parametrized constructors to MappingJackson2MessageConverter
Issue: SPR-12724
(cherry picked from commit 8159aa9)
2015-02-18 17:24:28 +01:00
Juergen Hoeller 2a240b0dc8 ServletUriComponentsBuilder.java avoids NPE on scheme check
Issue: SPR-12723
(cherry picked from commit 61cc3b5)
2015-02-18 17:14:43 +01:00
Juergen Hoeller 3b8d878285 ScheduledAnnotationBeanPostProcessor uses target class as cache key in case of proxy
Issue: SPR-12709
(cherry picked from commit 1273c90)
2015-02-18 17:14:33 +01:00
Sam Brannen d98f626be1 Make TestPropertySourceUtils more robust
- Added assertions for pre-conditions on method arguments for all
   public utility methods.

 - Introduced additional tests in TestPropertySourceUtilsTests to verify
   the new pre-conditions.

 - Introduced INLINED_PROPERTIES_PROPERTY_SOURCE_NAME constant for the
   name of the MapPropertySource created from inlined properties; the
   name therefore no longer contains the inlined properties, but the
   original values of the inlined properties can now be logged at debug
   level.

 - Simplified tests in InlinedPropertiesTestPropertySourceTests.

Issue: SPR-12721
(cherry picked from commit 42af33034d)
2015-02-17 19:52:51 +01:00
Sam Brannen b8dbf2345c Polish Javadoc for @TestPropertySource
(cherry picked from commit 0267715c9d)
2015-02-17 19:52:13 +01:00
Sam Brannen 2b135f0d51 Open up TestPropertySourceUtils for public consumption
Spring Framework 4.1 introduced support for @TestPropertySource;
however, the utilities used to parse inlined properties and add test
property sources to the environment are currently private which
prevents reuse by third-party frameworks like Spring Boot.

This commit addresses this issue by making such utilities public.

 - TestPropertySourceUtils is now a public class.

 - Various utility methods in TestPropertySourceUtils have been made
   public.

 - addResourcePropertySourcesToEnvironment() has been renamed to
   addPropertiesFilesToEnvironment().

 - extractEnvironmentProperties() has been renamed to
   convertInlinedPropertiesToMap().

 - All public methods in TestPropertySourceUtils are now fully
   documented.

Issue: SPR-12721
(cherry picked from commit 75e0bc9271)
2015-02-17 03:14:47 +01:00
Sam Brannen e5d41d91d5 Preserve ordering of inlined props in @TestPropertySource
The initial implementation for adding inlined properties configured via
@TestPropertySource to the context's environment did not preserve the
order in which the properties were physically declared. This makes
@TestPropertySource a poor testing facility for mimicking the
production environment's configuration if the property source mechanism
used in production preserves ordering of property names -- which is the
case for YAML-based property sources used in Spring Boot, Spring Yarn,
etc.

This commit addresses this issue by ensuring that the ordering of
inlined properties declared via @TestPropertySource is preserved.
Specifically, the original functionality has been refactored. extracted
from AbstractContextLoader, and moved to TestPropertySourceUtils where
it may later be made public for general purpose use in other frameworks.

Issue: SPR-12710
(cherry picked from commit d6a799ad4a)
2015-02-16 20:33:15 +01:00
Rossen Stoyanchev f398dd03a7 Pass SockJS session attributes to HandshakeHandler
Before this change the WebSocketTransportHandler passed
Collections.emptyMap as attributes to the HandshakeHandler because
it didn't matter what attributes the underlying WebSocketSession has
since it is wrapped by the SockJsSession and that's what exposed for
use everywhere.

This change has the WebSocketTransportHandler passing the attributes
from the SockJsSession instead since it's more accurate for the
underlying WebSocketSession to have access to the same map instance
and it allows the HandshakeHandler to change the attributes even if
it doesn't need to do that today.

Issue: SPR-12716
2015-02-16 14:32:03 -05:00
Sam Brannen f82c6635d7 Add further regression tests for @TestPropertySource
This commit introduces further regression tests to ensure proper parsing
of inlined properties configured via @TestPropertySource. Specifically,
these additional tests ensure that we do not introduce a bug like the
one raised in Spring Boot issue #1110 [0].

[0] https://github.com/spring-projects/spring-boot/issues/1110

Issue: SPR-12710
(cherry picked from commit 67934a22e2)
2015-02-12 20:26:48 +01:00
Andy Wilkinson 9499e4166c Fix Animal Sniffer
Jasper Reports’ transitive dependency on spring-context (via
castor-xml which is a new dependency in 6.0.3) was being mapped by
Gradle to a dependency on the spring-context project. For reasons that
I do not fully understand this was causing -source and -javadoc jars
to be added to the project's compile classpath which is used by the
Animal Sniffer Ant task. When the task runs these jars do not exist
which causes it to fail. This commit fixes the problem by adding an
exclusion of org.springframework:spring-context to the Jasper Reports
dependencies in spring-context-support and spring-webmvc.
(cherry picked from commit 7a6a132)
2015-02-11 20:17:54 +01:00
Juergen Hoeller bf442c093c Latest dependency updates (POI 3.11, FreeMarker 2.3.21) 2015-02-11 19:04:57 +01:00
Juergen Hoeller fab8dd3d0b Combined backport of Jackson configuration enhancements: Locale/TimeZone settings, refined module configuration
Issue: SPR-12594
Issue: SPR-12634
2015-02-11 19:03:54 +01:00
Stephane Nicoll 1646f62700 BeanWrapper auto-grows arrays if necessary
Previously, only indexed access for collections were supported. When
attempting to access the element of an array that had not the requested
size, the call would fail with an IndexOutOfBoundException

This commit harmonize the binding support so that the array is updated
according to the requested index if necessary.

Issue: SPR-12706
(cherry picked from commit aa21339)
2015-02-11 19:00:39 +01:00
Juergen Hoeller c8d9bfadad Latest dependency updates (JasperReports 6.0.3, Tomcat 8.0.18, Undertow 1.1.2) 2015-02-10 22:58:33 +01:00
Juergen Hoeller 0706889222 Polishing
(cherry picked from commit 058714b)
2015-02-10 22:58:16 +01:00
Juergen Hoeller 25644dbdb9 Revised validation javadoc, plus protected validate/isBindingErrorFatal template methods
Issue: SPR-12655
(cherry picked from commit 7585be8)
2015-02-10 22:00:11 +01:00
Juergen Hoeller 181b35243e Properly honor conditions on overriding bean methods
Issue: SPR-12694
(cherry picked from commit 981aefc)
2015-02-10 21:59:31 +01:00
Juergen Hoeller 6c47b5f5d3 Avoid ConcurrentModificationException in getBeansWithAnnotation
Issue: SPR-12688
(cherry picked from commit 918bc3b)
2015-02-10 21:59:09 +01:00
Rossen Stoyanchev 4a423e50a8 Add fromHttpRequest to UriComponentsBuilder
Before this change, detection of X-Forwarded-* headers was only built
into ServletUriComponentsBuilder.

This change adds a new method for creating a UriComponentsBuilder from
an existing HttpRequest. This is equivalent to the fromUri method +
X-Forwarded-* header values.
2015-02-10 06:54:10 +01:00
Brian Clozel 0225a7776c Revisit empty body response support in HTTP client
Prior to this commit, HTTP responses without body (response status 204
or 304, Content-Length: 0) were handled properly by RestTemplates. But
some other cases were not properly managed, throwing exceptions for
valid HTTP responses.

This commit better handles HTTP responses, using a response wrapper that
can tell if a response:

* has no message body (HTTP status 1XX, 204, 304 or Content-Length:0)
* has an empty message body

This covers rfc7230 Section 3.3.3.

Issue: SPR-8016
2015-02-09 15:10:39 +01:00
Brian Clozel d57f7f8783 Sort handler matches in ResourceUrlProvider
Prior to this change, the `ResourceUrlProvider.getForLookupPath` method
would try to match handlers using the keySet order in the
handlerMappings Map. In case of several matches, the handler used for
the return value could vary, since the registration order in the
handlerMappings can't be guaranteed in the configuration.

This commit now collects all matching handlers and sort them using a
`PatternComparator`, in order to try each handler from the most specific
mapping to the least.

Issue: SPR-12647
2015-02-09 15:08:54 +01:00
Brian Clozel 4550b4a254 Avoid loss of body content in AbstractRequestLoggingFilter
Prior to this commit, the `ContentCachingRequestWrapper` class would
cache the response content only if the reponse would be consumed using
its InputStream. In case of a Form request, Spring MVC consumes the
response using the `getParameter*` Servlet API methods. This causes the
cached content to never be written.

This commit makes the `ContentCachingResponseWrapper` write the request
body to the cache buffer by using the `getParameter*` API, thus avoiding
those issues.

Issue: SPR-7913
2015-02-09 15:08:35 +01:00
Brian Clozel f902fb911c Initialize ResourceUrlProvider only once
Prior to this change, the ResourceUrlProvider would listen to
ContextRefreshedEvents and autodetect resource handlers each time. This
can cause issues when multiple contexts are involved and the last one
has no resource handler, thus clearing the previously detected ones.

This commit disables resource handlers auto-detection once some have
been detected with a refreshed context.

Issue: SPR-12592
2015-02-09 15:08:15 +01:00
Brian Clozel 77c8aa53ae Allow relative paths within resource location path
Prior to this change, location paths used for resource handling would
not allow "non-cleaned, relative paths" such as
`file://home/user/static/../static/`. When checking if the resolved
resource's path starts with the location path, a mismatch would happen
when comparing for example:

* the location `file://home/user/static/../static/`
* and the resource `file://home/user/static/resource.txt`

This commit cleans the location path before comparing it to the resource
path.

Issue: SPR-12624
2015-02-09 15:07:52 +01:00
Brian Clozel bb5da15e1c Allow protocol relative URLs in CssLink Transformer
This commit allows the use of "protcol relative URLs" (i.e. URLs without
scheme, starting with `//`), often used to serve resources automatically
from https or http with third party domains.

This syntax is allowed by RFC 3986.

Issue: SPR-12632
2015-02-09 15:07:34 +01:00
Brian Clozel 0c8d07fcff Fix context-relative default value in XML parsing
This commit fixes the default value for the contextRelative attribute of
a RedirectView, when this view is registered via a
RedirectViewController in XML. The value is set to true.

Note that the default value for this is correctly documented in
spring-mvc-4.1.xsd. Also, the documentation and implementation for its
javadoc counterpart also enforces true as a default value.

Issue: SPR-12607
2015-02-09 15:07:06 +01:00
Stephane Nicoll 47fdac9214 Clarify the use of @Cacheable in PostConstruct code
Update documentation to explicitly mention that the cache interceptor
must be fully initialized to provide the expected behavior and therefore
initialization code should not rely on this feature, i;e. typically in
PostConstruct callback.

Since the Transactional infrastructure has the exact same infrastructure,
update that section of the doc as well.

Issue: SPR-12700
2015-02-09 10:28:14 +01:00
Stephane Nicoll 7ffa845aa8 Fix broken build
Add required  `@Deprecated` annotation.
2015-02-09 10:19:41 +01:00
Stephane Nicoll 09bd7037e9 Allow MBeans to be excluded additively
Previously, one could only set the list of bean names to exclude from
auto-detection and there was no way to add additional bean names.

MBeanExporter now exposes a addExcludedBean method that can be invoked
during the initialization phase to add bean names to ignore.

Issue: SPR-12686
2015-02-09 09:42:00 +01:00
Rossen Stoyanchev 49c21a0a06 Update javadoc for MultipartFile.transferTo
Issue: SPR-12650
2015-02-02 17:30:34 -05:00
Rossen Stoyanchev d9d8a79c30 Update Netty4ClientHttpRequestFactory buffer size
This change deprecates the maxRequestSize property and adds
maxResponseSize instead. The latter is required to create Netty's
HttpObjectAggregator and aggregates responses.

The maxRequestSize property is already removed in the master branch
and will not be available starting with 4.2.

Issue: SPR-12623
2015-02-02 16:15:42 -05:00
Rossen Stoyanchev 04840166af DefaultSubscriptionRegistry returns safe to iterate Map
Prior to this change when adding subscriptions
DefaultSubscriptionRegistry (incorrectly) made a copy of the given map
for its "access" cache rather than for its "update" cache.

Issue: SPR-12665
2015-02-02 13:57:46 -05:00
Stephane Nicoll 6fce6d4668 Allow subclasses to configure the Yaml instance
Provide an additional hook-point for YamlProcessor subclasses willing to
change how the Yaml instance is configured. Also expose the default
StrictMapAppenderConstructor so that  they can compose a custom instance
with it.

Issue: SPR-12671
2015-02-02 11:31:19 +01:00
Sam Brannen 0c856b3d22 Support @Configuration as meta-annotation in the TCF
Spring Framework 4.0 introduced support for using test-related
annotations as meta-annotations in the Spring TestContext Framework
(TCF) in order to create custom composed annotations within a test
suite; however, the detection of default @Configuration classes in test
classes was not updated to search for @Configuration declared as a
meta-annotation. Specifically, AnnotationConfigContextLoaderUtils
invokes Class.isAnnotated() which only searches for annotations
declared directly on the class in question.

This commit addresses this issue by refactoring the
isDefaultConfigurationClassCandidate() method in
AnnotationConfigContextLoaderUtils so that it uses
AnnotationUtils.findAnnotation() instead of Class.isAnnotated() for
detecting the presence of the @Configuration annotation, either
directly or as a meta-annotation.

Issue: SPR-12659
(cherry picked from commit 2d918380f0)
2015-01-23 22:18:56 +01:00
Rossen Stoyanchev d63cfc8eeb Add JdkIdGenerator and use it in SockJS client
Issue: SPR-12658
2015-01-22 21:29:18 -05:00
Rossen Stoyanchev 7621cc787d Remove ISE in ResourceUrlProvider
Issue: SPR-12630
2015-01-22 17:09:42 -05:00
Juergen Hoeller 2c1682af34 Latest applicable dependency updates (Jackson 2.4.5, Jetty 9.2.7) 2015-01-22 19:45:02 +01:00
Juergen Hoeller dcaa0987ee Polishing
(cherry picked from commit 1cd4433)
2015-01-22 19:44:38 +01:00
Juergen Hoeller 14a3bf3941 ScheduledAnnotationBeanPostProcessor registers tasks in ContextRefreshedEvent phase (again)
Issue: SPR-12641
(cherry picked from commit 0479ca6)
2015-01-22 19:42:44 +01:00
mgooty 23b09015fe Fix method documentation typo
Issue: SPR-12639
2015-01-22 19:23:51 +01:00
Juergen Hoeller b82e9c4d46 Allow schedulerWithHsqlDataSource to pass through reducing it to the trigger table check
Issue: SPR-12618
(cherry picked from commit 49e31c3)
2015-01-21 12:40:03 +01:00
Juergen Hoeller c158874d82 Latest dependency updates (Jackson 2.5, Hibernate 4.3.8, Netty 4.0.25)
(cherry picked from commit 4141bf3)
2015-01-21 12:39:45 +01:00
Juergen Hoeller 87b7cd6368 OperatorMatches caches compiled patterns
Issue: SPR-12610
(cherry picked from commit d34402d)
2015-01-21 12:34:24 +01:00
Juergen Hoeller 901d7d07db AbstractBeanDefinitionParser allows for skipping evaluation of XML "name" attribute
Issue: SPR-12643
(cherry picked from commit 11bf3b3)
2015-01-21 12:34:14 +01:00
Juergen Hoeller bfba988a0d AbstractRequestLoggingFilter allows for dedicated shouldLog check in CommonsRequestLoggingFilter
Issue: SPR-12609
(cherry picked from commit d4dac25)
2015-01-21 12:34:05 +01:00
Juergen Hoeller a7975c685d AnnotationUtils explicitly handles null parameters
Issue: SPR-12604
(cherry picked from commit 0ddf8dd)
2015-01-21 12:33:56 +01:00
Rossen Stoyanchev cdaf4497b6 Remove logging statement in ResponseBodyAdviceChain
Issue: SPR-12616
2015-01-20 17:23:47 -05:00
Rossen Stoyanchev 84807c5d61 Fix minor test issues
Issue: SPR-12615
2015-01-20 17:16:55 -05:00
Sam Brannen a76a6509e5 Enable reuse of DefaultActiveProfilesResolver
In order to allow DefaultActiveProfilesResolver to be reused (e.g., via
extension or delegation), the check which asserts that the 'resolver'
attribute of @ActiveProfiles is not set to a customer resolver class
has been removed.

Issue: SPR-12611
(cherry picked from commit 276712dcd1)
2015-01-10 20:37:55 +01:00
Sam Brannen 7d171e6722 Handle exceptions properly in SpringJUnit4ClassRunner
JUnit 4.9 introduced a regression in BlockJUnit4ClassRunner.runChild()
such that exceptions thrown from methodBlock() cause the current test
execution to abort immediately. As a result, the failing test method is
unrooted, and subsequent test methods are never invoked. Furthermore,
RunListeners registered with JUnit are not properly notified.

In conjunction with SPR-11908, SpringJUnit4ClassRunner was updated to
use the aforementioned changes to BlockJUnit4ClassRunner.runChild().
Consequently, SpringJUnit4ClassRunner now suffers from the same
regression.

This commit addresses this issue by ensuring that any exceptions thrown
during the invocation of methodBlock() are properly wrapped in a JUnit
Fail Statement.

Issue: SPR-12613
(cherry picked from commit b81c522ee1)
2015-01-10 17:58:18 +01:00
Juergen Hoeller 36da551280 Revised ExtendedBeanInfo test for SPR-8937 (for JDK 8u40 compatibility)
Issue: SPR-12582
(cherry picked from commit 7492129)
2015-01-02 15:54:18 +01:00
Juergen Hoeller ed665a12a3 ObjectToOptionalConverter uses Optional.ofNullable after ConversionService invocation
Issue: SPR-12589
(cherry picked from commit ec84fa6)
2015-01-02 15:53:55 +01:00
Stephane Nicoll adec0265a4 Restore default transaction manager by name lookup
Fix a regression introduced by 961574bd17 that prevents a proper lookup
of the default transaction manager by name as the absence of a qualifier
is represented by an empty string (passing the faulty null check).

Issue: SPR-12577
2014-12-31 16:01:56 +01:00
Juergen Hoeller 8c700b19da Polishing 2014-12-30 21:04:28 +01:00
Juergen Hoeller 9542313710 Polishing 2014-12-30 15:29:48 +01:00
Juergen Hoeller dcb821edb5 Polishing
(cherry picked from commit 730bd02)
2014-12-30 15:07:19 +01:00
Stephane Nicoll daa4adf2ed Next Development Version 2014-12-30 14:29:42 +01:00
128 changed files with 3405 additions and 1473 deletions
+18 -16
View File
@@ -27,34 +27,34 @@ configure(allprojects) { project ->
group = "org.springframework"
version = qualifyVersionIfNecessary(version)
ext.aspectjVersion = "1.8.4"
ext.aspectjVersion = "1.8.5"
ext.eclipseLinkVersion = "2.4.2"
ext.ehcacheVersion = "2.9.0"
ext.ehcacheJCacheVersion = "1.0.0"
ext.groovyVersion = "2.3.9"
ext.groovyVersion = "2.3.10"
ext.gsonVersion = "2.3.1"
ext.hibernate3Version = "3.6.10.Final"
ext.hibernate4Version = "4.3.7.Final"
ext.hibernate4Version = "4.3.8.Final"
ext.hibVal4Version = "4.3.2.Final"
ext.hibVal5Version = "5.1.3.Final"
ext.hsqldbVersion = "2.3.2"
ext.jackson2Version = "2.4.4"
ext.jasperReportsVersion = "6.0.0"
ext.jettyVersion = "9.2.6.v20141205"
ext.jodaVersion = "2.6"
ext.jackson2Version = "2.4.5"
ext.jasperReportsVersion = "6.0.3"
ext.jettyVersion = "9.2.7.v20150116"
ext.jodaVersion = "2.7"
ext.junitVersion = "4.12"
ext.nettyVersion = "4.0.24.Final"
ext.nettyVersion = "4.0.25.Final"
ext.openJpaVersion = "2.2.2" // 2.3.0 not Java 8 compatible (based on ASM 4)
ext.protobufVersion = "2.6.1"
ext.reactorVersion = "1.1.5.RELEASE"
ext.slf4jVersion = "1.7.9"
ext.slf4jVersion = "1.7.10"
ext.snakeYamlVersion = "1.14"
ext.snifferVersion = "1.11"
ext.tiles2Version = "2.2.2"
ext.tiles3Version = "3.0.5"
ext.tomcatVersion = "8.0.15"
ext.tomcatVersion = "8.0.18"
ext.tyrusVersion = "1.3.5" // constrained by WebLogic 12.1.3 support
ext.undertowVersion = "1.1.1.Final"
ext.undertowVersion = "1.1.2.Final"
ext.woodstoxVersion = "4.4.1"
ext.xstreamVersion = "1.4.7"
@@ -143,7 +143,6 @@ configure(allprojects) { project ->
inputs.dir sourceSets.main.output.classesDir
inputs.dir copyJavaApiSignature.to
outputs.upToDateWhen { true }
doLast {
ant.taskdef(
@@ -595,7 +594,7 @@ project("spring-jdbc") {
optional("javax.transaction:javax.transaction-api:1.2")
optional("com.mchange:c3p0:0.9.2.1")
optional("org.hsqldb:hsqldb:${hsqldbVersion}")
optional("com.h2database:h2:1.4.182")
optional("com.h2database:h2:1.4.185")
optional("org.apache.derby:derby:10.11.1.1")
optional("org.apache.derby:derbyclient:10.11.1.1")
}
@@ -625,9 +624,10 @@ project("spring-context-support") {
exclude group: "com.fasterxml.jackson.core", module: "jackson-databind"
exclude group: "org.olap4j", module: "olap4j"
exclude group: "xml-apis", module: "xml-apis"
exclude group: "org.springframework", module: "spring-context"
}
testCompile(project(":spring-context"))
testCompile("org.apache.poi:poi:3.10.1")
testCompile("org.apache.poi:poi:3.11")
testCompile("commons-beanutils:commons-beanutils:1.8.0") // for Velocity/JasperReports
testCompile("commons-digester:commons-digester:1.8.1") // for Velocity/JasperReports
testCompile("org.hsqldb:hsqldb:${hsqldbVersion}")
@@ -697,6 +697,7 @@ project("spring-web") {
testCompile("org.apache.taglibs:taglibs-standard-jstlel:1.2.1") {
exclude group: "org.apache.taglibs", module: "taglibs-standard-spec"
}
testCompile("com.fasterxml.jackson.datatype:jackson-datatype-joda:${jackson2Version}")
testRuntime("com.sun.mail:javax.mail:1.5.2")
}
@@ -826,10 +827,10 @@ project("spring-webmvc") {
optional("javax.servlet.jsp:javax.servlet.jsp-api:2.2.1")
optional("javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.1")
optional("net.sourceforge.jexcelapi:jxl:2.6.12")
optional("org.apache.poi:poi:3.10.1")
optional("org.apache.poi:poi:3.11")
optional("org.apache.velocity:velocity:1.7")
optional("velocity-tools:velocity-tools-view:1.4")
optional("org.freemarker:freemarker:2.3.20")
optional("org.freemarker:freemarker:2.3.21")
optional("org.codehaus.groovy:groovy-all:${groovyVersion}")
optional("com.lowagie:itext:2.1.7")
optional("net.sf.jasperreports:jasperreports:$jasperReportsVersion") {
@@ -838,6 +839,7 @@ project("spring-webmvc") {
exclude group: "com.fasterxml.jackson.core", module: "jackson-databind"
exclude group: "org.olap4j", module: "olap4j"
exclude group: "xml-apis", module: "xml-apis"
exclude group: "org.springframework", module: "spring-context"
}
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2Version}")
+1 -1
View File
@@ -1 +1 @@
version=4.1.4.BUILD-SNAPSHOT
version=4.1.5.RELEASE
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,6 +78,7 @@ import org.springframework.util.StringUtils;
* @author Rod Johnson
* @author Juergen Hoeller
* @author Rob Harrop
* @author Stephane Nicoll
* @since 15 April 2001
* @see #registerCustomEditor
* @see #setPropertyValues
@@ -978,6 +979,14 @@ public class BeanWrapperImpl extends AbstractPropertyAccessor implements BeanWra
}
Object convertedValue = convertIfNecessary(propertyName, oldValue, pv.getValue(),
requiredType, TypeDescriptor.nested(property(pd), tokens.keys.length));
int length = Array.getLength(propValue);
if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
Class<?> componentType = propValue.getClass().getComponentType();
Object newArray = Array.newInstance(componentType, arrayIndex + 1);
System.arraycopy(propValue, 0, newArray, 0, length);
setPropertyValue(actualName, newArray);
propValue = getPropertyValue(actualName);
}
Array.set(propValue, arrayIndex, convertedValue);
}
catch (IndexOutOfBoundsException ex) {
@@ -130,9 +130,10 @@ public abstract class YamlProcessor {
* Properties. Depending on the {@link #setResolutionMethod(ResolutionMethod)} not all
* of the documents will be parsed.
* @param callback a callback to delegate to once matching documents are found
* @see #createYaml()
*/
protected void process(MatchCallback callback) {
Yaml yaml = new Yaml(new StrictMapAppenderConstructor());
Yaml yaml = createYaml();
for (Resource resource : this.resources) {
boolean found = process(callback, yaml, resource);
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND && found) {
@@ -141,6 +142,13 @@ public abstract class YamlProcessor {
}
}
/**
* Create the {@link Yaml} instance to use.
*/
protected Yaml createYaml() {
return new Yaml(new StrictMapAppenderConstructor());
}
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
int count = 0;
try {
@@ -331,7 +339,7 @@ public abstract class YamlProcessor {
/**
* Status returned from {@link DocumentMatcher#matches(java.util.Properties)}
*/
public static enum MatchStatus {
public enum MatchStatus {
/**
* A match was found.
@@ -360,7 +368,7 @@ public abstract class YamlProcessor {
/**
* Method to use for resolving resources.
*/
public static enum ResolutionMethod {
public enum ResolutionMethod {
/**
* Replace values from earlier in the list.
@@ -382,7 +390,7 @@ public abstract class YamlProcessor {
/**
* A specialized {@link Constructor} that checks for duplicate keys.
*/
private static class StrictMapAppenderConstructor extends Constructor {
protected static class StrictMapAppenderConstructor extends Constructor {
public StrictMapAppenderConstructor() {
super();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -150,8 +150,8 @@ public class BeanDefinitionReaderUtils {
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -562,17 +562,10 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
@Override
public Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType) {
Map<String, Object> results = new LinkedHashMap<String, Object>();
for (String beanName : this.beanDefinitionNames) {
BeanDefinition beanDefinition = getBeanDefinition(beanName);
if (!beanDefinition.isAbstract() && findAnnotationOnBean(beanName, annotationType) != null) {
results.put(beanName, getBean(beanName));
}
}
for (String beanName : this.manualSingletonNames) {
if (!results.containsKey(beanName) && findAnnotationOnBean(beanName, annotationType) != null) {
results.put(beanName, getBean(beanName));
}
String[] beanNames = getBeanNamesForAnnotation(annotationType);
Map<String, Object> results = new LinkedHashMap<String, Object>(beanNames.length);
for (String beanName : beanNames) {
results.put(beanName, getBean(beanName));
}
return results;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author 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,12 +49,13 @@ import org.springframework.util.StringUtils;
*/
public abstract class AbstractBeanDefinitionParser implements BeanDefinitionParser {
/** Constant for the id attribute */
/** Constant for the "id" attribute */
public static final String ID_ATTRIBUTE = "id";
/** Constant for the name attribute */
/** Constant for the "name" attribute */
public static final String NAME_ATTRIBUTE = "name";
@Override
public final BeanDefinition parse(Element element, ParserContext parserContext) {
AbstractBeanDefinition definition = parseInternal(element, parserContext);
@@ -66,10 +67,12 @@ public abstract class AbstractBeanDefinitionParser implements BeanDefinitionPars
"Id is required for element '" + parserContext.getDelegate().getLocalName(element)
+ "' when used as a top-level tag", element);
}
String[] aliases = new String[0];
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
String[] aliases = null;
if (shouldParseNameAsAliases()) {
String name = element.getAttribute(NAME_ATTRIBUTE);
if (StringUtils.hasLength(name)) {
aliases = StringUtils.trimArrayElements(StringUtils.commaDelimitedListToStringArray(name));
}
}
BeanDefinitionHolder holder = new BeanDefinitionHolder(definition, id, aliases);
registerBeanDefinition(holder, parserContext.getRegistry());
@@ -170,7 +173,18 @@ public abstract class AbstractBeanDefinitionParser implements BeanDefinitionPars
}
/**
* Controls whether this parser is supposed to fire a
* Determine whether the element's "name" attribute should get parsed as
* bean definition aliases, i.e. alternative bean definition names.
* <p>The default implementation returns {@code true}.
* @return whether the parser should evaluate the "name" attribute as aliases
* @since 4.1.5
*/
protected boolean shouldParseNameAsAliases() {
return true;
}
/**
* Determine whether this parser is supposed to fire a
* {@link org.springframework.beans.factory.parsing.BeanComponentDefinition}
* event after parsing the bean definition.
* <p>This implementation returns {@code true} by default; that is,
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -518,6 +518,36 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor
assertTrue("correct values", pt.stringArray[0].equals("a1") && pt.stringArray[1].equals("b2"));
}
@Test
public void testStringArrayAutoGrow() throws Exception {
StringArrayBean target = new StringArrayBean();
BeanWrapper bw = new BeanWrapperImpl(target);
bw.setAutoGrowNestedPaths(true);
bw.setPropertyValue("array[0]", "Test0");
assertEquals(1, target.getArray().length);
bw.setPropertyValue("array[2]", "Test2");
assertEquals(3, target.getArray().length);
assertTrue("correct values", target.getArray()[0].equals("Test0") && target.getArray()[1] == null &&
target.getArray()[2].equals("Test2"));
}
@Test
public void testPrimitiveArrayAutoGrow() throws Exception {
PrimitiveArrayBean target = new PrimitiveArrayBean();
BeanWrapper bw = new BeanWrapperImpl(target);
bw.setAutoGrowNestedPaths(true);
bw.setPropertyValue("array[0]", 1);
assertEquals(1, target.getArray().length);
bw.setPropertyValue("array[2]", 3);
assertEquals(3, target.getArray().length);
assertTrue("correct values", target.getArray()[0] == 1 && target.getArray()[1] == 0 &&
target.getArray()[2] == 3);
}
@Test
public void testStringPropertyWithCustomEditor() throws Exception {
TestBean tb = new TestBean();
@@ -1723,6 +1753,20 @@ public final class BeanWrapperTests extends AbstractConfigurablePropertyAccessor
}
}
@SuppressWarnings("unused")
private static class StringArrayBean {
private String[] array;
public String[] getArray() {
return array;
}
public void setArray(String[] array) {
this.array = array;
}
}
@SuppressWarnings("unused")
private static class NumberPropertyBean {
@@ -841,19 +841,24 @@ public class ExtendedBeanInfoTests {
public String getAddress(int index){ return null; }
}
{ // baseline. ExtendedBeanInfo needs to behave exactly like the following
// Baseline: ExtendedBeanInfo needs to behave exactly like the following...
boolean hasReadMethod;
boolean hasWriteMethod;
boolean hasIndexedReadMethod;
boolean hasIndexedWriteMethod;
{
BeanInfo bi = Introspector.getBeanInfo(A.class);
assertThat(hasReadMethodForProperty(bi, "address"), is(false));
assertThat(hasWriteMethodForProperty(bi, "address"), is(false));
assertThat(hasIndexedReadMethodForProperty(bi, "address"), is(true));
assertThat(hasIndexedWriteMethodForProperty(bi, "address"), is(true));
hasReadMethod = hasReadMethodForProperty(bi, "address");
hasWriteMethod = hasWriteMethodForProperty(bi, "address");
hasIndexedReadMethod = hasIndexedReadMethodForProperty(bi, "address");
hasIndexedWriteMethod = hasIndexedWriteMethodForProperty(bi, "address");
}
{
BeanInfo bi = new ExtendedBeanInfo(Introspector.getBeanInfo(A.class));
assertThat(hasReadMethodForProperty(bi, "address"), is(false));
assertThat(hasWriteMethodForProperty(bi, "address"), is(false));
assertThat(hasIndexedReadMethodForProperty(bi, "address"), is(true));
assertThat(hasIndexedWriteMethodForProperty(bi, "address"), is(true));
assertEquals(hasReadMethodForProperty(bi, "address"), hasReadMethod);
assertEquals(hasWriteMethodForProperty(bi, "address"), hasWriteMethod);
assertEquals(hasIndexedReadMethodForProperty(bi, "address"), hasIndexedReadMethod);
assertEquals(hasIndexedWriteMethodForProperty(bi, "address"), hasIndexedWriteMethod);
}
}
@@ -381,17 +381,20 @@ public class QuartzSupportTests {
/**
* SPR-6038: detect HSQL and stop illegal locks being taken.
* TODO: Against Quartz 2.2, this test's job doesn't actually execute anymore...
*/
@Test
public void schedulerWithHsqlDataSource() throws Exception {
Assume.group(TestGroup.PERFORMANCE);
// Assume.group(TestGroup.PERFORMANCE);
DummyJob.param = 0;
DummyJob.count = 0;
ClassPathXmlApplicationContext ctx = context("databasePersistence.xml");
JdbcTemplate jdbcTemplate = new JdbcTemplate(ctx.getBean(DataSource.class));
assertTrue("No triggers were persisted", jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").size()>0);
assertFalse("No triggers were persisted", jdbcTemplate.queryForList("SELECT * FROM qrtz_triggers").isEmpty());
/*
Thread.sleep(3000);
try {
assertTrue(DummyJob.count > 0);
@@ -399,6 +402,7 @@ public class QuartzSupportTests {
finally {
ctx.close();
}
*/
}
private ClassPathXmlApplicationContext context(String path) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.context.annotation;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -63,6 +64,8 @@ final class ConfigurationClass {
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
new LinkedHashMap<ImportBeanDefinitionRegistrar, AnnotationMetadata>();
final Set<String> skippedBeans = new HashSet<String>();
/**
* Create a new {@link ConfigurationClass} with the given name.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -182,16 +182,37 @@ class ConfigurationClassBeanDefinitionReader {
* with the BeanDefinitionRegistry based on its contents.
*/
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
if (this.conditionEvaluator.shouldSkip(beanMethod.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
// Consider name and any aliases
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName());
// Do we need to mark the bean as skipped by its condition?
if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
configClass.skippedBeans.add(beanName);
return;
}
if (configClass.skippedBeans.contains(beanName)) {
return;
}
ConfigurationClass configClass = beanMethod.getConfigurationClass();
MethodMetadata metadata = beanMethod.getMetadata();
// Register aliases even when overridden
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
// Has this effectively been overridden before (e.g. via XML)?
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
return;
}
ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
beanDef.setResource(configClass.getResource());
beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));
if (metadata.isStatic()) {
// static @Bean method
beanDef.setBeanClassName(configClass.getMetadata().getClassName());
@@ -205,19 +226,6 @@ class ConfigurationClassBeanDefinitionReader {
beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);
// Consider name and any aliases
AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
String beanName = (names.size() > 0 ? names.remove(0) : beanMethod.getMetadata().getMethodName());
for (String alias : names) {
this.registry.registerAlias(beanName, alias);
}
// Has this effectively been overridden before (e.g. via XML)?
if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
return;
}
AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);
Autowire autowire = bean.getEnum("autowire");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -394,6 +394,8 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
@Override
public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException {
// Special handling of default encoding
if (format.equals("java.properties")) {
String bundleName = toBundleName(baseName, locale);
final String resourceName = toResourceName(bundleName, "properties");
@@ -441,6 +443,7 @@ public class ResourceBundleMessageSource extends AbstractMessageSource implement
}
}
else {
// Delegate handling of "java.class" format to standard Control
return super.newBundle(baseName, locale, format, loader, reload);
}
}
@@ -161,7 +161,7 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
private boolean exposeManagedResourceClassLoader = true;
/** A set of bean names that should be excluded from autodetection */
private Set<String> excludedBeans;
private Set<String> excludedBeans = new HashSet<String>();
/** The MBeanExporterListeners registered with this exporter. */
private MBeanExporterListener[] listeners;
@@ -314,7 +314,18 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
* Set the list of names for beans that should be excluded from autodetection.
*/
public void setExcludedBeans(String... excludedBeans) {
this.excludedBeans = (excludedBeans != null ? new HashSet<String>(Arrays.asList(excludedBeans)) : null);
this.excludedBeans.clear();
if (excludedBeans != null) {
this.excludedBeans.addAll(Arrays.asList(excludedBeans));
}
}
/**
* Add the name of bean that should be excluded from autodetection.
*/
public void addExcludedBean(String excludedBean) {
Assert.notNull(excludedBean, "ExcludedBean must not be null");
this.excludedBeans.add(excludedBean);
}
/**
@@ -922,10 +933,9 @@ public class MBeanExporter extends MBeanRegistrationSupport implements MBeanExpo
* Indicates whether or not a particular bean name is present in the excluded beans list.
*/
private boolean isExcluded(String beanName) {
return (this.excludedBeans != null &&
(this.excludedBeans.contains(beanName) ||
(beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX) &&
this.excludedBeans.contains(beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length())))));
return (this.excludedBeans.contains(beanName) ||
(beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX) &&
this.excludedBeans.contains(beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length()))));
}
/**
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,7 +39,10 @@ import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.scheduling.TaskScheduler;
@@ -61,12 +64,12 @@ import org.springframework.util.StringValueResolver;
* to the "fixedRate", "fixedDelay", or "cron" expression provided via the annotation.
*
* <p>This post-processor is automatically registered by Spring's
* {@code <task:annotation-driven>} XML element, and also by the @{@link EnableScheduling}
* annotation.
* {@code <task:annotation-driven>} XML element, and also by the
* @{@link EnableScheduling} annotation.
*
* <p>Auto-detects any {@link SchedulingConfigurer} instances in the container,
* allowing for customization of the scheduler to be used or for fine-grained control
* over task registration (e.g. registration of {@link Trigger} tasks.
* <p>Autodetects any {@link SchedulingConfigurer} instances in the container,
* allowing for customization of the scheduler to be used or for fine-grained
* control over task registration (e.g. registration of {@link Trigger} tasks.
* See the @{@link EnableScheduling} javadocs for complete usage details.
*
* @author Mark Fisher
@@ -80,7 +83,8 @@ import org.springframework.util.StringValueResolver;
* @see org.springframework.scheduling.config.ScheduledTaskRegistrar
*/
public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered,
EmbeddedValueResolverAware, BeanFactoryAware, SmartInitializingSingleton, DisposableBean {
EmbeddedValueResolverAware, BeanFactoryAware, ApplicationContextAware,
SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {
protected final Log logger = LogFactory.getLog(getClass());
@@ -90,6 +94,8 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
private BeanFactory beanFactory;
private ApplicationContext applicationContext;
private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();
private final Set<Class<?>> nonAnnotatedClasses =
@@ -126,10 +132,13 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
}
/**
* @deprecated as of Spring 4.1, in favor of {@link #setBeanFactory}
* Setting an {@link ApplicationContext} is optional: If set, registered
* tasks will be activated in the {@link ContextRefreshedEvent} phase;
* if not set, it will happen at {@link #afterSingletonsInstantiated} time.
*/
@Deprecated
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
if (this.beanFactory == null) {
this.beanFactory = applicationContext;
}
@@ -138,6 +147,23 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
@Override
public void afterSingletonsInstantiated() {
if (this.applicationContext == null) {
// Not running in an ApplicationContext -> register tasks early...
finishRegistration();
}
}
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
// Running in an ApplicationContext -> register tasks this late...
// giving other ContextRefreshedEvent listeners a chance to perform
// their work at the same time (e.g. Spring Batch's job registration).
finishRegistration();
}
}
private void finishRegistration() {
if (this.scheduler != null) {
this.registrar.setScheduler(this.scheduler);
}
@@ -190,9 +216,9 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
@Override
public Object postProcessAfterInitialization(final Object bean, String beanName) {
if (!this.nonAnnotatedClasses.contains(bean.getClass())) {
Class<?> targetClass = AopUtils.getTargetClass(bean);
if (!this.nonAnnotatedClasses.contains(targetClass)) {
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
Class<?> targetClass = AopUtils.getTargetClass(bean);
ReflectionUtils.doWithMethods(targetClass, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
@@ -204,7 +230,7 @@ public class ScheduledAnnotationBeanPostProcessor implements BeanPostProcessor,
}
});
if (annotatedMethods.isEmpty()) {
this.nonAnnotatedClasses.add(bean.getClass());
this.nonAnnotatedClasses.add(targetClass);
if (logger.isDebugEnabled()) {
logger.debug("No @Scheduled annotations found on bean class: " + bean.getClass());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import static org.junit.Assert.*;
* Test for {@link Conditional} beans.
*
* @author Phillip Webb
* @author Juergen Hoeller
*/
@SuppressWarnings("resource")
public class ConfigurationClassWithConditionTests {
@@ -117,6 +118,19 @@ public class ConfigurationClassWithConditionTests {
ctx.refresh();
}
@Test
public void conditionOnOverriddenMethodHonored() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithBeanSkipped.class);
assertEquals(0, context.getBeansOfType(ExampleBean.class).size());
}
@Test
public void noConditionOnOverriddenMethodHonored() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigWithBeanReactivated.class);
assertEquals(1, context.getBeansOfType(ExampleBean.class).size());
}
@Configuration
static class BeanOneConfiguration {
@Bean
@@ -197,6 +211,7 @@ public class ConfigurationClassWithConditionTests {
}
static class NeverCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
@@ -261,4 +276,32 @@ public class ConfigurationClassWithConditionTests {
static class ExampleBean {
}
@Configuration
private static class ConfigWithBeanActive {
@Bean
public ExampleBean baz() {
return new ExampleBean();
}
}
private static class ConfigWithBeanSkipped extends ConfigWithBeanActive {
@Override
@Bean
@Conditional(NeverCondition.class)
public ExampleBean baz() {
return new ExampleBean();
}
}
private static class ConfigWithBeanReactivated extends ConfigWithBeanSkipped {
@Override
@Bean
public ExampleBean baz() {
return new ExampleBean();
}
}
}
@@ -17,6 +17,8 @@
package org.springframework.jmx.export;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -643,6 +645,28 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests {
ObjectNameManager.getInstance(objectName2));
}
@Test
public void testIgnoreBeanName() throws MalformedObjectNameException {
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
String firstBeanName = "spring:type=TestBean";
factory.registerSingleton(firstBeanName, new TestBean("test"));
String secondBeanName = "spring:type=TestBean2";
factory.registerSingleton(secondBeanName, new TestBean("test2"));
MBeanExporter exporter = new MBeanExporter();
exporter.setServer(getServer());
exporter.setAssembler(new NamedBeanAutodetectCapableMBeanInfoAssemblerStub(firstBeanName, secondBeanName));
exporter.setBeanFactory(factory);
exporter.setAutodetectMode(MBeanExporter.AUTODETECT_ALL);
exporter.addExcludedBean(secondBeanName);
start(exporter);
assertIsRegistered("Bean not autodetected in (AUTODETECT_ALL) mode",
ObjectNameManager.getInstance(firstBeanName));
assertIsNotRegistered("Bean should have been excluded",
ObjectNameManager.getInstance(secondBeanName));
}
private ConfigurableApplicationContext load(String context) {
return new ClassPathXmlApplicationContext(context, getClass());
}
@@ -763,15 +787,15 @@ public final class MBeanExporterTests extends AbstractMBeanServerTests {
private static final class NamedBeanAutodetectCapableMBeanInfoAssemblerStub extends
SimpleReflectiveMBeanInfoAssembler implements AutodetectCapableMBeanInfoAssembler {
private String namedBean;
private Collection<String> namedBeans;
public NamedBeanAutodetectCapableMBeanInfoAssemblerStub(String namedBean) {
this.namedBean = namedBean;
public NamedBeanAutodetectCapableMBeanInfoAssemblerStub(String... namedBeans) {
this.namedBeans = Arrays.asList(namedBeans);
}
@Override
public boolean includeBean(Class<?> beanClass, String beanName) {
return this.namedBean.equals(beanName);
return this.namedBeans.contains(beanName);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -309,6 +309,7 @@ public class MethodParameter {
/**
* Return the generic type of the method/constructor parameter.
* @return the parameter type (never {@code null})
* @since 3.0
*/
public Type getGenericParameterType() {
if (this.genericParameterType == null) {
@@ -324,6 +325,12 @@ public class MethodParameter {
return this.genericParameterType;
}
/**
* Return the nested type of the method/constructor parameter.
* @return the parameter type (never {@code null})
* @see #getNestingLevel()
* @since 3.1
*/
public Class<?> getNestedParameterType() {
if (this.nestingLevel > 1) {
Type type = getGenericParameterType();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1092,9 +1092,8 @@ public final class ResolvableType implements Serializable {
public static ResolvableType forMethodParameter(MethodParameter methodParameter, Type targetType) {
Assert.notNull(methodParameter, "MethodParameter must not be null");
ResolvableType owner = forType(methodParameter.getContainingClass()).as(methodParameter.getDeclaringClass());
return forType(targetType, new MethodParameterTypeProvider(methodParameter),
owner.asVariableResolver()).getNested(methodParameter.getNestingLevel(),
methodParameter.typeIndexesPerLevel);
return forType(targetType, new MethodParameterTypeProvider(methodParameter), owner.asVariableResolver()).
getNested(methodParameter.getNestingLevel(), methodParameter.typeIndexesPerLevel);
}
/**
@@ -35,6 +35,7 @@ import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* General utility methods for working with annotations, handling bridge methods
@@ -654,6 +655,9 @@ public abstract class AnnotationUtils {
* @see #getValue(Annotation)
*/
public static Object getValue(Annotation annotation, String attributeName) {
if (annotation == null || !StringUtils.hasLength(attributeName)) {
return null;
}
try {
Method method = annotation.annotationType().getDeclaredMethod(attributeName);
ReflectionUtils.makeAccessible(method);
@@ -683,6 +687,9 @@ public abstract class AnnotationUtils {
* @see #getDefaultValue(Class, String)
*/
public static Object getDefaultValue(Annotation annotation, String attributeName) {
if (annotation == null) {
return null;
}
return getDefaultValue(annotation.annotationType(), attributeName);
}
@@ -706,6 +713,9 @@ public abstract class AnnotationUtils {
* @see #getDefaultValue(Annotation, String)
*/
public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
if (annotationType == null || !StringUtils.hasLength(attributeName)) {
return null;
}
try {
return annotationType.getDeclaredMethod(attributeName).getDefaultValue();
}
@@ -69,7 +69,7 @@ final class ObjectToOptionalConverter implements ConditionalGenericConverter {
}
else {
Object target = this.conversionService.convert(source, sourceType, new GenericTypeDescriptor(targetType));
return Optional.of(target);
return Optional.ofNullable(target);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author 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,9 +22,10 @@ import java.util.Random;
import java.util.UUID;
/**
* A variation of {@link UUID#randomUUID()} that uses {@link SecureRandom} only for the
* initial seed and {@link Random} thereafter. This provides better performance in
* exchange for less securely random id's.
* An {@link IdGenerator} that uses {@link SecureRandom} for the initial seed and
* {@link Random} thereafter, instead of calling {@link UUID#randomUUID()} every
* time as {@link org.springframework.util.JdkIdGenerator JdkIdGenerator} does.
* This provides a better balance between securely random ids and performance.
*
* @author Rossen Stoyanchev
* @author Rob Winch
@@ -43,8 +44,8 @@ public class AlternativeJdkIdGenerator implements IdGenerator {
}
@Override
public UUID generateId() {
byte[] randomBytes = new byte[16];
this.random.nextBytes(randomBytes);
@@ -0,0 +1,34 @@
/*
* Copyright 2002-2015 the original author 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.util;
import java.util.UUID;
/**
* An {@link IdGenerator} that calls {@link java.util.UUID#randomUUID()}.
*
* @author Rossen Stoyanchev
* @since 4.1.5
*/
public class JdkIdGenerator implements IdGenerator {
@Override
public UUID generateId() {
return UUID.randomUUID();
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2002-2015 the original author 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.util;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
/**
* A simple {@link IdGenerator} that starts at 1 and increments by 1 with each call.
*
* @author Rossen Stoyanchev
* @since 4.1.5
*/
public class SimpleIdGenerator implements IdGenerator {
private final AtomicLong mostSigBits = new AtomicLong(0);
private final AtomicLong leastSigBits = new AtomicLong(0);
@Override
public UUID generateId() {
long leastSigBits = this.leastSigBits.incrementAndGet();
if (leastSigBits == 0) {
this.mostSigBits.incrementAndGet();
}
return new UUID(this.mostSigBits.get(), leastSigBits);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,8 @@
package org.springframework.expression.spel.ast;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@@ -27,15 +29,20 @@ import org.springframework.expression.spel.SpelMessage;
import org.springframework.expression.spel.support.BooleanTypedValue;
/**
* Implements the matches operator. Matches takes two operands. The first is a string and
* the second is a java regex. It will return true when getValue() is called if the first
* operand matches the regex.
* Implements the matches operator. Matches takes two operands:
* The first is a String and the second is a Java regex.
* It will return {@code true} when {@link #getValue} is called
* if the first operand matches the regex.
*
* @author Andy Clement
* @author Juergen Hoeller
* @since 3.0
*/
public class OperatorMatches extends Operator {
private final ConcurrentMap<String, Pattern> patternCache = new ConcurrentHashMap<String, Pattern>();
public OperatorMatches(int pos, SpelNodeImpl... operands) {
super("matches", pos, operands);
}
@@ -66,8 +73,14 @@ public class OperatorMatches extends Operator {
}
try {
Pattern pattern = Pattern.compile((String) right);
Matcher matcher = pattern.matcher((String) left);
String leftString = (String) left;
String rightString = (String) right;
Pattern pattern = this.patternCache.get(rightString);
if (pattern == null) {
pattern = Pattern.compile(rightString);
this.patternCache.putIfAbsent(rightString, pattern);
}
Matcher matcher = pattern.matcher(leftString);
return BooleanTypedValue.forValue(matcher.matches());
}
catch (PatternSyntaxException ex) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,16 +70,8 @@ import org.springframework.util.IdGenerator;
*/
public class MessageHeaders implements Map<String, Object>, Serializable {
private static final long serialVersionUID = 7035068984263400920L;
private static final Log logger = LogFactory.getLog(MessageHeaders.class);
public static final UUID ID_VALUE_NONE = new UUID(0,0);
private static volatile IdGenerator idGenerator = null;
private static final IdGenerator defaultIdGenerator = new AlternativeJdkIdGenerator();
/**
* The key for the Message ID. This is an automatically generated UUID and
* should never be explicitly set in the header map <b>except</b> in the
@@ -90,11 +82,20 @@ public class MessageHeaders implements Map<String, Object>, Serializable {
public static final String TIMESTAMP = "timestamp";
public static final String CONTENT_TYPE = "contentType";
public static final String REPLY_CHANNEL = "replyChannel";
public static final String ERROR_CHANNEL = "errorChannel";
public static final String CONTENT_TYPE = "contentType";
private static final long serialVersionUID = 7035068984263400920L;
private static final Log logger = LogFactory.getLog(MessageHeaders.class);
private static final IdGenerator defaultIdGenerator = new AlternativeJdkIdGenerator();
private static volatile IdGenerator idGenerator = null;
private final Map<String, Object> headers;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,10 +32,10 @@ import org.springframework.util.Assert;
import org.springframework.util.MimeType;
/**
* Abstract base class for {@link MessageConverter} implementations including support for
* common properties and a partial implementation of the conversion methods mainly to
* check if the converter supports the conversion based on the payload class and MIME
* type.
* Abstract base class for {@link MessageConverter} implementations including support
* for common properties and a partial implementation of the conversion methods,
* mainly to check if the converter supports the conversion based on the payload class
* and MIME type.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -68,7 +68,7 @@ public abstract class AbstractMessageConverter implements MessageConverter {
* @param supportedMimeTypes the supported MIME types
*/
protected AbstractMessageConverter(Collection<MimeType> supportedMimeTypes) {
Assert.notNull(supportedMimeTypes, "SupportedMimeTypes must not be null");
Assert.notNull(supportedMimeTypes, "supportedMimeTypes must not be null");
this.supportedMimeTypes = new ArrayList<MimeType>(supportedMimeTypes);
}
@@ -83,13 +83,11 @@ public abstract class AbstractMessageConverter implements MessageConverter {
/**
* Configure the {@link ContentTypeResolver} to use to resolve the content
* type of an input message.
* <p>
* Note that if no resolver is configured, then
* <p>Note that if no resolver is configured, then
* {@link #setStrictContentTypeMatch(boolean) strictContentTypeMatch} should
* be left as {@code false} (the default) or otherwise this converter will
* ignore all messages.
* <p>
* By default, a {@code DefaultContentTypeResolver} instance is used.
* <p>By default, a {@code DefaultContentTypeResolver} instance is used.
*/
public void setContentTypeResolver(ContentTypeResolver resolver) {
this.contentTypeResolver = resolver;
@@ -106,20 +104,17 @@ public abstract class AbstractMessageConverter implements MessageConverter {
* Whether this converter should convert messages for which no content type
* could be resolved through the configured
* {@link org.springframework.messaging.converter.ContentTypeResolver}.
* A converter can configured to be strict only when a
* {@link #setContentTypeResolver(ContentTypeResolver) contentTypeResolver}
* is configured and the list of {@link #getSupportedMimeTypes() supportedMimeTypes}
* is not be empty.
*
* then requires the content type of a message to be resolved
*
* When set to true, #supportsMimeType(MessageHeaders) will return false if the
* contentTypeResolver is not defined or if no content-type header is present.
* <p>A converter can configured to be strict only when a
* {@link #setContentTypeResolver contentTypeResolver} is configured and the
* list of {@link #getSupportedMimeTypes() supportedMimeTypes} is not be empty.
* <p>When this flag is set to {@code true}, {@link #supportsMimeType(MessageHeaders)}
* will return {@code false} if the {@link #setContentTypeResolver contentTypeResolver}
* is not defined or if no content-type header is present.
*/
public void setStrictContentTypeMatch(boolean strictContentTypeMatch) {
if (strictContentTypeMatch) {
Assert.notEmpty(getSupportedMimeTypes(), "Strict match requires non-empty list of supported mime types.");
Assert.notNull(getContentTypeResolver(), "Strict match requires ContentTypeResolver.");
Assert.notEmpty(getSupportedMimeTypes(), "Strict match requires non-empty list of supported mime types");
Assert.notNull(getContentTypeResolver(), "Strict match requires ContentTypeResolver");
}
this.strictContentTypeMatch = strictContentTypeMatch;
}
@@ -166,14 +161,6 @@ public abstract class AbstractMessageConverter implements MessageConverter {
return (!mimeTypes.isEmpty() ? mimeTypes.get(0) : null);
}
/**
* Whether the given class is supported by this converter.
* @param clazz the class to test for support
* @return {@code true} if supported; {@code false} otherwise
*/
protected abstract boolean supports(Class<?> clazz);
@Override
public final Object fromMessage(Message<?> message, Class<?> targetClass) {
if (!canConvertFrom(message, targetClass)) {
@@ -186,14 +173,8 @@ public abstract class AbstractMessageConverter implements MessageConverter {
return (supports(targetClass) && supportsMimeType(message.getHeaders()));
}
/**
* Convert the message payload from serialized form to an Object.
*/
public abstract Object convertFromInternal(Message<?> message, Class<?> targetClass);
@Override
public final Message<?> toMessage(Object payload, MessageHeaders headers) {
if (!canConvertTo(payload, headers)) {
return null;
}
@@ -218,15 +199,10 @@ public abstract class AbstractMessageConverter implements MessageConverter {
}
protected boolean canConvertTo(Object payload, MessageHeaders headers) {
Class<?> clazz = (payload != null) ? payload.getClass() : null;
Class<?> clazz = (payload != null ? payload.getClass() : null);
return (supports(clazz) && supportsMimeType(headers));
}
/**
* Convert the payload object to serialized form.
*/
public abstract Object convertToInternal(Object payload, MessageHeaders headers);
protected boolean supportsMimeType(MessageHeaders headers) {
if (getSupportedMimeTypes().isEmpty()) {
return true;
@@ -249,7 +225,26 @@ public abstract class AbstractMessageConverter implements MessageConverter {
}
protected MimeType getMimeType(MessageHeaders headers) {
return (this.contentTypeResolver != null) ? this.contentTypeResolver.resolve(headers) : null;
return (this.contentTypeResolver != null ? this.contentTypeResolver.resolve(headers) : null);
}
/**
* Whether the given class is supported by this converter.
* @param clazz the class to test for support
* @return {@code true} if supported; {@code false} otherwise
*/
protected abstract boolean supports(Class<?> clazz);
/**
* Convert the message payload from serialized form to an Object.
*/
public abstract Object convertFromInternal(Message<?> message, Class<?> targetClass);
/**
* Convert the payload object to serialized form.
*/
public abstract Object convertToInternal(Object payload, MessageHeaders headers);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
import com.fasterxml.jackson.core.JsonEncoding;
@@ -51,6 +52,7 @@ import org.springframework.util.MimeType;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Sebastien Deleuze
* @since 4.0
*/
public class MappingJackson2MessageConverter extends AbstractMessageConverter {
@@ -65,8 +67,28 @@ public class MappingJackson2MessageConverter extends AbstractMessageConverter {
private Boolean prettyPrint;
/**
* Construct a {@code MappingJackson2MessageConverter} supporting
* the {@code application/json} MIME type.
*/
public MappingJackson2MessageConverter() {
super(new MimeType("application", "json", Charset.forName("UTF-8")));
initObjectMapper();
}
/**
* Construct a {@code MappingJackson2MessageConverter} supporting
* one or more custom MIME types.
* @param supportedMimeTypes the supported MIME types
* @since 4.1.5
*/
public MappingJackson2MessageConverter(MimeType... supportedMimeTypes) {
super(Arrays.asList(supportedMimeTypes));
initObjectMapper();
}
private void initObjectMapper() {
this.objectMapper = new ObjectMapper();
this.objectMapper.configure(MapperFeature.DEFAULT_VIEW_INCLUSION, false);
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,23 +42,23 @@ public abstract class AbstractMethodArgumentResolutionException extends Messagin
/**
* Create a new instance providing the invalid {@code MethodParameter} and
* a prepared description. Sub-classes should prepend the description with
* a prepared description. Subclasses should prepend the description with
* the help of {@link #getMethodParamMessage(org.springframework.core.MethodParameter)}.
*/
protected AbstractMethodArgumentResolutionException(Message<?> message,
MethodParameter parameter, String description) {
protected AbstractMethodArgumentResolutionException(Message<?> message, MethodParameter param, String description) {
super(message, description);
this.parameter = parameter;
this.parameter = param;
}
/**
* Return the MethodParameter that was rejected.
*/
public MethodParameter getMethodParameter() {
public final MethodParameter getMethodParameter() {
return this.parameter;
}
protected static String getMethodParamMessage(MethodParameter param) {
return new StringBuilder("Could not resolve method parameter at index ")
.append(param.getParameterIndex()).append(" in method: ")
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,22 +49,21 @@ public class MethodArgumentNotValidException extends AbstractMethodArgumentResol
public MethodArgumentNotValidException(Message<?> message, MethodParameter parameter,
BindingResult bindingResult) {
super(message, parameter, getMethodParamMessage(parameter) +
getValidationErrorMessage(parameter, bindingResult));
super(message, parameter, getMethodParamMessage(parameter) + getValidationErrorMessage(bindingResult));
this.bindingResult = bindingResult;
}
/**
* Return the BindingResult if the failure is validation-related or {@code null}.
* Return the BindingResult if the failure is validation-related,
* or {@code null} if none.
*/
public BindingResult getBindingResult() {
public final BindingResult getBindingResult() {
return this.bindingResult;
}
private static String getValidationErrorMessage(MethodParameter parameter, BindingResult bindingResult) {
private static String getValidationErrorMessage(BindingResult bindingResult) {
if (bindingResult != null) {
StringBuilder sb = new StringBuilder();
sb.append(", with ").append(bindingResult.getErrorCount()).append(" error(s): ");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,11 +29,11 @@ import org.springframework.messaging.Message;
@SuppressWarnings("serial")
public class MethodArgumentTypeMismatchException extends AbstractMethodArgumentResolutionException {
/**
* Create a new instance with the invalid {@code MethodParameter}.
*/
public MethodArgumentTypeMismatchException(Message<?> message, MethodParameter parameter, String description) {
super(message, parameter, getMethodParamMessage(parameter) + description);
public MethodArgumentTypeMismatchException(Message<?> message, MethodParameter param, String description) {
super(message, param, getMethodParamMessage(param) + description);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -144,6 +144,16 @@ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver {
}
}
/**
* Validate the payload if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param message the currently processed message
* @param parameter the method parameter
* @param target the target payload object
* @throws MethodArgumentNotValidException in case of binding errors
*/
protected void validate(Message<?> message, MethodParameter parameter, Object target) {
if (this.validator == null) {
return;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author 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,8 +20,8 @@ import org.springframework.core.MethodParameter;
import org.springframework.messaging.Message;
/**
* Strategy interface for resolving method parameters into argument values in
* the context of a given {@link Message}.
* Strategy interface for resolving method parameters into argument values
* in the context of a given {@link Message}.
*
* @author Rossen Stoyanchev
* @since 4.0
@@ -39,12 +39,12 @@ public interface HandlerMethodArgumentResolver {
/**
* Resolves a method parameter into an argument value from a given message.
* @param parameter the method parameter to resolve. This parameter must
* have previously been passed to
* @param parameter the method parameter to resolve.
* This parameter must have previously been passed to
* {@link #supportsParameter(org.springframework.core.MethodParameter)}
* and it must have returned {@code true}
* which must have returned {@code true}.
* @param message the currently processed message
* @return the resolved argument value, or {@code null}.
* @return the resolved argument value, or {@code null}
* @throws Exception in case of errors with the preparation of argument values
*/
Object resolveArgument(MethodParameter parameter, Message<?> message) throws Exception;
@@ -188,7 +188,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
}
/**
* The configured {@link ConversionService}.
* Return the configured {@link ConversionService}.
*/
public ConversionService getConversionService() {
return this.conversionService;
@@ -206,14 +206,14 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
}
/**
* Return the PathMatcher implementation to use for matching destinations
* Return the PathMatcher implementation to use for matching destinations.
*/
public PathMatcher getPathMatcher() {
return this.pathMatcher;
}
/**
* The configured Validator instance
* Return the configured Validator instance.
*/
public Validator getValidator() {
return this.validator;
@@ -343,17 +343,17 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan
@Override
protected SimpMessageMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
MessageMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, MessageMapping.class);
MessageMapping messageAnnot = AnnotationUtils.findAnnotation(method, MessageMapping.class);
if (messageAnnot != null) {
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnot);
MessageMapping messageAnnotation = AnnotationUtils.findAnnotation(method, MessageMapping.class);
if (messageAnnotation != null) {
SimpMessageMappingInfo result = createMessageMappingCondition(messageAnnotation);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation).combine(result);
}
return result;
}
SubscribeMapping subsribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
if (subsribeAnnotation != null) {
SimpMessageMappingInfo result = createSubscribeCondition(subsribeAnnotation);
SubscribeMapping subscribeAnnotation = AnnotationUtils.findAnnotation(method, SubscribeMapping.class);
if (subscribeAnnotation != null) {
SimpMessageMappingInfo result = createSubscribeCondition(subscribeAnnotation);
if (typeAnnotation != null) {
result = createMessageMappingCondition(typeAnnotation).combine(result);
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.PathMatcher;
/**
* A default, simple in-memory implementation of {@link SubscriptionRegistry}.
*
@@ -165,8 +166,8 @@ public class DefaultSubscriptionRegistry extends AbstractSubscriptionRegistry {
public void addSubscriptions(String destination, MultiValueMap<String, String> subscriptions) {
synchronized (this.updateCache) {
this.updateCache.put(destination, subscriptions);
this.accessCache.put(destination, new LinkedMultiValueMap<String, String>(subscriptions));
this.updateCache.put(destination, new LinkedMultiValueMap<String, String>(subscriptions));
this.accessCache.put(destination, subscriptions);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,6 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
/**
* Configure a custom SubscriptionRegistry to use for storing subscriptions.
*
* <p><strong>Note</strong> that when a custom PathMatcher is configured via
* {@link #setPathMatcher}, if the custom registry is not an instance of
* {@link DefaultSubscriptionRegistry}, the provided PathMatcher is not used
@@ -102,9 +101,8 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
}
/**
* Configure a {@link MessageHeaderInitializer} to apply to the headers of all
* messages sent to the client outbound channel.
*
* Configure a {@link MessageHeaderInitializer} to apply to the headers
* of all messages sent to the client outbound channel.
* <p>By default this property is not set.
*/
public void setHeaderInitializer(MessageHeaderInitializer headerInitializer) {
@@ -112,7 +110,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
}
/**
* @return the configured header initializer.
* Return the configured header initializer.
*/
public MessageHeaderInitializer getHeaderInitializer() {
return this.headerInitializer;
@@ -131,7 +129,6 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
@Override
protected void handleMessageInternal(Message<?> message) {
MessageHeaders headers = message.getHeaders();
SimpMessageType messageType = SimpMessageHeaderAccessor.getMessageType(headers);
String destination = SimpMessageHeaderAccessor.getDestination(headers);
@@ -197,7 +194,7 @@ public class SimpleBrokerMessageHandler extends AbstractBrokerMessageHandler {
protected void sendMessageToSubscribers(String destination, Message<?> message) {
MultiValueMap<String,String> subscriptions = this.subscriptionRegistry.findSubscriptions(message);
if ((subscriptions.size() > 0) && logger.isDebugEnabled()) {
if (!subscriptions.isEmpty() && logger.isDebugEnabled()) {
logger.debug("Broadcasting to " + subscriptions.size() + " sessions.");
}
for (String sessionId : subscriptions.keySet()) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -45,9 +45,11 @@ public interface SubscriptionRegistry {
void unregisterAllSubscriptions(String sessionId);
/**
* Find all subscriptions that should receive the given message.
* Find all subscriptions that should receive the given message. The map
* returned is safe to iterate and will never be modified.
* @param message the message
* @return a {@link MultiValueMap} from sessionId to subscriptionId's, possibly empty.
* @return a {@code MultiValueMap} with sessionId-subscriptionId pairs,
* possibly empty.
*/
MultiValueMap<String, String> findSubscriptions(Message<?> message);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -418,11 +418,31 @@ public class MessageHeaderAccessor {
// Specific header accessors
public UUID getId() {
return (UUID) getHeader(MessageHeaders.ID);
Object value = getHeader(MessageHeaders.ID);
if (value == null) {
return null;
}
return (value instanceof UUID ? (UUID) value : UUID.fromString(value.toString()));
}
public Long getTimestamp() {
return (Long) getHeader(MessageHeaders.TIMESTAMP);
Object value = getHeader(MessageHeaders.TIMESTAMP);
if (value == null) {
return null;
}
return (value instanceof Long ? (Long) value : Long.parseLong(value.toString()));
}
public void setContentType(MimeType contentType) {
setHeader(MessageHeaders.CONTENT_TYPE, contentType);
}
public MimeType getContentType() {
Object value = getHeader(MessageHeaders.CONTENT_TYPE);
if (value == null) {
return null;
}
return (value instanceof MimeType ? (MimeType) value : MimeType.valueOf(value.toString()));
}
public void setReplyChannelName(String replyChannelName) {
@@ -449,14 +469,6 @@ public class MessageHeaderAccessor {
return getHeader(MessageHeaders.ERROR_CHANNEL);
}
public void setContentType(MimeType contentType) {
setHeader(MessageHeaders.CONTENT_TYPE, contentType);
}
public MimeType getContentType() {
return (MimeType) getHeader(MessageHeaders.CONTENT_TYPE);
}
// Log message stuff
@@ -508,7 +520,7 @@ public class MessageHeaderAccessor {
protected String getDetailedPayloadLogMessage(Object payload) {
if (payload instanceof String) {
return " payload=" + ((String) payload);
return " payload=" + payload;
}
else if (payload instanceof byte[]) {
byte[] bytes = (byte[]) payload;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,7 +22,7 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import com.fasterxml.jackson.databind.DeserializationFeature;
import org.junit.Test;
import org.springframework.messaging.Message;
@@ -30,31 +30,51 @@ import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.MimeType;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Test fixture for {@link org.springframework.messaging.converter.MappingJackson2MessageConverter}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class MappingJackson2MessageConverterTests {
private static Charset UTF_8 = Charset.forName("UTF-8");
private MappingJackson2MessageConverter converter;
@Test
public void defaultConstructor() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
assertThat(converter.getSupportedMimeTypes(), contains(new MimeType("application", "json", UTF_8)));
assertFalse(converter.getObjectMapper().getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
}
@Before
public void setup() {
this.converter = new MappingJackson2MessageConverter();
@Test // SPR-12724
public void mimetypeParametrizedConstructor() {
MimeType mimetype = new MimeType("application", "xml", UTF_8);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(mimetype);
assertThat(converter.getSupportedMimeTypes(), contains(mimetype));
assertFalse(converter.getObjectMapper().getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
}
@Test // SPR-12724
public void mimetypesParametrizedConstructor() {
MimeType jsonMimetype = new MimeType("application", "json", UTF_8);
MimeType xmlMimetype = new MimeType("application", "xml", UTF_8);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(jsonMimetype, xmlMimetype);
assertThat(converter.getSupportedMimeTypes(), contains(jsonMimetype, xmlMimetype));
assertFalse(converter.getObjectMapper().getDeserializationConfig().isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES));
}
@Test
public void fromMessage() throws Exception {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],"
+ "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
MyBean actual = (MyBean) this.converter.fromMessage(message, MyBean.class);
MyBean actual = (MyBean) converter.fromMessage(message, MyBean.class);
assertEquals("Foo", actual.getString());
assertEquals(42, actual.getNumber());
@@ -66,11 +86,12 @@ public class MappingJackson2MessageConverterTests {
@Test
public void fromMessageUntyped() throws Exception {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
String payload = "{\"bytes\":\"AQI=\",\"array\":[\"Foo\",\"Bar\"],"
+ "\"number\":42,\"string\":\"Foo\",\"bool\":true,\"fraction\":42.0}";
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
@SuppressWarnings("unchecked")
HashMap<String, Object> actual = (HashMap<String, Object>) this.converter.fromMessage(message, HashMap.class);
HashMap<String, Object> actual = (HashMap<String, Object>) converter.fromMessage(message, HashMap.class);
assertEquals("Foo", actual.get("string"));
assertEquals(42, actual.get("number"));
@@ -82,21 +103,24 @@ public class MappingJackson2MessageConverterTests {
@Test(expected = MessageConversionException.class)
public void fromMessageInvalidJson() throws Exception {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
String payload = "FooBar";
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
this.converter.fromMessage(message, MyBean.class);
converter.fromMessage(message, MyBean.class);
}
@Test
public void fromMessageValidJsonWithUnknownProperty() throws IOException {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
String payload = "{\"string\":\"string\",\"unknownProperty\":\"value\"}";
Message<?> message = MessageBuilder.withPayload(payload.getBytes(UTF_8)).build();
MyBean myBean = (MyBean)this.converter.fromMessage(message, MyBean.class);
MyBean myBean = (MyBean)converter.fromMessage(message, MyBean.class);
assertEquals("string", myBean.getString());
}
@Test
public void toMessage() throws Exception {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
MyBean payload = new MyBean();
payload.setString("Foo");
payload.setNumber(42);
@@ -105,7 +129,7 @@ public class MappingJackson2MessageConverterTests {
payload.setBool(true);
payload.setBytes(new byte[]{0x1, 0x2});
Message<?> message = this.converter.toMessage(payload, null);
Message<?> message = converter.toMessage(payload, null);
String actual = new String((byte[]) message.getPayload(), UTF_8);
assertTrue(actual.contains("\"string\":\"Foo\""));
@@ -120,13 +144,14 @@ public class MappingJackson2MessageConverterTests {
@Test
public void toMessageUtf16() {
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
Charset utf16 = Charset.forName("UTF-16BE");
MimeType contentType = new MimeType("application", "json", utf16);
Map<String, Object> map = new HashMap<>();
map.put(MessageHeaders.CONTENT_TYPE, contentType);
MessageHeaders headers = new MessageHeaders(map);
String payload = "H\u00e9llo W\u00f6rld";
Message<?> message = this.converter.toMessage(payload, headers);
Message<?> message = converter.toMessage(payload, headers);
assertEquals("\"" + payload + "\"", new String((byte[]) message.getPayload(), utf16));
assertEquals(contentType, message.getHeaders().get(MessageHeaders.CONTENT_TYPE));
@@ -134,7 +159,8 @@ public class MappingJackson2MessageConverterTests {
@Test
public void toMessageUtf16String() {
this.converter.setSerializedPayloadClass(String.class);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setSerializedPayloadClass(String.class);
Charset utf16 = Charset.forName("UTF-16BE");
MimeType contentType = new MimeType("application", "json", utf16);
@@ -142,7 +168,7 @@ public class MappingJackson2MessageConverterTests {
map.put(MessageHeaders.CONTENT_TYPE, contentType);
MessageHeaders headers = new MessageHeaders(map);
String payload = "H\u00e9llo W\u00f6rld";
Message<?> message = this.converter.toMessage(payload, headers);
Message<?> message = converter.toMessage(payload, headers);
assertEquals("\"" + payload + "\"", message.getPayload());
assertEquals(contentType, message.getHeaders().get(MessageHeaders.CONTENT_TYPE));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.messaging.simp.broker;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.junit.Before;
@@ -31,6 +32,7 @@ import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
/**
* Test fixture for {@link org.springframework.messaging.simp.broker.DefaultSubscriptionRegistry}.
*
@@ -266,7 +268,7 @@ public class DefaultSubscriptionRegistryTests {
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected three elements " + actual, 2, actual.size());
assertEquals("Expected two elements: " + actual, 2, actual.size());
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(1))));
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(2))));
}
@@ -310,7 +312,7 @@ public class DefaultSubscriptionRegistryTests {
MultiValueMap<String, String> actual = this.registry.findSubscriptions(message(dest));
assertEquals("Expected three elements " + actual, 1, actual.size());
assertEquals("Expected one element: " + actual, 1, actual.size());
assertEquals(subscriptionIds, sort(actual.get(sessIds.get(2))));
}
@@ -326,6 +328,23 @@ public class DefaultSubscriptionRegistryTests {
assertEquals("Expected no elements " + actual, 0, actual.size());
}
// SPR-12665
@Test
public void findSubscriptionsReturnsMapSafeToIterate() throws Exception {
this.registry.registerSubscription(subscribeMessage("sess1", "1", "/foo"));
this.registry.registerSubscription(subscribeMessage("sess2", "1", "/foo"));
MultiValueMap<String, String> subscriptions = this.registry.findSubscriptions(message("/foo"));
assertEquals(2, subscriptions.size());
Iterator iterator = subscriptions.entrySet().iterator();
iterator.next();
this.registry.registerSubscription(subscribeMessage("sess3", "1", "/foo"));
iterator.next();
// no ConcurrentModificationException
}
private Message<?> subscribeMessage(String sessionId, String subscriptionId, String destination) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.SUBSCRIBE);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.messaging.simp.broker;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -84,12 +85,12 @@ public class SimpleBrokerMessageHandlerTests {
this.messageHandler.handleMessage(createMessage("/bar", "message2"));
verify(this.clientOutboundChannel, times(6)).send(this.messageCaptor.capture());
assertCapturedMessage("sess1", "sub1", "/foo");
assertCapturedMessage("sess1", "sub2", "/foo");
assertCapturedMessage("sess2", "sub1", "/foo");
assertCapturedMessage("sess2", "sub2", "/foo");
assertCapturedMessage("sess1", "sub3", "/bar");
assertCapturedMessage("sess2", "sub3", "/bar");
assertTrue(messageCaptured("sess1", "sub1", "/foo"));
assertTrue(messageCaptured("sess1", "sub2", "/foo"));
assertTrue(messageCaptured("sess2", "sub1", "/foo"));
assertTrue(messageCaptured("sess2", "sub2", "/foo"));
assertTrue(messageCaptured("sess1", "sub3", "/bar"));
assertTrue(messageCaptured("sess2", "sub3", "/bar"));
}
@Test
@@ -124,9 +125,9 @@ public class SimpleBrokerMessageHandlerTests {
assertEquals(sess1, SimpMessageHeaderAccessor.getSessionId(captured.getHeaders()));
assertEquals("joe", SimpMessageHeaderAccessor.getUser(captured.getHeaders()).getName());
assertCapturedMessage(sess2, "sub1", "/foo");
assertCapturedMessage(sess2, "sub2", "/foo");
assertCapturedMessage(sess2, "sub3", "/bar");
assertTrue(messageCaptured(sess2, "sub1", "/foo"));
assertTrue(messageCaptured(sess2, "sub2", "/foo"));
assertTrue(messageCaptured(sess2, "sub3", "/bar"));
}
@Test
@@ -149,7 +150,7 @@ public class SimpleBrokerMessageHandlerTests {
}
protected Message<String> createSubscriptionMessage(String sessionId, String subcriptionId, String destination) {
private Message<String> createSubscriptionMessage(String sessionId, String subcriptionId, String destination) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.SUBSCRIBE);
headers.setSubscriptionId(subcriptionId);
headers.setDestination(destination);
@@ -157,20 +158,20 @@ public class SimpleBrokerMessageHandlerTests {
return MessageBuilder.createMessage("", headers.getMessageHeaders());
}
protected Message<String> createConnectMessage(String sessionId) {
private Message<String> createConnectMessage(String sessionId) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
headers.setSessionId(sessionId);
headers.setUser(new TestPrincipal("joe"));
return MessageBuilder.createMessage("", headers.getMessageHeaders());
}
protected Message<String> createMessage(String destination, String payload) {
private Message<String> createMessage(String destination, String payload) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headers.setDestination(destination);
return MessageBuilder.createMessage("", headers.getMessageHeaders());
}
protected boolean assertCapturedMessage(String sessionId, String subcriptionId, String destination) {
private boolean messageCaptured(String sessionId, String subcriptionId, String destination) {
for (Message<?> message : this.messageCaptor.getAllValues()) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(message);
if (sessionId.equals(headers.getSessionId())) {
@@ -68,35 +68,47 @@ public class MergedContextConfiguration implements Serializable {
private static final long serialVersionUID = -3290560718464957422L;
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES = //
Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet();
private static final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> EMPTY_INITIALIZER_CLASSES =
Collections.<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> emptySet();
private final Class<?> testClass;
private final String[] locations;
private final Class<?>[] classes;
private final Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses;
private final String[] activeProfiles;
private final String[] propertySourceLocations;
private final String[] propertySourceProperties;
private final ContextLoader contextLoader;
private final CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate;
private final MergedContextConfiguration parent;
private static String[] processStrings(String[] array) {
return array == null ? EMPTY_STRING_ARRAY : array;
return (array != null ? array : EMPTY_STRING_ARRAY);
}
private static Class<?>[] processClasses(Class<?>[] classes) {
return classes == null ? EMPTY_CLASS_ARRAY : classes;
return (classes != null ? classes : EMPTY_CLASS_ARRAY);
}
private static Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> processContextInitializerClasses(
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses) {
return contextInitializerClasses == null ? EMPTY_INITIALIZER_CLASSES
: Collections.unmodifiableSet(contextInitializerClasses);
return (contextInitializerClasses != null ?
Collections.unmodifiableSet(contextInitializerClasses) : EMPTY_INITIALIZER_CLASSES);
}
private static String[] processActiveProfiles(String[] activeProfiles) {
@@ -117,15 +129,15 @@ public class MergedContextConfiguration implements Serializable {
* loader or &quot;null&quot; if the supplied loaded is {@code null}.
*/
protected static String nullSafeToString(ContextLoader contextLoader) {
return contextLoader == null ? "null" : contextLoader.getClass().getName();
return (contextLoader != null ? contextLoader.getClass().getName() : "null");
}
/**
* Create a new {@code MergedContextConfiguration} instance for the
* supplied parameters.
* <p>Delegates to
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
@@ -134,6 +146,7 @@ public class MergedContextConfiguration implements Serializable {
*/
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
String[] activeProfiles, ContextLoader contextLoader) {
this(testClass, locations, classes, null, activeProfiles, contextLoader);
}
@@ -142,7 +155,6 @@ public class MergedContextConfiguration implements Serializable {
* supplied parameters.
* <p>Delegates to
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
@@ -151,12 +163,10 @@ public class MergedContextConfiguration implements Serializable {
* @param contextLoader the resolved {@code ContextLoader}
* @see #MergedContextConfiguration(Class, String[], Class[], Set, String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)
*/
public MergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, ContextLoader contextLoader) {
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, contextLoader, null, null);
}
@@ -165,7 +175,6 @@ public class MergedContextConfiguration implements Serializable {
* supplied parameters.
* <p>Delegates to
* {@link #MergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
@@ -177,13 +186,11 @@ public class MergedContextConfiguration implements Serializable {
* @param parent the parent configuration or {@code null} if there is no parent
* @since 3.2.2
*/
public MergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
this(testClass, locations, classes, contextInitializerClasses, activeProfiles, null, null, contextLoader,
cacheAwareContextLoaderDelegate, parent);
}
@@ -203,7 +210,6 @@ public class MergedContextConfiguration implements Serializable {
/**
* Create a new {@code MergedContextConfiguration} instance for the
* supplied parameters.
*
* <p>If a {@code null} value is supplied for {@code locations},
* {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
* or {@code propertySourceProperties} an empty array will be stored instead.
@@ -211,7 +217,6 @@ public class MergedContextConfiguration implements Serializable {
* {@code contextInitializerClasses} an empty set will be stored instead.
* Furthermore, active profiles will be sorted, and duplicate profiles
* will be removed.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged context resource locations
* @param classes the merged annotated classes
@@ -225,14 +230,12 @@ public class MergedContextConfiguration implements Serializable {
* @param parent the parent configuration or {@code null} if there is no parent
* @since 4.1
*/
public MergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
public MergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
ContextLoader contextLoader, CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate,
MergedContextConfiguration parent) {
this.testClass = testClass;
this.locations = processStrings(locations);
this.classes = processClasses(classes);
@@ -245,11 +248,13 @@ public class MergedContextConfiguration implements Serializable {
this.parent = parent;
}
/**
* Get the {@linkplain Class test class} associated with this {@code MergedContextConfiguration}.
* Get the {@linkplain Class test class} associated with this
* {@code MergedContextConfiguration}.
*/
public Class<?> getTestClass() {
return testClass;
return this.testClass;
}
/**
@@ -259,20 +264,19 @@ public class MergedContextConfiguration implements Serializable {
* files or Groovy scripts.
*/
public String[] getLocations() {
return locations;
return this.locations;
}
/**
* Get the merged annotated classes for the {@linkplain #getTestClass() test class}.
*/
public Class<?>[] getClasses() {
return classes;
return this.classes;
}
/**
* Determine if this {@code MergedContextConfiguration} instance has
* path-based context resource locations.
*
* @return {@code true} if the {@link #getLocations() locations} array is not empty
* @since 4.0.4
* @see #hasResources()
@@ -285,7 +289,6 @@ public class MergedContextConfiguration implements Serializable {
/**
* Determine if this {@code MergedContextConfiguration} instance has
* class-based resources.
*
* @return {@code true} if the {@link #getClasses() classes} array is not empty
* @since 4.0.4
* @see #hasResources()
@@ -298,7 +301,6 @@ public class MergedContextConfiguration implements Serializable {
/**
* Determine if this {@code MergedContextConfiguration} instance has
* either path-based context resource locations or class-based resources.
*
* @return {@code true} if either the {@link #getLocations() locations}
* or the {@link #getClasses() classes} array is not empty
* @since 4.0.4
@@ -306,7 +308,7 @@ public class MergedContextConfiguration implements Serializable {
* @see #hasClasses()
*/
public boolean hasResources() {
return hasLocations() || hasClasses();
return (hasLocations() || hasClasses());
}
/**
@@ -314,7 +316,7 @@ public class MergedContextConfiguration implements Serializable {
* {@linkplain #getTestClass() test class}.
*/
public Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> getContextInitializerClasses() {
return contextInitializerClasses;
return this.contextInitializerClasses;
}
/**
@@ -323,17 +325,17 @@ public class MergedContextConfiguration implements Serializable {
* @see ActiveProfiles
*/
public String[] getActiveProfiles() {
return activeProfiles;
return this.activeProfiles;
}
/**
* Get the merged resource locations for test {@code PropertySources} for the
* {@linkplain #getTestClass() test class}.
* Get the merged resource locations for test {@code PropertySources}
* for the {@linkplain #getTestClass() test class}.
* @see TestPropertySource#locations
* @see java.util.Properties
*/
public String[] getPropertySourceLocations() {
return propertySourceLocations;
return this.propertySourceLocations;
}
/**
@@ -345,20 +347,19 @@ public class MergedContextConfiguration implements Serializable {
* @see java.util.Properties
*/
public String[] getPropertySourceProperties() {
return propertySourceProperties;
return this.propertySourceProperties;
}
/**
* Get the resolved {@link ContextLoader} for the {@linkplain #getTestClass() test class}.
*/
public ContextLoader getContextLoader() {
return contextLoader;
return this.contextLoader;
}
/**
* Get the {@link MergedContextConfiguration} for the parent application context in a
* context hierarchy.
*
* Get the {@link MergedContextConfiguration} for the parent application context
* in a context hierarchy.
* @return the parent configuration or {@code null} if there is no parent
* @see #getParentApplicationContext()
* @since 3.2.2
@@ -370,43 +371,21 @@ public class MergedContextConfiguration implements Serializable {
/**
* Get the parent {@link ApplicationContext} for the context defined by this
* {@code MergedContextConfiguration} from the context cache.
* <p>
* If the parent context has not yet been loaded, it will be loaded, stored in the
* cache, and then returned.
*
* <p>If the parent context has not yet been loaded, it will be loaded, stored
* in the cache, and then returned.
* @return the parent {@code ApplicationContext} or {@code null} if there is no parent
* @see #getParent()
* @since 3.2.2
*/
public ApplicationContext getParentApplicationContext() {
if (parent == null) {
if (this.parent == null) {
return null;
}
Assert.state(cacheAwareContextLoaderDelegate != null,
"Cannot retrieve a parent application context without access to the CacheAwareContextLoaderDelegate.");
return cacheAwareContextLoaderDelegate.loadContext(parent);
Assert.state(this.cacheAwareContextLoaderDelegate != null,
"Cannot retrieve a parent application context without access to the CacheAwareContextLoaderDelegate");
return this.cacheAwareContextLoaderDelegate.loadContext(this.parent);
}
/**
* Generate a unique hash code for all properties of this
* {@code MergedContextConfiguration} excluding the
* {@linkplain #getTestClass() test class}.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(locations);
result = prime * result + Arrays.hashCode(classes);
result = prime * result + contextInitializerClasses.hashCode();
result = prime * result + Arrays.hashCode(activeProfiles);
result = prime * result + Arrays.hashCode(propertySourceLocations);
result = prime * result + Arrays.hashCode(propertySourceProperties);
result = prime * result + (parent == null ? 0 : parent.hashCode());
result = prime * result + nullSafeToString(contextLoader).hashCode();
return result;
}
/**
* Determine if the supplied object is equal to this {@code MergedContextConfiguration}
@@ -420,57 +399,68 @@ public class MergedContextConfiguration implements Serializable {
* {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(obj instanceof MergedContextConfiguration)) {
if (!(other instanceof MergedContextConfiguration)) {
return false;
}
final MergedContextConfiguration that = (MergedContextConfiguration) obj;
if (!Arrays.equals(this.locations, that.locations)) {
MergedContextConfiguration otherConfig = (MergedContextConfiguration) other;
if (!Arrays.equals(this.locations, otherConfig.locations)) {
return false;
}
if (!Arrays.equals(this.classes, that.classes)) {
if (!Arrays.equals(this.classes, otherConfig.classes)) {
return false;
}
if (!this.contextInitializerClasses.equals(that.contextInitializerClasses)) {
if (!this.contextInitializerClasses.equals(otherConfig.contextInitializerClasses)) {
return false;
}
if (!Arrays.equals(this.activeProfiles, that.activeProfiles)) {
if (!Arrays.equals(this.activeProfiles, otherConfig.activeProfiles)) {
return false;
}
if (!Arrays.equals(this.propertySourceLocations, that.propertySourceLocations)) {
if (!Arrays.equals(this.propertySourceLocations, otherConfig.propertySourceLocations)) {
return false;
}
if (!Arrays.equals(this.propertySourceProperties, that.propertySourceProperties)) {
if (!Arrays.equals(this.propertySourceProperties, otherConfig.propertySourceProperties)) {
return false;
}
if (this.parent == null) {
if (that.parent != null) {
if (otherConfig.parent != null) {
return false;
}
}
else if (!this.parent.equals(that.parent)) {
else if (!this.parent.equals(otherConfig.parent)) {
return false;
}
if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(that.contextLoader))) {
if (!nullSafeToString(this.contextLoader).equals(nullSafeToString(otherConfig.contextLoader))) {
return false;
}
return true;
}
/**
* Generate a unique hash code for all properties of this
* {@code MergedContextConfiguration} excluding the
* {@linkplain #getTestClass() test class}.
*/
@Override
public int hashCode() {
int result = Arrays.hashCode(this.locations);
result = 31 * result + Arrays.hashCode(this.classes);
result = 31 * result + this.contextInitializerClasses.hashCode();
result = 31 * result + Arrays.hashCode(this.activeProfiles);
result = 31 * result + Arrays.hashCode(this.propertySourceLocations);
result = 31 * result + Arrays.hashCode(this.propertySourceProperties);
result = 31 * result + (this.parent != null ? this.parent.hashCode() : 0);
result = 31 * result + nullSafeToString(this.contextLoader).hashCode();
return result;
}
/**
* Provide a String representation of the {@linkplain #getTestClass() test class},
* {@linkplain #getLocations() locations}, {@linkplain #getClasses() annotated classes},
@@ -483,17 +473,17 @@ public class MergedContextConfiguration implements Serializable {
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("testClass", testClass)//
.append("locations", ObjectUtils.nullSafeToString(locations))//
.append("classes", ObjectUtils.nullSafeToString(classes))//
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(contextInitializerClasses))//
.append("activeProfiles", ObjectUtils.nullSafeToString(activeProfiles))//
.append("propertySourceLocations", ObjectUtils.nullSafeToString(propertySourceLocations))//
.append("propertySourceProperties", ObjectUtils.nullSafeToString(propertySourceProperties))//
.append("contextLoader", nullSafeToString(contextLoader))//
.append("parent", parent)//
.toString();
return new ToStringCreator(this)
.append("testClass", this.testClass)
.append("locations", ObjectUtils.nullSafeToString(this.locations))
.append("classes", ObjectUtils.nullSafeToString(this.classes))
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(this.contextInitializerClasses))
.append("activeProfiles", ObjectUtils.nullSafeToString(this.activeProfiles))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(this.propertySourceLocations))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(this.propertySourceProperties))
.append("contextLoader", nullSafeToString(this.contextLoader))
.append("parent", this.parent)
.toString();
}
}
@@ -121,7 +121,10 @@ public @interface TestPropertySource {
* {@code http:}, etc.) will be loaded using the specified resource protocol.
* Resource location wildcards (e.g. <code>*&#42;/*.properties</code>)
* are not permitted: each location must evaluate to exactly one
* {@code .properties} or {@code .xml} resource.
* {@code .properties} or {@code .xml} resource. Property placeholders
* in paths (i.e., <code>${...}</code>) will be
* {@linkplain org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
*
* <h3>Default Properties File Detection</h3>
* <p>See the class-level Javadoc for a discussion on detection of defaults.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -214,7 +214,14 @@ public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner {
notifier.fireTestIgnored(description);
}
else {
runLeaf(methodBlock(frameworkMethod), description, notifier);
Statement statement;
try {
statement = methodBlock(frameworkMethod);
}
catch (Throwable ex) {
statement = new Fail(ex);
}
runLeaf(statement, description, notifier);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,13 +16,8 @@
package org.springframework.test.context.support;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
@@ -34,12 +29,8 @@ import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextLoader;
import org.springframework.test.context.MergedContextConfiguration;
@@ -64,7 +55,6 @@ import org.springframework.util.ResourceUtils;
*
* @author Sam Brannen
* @author Juergen Hoeller
* @author Dave Syer
* @since 2.5
* @see #generateDefaultLocations
* @see #getResourceSuffixes
@@ -74,8 +64,6 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private static final Log logger = LogFactory.getLog(AbstractContextLoader.class);
@@ -131,80 +119,19 @@ public abstract class AbstractContextLoader implements SmartContextLoader {
* @param context the newly created application context
* @param mergedConfig the merged context configuration
* @since 3.2
* @see TestPropertySourceUtils#addPropertiesFilesToEnvironment
* @see TestPropertySourceUtils#addInlinedPropertiesToEnvironment
* @see ApplicationContextInitializer#initialize(ConfigurableApplicationContext)
* @see #loadContext(MergedContextConfiguration)
* @see ConfigurableApplicationContext#setId
*/
protected void prepareContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles());
addResourcePropertySourcesToEnvironment(context, mergedConfig);
addInlinedPropertiesToEnvironment(context, mergedConfig);
TestPropertySourceUtils.addPropertiesFilesToEnvironment(context, mergedConfig.getPropertySourceLocations());
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(context, mergedConfig.getPropertySourceProperties());
invokeApplicationContextInitializers(context, mergedConfig);
}
/**
* @since 4.1
*/
private void addResourcePropertySourcesToEnvironment(ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
try {
ConfigurableEnvironment environment = context.getEnvironment();
String[] locations = mergedConfig.getPropertySourceLocations();
for (String location : locations) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
Resource resource = context.getResource(resolvedLocation);
ResourcePropertySource ps = new ResourcePropertySource(resource);
environment.getPropertySources().addFirst(ps);
}
}
catch (IOException e) {
throw new IllegalStateException("Failed to add PropertySource to Environment", e);
}
}
/**
* @since 4.1
*/
private void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
String[] keyValuePairs = mergedConfig.getPropertySourceProperties();
if (!ObjectUtils.isEmpty(keyValuePairs)) {
String name = "test properties " + ObjectUtils.nullSafeToString(keyValuePairs);
MapPropertySource ps = new MapPropertySource(name, extractEnvironmentProperties(keyValuePairs));
context.getEnvironment().getPropertySources().addFirst(ps);
}
}
/**
* Extract environment properties from the supplied key/value pairs.
* <p>Parsing of the key/value pairs is achieved by converting all pairs
* into a single <em>virtual</em> properties file in memory and delegating
* to {@link Properties#load(java.io.Reader)} to parse that virtual file.
* <p>This code has been adapted from Spring Boot's
* {@link org.springframework.boot.test.SpringApplicationContextLoader SpringApplicationContextLoader}.
* @since 4.1
*/
private Map<String, Object> extractEnvironmentProperties(String[] keyValuePairs) {
StringBuilder sb = new StringBuilder();
for (String keyValuePair : keyValuePairs) {
sb.append(keyValuePair).append(LINE_SEPARATOR);
}
String content = sb.toString();
Properties props = new Properties();
try {
props.load(new StringReader(content));
}
catch (IOException e) {
throw new IllegalStateException("Failed to load test environment properties from: " + content, e);
}
Map<String, Object> properties = new HashMap<String, Object>();
for (String name : props.stringPropertyNames()) {
properties.put(name, props.getProperty(name));
}
return properties;
}
@SuppressWarnings("unchecked")
private void invokeApplicationContextInitializers(ConfigurableApplicationContext context,
MergedContextConfiguration mergedConfig) {
@@ -24,6 +24,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.test.context.SmartContextLoader;
import org.springframework.util.Assert;
@@ -95,13 +96,14 @@ public abstract class AnnotationConfigContextLoaderUtils {
* <li>must not be {@code private}</li>
* <li>must not be {@code final}</li>
* <li>must be {@code static}</li>
* <li>must be annotated with {@code @Configuration}</li>
* <li>must be annotated or meta-annotated with {@code @Configuration}</li>
* </ul>
* @param clazz the class to check
* @return {@code true} if the supplied class meets the candidate criteria
*/
private static boolean isDefaultConfigurationClassCandidate(Class<?> clazz) {
return (clazz != null && isStaticNonPrivateAndNonFinal(clazz) && clazz.isAnnotationPresent(Configuration.class));
return (clazz != null && isStaticNonPrivateAndNonFinal(clazz) &&
(AnnotationUtils.findAnnotation(clazz, Configuration.class) != null));
}
private static boolean isStaticNonPrivateAndNonFinal(Class<?> clazz) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -73,7 +73,6 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
}
}
else {
Class<?> rootDeclaringClass = descriptor.getRootDeclaringClass();
Class<?> declaringClass = descriptor.getDeclaringClass();
AnnotationAttributes annAttrs = descriptor.getAnnotationAttributes();
@@ -82,15 +81,6 @@ public class DefaultActiveProfilesResolver implements ActiveProfilesResolver {
annAttrs, declaringClass.getName()));
}
Class<? extends ActiveProfilesResolver> resolverClass = annAttrs.getClass("resolver");
if (!ActiveProfilesResolver.class.equals(resolverClass)) {
String msg = String.format("Configuration error for test class [%s]: %s cannot be used "
+ "in conjunction with custom resolver [%s].", rootDeclaringClass.getName(),
getClass().getSimpleName(), resolverClass.getName());
logger.error(msg);
throw new IllegalStateException(msg);
}
String[] profiles = annAttrs.getStringArray("profiles");
String[] valueProfiles = annAttrs.getStringArray("value");
boolean valueDeclared = !ObjectUtils.isEmpty(valueProfiles);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,33 +16,57 @@
package org.springframework.test.context.support;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.PropertySources;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePropertySource;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.util.TestContextResourceUtils;
import org.springframework.test.util.MetaAnnotationUtils.*;
import org.springframework.test.util.MetaAnnotationUtils.AnnotationDescriptor;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import static org.springframework.test.util.MetaAnnotationUtils.*;
/**
* Utility methods for working with {@link TestPropertySource @TestPropertySource}.
* Utility methods for working with {@link TestPropertySource @TestPropertySource}
* and adding test {@link PropertySource PropertySources} to the {@code Environment}.
*
* <p>Primarily intended for use within the framework.
*
* @author Sam Brannen
* @since 4.1
* @see TestPropertySource
*/
abstract class TestPropertySourceUtils {
public abstract class TestPropertySourceUtils {
private static final Log logger = LogFactory.getLog(TestPropertySourceUtils.class);
/**
* The name of the {@link MapPropertySource} created from <em>inlined properties</em>.
* @since 4.1.5
* @see {@link #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])}
*/
public static final String INLINED_PROPERTIES_PROPERTY_SOURCE_NAME = "Inlined Test Properties";
private TestPropertySourceUtils() {
/* no-op */
@@ -131,4 +155,133 @@ abstract class TestPropertySourceUtils {
return StringUtils.toStringArray(properties);
}
/**
* Add the {@link Properties} files from the given resource {@code locations}
* to the {@link Environment} of the supplied {@code context}.
* <p>Property placeholders in resource locations (i.e., <code>${...}</code>)
* will be {@linkplain Environment#resolveRequiredPlaceholders(String) resolved}
* against the {@code Environment}.
* <p>Each properties file will be converted to a {@link ResourcePropertySource}
* that will be added to the {@link PropertySources} of the environment with
* highest precedence.
* @param context the application context whose environment should be updated;
* never {@code null}
* @param locations the resource locations of {@code Properties} files to add
* to the environment; potentially empty but never {@code null}
* @since 4.1.5
* @see ResourcePropertySource
* @see TestPropertySource#locations
* @throws IllegalStateException if an error occurs while processing a properties file
*/
public static void addPropertiesFilesToEnvironment(ConfigurableApplicationContext context,
String[] locations) {
Assert.notNull(context, "context must not be null");
Assert.notNull(locations, "locations must not be null");
try {
ConfigurableEnvironment environment = context.getEnvironment();
for (String location : locations) {
String resolvedLocation = environment.resolveRequiredPlaceholders(location);
Resource resource = context.getResource(resolvedLocation);
environment.getPropertySources().addFirst(new ResourcePropertySource(resource));
}
}
catch (IOException e) {
throw new IllegalStateException("Failed to add PropertySource to Environment", e);
}
}
/**
* Add the given <em>inlined properties</em> to the {@link Environment} of the
* supplied {@code context}.
* <p>This method simply delegates to
* {@link #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])}.
* @param context the application context whose environment should be updated;
* never {@code null}
* @param inlinedProperties the inlined properties to add to the environment;
* potentially empty but never {@code null}
* @since 4.1.5
* @see TestPropertySource#properties
* @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])
*/
public static void addInlinedPropertiesToEnvironment(ConfigurableApplicationContext context,
String[] inlinedProperties) {
Assert.notNull(context, "context must not be null");
Assert.notNull(inlinedProperties, "inlinedProperties must not be null");
addInlinedPropertiesToEnvironment(context.getEnvironment(), inlinedProperties);
}
/**
* Add the given <em>inlined properties</em> (in the form of <em>key-value</em>
* pairs) to the supplied {@link ConfigurableEnvironment environment}.
* <p>All key-value pairs will be added to the {@code Environment} as a
* single {@link MapPropertySource} with the highest precedence.
* <p>For details on the parsing of <em>inlined properties</em>, consult the
* Javadoc for {@link #convertInlinedPropertiesToMap}.
* @param environment the environment to update; never {@code null}
* @param inlinedProperties the inlined properties to add to the environment;
* potentially empty but never {@code null}
* @since 4.1.5
* @see MapPropertySource
* @see #INLINED_PROPERTIES_PROPERTY_SOURCE_NAME
* @see TestPropertySource#properties
* @see #convertInlinedPropertiesToMap
*/
public static void addInlinedPropertiesToEnvironment(ConfigurableEnvironment environment, String[] inlinedProperties) {
Assert.notNull(environment, "environment must not be null");
Assert.notNull(inlinedProperties, "inlinedProperties must not be null");
if (!ObjectUtils.isEmpty(inlinedProperties)) {
if (logger.isDebugEnabled()) {
logger.debug("Adding inlined properties to environment: "
+ ObjectUtils.nullSafeToString(inlinedProperties));
}
MapPropertySource ps = new MapPropertySource(INLINED_PROPERTIES_PROPERTY_SOURCE_NAME,
convertInlinedPropertiesToMap(inlinedProperties));
environment.getPropertySources().addFirst(ps);
}
}
/**
* Convert the supplied <em>inlined properties</em> (in the form of <em>key-value</em>
* pairs) into a map keyed by property name, preserving the ordering of property names
* in the returned map.
* <p>Parsing of the key-value pairs is achieved by converting all pairs
* into <em>virtual</em> properties files in memory and delegating to
* {@link Properties#load(java.io.Reader)} to parse each virtual file.
* <p>For a full discussion of <em>inlined properties</em>, consult the Javadoc
* for {@link TestPropertySource#properties}.
* @param inlinedProperties the inlined properties to convert; potentially empty
* but never {@code null}
* @return a new, ordered map containing the converted properties
* @since 4.1.5
* @throws IllegalStateException if a given key-value pair cannot be parsed, or if
* a given inlined property contains multiple key-value pairs
* @see #addInlinedPropertiesToEnvironment(ConfigurableEnvironment, String[])
*/
public static Map<String, Object> convertInlinedPropertiesToMap(String[] inlinedProperties) {
Assert.notNull(inlinedProperties, "inlinedProperties must not be null");
Map<String, Object> map = new LinkedHashMap<String, Object>();
Properties props = new Properties();
for (String pair : inlinedProperties) {
if (!StringUtils.hasText(pair)) {
continue;
}
try {
props.load(new StringReader(pair));
}
catch (Exception e) {
throw new IllegalStateException("Failed to load test environment property from [" + pair + "].", e);
}
Assert.state(props.size() == 1, "Failed to load exactly one test environment property from [" + pair + "].");
for (String name : props.stringPropertyNames()) {
map.put(name, props.getProperty(name));
}
props.clear();
}
return map;
}
}
@@ -63,7 +63,6 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* supplied parameters.
* <p>Delegates to
* {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged resource locations
* @param classes the merged annotated classes
@@ -76,10 +75,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)} instead.
*/
@Deprecated
public WebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
public WebMergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader) {
@@ -92,7 +88,6 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* supplied parameters.
* <p>Delegates to
* {@link #WebMergedContextConfiguration(Class, String[], Class[], Set, String[], String[], String[], String, ContextLoader, CacheAwareContextLoaderDelegate, MergedContextConfiguration)}.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged resource locations
* @param classes the merged annotated classes
@@ -109,10 +104,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* instead.
*/
@Deprecated
public WebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
public WebMergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String resourceBasePath, ContextLoader contextLoader,
CacheAwareContextLoaderDelegate cacheAwareContextLoaderDelegate, MergedContextConfiguration parent) {
@@ -137,7 +129,6 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
/**
* Create a new {@code WebMergedContextConfiguration} instance for the
* supplied parameters.
*
* <p>If a {@code null} value is supplied for {@code locations},
* {@code classes}, {@code activeProfiles}, {@code propertySourceLocations},
* or {@code propertySourceProperties} an empty array will be stored instead.
@@ -146,7 +137,6 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* If an <em>empty</em> value is supplied for the {@code resourceBasePath}
* an empty string will be used. Furthermore, active profiles will be sorted,
* and duplicate profiles will be removed.
*
* @param testClass the test class for which the configuration was merged
* @param locations the merged resource locations
* @param classes the merged annotated classes
@@ -161,10 +151,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* @param parent the parent configuration or {@code null} if there is no parent
* @since 4.1
*/
public WebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
public WebMergedContextConfiguration(Class<?> testClass, String[] locations, Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> contextInitializerClasses,
String[] activeProfiles, String[] propertySourceLocations, String[] propertySourceProperties,
String resourceBasePath, ContextLoader contextLoader,
@@ -176,6 +163,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
}
/**
* Get the resource path to the root directory of the web application for the
* {@linkplain #getTestClass() test class}, configured via {@code @WebAppConfiguration}.
@@ -185,18 +173,6 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
return this.resourceBasePath;
}
/**
* Generate a unique hash code for all properties of this
* {@code WebMergedContextConfiguration} excluding the
* {@linkplain #getTestClass() test class}.
*/
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + resourceBasePath.hashCode();
return result;
}
/**
* Determine if the supplied object is equal to this {@code WebMergedContextConfiguration}
@@ -209,18 +185,25 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
* {@link #getContextLoader() ContextLoaders}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(obj instanceof WebMergedContextConfiguration)) {
if (!(other instanceof WebMergedContextConfiguration)) {
return false;
}
WebMergedContextConfiguration otherConfig = (WebMergedContextConfiguration) other;
return super.equals(otherConfig) && this.getResourceBasePath().equals(otherConfig.getResourceBasePath());
}
final WebMergedContextConfiguration that = (WebMergedContextConfiguration) obj;
return super.equals(that) && this.getResourceBasePath().equals(that.getResourceBasePath());
/**
* Generate a unique hash code for all properties of this
* {@code WebMergedContextConfiguration} excluding the
* {@linkplain #getTestClass() test class}.
*/
@Override
public int hashCode() {
return 31 * super.hashCode() + this.resourceBasePath.hashCode();
}
/**
@@ -236,18 +219,18 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
*/
@Override
public String toString() {
return new ToStringCreator(this)//
.append("testClass", getTestClass())//
.append("locations", ObjectUtils.nullSafeToString(getLocations()))//
.append("classes", ObjectUtils.nullSafeToString(getClasses()))//
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(getContextInitializerClasses()))//
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))//
.append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations()))//
.append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties()))//
.append("resourceBasePath", getResourceBasePath())//
.append("contextLoader", nullSafeToString(getContextLoader()))//
.append("parent", getParent())//
.toString();
return new ToStringCreator(this)
.append("testClass", getTestClass())
.append("locations", ObjectUtils.nullSafeToString(getLocations()))
.append("classes", ObjectUtils.nullSafeToString(getClasses()))
.append("contextInitializerClasses", ObjectUtils.nullSafeToString(getContextInitializerClasses()))
.append("activeProfiles", ObjectUtils.nullSafeToString(getActiveProfiles()))
.append("propertySourceLocations", ObjectUtils.nullSafeToString(getPropertySourceLocations()))
.append("propertySourceProperties", ObjectUtils.nullSafeToString(getPropertySourceProperties()))
.append("resourceBasePath", getResourceBasePath())
.append("contextLoader", nullSafeToString(getContextLoader()))
.append("parent", getParent())
.toString();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,8 +41,10 @@ import static org.junit.Assert.*;
public class MergedContextConfigurationTests {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];
private final GenericXmlContextLoader loader = new GenericXmlContextLoader();
@@ -50,7 +52,6 @@ public class MergedContextConfigurationTests {
public void hashCodeWithNulls() {
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(null, null, null, null, null);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(null, null, null, null, null);
assertTrue(mergedConfig1.hashCode() > 0);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -63,19 +64,19 @@ public class MergedContextConfigurationTests {
@Test
public void hashCodeWithEmptyArrays() {
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
public void hashCodeWithEmptyArraysAndDifferentLoaders() {
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -83,9 +84,9 @@ public class MergedContextConfigurationTests {
public void hashCodeWithSameLocations() {
String[] locations = new String[] { "foo", "bar}" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -94,19 +95,19 @@ public class MergedContextConfigurationTests {
String[] locations1 = new String[] { "foo", "bar}" };
String[] locations2 = new String[] { "baz", "quux}" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations1,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations2,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
public void hashCodeWithSameConfigClasses() {
Class<?>[] classes = new Class<?>[] { String.class, Integer.class };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -114,20 +115,20 @@ public class MergedContextConfigurationTests {
public void hashCodeWithDifferentConfigClasses() {
Class<?>[] classes1 = new Class<?>[] { String.class, Integer.class };
Class<?>[] classes2 = new Class<?>[] { Boolean.class, Number.class };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes2, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes2, EMPTY_STRING_ARRAY, loader);
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
public void hashCodeWithSameProfiles() {
String[] activeProfiles = new String[] { "catbert", "dogbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -135,10 +136,10 @@ public class MergedContextConfigurationTests {
public void hashCodeWithSameProfilesReversed() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "dogbert", "catbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -146,10 +147,10 @@ public class MergedContextConfigurationTests {
public void hashCodeWithSameDuplicateProfiles() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "catbert", "dogbert", "catbert", "dogbert", "catbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -157,46 +158,46 @@ public class MergedContextConfigurationTests {
public void hashCodeWithDifferentProfiles() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "X", "Y" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
public void hashCodeWithSameInitializers() {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses1.add(FooInitializer.class);
initializerClasses1.add(BarInitializer.class);
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses2.add(BarInitializer.class);
initializerClasses2.add(FooInitializer.class);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@Test
public void hashCodeWithDifferentInitializers() {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses1.add(FooInitializer.class);
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses2.add(BarInitializer.class);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -206,12 +207,12 @@ public class MergedContextConfigurationTests {
@Test
public void hashCodeWithSameParent() {
MergedContextConfiguration parent = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent);
assertEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -221,14 +222,14 @@ public class MergedContextConfigurationTests {
@Test
public void hashCodeWithDifferentParents() {
MergedContextConfiguration parent1 = new MergedContextConfiguration(getClass(), new String[] { "foo", "bar}" },
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration parent2 = new MergedContextConfiguration(getClass(), new String[] { "baz", "quux" },
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent1);
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent1);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent2);
EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, loader, null, parent2);
assertNotEquals(mergedConfig1.hashCode(), mergedConfig2.hashCode());
}
@@ -237,7 +238,7 @@ public class MergedContextConfigurationTests {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(null, null, null, null, null);
assertEquals(mergedConfig, mergedConfig);
assertNotEquals(mergedConfig, null);
assertNotEquals(mergedConfig, new Integer(1));
assertNotEquals(mergedConfig, 1);
}
@Test
@@ -256,19 +257,19 @@ public class MergedContextConfigurationTests {
@Test
public void equalsWithEmptyArrays() {
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@Test
public void equalsWithEmptyArraysAndDifferentLoaders() {
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, new AnnotationConfigContextLoader());
assertNotEquals(mergedConfig1, mergedConfig2);
assertNotEquals(mergedConfig2, mergedConfig1);
}
@@ -276,10 +277,10 @@ public class MergedContextConfigurationTests {
@Test
public void equalsWithSameLocations() {
String[] locations = new String[] { "foo", "bar}" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
locations, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
locations, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@@ -287,10 +288,10 @@ public class MergedContextConfigurationTests {
public void equalsWithDifferentLocations() {
String[] locations1 = new String[] { "foo", "bar}" };
String[] locations2 = new String[] { "baz", "quux}" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), locations1,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), locations2,
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
locations1, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
locations2, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertNotEquals(mergedConfig1, mergedConfig2);
assertNotEquals(mergedConfig2, mergedConfig1);
}
@@ -298,10 +299,10 @@ public class MergedContextConfigurationTests {
@Test
public void equalsWithSameConfigClasses() {
Class<?>[] classes = new Class<?>[] { String.class, Integer.class };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@@ -309,10 +310,10 @@ public class MergedContextConfigurationTests {
public void equalsWithDifferentConfigClasses() {
Class<?>[] classes1 = new Class<?>[] { String.class, Integer.class };
Class<?>[] classes2 = new Class<?>[] { Boolean.class, Number.class };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
classes2, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, classes2, EMPTY_STRING_ARRAY, loader);
assertNotEquals(mergedConfig1, mergedConfig2);
assertNotEquals(mergedConfig2, mergedConfig1);
}
@@ -320,10 +321,10 @@ public class MergedContextConfigurationTests {
@Test
public void equalsWithSameProfiles() {
String[] activeProfiles = new String[] { "catbert", "dogbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@@ -331,10 +332,10 @@ public class MergedContextConfigurationTests {
public void equalsWithSameProfilesReversed() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "dogbert", "catbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@@ -342,10 +343,10 @@ public class MergedContextConfigurationTests {
public void equalsWithSameDuplicateProfiles() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "catbert", "dogbert", "catbert", "dogbert", "catbert" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@@ -353,47 +354,47 @@ public class MergedContextConfigurationTests {
public void equalsWithDifferentProfiles() {
String[] activeProfiles1 = new String[] { "catbert", "dogbert" };
String[] activeProfiles2 = new String[] { "X", "Y" };
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, activeProfiles2, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles1, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, activeProfiles2, loader);
assertNotEquals(mergedConfig1, mergedConfig2);
assertNotEquals(mergedConfig2, mergedConfig1);
}
@Test
public void equalsWithSameInitializers() {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses1.add(FooInitializer.class);
initializerClasses1.add(BarInitializer.class);
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses2.add(BarInitializer.class);
initializerClasses2.add(FooInitializer.class);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
assertEquals(mergedConfig1, mergedConfig2);
}
@Test
public void equalsWithDifferentInitializers() {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses1 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses1.add(FooInitializer.class);
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 = //
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses2 =
new HashSet<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>>();
initializerClasses2.add(BarInitializer.class);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig1 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses1, EMPTY_STRING_ARRAY, loader);
MergedContextConfiguration mergedConfig2 = new MergedContextConfiguration(getClass(),
EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, initializerClasses2, EMPTY_STRING_ARRAY, loader);
assertNotEquals(mergedConfig1, mergedConfig2);
assertNotEquals(mergedConfig2, mergedConfig1);
}
@@ -440,6 +441,7 @@ public class MergedContextConfigurationTests {
}
}
private static class BarInitializer implements ApplicationContextInitializer<GenericApplicationContext> {
@Override
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,33 +21,60 @@ import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.test.context.support.TestPropertySourceUtils.*;
/**
* Integration tests for {@link TestPropertySource @TestPropertySource}
* support with an inlined properties.
* Integration tests for {@link TestPropertySource @TestPropertySource} support with
* inlined properties.
*
* @author Sam Brannen
* @since 4.1
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TestPropertySource(properties = { "foo = bar", "enigma: 42" })
@TestPropertySource(properties = { "", "foo = bar", "baz quux", "enigma: 42", "x.y.z = a=b=c",
"server.url = http://example.com", "key.value.1: key=value", "key.value.2 key=value", "key.value.3 key:value" })
public class InlinedPropertiesTestPropertySourceTests {
@Autowired
protected Environment env;
private ConfigurableEnvironment env;
private String property(String key) {
return env.getProperty(key);
}
@Test
public void verifyPropertiesAreAvailableInEnvironment() {
assertEquals("bar", env.getProperty("foo"));
assertEquals(42, env.getProperty("enigma", Integer.class).intValue());
public void propertiesAreAvailableInEnvironment() {
// Simple key/value pairs
assertThat(property("foo"), is("bar"));
assertThat(property("baz"), is("quux"));
assertThat(property("enigma"), is("42"));
// Values containing key/value delimiters (":", "=", " ")
assertThat(property("x.y.z"), is("a=b=c"));
assertThat(property("server.url"), is("http://example.com"));
assertThat(property("key.value.1"), is("key=value"));
assertThat(property("key.value.2"), is("key=value"));
assertThat(property("key.value.3"), is("key:value"));
}
@Test
@SuppressWarnings("rawtypes")
public void propertyNameOrderingIsPreservedInEnvironment() {
final String[] expectedPropertyNames = new String[] { "foo", "baz", "enigma", "x.y.z", "server.url",
"key.value.1", "key.value.2", "key.value.3" };
EnumerablePropertySource eps = (EnumerablePropertySource) env.getPropertySources().get(
INLINED_PROPERTIES_PROPERTY_SOURCE_NAME);
assertArrayEquals(expectedPropertyNames, eps.getPropertyNames());
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,14 +20,17 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ActiveProfilesResolver;
import org.springframework.util.StringUtils;
import static org.junit.Assert.*;
import static org.springframework.test.context.support.ActiveProfilesUtils.*;
@@ -211,7 +214,6 @@ public class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsT
/**
* This test verifies that the actual test class, not the composed annotation,
* is passed to the resolver.
*
* @since 4.0.3
*/
@Test
@@ -220,6 +222,24 @@ public class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsT
assertResolvedProfiles(testClass, testClass.getSimpleName());
}
/**
* This test verifies that {@link DefaultActiveProfilesResolver} can be declared explicitly.
* @since 4.1.5
*/
@Test
public void resolveActiveProfilesWithDefaultActiveProfilesResolver() {
assertResolvedProfiles(DefaultActiveProfilesResolverTestCase.class, "default");
}
/**
* This test verifies that {@link DefaultActiveProfilesResolver} can be extended.
* @since 4.1.5
*/
@Test
public void resolveActiveProfilesWithExtendedDefaultActiveProfilesResolver() {
assertResolvedProfiles(ExtendedDefaultActiveProfilesResolverTestCase.class, "default", "foo");
}
// -------------------------------------------------------------------------
@@ -294,6 +314,14 @@ public class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsT
private static class TestClassVerifyingActiveProfilesResolverTestCase {
}
@ActiveProfiles(profiles = "default", resolver = DefaultActiveProfilesResolver.class)
private static class DefaultActiveProfilesResolverTestCase {
}
@ActiveProfiles(profiles = "default", resolver = ExtendedDefaultActiveProfilesResolver.class)
private static class ExtendedDefaultActiveProfilesResolverTestCase {
}
@ActiveProfiles(profiles = "conflict 1", value = "conflict 2")
private static class ConflictingProfilesAndValueTestCase {
}
@@ -343,4 +371,14 @@ public class ActiveProfilesUtilsTests extends AbstractContextConfigurationUtilsT
}
}
private static class ExtendedDefaultActiveProfilesResolver extends DefaultActiveProfilesResolver {
@Override
public String[] resolve(Class<?> testClass) {
List<String> profiles = new ArrayList<String>(Arrays.asList(super.resolve(testClass)));
profiles.add("foo");
return StringUtils.toStringArray(profiles);
}
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2002-2015 the original author 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.context.support;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.Test;
import org.springframework.context.annotation.Configuration;
import static org.junit.Assert.*;
import static org.springframework.test.context.support.AnnotationConfigContextLoaderUtils.*;
/**
* Unit tests for {@link AnnotationConfigContextLoaderUtils}.
*
* @author Sam Brannen
* @since 4.1.5
*/
public class AnnotationConfigContextLoaderUtilsTests {
@Test(expected = IllegalArgumentException.class)
public void detectDefaultConfigurationClassesWithNullDeclaringClass() {
detectDefaultConfigurationClasses(null);
}
@Test
public void detectDefaultConfigurationClassesWithoutConfigurationClass() {
Class<?>[] configClasses = detectDefaultConfigurationClasses(NoConfigTestCase.class);
assertNotNull(configClasses);
assertEquals(0, configClasses.length);
}
@Test
public void detectDefaultConfigurationClassesWithExplicitConfigurationAnnotation() {
Class<?>[] configClasses = detectDefaultConfigurationClasses(ExplicitConfigTestCase.class);
assertNotNull(configClasses);
assertArrayEquals(new Class<?>[] { ExplicitConfigTestCase.Config.class }, configClasses);
}
@Test
public void detectDefaultConfigurationClassesWithConfigurationMetaAnnotation() {
Class<?>[] configClasses = detectDefaultConfigurationClasses(MetaAnnotatedConfigTestCase.class);
assertNotNull(configClasses);
assertArrayEquals(new Class<?>[] { MetaAnnotatedConfigTestCase.Config.class }, configClasses);
}
private static class NoConfigTestCase {
}
private static class ExplicitConfigTestCase {
@Configuration
static class Config {
}
}
@Configuration
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
private static @interface MetaConfig {
}
private static class MetaAnnotatedConfigTestCase {
@MetaConfig
static class Config {
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,14 +16,22 @@
package org.springframework.test.context.support;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.env.MockPropertySource;
import org.springframework.test.context.TestPropertySource;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.springframework.test.context.support.TestPropertySourceUtils.*;
/**
@@ -35,6 +43,7 @@ import static org.springframework.test.context.support.TestPropertySourceUtils.*
public class TestPropertySourceUtilsTests {
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final String[] KEY_VALUE_PAIR = new String[] { "key = value" };
@Rule
public ExpectedException expectedException = ExpectedException.none();
@@ -104,15 +113,96 @@ public class TestPropertySourceUtilsTests {
@Test
public void overriddenProperties() {
assertMergedTestPropertySources(OverriddenPropertiesPropertySources.class, new String[] {
"classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties" }, new String[] { "key = value" });
"classpath:/foo1.xml", "classpath:/foo2.xml", "classpath:/baz.properties" }, KEY_VALUE_PAIR);
}
@Test
public void overriddenLocationsAndProperties() {
assertMergedTestPropertySources(OverriddenLocationsAndPropertiesPropertySources.class,
new String[] { "classpath:/baz.properties" }, new String[] { "key = value" });
new String[] { "classpath:/baz.properties" }, KEY_VALUE_PAIR);
}
/**
* @since 4.1.5
*/
@Test
public void addInlinedPropertiesToEnvironmentWithNullContext() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("context");
addInlinedPropertiesToEnvironment((ConfigurableApplicationContext) null, KEY_VALUE_PAIR);
}
/**
* @since 4.1.5
*/
@Test
public void addInlinedPropertiesToEnvironmentWithContextAndNullInlinedProperties() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("inlined");
addInlinedPropertiesToEnvironment(mock(ConfigurableApplicationContext.class), null);
}
/**
* @since 4.1.5
*/
@Test
public void addInlinedPropertiesToEnvironmentWithNullEnvironment() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("environment");
addInlinedPropertiesToEnvironment((ConfigurableEnvironment) null, KEY_VALUE_PAIR);
}
/**
* @since 4.1.5
*/
@Test
public void addInlinedPropertiesToEnvironmentWithEnvironmentAndNullInlinedProperties() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("inlined");
addInlinedPropertiesToEnvironment(new MockEnvironment(), null);
}
/**
* @since 4.1.5
*/
@Test
public void addInlinedPropertiesToEnvironmentWithMalformedUnicodeInValue() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Failed to load test environment property");
addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "key = \\uZZZZ" });
}
/**
* @since 4.1.5
*/
@Test
public void addInlinedPropertiesToEnvironmentWithMultipleKeyValuePairsInSingleInlinedProperty() {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Failed to load exactly one test environment property");
addInlinedPropertiesToEnvironment(new MockEnvironment(), new String[] { "a=b\nx=y" });
}
/**
* @since 4.1.5
*/
@Test
@SuppressWarnings("rawtypes")
public void addInlinedPropertiesToEnvironmentWithEmptyProperty() {
ConfigurableEnvironment environment = new MockEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(MockPropertySource.MOCK_PROPERTIES_PROPERTY_SOURCE_NAME);
assertEquals(0, propertySources.size());
addInlinedPropertiesToEnvironment(environment, new String[] { " " });
assertEquals(1, propertySources.size());
assertEquals(0, ((Map) propertySources.iterator().next().getSource()).size());
}
@Test
public void convertInlinedPropertiesToMapWithNullInlinedProperties() {
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("inlined");
convertInlinedPropertiesToMap(null);
}
// -------------------------------------------------------------------
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,7 +41,7 @@ public abstract class AbstractTransactionManagementConfiguration implements Impo
protected AnnotationAttributes enableTx;
/**
* Default transaction manager.
* Default transaction manager, as configured through a {@link TransactionManagementConfigurer}.
*/
protected PlatformTransactionManager txManager;
@@ -54,7 +54,7 @@ public abstract class AbstractTransactionManagementConfiguration implements Impo
"@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName());
}
@Autowired(required=false)
@Autowired(required = false)
void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {
if (CollectionUtils.isEmpty(configurers)) {
return;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,18 +21,20 @@ import org.springframework.transaction.PlatformTransactionManager;
/**
* Interface to be implemented by @{@link org.springframework.context.annotation.Configuration
* Configuration} classes annotated with @{@link EnableTransactionManagement} that wish to
* or need to explicitly specify the default {@link PlatformTransactionManager} bean to be used
* for annotation-driven transaction management, as opposed to the default approach of a
* by-type lookup. One reason this might be necessary is if there are two
* or need to explicitly specify the default {@link PlatformTransactionManager} bean to be
* used for annotation-driven transaction management, as opposed to the default approach
* of a by-type lookup. One reason this might be necessary is if there are two
* {@code PlatformTransactionManager} beans present in the container.
*
* <p>See @{@link EnableTransactionManagement} for general examples and context; see
* {@link #annotationDrivenTransactionManager()} for detailed instructions.
* <p>See @{@link EnableTransactionManagement} for general examples and context;
* see {@link #annotationDrivenTransactionManager()} for detailed instructions.
*
* <p>Note that in by-type lookup disambiguation cases, an alternative approach to
* implementing this interface is to simply mark one of the offending {@code
* PlatformTransactionManager} {@code @Bean} methods as @{@link
* org.springframework.context.annotation.Primary Primary}.
* implementing this interface is to simply mark one of the offending
* {@code PlatformTransactionManager} {@code @Bean} methods as
* @{@link org.springframework.context.annotation.Primary Primary}.
* This is even generally preferred since it doesn't lead to early initialization
* of the {@code PlatformTransactionManager} bean.
*
* @author Chris Beams
* @since 3.1
@@ -44,7 +46,6 @@ public interface TransactionManagementConfigurer {
/**
* Return the default transaction manager bean to use for annotation-driven database
* transaction management, i.e. when processing {@code @Transactional} methods.
*
* <p>There are two basic approaches to implementing this method:
* <h3>1. Implement the method and annotate it with {@code @Bean}</h3>
* In this case, the implementing {@code @Configuration} class implements this method,
@@ -68,15 +69,13 @@ public interface TransactionManagementConfigurer {
* public PlatformTransactionManager annotationDrivenTransactionManager() {
* return txManager(); // reference the existing {@code @Bean} method above
* }</pre>
*
* If taking approach #2, be sure that <em>only one</em> of the methods is marked with
* {@code @Bean}!
*
* If taking approach #2, be sure that <em>only one</em> of the methods is marked
* with {@code @Bean}!
* <p>In either scenario #1 or #2, it is important that the
* {@code PlatformTransactionManager} instance is managed as a Spring bean within the
* container as all {@code PlatformTransactionManager} implementations take
* advantage of Spring lifecycle callbacks such as {@code InitializingBean} and {@code
* BeanFactoryAware}.
* container as all {@code PlatformTransactionManager} implementations take advantage
* of Spring lifecycle callbacks such as {@code InitializingBean} and
* {@code BeanFactoryAware}.
*/
PlatformTransactionManager annotationDrivenTransactionManager();
@@ -353,16 +353,12 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
if (txAttr == null || this.beanFactory == null) {
return getTransactionManager();
}
String qualifier = (txAttr.getQualifier() != null ?
txAttr.getQualifier() : this.transactionManagerBeanName);
String qualifier = txAttr.getQualifier();
if (StringUtils.hasText(qualifier)) {
PlatformTransactionManager txManager = this.transactionManagerCache.get(qualifier);
if (txManager == null) {
txManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
this.beanFactory, PlatformTransactionManager.class, qualifier);
this.transactionManagerCache.putIfAbsent(qualifier, txManager);
}
return txManager;
return determineQualifiedTransactionManager(qualifier);
}
else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.transactionManagerBeanName);
}
else {
PlatformTransactionManager defaultTransactionManager = getTransactionManager();
@@ -375,6 +371,16 @@ public abstract class TransactionAspectSupport implements BeanFactoryAware, Init
}
}
private PlatformTransactionManager determineQualifiedTransactionManager(String qualifier) {
PlatformTransactionManager txManager = this.transactionManagerCache.get(qualifier);
if (txManager == null) {
txManager = BeanFactoryAnnotationUtils.qualifiedBeanOfType(
this.beanFactory, PlatformTransactionManager.class, qualifier);
this.transactionManagerCache.putIfAbsent(qualifier, txManager);
}
return txManager;
}
/**
* Convenience method to return a String representation of this Method
* for use in logging. Can be overridden in subclasses to provide a
@@ -126,7 +126,7 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
@Test
public void determineTransactionManagerWithNoBeanFactory() {
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
TransactionInterceptor ti = createTestTransactionInterceptor(null, transactionManager);
TransactionInterceptor ti = transactionInterceptorWithTransactionManager(transactionManager, null);
assertSame(transactionManager, ti.determineTransactionManager(new DefaultTransactionAttribute()));
}
@@ -134,7 +134,7 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
@Test
public void determineTransactionManagerWithNoBeanFactoryAndNoTransactionAttribute() {
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
TransactionInterceptor ti = createTestTransactionInterceptor(null, transactionManager);
TransactionInterceptor ti = transactionInterceptorWithTransactionManager(transactionManager, null);
assertSame(transactionManager, ti.determineTransactionManager(null));
}
@@ -142,7 +142,7 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
@Test
public void determineTransactionManagerWithNoTransactionAttribute() {
BeanFactory beanFactory = mock(BeanFactory.class);
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory, null);
TransactionInterceptor ti = simpleTransactionInterceptor(beanFactory);
assertNull(ti.determineTransactionManager(null));
}
@@ -150,7 +150,7 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
@Test
public void determineTransactionManagerWithQualifierUnknown() {
BeanFactory beanFactory = mock(BeanFactory.class);
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory);
TransactionInterceptor ti = simpleTransactionInterceptor(beanFactory);
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
attribute.setQualifier("fooTransactionManager");
@@ -163,7 +163,7 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
public void determineTransactionManagerWithQualifierAndDefault() {
BeanFactory beanFactory = mock(BeanFactory.class);
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory, transactionManager);
TransactionInterceptor ti = transactionInterceptorWithTransactionManager(transactionManager, beanFactory);
PlatformTransactionManager fooTransactionManager =
associateTransactionManager(beanFactory, "fooTransactionManager");
@@ -177,8 +177,8 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
public void determineTransactionManagerWithQualifierAndDefaultName() {
BeanFactory beanFactory = mock(BeanFactory.class);
associateTransactionManager(beanFactory, "defaultTransactionManager");
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory);
ti.setTransactionManagerBeanName("defaultTransactionManager");
TransactionInterceptor ti = transactionInterceptorWithTransactionManagerName(
"defaultTransactionManager", beanFactory);
PlatformTransactionManager fooTransactionManager =
associateTransactionManager(beanFactory, "fooTransactionManager");
@@ -188,10 +188,24 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
assertSame(fooTransactionManager, ti.determineTransactionManager(attribute));
}
@Test
public void determineTransactionManagerWithEmptyQualifierAndDefaultName() {
BeanFactory beanFactory = mock(BeanFactory.class);
PlatformTransactionManager defaultTransactionManager
= associateTransactionManager(beanFactory, "defaultTransactionManager");
TransactionInterceptor ti = transactionInterceptorWithTransactionManagerName(
"defaultTransactionManager", beanFactory);
DefaultTransactionAttribute attribute = new DefaultTransactionAttribute();
attribute.setQualifier("");
assertSame(defaultTransactionManager, ti.determineTransactionManager(attribute));
}
@Test
public void determineTransactionManagerWithQualifierSeveralTimes() {
BeanFactory beanFactory = mock(BeanFactory.class);
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory);
TransactionInterceptor ti = simpleTransactionInterceptor(beanFactory);
PlatformTransactionManager txManager = associateTransactionManager(beanFactory, "fooTransactionManager");
@@ -210,8 +224,8 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
@Test
public void determineTransactionManagerWithBeanNameSeveralTimes() {
BeanFactory beanFactory = mock(BeanFactory.class);
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory);
ti.setTransactionManagerBeanName("fooTransactionManager");
TransactionInterceptor ti = transactionInterceptorWithTransactionManagerName(
"fooTransactionManager", beanFactory);
PlatformTransactionManager txManager = associateTransactionManager(beanFactory, "fooTransactionManager");
@@ -228,7 +242,7 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
@Test
public void determineTransactionManagerDefaultSeveralTimes() {
BeanFactory beanFactory = mock(BeanFactory.class);
TransactionInterceptor ti = createTestTransactionInterceptor(beanFactory);
TransactionInterceptor ti = simpleTransactionInterceptor(beanFactory);
PlatformTransactionManager txManager = mock(PlatformTransactionManager.class);
given(beanFactory.getBean(PlatformTransactionManager.class)).willReturn(txManager);
@@ -243,12 +257,16 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
verify(beanFactory, times(1)).getBean(PlatformTransactionManager.class);
}
private TransactionInterceptor createTestTransactionInterceptor(BeanFactory beanFactory,
PlatformTransactionManager transactionManager) {
private TransactionInterceptor createTransactionInterceptor(BeanFactory beanFactory,
String transactionManagerName, PlatformTransactionManager transactionManager) {
TransactionInterceptor ti = new TransactionInterceptor();
if (beanFactory != null) {
ti.setBeanFactory(beanFactory);
}
if (transactionManagerName != null) {
ti.setTransactionManagerBeanName(transactionManagerName);
}
if (transactionManager != null) {
ti.setTransactionManager(transactionManager);
}
@@ -257,8 +275,20 @@ public class TransactionInterceptorTests extends AbstractTransactionAspectTests
return ti;
}
private TransactionInterceptor createTestTransactionInterceptor(BeanFactory beanFactory) {
return createTestTransactionInterceptor(beanFactory, null);
private TransactionInterceptor transactionInterceptorWithTransactionManager(
PlatformTransactionManager transactionManager, BeanFactory beanFactory) {
return createTransactionInterceptor(beanFactory, null, transactionManager);
}
private TransactionInterceptor transactionInterceptorWithTransactionManagerName(
String transactionManagerName, BeanFactory beanFactory) {
return createTransactionInterceptor(beanFactory, transactionManagerName, null);
}
private TransactionInterceptor simpleTransactionInterceptor(BeanFactory beanFactory) {
return createTransactionInterceptor(beanFactory, null, null);
}
private PlatformTransactionManager associateTransactionManager(BeanFactory beanFactory, String name) {
@@ -25,7 +25,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.util.StreamUtils;
/**
* Simple implementation of {@link ClientHttpResponse} that reads the request's body into memory,
* Simple implementation of {@link ClientHttpResponse} that reads the response's body into memory,
* thus allowing for multiple invocations of {@link #getBody()}.
*
* @author Arjen Poutsma
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -65,7 +65,7 @@ class Netty4ClientHttpRequest extends AbstractAsyncClientHttpRequest implements
this.bootstrap = bootstrap;
this.uri = uri;
this.method = method;
this.body = new ByteBufOutputStream(Unpooled.buffer(maxRequestSize));
this.body = new ByteBufOutputStream(Unpooled.buffer(1024, maxRequestSize));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.util.Assert;
* across multiple clients.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 4.1.2
*/
public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
@@ -51,9 +52,17 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
/**
* The default maximum request size.
* @see #setMaxRequestSize(int)
* @deprecated
*/
@Deprecated
public static final int DEFAULT_MAX_REQUEST_SIZE = 1024 * 1024 * 10;
/**
* The default maximum response size.
* @see #setMaxResponseSize(int)
*/
public static final int DEFAULT_MAX_RESPONSE_SIZE = 1024 * 1024 * 10;
private final EventLoopGroup eventLoopGroup;
@@ -61,6 +70,8 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
private int maxRequestSize = DEFAULT_MAX_REQUEST_SIZE;
private int maxResponseSize = DEFAULT_MAX_REQUEST_SIZE;
private SslContext sslContext;
private volatile Bootstrap bootstrap;
@@ -94,11 +105,25 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
* Set the default maximum request size.
* <p>By default this is set to {@link #DEFAULT_MAX_REQUEST_SIZE}.
* @see HttpObjectAggregator#HttpObjectAggregator(int)
* @deprecated as of 4.1.5 this property is no longer supported;
* effectively renamed to {@link #setMaxResponseSize(int)}.
*/
@Deprecated
public void setMaxRequestSize(int maxRequestSize) {
this.maxRequestSize = maxRequestSize;
}
/**
* Set the default maximum response size.
* <p>By default this is set to {@link #DEFAULT_MAX_RESPONSE_SIZE}.
* @see HttpObjectAggregator#HttpObjectAggregator(int)
* @since 4.1.5
*/
public void setMaxResponseSize(int maxResponseSize) {
this.maxResponseSize = maxResponseSize;
}
/**
* Set the SSL context. When configured it is used to create and insert an
* {@link io.netty.handler.ssl.SslHandler} in the channel pipeline.
@@ -120,7 +145,7 @@ public class Netty4ClientHttpRequestFactory implements ClientHttpRequestFactory,
pipeline.addLast(sslContext.newHandler(channel.alloc()));
}
pipeline.addLast(new HttpClientCodec());
pipeline.addLast(new HttpObjectAggregator(maxRequestSize));
pipeline.addLast(new HttpObjectAggregator(maxResponseSize));
}
});
this.bootstrap = bootstrap;
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author 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,6 @@ import org.springframework.http.MediaType;
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.2
*
* @see org.springframework.core.ParameterizedTypeReference
*/
public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T> {
@@ -38,7 +37,7 @@ public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T>
* Indicates whether the given type can be read by this converter.
* @param type the type to test for readability
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature, can be {@code null}
* in which the target type appears in a method signature (can be {@code null})
* @param mediaType the media type to read, can be {@code null} if not specified.
* Typically the value of a {@code Content-Type} header.
* @return {@code true} if readable; {@code false} otherwise
@@ -51,7 +50,7 @@ public interface GenericHttpMessageConverter<T> extends HttpMessageConverter<T>
* been passed to the {@link #canRead canRead} method of this interface,
* which must have returned {@code true}.
* @param contextClass a context class for the target type, for example a class
* in which the target type appears in a method signature, can be {@code null}
* in which the target type appears in a method signature (can be {@code null})
* @param inputMessage the HTTP input message to read from
* @return the converted object
* @throws IOException in case of I/O errors
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,11 +18,14 @@ package org.springframework.http.converter.json;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
@@ -46,6 +49,7 @@ import org.springframework.beans.FatalBeanException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* A builder used to create {@link ObjectMapper} instances with a fluent API.
@@ -75,6 +79,10 @@ public class Jackson2ObjectMapperBuilder {
private DateFormat dateFormat;
private Locale locale;
private TimeZone timeZone;
private AnnotationIntrospector annotationIntrospector;
private PropertyNamingStrategy propertyNamingStrategy;
@@ -91,9 +99,11 @@ public class Jackson2ObjectMapperBuilder {
private List<Module> modules;
private Class<? extends Module>[] modulesToInstall;
private Class<? extends Module>[] moduleClasses;
private boolean findModulesViaServiceLoader;
private boolean findModulesViaServiceLoader = false;
private boolean findWellKnownModules = true;
private ClassLoader moduleClassLoader = getClass().getClassLoader();
@@ -134,6 +144,48 @@ public class Jackson2ObjectMapperBuilder {
return this;
}
/**
* Override the default {@link Locale} to use for formatting.
* Default value used is {@link Locale#getDefault()}.
* @since 4.1.5
*/
public Jackson2ObjectMapperBuilder locale(Locale locale) {
this.locale = locale;
return this;
}
/**
* Override the default {@link Locale} to use for formatting.
* Default value used is {@link Locale#getDefault()}.
* @param localeString the locale ID as a String representation
* @since 4.1.5
*/
public Jackson2ObjectMapperBuilder locale(String localeString) {
this.locale = StringUtils.parseLocaleString(localeString);
return this;
}
/**
* Override the default {@link TimeZone} to use for formatting.
* Default value used is UTC (NOT local timezone).
* @since 4.1.5
*/
public Jackson2ObjectMapperBuilder timeZone(TimeZone timeZone) {
this.timeZone = timeZone;
return this;
}
/**
* Override the default {@link TimeZone} to use for formatting.
* Default value used is UTC (NOT local timezone).
* @param timeZoneString the zone ID as a String representation
* @since 4.1.5
*/
public Jackson2ObjectMapperBuilder timeZone(String timeZoneString) {
this.timeZone = StringUtils.parseTimeZoneString(timeZoneString);
return this;
}
/**
* Set an {@link AnnotationIntrospector} for both serialization and deserialization.
*/
@@ -232,7 +284,7 @@ public class Jackson2ObjectMapperBuilder {
* @see com.fasterxml.jackson.databind.ObjectMapper#addMixInAnnotations(Class, Class)
*/
public Jackson2ObjectMapperBuilder mixIn(Class<?> target, Class<?> mixinSource) {
if (mixIns != null) {
if (mixinSource != null) {
this.mixIns.put(target, mixinSource);
}
return this;
@@ -337,6 +389,21 @@ public class Jackson2ObjectMapperBuilder {
return this;
}
/**
* Specify one or more modules to be registered with the {@link ObjectMapper}.
* <p>Note: If this is set, no finding of modules is going to happen - not by
* Jackson, and not by Spring either (see {@link #findModulesViaServiceLoader}).
* As a consequence, specifying an empty list here will suppress any kind of
* module detection.
* <p>Specify either this or {@link #modulesToInstall}, not both.
* @since 4.1.5
* @see #modules(List)
* @see com.fasterxml.jackson.databind.Module
*/
public Jackson2ObjectMapperBuilder modules(Module... modules) {
return modules(Arrays.asList(modules));
}
/**
* Set a complete list of modules to be registered with the {@link ObjectMapper}.
* <p>Note: If this is set, no finding of modules is going to happen - not by
@@ -344,24 +411,46 @@ public class Jackson2ObjectMapperBuilder {
* As a consequence, specifying an empty list here will suppress any kind of
* module detection.
* <p>Specify either this or {@link #modulesToInstall}, not both.
* @see #modules(Module...)
* @see com.fasterxml.jackson.databind.Module
*/
public Jackson2ObjectMapperBuilder modules(List<Module> modules) {
this.modules = new LinkedList<Module>(modules);
this.findModulesViaServiceLoader = false;
this.findWellKnownModules = false;
return this;
}
/**
* Specify one or more modules by class (or class name in XML),
* to be registered with the {@link ObjectMapper}.
* <p>Modules specified here will be registered in combination with
* Specify one or more modules to be registered with the {@link ObjectMapper}.
* <p>Modules specified here will be registered after
* Spring's autodetection of JSR-310 and Joda-Time, or Jackson's
* finding of modules (see {@link #findModulesViaServiceLoader}).
* finding of modules (see {@link #findModulesViaServiceLoader}),
* allowing to eventually override their configuration.
* <p>Specify either this or {@link #modules}, not both.
* @since 4.1.5
* @see com.fasterxml.jackson.databind.Module
*/
public Jackson2ObjectMapperBuilder modulesToInstall(Module... modules) {
this.modules = Arrays.asList(modules);
this.findWellKnownModules = true;
return this;
}
/**
* Specify one or more modules by class to be registered with
* the {@link ObjectMapper}.
* <p>Modules specified here will be registered after
* Spring's autodetection of JSR-310 and Joda-Time, or Jackson's
* finding of modules (see {@link #findModulesViaServiceLoader}),
* allowing to eventually override their configuration.
* <p>Specify either this or {@link #modules}, not both.
* @see #modulesToInstall(Module...)
* @see com.fasterxml.jackson.databind.Module
*/
public Jackson2ObjectMapperBuilder modulesToInstall(Class<? extends Module>... modules) {
this.modulesToInstall = modules;
this.moduleClasses = modules;
this.findWellKnownModules = true;
return this;
}
@@ -444,9 +533,35 @@ public class Jackson2ObjectMapperBuilder {
public void configure(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "ObjectMapper must not be null");
if (this.findModulesViaServiceLoader) {
// Jackson 2.2+
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
}
else if (this.findWellKnownModules) {
registerWellKnownModulesIfAvailable(objectMapper);
}
if (this.modules != null) {
for (Module module : this.modules) {
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
objectMapper.registerModule(module);
}
}
if (this.moduleClasses != null) {
for (Class<? extends Module> module : this.moduleClasses) {
objectMapper.registerModule(BeanUtils.instantiate(module));
}
}
if (this.dateFormat != null) {
objectMapper.setDateFormat(this.dateFormat);
}
if (this.locale != null) {
objectMapper.setLocale(this.locale);
}
if (this.timeZone != null) {
objectMapper.setTimeZone(this.timeZone);
}
if (this.annotationIntrospector != null) {
objectMapper.setAnnotationIntrospector(this.annotationIntrospector);
@@ -468,29 +583,6 @@ public class Jackson2ObjectMapperBuilder {
configureFeature(objectMapper, feature, this.features.get(feature));
}
if (this.modules != null) {
// Complete list of modules given
for (Module module : this.modules) {
// Using Jackson 2.0+ registerModule method, not Jackson 2.2+ registerModules
objectMapper.registerModule(module);
}
}
else {
// Combination of modules by class names specified and class presence in the classpath
if (this.modulesToInstall != null) {
for (Class<? extends Module> module : this.modulesToInstall) {
objectMapper.registerModule(BeanUtils.instantiate(module));
}
}
if (this.findModulesViaServiceLoader) {
// Jackson 2.2+
objectMapper.registerModules(ObjectMapper.findModules(this.moduleClassLoader));
}
else {
registerWellKnownModulesIfAvailable(objectMapper);
}
}
if (this.propertyNamingStrategy != null) {
objectMapper.setPropertyNamingStrategy(this.propertyNamingStrategy);
}
@@ -19,7 +19,9 @@ package org.springframework.http.converter.json;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.AnnotationIntrospector;
@@ -171,6 +173,24 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
this.builder.simpleDateFormat(format);
}
/**
* Override the default {@link Locale} to use for formatting.
* Default value used is {@link Locale#getDefault()}.
* @since 4.1.5
*/
public void setLocale(Locale locale) {
this.builder.locale(locale);
}
/**
* Override the default {@link TimeZone} to use for formatting.
* Default value used is UTC (NOT local timezone).
* @since 4.1.5
*/
public void setTimeZone(TimeZone timeZone) {
this.builder.timeZone(timeZone);
}
/**
* Set an {@link AnnotationIntrospector} for both serialization and deserialization.
*/
@@ -316,11 +336,12 @@ public class Jackson2ObjectMapperFactoryBean implements FactoryBean<ObjectMapper
}
/**
* Specify one or more modules by class (or class name in XML),
* Specify one or more modules by class (or class name in XML)
* to be registered with the {@link ObjectMapper}.
* <p>Modules specified here will be registered in combination with
* <p>Modules specified here will be registered after
* Spring's autodetection of JSR-310 and Joda-Time, or Jackson's
* finding of modules (see {@link #setFindModulesViaServiceLoader}).
* finding of modules (see {@link #setFindModulesViaServiceLoader}),
* allowing to eventually override their configuration.
* <p>Specify either this or {@link #setModules}, not both.
* @since 4.0.1
* @see com.fasterxml.jackson.databind.Module
@@ -23,7 +23,6 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
@@ -80,10 +79,12 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public T extractData(ClientHttpResponse response) throws IOException {
if (!hasMessageBody(response)) {
MessageBodyClientHttpResponseWrapper responseWrapper = new MessageBodyClientHttpResponseWrapper(response);
if(!responseWrapper.hasMessageBody() || responseWrapper.hasEmptyMessageBody()) {
return null;
}
MediaType contentType = getContentType(response);
MediaType contentType = getContentType(responseWrapper);
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
if (messageConverter instanceof GenericHttpMessageConverter) {
@@ -93,7 +94,7 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
logger.debug("Reading [" + this.responseType + "] as \"" +
contentType + "\" using [" + messageConverter + "]");
}
return (T) genericMessageConverter.read(this.responseType, null, response);
return (T) genericMessageConverter.read(this.responseType, null, responseWrapper);
}
}
if (this.responseClass != null) {
@@ -102,7 +103,7 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
logger.debug("Reading [" + this.responseClass.getName() + "] as \"" +
contentType + "\" using [" + messageConverter + "]");
}
return (T) messageConverter.read((Class) this.responseClass, response);
return (T) messageConverter.read((Class) this.responseClass, responseWrapper);
}
}
}
@@ -122,39 +123,4 @@ public class HttpMessageConverterExtractor<T> implements ResponseExtractor<T> {
return contentType;
}
/**
* Indicates whether the given response has a message body.
* <p>Default implementation returns {@code false} for:
* <ul>
* <li>a response status of {@code 204} or {@code 304}</li>
* <li>a {@code Content-Length} of {@code 0}</li>
* <li>no indication of content (no {@code Content-Length} nor {@code Transfer-encoding: chunked}) and
* a ({@code Connection: closed}) header. See rfc7230 section 3.4</li>
* </ul>
*
* @param response the response to check for a message body
* @return {@code true} if the response has a body, {@code false} otherwise
* @throws IOException in case of I/O errors
*/
protected boolean hasMessageBody(ClientHttpResponse response) throws IOException {
HttpStatus responseStatus = response.getStatusCode();
if (responseStatus == HttpStatus.NO_CONTENT ||
responseStatus == HttpStatus.NOT_MODIFIED) {
return false;
}
HttpHeaders headers = response.getHeaders();
long contentLength = headers.getContentLength();
if(contentLength == 0) {
return false;
}
boolean chunked = headers.containsKey(HttpHeaders.TRANSFER_ENCODING)
&& headers.get(HttpHeaders.TRANSFER_ENCODING).contains("chunked");
boolean closed = headers.containsKey(HttpHeaders.CONNECTION)
&& headers.getConnection().contains("close");
if(!chunked && contentLength == -1 && closed) {
return false;
}
return true;
}
}
@@ -0,0 +1,139 @@
/*
* Copyright 2002-2015 the original author 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.web.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
/**
* Implementation of {@link ClientHttpResponse} that can not only check if
* the response has a message body, but also if its length is 0 (i.e. empty)
* by actually reading the input stream.
*
* @author Brian Clozel
* @since 4.1.5
* @see <a href="http://tools.ietf.org/html/rfc7230#section-3.3.3">rfc7230 Section 3.3.3</a>
*/
class MessageBodyClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response;
private PushbackInputStream pushbackInputStream;
public MessageBodyClientHttpResponseWrapper(ClientHttpResponse response) throws IOException {
this.response = response;
}
/**
* Indicates whether the response has a message body.
* <p>Implementation returns {@code false} for:
* <ul>
* <li>a response status of {@code 1XX}, {@code 204} or {@code 304}</li>
* <li>a {@code Content-Length} header of {@code 0}</li>
* </ul>
* @return {@code true} if the response has a message body, {@code false} otherwise
* @throws IOException in case of I/O errors
*/
public boolean hasMessageBody() throws IOException {
HttpStatus responseStatus = this.getStatusCode();
if (responseStatus.is1xxInformational() || responseStatus == HttpStatus.NO_CONTENT ||
responseStatus == HttpStatus.NOT_MODIFIED) {
return false;
}
else if(this.getHeaders().getContentLength() == 0) {
return false;
}
return true;
}
/**
* Indicates whether the response has an empty message body.
* <p>Implementation tries to read the first bytes of the response stream:
* <ul>
* <li>if no bytes are available, the message body is empty</li>
* <li>otherwise it is not empty and the stream is reset to its start for further reading</li>
* </ul>
* @return {@code true} if the response has a zero-length message body, {@code false} otherwise
* @throws IOException in case of I/O errors
*/
public boolean hasEmptyMessageBody() throws IOException {
InputStream body = this.response.getBody();
if (body == null) {
return true;
}
else if (body.markSupported()) {
body.mark(1);
if (body.read() == -1) {
return true;
}
else {
body.reset();
return false;
}
}
else {
this.pushbackInputStream = new PushbackInputStream(body);
int b = this.pushbackInputStream.read();
if (b == -1) {
return true;
}
else {
this.pushbackInputStream.unread(b);
return false;
}
}
}
@Override
public HttpHeaders getHeaders() {
return this.response.getHeaders();
}
@Override
public InputStream getBody() throws IOException {
return (this.pushbackInputStream != null ? this.pushbackInputStream : this.response.getBody());
}
@Override
public HttpStatus getStatusCode() throws IOException {
return this.response.getStatusCode();
}
@Override
public int getRawStatusCode() throws IOException {
return this.response.getRawStatusCode();
}
@Override
public String getStatusText() throws IOException {
return this.response.getStatusText();
}
@Override
public void close() {
this.response.close();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -596,7 +596,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
logger.debug(method.name() + " request for \"" + url + "\" resulted in " +
response.getRawStatusCode() + " (" + response.getStatusText() + ")");
}
catch (IOException e) {
catch (IOException ex) {
// ignore
}
}
@@ -608,7 +608,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
logger.warn(method.name() + " request for \"" + url + "\" resulted in " +
response.getRawStatusCode() + " (" + response.getStatusText() + "); invoking error handler");
}
catch (IOException e) {
catch (IOException ex) {
// ignore
}
}
@@ -668,12 +668,11 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
if (responseType != null) {
if (this.responseType != null) {
Class<?> responseClass = null;
if (responseType instanceof Class) {
responseClass = (Class<?>) responseType;
if (this.responseType instanceof Class) {
responseClass = (Class<?>) this.responseType;
}
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> converter : getMessageConverters()) {
if (responseClass != null) {
@@ -682,13 +681,11 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
}
else if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
if (genericConverter.canRead(responseType, null, null)) {
if (genericConverter.canRead(this.responseType, null, null)) {
allSupportedMediaTypes.addAll(getSupportedMediaTypes(converter));
}
}
}
if (!allSupportedMediaTypes.isEmpty()) {
MediaType.sortBySpecificity(allSupportedMediaTypes);
@@ -744,9 +741,9 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@SuppressWarnings("unchecked")
public void doWithRequest(ClientHttpRequest httpRequest) throws IOException {
super.doWithRequest(httpRequest);
if (!requestEntity.hasBody()) {
if (!this.requestEntity.hasBody()) {
HttpHeaders httpHeaders = httpRequest.getHeaders();
HttpHeaders requestHeaders = requestEntity.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
if (!requestHeaders.isEmpty()) {
httpHeaders.putAll(requestHeaders);
}
@@ -755,9 +752,9 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
}
}
else {
Object requestBody = requestEntity.getBody();
Object requestBody = this.requestEntity.getBody();
Class<?> requestType = requestBody.getClass();
HttpHeaders requestHeaders = requestEntity.getHeaders();
HttpHeaders requestHeaders = this.requestEntity.getHeaders();
MediaType requestContentType = requestHeaders.getContentType();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter.canWrite(requestType, requestContentType)) {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -211,14 +211,15 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
requestToUse = new ContentCachingRequestWrapper(request);
}
if (isFirstRequest) {
boolean shouldLog = shouldLog(requestToUse);
if (shouldLog && isFirstRequest) {
beforeRequest(requestToUse, getBeforeMessage(requestToUse));
}
try {
filterChain.doFilter(requestToUse, response);
}
finally {
if (!isAsyncStarted(requestToUse)) {
if (shouldLog && !isAsyncStarted(requestToUse)) {
afterRequest(requestToUse, getAfterMessage(requestToUse));
}
}
@@ -289,6 +290,22 @@ public abstract class AbstractRequestLoggingFilter extends OncePerRequestFilter
return msg.toString();
}
/**
* Determine whether to call the {@link #beforeRequest}/{@link #afterRequest}
* methods for the current request, i.e. whether logging is currently active
* (and the log message is worth building).
* <p>The default implementation always returns {@code true}. Subclasses may
* override this with a log level check.
* @param request current HTTP request
* @return {@code true} if the before/after method should get called;
* {@code false} otherwise
* @since 4.1.5
*/
protected boolean shouldLog(HttpServletRequest request) {
return true;
}
/**
* Concrete subclasses should implement this method to write a log message
* <i>before</i> the request is processed.
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2005 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@ import javax.servlet.http.HttpServletRequest;
* (and optionally the query string) to the Commons Log.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 1.2.5
* @see #setIncludeQueryString
* @see #setBeforeMessagePrefix
@@ -33,14 +34,17 @@ import javax.servlet.http.HttpServletRequest;
*/
public class CommonsRequestLoggingFilter extends AbstractRequestLoggingFilter {
@Override
protected boolean shouldLog(HttpServletRequest request) {
return logger.isDebugEnabled();
}
/**
* Writes a log message before the request is processed.
*/
@Override
protected void beforeRequest(HttpServletRequest request, String message) {
if (logger.isDebugEnabled()) {
logger.debug(message);
}
logger.debug(message);
}
/**
@@ -48,9 +52,7 @@ public class CommonsRequestLoggingFilter extends AbstractRequestLoggingFilter {
*/
@Override
protected void afterRequest(HttpServletRequest request, String message) {
if (logger.isDebugEnabled()) {
logger.debug(message);
}
logger.debug(message);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -56,7 +56,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
protected Log logger = LogFactory.getLog(this.getClass());
protected final Log logger = LogFactory.getLog(getClass());
private final boolean annotationNotRequired;
@@ -72,8 +72,8 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* @return true if the parameter is annotated with {@link ModelAttribute}
* or in default resolution mode also if it is not a simple type.
* Returns {@code true} if the parameter is annotated with {@link ModelAttribute}
* or in default resolution mode, and also if it is not a simple type.
*/
@Override
public boolean supportsParameter(MethodParameter parameter) {
@@ -109,15 +109,12 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
if (binder.getTarget() != null) {
bindRequestParameters(binder, webRequest);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
// Add resolved attribute and BindingResult at the end of the model
Map<String, Object> bindingResultModel = binder.getBindingResult().getModel();
mavContainer.removeAttributes(bindingResultModel);
mavContainer.addAllAttributes(bindingResultModel);
@@ -129,15 +126,15 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
* Extension point to create the model attribute if not found in the model.
* The default implementation uses the default constructor.
* @param attributeName the name of the attribute (never {@code null})
* @param parameter the method parameter
* @param methodParam the method parameter
* @param binderFactory for creating WebDataBinder instance
* @param request the current request
* @return the created model attribute (never {@code null})
*/
protected Object createAttribute(String attributeName, MethodParameter parameter,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
protected Object createAttribute(String attributeName, MethodParameter methodParam,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
return BeanUtils.instantiateClass(parameter.getParameterType());
return BeanUtils.instantiateClass(methodParam.getParameterType());
}
/**
@@ -151,12 +148,14 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
/**
* Validate the model attribute if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid}.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param parameter the method parameter
* @param methodParam the method parameter
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
@@ -169,16 +168,15 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol
}
/**
* Whether to raise a {@link BindException} on validation errors.
* Whether to raise a fatal bind exception on validation errors.
* @param binder the data binder used to perform data binding
* @param parameter the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}.
* @param methodParam the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}
*/
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -94,6 +94,9 @@ public interface MultipartFile {
* <p>If the file has been moved in the filesystem, this operation cannot
* be invoked again. Therefore, call this method just once to be able to
* work with any storage mechanism.
* <p><strong>Note:</strong> when using Servlet 3.0 multipart support you
* need to configure the location relative to which files will be copied
* as explained in {@link javax.servlet.http.Part#write}.
* @param dest the destination file
* @throws IOException in case of reading or writing errors
* @throws IllegalStateException if the file has already been moved
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,14 @@ import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@@ -36,6 +44,10 @@ import javax.servlet.http.HttpServletRequestWrapper;
*/
public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
private static final String METHOD_POST = "POST";
private final ByteArrayOutputStream cachedContent;
private ServletInputStream inputStream;
@@ -80,9 +92,46 @@ public class ContentCachingRequestWrapper extends HttpServletRequestWrapper {
* Return the cached request content as a byte array.
*/
public byte[] getContentAsByteArray() {
if(this.cachedContent.size() == 0 && isFormPost()) {
writeRequestParamsToContent();
}
return this.cachedContent.toByteArray();
}
private boolean isFormPost() {
return (getContentType() != null && getContentType().contains(FORM_CONTENT_TYPE) &&
METHOD_POST.equalsIgnoreCase(getMethod()));
}
private void writeRequestParamsToContent() {
try {
if (this.cachedContent.size() == 0) {
String requestEncoding = getCharacterEncoding();
Map<String, String[]> form = getParameterMap();
for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext(); ) {
String name = nameIterator.next();
List<String> values = Arrays.asList(form.get(name));
for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext(); ) {
String value = valueIterator.next();
cachedContent.write(URLEncoder.encode(name, requestEncoding).getBytes());
if (value != null) {
cachedContent.write('=');
cachedContent.write(URLEncoder.encode(value, requestEncoding).getBytes());
if (valueIterator.hasNext()) {
cachedContent.write('&');
}
}
}
if (nameIterator.hasNext()) {
cachedContent.write('&');
}
}
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
private class ContentCachingInputStream extends ServletInputStream {
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.http.HttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -263,6 +264,55 @@ public class UriComponentsBuilder implements Cloneable {
}
}
/**
* Create a new {@code UriComponents} object from the URI associated with
* the given HttpRequest while also overlaying with values from the headers
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if present.
* @param request the source request
* @return the URI components of the URI
* @since 4.1.5
*/
public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
URI uri = request.getURI();
UriComponentsBuilder builder = UriComponentsBuilder.fromUri(uri);
String scheme = uri.getScheme();
String host = uri.getHost();
int port = uri.getPort();
String hostHeader = request.getHeaders().getFirst("X-Forwarded-Host");
if (StringUtils.hasText(hostHeader)) {
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
String hostToUse = hosts[0];
if (hostToUse.contains(":")) {
String[] hostAndPort = StringUtils.split(hostToUse, ":");
host = hostAndPort[0];
port = Integer.parseInt(hostAndPort[1]);
}
else {
host = hostToUse;
port = -1;
}
}
String portHeader = request.getHeaders().getFirst("X-Forwarded-Port");
if (StringUtils.hasText(portHeader)) {
port = Integer.parseInt(portHeader);
}
String protocolHeader = request.getHeaders().getFirst("X-Forwarded-Proto");
if (StringUtils.hasText(protocolHeader)) {
scheme = protocolHeader;
}
builder.scheme(scheme);
builder.host(host);
if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) {
builder.port(port);
}
return builder;
}
// build methods
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.web.util;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.StringTokenizer;
@@ -32,6 +33,10 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@@ -43,6 +48,7 @@ import org.springframework.util.StringUtils;
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sebastien Deleuze
*/
public abstract class WebUtils {
@@ -109,6 +115,7 @@ public abstract class WebUtils {
/**
* Use of response encoding for HTML escaping parameter at the servlet context level
* (i.e. a context-param in {@code web.xml}): "responseEncodedHtmlEscape".
* @since 4.1.2
*/
public static final String RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM = "responseEncodedHtmlEscape";
@@ -127,6 +134,8 @@ public abstract class WebUtils {
/** Key for the mutex session attribute */
public static final String SESSION_MUTEX_ATTRIBUTE = WebUtils.class.getName() + ".MUTEX";
private static final Log logger = LogFactory.getLog(WebUtils.class);
/**
* Set a system property to the web application root directory.
@@ -207,7 +216,7 @@ public abstract class WebUtils {
return null;
}
String param = servletContext.getInitParameter(HTML_ESCAPE_CONTEXT_PARAM);
return (StringUtils.hasText(param)? Boolean.valueOf(param) : null);
return (StringUtils.hasText(param) ? Boolean.valueOf(param) : null);
}
/**
@@ -221,13 +230,14 @@ public abstract class WebUtils {
* default in case of no setting at the global level.
* @param servletContext the servlet context of the web application
* @return whether response encoding is used for HTML escaping (null = no explicit default)
* @since 4.1.2
*/
public static Boolean getResponseEncodedHtmlEscape(ServletContext servletContext) {
if (servletContext == null) {
return null;
}
String param = servletContext.getInitParameter(RESPONSE_ENCODED_HTML_ESCAPE_CONTEXT_PARAM);
return (StringUtils.hasText(param)? Boolean.valueOf(param) : null);
return (StringUtils.hasText(param) ? Boolean.valueOf(param) : null);
}
/**
@@ -737,9 +747,9 @@ public abstract class WebUtils {
* like this {@code "q1=a;q1=b;q2=a,b,c"}. The resulting map would contain
* keys {@code "q1"} and {@code "q2"} with values {@code ["a","b"]} and
* {@code ["a","b","c"]} respectively.
*
* @param matrixVariables the unparsed matrix variables string
* @return a map with matrix variable names and values, never {@code null}
* @since 3.2
*/
public static MultiValueMap<String, String> parseMatrixVariables(String matrixVariables) {
MultiValueMap<String, String> result = new LinkedMultiValueMap<String, String>();
@@ -763,4 +773,55 @@ public abstract class WebUtils {
}
return result;
}
/**
* Check the given request origin against a list of allowed origins.
* A list containing "*" means that all origins are allowed.
* An empty list means only same origin is allowed.
* @return true if the request origin is valid, false otherwise
* @since 4.1.5
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454: The Web Origin Concept</a>
*/
public static boolean isValidOrigin(HttpRequest request, Collection<String> allowedOrigins) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(allowedOrigins, "Allowed origins must not be null");
String origin = request.getHeaders().getOrigin();
if (origin == null || allowedOrigins.contains("*")) {
return true;
}
else if (allowedOrigins.isEmpty()) {
UriComponents originComponents;
try {
originComponents = UriComponentsBuilder.fromHttpUrl(origin).build();
}
catch (IllegalArgumentException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Failed to parse Origin header value [" + origin + "]");
}
return false;
}
UriComponents requestComponents = UriComponentsBuilder.fromHttpRequest(request).build();
int originPort = getPort(originComponents);
int requestPort = getPort(requestComponents);
return (originComponents.getHost().equals(requestComponents.getHost()) && originPort == requestPort);
}
else {
return allowedOrigins.contains(origin);
}
}
private static int getPort(UriComponents component) {
int port = component.getPort();
if (port == -1) {
if ("http".equals(component.getScheme())) {
port = 80;
}
else if ("https".equals(component.getScheme())) {
port = 443;
}
}
return port;
}
}
@@ -42,11 +42,13 @@ public class HttpHeadersTests {
private HttpHeaders headers;
@Before
public void setUp() {
headers = new HttpHeaders();
}
@Test
public void accept() {
MediaType mediaType1 = new MediaType("text", "html");
@@ -59,9 +61,7 @@ public class HttpHeadersTests {
assertEquals("Invalid Accept header", "text/html, text/plain", headers.getFirst("Accept"));
}
// SPR-9655
@Test
@Test // SPR-9655
public void acceptiPlanet() {
headers.add("Accept", "text/html");
headers.add("Accept", "text/plain");
@@ -228,7 +228,7 @@ public class HttpHeadersTests {
calendar.setTimeZone(TimeZone.getTimeZone("CET"));
long date = calendar.getTimeInMillis();
headers.setIfModifiedSince(date);
assertEquals("Invalid If-Modified-Since header", date, headers.getIfNotModifiedSince());
assertEquals("Invalid If-Modified-Since header", date, headers.getIfModifiedSince());
assertEquals("Invalid If-Modified-Since header", "Thu, 18 Dec 2008 10:20:00 GMT",
headers.getFirst("if-modified-since"));
}
@@ -260,12 +260,9 @@ public class HttpHeadersTests {
headers.getFirst("Content-Disposition"));
}
// SPR-11917
@Test
@Test // SPR-11917
public void getAllowEmptySet() {
headers.setAllow(Collections.<HttpMethod> emptySet());
assertThat(headers.getAllow(), Matchers.emptyCollectionOf(HttpMethod.class));
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,22 @@
package org.springframework.http.converter.json;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
@@ -35,6 +41,7 @@ import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory;
@@ -42,16 +49,20 @@ import com.fasterxml.jackson.databind.deser.Deserializers;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
import com.fasterxml.jackson.databind.ser.Serializers;
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
@@ -167,15 +178,94 @@ public class Jackson2ObjectMapperBuilderTests {
}
@Test
public void setModules() {
public void localeSetter() {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().locale(Locale.FRENCH).build();
assertEquals(Locale.FRENCH, objectMapper.getSerializationConfig().getLocale());
assertEquals(Locale.FRENCH, objectMapper.getDeserializationConfig().getLocale());
}
@Test
public void timeZoneSetter() {
TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris");
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().timeZone(timeZone).build();
assertEquals(timeZone, objectMapper.getSerializationConfig().getTimeZone());
assertEquals(timeZone, objectMapper.getDeserializationConfig().getTimeZone());
}
@Test
public void timeZoneStringSetter() {
String zoneId = "Europe/Paris";
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().timeZone(zoneId).build();
TimeZone timeZone = TimeZone.getTimeZone(zoneId);
assertEquals(timeZone, objectMapper.getSerializationConfig().getTimeZone());
assertEquals(timeZone, objectMapper.getDeserializationConfig().getTimeZone());
}
@Test(expected = IllegalArgumentException.class)
public void wrongTimeZoneStringSetter() {
String zoneId = "foo";
Jackson2ObjectMapperBuilder.json().timeZone(zoneId).build();
}
@Test
public void modules() {
NumberSerializer serializer1 = new NumberSerializer();
SimpleModule module = new SimpleModule();
module.addSerializer(Integer.class, serializer1);
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(Arrays.asList(new Module[]{module})).build();
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modules(module).build();
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1);
}
@Test
public void modulesToInstallByClass() {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build();
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null).getClass() == CustomIntegerSerializer.class);
}
@Test
public void modulesToInstallByInstance() {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(new CustomIntegerModule()).build();
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null).getClass() == CustomIntegerSerializer.class);
}
@Test
public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
Long timestamp = 1322903730000L;
DateTime dateTime = new DateTime(timestamp, DateTimeZone.UTC);
assertEquals(timestamp.toString(), new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
}
@Test // SPR-12634
public void customizeDefaultModulesWithModule() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.modulesToInstall(new CustomIntegerModule()).build();
DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC);
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
@Test // SPR-12634
public void customizeDefaultModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().modulesToInstall(CustomIntegerModule.class).build();
DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC);
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
@Test // SPR-12634
public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.serializerByType(Integer.class, new CustomIntegerSerializer()).build();
DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC);
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
private static SerializerFactoryConfig getSerializerFactoryConfig(ObjectMapper objectMapper) {
return ((BasicSerializerFactory) objectMapper.getSerializerFactory()).getFactoryConfig();
}
@@ -196,6 +286,7 @@ public class Jackson2ObjectMapperBuilderTests {
public void serializerByType() {
JsonSerializer<Number> serializer = new NumberSerializer();
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.modules(new ArrayList<>()) // Disable well-known modules detection
.serializerByType(Boolean.class, serializer).build();
assertTrue(getSerializerFactoryConfig(objectMapper).hasSerializers());
Serializers serializers = getSerializerFactoryConfig(objectMapper).serializers().iterator().next();
@@ -206,6 +297,7 @@ public class Jackson2ObjectMapperBuilderTests {
public void deserializerByType() throws JsonMappingException {
JsonDeserializer<Date> deserializer = new DateDeserializers.DateDeserializer();
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json()
.modules(new ArrayList<>()) // Disable well-known modules detection
.deserializerByType(Date.class, deserializer).build();
assertTrue(getDeserializerFactoryConfig(objectMapper).hasDeserializers());
Deserializers deserializers = getDeserializerFactoryConfig(objectMapper).deserializers().iterator().next();
@@ -249,6 +341,7 @@ public class Jackson2ObjectMapperBuilderTests {
JsonSerializer<Number> serializer2 = new NumberSerializer();
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json()
.modules(new ArrayList<>()) // Disable well-known modules detection
.serializers(serializer1)
.serializersByType(Collections.<Class<?>, JsonSerializer<?>>singletonMap(Boolean.class, serializer2))
.deserializersByType(deserializerMap)
@@ -311,4 +404,36 @@ public class Jackson2ObjectMapperBuilderTests {
assertTrue(xmlObjectMapper.getClass().isAssignableFrom(XmlMapper.class));
}
public static class CustomIntegerModule extends Module {
@Override
public String getModuleName() {
return this.getClass().getSimpleName();
}
@Override
public Version version() {
return Version.unknownVersion();
}
@Override
public void setupModule(SetupContext context) {
SimpleSerializers serializers = new SimpleSerializers();
serializers.addSerializer(Integer.class, new CustomIntegerSerializer());
context.addSerializers(serializers);
}
}
public static class CustomIntegerSerializer extends JsonSerializer<Integer> {
@Override
public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeNumberField("customid", value);
gen.writeEndObject();
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,23 @@
package org.springframework.http.converter.json;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonSerializer;
@@ -34,23 +41,28 @@ import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.cfg.DeserializerFactoryConfig;
import com.fasterxml.jackson.databind.cfg.SerializerFactoryConfig;
import com.fasterxml.jackson.databind.deser.BasicDeserializerFactory;
import com.fasterxml.jackson.databind.deser.std.DateDeserializers.DateDeserializer;
import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.module.SimpleSerializers;
import com.fasterxml.jackson.databind.ser.BasicSerializerFactory;
import com.fasterxml.jackson.databind.ser.Serializers;
import com.fasterxml.jackson.databind.ser.std.ClassSerializer;
import com.fasterxml.jackson.databind.ser.std.NumberSerializer;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.FatalBeanException;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
@@ -58,6 +70,7 @@ import static org.junit.Assert.*;
*
* @author <a href="mailto:dmitry.katsubo@gmail.com">Dmitry Katsubo</a>
* @author Brian Clozel
* @author Sebastien Deleuze
*/
public class Jackson2ObjectMapperFactoryBeanTests {
@@ -161,13 +174,57 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertEquals(dateFormat, this.factory.getObject().getDeserializationConfig().getDateFormat());
}
@Test
public void localeSetter() {
this.factory.setLocale(Locale.FRENCH);
this.factory.afterPropertiesSet();
assertEquals(Locale.FRENCH, this.factory.getObject().getSerializationConfig().getLocale());
assertEquals(Locale.FRENCH, this.factory.getObject().getDeserializationConfig().getLocale());
}
@Test
public void timeZoneSetter() {
TimeZone timeZone = TimeZone.getTimeZone("Europe/Paris");
this.factory.setTimeZone(timeZone);
this.factory.afterPropertiesSet();
assertEquals(timeZone, this.factory.getObject().getSerializationConfig().getTimeZone());
assertEquals(timeZone, this.factory.getObject().getDeserializationConfig().getTimeZone());
}
@Test
public void timeZoneStringSetter() {
String zoneId = "Europe/Paris";
this.factory.setTimeZone(TimeZone.getTimeZone(zoneId));
this.factory.afterPropertiesSet();
TimeZone timeZone = TimeZone.getTimeZone(zoneId);
assertEquals(timeZone, this.factory.getObject().getSerializationConfig().getTimeZone());
assertEquals(timeZone, this.factory.getObject().getDeserializationConfig().getTimeZone());
}
@Test
public void wrongTimeZoneStringSetter() {
String zoneId = "foo";
this.factory.setTimeZone(TimeZone.getTimeZone(zoneId));
this.factory.afterPropertiesSet();
TimeZone timeZone = TimeZone.getTimeZone("GMT");
assertEquals(timeZone, this.factory.getObject().getSerializationConfig().getTimeZone());
assertEquals(timeZone, this.factory.getObject().getDeserializationConfig().getTimeZone());
}
@Test
public void setModules() {
NumberSerializer serializer1 = new NumberSerializer();
SimpleModule module = new SimpleModule();
module.addSerializer(Integer.class, serializer1);
this.factory.setModules(Arrays.asList(new Module[] {module}));
this.factory.setModules(Arrays.asList(new Module[]{module}));
this.factory.afterPropertiesSet();
ObjectMapper objectMapper = this.factory.getObject();
@@ -175,6 +232,41 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertTrue(serializers.findSerializer(null, SimpleType.construct(Integer.class), null) == serializer1);
}
@Test
public void defaultModules() throws JsonProcessingException, UnsupportedEncodingException {
this.factory.afterPropertiesSet();
ObjectMapper objectMapper = this.factory.getObject();
Long timestamp = 1322903730000L;
DateTime dateTime = new DateTime(timestamp, DateTimeZone.UTC);
assertEquals(timestamp.toString(), new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
}
@Test // SPR-12634
public void customizeDefaultModulesWithModuleClass() throws JsonProcessingException, UnsupportedEncodingException {
this.factory.setModulesToInstall(CustomIntegerModule.class);
this.factory.afterPropertiesSet();
ObjectMapper objectMapper = this.factory.getObject();
DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC);
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
@Test // SPR-12634
public void customizeDefaultModulesWithSerializer() throws JsonProcessingException, UnsupportedEncodingException {
Map<Class<?>, JsonSerializer<?>> serializers = new HashMap<>();
serializers.put(Integer.class, new CustomIntegerSerializer());
this.factory.setSerializersByType(serializers);
this.factory.afterPropertiesSet();
ObjectMapper objectMapper = this.factory.getObject();
DateTime dateTime = new DateTime(1322903730000L, DateTimeZone.UTC);
assertEquals("1322903730000", new String(objectMapper.writeValueAsBytes(dateTime), "UTF-8"));
assertThat(new String(objectMapper.writeValueAsBytes(new Integer(4)), "UTF-8"), containsString("customid"));
}
@Test
public void simpleSetup() {
this.factory.afterPropertiesSet();
@@ -237,6 +329,7 @@ public class Jackson2ObjectMapperFactoryBeanTests {
JsonSerializer<Class<?>> serializer1 = new ClassSerializer();
JsonSerializer<Number> serializer2 = new NumberSerializer();
factory.setModules(new ArrayList<>()); // Disable well-known modules detection
factory.setSerializers(serializer1);
factory.setSerializersByType(Collections.<Class<?>, JsonSerializer<?>> singletonMap(Boolean.class, serializer2));
factory.setDeserializersByType(deserializers);
@@ -304,4 +397,36 @@ public class Jackson2ObjectMapperFactoryBeanTests {
assertEquals(XmlMapper.class, this.factory.getObjectType());
}
public static class CustomIntegerModule extends Module {
@Override
public String getModuleName() {
return this.getClass().getSimpleName();
}
@Override
public Version version() {
return Version.unknownVersion();
}
@Override
public void setupModule(SetupContext context) {
SimpleSerializers serializers = new SimpleSerializers();
serializers.addSerializer(Integer.class, new CustomIntegerSerializer());
context.addSerializers(serializers);
}
}
public static class CustomIntegerSerializer extends JsonSerializer<Integer> {
@Override
public void serialize(Integer value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeStartObject();
gen.writeNumberField("customid", value);
gen.writeEndObject();
}
}
}
@@ -16,6 +16,7 @@
package org.springframework.web.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
@@ -26,6 +27,7 @@ import org.junit.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
@@ -73,6 +75,17 @@ public class HttpMessageConverterExtractorTests {
assertNull(result);
}
@Test
public void informational() throws IOException {
HttpMessageConverter<?> converter = mock(HttpMessageConverter.class);
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter));
given(response.getStatusCode()).willReturn(HttpStatus.CONTINUE);
Object result = extractor.extractData(response);
assertNull(result);
}
@Test
public void zeroContentLength() throws IOException {
HttpMessageConverter<?> converter = mock(HttpMessageConverter.class);
@@ -87,6 +100,22 @@ public class HttpMessageConverterExtractorTests {
assertNull(result);
}
@Test
@SuppressWarnings("unchecked")
public void emptyMessageBody() throws IOException {
HttpMessageConverter<String> converter = mock(HttpMessageConverter.class);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(converter);
HttpHeaders responseHeaders = new HttpHeaders();
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter));
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream("".getBytes()));
Object result = extractor.extractData(response);
assertNull(result);
}
@Test
@SuppressWarnings("unchecked")
public void normal() throws IOException {
@@ -100,8 +129,9 @@ public class HttpMessageConverterExtractorTests {
extractor = new HttpMessageConverterExtractor<String>(String.class, converters);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.getBytes()));
given(converter.canRead(String.class, contentType)).willReturn(true);
given(converter.read(String.class, response)).willReturn(expected);
given(converter.read(eq(String.class), any(HttpInputMessage.class))).willReturn(expected);
Object result = extractor.extractData(response);
@@ -120,28 +150,13 @@ public class HttpMessageConverterExtractorTests {
extractor = new HttpMessageConverterExtractor<String>(String.class, converters);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream("Foobar".getBytes()));
given(converter.canRead(String.class, contentType)).willReturn(false);
extractor.extractData(response);
}
@Test
public void connectionClose() throws IOException {
HttpMessageConverter<String> converter = mock(HttpMessageConverter.class);
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(converter);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setConnection("close");
extractor = new HttpMessageConverterExtractor<String>(String.class, createConverterList(converter));
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
Object result = extractor.extractData(response);
assertNull(result);
}
@Test
@SuppressWarnings("unchecked")
public void generics() throws IOException {
GenericHttpMessageConverter<String> converter = mock(GenericHttpMessageConverter.class);
List<HttpMessageConverter<?>> converters = createConverterList(converter);
@@ -154,8 +169,9 @@ public class HttpMessageConverterExtractorTests {
extractor = new HttpMessageConverterExtractor<List<String>>(type, converters);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.getBytes()));
given(converter.canRead(type, null, contentType)).willReturn(true);
given(converter.read(type, null, response)).willReturn(expected);
given(converter.read(eq(type), eq(null), any(HttpInputMessage.class))).willReturn(expected);
Object result = extractor.extractData(response);
@@ -16,6 +16,7 @@
package org.springframework.web.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
@@ -31,6 +32,7 @@ import org.junit.Test;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@@ -170,14 +172,15 @@ public class RestTemplateTests {
given(request.getHeaders()).willReturn(requestHeaders);
given(request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
String expected = "Hello World";
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.getBytes()));
given(converter.canRead(String.class, textPlain)).willReturn(true);
String expected = "Hello World";
given(converter.read(String.class, response)).willReturn(expected);
given(converter.read(eq(String.class), any(HttpInputMessage.class))).willReturn(expected);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
given(response.getStatusText()).willReturn(status.getReasonPhrase());
@@ -205,6 +208,7 @@ public class RestTemplateTests {
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream("Foo".getBytes()));
given(converter.canRead(String.class, contentType)).willReturn(false);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
@@ -232,14 +236,15 @@ public class RestTemplateTests {
given(request.getHeaders()).willReturn(requestHeaders);
given(request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
String expected = "Hello World";
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.getBytes()));
given(converter.canRead(String.class, textPlain)).willReturn(true);
String expected = "Hello World";
given(converter.read(String.class, response)).willReturn(expected);
given(converter.read(eq(String.class), any(HttpInputMessage.class))).willReturn(expected);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
@@ -405,14 +410,15 @@ public class RestTemplateTests {
converter.write(request, null, this.request);
given(this.request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
Integer expected = 42;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
Integer expected = 42;
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.toString().getBytes()));
given(converter.canRead(Integer.class, textPlain)).willReturn(true);
given(converter.read(Integer.class, response)).willReturn(expected);
given(converter.read(eq(Integer.class), any(HttpInputMessage.class))).willReturn(expected);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
given(response.getStatusText()).willReturn(status.getReasonPhrase());
@@ -437,14 +443,15 @@ public class RestTemplateTests {
converter.write(request, null, this.request);
given(this.request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
Integer expected = 42;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(textPlain);
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
Integer expected = 42;
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.toString().getBytes()));
given(converter.canRead(Integer.class, textPlain)).willReturn(true);
given(converter.read(Integer.class, response)).willReturn(expected);
given(converter.read(eq(Integer.class), any(HttpInputMessage.class))).willReturn(expected);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
@@ -615,14 +622,16 @@ public class RestTemplateTests {
converter.write(body, null, this.request);
given(this.request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
Integer expected = 42;
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
Integer expected = 42;
given(response.getBody()).willReturn(new ByteArrayInputStream(expected.toString().getBytes()));
given(converter.canRead(Integer.class, MediaType.TEXT_PLAIN)).willReturn(true);
given(converter.read(Integer.class, response)).willReturn(expected);
given(converter.read(eq(Integer.class), any(HttpInputMessage.class))).willReturn(expected);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
@@ -658,14 +667,15 @@ public class RestTemplateTests {
converter.write(requestBody, null, this.request);
given(this.request.execute()).willReturn(response);
given(errorHandler.hasError(response)).willReturn(false);
List<Integer> expected = Collections.singletonList(42);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentType(MediaType.TEXT_PLAIN);
responseHeaders.setContentLength(10);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
given(response.getHeaders()).willReturn(responseHeaders);
List<Integer> expected = Collections.singletonList(42);
given(response.getBody()).willReturn(new ByteArrayInputStream(new Integer(42).toString().getBytes()));
given(converter.canRead(intList.getType(), null, MediaType.TEXT_PLAIN)).willReturn(true);
given(converter.read(intList.getType(), null, response)).willReturn(expected);
given(converter.read(eq(intList.getType()), eq(null), any(HttpInputMessage.class))).willReturn(expected);
given(response.getStatusCode()).willReturn(HttpStatus.OK);
HttpStatus status = HttpStatus.OK;
given(response.getStatusCode()).willReturn(status);
@@ -0,0 +1,66 @@
/*
* Copyright 2002-2015 the original author 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.web.util;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.util.FileCopyUtils;
/**
* @author Brian Clozel
*/
public class ContentCachingRequestWrapperTests {
protected static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
protected static final String CHARSET = "UTF-8";
private MockHttpServletRequest request;
@Before
public void setup() throws Exception {
this.request = new MockHttpServletRequest();
}
@Test
public void cachedContent() throws Exception {
this.request.setMethod("GET");
this.request.setCharacterEncoding(CHARSET);
this.request.setContent("Hello World".getBytes(CHARSET));
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request);
byte[] response = FileCopyUtils.copyToByteArray(wrapper.getInputStream());
Assert.assertArrayEquals(response, wrapper.getContentAsByteArray());
}
@Test
public void requestParams() throws Exception {
this.request.setMethod("POST");
this.request.setContentType(FORM_CONTENT_TYPE);
this.request.setCharacterEncoding(CHARSET);
this.request.setParameter("first", "value");
this.request.setParameter("second", new String[] {"foo", "bar"});
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(this.request);
// getting request parameters will consume the request body
Assert.assertFalse(wrapper.getParameterMap().isEmpty());
Assert.assertEquals("first=value&second=foo&second=bar", new String(wrapper.getContentAsByteArray()));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,11 +31,16 @@ import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
/**
* Unit tests for {@link org.springframework.web.util.UriComponentsBuilder}.
*
* @author Arjen Poutsma
* @author Phillip Webb
* @author Oliver Gierke
@@ -232,6 +237,25 @@ public class UriComponentsBuilderTests {
assertEquals("bar@baz", result.getQueryParams().getFirst("foo"));
}
// Also see X-Forwarded-* related tests in ServletUriComponentsBuilderTests
@Test
public void fromHttpRequest() throws URISyntaxException {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setScheme("http");
request.setServerName("localhost");
request.setServerPort(-1);
request.setRequestURI("/path");
request.setQueryString("a=1");
UriComponents result = UriComponentsBuilder.fromHttpRequest(new ServletServerHttpRequest(request)).build();
assertEquals("http", result.getScheme());
assertEquals("localhost", result.getHost());
assertEquals(-1, result.getPort());
assertEquals("/path", result.getPath());
assertEquals("a=1", result.getQuery());
}
@Test
public void path() throws URISyntaxException {
UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/foo/bar");
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2014 the original author 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,7 +30,6 @@ public class UriUtilsTests {
private static final String ENC = "UTF-8";
@Test
public void encodeScheme() throws UnsupportedEncodingException {
assertEquals("Invalid encoded result", "foobar+-.", UriUtils.encodeScheme("foobar+-.", ENC));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2008 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,12 +16,18 @@
package org.springframework.web.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.util.MultiValueMap;
import static org.junit.Assert.*;
@@ -30,6 +36,7 @@ import static org.junit.Assert.*;
* @author Juergen Hoeller
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
*/
public class WebUtilsTests {
@@ -98,4 +105,61 @@ public class WebUtilsTests {
assertEquals(Arrays.asList("red", "blue", "green"), variables.get("colors"));
}
@Test
public void isValidOrigin() {
List<String> allowedOrigins = new ArrayList<>();
MockHttpServletRequest servletRequest = new MockHttpServletRequest();
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
servletRequest.setServerName("mydomain1.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("mydomain1.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com:80");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("mydomain1.com");
servletRequest.setServerPort(443);
request.getHeaders().set(HttpHeaders.ORIGIN, "https://mydomain1.com");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("mydomain1.com");
servletRequest.setServerPort(443);
request.getHeaders().set(HttpHeaders.ORIGIN, "https://mydomain1.com:443");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("mydomain1.com");
servletRequest.setServerPort(123);
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com:123");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("mydomain1.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain2.com");
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("mydomain1.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "https://mydomain1.com");
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
servletRequest.setServerName("invalid-origin");
request.getHeaders().set(HttpHeaders.ORIGIN, "invalid-origin");
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
allowedOrigins = Arrays.asList("*");
servletRequest.setServerName("mydomain1.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain2.com");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
allowedOrigins = Arrays.asList("http://mydomain1.com");
servletRequest.setServerName("mydomain2.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain1.com");
assertTrue(WebUtils.isValidOrigin(request, allowedOrigins));
allowedOrigins = Arrays.asList("http://mydomain1.com");
servletRequest.setServerName("mydomain2.com");
request.getHeaders().set(HttpHeaders.ORIGIN, "http://mydomain3.com");
assertFalse(WebUtils.isValidOrigin(request, allowedOrigins));
}
}
@@ -140,6 +140,8 @@ class ViewControllerBeanDefinitionParser implements BeanDefinitionParser {
}
if (element.hasAttribute("context-relative")) {
redirectView.getPropertyValues().add("contextRelative", element.getAttribute("context-relative"));
} else {
redirectView.getPropertyValues().add("contextRelative", true);
}
if (element.hasAttribute("keep-query-params")) {
redirectView.getPropertyValues().add("propagateQueryParams", element.getAttribute("keep-query-params"));
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 the original author 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.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,6 +31,7 @@ import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.MediaType;
@@ -37,7 +39,10 @@ import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -79,6 +84,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return Collections.unmodifiableList(result);
}
/**
* Create the method argument value of the expected parameter type by
* reading from the given request.
@@ -102,7 +108,7 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
* from the given HttpInputMessage.
* @param <T> the expected type of the argument value to be created
* @param inputMessage the HTTP input message representing the current request
* @param methodParam the method argument
* @param methodParam the method parameter descriptor
* @param targetType the type of object to create, not necessarily the same as
* the method parameter type (e.g. for {@code HttpEntity<String>} method
* parameter the target type is String)
@@ -126,6 +132,8 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
}
Class<?> contextClass = methodParam.getContainingClass();
Class<T> targetClass = (Class<T>)
ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
for (HttpMessageConverter<?> converter : this.messageConverters) {
if (converter instanceof GenericHttpMessageConverter) {
@@ -138,8 +146,6 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return genericConverter.read(targetType, contextClass, inputMessage);
}
}
Class<T> targetClass = (Class<T>)
ResolvableType.forMethodParameter(methodParam, targetType).resolve(Object.class);
if (converter.canRead(targetClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Reading [" + targetClass.getName() + "] as \"" +
@@ -162,4 +168,41 @@ public abstract class AbstractMessageConverterMethodArgumentResolver implements
return new ServletServerHttpRequest(servletRequest);
}
/**
* Validate the request part if applicable.
* <p>The default implementation checks for {@code @javax.validation.Valid},
* Spring's {@link org.springframework.validation.annotation.Validated},
* and custom annotations whose name starts with "Valid".
* @param binder the DataBinder to be used
* @param methodParam the method parameter
* @see #isBindExceptionRequired
* @since 4.1.5
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
/**
* Whether to raise a fatal bind exception on validation errors.
* @param binder the data binder used to perform data binding
* @param methodParam the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}
* @since 4.1.5
*/
protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter methodParam) {
int i = methodParam.getParameterIndex();
Class<?>[] paramTypes = methodParam.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.web.servlet.mvc.method.annotation;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -25,13 +24,10 @@ import javax.servlet.http.Part;
import org.springframework.core.GenericCollectionTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
@@ -80,12 +76,13 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
super(messageConverters);
}
/**
* Supports the following:
* <ul>
* <li>Annotated with {@code @RequestPart}
* <li>Of type {@link MultipartFile} unless annotated with {@code @RequestParam}.
* <li>Of type {@code javax.servlet.http.Part} unless annotated with {@code @RequestParam}.
* <li>annotated with {@code @RequestPart}
* <li>of type {@link MultipartFile} unless annotated with {@code @RequestParam}
* <li>of type {@code javax.servlet.http.Part} unless annotated with {@code @RequestParam}
* </ul>
*/
@Override
@@ -117,7 +114,7 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
assertIsMultipartRequest(servletRequest);
MultipartHttpServletRequest multipartRequest =
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
String partName = getPartName(parameter);
Object arg;
@@ -153,7 +150,10 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
arg = readWithMessageConverters(inputMessage, parameter, parameter.getParameterType());
WebDataBinder binder = binderFactory.createBinder(request, arg, partName);
if (arg != null) {
validate(binder, parameter);
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + partName, binder.getBindingResult());
}
@@ -163,8 +163,8 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
}
}
RequestPart annot = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = (annot == null || annot.required());
RequestPart ann = parameter.getParameterAnnotation(RequestPart.class);
boolean isRequired = (ann == null || ann.required());
if (arg == null && isRequired) {
throw new MissingServletRequestPartException(partName);
@@ -180,41 +180,44 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
}
}
private String getPartName(MethodParameter parameter) {
RequestPart annot = parameter.getParameterAnnotation(RequestPart.class);
String partName = (annot != null ? annot.value() : "");
private String getPartName(MethodParameter methodParam) {
RequestPart ann = methodParam.getParameterAnnotation(RequestPart.class);
String partName = (ann != null ? ann.value() : "");
if (partName.length() == 0) {
partName = parameter.getParameterName();
Assert.notNull(partName, "Request part name for argument type [" + parameter.getParameterType().getName() +
"] not specified, and parameter name information not found in class file either.");
partName = methodParam.getParameterName();
if (partName == null) {
throw new IllegalArgumentException("Request part name for argument type [" +
methodParam.getNestedParameterType().getName() +
"] not specified, and parameter name information not found in class file either.");
}
}
return partName;
}
private boolean isMultipartFileCollection(MethodParameter parameter) {
Class<?> collectionType = getCollectionParameterType(parameter);
return (collectionType != null && collectionType.equals(MultipartFile.class));
private boolean isMultipartFileCollection(MethodParameter methodParam) {
Class<?> collectionType = getCollectionParameterType(methodParam);
return MultipartFile.class.equals(collectionType);
}
private boolean isMultipartFileArray(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType().getComponentType();
return (paramType != null && MultipartFile.class.equals(paramType));
private boolean isMultipartFileArray(MethodParameter methodParam) {
Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
return MultipartFile.class.equals(paramType);
}
private boolean isPartCollection(MethodParameter parameter) {
Class<?> collectionType = getCollectionParameterType(parameter);
private boolean isPartCollection(MethodParameter methodParam) {
Class<?> collectionType = getCollectionParameterType(methodParam);
return (collectionType != null && "javax.servlet.http.Part".equals(collectionType.getName()));
}
private boolean isPartArray(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType().getComponentType();
private boolean isPartArray(MethodParameter methodParam) {
Class<?> paramType = methodParam.getNestedParameterType().getComponentType();
return (paramType != null && "javax.servlet.http.Part".equals(paramType.getName()));
}
private Class<?> getCollectionParameterType(MethodParameter parameter) {
Class<?> paramType = parameter.getParameterType();
private Class<?> getCollectionParameterType(MethodParameter methodParam) {
Class<?> paramType = methodParam.getNestedParameterType();
if (Collection.class.equals(paramType) || List.class.isAssignableFrom(paramType)){
Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(parameter);
Class<?> valueType = GenericCollectionTypeResolver.getCollectionParameterType(methodParam);
if (valueType != null) {
return valueType;
}
@@ -222,36 +225,6 @@ public class RequestPartMethodArgumentResolver extends AbstractMessageConverterM
return null;
}
private void validate(WebDataBinder binder, MethodParameter parameter) throws MethodArgumentNotValidException {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
if (isBindingErrorFatal(parameter)) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
}
}
}
/**
* Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
* @param parameter the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}
*/
private boolean isBindingErrorFatal(MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
}
/**
* Inner class to avoid hard-coded dependency on Servlet 3.0 Part type...
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
@@ -32,8 +31,6 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.accept.ContentNegotiationManager;
@@ -47,16 +44,14 @@ import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
/**
* Resolves method arguments annotated with {@code @RequestBody} and handles
* return values from methods annotated with {@code @ResponseBody} by reading
* and writing to the body of the request or response with an
* {@link HttpMessageConverter}.
* Resolves method arguments annotated with {@code @RequestBody} and handles return
* values from methods annotated with {@code @ResponseBody} by reading and writing
* to the body of the request or response with an {@link HttpMessageConverter}.
*
* <p>An {@code @RequestBody} method argument is also validated if it is
* annotated with {@code @javax.validation.Valid}. In case of validation
* failure, {@link MethodArgumentNotValidException} is raised and results
* in a 400 response status code if {@link DefaultHandlerExceptionResolver}
* is configured.
* <p>An {@code @RequestBody} method argument is also validated if it is annotated
* with {@code @javax.validation.Valid}. In case of validation failure,
* {@link MethodArgumentNotValidException} is raised and results in a 400 response
* status code if {@link DefaultHandlerExceptionResolver} is configured.
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
@@ -93,7 +88,7 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
}
/**
* @throws MethodArgumentNotValidException if validation fails
* Throws MethodArgumentNotValidException if validation fails.
* @throws HttpMessageNotReadableException if {@link RequestBody#required()}
* is {@code true} and there is no body content or if there is no suitable
* converter to read the content with.
@@ -102,52 +97,22 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object argument = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
WebDataBinder binder = binderFactory.createBinder(webRequest, argument, name);
if (argument != null) {
validate(binder, parameter);
}
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return argument;
}
private void validate(WebDataBinder binder, MethodParameter parameter) throws Exception {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
BindingResult bindingResult = binder.getBindingResult();
if (bindingResult.hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, bindingResult);
}
}
break;
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
}
/**
* Whether to raise a {@link MethodArgumentNotValidException} on validation errors.
* @param binder the data binder used to perform data binding
* @param parameter the method argument
* @return {@code true} if the next method argument is not of type {@link Errors}.
*/
private boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {
int i = parameter.getParameterIndex();
Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();
boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));
return !hasBindingResult;
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
return arg;
}
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest,
MethodParameter methodParam, Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException {
final HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
HttpInputMessage inputMessage = new ServletServerHttpRequest(servletRequest);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,6 @@ package org.springframework.web.servlet.mvc.method.annotation;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
@@ -37,8 +34,6 @@ import org.springframework.web.method.ControllerAdviceBean;
*/
class ResponseBodyAdviceChain {
private static final Log logger = LogFactory.getLog(ResponseBodyAdviceChain.class);
private final List<Object> advice;
@@ -57,9 +52,6 @@ class ResponseBodyAdviceChain {
ServerHttpRequest request, ServerHttpResponse response) {
if (this.advice != null) {
if (logger.isDebugEnabled()) {
logger.debug("Invoking ResponseBodyAdvice chain for body=" + body);
}
for (Object advice : this.advice) {
if (advice instanceof ControllerAdviceBean) {
ControllerAdviceBean adviceBean = (ControllerAdviceBean) advice;
@@ -79,9 +71,6 @@ class ResponseBodyAdviceChain {
throw new IllegalStateException("Expected ResponseBodyAdvice: " + advice);
}
}
if (logger.isDebugEnabled()) {
logger.debug("After ResponseBodyAdvice chain body=" + body);
}
}
return body;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,7 +50,7 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
/**
* @param annotationNotRequired if "true", non-simple method arguments and
* return values are considered model attributes with or without a
* {@code @ModelAttribute} annotation.
* {@code @ModelAttribute} annotation
*/
public ServletModelAttributeMethodProcessor(boolean annotationNotRequired) {
super(annotationNotRequired);
@@ -62,21 +62,22 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
* request parameter if the name matches to the model attribute name and
* if there is an appropriate type conversion strategy. If none of these
* are true delegate back to the base class.
* @see #createAttributeFromRequestValue(String, String, MethodParameter, WebDataBinderFactory, NativeWebRequest)
* @see #createAttributeFromRequestValue
*/
@Override
protected final Object createAttribute(String attributeName, MethodParameter parameter,
protected final Object createAttribute(String attributeName, MethodParameter methodParam,
WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {
String value = getRequestValueForAttribute(attributeName, request);
if (value != null) {
Object attribute = createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request);
Object attribute = createAttributeFromRequestValue(
value, attributeName, methodParam, binderFactory, request);
if (attribute != null) {
return attribute;
}
}
return super.createAttribute(attributeName, parameter, binderFactory, request);
return super.createAttribute(attributeName, methodParam, binderFactory, request);
}
/**
@@ -103,9 +104,8 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
@SuppressWarnings("unchecked")
protected final Map<String, String> getUriTemplateVariables(NativeWebRequest request) {
Map<String, String> variables =
(Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
Map<String, String> variables = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
return (variables != null ? variables : Collections.<String, String>emptyMap());
}
@@ -116,23 +116,23 @@ public class ServletModelAttributeMethodProcessor extends ModelAttributeMethodPr
* {@link Converter} that can perform the conversion.
* @param sourceValue the source value to create the model attribute from
* @param attributeName the name of the attribute, never {@code null}
* @param parameter the method parameter
* @param methodParam the method parameter
* @param binderFactory for creating WebDataBinder instance
* @param request the current request
* @return the created model attribute, or {@code null}
* @throws Exception
*/
protected Object createAttributeFromRequestValue(String sourceValue, String attributeName,
MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request)
MethodParameter methodParam, WebDataBinderFactory binderFactory, NativeWebRequest request)
throws Exception {
DataBinder binder = binderFactory.createBinder(request, null, attributeName);
ConversionService conversionService = binder.getConversionService();
if (conversionService != null) {
TypeDescriptor source = TypeDescriptor.valueOf(String.class);
TypeDescriptor target = new TypeDescriptor(parameter);
TypeDescriptor target = new TypeDescriptor(methodParam);
if (conversionService.canConvert(source, target)) {
return binder.convertIfNecessary(sourceValue, parameter.getParameterType(), parameter);
return binder.convertIfNecessary(sourceValue, methodParam.getParameterType(), methodParam);
}
}
return null;
@@ -123,7 +123,8 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
private boolean hasScheme(String link) {
int schemeIndex = link.indexOf(":");
return schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/");
return (schemeIndex > 0 && !link.substring(0, schemeIndex).contains("/"))
|| link.indexOf("//") == 0;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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 javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ServletContextResource;
/**
@@ -164,19 +165,19 @@ public class PathResourceResolver extends AbstractResourceResolver {
String locationPath;
if (resource instanceof UrlResource) {
resourcePath = resource.getURL().toExternalForm();
locationPath = location.getURL().toExternalForm();
locationPath = StringUtils.cleanPath(location.getURL().toString());
}
else if (resource instanceof ClassPathResource) {
resourcePath = ((ClassPathResource) resource).getPath();
locationPath = ((ClassPathResource) location).getPath();
locationPath = StringUtils.cleanPath(((ClassPathResource) location).getPath());
}
else if (resource instanceof ServletContextResource) {
resourcePath = ((ServletContextResource) resource).getPath();
locationPath = ((ServletContextResource) location).getPath();
locationPath = StringUtils.cleanPath(((ServletContextResource) location).getPath());
}
else {
resourcePath = resource.getURL().getPath();
locationPath = location.getURL().getPath();
locationPath = StringUtils.cleanPath(location.getURL().getPath());
}
locationPath = (locationPath.endsWith("/") || locationPath.isEmpty() ? locationPath : locationPath + "/");
if (!resourcePath.startsWith(locationPath)) {
@@ -21,7 +21,6 @@ import java.io.InputStream;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import javax.activation.FileTypeMap;
import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletException;
@@ -30,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
@@ -51,29 +51,30 @@ import org.springframework.web.servlet.support.WebContentGenerator;
* (according to the guidelines of Page Speed, YSlow, etc.) by allowing for flexible cache settings
* ({@linkplain #setCacheSeconds "cacheSeconds" property}, last-modified support).
*
* <p>The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource} locations
* from which static resources are allowed to be served by this handler. For a given request, the
* list of locations will be consulted in order for the presence of the requested resource, and the
* <p>The {@linkplain #setLocations "locations" property} takes a list of Spring {@link Resource}
* locations from which static resources are allowed to be served by this handler. For a given request,
* the list of locations will be consulted in order for the presence of the requested resource, and the
* first found match will be written to the response, with {@code Expires} and {@code Cache-Control}
* headers set as configured. The handler also properly evaluates the {@code Last-Modified} header
* (if present) so that a {@code 304} status code will be returned as appropriate, avoiding unnecessary
* overhead for resources that are already cached by the client. The use of {@code Resource} locations
* allows resource requests to easily be mapped to locations other than the web application root. For
* example, resources could be served from a classpath location such as "classpath:/META-INF/public-web-resources/",
* allowing convenient packaging and serving of resources such as a JavaScript library from within jar files.
* allows resource requests to easily be mapped to locations other than the web application root.
* For example, resources could be served from a classpath location such as
* "classpath:/META-INF/public-web-resources/", allowing convenient packaging and serving of resources
* such as a JavaScript library from within jar files.
*
* <p>To ensure that users with a primed browser cache get the latest changes to application-specific
* resources upon deployment of new versions of the application, it is recommended that a version string
* is used in the URL mapping pattern that selects this handler. Such patterns can be easily parameterized
* using Spring EL. See the reference manual for further examples of this approach.
* resources upon deployment of new versions of the application, it is recommended that a version
* string is used in the URL mapping pattern that selects this handler. Such patterns can be easily
* parameterized using Spring EL. See the reference manual for further examples of this approach.
*
* <p>For various front-end needs &mdash; such as ensuring that users with a primed browser cache get the
* latest changes, or serving variations of resources (e.g., minified versions) &mdash;
* <p>For various front-end needs &mdash; such as ensuring that users with a primed browser cache
* get the latest changes, or serving variations of resources (e.g., minified versions) &mdash;
* {@link org.springframework.web.servlet.resource.ResourceResolver}s can be configured.
*
* <p>This handler can be configured through use of a
* {@link org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry} or the {@code <mvc:resources/>}
* XML configuration element.
* {@link org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry}
* or the {@code <mvc:resources/>} XML configuration element.
*
* @author Keith Donald
* @author Jeremy Grelle
@@ -118,9 +119,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
/**
* Configure the list of {@link ResourceResolver}s to use.
*
* <p>By default {@link PathResourceResolver} is configured. If using this property, it
* is recommended to add {@link PathResourceResolver} as the last resolver.
* <p>By default {@link PathResourceResolver} is configured. If using this property,
* it is recommended to add {@link PathResourceResolver} as the last resolver.
*/
public void setResourceResolvers(List<ResourceResolver> resourceResolvers) {
this.resourceResolvers.clear();
@@ -272,7 +272,8 @@ public class ResourceHttpRequestHandler extends WebContentGenerator implements H
if (resource == null || getResourceTransformers().isEmpty()) {
return resource;
}
ResourceTransformerChain transformChain = new DefaultResourceTransformerChain(resolveChain, getResourceTransformers());
ResourceTransformerChain transformChain =
new DefaultResourceTransformerChain(resolveChain, getResourceTransformers());
resource = transformChain.transform(request, resource);
return resource;
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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.web.servlet.resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -129,6 +130,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
if (this.handlerMap.isEmpty() && logger.isDebugEnabled()) {
logger.debug("No resource handling mappings found");
}
if(!this.handlerMap.isEmpty()) {
this.autodetect = false;
}
}
}
@@ -188,6 +192,8 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
* public use.
* <p>It is expected that the given path is what Spring MVC would use for
* request mapping purposes, i.e. excluding context and servlet path portions.
* <p>If several handler mappings match, the handler used will be the one
* configured with the most specific pattern.
* @param lookupPath the lookup path to check
* @return the resolved public URL path, or {@code null} if unresolved
*/
@@ -195,25 +201,32 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
if (logger.isTraceEnabled()) {
logger.trace("Getting resource URL for lookupPath=" + lookupPath);
}
List<String> matchingPatterns = new ArrayList<String>();
for (String pattern : this.handlerMap.keySet()) {
if (!getPathMatcher().match(pattern, lookupPath)) {
continue;
if (getPathMatcher().match(pattern, lookupPath)) {
matchingPatterns.add(pattern);
}
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
if (logger.isTraceEnabled()) {
logger.trace("Invoking ResourceResolverChain for URL pattern=\"" + pattern + "\"");
}
if (!matchingPatterns.isEmpty()) {
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
Collections.sort(matchingPatterns, patternComparator);
for(String pattern : matchingPatterns) {
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
if (logger.isTraceEnabled()) {
logger.trace("Invoking ResourceResolverChain for URL pattern=\"" + pattern + "\"");
}
ResourceHttpRequestHandler handler = this.handlerMap.get(pattern);
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
String resolved = chain.resolveUrlPath(pathWithinMapping, handler.getLocations());
if (resolved == null) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved public resource URL path=\"" + resolved + "\"");
}
return pathMapping + resolved;
}
ResourceHttpRequestHandler handler = this.handlerMap.get(pattern);
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
String resolved = chain.resolveUrlPath(pathWithinMapping, handler.getLocations());
if (resolved == null) {
throw new IllegalStateException("Failed to get public resource URL path for " + pathWithinMapping);
}
if (logger.isTraceEnabled()) {
logger.trace("Resolved public resource URL path=\"" + resolved + "\"");
}
return pathMapping + resolved;
}
logger.debug("No matching resource mapping");
return null;
@@ -265,6 +265,8 @@ public class RequestContext {
// context-param in web.xml, if any.
this.defaultHtmlEscape = WebUtils.getDefaultHtmlEscape(this.webApplicationContext.getServletContext());
// Determine response-encoded HTML escape setting from the "responseEncodedHtmlEscape"
// context-param in web.xml, if any.
this.responseEncodedHtmlEscape = WebUtils.getResponseEncodedHtmlEscape(this.webApplicationContext.getServletContext());
this.urlPathHelper = new UrlPathHelper();
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,11 +18,14 @@ package org.springframework.web.servlet.support;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpRequest;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UrlPathHelper;
import org.springframework.web.util.WebUtils;
@@ -60,6 +63,9 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
this.originalPath = other.originalPath;
}
// Factory methods based on a HttpServletRequest
/**
* Prepare a builder from the host, port, scheme, and context path of the
* given HttpServletRequest.
@@ -73,7 +79,6 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
/**
* Prepare a builder from the host, port, scheme, context path, and
* servlet mapping of the given HttpServletRequest.
*
* <p>If the servlet is mapped by name, e.g. {@code "/main/*"}, the path
* will end with "/main". If the servlet is mapped otherwise, e.g.
* {@code "/"} or {@code "*.do"}, the result will be the same as
@@ -112,49 +117,21 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
* Initialize a builder with a scheme, host,and port (but not path and query).
*/
private static ServletUriComponentsBuilder initFromRequest(HttpServletRequest request) {
String scheme = request.getScheme();
String host = request.getServerName();
int port = request.getServerPort();
String hostHeader = request.getHeader("X-Forwarded-Host");
if (StringUtils.hasText(hostHeader)) {
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
String hostToUse = hosts[0];
if (hostToUse.contains(":")) {
String[] hostAndPort = StringUtils.split(hostToUse, ":");
host = hostAndPort[0];
port = Integer.parseInt(hostAndPort[1]);
}
else {
host = hostToUse;
port = -1;
}
}
String portHeader = request.getHeader("X-Forwarded-Port");
if (StringUtils.hasText(portHeader)) {
port = Integer.parseInt(portHeader);
}
String protocolHeader = request.getHeader("X-Forwarded-Proto");
if (StringUtils.hasText(protocolHeader)) {
scheme = protocolHeader;
}
HttpRequest httpRequest = new ServletServerHttpRequest(request);
UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
String scheme = uriComponents.getScheme();
String host = uriComponents.getHost();
int port = uriComponents.getPort();
ServletUriComponentsBuilder builder = new ServletUriComponentsBuilder();
builder.scheme(scheme);
builder.host(host);
if (scheme.equals("http") && port != 80 || scheme.equals("https") && port != 443) {
if (("http".equals(scheme) && port != 80) || ("https".equals(scheme) && port != 443)) {
builder.port(port);
}
return builder;
}
private void initPath(String path) {
this.originalPath = path;
replacePath(path);
}
private static String prependForwardedPrefix(HttpServletRequest request, String path) {
String prefix = request.getHeader("X-Forwarded-Prefix");
if (StringUtils.hasText(prefix)) {
@@ -198,9 +175,8 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
return fromRequest(getCurrentRequest());
}
/**
* Get the request through {@link RequestContextHolder}.
* Obtain current request through {@link RequestContextHolder}.
*/
protected static HttpServletRequest getCurrentRequest() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
@@ -211,12 +187,17 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder {
return servletRequest;
}
private void initPath(String path) {
this.originalPath = path;
replacePath(path);
}
/**
* Remove any path extension from the {@link HttpServletRequest#getRequestURI()
* requestURI}. This method must be invoked before any calls to {@link #path(String)}
* or {@link #pathSegment(String...)}.
* <pre>
*
* GET http://foo.com/rest/books/6.json
*
* ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequestUri(this.request);
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -186,6 +186,7 @@ public class FreeMarkerView extends AbstractTemplateView {
* {@link ObjectWrapper#DEFAULT_WRAPPER default wrapper} if none specified.
* @see freemarker.template.Configuration#getObjectWrapper()
*/
@SuppressWarnings("deprecation")
protected ObjectWrapper getObjectWrapper() {
ObjectWrapper ow = getConfiguration().getObjectWrapper();
return (ow != null ? ow : ObjectWrapper.DEFAULT_WRAPPER);
@@ -95,6 +95,7 @@ import org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor;
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
import org.springframework.web.servlet.mvc.ParameterizableViewController;
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@@ -648,9 +649,17 @@ public class MvcNamespaceTests {
SimpleUrlHandlerMapping hm = this.appContext.getBean(SimpleUrlHandlerMapping.class);
assertNotNull(hm);
assertNotNull(hm.getUrlMap().get("/path"));
assertNotNull(hm.getUrlMap().get("/old"));
assertNotNull(hm.getUrlMap().get("/bad"));
ParameterizableViewController viewController = (ParameterizableViewController) hm.getUrlMap().get("/path");
assertNotNull(viewController);
assertEquals("home", viewController.getViewName());
ParameterizableViewController redirectViewController = (ParameterizableViewController) hm.getUrlMap().get("/old");
assertNotNull(redirectViewController);
assertThat(redirectViewController.getView(), Matchers.instanceOf(RedirectView.class));
ParameterizableViewController statusViewController = (ParameterizableViewController) hm.getUrlMap().get("/bad");
assertNotNull(statusViewController);
assertEquals(404, statusViewController.getStatusCode().value());
BeanNameUrlHandlerMapping beanNameMapping = this.appContext.getBean(BeanNameUrlHandlerMapping.class);
assertNotNull(beanNameMapping);
@@ -99,7 +99,8 @@ public class CssLinkResourceTransformerTests {
TransformedResource transformedResource = (TransformedResource) resource;
String expected = "@import url(\"http://example.org/fonts/css\");\n" +
"body { background: url(\"file:///home/spring/image.png\") }";
"body { background: url(\"file:///home/spring/image.png\") }\n" +
"figure { background: url(\"//example.org/style.css\")}";
String result = new String(transformedResource.getByteArray(), "UTF-8");
result = StringUtils.deleteAny(result, "\r");
assertEquals(expected, result);
@@ -108,6 +109,8 @@ public class CssLinkResourceTransformerTests {
.resolveUrlPath("http://example.org/fonts/css", Arrays.asList(externalCss));
Mockito.verify(resolverChain, Mockito.never())
.resolveUrlPath("file:///home/spring/image.png", Arrays.asList(externalCss));
Mockito.verify(resolverChain, Mockito.never())
.resolveUrlPath("//example.org/style.css", Arrays.asList(externalCss));
}
@Test
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,4 +108,13 @@ public class PathResourceResolverTests {
assertTrue(this.resolver.checkResource(resource, servletContextLocation));
}
// SPR-12624
@Test
public void checkRelativeLocation() throws Exception {
String locationUrl= new UrlResource(getClass().getResource("./test/")).getURL().toExternalForm();
Resource location = new UrlResource(locationUrl.replace("/springframework","/../org/springframework"));
assertNotNull(this.resolver.resolveResource(null, "main.css", Arrays.asList(location), null));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author 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,11 +21,17 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.mock.web.test.MockServletContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import static org.junit.Assert.*;
@@ -38,6 +44,8 @@ import static org.junit.Assert.*;
*/
public class ResourceUrlProviderTests {
private List<Resource> locations;
private ResourceUrlProvider translator;
private ResourceHttpRequestHandler handler;
@@ -47,9 +55,9 @@ public class ResourceUrlProviderTests {
@Before
public void setUp() {
List<Resource> locations = new ArrayList<Resource>();
locations.add(new ClassPathResource("test/", getClass()));
locations.add(new ClassPathResource("testalternatepath/", getClass()));
this.locations = new ArrayList<Resource>();
this.locations.add(new ClassPathResource("test/", getClass()));
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
this.handler = new ResourceHttpRequestHandler();
this.handler.setLocations(locations);
@@ -88,4 +96,56 @@ public class ResourceUrlProviderTests {
this.translator.setHandlerMap(this.handlerMap);
}
// SPR-12647
@Test
public void bestPatternMatch() throws Exception {
ResourceHttpRequestHandler otherHandler = new ResourceHttpRequestHandler();
otherHandler.setLocations(this.locations);
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
versionStrategyMap.put("/**", new ContentVersionStrategy());
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(versionStrategyMap);
List<ResourceResolver> resolvers = new ArrayList<ResourceResolver>();
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
otherHandler.setResourceResolvers(resolvers);
this.handlerMap.put("/resources/*.css", otherHandler);
initTranslator();
String url = this.translator.getForLookupPath("/resources/foo.css");
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
}
// SPR-12592
@Test
public void initializeOnce() throws Exception {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(new MockServletContext());
context.register(HandlerMappingConfiguration.class);
context.refresh();
ResourceUrlProvider translator = context.getBean(ResourceUrlProvider.class);
assertThat(translator.getHandlerMap(), Matchers.hasKey("/resources/**"));
assertFalse(translator.isAutodetect());
}
@Configuration
public static class HandlerMappingConfiguration {
@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
ResourceHttpRequestHandler handler = new ResourceHttpRequestHandler();
HashMap<String, ResourceHttpRequestHandler> handlerMap = new HashMap<String, ResourceHttpRequestHandler>();
handlerMap.put("/resources/**", handler);
SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
hm.setUrlMap(handlerMap);
return hm;
}
@Bean
public ResourceUrlProvider resourceUrlProvider() {
return new ResourceUrlProvider();
}
}
}

Some files were not shown because too many files have changed in this diff Show More