Compare commits

...

153 Commits

Author SHA1 Message Date
Lukasz Lenart bb7cce26e9 [maven-release-plugin] prepare release STRUTS_6_2_0 2023-06-28 09:13:07 +02:00
Lukasz Lenart 7a92cdcbec Merge pull request #692 from apache/WW-5310-fragment
[WW-5310] Supports fragment in URL
2023-06-28 08:55:46 +02:00
Lukasz Lenart 4a85b6c4f4 WW-5310 Deprecates the old API in favour of new one 2023-06-27 20:18:38 +02:00
Lukasz Lenart 3891a6bc5c Merge pull request #693 from atlassian/WW-5314-log-jakarta
WW-5314 Do not log warnings for bad user input from JakartaMultiPartRequest
2023-06-27 08:45:47 +02:00
Kusal Kithul-Godage 71050c446d WW-5314 Update log level in JakartaStreamMultiPartRequest 2023-06-27 13:04:09 +10:00
Kusal Kithul-Godage de5d741b61 WW-5314 Do not log warnings for bad user input from JakartaMultiPartRequest 2023-06-27 12:55:52 +10:00
Lukasz Lenart 13013e7565 Merge pull request #691 from apache/WW-5261-tag-utils
[WW-5261] Avoids creating ValueStack if no ActionContext is available
2023-06-19 09:27:17 +02:00
Yasser Zamani 5a67d58b75 add some improvements 2023-06-04 14:22:49 +04:30
Lukasz Lenart 93304a3ee5 WW-5261 Avoids creating ValueStack if no ActionContext is available 2023-05-29 15:55:22 +02:00
Lukasz Lenart 97137bd0f9 WW-5310 Supports fragment in URL 2023-05-29 15:52:38 +02:00
Lukasz Lenart 3ef77471dd Merge pull request #690 from JCgH4164838Gh792C124B5/localS2_62_SecurityMemberTestUpdate1
WW-5288 follow-up test case updates
2023-05-29 08:37:41 +02:00
JCgH4164838Gh792C124B5 269a102749 Update:
- Add a few additional tests to SecurityMemberAccessTest.
- Rename some existing tests involving non-static methods to more
accurately reflect that.
- Add one minor optimization to SecurityMemberAccess.
2023-05-28 19:43:47 -04:00
Lukasz Lenart 6d0a4bb0c6 Merge pull request #689 from apache/WW-5310-equal-sign
[WW-5310] Properly parses param value with equal sign
2023-05-28 09:12:56 +02:00
Lukasz Lenart c92f542180 Merge pull request #688 from JCgH4164838Gh792C124B5/localS2_62_ExecWaitCleanup
[WW-5312] Attempt to fix ExecuteAndWaitInterceptor inconsistent processing
2023-05-28 09:07:46 +02:00
Lukasz Lenart b97339e31e WW-5310 Properly parses param value with equal sign 2023-05-24 07:06:15 +02:00
Lukasz Lenart 88c847e535 Merge pull request #687 from atlassian/WW-5301-velocity-bean-selection
WW-5301 Fix custom VelocityManager bean selection
2023-05-23 07:24:20 +02:00
JCgH4164838Gh792C124B5 dd01a47ddb Update:
- Improve ExecuteAndWaitInterceptor state behaviour, added debug and trace
logging.
- Ensure StrutsBackgroundProcess thread done state always set on
completion.
- Fix SessionMap processing issue caused by divergence of method
signatures, which can result in the ancestor methods getting called
(bypassing actual session processing).
- Update SessionMapTest to correspond to changes, add tests to try and
detect some (put/remove) method signature behaviour changes.
2023-05-20 18:05:04 -04:00
Kusal Kithul-Godage 9a7398f9af WW-5301 Fix custom VelocityManager bean selection 2023-05-20 19:56:11 +10:00
Lukasz Lenart 50be77a788 Merge pull request #686 from apache/WW-5309-named-matcher
[WW-5309] Supports patterns starting with variable
2023-05-19 12:21:50 +02:00
Lukasz Lenart c057761776 Merge pull request #685 from apache/WW-5296-dtds
[WW-5296] Uses proper DTDs
2023-05-17 17:04:52 +02:00
Lukasz Lenart 23bbb10fe5 Merge pull request #681 from apache/dependabot/maven/org.springframework-spring-core-5.3.27
Bump spring-core from 5.3.26 to 5.3.27
2023-05-16 20:56:55 +02:00
Lukasz Lenart 1df2f0a365 WW-5309 Supports patterns starting with variable 2023-05-16 20:44:55 +02:00
Lukasz Lenart c99526ce1f WW-5296 Uses proper DTDs 2023-05-16 20:22:03 +02:00
Lukasz Lenart 1eedfe329b Merge pull request #678 from apache/WW-5302-unevaluated-id
[WW-5302] Evaluates the name attribute before assigning it to the id attribute
2023-05-16 20:18:17 +02:00
Lukasz Lenart f9f05dc979 WW-5302 Adds additional test case to cover evaluating action & method attribute at the same time 2023-05-16 20:00:18 +02:00
Lukasz Lenart 92705b92cf Merge pull request #684 from apache/dependabot/maven/org.testng-testng-7.5.1
Bump testng from 7.5 to 7.5.1
2023-05-16 19:54:18 +02:00
dependabot[bot] 5d9736747a Bump spring-core from 5.3.26 to 5.3.27
Bumps [spring-core](https://github.com/spring-projects/spring-framework) from 5.3.26 to 5.3.27.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.26...v5.3.27)

---
updated-dependencies:
- dependency-name: org.springframework:spring-core
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-16 17:50:43 +00:00
Lukasz Lenart 639f6f22ea Merge pull request #682 from fischey/master
Improve [WW-4434] - add documentation and rename existing ftl to achieve the wanted behaviour
2023-05-16 19:49:13 +02:00
Lukasz Lenart d87c4d2ec6 Merge pull request #680 from apache/WW-5304-depreacted
[WW-5304] Drops deprecated methods and fields in ActionContext
2023-05-16 19:47:28 +02:00
Lukasz Lenart b5e51bcf8c Merge pull request #679 from apache/WW-5280-no-params
[WW-5280] Cleans up NoParameters interfaces
2023-05-16 19:46:54 +02:00
Lukasz Lenart 0e85c6349a Merge pull request #683 from gregh3269/WW-5308-min-max-length
WW-5308 Java templates plugin, add minlength and maxlength to textarea.
2023-05-16 19:46:24 +02:00
dependabot[bot] 191fb8d130 Bump testng from 7.5 to 7.5.1
Bumps [testng](https://github.com/cbeust/testng) from 7.5 to 7.5.1.
- [Release notes](https://github.com/cbeust/testng/releases)
- [Changelog](https://github.com/testng-team/testng/blob/master/CHANGES.txt)
- [Commits](https://github.com/cbeust/testng/compare/7.5...7.5.1)

---
updated-dependencies:
- dependency-name: org.testng:testng
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-09 19:01:11 +00:00
Greg Huber 9085d144d7 WW-5308 Java templates plugin, add minlength and maxlength to textarea. 2023-05-09 09:40:54 +01:00
Rene Fischer 16df737502 renamed datetext.ftl to datetextfield.ftl to align with the intended behaviour see https://issues.apache.org/jira/browse/WW-4434 2023-05-05 13:27:04 +02:00
Rene Fischer 6f5aee13c0 Improve doc on s:datetextfield 2023-05-05 13:06:58 +02:00
Lukasz Lenart f0f9e42fa9 WW-5304 Drops deprecated methods and fields in ActionContext 2023-04-10 15:12:11 +02:00
Lukasz Lenart a6608c16d2 WW-5302 Evaluates attributes before using them to generate the id attribute 2023-04-10 11:01:48 +02:00
Lukasz Lenart 73eafddd73 WW-5280 Cleans up NoParameters interfaces 2023-04-09 12:00:20 +02:00
Lukasz Lenart 7f3c1c2944 Merge pull request #676 from atlassian/WW-5300-dispatcher
WW-5300 Make Dispatcher methods overridable
2023-04-07 15:30:12 +02:00
Lukasz Lenart f9042a75bd Merge pull request #675 from atlassian/WW-5299-actionchainresult
WW-5299 Clean up ActionChainResult
2023-04-07 15:29:44 +02:00
Lukasz Lenart 72d16da58e Merge pull request #674 from atlassian/WW-5298-strutsvelocitycontext
WW-5298 Clean up StrutsVelocityContext
2023-04-07 15:28:34 +02:00
Lukasz Lenart 7d1c821015 Merge pull request #677 from apache/WW-5295-local-time
[WW-5295] Adds support for java.time.LocalTime to <s:date/> tag
2023-04-07 15:13:12 +02:00
Lukasz Lenart 34208e6960 WW-5295 Adds support for java.time.LocalTime to <s:date/> tag 2023-03-30 19:45:56 +02:00
Lukasz Lenart 365f39e9f4 Merge pull request #673 from apache/WW-5289-executor
[WW-5289] Fixes creating executor to avoid locking JVM on shutdown
2023-03-30 19:26:29 +02:00
Kusal Kithul-Godage d31c8c4027 WW-5298 Clean up StrutsVelocityContext 2023-03-29 19:52:03 +11:00
Kusal Kithul-Godage d47fa818ed WW-5299 Clean up ActionChainResult 2023-03-29 11:35:16 +11:00
Kusal Kithul-Godage 63ab83632c WW-5300 Make Dispatcher methods overridable 2023-03-29 11:31:43 +11:00
Lukasz Lenart 1a3af1907a WW-5289 Fixes creating executor to avoid locking JVM on shutdown 2023-03-26 14:11:10 +02:00
Lukasz Lenart babbd5efda Merge pull request #672 from apache/dependabot/maven/org.springframework-spring-core-5.3.26
Bump spring-core from 5.3.23 to 5.3.26
2023-03-24 06:46:46 +01:00
dependabot[bot] e2176d5c21 Bump spring-core from 5.3.23 to 5.3.26
Bumps [spring-core](https://github.com/spring-projects/spring-framework) from 5.3.23 to 5.3.26.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.3.23...v5.3.26)

---
updated-dependencies:
- dependency-name: org.springframework:spring-core
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-23 23:22:52 +00:00
Lukasz Lenart 48e57b70bc Merge pull request #668 from atlassian/xml-config-provider-versatile
WW-5293 Allow loading XML configuration from other than filesystem
2023-03-22 06:32:40 +01:00
Kusal Kithul-Godage c11334c600 WW-5293 Add JavaDoc for XmlDocConfigurationProvider class 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage bd805cd686 WW-5293 Make more methods overridable 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 4bc790dd50 WW-5293 Split #buildResultTypeConfig from #addResultTypes and remove unused field and param 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage c27cf4833a WW-5293 Fix trying to clear immutable list 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 7fa67c290e WW-5293 Add protected #loadClass 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 3689d3f25f WW-5293 Define protected field addedResultTypes and split #buildResultConfig from #buildResults 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage fb03589c7b WW-5293 Split #buildInterceptorConfig from #loadInterceptors 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage f2e2c05950 WW-5293 Split #buildActionConfig from #addAction 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 8db9e39439 WW-5293 Split XmlConfigurationProvider into XmlDocConfigurationProvider Part 2 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 4f5e0ef49e WW-5293 Split XmlConfigurationProvider into XmlDocConfigurationProvider Part 1 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 250aeb3e0f WW-5293 Hide documents in XmlConfigurationProvider 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 92ac50c795 WW-5293 Make Struts DTDs static 2023-03-22 13:29:38 +11:00
Kusal Kithul-Godage 6aee0eb20d WW-5293 Update deprecations 2023-03-22 13:29:38 +11:00
Lukasz Lenart 960682ae8a Merge pull request #664 from atlassian/WW-5288-exemption-strict
WW-5288 Make excluded package exemption logic more strict
2023-03-21 19:29:45 +01:00
Lukasz Lenart bad445ba36 Merge pull request #670 from sdutry/issue/WW-5251
WW-5251 remove deprecated interfaces related to ServletConfigInterceptor
2023-03-15 08:50:51 +01:00
Lukasz Lenart 07c2e70b90 Merge pull request #671 from sdutry/issue/WW-5253
WW-5253 Remove deprecated methods from DefaultUrlHelper
2023-03-15 08:48:40 +01:00
Lukasz Lenart b5e7b43988 Merge pull request #669 from sdutry/issue/WW-5243
WW-5243 remove deprecated action prefix cross namespaces
2023-03-15 08:37:45 +01:00
Stefaan Dutry 61dec7e185 WW-5253 Remove deprecated methods from DefaultUrlHelper 2023-03-14 23:52:12 +01:00
Stefaan Dutry 95a6dff1f5 WW-5251 remove deprecated interfaces related to ServletConfigInterceptor 2023-03-14 00:04:32 +01:00
Stefaan Dutry 8b4f070d7c WW-5243 remove deprecated action prefix cross namespaces 2023-03-13 23:07:45 +01:00
Lukasz Lenart d204f9b17b Merge pull request #585 from sdutry/issue/WW-5196
WW-5196 use generics for RequestMap and ApplicationMap and correct SessionMap to also be of type <String, Object>
2023-03-13 07:05:37 +01:00
Lukasz Lenart ea9ff33a8c Merge pull request #665 from atlassian/WW-5266-max-filesize
WW-5266 Implement struts.multipart.maxFileSize
2023-03-13 06:58:32 +01:00
Stefaan Dutry 126c0a9d02 add missing license headers 2023-03-12 20:57:29 +01:00
Stefaan Dutry 3520d346a4 fix nonce test 2023-03-12 20:52:06 +01:00
Stefaan Dutry a45914b4b7 fix compilation failures after merging master 2023-03-12 20:01:04 +01:00
Stefaan Dutry 63d728460b Merge branch 'master' of https://github.com/apache/struts into issue/WW-5196 2023-03-12 19:47:43 +01:00
Kusal Kithul-Godage 0c2d218b68 WW-5266 Disable struts.multipart.maxFileSize by default 2023-03-12 20:04:27 +11:00
Lukasz Lenart c32be7279d Merge pull request #667 from atlassian/WW-5292-operations
WW-5292 Allow overriding of Operations classes in two filter setup and assorted clean up
2023-03-11 10:41:06 +01:00
Kusal Kithul-Godage d0bc243c97 WW-5292 Fix lack of Optional::get 2023-03-08 00:03:45 +11:00
Kusal Kithul-Godage 2a5257bc4c WW-5292 Clean up URL exclusion logic 2023-03-08 00:03:45 +11:00
Kusal Kithul-Godage 86028346a1 WW-5292 Add ability to override Operations classes in two filter setup 2023-03-08 00:03:45 +11:00
Kusal Kithul-Godage dff04146d0 WW-5292 Add integration test for forwarding from excluded url 2023-03-08 00:03:45 +11:00
Kusal Kithul-Godage e2a4b5fddd WW-5292 Clean up TwoFilterIntegrationTest further 2023-03-08 00:00:13 +11:00
Kusal Kithul-Godage 752088d4b3 WW-5292 Modernise unit tests 2023-03-08 00:00:04 +11:00
Kusal Kithul-Godage b4821330d5 WW-5288 Reinstate mistakenly deleted test classes and refactor 2023-03-07 20:03:00 +11:00
Lukasz Lenart 21b4228381 Merge pull request #666 from atlassian/config-manager-refactor
WW-5290 Refactor ConfigurationManager
2023-03-07 07:43:47 +01:00
Kusal Kithul-Godage 8f5b77ece1 WW-5290 Fix logging and needsReload loop 2023-03-04 13:46:19 +11:00
Kusal Kithul-Godage f69666b5c7 WW-5290 Refactor ConfigurationManager 2023-03-03 22:48:18 +11:00
Kusal Kithul-Godage 6cde7b4160 WW-5266 Implement struts.multipart.maxFileSize 2023-03-02 19:06:24 +11:00
Kusal Kithul-Godage acf68be1ee WW-5288 Export exempt classes as part of ConstantConfig 2023-03-02 18:14:54 +11:00
Kusal Kithul-Godage ff19dfee6f WW-5288 Make excluded package exemption logic more strict 2023-03-02 15:00:58 +11:00
Yasser Zamani ab03231484 Merge pull request #662 from apache/WW-5285-max-files
[WW-5285] Limits max number of files to upload at once
2023-03-01 21:30:20 +03:30
Lukasz Lenart 6a27f460c0 Merge pull request #660 from atlassian/WW-5268-excluded-package-exemptions
WW-5268 Ability to exempt classes from package exclusions
2023-02-28 08:26:15 +01:00
Lukasz Lenart 13c93aaa75 WW-5285 Uses Long and null to check if option has been defined 2023-02-28 08:10:31 +01:00
Lukasz Lenart ca637ef70c WW-5285 Limits max number of files to upload at once
Upgrades commons-fileupload to ver. 1.5 and sets default limit to 256 files
2023-02-25 10:42:21 +01:00
Lukasz Lenart f67e5f1ace Merge pull request #659 from atlassian/WW-5284-clean-up-validator
WW-5284 Refactor ActionValidatorManager implementations
2023-02-20 07:48:15 +01:00
Kusal Kithul-Godage d30870ac63 WW-5284 Delete unnecessary override 2023-02-20 13:08:44 +11:00
Lukasz Lenart 31a24ee709 Merge pull request #658 from apache/WW-5275-custom-csp
[WW-5275] Allows to provide a custom CspSettings per action
2023-02-18 16:14:54 +01:00
Kusal Kithul-Godage 5fcee890d5 WW-5268 Implement ability to specify exempt classes for package exclusions 2023-02-15 00:39:09 +11:00
Kusal Kithul-Godage 816e606ffe WW-5284 Refactor ActionValidatorManager implementations 2023-02-14 23:39:10 +11:00
Lukasz Lenart 68a401aacf WW-5275 Allows to provide a custom CspSettings per action 2023-02-12 17:04:01 +01:00
Lukasz Lenart 930c6de807 Merge pull request #657 from atlassian/WW-5279-xml-config-provider
WW-5279 Improve readability of XmlConfigurationProvider class
2023-02-01 07:23:19 +01:00
Kusal Kithul-Godage 2f7a50935b WW-5279 Improve readability of XmlConfigurationProvider class 2023-01-31 03:02:24 +11:00
Lukasz Lenart 533d7e3319 Merge pull request #656 from atlassian/WW-5278-clean-up-action-validators
WW-5278 Collect duplicated code into AbstractActionValidatorManager
2023-01-30 13:58:52 +01:00
Lukasz Lenart 7d40a81a40 Merge pull request #648 from atlassian/WW-5270-forwarding-from-excluded-url
WW-5270 Fix forwarding from Struts excluded URL
2023-01-30 13:56:42 +01:00
Kusal Kithul-Godage 9d71ed6663 WW-5278 Fix incorrect logging statement 2023-01-30 23:41:27 +11:00
Kusal Kithul-Godage 89e2d4fb14 WW-5278 Collect common code into AbstractActionValidatorManager 2023-01-30 23:40:58 +11:00
Kusal Kithul-Godage 0ce254dc48 WW-5270 Rework and fix Struts filter cleanup 2023-01-30 22:54:32 +11:00
Kusal Kithul-Godage 92c0103b38 WW-5270 Struts exclusion flag fix when forwarding 2023-01-30 21:54:16 +11:00
Kusal Kithul-Godage 6ea67a6d64 WW-5270 Test for forwarding from Struts excluded URL to Struts Action 2023-01-30 21:54:16 +11:00
Lukasz Lenart 2a85d0fbc4 Merge pull request #655 from apache/http-interceptor
[WW-4404] Http interceptor
2023-01-26 13:25:49 +01:00
Lukasz Lenart c5130477ba Merge pull request #654 from apache/WW-5276-cleanup
[WW-5276] Cleans up also wrapper request to avoid resource leak and potential DoS attack
2023-01-26 13:25:02 +01:00
Lukasz Lenart faa1867d63 Merge pull request #653 from apache/WW-5274-pell
[WW-5274] Marks the Pell multipart plugin as deprecated
2023-01-25 08:32:13 +01:00
Lukasz Lenart 3b2c6057e7 WW-4404 Implements HttpInterceptor 2023-01-23 09:38:50 +01:00
Lukasz Lenart 05d7196e6c WW-5276 Cleans up also wrapper request to avoid resource leak and potential DoS attack 2023-01-22 11:04:58 +01:00
Lukasz Lenart 474a340553 WW-5274 Marks the Pell multipart plugin as deprecated 2023-01-22 10:53:52 +01:00
Lukasz Lenart 46738c970e Merge pull request #651 from apache/WW-5277-freemarker
[WW-5277] Upgrades Freemarker to version 2.3.32
2023-01-22 10:46:13 +01:00
Lukasz Lenart cde1753341 WW-5277 Upgrades Freemarker to version 2.3.32 2023-01-16 07:47:40 +01:00
Lukasz Lenart d90bee49b5 Merge pull request #649 from apache/WW-5272-time
[WW-5272] Extends <s:date/> to support java.sql.Time
2023-01-04 17:17:58 +01:00
Lukasz Lenart 08de24588a WW-5272 Extends <s:date/> to support java.sql.Time 2022-12-29 10:54:00 +01:00
Lukasz Lenart bcf2e11518 Merge pull request #647 from apache/WW-5269-jackson
[WW-5269] Upgrades Jackson to version 2.14.1
2022-12-28 13:53:02 +01:00
Lukasz Lenart 3bad2d0cec WW-5269 Upgrades Jackson to version 2.14.1 2022-12-18 08:55:47 +01:00
Lukasz Lenart 266d2d4ed5 Merge pull request #645 from atlassian/WW-5265-remove-container-provider
WW-5265 Allow removal of a single/specific container provider
2022-12-14 07:03:23 +01:00
Kusal Kithul-Godage 45aa172386 WW-5265 Allow removal of a single/specific container provider from Struts configuration 2022-12-13 18:17:01 +11:00
Kusal Kithul-Godage b795a920a8 WW-5265 Formatting fixes 2022-12-13 18:17:01 +11:00
Lukasz Lenart 0d81cdd85d Merge pull request #646 from apache/WW-5264-xslt-cleanup
[WW-5264] Removes XSLTResult from struts-default.xml as it was moved in to plugin
2022-12-13 08:13:04 +01:00
Lukasz Lenart d11cf771b6 WW-5264 Uses proper parent package when testing XSLT result 2022-12-13 07:43:10 +01:00
Lukasz Lenart 63dd9536ca WW-5264 Removes XSLTResult from struts-default.xml as it was moved into plugin 2022-12-13 07:34:22 +01:00
Lukasz Lenart 56d905d726 Merge pull request #643 from apache/gh-permission
Applies permission to GH workflows
2022-12-09 10:36:39 +01:00
Lukasz Lenart 0b67350084 Applies permission to GH workflows 2022-12-09 10:25:59 +01:00
Lukasz Lenart 0e62b4b534 Merge pull request #641 from apache/WW-5264-xslt
[WW-5264] Moves XSLT result into a dedicated plugin
2022-12-09 10:07:03 +01:00
Lukasz Lenart 15dd2f2234 WW-5264 Moves XSLT result into a dedicated plugin 2022-12-09 09:45:29 +01:00
Lukasz Lenart 579796de5a Merge pull request #642 from atlassian/WW-5199-forward-action
WW-5199 Allow forwarding from/to actions
2022-12-09 09:09:04 +01:00
Kusal Kithul-Godage afd04ea5af WW-5199 Allow forwarding from/to actions 2022-12-09 00:26:40 +11:00
Lukasz Lenart 97691a106d Merge pull request #640 from apache/WW-5262-beans
[WW-5262] Extracts excluded classes and beans into dedicated XML config files
2022-12-04 12:09:00 +01:00
Lukasz Lenart b9b9aaf710 WW-5262 Extracts excluded classes and beans into dedicated XML config files 2022-12-04 11:20:03 +01:00
Lukasz Lenart 79eebf3717 Merge pull request #639 from apache/WW-5263-csp-naming
[WW-5263] Uses proper names for CSP, COOP and COEP interceptors
2022-12-04 11:18:27 +01:00
Lukasz Lenart ea15306aa5 WW-5263 Uses porper names for CSP, COOP and COEP interceptors 2022-12-01 07:18:52 +01:00
Lukasz Lenart 45b36a13ed Merge pull request #638 from apache/jira-autolink
Defines to autolink PRs to issues in ASF JIRA
2022-11-28 13:21:48 +01:00
Lukasz Lenart c6c5605160 Defines to autolink PRs to issues in ASF JIRA 2022-11-28 11:51:39 +01:00
Lukasz Lenart 0d9a874182 Merge pull request #637 from apache/owasp-check
Updates OWASP suppressions as some reports are false positive
2022-11-27 09:23:07 +01:00
Lukasz Lenart efd4c61a26 Updates OWASP supressions as some reports are false positive 2022-11-27 08:51:34 +01:00
Lukasz Lenart d166d79a66 [maven-release-plugin] prepare for next development iteration 2022-11-15 15:30:13 +01:00
Stefaan Dutry 65bc8aba4f small changes after review
-) make StringObjectEntry constructor have default access (sonar java:S5993)
-) change tabs to spaces for ApplicationMap.put
-) add @Override annotations where applicable
-) add test for StringObjectEntry
-) fix compilation of other tests after changes to SessionMap
2022-08-08 23:59:02 +02:00
Stefaan Dutry 6e64486738 remove unneeded toString call on String enumeration element 2022-08-08 19:37:38 +02:00
Stefaan Dutry a38df608fa fix another compilation issue after changes to sessionMap 2022-08-08 19:35:46 +02:00
Stefaan Dutry 85a535fc81 fix more compile issues 2022-08-08 19:34:49 +02:00
Stefaan Dutry 61017aff6e fix compile issue with SessionMap changes 2022-08-08 19:33:52 +02:00
Stefaan Dutry af4cdac3d1 fix SessionMapTest 2022-08-08 19:31:03 +02:00
Stefaan Dutry f49d6981b5 Merge branch 'master' of https://github.com/apache/struts into issue/WW-5196 2022-08-08 19:13:15 +02:00
Stefaan Dutry f1c3c8f819 WW-5196 rework SessionMap to also be defined with String keys and Object values 2022-08-08 19:12:26 +02:00
Stefaan Dutry 6ba4432fbe WW-5196 use generics for RequestMap and ApplicationMap 2022-08-06 20:28:55 +02:00
399 changed files with 7332 additions and 6679 deletions
+2
View File
@@ -14,3 +14,5 @@ github:
del_branch_on_merge: true
protected_branches:
master: { }
autolink_jira:
- WW
+9 -1
View File
@@ -20,6 +20,14 @@ on:
branches: [ "master" ]
pull_request:
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
actions: read
contents: read
# Needed to access OIDC token.
id-token: write
jobs:
analyze:
name: Analyze
@@ -38,7 +46,7 @@ jobs:
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
+2
View File
@@ -21,6 +21,8 @@ on:
branches:
- master
permissions: read-all
env:
MAVEN_OPTS: -Xmx2048m -Xms1024m
LANG: en_US.utf8
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-apps</artifactId>
<packaging>pom</packaging>
+2 -2
View File
@@ -24,12 +24,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>6.1.1</version>
<version>6.2.0</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
+6 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-showcase</artifactId>
@@ -99,6 +99,11 @@
<artifactId>struts2-velocity-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-xslt-plugin</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
@@ -21,16 +21,17 @@
package org.apache.struts2.showcase.chat;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class ChatLoginAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 1L;
private ChatService chatService;
private Map session;
private Map<String, Object> session;
private String name;
@@ -60,8 +61,8 @@ public class ChatLoginAction extends ActionSupport implements SessionAware {
}
// === SessionAware ===
public void setSession(Map session) {
this.session = session;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,17 +21,18 @@
package org.apache.struts2.showcase.chat;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class ChatLogoutAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 1L;
private ChatService chatService;
private Map session;
private Map<String, Object> session;
public ChatLogoutAction(ChatService chatService) {
@@ -50,8 +51,8 @@ public class ChatLogoutAction extends ActionSupport implements SessionAware {
}
// === SessionAware ===
public void setSession(Map session) {
this.session = session;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,16 +21,17 @@
package org.apache.struts2.showcase.chat;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class EnterRoomAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 1L;
private ChatService chatService;
private Map session;
private Map<String, Object> session;
private String roomName;
public String getRoomName() {
@@ -56,10 +57,9 @@ public class EnterRoomAction extends ActionSupport implements SessionAware {
return SUCCESS;
}
// === SessionAware ===
public void setSession(Map session) {
this.session = session;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,17 +21,18 @@
package org.apache.struts2.showcase.chat;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class ExitRoomAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 1L;
private String roomName;
private Map session;
private Map<String, Object> session;
public String getRoomName() {
return roomName;
@@ -54,9 +55,9 @@ public class ExitRoomAction extends ActionSupport implements SessionAware {
return SUCCESS;
}
// === SessionAware ===
public void setSession(Map session) {
this.session = session;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,10 +21,11 @@
package org.apache.struts2.showcase.chat;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class SendMessageToRoomAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 1L;
@@ -33,7 +34,7 @@ public class SendMessageToRoomAction extends ActionSupport implements SessionAwa
private String roomName;
private String message;
private Map session;
private Map<String, Object> session;
public SendMessageToRoomAction(ChatService chatService) {
@@ -67,9 +68,10 @@ public class SendMessageToRoomAction extends ActionSupport implements SessionAwa
return SUCCESS;
}
public void setSession(Map session) {
this.session = session;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,15 +21,16 @@
package org.apache.struts2.showcase.hangman;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class GetUpdatedHangmanAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 5506025785406043027L;
private Map session;
private Map<String, Object> session;
private Hangman hangman;
@@ -45,10 +46,6 @@ public class GetUpdatedHangmanAction extends ActionSupport implements SessionAwa
return SUCCESS;
}
public void setSession(Map session) {
this.session = session;
}
public Hangman getHangman() {
return hangman;
}
@@ -56,4 +53,9 @@ public class GetUpdatedHangmanAction extends ActionSupport implements SessionAwa
public void setHangman(Hangman hangman) {
this.hangman = hangman;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,15 +21,16 @@
package org.apache.struts2.showcase.hangman;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
public class GuessCharacterAction extends ActionSupport implements SessionAware {
private static final long serialVersionUID = 9050915577007590674L;
private Map session;
private Map<String, Object> session;
private Character character;
private Hangman hangman;
@@ -44,10 +45,6 @@ public class GuessCharacterAction extends ActionSupport implements SessionAware
return hangman;
}
public void setSession(Map session) {
this.session = session;
}
public void setCharacter(Character character) {
this.character = character;
}
@@ -55,4 +52,9 @@ public class GuessCharacterAction extends ActionSupport implements SessionAware
public Character getCharacter() {
return this.character;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -21,10 +21,11 @@
package org.apache.struts2.showcase.hangman;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.SessionAware;
import java.util.Map;
import org.apache.struts2.action.SessionAware;
import static org.apache.struts2.showcase.hangman.HangmanConstants.HANGMAN_SESSION_KEY;
public class StartHangmanAction extends ActionSupport implements SessionAware {
@@ -33,7 +34,7 @@ public class StartHangmanAction extends ActionSupport implements SessionAware {
private HangmanService service;
private Hangman hangman;
private Map session;
private Map<String, Object> session;
public StartHangmanAction(HangmanService service) {
@@ -53,8 +54,8 @@ public class StartHangmanAction extends ActionSupport implements SessionAware {
}
// === SessionAware ===
public void setSession(Map session) {
this.session = session;
}
@Override
public void withSession(Map<String, Object> session) {
this.session = session;
}
}
@@ -1,4 +1,6 @@
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -16,23 +18,24 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
package org.apache.struts2.showcase.servlet;
/**
* Actions that want access to the Principal information from HttpServletRequest object
* should implement this interface.
*
* <p>This interface is only relevant if the Action is used in a servlet environment.
* By using this interface you will not become tied to servlet environment.</p>
*
* @deprecated please use {@link org.apache.struts2.action.PrincipalAware} instead
*/
@Deprecated
public interface PrincipalAware {
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @deprecated please use {@link org.apache.struts2.action.PrincipalAware#withPrincipalProxy(PrincipalProxy)} instead
*/
@Deprecated
void setPrincipalProxy(PrincipalProxy principalProxy);
public class TestServlet extends HttpServlet {
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
switch (request.getPathInfo()) {
case "/forward":
getServletContext().getRequestDispatcher("/dispatcher/dispatch.action").forward(request, response);
break;
default:
response.sendError(404);
break;
}
}
}
@@ -21,9 +21,11 @@
package org.apache.struts2.showcase.xslt;
import com.opensymphony.xwork2.ActionSupport;
import org.apache.struts2.interceptor.ServletRequestAware;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.action.ServletRequestAware;
import java.util.Map;
import java.util.Properties;
@@ -53,10 +55,6 @@ public class JVMAction implements ServletRequestAware {
return servletRequest;
}
public void setServletRequest(HttpServletRequest servletRequest) {
this.servletRequest = servletRequest;
}
public Map<String, String> getEnvironment() {
return environment;
}
@@ -100,4 +98,9 @@ public class JVMAction implements ServletRequestAware {
this.systemProperties = systemProperties;
}
}
@Override
public void withServletRequest(HttpServletRequest request) {
this.servletRequest = request;
}
}
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="actionchaining" extends="struts-default" namespace="/actionchaining">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="async" extends="json-default" namespace="/async">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="conversion" namespace="/conversion" extends="struts-default">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="dispatcher" extends="struts-default" namespace="/dispatcher">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="filedownload" extends="struts-default" namespace="/filedownload">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="fileupload" extends="struts-default" namespace="/fileupload">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="freemarker" namespace="/freemarker" extends="struts-default">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="hangman" extends="struts-default" namespace="/hangman">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="interactive" namespace="/interactive" extends="struts-default">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="modelDriven" extends="struts-default" namespace="/modelDriven">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<!-- START SNIPPET: xworkSample -->
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="ui-tags" extends="velocity-default" namespace="/tags/ui">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<include file="struts-tags-ui.xml"/>
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="tiles" extends="tiles-default" namespace="/tiles">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="token" extends="struts-default" namespace="/token">
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
@@ -20,12 +20,15 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<bean type="org.apache.struts2.interceptor.exec.ExecutorProvider" class="org.apache.struts2.showcase.wait.ThreadPoolExecutorProvider"/>
<bean type="org.apache.struts2.interceptor.exec.ExecutorProvider" name="threadPool"
class="org.apache.struts2.showcase.wait.ThreadPoolExecutorProvider"/>
<constant name="struts.executor.provider" value="threadPool"/>
<package name="wait" extends="struts-default" namespace="/wait">
<default-action-ref name="index"/>
@@ -20,11 +20,11 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<package name="xslt" extends="struts-default" namespace="/xslt">
<package name="xslt" extends="xslt-default" namespace="/xslt">
<default-action-ref name="index"/>
<action name="index">
+3 -3
View File
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<!-- START SNIPPET: xworkSample -->
<struts>
@@ -44,7 +44,7 @@
<constant name="struts.serve.static" value="true" />
<constant name="struts.serve.static.browserCache" value="false" />
<constant name="struts.action.excludePattern" value=".*/images/.*\.gif,.*/img/.*\.gif,.*/styles/.*\.css,.*/js/.*\.js"/>
<constant name="struts.action.excludePattern" value=".*/images/.*\.gif,.*/img/.*\.gif,.*/styles/.*\.css,.*/js/.*\.js,/testServlet/.*"/>
<include file="struts-interactive.xml" />
@@ -142,6 +142,11 @@
<load-on-startup>4</load-on-startup>
</servlet>
<servlet>
<servlet-name>testServlet</servlet-name>
<servlet-class>org.apache.struts2.showcase.servlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dwr</servlet-name>
<url-pattern>/dwr/*</url-pattern>
@@ -162,6 +167,11 @@
<url-pattern>/async/receiveNewMessages</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>testServlet</servlet-name>
<url-pattern>/testServlet/*</url-pattern>
</servlet-mapping>
<!-- END SNIPPET: dwr -->
<!-- SNIPPET START: example.velocity.filter.chain
@@ -32,7 +32,6 @@ public class DispatcherResultTest {
final HtmlPage page = webClient.getPage(ParameterUtils.getBaseUrl() + "/dispatcher/dispatch.action");
DomElement div = page.getElementById("dispatcher-result");
Assert.assertEquals("This page is a result of \"dispatching\" to it from an action", div.asNormalizedText());
}
}
@@ -40,16 +39,10 @@ public class DispatcherResultTest {
@Test
public void testDispatchingToAction() throws Exception {
try (final WebClient webClient = new WebClient()) {
webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
final HtmlPage page = webClient.getPage(ParameterUtils.getBaseUrl() + "/dispatcher/forward.action");
//DomElement div = page.getElementById("dispatcher-result");
//Assert.assertEquals("This page is a result of \"dispatching\" to it from an action", div.asNormalizedText());
// support for forwarding to another action is broken on StrutsPrepareFilter/StrutsExecuteFilter
// it only works in StrutsPrepareAndExecuteFilter
// this will be fixed in Struts 6.1.x
Assert.assertEquals(404, page.getWebResponse().getStatusCode());
DomElement div = page.getElementById("dispatcher-result");
Assert.assertEquals("This page is a result of \"dispatching\" to it from an action", div.asNormalizedText());
}
}
@@ -0,0 +1,41 @@
/*
* $Id$
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 it.org.apache.struts2.showcase;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import org.junit.Assert;
import org.junit.Test;
public class ForwardTest {
@Test
public void testServletForwardingToAction() throws Exception {
try (final WebClient webClient = new WebClient()) {
// Struts excluded URL, as defined by struts.action.excludePattern
final HtmlPage page = webClient.getPage(ParameterUtils.getBaseUrl() + "/testServlet/forward");
DomElement div = page.getElementById("dispatcher-result");
Assert.assertEquals("This page is a result of \"dispatching\" to it from an action", div.asNormalizedText());
}
}
}
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-assembly</artifactId>
+8 -3
View File
@@ -29,7 +29,7 @@
</parent>
<artifactId>struts2-bom</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
<packaging>pom</packaging>
<name>Struts 2 Bill of Materials</name>
@@ -44,7 +44,7 @@
</licenses>
<properties>
<struts-version.version>6.1.1</struts-version.version>
<struts-version.version>6.2.0</struts-version.version>
<maven.site.skip>true</maven.site.skip>
<maven.site.deploy.skip>true</maven.site.deploy.skip>
</properties>
@@ -181,11 +181,16 @@
<artifactId>struts2-velocity-plugin</artifactId>
<version>${struts-version.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-xslt-plugin</artifactId>
<version>${struts-version.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<scm>
<tag>STRUTS_6_1_1</tag>
<tag>STRUTS_6_2_0</tag>
<connection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/struts.git</developerConnection>
<url>https://github.com/apache/struts/</url>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-bundles</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-osgi-admin-bundle</artifactId>
+2 -2
View File
@@ -21,8 +21,8 @@
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<constant name="struts.enable.DynamicMethodInvocation" value="false" />
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-bundles</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-osgi-demo-bundle</artifactId>
+2 -2
View File
@@ -20,8 +20,8 @@
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"https://struts.apache.org/dtds/struts-2.5.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 6.0//EN"
"https://struts.apache.org/dtds/struts-6.0.dtd">
<struts>
<!-- Set some Struts 2 constants relevant to the OSGi Plugin.
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-osgi-bundles</artifactId>
+1 -1
View File
@@ -24,7 +24,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>6.1.1</version>
<version>6.2.0</version>
</parent>
<artifactId>struts2-core</artifactId>
<packaging>jar</packaging>
@@ -20,12 +20,17 @@ package com.opensymphony.xwork2;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsException;
import java.util.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* <!-- START SNIPPET: description -->
@@ -96,15 +101,9 @@ public class ActionChainResult implements Result {
*/
private static final String CHAIN_HISTORY = "CHAIN_HISTORY";
/**
* The result parameter name to set the name of the action to chain to.
*/
public static final String SKIP_ACTIONS_PARAM = "skipActions";
private ActionProxy proxy;
private String actionName;
private String namespace;
private String methodName;
@@ -133,7 +132,6 @@ public class ActionChainResult implements Result {
this.skipActions = skipActions;
}
/**
* @param actionProxyFactory the actionProxyFactory to set
*/
@@ -172,7 +170,6 @@ public class ActionChainResult implements Result {
this.skipActions = actions;
}
public void setMethod(String method) {
this.methodName = method;
}
@@ -206,18 +203,14 @@ public class ActionChainResult implements Result {
throw new IllegalArgumentException("Invocation cannot be null!");
}
ValueStack stack = invocation.getInvocationContext().getValueStack();
String finalNamespace = this.namespace != null
? TextParseUtil.translateVariables(namespace, stack)
: invocation.getProxy().getNamespace();
String finalActionName = TextParseUtil.translateVariables(actionName, stack);
String finalMethodName = this.methodName != null
? TextParseUtil.translateVariables(this.methodName, stack)
: null;
String finalNamespace = namespace != null ? translateVariables(namespace) : invocation.getProxy()
.getNamespace();
String finalActionName = translateVariables(actionName);
String finalMethodName = methodName != null ? translateVariables(methodName) : null;
if (isInChainHistory(finalNamespace, finalActionName, finalMethodName)) {
addToHistory(finalNamespace, finalActionName, finalMethodName);
throw new StrutsException("Infinite recursion detected: " + ActionChainResult.getChainHistory().toString());
throw new StrutsException("Infinite recursion detected: " + ActionChainResult.getChainHistory());
}
if (ActionChainResult.getChainHistory().isEmpty() && invocation.getProxy() != null) {
@@ -225,7 +218,7 @@ public class ActionChainResult implements Result {
}
addToHistory(finalNamespace, finalActionName, finalMethodName);
Map<String, Object> extraContext = ActionContext.of(new HashMap<>())
Map<String, Object> extraContext = ActionContext.of()
.withValueStack(invocation.getInvocationContext().getValueStack())
.withParameters(invocation.getInvocationContext().getParameters())
.with(CHAIN_HISTORY, ActionChainResult.getChainHistory())
@@ -237,20 +230,25 @@ public class ActionChainResult implements Result {
proxy.execute();
}
@Override public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final ActionChainResult that = (ActionChainResult) o;
if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) return false;
if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false;
if (namespace != null ? !namespace.equals(that.namespace) : that.namespace != null) return false;
return true;
protected String translateVariables(String text) {
return TextParseUtil.translateVariables(text, ActionContext.getContext().getValueStack());
}
@Override public int hashCode() {
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ActionChainResult that = (ActionChainResult) o;
return Objects.equals(actionName, that.actionName) && Objects.equals(methodName,
that.methodName) && Objects.equals(namespace, that.namespace);
}
@Override
public int hashCode() {
int result;
result = (actionName != null ? actionName.hashCode() : 0);
result = 31 * result + (namespace != null ? namespace.hashCode() : 0);
@@ -260,24 +258,15 @@ public class ActionChainResult implements Result {
private boolean isInChainHistory(String namespace, String actionName, String methodName) {
LinkedList<? extends String> chainHistory = ActionChainResult.getChainHistory();
if (chainHistory == null) {
return false;
} else {
// Actions to skip
Set<String> skipActionsList = new HashSet<>();
if (skipActions != null && skipActions.length() > 0) {
ValueStack stack = ActionContext.getContext().getValueStack();
String finalSkipActions = TextParseUtil.translateVariables(this.skipActions, stack);
skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions));
}
if (!skipActionsList.contains(actionName)) {
// Get if key is in the chain history
return chainHistory.contains(makeKey(namespace, actionName, methodName));
}
return false;
Set<String> skipActionsList = new HashSet<>();
if (skipActions != null && skipActions.length() > 0) {
String finalSkipActions = translateVariables(skipActions);
skipActionsList.addAll(TextParseUtil.commaDelimitedStringToSet(finalSkipActions));
}
if (!skipActionsList.contains(actionName)) {
return chainHistory.contains(makeKey(namespace, actionName, methodName));
}
return false;
}
private void addToHistory(String namespace, String actionName, String methodName) {
@@ -286,10 +275,6 @@ public class ActionChainResult implements Result {
}
private String makeKey(String namespace, String actionName, String methodName) {
if (null == methodName) {
return namespace + "/" + actionName;
}
return namespace + "/" + actionName + "!" + methodName;
return namespace + "/" + actionName + (methodName != null ? "!" + methodName : "");
}
}
@@ -58,79 +58,52 @@ import java.util.Map;
*/
public class ActionContext implements Serializable {
static ThreadLocal<ActionContext> actionContext = new ThreadLocal<>();
private static final ThreadLocal<ActionContext> actionContext = new ThreadLocal<>();
/**
* Constant for the name of the action being executed.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name";
private static final String ACTION_NAME = "org.apache.struts2.ActionContext.name";
/**
* Constant for the {@link com.opensymphony.xwork2.util.ValueStack OGNL value stack}.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String VALUE_STACK = ValueStack.VALUE_STACK;
private static final String VALUE_STACK = ValueStack.VALUE_STACK;
/**
* Constant for the action's session.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session";
private static final String SESSION = "org.apache.struts2.ActionContext.session";
/**
* Constant for the action's application context.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application";
private static final String APPLICATION = "org.apache.struts2.ActionContext.application";
/**
* Constant for the action's parameters.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String PARAMETERS = "com.opensymphony.xwork2.ActionContext.parameters";
private static final String PARAMETERS = "org.apache.struts2.ActionContext.parameters";
/**
* Constant for the action's locale.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String LOCALE = "com.opensymphony.xwork2.ActionContext.locale";
private static final String LOCALE = "org.apache.struts2.ActionContext.locale";
/**
* Constant for the action's {@link com.opensymphony.xwork2.ActionInvocation invocation} context.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String ACTION_INVOCATION = "com.opensymphony.xwork2.ActionContext.actionInvocation";
private static final String ACTION_INVOCATION = "org.apache.struts2.ActionContext.actionInvocation";
/**
* Constant for the map of type conversion errors.
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String CONVERSION_ERRORS = "com.opensymphony.xwork2.ActionContext.conversionErrors";
private static final String CONVERSION_ERRORS = "org.apache.struts2.ActionContext.conversionErrors";
/**
* Constant for the container
*
* @deprecated scope will be narrowed to "private", use helper methods instead
*/
@Deprecated
public static final String CONTAINER = "com.opensymphony.xwork2.ActionContext.container";
private static final String CONTAINER = "org.apache.struts2.ActionContext.container";
private final Map<String, Object> context;
@@ -145,7 +118,6 @@ public class ActionContext implements Serializable {
/**
* Creates a new ActionContext based on passed in Map
* and assign this instance to the current thread
*
* @param context a map with context values
* @return new ActionContext
@@ -157,6 +129,15 @@ public class ActionContext implements Serializable {
return new ActionContext(context);
}
/**
* Creates a new ActionContext based on empty Map
*
* @return new ActionContext
*/
public static ActionContext of() {
return of(new HashMap<>());
}
/**
* Binds the provided context with the current thread
*
@@ -211,13 +192,7 @@ public class ActionContext implements Serializable {
* Sets the action invocation (the execution state).
*
* @param actionInvocation the action execution state.
* @deprecated use {@link #withActionInvocation(ActionInvocation)} instead
*/
@Deprecated
public void setActionInvocation(ActionInvocation actionInvocation) {
put(ACTION_INVOCATION, actionInvocation);
}
public ActionContext withActionInvocation(ActionInvocation actionInvocation) {
put(ACTION_INVOCATION, actionInvocation);
return this;
@@ -236,13 +211,7 @@ public class ActionContext implements Serializable {
* Sets the action's application context.
*
* @param application the action's application context.
* @deprecated use {@link #withApplication(Map)} instead
*/
@Deprecated
public void setApplication(Map<String, Object> application) {
put(APPLICATION, application);
}
public ActionContext withApplication(Map<String, Object> application) {
put(APPLICATION, application);
return this;
@@ -271,13 +240,7 @@ public class ActionContext implements Serializable {
* Sets conversion errors which occurred when executing the action.
*
* @param conversionErrors a Map of errors which occurred when executing the action.
* @deprecated use {@link #withConversionErrors(Map)} instead
*/
@Deprecated
public void setConversionErrors(Map<String, ConversionData> conversionErrors) {
put(CONVERSION_ERRORS, conversionErrors);
}
public ActionContext withConversionErrors(Map<String, ConversionData> conversionErrors) {
put(CONVERSION_ERRORS, conversionErrors);
return this;
@@ -304,13 +267,7 @@ public class ActionContext implements Serializable {
* Sets the Locale for the current action.
*
* @param locale the Locale for the current action.
* @deprecated use {@link #withLocale(Locale)} instead
*/
@Deprecated
public void setLocale(Locale locale) {
put(LOCALE, locale);
}
public ActionContext withLocale(Locale locale) {
put(LOCALE, locale);
return this;
@@ -327,7 +284,7 @@ public class ActionContext implements Serializable {
if (locale == null) {
locale = Locale.getDefault();
setLocale(locale);
withLocale(locale);
}
return locale;
@@ -336,28 +293,13 @@ public class ActionContext implements Serializable {
/**
* Sets the name of the current Action in the ActionContext.
*
* @param name the name of the current action.
* @deprecated use {@link #withActionName(String)} instead
* @param actionName the name of the current action.
*/
@Deprecated
public void setName(String name) {
put(ACTION_NAME, name);
}
public ActionContext withActionName(String actionName) {
put(ACTION_NAME, actionName);
return this;
}
/**
* Gets the name of the current Action.
*
* @return the name of the current action.
*/
public String getName() {
return (String) get(ACTION_NAME);
}
/**
* Gets the name of the current Action.
*
@@ -372,10 +314,6 @@ public class ActionContext implements Serializable {
*
* @param parameters the parameters for the current action.
*/
public void setParameters(HttpParameters parameters) {
put(PARAMETERS, parameters);
}
public ActionContext withParameters(HttpParameters parameters) {
put(PARAMETERS, parameters);
return this;
@@ -396,13 +334,7 @@ public class ActionContext implements Serializable {
* Sets a map of action session values.
*
* @param session the session values.
* @deprecated use {@link #withSession(Map)} instead
*/
@Deprecated
public void setSession(Map<String, Object> session) {
put(SESSION, session);
}
public ActionContext withSession(Map<String, Object> session) {
put(SESSION, session);
return this;
@@ -421,14 +353,8 @@ public class ActionContext implements Serializable {
/**
* Sets the OGNL value stack.
*
* @param stack the OGNL value stack.
* @deprecated Use {@link #withValueStack(ValueStack)} instead
* @param valueStack the OGNL value stack.
*/
@Deprecated
public void setValueStack(ValueStack stack) {
put(VALUE_STACK, stack);
}
public ActionContext withValueStack(ValueStack valueStack) {
put(VALUE_STACK, valueStack);
return this;
@@ -446,14 +372,8 @@ public class ActionContext implements Serializable {
/**
* Gets the container for this request
*
* @param cont The container
* @deprecated use {@link #withContainer(Container)} instead
* @param container The container
*/
@Deprecated
public void setContainer(Container cont) {
put(CONTAINER, cont);
}
public ActionContext withContainer(Container container) {
put(CONTAINER, container);
return this;
@@ -101,7 +101,7 @@ public abstract class XWorkTestCase extends TestCase {
}
protected Map<String, Object> createContextWithLocale(Locale locale) {
return ActionContext.of(new HashMap<>())
return ActionContext.of()
.withLocale(locale)
.getContextMap();
}
@@ -22,12 +22,12 @@ import com.opensymphony.xwork2.config.impl.DefaultConfiguration;
import com.opensymphony.xwork2.config.providers.StrutsDefaultConfigurationProvider;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.Optional;
import static org.apache.struts2.StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD;
/**
@@ -42,12 +42,11 @@ public class ConfigurationManager {
protected static final Logger LOG = LogManager.getLogger(ConfigurationManager.class);
protected Configuration configuration;
protected Lock providerLock = new ReentrantLock();
private List<ContainerProvider> containerProviders = new CopyOnWriteArrayList<>();
private List<PackageProvider> packageProviders = new CopyOnWriteArrayList<>();
private List<ContainerProvider> containerProviders = new ArrayList<>();
private List<PackageProvider> packageProviders = new ArrayList<>();
protected String defaultFrameworkBeanName;
private boolean providersChanged = false;
private boolean reloadConfigs = true; // for the first time
private boolean providersChanged = true;
private boolean alwaysReloadConfigs = false;
public ConfigurationManager(String name) {
this.defaultFrameworkBeanName = name;
@@ -59,54 +58,69 @@ public class ConfigurationManager {
* @see com.opensymphony.xwork2.config.impl.DefaultConfiguration
*/
public synchronized Configuration getConfiguration() {
if (configuration == null) {
setConfiguration(createConfiguration(defaultFrameworkBeanName));
try {
configuration.reloadContainer(getContainerProviders());
} catch (ConfigurationException e) {
setConfiguration(null);
throw new ConfigurationException("Unable to load configuration.", e);
}
} else {
if (wasConfigInitialised()) {
conditionalReload();
}
return configuration;
}
/**
* @return whether configuration was initialised (was null)
*/
private boolean wasConfigInitialised() {
if (configuration == null) {
initialiseConfiguration();
return false;
}
return true;
}
protected void initialiseConfiguration() {
if (containerProviders.isEmpty()) {
addDefaultContainerProviders();
}
configuration = createConfiguration(defaultFrameworkBeanName);
try {
reload();
} catch (ConfigurationException e) {
configuration.destroy();
configuration = null;
providersChanged = true;
throw new ConfigurationException("Unable to load configuration.", e);
}
}
protected void addDefaultContainerProviders() {
containerProviders.add(new StrutsDefaultConfigurationProvider());
}
protected Configuration createConfiguration(String beanName) {
return new DefaultConfiguration(beanName);
}
/**
* Clear all container providers and destroy managing Configuration instance
*/
public synchronized void destroyConfiguration() {
clearContainerProviders();
if (configuration != null) {
configuration.destroy();
configuration = null;
}
}
public synchronized void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
/**
* <p>
* Get the current list of ConfigurationProviders. If no custom ConfigurationProviders have been added, this method
* will return a list containing only a default ConfigurationProvider, {@link StrutsDefaultConfigurationProvider}.
* If a custom ConfigurationProvider has been added, then the StrutsDefaultConfigurationProvider must be added by hand.
* </p>
*
* <p>
* TODO: The lazy instantiation of XmlConfigurationProvider should be refactored to be elsewhere. The behavior described above seems unintuitive.
* </p>
* Get the current list of ConfigurationProviders.
*
* @return the list of registered ConfigurationProvider objects
* @see ConfigurationProvider
*/
public List<ContainerProvider> getContainerProviders() {
providerLock.lock();
try {
if (containerProviders.size() == 0) {
containerProviders.add(new StrutsDefaultConfigurationProvider());
}
return containerProviders;
} finally {
providerLock.unlock();
}
public synchronized List<ContainerProvider> getContainerProviders() {
return new ArrayList<>(containerProviders);
}
/**
@@ -114,14 +128,9 @@ public class ConfigurationManager {
*
* @param containerProviders list of {@link ConfigurationProvider} to be set
*/
public void setContainerProviders(List<ContainerProvider> containerProviders) {
providerLock.lock();
try {
this.containerProviders = new CopyOnWriteArrayList<>(containerProviders);
providersChanged = true;
} finally {
providerLock.unlock();
}
public synchronized void setContainerProviders(List<ContainerProvider> containerProviders) {
this.containerProviders = new ArrayList<>(containerProviders);
providersChanged = true;
}
/**
@@ -130,22 +139,32 @@ public class ConfigurationManager {
*
* @param provider the ConfigurationProvider to register
*/
public void addContainerProvider(ContainerProvider provider) {
public synchronized void addContainerProvider(ContainerProvider provider) {
if (!containerProviders.contains(provider)) {
containerProviders.add(provider);
providersChanged = true;
}
}
public void clearContainerProviders() {
for (ContainerProvider containerProvider : containerProviders) {
clearContainerProvider(containerProvider);
public synchronized void removeContainerProvider(ContainerProvider provider) {
if (containerProviders.remove(provider)) {
destroyContainerProvider(provider);
providersChanged = true;
}
}
public synchronized void clearContainerProviders() {
destroyContainerProviders();
containerProviders.clear();
providersChanged = true;
}
private void clearContainerProvider(ContainerProvider containerProvider) {
private void destroyContainerProviders() {
LOG.debug("Destroying all providers.");
containerProviders.forEach(this::destroyContainerProvider);
}
private void destroyContainerProvider(ContainerProvider containerProvider) {
try {
containerProvider.destroy();
} catch (Exception e) {
@@ -153,80 +172,61 @@ public class ConfigurationManager {
}
}
/**
* Destroy its managing Configuration instance
*/
public synchronized void destroyConfiguration() {
clearContainerProviders(); // let's destroy the ConfigurationProvider first
containerProviders = new CopyOnWriteArrayList<>();
if (configuration != null)
configuration.destroy(); // let's destroy it first, before nulling it.
configuration = null;
}
/**
* Reloads the Configuration files if the configuration files indicate that they need to be reloaded.
*/
public synchronized void conditionalReload() {
if (reloadConfigs || providersChanged) {
if (alwaysReloadConfigs || providersChanged) {
LOG.debug("Checking ConfigurationProviders for reload.");
List<ContainerProvider> providers = getContainerProviders();
boolean reload = needReloadContainerProviders(providers);
if (!reload) {
reload = needReloadPackageProviders();
if (needReloadContainerProviders() || needReloadPackageProviders()) {
destroyAndReload();
}
if (reload) {
reloadProviders(providers);
}
updateReloadConfigsFlag();
providersChanged = false;
}
}
private void updateReloadConfigsFlag() {
reloadConfigs = Boolean.parseBoolean(configuration.getContainer().getInstance(String.class, StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD));
if (LOG.isDebugEnabled()) {
LOG.debug("Updating [{}], current value is [{}], new value [{}]",
StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, String.valueOf(reloadConfigs), String.valueOf(reloadConfigs));
private void updateAlwaysReloadFlag() {
boolean newValue = Boolean.parseBoolean(configuration.getContainer()
.getInstance(String.class, STRUTS_CONFIGURATION_XML_RELOAD));
if (alwaysReloadConfigs != newValue) {
LOG.debug(
"Updating [{}], current value is [{}], new value [{}]",
STRUTS_CONFIGURATION_XML_RELOAD,
String.valueOf(alwaysReloadConfigs),
String.valueOf(newValue));
alwaysReloadConfigs = newValue;
}
}
private boolean needReloadPackageProviders() {
if (packageProviders != null) {
for (PackageProvider provider : packageProviders) {
if (provider.needsReload()) {
LOG.info("Detected package provider [{}] needs to be reloaded. Reloading all providers.", provider);
return true;
}
}
Optional<PackageProvider> provider = packageProviders.stream().filter(PackageProvider::needsReload).findAny();
if (provider.isPresent()) {
LOG.info("Detected package provider [{}] needs to be reloaded.", provider.get());
return true;
}
return false;
}
private boolean needReloadContainerProviders(List<ContainerProvider> providers) {
for (ContainerProvider provider : providers) {
if (provider.needsReload()) {
LOG.info("Detected container provider [{}] needs to be reloaded. Reloading all providers.", provider);
return true;
}
private boolean needReloadContainerProviders() {
Optional<ContainerProvider> provider = containerProviders.stream().filter(ContainerProvider::needsReload).findAny();
if (provider.isPresent()) {
LOG.info("Detected container provider [{}] needs to be reloaded.", provider.get());
return true;
}
return false;
}
private void reloadProviders(List<ContainerProvider> providers) {
for (ContainerProvider containerProvider : containerProviders) {
try {
containerProvider.destroy();
} catch (Exception e) {
LOG.warn("error while destroying configuration provider [{}]", containerProvider, e);
}
}
packageProviders = this.configuration.reloadContainer(providers);
public synchronized void destroyAndReload() {
destroyContainerProviders();
reload();
}
public synchronized void reload() {
packageProviders = getConfiguration().reloadContainer(getContainerProviders());
if (wasConfigInitialised()) {
LOG.debug("Reloading all providers.");
packageProviders = configuration.reloadContainer(containerProviders);
providersChanged = false;
updateAlwaysReloadFlag();
}
}
}
@@ -24,7 +24,7 @@ import com.opensymphony.xwork2.util.location.LocatableProperties;
/**
* Provides beans and constants/properties for the Container
*
*
* @since 2.1
*/
public interface ContainerProvider {
@@ -32,29 +32,29 @@ public interface ContainerProvider {
/**
* Called before removed from the configuration manager
*/
public void destroy();
void destroy();
/**
* Initializes with the configuration
* @param configuration The configuration
* @throws ConfigurationException If anything goes wrong
*/
public void init(Configuration configuration) throws ConfigurationException;
void init(Configuration configuration) throws ConfigurationException;
/**
* Tells whether the ContainerProvider should reload its configuration
*
* @return <tt>true</tt>, whether the ContainerProvider should reload its configuration, <tt>false</tt>otherwise.
*/
public boolean needsReload();
boolean needsReload();
/**
* Registers beans and properties for the Container
*
*
* @param builder The builder to register beans with
* @param props The properties to register constants with
* @throws ConfigurationException If anything goes wrong
*/
public void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException;
void register(ContainerBuilder builder, LocatableProperties props) throws ConfigurationException;
}
@@ -118,6 +118,8 @@ import org.apache.struts2.conversion.StrutsTypeConverterCreator;
import org.apache.struts2.conversion.StrutsTypeConverterHolder;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.interceptor.exec.ExecutorProvider;
import org.apache.struts2.interceptor.exec.StrutsExecutorProvider;
import org.apache.struts2.url.QueryStringBuilder;
import org.apache.struts2.url.QueryStringParser;
import org.apache.struts2.url.StrutsQueryStringBuilder;
@@ -242,6 +244,8 @@ public class StrutsDefaultConfigurationProvider implements ConfigurationProvider
.factory(QueryStringParser.class, StrutsQueryStringParser.class, Scope.SINGLETON)
.factory(UrlEncoder.class, StrutsUrlEncoder.class, Scope.SINGLETON)
.factory(UrlDecoder.class, StrutsUrlDecoder.class, Scope.SINGLETON)
.factory(ExecutorProvider.class, StrutsExecutorProvider.class, Scope.SINGLETON)
;
props.setProperty(StrutsConstants.STRUTS_ENABLE_DYNAMIC_METHOD_INVOCATION, Boolean.FALSE.toString());
@@ -0,0 +1,990 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 com.opensymphony.xwork2.config.providers;
import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ObjectFactory;
import com.opensymphony.xwork2.config.BeanSelectionProvider;
import com.opensymphony.xwork2.config.Configuration;
import com.opensymphony.xwork2.config.ConfigurationException;
import com.opensymphony.xwork2.config.ConfigurationProvider;
import com.opensymphony.xwork2.config.ConfigurationUtil;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.config.entities.InterceptorConfig;
import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.config.entities.InterceptorStackConfig;
import com.opensymphony.xwork2.config.entities.PackageConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.config.entities.ResultTypeConfig;
import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
import com.opensymphony.xwork2.config.impl.LocatableFactory;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.ContainerBuilder;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.inject.Scope;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.DomHelper;
import com.opensymphony.xwork2.util.location.LocatableProperties;
import com.opensymphony.xwork2.util.location.Location;
import com.opensymphony.xwork2.util.location.LocationUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import static com.opensymphony.xwork2.util.TextParseUtil.commaDelimitedStringToSet;
import static java.lang.Boolean.parseBoolean;
import static java.lang.Character.isLowerCase;
import static java.lang.Character.toUpperCase;
import static java.lang.String.format;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import static org.apache.commons.lang3.StringUtils.trimToNull;
/**
* This is a base XWork2 {@link ConfigurationProvider} for loading configuration from a parsed
* {@link Document XML document}. By extending this class, configuration can be loaded from any source that an XML
* document can be parsed from. Note that this class does not validate the document against any provided DTDs. For
* loading configuration from an XML file with DTD validation, please see
* {@link org.apache.struts2.config.StrutsXmlConfigurationProvider StrutsXmlConfigurationProvider}.
*
* @since 6.2.0
*/
public abstract class XmlDocConfigurationProvider implements ConfigurationProvider {
private static final Logger LOG = LogManager.getLogger(XmlConfigurationProvider.class);
protected final Map<String, Element> declaredPackages = new HashMap<>();
protected List<Document> documents;
protected ObjectFactory objectFactory;
protected Map<String, String> dtdMappings = new HashMap<>();
protected Configuration configuration;
protected boolean throwExceptionOnDuplicateBeans = true;
protected ValueSubstitutor valueSubstitutor;
@Inject
public void setObjectFactory(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}
@Inject(required = false)
public void setValueSubstitutor(ValueSubstitutor valueSubstitutor) {
this.valueSubstitutor = valueSubstitutor;
}
public XmlDocConfigurationProvider(Document... documents) {
this.documents = Arrays.asList(documents);
}
public void setThrowExceptionOnDuplicateBeans(boolean val) {
this.throwExceptionOnDuplicateBeans = val;
}
public void setDtdMappings(Map<String, String> mappings) {
this.dtdMappings = Collections.unmodifiableMap(mappings);
}
/**
* Returns an unmodifiable map of DTD mappings
*
* @return map of DTD mappings
*/
public Map<String, String> getDtdMappings() {
return dtdMappings;
}
@Override
public void init(Configuration configuration) {
this.configuration = configuration;
}
@Override
public void destroy() {
}
protected Class<?> loadClass(String className) throws ClassNotFoundException {
return objectFactory.getClassInstance(className);
}
public static void iterateElementChildren(Document doc, Consumer<Element> function) {
iterateElementChildren(doc.getDocumentElement(), function);
}
public static void iterateElementChildren(Node node, Consumer<Element> function) {
iterateChildren(node, childNode -> {
if (!(childNode instanceof Element)) {
return;
}
function.accept((Element) childNode);
});
}
public static void iterateChildren(Node node, Consumer<Node> function) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
function.accept(children.item(i));
}
}
public static void iterateChildrenByTagName(Element el, String tagName, Consumer<Element> function) {
NodeList childrenByTag = el.getElementsByTagName(tagName);
for (int i = 0; i < childrenByTag.getLength(); i++) {
Element childEl = (Element) childrenByTag.item(i);
function.accept(childEl);
}
}
@Override
public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
Map<String, Node> loadedBeans = new HashMap<>();
for (Document doc : documents) {
iterateElementChildren(doc, child -> {
switch (child.getNodeName()) {
case "bean-selection": {
registerBeanSelection(child, containerBuilder, props);
break;
}
case "bean": {
registerBean(child, loadedBeans, containerBuilder);
break;
}
case "constant": {
registerConstant(child, props);
break;
}
case "unknown-handler-stack":
registerUnknownHandlerStack(child);
break;
}
});
}
}
protected void registerBeanSelection(Element child, ContainerBuilder containerBuilder, LocatableProperties props) {
String name = child.getAttribute("name");
String impl = child.getAttribute("class");
try {
Class<?> classImpl = loadClass(impl);
if (BeanSelectionProvider.class.isAssignableFrom(classImpl)) {
BeanSelectionProvider provider = (BeanSelectionProvider) classImpl.newInstance();
provider.register(containerBuilder, props);
} else {
throw new ConfigurationException(format("The bean-provider: name:%s class:%s does not implement %s", name, impl, BeanSelectionProvider.class.getName()), child);
}
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
throw new ConfigurationException(format("Unable to load bean-provider: name:%s class:%s", name, impl), e, child);
}
}
protected void registerBean(Element child, Map<String, Node> loadedBeans, ContainerBuilder containerBuilder) {
String type = child.getAttribute("type");
String name = child.getAttribute("name");
String impl = child.getAttribute("class");
String onlyStatic = child.getAttribute("static");
String scopeStr = child.getAttribute("scope");
boolean optional = "true".equals(child.getAttribute("optional"));
Scope scope = Scope.fromString(scopeStr);
if (name.isEmpty()) {
name = Container.DEFAULT_NAME;
}
try {
Class<?> classImpl = ClassLoaderUtil.loadClass(impl, getClass());
Class<?> classType = classImpl;
if (!type.isEmpty()) {
classType = ClassLoaderUtil.loadClass(type, getClass());
}
if ("true".equals(onlyStatic)) {
// Force loading of class to detect no class def found exceptions
classImpl.getDeclaredClasses();
containerBuilder.injectStatics(classImpl);
} else {
if (containerBuilder.contains(classType, name)) {
Location loc = LocationUtils.getLocation(loadedBeans.get(classType.getName() + name));
if (throwExceptionOnDuplicateBeans) {
throw new ConfigurationException(format("Bean type %s with the name %s has already been loaded by %s", classType, name, loc), child);
}
}
// Force loading of class to detect no class def found exceptions
classImpl.getDeclaredConstructors();
LOG.debug("Loaded type: {} name: {} impl: {}", type, name, impl);
containerBuilder.factory(classType, name, new LocatableFactory<>(name, classType, classImpl, scope, child), scope);
}
loadedBeans.put(classType.getName() + name, child);
} catch (Throwable ex) {
if (!optional) {
throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, child);
} else {
LOG.debug("Unable to load optional class: {}", impl);
}
}
}
protected void registerConstant(Element child, LocatableProperties props) {
String name = child.getAttribute("name");
String value = child.getAttribute("value");
if (valueSubstitutor != null) {
LOG.debug("Substituting value [{}] using [{}]", value, valueSubstitutor.getClass().getName());
value = valueSubstitutor.substitute(value);
}
props.setProperty(name, value, child);
}
protected void registerUnknownHandlerStack(Element child) {
List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<>();
iterateChildrenByTagName(child, "unknown-handler-ref", unknownHandler -> {
Location location = LocationUtils.getLocation(unknownHandler);
unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"), location));
});
if (!unknownHandlerStack.isEmpty()) {
configuration.setUnknownHandlerStack(unknownHandlerStack);
}
}
@Override
public boolean needsReload() {
return false;
}
@Override
public void loadPackages() throws ConfigurationException {
List<Element> reloads = new ArrayList<>();
verifyPackageStructure();
for (Document doc : documents) {
iterateElementChildren(doc, child -> {
if ("package".equals(child.getNodeName())) {
PackageConfig cfg = addPackage(child);
if (cfg.isNeedsRefresh()) {
reloads.add(child);
}
}
});
loadExtraConfiguration(doc);
}
if (reloads.size() > 0) {
reloadRequiredPackages(reloads);
}
for (Document doc : documents) {
loadExtraConfiguration(doc);
}
declaredPackages.clear();
configuration = null;
}
private void verifyPackageStructure() {
DirectedGraph<String> graph = new DirectedGraph<>();
for (Document doc : documents) {
iterateElementChildren(doc, child -> {
if (!"package".equals(child.getNodeName())) {
return;
}
String packageName = child.getAttribute("name");
declaredPackages.put(packageName, child);
graph.addNode(packageName);
String extendsAttribute = child.getAttribute("extends");
for (String parent : ConfigurationUtil.buildParentListFromString(extendsAttribute)) {
graph.addNode(parent);
graph.addEdge(packageName, parent);
}
});
}
CycleDetector<String> detector = new CycleDetector<>(graph);
if (detector.containsCycle()) {
StringBuilder builder = new StringBuilder("The following packages participate in cycles:");
for (String packageName : detector.getVerticesInCycles()) {
builder.append(" ");
builder.append(packageName);
}
throw new ConfigurationException(builder.toString());
}
}
/**
* Allows subclasses to load extra information from the document
*
* @param doc The configuration document
*/
protected void loadExtraConfiguration(Document doc) {
// no op
}
private void reloadRequiredPackages(List<Element> reloads) {
if (reloads.isEmpty()) {
return;
}
List<Element> result = new ArrayList<>();
for (Element pkg : reloads) {
PackageConfig cfg = addPackage(pkg);
if (cfg.isNeedsRefresh()) {
result.add(pkg);
}
}
if (!result.isEmpty() && result.size() != reloads.size()) {
reloadRequiredPackages(result);
return;
}
// Print out error messages for all misconfigured inheritance packages
for (Element rp : result) {
String parent = rp.getAttribute("extends");
if (!parent.isEmpty() && ConfigurationUtil.buildParentsFromString(configuration, parent).isEmpty()) {
LOG.error("Unable to find parent packages {}", parent);
}
}
}
/**
* Create a PackageConfig from an XML element representing it.
*
* @param packageElement the given XML element
* @return the package config
* @throws ConfigurationException in case of configuration errors
*/
protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
String packageName = packageElement.getAttribute("name");
PackageConfig packageConfig = configuration.getPackageConfig(packageName);
if (packageConfig != null) {
LOG.debug("Package [{}] already loaded, skipping re-loading it and using existing PackageConfig [{}]", packageName, packageConfig);
return packageConfig;
}
PackageConfig.Builder newPackage = buildPackageContext(packageElement);
if (newPackage.isNeedsRefresh()) {
return newPackage.build();
}
LOG.debug("Loaded {}", newPackage);
// add result types (and default result) to this package
addResultTypes(newPackage, packageElement);
// load the interceptors and interceptor stacks for this package
loadInterceptors(newPackage, packageElement);
// load the default interceptor reference for this package
loadDefaultInterceptorRef(newPackage, packageElement);
// load the default class ref for this package
loadDefaultClassRef(newPackage, packageElement);
// load the global result list for this package
loadGlobalResults(newPackage, packageElement);
loadGlobalAllowedMethods(newPackage, packageElement);
// load the global exception handler list for this package
loadGlobalExceptionMappings(newPackage, packageElement);
// get actions
iterateChildrenByTagName(packageElement, "action", actionElement -> addAction(actionElement, newPackage));
// load the default action reference for this package
loadDefaultActionRef(newPackage, packageElement);
PackageConfig cfg = newPackage.build();
configuration.addPackageConfig(cfg.getName(), cfg);
return cfg;
}
protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException {
String name = actionElement.getAttribute("name");
String className = actionElement.getAttribute("class");
Location location = DomHelper.getLocationObject(actionElement);
if (location == null) {
LOG.warn("Location null for {}", className);
}
if (!className.isEmpty() && !verifyAction(className, name, location)) {
LOG.error("Unable to verify action [{}] with class [{}], from [{}]", name, className, location);
return;
}
Map<String, ResultConfig> results;
try {
results = buildResults(actionElement, packageContext);
} catch (ConfigurationException e) {
throw new ConfigurationException(
format("Error building results for action %s in namespace %s", name, packageContext.getNamespace()),
e,
actionElement);
}
ActionConfig actionConfig = buildActionConfig(actionElement, location, packageContext, results);
packageContext.addActionConfig(actionConfig.getName(), actionConfig);
LOG.debug("Loaded {}{} in '{}' package: {}",
isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "",
name, packageContext.getName(), actionConfig);
}
protected ActionConfig buildActionConfig(Element actionElement,
Location location,
PackageConfig.Builder packageContext,
Map<String, ResultConfig> results) {
String actionName = actionElement.getAttribute("name");
String className = actionElement.getAttribute("class");
// methodName should be null if it's not set
String methodName = trimToNull(actionElement.getAttribute("method"));
List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext);
List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext);
Set<String> allowedMethods = buildAllowedMethods(actionElement, packageContext);
return new ActionConfig.Builder(packageContext.getName(), actionName, className)
.methodName(methodName)
.addResultConfigs(results)
.addInterceptors(interceptorList)
.addExceptionMappings(exceptionMappings)
.addParams(XmlHelper.getParams(actionElement))
.setStrictMethodInvocation(packageContext.isStrictMethodInvocation())
.addAllowedMethod(allowedMethods)
.location(location)
.build();
}
/**
* @deprecated since 6.2.0, use {@link #verifyAction(String, Location)}
*/
@Deprecated
protected boolean verifyAction(String className, String name, Location loc) {
return verifyAction(className, loc);
}
protected boolean verifyAction(String className, Location loc) {
if (className.contains("{")) {
LOG.debug("Action class [{}] contains a wildcard replacement value, so it can't be verified", className);
return true;
}
try {
if (objectFactory.isNoArgConstructorRequired()) {
Class<?> clazz = loadClass(className);
if (!Modifier.isPublic(clazz.getModifiers())) {
throw new ConfigurationException("Action class [" + className + "] is not public", loc);
}
clazz.getConstructor();
}
} catch (ClassNotFoundException e) {
LOG.debug("Class not found for action [{}]", className, e);
throw new ConfigurationException("Action class [" + className + "] not found", loc);
} catch (NoSuchMethodException e) {
LOG.debug("No constructor found for action [{}]", className, e);
throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc);
} catch (RuntimeException ex) {
// Probably not a big deal, like request or session-scoped Spring beans that need a real request
LOG.info("Unable to verify action class [{}] exists at initialization", className);
LOG.debug("Action verification cause", ex);
} catch (Exception ex) {
// Default to failing fast
LOG.debug("Unable to verify action class [{}]", className, ex);
throw new ConfigurationException(ex, loc);
}
return true;
}
protected void addResultTypes(PackageConfig.Builder packageContext, Element element) {
iterateChildrenByTagName(element, "result-type", resultTypeElement -> {
String name = resultTypeElement.getAttribute("name");
String className = resultTypeElement.getAttribute("class");
String def = resultTypeElement.getAttribute("default");
Location loc = DomHelper.getLocationObject(resultTypeElement);
Class<?> clazz = verifyResultType(className, loc);
if (clazz == null) {
return;
}
String paramName = null;
try {
paramName = (String) clazz.getField("DEFAULT_PARAM").get(null);
} catch (Throwable t) {
LOG.debug("The result type [{}] doesn't have a default param [DEFAULT_PARAM] defined!", className, t);
}
packageContext.addResultTypeConfig(buildResultTypeConfig(resultTypeElement, loc, paramName));
if (BooleanUtils.toBoolean(def)) {
packageContext.defaultResultType(name);
}
});
}
protected ResultTypeConfig buildResultTypeConfig(Element resultTypeElement, Location location, String paramName) {
String name = resultTypeElement.getAttribute("name");
String className = resultTypeElement.getAttribute("class");
ResultTypeConfig.Builder resultType = new ResultTypeConfig.Builder(name,
className).defaultResultParam(paramName).location(location);
Map<String, String> params = XmlHelper.getParams(resultTypeElement);
if (!params.isEmpty()) {
resultType.addParams(params);
}
return resultType.build();
}
protected Class<?> verifyResultType(String className, Location loc) {
try {
return loadClass(className);
} catch (ClassNotFoundException | NoClassDefFoundError e) {
LOG.warn("Result class [{}] doesn't exist ({}) at {}, ignoring", className, e.getClass().getSimpleName(), loc, e);
}
return null;
}
/**
* <p>This method builds a package context by looking for the parents of this new package.</p>
* <p>If no parents are found, it will return a root package.</p>
*
* @param packageElement the package element
* @return the package config builder
*/
protected PackageConfig.Builder buildPackageContext(Element packageElement) {
String parent = packageElement.getAttribute("extends");
String abstractVal = packageElement.getAttribute("abstract");
boolean isAbstract = parseBoolean(abstractVal);
String name = defaultString(packageElement.getAttribute("name"));
String namespace = defaultString(packageElement.getAttribute("namespace"));
// Strict DMI is enabled by default, it can be disabled by user
boolean strictDMI = true;
if (packageElement.hasAttribute("strict-method-invocation")) {
strictDMI = parseBoolean(packageElement.getAttribute("strict-method-invocation"));
}
PackageConfig.Builder cfg = new PackageConfig.Builder(name)
.namespace(namespace)
.isAbstract(isAbstract)
.strictMethodInvocation(strictDMI)
.location(DomHelper.getLocationObject(packageElement));
if (parent.isEmpty()) {
return cfg;
}
// has parents, let's look it up
List<PackageConfig> parents = new ArrayList<>();
for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) {
if (configuration.getPackageConfigNames().contains(parentPackageName)) {
parents.add(configuration.getPackageConfig(parentPackageName));
} else if (declaredPackages.containsKey(parentPackageName)) {
if (configuration.getPackageConfig(parentPackageName) == null) {
addPackage(declaredPackages.get(parentPackageName));
}
parents.add(configuration.getPackageConfig(parentPackageName));
} else {
throw new ConfigurationException("Parent package is not defined: " + parentPackageName);
}
}
if (parents.isEmpty()) {
cfg.needsRefresh(true);
} else {
cfg.addParents(parents);
}
return cfg;
}
/**
* Build a map of ResultConfig objects from below a given XML element.
*
* @param element the given XML element
* @param packageContext the package context
* @return map of result config objects
*/
protected Map<String, ResultConfig> buildResults(Element element, PackageConfig.Builder packageContext) {
Map<String, ResultConfig> results = new LinkedHashMap<>();
iterateChildrenByTagName(element, "result", resultElement -> {
Node parNode = resultElement.getParentNode();
if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) {
return;
}
String resultName = resultElement.getAttribute("name");
String resultType = resultElement.getAttribute("type");
// if you don't specify a name on <result/>, it defaults to "success"
if (StringUtils.isEmpty(resultName)) {
resultName = Action.SUCCESS;
}
// there is no result type, so let's inherit from the parent package
if (resultType.isEmpty()) {
resultType = packageContext.getFullDefaultResultType();
// now check if there is a result type now
if (resultType.isEmpty()) {
throw new ConfigurationException("No result type specified for result named '"
+ resultName + "', perhaps the parent package does not specify the result type?", resultElement);
}
}
ResultTypeConfig config = packageContext.getResultType(resultType);
if (config == null) {
throw new ConfigurationException(format("There is no result type defined for type '%s' mapped with name '%s'. Did you mean '%s'?", resultType, resultName, guessResultType(resultType)), resultElement);
}
String resultClass = config.getClassName();
if (resultClass == null) {
throw new ConfigurationException("Result type '" + resultType + "' is invalid");
}
Set<String> resultNamesSet = commaDelimitedStringToSet(resultName);
if (resultNamesSet.isEmpty()) {
resultNamesSet.add(resultName);
}
Map<String, String> params = buildResultParams(resultElement, config);
Location location = DomHelper.getLocationObject(element);
for (String name : resultNamesSet) {
ResultConfig resultConfig = buildResultConfig(name, config, location, params);
results.put(resultConfig.getName(), resultConfig);
}
});
return results;
}
protected ResultConfig buildResultConfig(String name,
ResultTypeConfig config,
Location location,
Map<String, String> params) {
return new ResultConfig.Builder(name, config.getClassName()).location(location).addParams(params).build();
}
protected Map<String, String> buildResultParams(Element resultElement, ResultTypeConfig config) {
Map<String, String> resultParams = XmlHelper.getParams(resultElement);
// maybe we just have a body - therefore a default parameter
if (resultParams.isEmpty() && resultElement.getChildNodes().getLength() > 0) {
// if <result ...>something</result> then we add a parameter of 'something' as this is the most used result param
resultParams = new LinkedHashMap<>();
String paramName = config.getDefaultResultParam();
if (paramName != null) {
StringBuilder paramValue = new StringBuilder();
iterateChildren(resultElement, child -> {
if (child.getNodeType() == Node.TEXT_NODE) {
String val = child.getNodeValue();
if (val != null) {
paramValue.append(val);
}
}
});
String val = paramValue.toString().trim();
if (val.length() > 0) {
resultParams.put(paramName, val);
}
} else {
LOG.debug(
"No default parameter defined for result [{}] of type [{}] ",
config.getName(),
config.getClassName());
}
}
// create new param map, so that the result param can override the config param
Map<String, String> params = new LinkedHashMap<>();
Map<String, String> configParams = config.getParams();
if (configParams != null) {
params.putAll(configParams);
}
params.putAll(resultParams);
return params;
}
protected static String guessResultType(String type) {
if (type == null) {
return null;
}
StringBuilder sb = new StringBuilder();
boolean capNext = false;
for (int x = 0; x < type.length(); x++) {
char c = type.charAt(x);
if (c == '-') {
capNext = true;
continue;
} else if (isLowerCase(c) && capNext) {
c = toUpperCase(c);
capNext = false;
}
sb.append(c);
}
return sb.toString();
}
/**
* @deprecated since 6.2.0, use {@link #buildExceptionMappings(Element)}
*/
@Deprecated
protected List<ExceptionMappingConfig> buildExceptionMappings(Element element, PackageConfig.Builder packageContext) {
return buildExceptionMappings(element);
}
/**
* Build a list of exception mapping objects from below a given XML element.
*
* @param element the given XML element
* @return list of exception mapping config objects
*/
protected List<ExceptionMappingConfig> buildExceptionMappings(Element element) {
List<ExceptionMappingConfig> exceptionMappings = new ArrayList<>();
iterateChildrenByTagName(element, "exception-mapping", ehElement -> {
Node parNode = ehElement.getParentNode();
if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) {
return;
}
String emName = ehElement.getAttribute("name");
String exceptionClassName = ehElement.getAttribute("exception");
String exceptionResult = ehElement.getAttribute("result");
Map<String, String> params = XmlHelper.getParams(ehElement);
if (emName.isEmpty()) {
emName = exceptionResult;
}
ExceptionMappingConfig ehConfig = new ExceptionMappingConfig.Builder(emName, exceptionClassName, exceptionResult)
.addParams(params)
.location(DomHelper.getLocationObject(ehElement))
.build();
exceptionMappings.add(ehConfig);
});
return exceptionMappings;
}
protected Set<String> buildAllowedMethods(Element element, PackageConfig.Builder packageContext) {
NodeList allowedMethodsEls = element.getElementsByTagName("allowed-methods");
Set<String> allowedMethods;
if (allowedMethodsEls.getLength() > 0) {
// user defined 'allowed-methods' so used them whatever Strict DMI was enabled or not
allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods());
// Fix for WW-5029 (concatenate all possible text node children)
Node allowedMethodsNode = allowedMethodsEls.item(0);
addAllowedMethodsToSet(allowedMethodsNode, allowedMethods);
} else if (packageContext.isStrictMethodInvocation()) {
// user enabled Strict DMI but didn't define action specific 'allowed-methods' so we use 'global-allowed-methods' only
allowedMethods = new HashSet<>(packageContext.getGlobalAllowedMethods());
} else {
// Strict DMI is disabled so any method can be called
allowedMethods = new HashSet<>();
allowedMethods.add(ActionConfig.WILDCARD);
}
LOG.debug("Collected allowed methods: {}", allowedMethods);
return Collections.unmodifiableSet(allowedMethods);
}
protected void loadDefaultActionRef(PackageConfig.Builder packageContext, Element element) {
NodeList resultTypeList = element.getElementsByTagName("default-action-ref");
if (resultTypeList.getLength() > 0) {
Element defaultRefElement = (Element) resultTypeList.item(0);
packageContext.defaultActionRef(defaultRefElement.getAttribute("name"));
}
}
/**
* Load all the global results for this package from the XML element.
*
* @param packageContext the package context
* @param packageElement the given XML element
*/
protected void loadGlobalResults(PackageConfig.Builder packageContext, Element packageElement) {
NodeList globalResultList = packageElement.getElementsByTagName("global-results");
if (globalResultList.getLength() > 0) {
Element globalResultElement = (Element) globalResultList.item(0);
Map<String, ResultConfig> results = buildResults(globalResultElement, packageContext);
packageContext.addGlobalResultConfigs(results);
}
}
protected void loadGlobalAllowedMethods(PackageConfig.Builder packageContext, Element packageElement) {
NodeList globalAllowedMethodsElms = packageElement.getElementsByTagName("global-allowed-methods");
if (globalAllowedMethodsElms.getLength() > 0) {
Set<String> globalAllowedMethods = new HashSet<>();
// Fix for WW-5029 (concatenate all possible text node children)
Node globalAllowedMethodsNode = globalAllowedMethodsElms.item(0);
addAllowedMethodsToSet(globalAllowedMethodsNode, globalAllowedMethods);
packageContext.addGlobalAllowedMethods(globalAllowedMethods);
}
}
protected static void addAllowedMethodsToSet(Node allowedMethodsNode, Set<String> allowedMethodsSet) {
if (allowedMethodsNode == null) {
return;
}
StringBuilder allowedMethodsSB = new StringBuilder();
iterateChildren(allowedMethodsNode, allowedMethodsChildNode -> {
if (allowedMethodsChildNode != null && allowedMethodsChildNode.getNodeType() == Node.TEXT_NODE) {
String childNodeValue = allowedMethodsChildNode.getNodeValue();
childNodeValue = (childNodeValue != null ? childNodeValue.trim() : "");
if (childNodeValue.length() > 0) {
allowedMethodsSB.append(childNodeValue);
}
}
});
if (allowedMethodsSB.length() > 0) {
allowedMethodsSet.addAll(commaDelimitedStringToSet(allowedMethodsSB.toString()));
}
}
protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) {
NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref");
if (defaultClassRefList.getLength() > 0) {
Element defaultClassRefElement = (Element) defaultClassRefList.item(0);
packageContext.defaultClassRef(defaultClassRefElement.getAttribute("class"));
}
}
/**
* Load all the global results for this package from the XML element.
*
* @param packageContext the package context
* @param packageElement the given XML element
*/
protected void loadGlobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) {
NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings");
if (globalExceptionMappingList.getLength() > 0) {
Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0);
List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext);
packageContext.addGlobalExceptionMappingConfigs(exceptionMappings);
}
}
protected List<InterceptorMapping> buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException {
List<InterceptorMapping> interceptorList = new ArrayList<>();
iterateChildrenByTagName(element, "interceptor-ref", interceptorRefElement -> {
Node parNode = interceptorRefElement.getParentNode();
if (!parNode.equals(element) && !parNode.getNodeName().equals(element.getNodeName())) {
return;
}
List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement);
interceptorList.addAll(interceptors);
});
return interceptorList;
}
protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException {
iterateChildrenByTagName(
element,
"interceptor",
interceptorElement -> context.addInterceptorConfig(buildInterceptorConfig(interceptorElement)));
loadInterceptorStacks(element, context);
}
protected InterceptorConfig buildInterceptorConfig(Element interceptorElement) {
String interceptorName = interceptorElement.getAttribute("name");
String className = interceptorElement.getAttribute("class");
Map<String, String> params = XmlHelper.getParams(interceptorElement);
return new InterceptorConfig.Builder(interceptorName, className)
.addParams(params)
.location(DomHelper.getLocationObject(interceptorElement))
.build();
}
protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException {
iterateChildrenByTagName(element, "interceptor-stack", interceptorStackElement -> {
InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context);
context.addInterceptorStackConfig(config);
});
}
protected InterceptorStackConfig loadInterceptorStack(Element element, PackageConfig.Builder context) throws ConfigurationException {
String name = element.getAttribute("name");
InterceptorStackConfig.Builder config = new InterceptorStackConfig.Builder(name)
.location(DomHelper.getLocationObject(element));
iterateChildrenByTagName(element, "interceptor-ref", interceptorRefElement -> {
List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement);
config.addInterceptors(interceptors);
});
return config.build();
}
/**
* Looks up the Interceptor Class from the interceptor-ref name and creates an instance, which is added to the
* provided List, or, if this is a ref to a stack, it adds the Interceptor instances from the List to this stack.
*
* @param context The PackageConfig to look up the interceptor from
* @param interceptorRefElement Element to pull interceptor ref data from
* @return A list of Interceptor objects
* @throws ConfigurationException in case of configuration errors
*/
protected List<InterceptorMapping> lookupInterceptorReference(PackageConfig.Builder context, Element interceptorRefElement) throws ConfigurationException {
String refName = interceptorRefElement.getAttribute("name");
Map<String, String> refParams = XmlHelper.getParams(interceptorRefElement);
Location loc = LocationUtils.getLocation(interceptorRefElement);
return InterceptorBuilder.constructInterceptorReference(context, refName, refParams, loc, objectFactory);
}
protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) {
NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref");
if (resultTypeList.getLength() > 0) {
Element defaultRefElement = (Element) resultTypeList.item(0);
packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name"));
}
}
}
@@ -201,11 +201,25 @@ public enum Scope {
<T> Callable<? extends T> toCallable(final InternalContext context,
final InternalFactory<? extends T> factory) {
return new Callable<T>() {
public T call() throws Exception {
return InitializableFactory.wrapIfNeeded(factory).create(context);
}
};
return (Callable<T>) () -> InitializableFactory.wrapIfNeeded(factory).create(context);
}
public static Scope fromString(String scopeStr) {
switch (scopeStr) {
case "prototype":
return Scope.PROTOTYPE;
case "request":
return Scope.REQUEST;
case "session":
return Scope.SESSION;
case "thread":
return Scope.THREAD;
case "wizard":
return Scope.WIZARD;
case "singleton":
default:
return Scope.SINGLETON;
}
}
/**
@@ -52,9 +52,9 @@ import java.util.Map;
* <ul>
*
* <li>logEnabled (optional) - Should exceptions also be logged? (boolean true|false)</li>
*
*
* <li>logLevel (optional) - what log level should we use (<code>trace, debug, info, warn, error, fatal</code>)? - defaut is <code>debug</code></li>
*
*
* <li>logCategory (optional) - If provided we would use this category (eg. <code>com.mycompany.app</code>).
* Default is to use <code>com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor</code>.</li>
*
@@ -102,7 +102,7 @@ import java.util.Map;
* &lt;/xwork&gt;
* <!-- END SNIPPET: example -->
* </pre>
*
*
* <p>
* This second example will also log the exceptions using our own category
* <code>com.mycompany.app.unhandled</code> at WARN level.
@@ -117,8 +117,8 @@ import java.util.Map;
* &lt;interceptor-ref name="exception"&gt;
* &lt;param name="logEnabled"&gt;true&lt;/param&gt;
* &lt;param name="logCategory"&gt;com.mycompany.app.unhandled&lt;/param&gt;
* &lt;param name="logLevel"&gt;WARN&lt;/param&gt;
* &lt;/interceptor-ref&gt;
* &lt;param name="logLevel"&gt;WARN&lt;/param&gt;
* &lt;/interceptor-ref&gt;
* &lt;interceptor-ref name="i18n"/&gt;
* &lt;interceptor-ref name="staticParams"/&gt;
* &lt;interceptor-ref name="params"/&gt;
@@ -129,7 +129,7 @@ import java.util.Map;
* &lt;/interceptors&gt;
*
* &lt;default-interceptor-ref name="exceptionmappingStack"/&gt;
*
*
* &lt;global-results&gt;
* &lt;result name="unhandledException"&gt;/unhandled-exception.jsp&lt;/result&gt;
* &lt;/global-results&gt;
@@ -137,12 +137,12 @@ import java.util.Map;
* &lt;global-exception-mappings&gt;
* &lt;exception-mapping exception="java.lang.Exception" result="unhandledException"/&gt;
* &lt;/global-exception-mappings&gt;
*
*
* &lt;action name="exceptionDemo" class="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingAction"&gt;
* &lt;exception-mapping exception="org.apache.struts2.showcase.exceptionmapping.ExceptionMappingException"
* result="damm"/&gt;
* &lt;result name="input"&gt;index.jsp&lt;/result&gt;
* &lt;result name="success"&gt;success.jsp&lt;/result&gt;
* &lt;result name="success"&gt;success.jsp&lt;/result&gt;
* &lt;result name="damm"&gt;damm.jsp&lt;/result&gt;
* &lt;/action&gt;
*
@@ -151,18 +151,18 @@ import java.util.Map;
* <!-- END SNIPPET: example2 -->
* </pre>
*
* @author Matthew E. Porter (matthew dot porter at metissian dot com)
* @author Matthew E. Porter (matthew dot porter at metissian dot com)
* @author Claus Ibsen
*/
public class ExceptionMappingInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ExceptionMappingInterceptor.class);
protected Logger categoryLogger;
protected boolean logEnabled = false;
protected String logCategory;
protected String logLevel;
public boolean isLogEnabled() {
return logEnabled;
@@ -204,7 +204,7 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
Map<String, String> mappingParams = mappingConfig.getParams();
// create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
HttpParameters parameters = HttpParameters.create(mappingParams).build();
invocation.getInvocationContext().setParameters(parameters);
invocation.getInvocationContext().withParameters(parameters);
result = mappingConfig.getResult();
publishException(invocation, new ExceptionHolder(e));
} else {
@@ -217,7 +217,7 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
/**
* Handles the logging of the exception.
*
*
* @param e the exception to log.
*/
protected void handleLogging(Exception e) {
@@ -231,10 +231,10 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
doLog(LOG, e);
}
}
/**
* Performs the actual logging.
*
*
* @param logger the provided logger to use.
* @param e the exception to log.
*/
@@ -243,7 +243,7 @@ public class ExceptionMappingInterceptor extends AbstractInterceptor {
logger.debug(e.getMessage(), e);
return;
}
if ("trace".equalsIgnoreCase(logLevel)) {
logger.trace(e.getMessage(), e);
} else if ("debug".equalsIgnoreCase(logLevel)) {
@@ -30,7 +30,8 @@ package com.opensymphony.xwork2.interceptor;
* parameters cannot be set by malicious users.
* </p>
*
* @author Dick Zetterberg (dick@transitor.se)
* @deprecated since Struts 6.2.0, use {@link org.apache.struts2.action.NoParameters}
*/
@Deprecated
public interface NoParameters {
}
@@ -34,33 +34,33 @@ import java.util.TreeMap;
* <!-- START SNIPPET: description -->
*
* The Parameter Filter Interceptor blocks parameters from getting
* to the rest of the stack or your action. You can use multiple
* to the rest of the stack or your action. You can use multiple
* parameter filter interceptors for a given action, so, for example,
* you could use one in your default stack that filtered parameters
* you wanted blocked from every action and those you wanted blocked
* you wanted blocked from every action and those you wanted blocked
* from an individual action you could add an additional interceptor
* for each action.
*
*
* <!-- END SNIPPET: description -->
*
*
* <!-- START SNIPPET: parameters -->
*
* <ul>
* <li>allowed - a comma delimited list of parameter prefixes
* that are allowed to pass to the action</li>
* <li>blocked - a comma delimited list of parameter prefixes
* <li>blocked - a comma delimited list of parameter prefixes
* that are not allowed to pass to the action</li>
* <li>defaultBlock - boolean (default to false) whether by
* default a given parameter is blocked. If true, then a parameter
* must have a prefix in the allowed list in order to be able
* must have a prefix in the allowed list in order to be able
* to pass to the action
* </ul>
*
*
* <p>The way parameters are filtered for the least configuration is that
* if a string is in the allowed or blocked lists, then any parameter
* that is a member of the object represented by the parameter is allowed
* or blocked respectively.</p>
*
*
* <p>For example, if the parameters are:
* <ul>
* <li>blocked: person,person.address.createDate,personDao</li>
@@ -69,16 +69,16 @@ import java.util.TreeMap;
* </ul>
*
* <p>
* The parameters person.name, person.phoneNum etc would be blocked
* The parameters person.name, person.phoneNum etc would be blocked
* because 'person' is in the blocked list. However, person.address.street
* and person.address.city would be allowed because person.address is
* in the allowed list (the longer string determines permissions).</p>
* in the allowed list (the longer string determines permissions).</p>
* <!-- END SNIPPET: parameters -->
*
* <!-- START SNIPPET: extending -->
* There are no known extension points to this interceptor.
* <!-- END SNIPPET: extending -->
*
*
* <pre>
* <!-- START SNIPPET: example -->
* &lt;interceptors&gt;
@@ -86,7 +86,7 @@ import java.util.TreeMap;
* &lt;interceptor name="parameterFilter" class="com.opensymphony.xwork2.interceptor.ParameterFilterInterceptor"/&gt;
* ...
* &lt;/interceptors&gt;
*
*
* &lt;action ....&gt;
* ...
* &lt;interceptor-ref name="parameterFilter"&gt;
@@ -96,7 +96,7 @@ import java.util.TreeMap;
* &lt;/action&gt;
* <!-- END SNIPPET: example -->
* </pre>
*
*
* @author Gabe
*/
public class ParameterFilterInterceptor extends AbstractInterceptor {
@@ -133,7 +133,7 @@ public class ParameterFilterInterceptor extends AbstractInterceptor {
}
}
invocation.getInvocationContext().setParameters(parameters);
invocation.getInvocationContext().withParameters(parameters);
return invocation.invoke();
}
@@ -23,120 +23,102 @@ import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.util.TextParseUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.Parameter;
import org.apache.struts2.action.NoParameters;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.Parameter;
import java.util.Collections;
import java.util.Set;
/**
* <!-- START SNIPPET: description -->
* This is a simple XWork interceptor that allows parameters (matching
* one of the paramNames attribute csv value) to be
* one of the paramNames attribute csv value) to be
* removed from the parameter map if they match a certain value
* (matching one of the paramValues attribute csv value), before they
* are set on the action. A typical usage would be to want a dropdown/select
* to map onto a boolean value on an action. The select had the options
* none, yes and no with values -1, true and false. The true and false would
* map across correctly. However the -1 would be set to false.
* This was not desired as one might needed the value on the action to stay null.
* This interceptor fixes this by preventing the parameter from ever reaching
* (matching one of the paramValues attribute csv value), before they
* are set on the action. A typical usage would be to want a dropdown/select
* to map onto a boolean value on an action. The select had the options
* none, yes and no with values -1, true and false. The true and false would
* map across correctly. However the -1 would be set to false.
* This was not desired as one might needed the value on the action to stay null.
* This interceptor fixes this by preventing the parameter from ever reaching
* the action.
* <!-- END SNIPPET: description -->
*
*
* <!-- START SNIPPET: parameters -->
*
* <ul>
* <li>paramNames - A comma separated value (csv) indicating the parameter name
* whose param value should be considered that if they match any of the
* comma separated value (csv) from paramValues attribute, shall be
* removed from the parameter map such that they will not be applied
* to the action</li>
* <li>paramValues - A comma separated value (csv) indicating the parameter value that if
* matched shall have its parameter be removed from the parameter map
* such that they will not be applied to the action</li>
* <li>paramNames - A comma separated value (csv) indicating the parameter name
* whose param value should be considered that if they match any of the
* comma separated value (csv) from paramValues attribute, shall be
* removed from the parameter map such that they will not be applied
* to the action</li>
* <li>paramValues - A comma separated value (csv) indicating the parameter value that if
* matched shall have its parameter be removed from the parameter map
* such that they will not be applied to the action</li>
* </ul>
* <!-- END SNIPPET: parameters -->
*
*
* <!-- START SNIPPET: extending -->
* <p>
* No intended extension point
* <!-- END SNIPPET: extending -->
*
*
* <pre>
* <!-- START SNIPPET: example -->
*
* &lt;action name="sample" class="org.martingilday.Sample"&gt;
* &lt;interceptor-ref name="paramRemover"&gt;
* &lt;param name="paramNames"&gt;aParam,anotherParam&lt;/param&gt;
* &lt;param name="paramValues"&gt;--,-1&lt;/param&gt;
* &lt;param name="paramNames"&gt;aParam,anotherParam&lt;/param&gt;
* &lt;param name="paramValues"&gt;--,-1&lt;/param&gt;
* &lt;/interceptor-ref&gt;
* &lt;interceptor-ref name="defaultStack" /&gt;
* ...
* &lt;/action&gt;
*
* <!-- END SNIPPET: example -->
* </pre>
*
*
* @author martin.gilday
*/
public class ParameterRemoverInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class);
private static final Logger LOG = LogManager.getLogger(ParameterRemoverInterceptor.class);
private static final long serialVersionUID = 1;
private Set<String> paramNames = Collections.emptySet();
private Set<String> paramValues = Collections.emptySet();
private Set<String> paramNames = Collections.emptySet();
private Set<String> paramValues = Collections.emptySet();
/**
* Decide if the parameter should be removed from the parameter map based on
* <code>paramNames</code> and <code>paramValues</code>.
*
* @see com.opensymphony.xwork2.interceptor.AbstractInterceptor
*/
@Override
public String intercept(ActionInvocation invocation) throws Exception {
if (!(invocation.getAction() instanceof NoParameters)
&& (null != this.paramNames)) {
ActionContext ac = invocation.getInvocationContext();
HttpParameters parameters = ac.getParameters();
/**
* Decide if the parameter should be removed from the parameter map based on
* <code>paramNames</code> and <code>paramValues</code>.
*
* @see com.opensymphony.xwork2.interceptor.AbstractInterceptor
*/
@Override
public String intercept(ActionInvocation invocation) throws Exception {
if (!(invocation.getAction() instanceof NoParameters)
&& (null != this.paramNames)) {
ActionContext ac = invocation.getInvocationContext();
HttpParameters parameters = ac.getParameters();
if (parameters != null) {
if (parameters != null) {
for (String removeName : paramNames) {
try {
Parameter parameter = parameters.get(removeName);
if (parameter.isDefined() && this.paramValues.contains(parameter.getValue())) {
parameters.remove(removeName);
}
} catch (Exception e) {
LOG.error("Failed to convert parameter to string", e);
}
try {
Parameter parameter = parameters.get(removeName);
if (parameter.isDefined() && this.paramValues.contains(parameter.getValue())) {
parameters.remove(removeName);
}
} catch (Exception e) {
LOG.error("Failed to convert parameter to string", e);
}
}
}
}
return invocation.invoke();
}
}
}
return invocation.invoke();
}
/**
* Allows <code>paramNames</code> attribute to be set as comma-separated-values (csv).
*
* @param paramNames the paramNames to set
*/
public void setParamNames(String paramNames) {
this.paramNames = TextParseUtil.commaDelimitedStringToSet(paramNames);
}
/**
* Allows <code>paramNames</code> attribute to be set as comma-separated-values (csv).
*
* @param paramNames the paramNames to set
*/
public void setParamNames(String paramNames) {
this.paramNames = TextParseUtil.commaDelimitedStringToSet(paramNames);
}
/**
* Allows <code>paramValues</code> attribute to be set as a comma-separated-values (csv).
*
* @param paramValues the paramValues to set
*/
public void setParamValues(String paramValues) {
this.paramValues = TextParseUtil.commaDelimitedStringToSet(paramValues);
}
/**
* Allows <code>paramValues</code> attribute to be set as a comma-separated-values (csv).
*
* @param paramValues the paramValues to set
*/
public void setParamValues(String paramValues) {
this.paramValues = TextParseUtil.commaDelimitedStringToSet(paramValues);
}
}
@@ -236,6 +236,6 @@ public class StaticParametersInterceptor extends AbstractInterceptor {
combinedParams = HttpParameters.create(newParams);
combinedParams = combinedParams.withExtraParams(previousParams);
}
ac.setParameters(combinedParams.build());
ac.withParameters(combinedParams.build());
}
}
@@ -104,7 +104,7 @@ public class AnnotationParameterFilterInterceptor extends AbstractInterceptor {
}
}
invocation.getInvocationContext().setParameters(parameters);
invocation.getInvocationContext().withParameters(parameters);
return invocation.invoke();
}
@@ -26,7 +26,13 @@ import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.TextParseUtil;
import com.opensymphony.xwork2.util.reflection.ReflectionException;
import ognl.*;
import ognl.ClassResolver;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.OgnlRuntime;
import ognl.SimpleNode;
import ognl.TypeConverter;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -37,7 +43,12 @@ import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
@@ -66,10 +77,12 @@ public class OgnlUtil {
private Set<Class<?>> excludedClasses;
private Set<Pattern> excludedPackageNamePatterns;
private Set<String> excludedPackageNames;
private Set<Class<?>> excludedPackageExemptClasses;
private Set<Class<?>> devModeExcludedClasses;
private Set<Pattern> devModeExcludedPackageNamePatterns;
private Set<String> devModeExcludedPackageNames;
private Set<Class<?>> devModeExcludedPackageExemptClasses;
private Container container;
private boolean allowStaticFieldAccess = true;
@@ -112,10 +125,12 @@ public class OgnlUtil {
excludedClasses = Collections.unmodifiableSet(new HashSet<>());
excludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>());
excludedPackageNames = Collections.unmodifiableSet(new HashSet<>());
excludedPackageExemptClasses = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedClasses = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageNamePatterns = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageNames = Collections.unmodifiableSet(new HashSet<>());
devModeExcludedPackageExemptClasses = Collections.unmodifiableSet(new HashSet<>());
this.expressionCache = ognlExpressionCacheFactory.buildOgnlCache();
this.beanInfoCache = ognlBeanInfoCacheFactory.buildOgnlCache();
@@ -159,7 +174,7 @@ public class OgnlUtil {
protected void setExcludedClasses(String commaDelimitedClasses) {
Set<Class<?>> excludedClasses = new HashSet<>();
excludedClasses.addAll(this.excludedClasses);
excludedClasses.addAll(parseExcludedClasses(commaDelimitedClasses));
excludedClasses.addAll(parseClasses(commaDelimitedClasses));
this.excludedClasses = Collections.unmodifiableSet(excludedClasses);
}
@@ -167,11 +182,11 @@ public class OgnlUtil {
protected void setDevModeExcludedClasses(String commaDelimitedClasses) {
Set<Class<?>> excludedClasses = new HashSet<>();
excludedClasses.addAll(this.devModeExcludedClasses);
excludedClasses.addAll(parseExcludedClasses(commaDelimitedClasses));
excludedClasses.addAll(parseClasses(commaDelimitedClasses));
this.devModeExcludedClasses = Collections.unmodifiableSet(excludedClasses);
}
private Set<Class<?>> parseExcludedClasses(String commaDelimitedClasses) {
private Set<Class<?>> parseClasses(String commaDelimitedClasses) {
Set<String> classNames = TextParseUtil.commaDelimitedStringToSet(commaDelimitedClasses);
Set<Class<?>> classes = new HashSet<>();
@@ -179,7 +194,7 @@ public class OgnlUtil {
try {
classes.add(Class.forName(className));
} catch (ClassNotFoundException e) {
throw new ConfigurationException("Cannot load excluded class: " + className, e);
throw new ConfigurationException("Cannot load class for exclusion/exemption configuration: " + className, e);
}
}
@@ -229,6 +244,22 @@ public class OgnlUtil {
this.devModeExcludedPackageNames = Collections.unmodifiableSet(excludedPackageNames);
}
@Inject(value = StrutsConstants.STRUTS_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
public void setExcludedPackageExemptClasses(String commaDelimitedClasses) {
Set<Class<?>> excludedPackageExemptClasses = new HashSet<>();
excludedPackageExemptClasses.addAll(this.excludedPackageExemptClasses);
excludedPackageExemptClasses.addAll(parseClasses(commaDelimitedClasses));
this.excludedPackageExemptClasses = Collections.unmodifiableSet(excludedPackageExemptClasses);
}
@Inject(value = StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, required = false)
public void setDevModeExcludedPackageExemptClasses(String commaDelimitedClasses) {
Set<Class<?>> excludedPackageExemptClasses = new HashSet<>();
excludedPackageExemptClasses.addAll(this.devModeExcludedPackageExemptClasses);
excludedPackageExemptClasses.addAll(parseClasses(commaDelimitedClasses));
this.devModeExcludedPackageExemptClasses = Collections.unmodifiableSet(excludedPackageExemptClasses);
}
private Set<String> parseExcludedPackageNames(String commaDelimitedPackageNames) {
return TextParseUtil.commaDelimitedStringToSet(commaDelimitedPackageNames);
}
@@ -245,6 +276,10 @@ public class OgnlUtil {
return excludedPackageNames;
}
public Set<Class<?>> getExcludedPackageExemptClasses() {
return excludedPackageExemptClasses;
}
@Inject
protected void setContainer(Container container) {
this.container = container;
@@ -847,10 +882,12 @@ public class OgnlUtil {
memberAccess.setExcludedClasses(devModeExcludedClasses);
memberAccess.setExcludedPackageNamePatterns(devModeExcludedPackageNamePatterns);
memberAccess.setExcludedPackageNames(devModeExcludedPackageNames);
memberAccess.setExcludedPackageExemptClasses(devModeExcludedPackageExemptClasses);
} else {
memberAccess.setExcludedClasses(excludedClasses);
memberAccess.setExcludedPackageNamePatterns(excludedPackageNamePatterns);
memberAccess.setExcludedPackageNames(excludedPackageNames);
memberAccess.setExcludedPackageExemptClasses(excludedPackageExemptClasses);
}
return Ognl.createDefaultContext(root, memberAccess, resolver, defaultConverter);
@@ -29,7 +29,12 @@ import com.opensymphony.xwork2.util.CompoundRoot;
import com.opensymphony.xwork2.util.MemberAccessValueStack;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
import ognl.*;
import ognl.MethodFailedException;
import ognl.NoSuchPropertyException;
import ognl.Ognl;
import ognl.OgnlContext;
import ognl.OgnlException;
import ognl.PropertyAccessor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -87,6 +92,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
securityMemberAccess.setExcludedClasses(ognlUtil.getExcludedClasses());
securityMemberAccess.setExcludedPackageNamePatterns(ognlUtil.getExcludedPackageNamePatterns());
securityMemberAccess.setExcludedPackageNames(ognlUtil.getExcludedPackageNames());
securityMemberAccess.setExcludedPackageExemptClasses(ognlUtil.getExcludedPackageExemptClasses());
securityMemberAccess.setDisallowProxyMemberAccess(ognlUtil.isDisallowProxyMemberAccess());
}
@@ -28,6 +28,7 @@ import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
@@ -47,6 +48,7 @@ public class SecurityMemberAccess implements MemberAccess {
private Set<Class<?>> excludedClasses = Collections.emptySet();
private Set<Pattern> excludedPackageNamePatterns = Collections.emptySet();
private Set<String> excludedPackageNames = Collections.emptySet();
private Set<Class<?>> excludedPackageExemptClasses = Collections.emptySet();
private boolean disallowProxyMemberAccess;
/**
@@ -94,6 +96,12 @@ public class SecurityMemberAccess implements MemberAccess {
LOG.debug("Checking access for [target: {}, member: {}, property: {}]", target, member, propertyName);
final int memberModifiers = member.getModifiers();
final Class<?> memberClass = member.getDeclaringClass();
// target can be null in case of accessing static fields, since OGNL 3.2.8
final Class<?> targetClass = Modifier.isStatic(memberModifiers) ? memberClass : target.getClass();
if (!memberClass.isAssignableFrom(targetClass)) {
throw new IllegalArgumentException("Target does not match member!");
}
if (!checkPublicMemberAccess(memberModifiers)) {
LOG.warn("Access to non-public [{}] is blocked!", member);
@@ -116,24 +124,29 @@ public class SecurityMemberAccess implements MemberAccess {
return false;
}
final Class<?> memberClass = member.getDeclaringClass();
if (isClassExcluded(memberClass)) {
LOG.warn("Declaring class of member type [{}] is excluded!", member);
return false;
}
// target can be null in case of accessing static fields, since OGNL 3.2.8
final Class<?> targetClass = Modifier.isStatic(memberModifiers) ? memberClass : target.getClass();
if (isPackageExcluded(targetClass.getPackage(), memberClass.getPackage())) {
LOG.warn("Package [{}] of target class [{}] of target [{}] or package [{}] of member [{}] are excluded!", targetClass.getPackage(), targetClass,
target, memberClass.getPackage(), member);
if (targetClass != memberClass && isClassExcluded(targetClass)) {
// Optimization: Already checked memberClass exclusion, so if-and-only-if targetClass == memberClass, this check is redundant.
LOG.warn("Target class [{}] of target [{}] is excluded!", targetClass, target);
return false;
}
if (isClassExcluded(targetClass)) {
LOG.warn("Target class [{}] of target [{}] is excluded!", targetClass, target);
if (targetClass.getPackage() == null || memberClass.getPackage() == null) {
LOG.warn("The use of the default (unnamed) package is discouraged!");
}
if (isPackageExcluded(targetClass, memberClass)) {
LOG.warn(
"Package [{}] of target class [{}] of target [{}] or package [{}] of member [{}] are excluded!",
targetClass.getPackage(),
targetClass,
target,
memberClass.getPackage(),
member);
return false;
}
@@ -196,29 +209,49 @@ public class SecurityMemberAccess implements MemberAccess {
return false;
}
protected boolean isPackageExcluded(Package targetPackage, Package memberPackage) {
if (targetPackage == null || memberPackage == null) {
LOG.warn("The use of the default (unnamed) package is discouraged!");
protected boolean isPackageExcluded(Class<?> targetClass, Class<?> memberClass) {
if (targetClass == null || memberClass == null) {
throw new IllegalArgumentException(
"Parameters should never be null - if member is static, targetClass should be the same as memberClass.");
}
String targetPackageName = targetPackage == null ? "" : targetPackage.getName();
String memberPackageName = memberPackage == null ? "" : memberPackage.getName();
Set<Class<?>> classesToCheck = new HashSet<>();
classesToCheck.add(targetClass);
classesToCheck.add(memberClass);
for (Class<?> clazz : classesToCheck) {
if (!isExcludedPackageExempt(clazz) && (isExcludedPackageNamePatterns(clazz) || isExcludedPackageNames(clazz))) {
return true;
}
}
return false;
}
protected String toPackageName(Class<?> clazz) {
if (clazz.getPackage() == null) {
return "";
} else {
return clazz.getPackage().getName();
}
}
protected boolean isExcludedPackageNamePatterns(Class<?> clazz) {
String packageName = toPackageName(clazz);
for (Pattern pattern : excludedPackageNamePatterns) {
if (pattern.matcher(targetPackageName).matches() || pattern.matcher(memberPackageName).matches()) {
if (pattern.matcher(packageName).matches()) {
return true;
}
}
return false;
}
targetPackageName = targetPackageName + ".";
memberPackageName = memberPackageName + ".";
for (String packageName : excludedPackageNames) {
if (targetPackageName.startsWith(packageName) || memberPackageName.startsWith(packageName)) {
protected boolean isExcludedPackageNames(Class<?> clazz) {
String suffixedPackageName = toPackageName(clazz) + ".";
for (String excludedPackageName : excludedPackageNames) {
if (suffixedPackageName.startsWith(excludedPackageName)) {
return true;
}
}
return false;
}
@@ -226,12 +259,11 @@ public class SecurityMemberAccess implements MemberAccess {
if (clazz == Object.class || (clazz == Class.class && !allowStaticFieldAccess)) {
return true;
}
for (Class<?> excludedClass : excludedClasses) {
if (clazz.isAssignableFrom(excludedClass)) {
return true;
}
}
return false;
return excludedClasses.stream().anyMatch(clazz::isAssignableFrom);
}
protected boolean isExcludedPackageExempt(Class<?> clazz) {
return excludedPackageExemptClasses.stream().anyMatch(clazz::equals);
}
protected boolean isAcceptableProperty(String name) {
@@ -287,6 +319,10 @@ public class SecurityMemberAccess implements MemberAccess {
this.excludedPackageNames = excludedPackageNames;
}
public void setExcludedPackageExemptClasses(Set<Class<?>> excludedPackageExemptClasses) {
this.excludedPackageExemptClasses = excludedPackageExemptClasses;
}
public void setDisallowProxyMemberAccess(boolean disallowProxyMemberAccess) {
this.disallowProxyMemberAccess = disallowProxyMemberAccess;
}
@@ -109,6 +109,11 @@ public class XWorkListPropertyAccessor extends ListPropertyAccessor {
if (listSize <= index) {
Object result;
if (index > autoGrowCollectionLimit) {
throw new OgnlException("Error auto growing collection size to " + index + " which limited to "
+ autoGrowCollectionLimit);
}
for (int i = listSize; i < index; i++) {
list.add(null);
}
@@ -18,9 +18,11 @@
*/
package com.opensymphony.xwork2.util;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.ClassUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
@@ -52,7 +54,7 @@ public class AnnotationUtils {
* @param clazz The {@link Class} to inspect
* @param allFields list of all fields
*/
public static void addAllFields(Class<? extends Annotation> annotationClass, Class clazz, List<Field> allFields) {
public static void addAllFields(Class<? extends Annotation> annotationClass, Class<?> clazz, List<Field> allFields) {
if (clazz == null) {
return;
@@ -76,7 +78,7 @@ public class AnnotationUtils {
* @param clazz The {@link Class} to inspect
* @param allMethods list of all methods
*/
public static void addAllMethods(Class<? extends Annotation> annotationClass, Class clazz, List<Method> allMethods) {
public static void addAllMethods(Class<? extends Annotation> annotationClass, Class<?> clazz, List<Method> allMethods) {
if (clazz == null) {
return;
@@ -97,12 +99,12 @@ public class AnnotationUtils {
* @param clazz The {@link Class} to inspect
* @param allInterfaces list of all interfaces
*/
public static void addAllInterfaces(Class clazz, List<Class> allInterfaces) {
public static void addAllInterfaces(Class<?> clazz, List<Class<?>> allInterfaces) {
if (clazz == null) {
return;
}
Class[] interfaces = clazz.getInterfaces();
Class<?>[] interfaces = clazz.getInterfaces();
allInterfaces.addAll(Arrays.asList(interfaces));
addAllInterfaces(clazz.getSuperclass(), allInterfaces);
}
@@ -189,4 +191,21 @@ public class AnnotationUtils {
return anns;
}
/**
* Varargs version of <code>AnnotatedElement.isAnnotationPresent()</code>
*
* @see AnnotatedElement
*/
@SafeVarargs
public static boolean isAnnotatedBy(AnnotatedElement annotatedElement, Class<? extends Annotation>... annotation) {
if (ArrayUtils.isEmpty(annotation)) return false;
for (Class<? extends Annotation> c : annotation) {
if (annotatedElement.isAnnotationPresent(c)) return true;
}
return false;
}
}
@@ -87,8 +87,8 @@ public class NamedVariablePatternMatcher implements PatternMatcher<NamedVariable
int s = 0;
while (s < len) {
int e = data.indexOf('{', s);
if (e < 0 && data.indexOf('}') > -1) {
throw new IllegalArgumentException("Missing openning '{' in [" + data + "]!");
if (e < 0 && data.indexOf('}', s) > -1) {
throw new IllegalArgumentException("Missing opening '{' in [" + data + "]!");
}
if (e < 0) {
regex.append(Pattern.quote(data.substring(s)));
@@ -18,20 +18,14 @@
*/
package com.opensymphony.xwork2.validator;
import com.opensymphony.xwork2.*;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.ValueStack;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
/**
* AnnotationActionValidatorManager is the entry point into XWork's annotations-based validator framework.
@@ -40,185 +34,9 @@ import java.util.*;
* @author Rainer Hermanns
* @author jepjep
*/
public class AnnotationActionValidatorManager implements ActionValidatorManager {
public class AnnotationActionValidatorManager extends DefaultActionValidatorManager {
/**
* The file suffix for any validation file.
*/
protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
private final Map<String, List<ValidatorConfig>> validatorCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
private final Map<String, List<ValidatorConfig>> validatorFileCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
private static final Logger LOG = LogManager.getLogger(AnnotationActionValidatorManager.class);
private ValidatorFactory validatorFactory;
private ValidatorFileParser validatorFileParser;
private FileManager fileManager;
private boolean reloadingConfigs;
private TextProviderFactory textProviderFactory;
@Inject
public void setValidatorFactory(ValidatorFactory fac) {
this.validatorFactory = fac;
}
@Inject
public void setValidatorFileParser(ValidatorFileParser parser) {
this.validatorFileParser = parser;
}
@Inject
public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
this.fileManager = fileManagerFactory.getFileManager();
}
@Inject(value = StrutsConstants.STRUTS_CONFIGURATION_XML_RELOAD, required = false)
public void setReloadingConfigs(String reloadingConfigs) {
this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
}
@Inject
public void setTextProviderFactory(TextProviderFactory textProviderFactory) {
this.textProviderFactory = textProviderFactory;
}
public List<Validator> getValidators(Class clazz, String context) {
return getValidators(clazz, context, null);
}
public List<Validator> getValidators(Class clazz, String context, String method) {
final String validatorKey = buildValidatorKey(clazz, context);
final List<ValidatorConfig> cfgs;
if (validatorCache.containsKey(validatorKey)) {
if (reloadingConfigs) {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
}
} else {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
}
// get the set of validator configs
cfgs = new ArrayList<ValidatorConfig>(validatorCache.get(validatorKey));
ValueStack stack = ActionContext.getContext().getValueStack();
// create clean instances of the validators for the caller's use
ArrayList<Validator> validators = new ArrayList<>(cfgs.size());
for (ValidatorConfig cfg : cfgs) {
if (method == null || method.equals(cfg.getParams().get("methodName"))) {
Validator validator = validatorFactory.getValidator(
new ValidatorConfig.Builder(cfg)
.removeParam("methodName")
.build());
validator.setValidatorType(cfg.getType());
validator.setValueStack(stack);
validators.add(validator);
}
}
return validators;
}
public void validate(Object object, String context) throws ValidationException {
validate(object, context, (String) null);
}
public void validate(Object object, String context, String method) throws ValidationException {
ValidatorContext validatorContext = new DelegatingValidatorContext(object, textProviderFactory);
validate(object, context, validatorContext, method);
}
public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
validate(object, context, validatorContext, null);
}
public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
List<Validator> validators = getValidators(object.getClass(), context, method);
Set<String> shortcircuitedFields = null;
for (final Validator validator : validators) {
try {
validator.setValidatorContext(validatorContext);
LOG.debug("Running validator: {} for object {} and method {}", validator, object, method);
FieldValidator fValidator = null;
String fullFieldName = null;
if (validator instanceof FieldValidator) {
fValidator = (FieldValidator) validator;
fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName());
if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
LOG.debug("Short-circuited, skipping");
continue;
}
}
if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
// get number of existing errors
List<String> errs = null;
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
if (fieldErrors != null) {
errs = new ArrayList<>(fieldErrors);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection<String> actionErrors = validatorContext.getActionErrors();
if (actionErrors != null) {
errs = new ArrayList<>(actionErrors);
}
}
validator.validate(object);
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
if ((errCol != null) && !errCol.equals(errs)) {
LOG.debug("Short-circuiting on field validation");
if (shortcircuitedFields == null) {
shortcircuitedFields = new TreeSet<String>();
}
shortcircuitedFields.add(fullFieldName);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection<String> errCol = validatorContext.getActionErrors();
if ((errCol != null) && !errCol.equals(errs)) {
LOG.debug("Short-circuiting");
break;
}
}
continue;
}
validator.validate(object);
} finally {
validator.setValidatorContext(null);
}
}
}
/**
* Builds a key for validators - used when caching validators.
*
* @param clazz the action.
* @param context context
* @return a validator key which is the class name plus context.
*/
@Override
protected String buildValidatorKey(Class clazz, String context) {
ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
ActionProxy proxy = invocation.getProxy();
@@ -230,20 +48,11 @@ public class AnnotationActionValidatorManager implements ActionValidatorManager
sb.append(config.getPackageName());
sb.append("/");
}
// the key needs to use the name of the action from the config file,
// instead of the url, so wild card actions will have the same validator
// see WW-2996
// UPDATE:
// WW-3753 Using the config name instead of the context only for
// wild card actions to keep the flexibility provided
// by the original design (such as mapping different contexts
// to the same action and method if desired)
// UPDATE:
// WW-4536 Using NameVariablePatternMatcher allows defines actions
// with patterns enclosed with '{}', it's similar case to WW-3753
// WW-2996: key needs to use the name of the action from the config file, instead of the url,
// so wildcard actions will have the same validator
// WW-3753: Using the config name instead of the context only for wildcard actions to keep the flexibility
// provided by the original design (such as mapping different contexts to the same action and method if desired)
// WW-4536: Using NamedVariablePatternMatcher allows defines actions with patterns enclosed with '{}'
String configName = config.getName();
if (configName.contains(ActionConfig.WILDCARD) || (configName.contains("{") && configName.contains("}"))) {
sb.append(configName);
@@ -252,143 +61,22 @@ public class AnnotationActionValidatorManager implements ActionValidatorManager
} else {
sb.append(context);
}
return sb.toString();
}
private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
@Override
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context.replace('/', '-') + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
@Override
protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
List<ValidatorConfig> result = new ArrayList<>(loadFile(fileName, aClass, checkFile));
AnnotationValidationConfigurationBuilder builder = new AnnotationValidationConfigurationBuilder(validatorFactory);
List<ValidatorConfig> annotationResult = new ArrayList<>(builder.buildAnnotationClassValidatorConfigs(aClass));
result.addAll(annotationResult);
return result;
}
/**
* <p>This method 'collects' all the validator configurations for a given
* action invocation.</p>
*
* <p>It will traverse up the class hierarchy looking for validators for every super class
* and directly implemented interface of the current action, as well as adding validators for
* any alias of this invocation. Nifty!</p>
*
* <p>Given the following class structure:</p>
* <pre>
* interface Thing;
* interface Animal extends Thing;
* interface Quadraped extends Animal;
* class AnimalImpl implements Animal;
* class QuadrapedImpl extends AnimalImpl implements Quadraped;
* class Dog extends QuadrapedImpl;
* </pre>
*
* <p>This method will look for the following config files for Dog:</p>
* <pre>
* Animal
* Animal-context
* AnimalImpl
* AnimalImpl-context
* Quadraped
* Quadraped-context
* QuadrapedImpl
* QuadrapedImpl-context
* Dog
* Dog-context
* </pre>
*
* <p>Note that the validation rules for Thing is never looked for because no class in the
* hierarchy directly implements Thing.</p>
*
* @param clazz the Class to look up validators for.
* @param context the context to use when looking up validators.
* @param checkFile true if the validation config file should be checked to see if it has been
* updated.
* @param checked the set of previously checked class-contexts, null if none have been checked
* @return a list of validator configs for the given class and context.
*/
private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
List<ValidatorConfig> validatorConfigs = new ArrayList<>();
if (checked == null) {
checked = new TreeSet<>();
} else if (checked.contains(clazz.getName())) {
return validatorConfigs;
}
if (clazz.isInterface()) {
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
}
} else {
if (!clazz.equals(Object.class)) {
validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
}
}
// look for validators for implemented interfaces
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface1 : interfaces) {
if (checked.contains(anInterface1.getName())) {
continue;
}
validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
if (context != null) {
validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
}
checked.add(anInterface1.getName());
}
validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
if (context != null) {
validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
}
checked.add(clazz.getName());
return validatorConfigs;
}
private List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
List<ValidatorConfig> retList = Collections.emptyList();
URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) {
try (InputStream is = fileManager.loadFile(fileUrl)) {
if (is != null) {
retList = new ArrayList<>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
}
} catch (IOException e) {
LOG.error("Caught exception while loading file {}", fileName, e);
}
validatorFileCache.put(fileName, retList);
} else {
retList = validatorFileCache.get(fileName);
}
return retList;
}
}
@@ -32,7 +32,16 @@ import org.apache.struts2.StrutsConstants;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import static java.util.Collections.synchronizedMap;
/**
* <p>
@@ -53,29 +62,30 @@ import java.util.*;
*/
public class DefaultActionValidatorManager implements ActionValidatorManager {
private final static Logger LOG = LogManager.getLogger(DefaultActionValidatorManager.class);
/** The file suffix for any validation file. */
/**
* The file suffix for any validation file.
*/
protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";
private final Map<String, List<ValidatorConfig>> validatorCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
private final Map<String, List<ValidatorConfig>> validatorFileCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
protected final Map<String, List<ValidatorConfig>> validatorCache = synchronizedMap(new HashMap<>());
protected final Map<String, List<ValidatorConfig>> validatorFileCache = synchronizedMap(new HashMap<>());
private static final Logger LOG = LogManager.getLogger(DefaultActionValidatorManager.class);
private ValidatorFactory validatorFactory;
private ValidatorFileParser validatorFileParser;
private FileManager fileManager;
private boolean reloadingConfigs;
private TextProviderFactory textProviderFactory;
protected ValidatorFactory validatorFactory;
protected ValidatorFileParser validatorFileParser;
protected FileManager fileManager;
protected boolean reloadingConfigs;
protected TextProviderFactory textProviderFactory;
@Inject
public void setValidatorFactory(ValidatorFactory fac) {
this.validatorFactory = fac;
}
@Inject
public void setValidatorFileParser(ValidatorFileParser parser) {
this.validatorFileParser = parser;
}
@Inject
public void setValidatorFactory(ValidatorFactory fac) {
this.validatorFactory = fac;
}
@Inject
public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
@@ -92,153 +102,137 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
this.textProviderFactory = textProviderFactory;
}
public synchronized List<Validator> getValidators(Class clazz, String context) {
return getValidators(clazz, context, null);
}
public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
final String validatorKey = buildValidatorKey(clazz, context);
if (validatorCache.containsKey(validatorKey)) {
if (reloadingConfigs) {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
}
} else {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
}
ValueStack stack = ActionContext.getContext().getValueStack();
// get the set of validator configs
List<ValidatorConfig> cfgs = validatorCache.get(validatorKey);
// create clean instances of the validators for the caller's use
ArrayList<Validator> validators = new ArrayList<>(cfgs.size());
for (ValidatorConfig cfg : cfgs) {
if (method == null || method.equals(cfg.getParams().get("methodName"))) {
Validator validator = validatorFactory.getValidator(cfg);
validator.setValidatorType(cfg.getType());
validator.setValueStack(stack);
validators.add(validator);
}
}
return validators;
}
@Override
public void validate(Object object, String context) throws ValidationException {
validate(object, context, (String) null);
}
@Override
public void validate(Object object, String context, String method) throws ValidationException {
ValidatorContext validatorContext = new DelegatingValidatorContext(object, textProviderFactory);
validate(object, context, validatorContext, method);
}
@Override
public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
validate(object, context, validatorContext, null);
}
public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
List<Validator> validators = getValidators(object.getClass(), context, method);
Set<String> shortcircuitedFields = null;
for (final Validator validator : validators) {
try {
validator.setValidatorContext(validatorContext);
LOG.debug("Running validator: {} for object {} and method {}", validator, object, method);
FieldValidator fValidator = null;
String fullFieldName = null;
if (validator instanceof FieldValidator) {
fValidator = (FieldValidator) validator;
fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName());
if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
LOG.debug("Short-circuited, skipping");
continue;
}
}
if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
// get number of existing errors
List<String> errs = null;
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
if (fieldErrors != null) {
errs = new ArrayList<>(fieldErrors);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection<String> actionErrors = validatorContext.getActionErrors();
if (actionErrors != null) {
errs = new ArrayList<String>(actionErrors);
}
}
validator.validate(object);
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
if ((errCol != null) && !errCol.equals(errs)) {
LOG.debug("Short-circuiting on field validation");
if (shortcircuitedFields == null) {
shortcircuitedFields = new TreeSet<>();
}
shortcircuitedFields.add(fullFieldName);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection<String> errCol = validatorContext.getActionErrors();
if ((errCol != null) && !errCol.equals(errs)) {
LOG.debug("Short-circuiting");
break;
}
}
continue;
}
validator.validate(object);
}
finally {
validator.setValidatorContext(null);
}
}
}
/**
* Builds a key for validators - used when caching validators.
*
* @param clazz the action.
* @param context the action's context.
* @param context context
* @return a validator key which is the class name plus context.
*/
protected static String buildValidatorKey(Class clazz, String context) {
StringBuilder sb = new StringBuilder(clazz.getName());
sb.append("/");
sb.append(context);
return sb.toString();
protected String buildValidatorKey(Class clazz, String context) {
return clazz.getName() + "/" + context;
}
private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
protected Validator getValidatorFromValidatorConfig(ValidatorConfig config, ValueStack stack) {
Validator validator = validatorFactory.getValidator(config);
validator.setValidatorType(config.getType());
validator.setValueStack(stack);
return validator;
}
private List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
@Override
public synchronized List<Validator> getValidators(Class clazz, String context, String method) {
String validatorKey = buildValidatorKey(clazz, context);
return loadFile(fileName, aClass, checkFile);
if (!validatorCache.containsKey(validatorKey)) {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
} else if (reloadingConfigs) {
validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
}
ValueStack stack = ActionContext.getContext().getValueStack();
List<ValidatorConfig> configs = validatorCache.get(validatorKey);
List<Validator> validators = new ArrayList<>();
for (ValidatorConfig config : configs) {
if (method == null || method.equals(config.getParams().get("methodName"))) {
validators.add(getValidatorFromValidatorConfig(config, stack));
}
}
return validators;
}
@Override
public synchronized List<Validator> getValidators(Class clazz, String context) {
return getValidators(clazz, context, null);
}
@Override
public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
List<Validator> validators = getValidators(object.getClass(), context, method);
Set<String> shortcircuitedFields = null;
for (Validator validator : validators) {
validator.setValidatorContext(validatorContext);
LOG.debug("Running validator: {} for object {} and method {}", validator, object, method);
FieldValidator fValidator = null;
String fullFieldName = null;
if (validator instanceof FieldValidator) {
fValidator = (FieldValidator) validator;
fullFieldName = validatorContext.getFullFieldName(fValidator.getFieldName());
if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
LOG.debug("Short-circuited, skipping");
continue;
}
}
if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
// get number of existing errors
List<String> errs = null;
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);
if (fieldErrors != null) {
errs = new ArrayList<>(fieldErrors);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection<String> actionErrors = validatorContext.getActionErrors();
if (actionErrors != null) {
errs = new ArrayList<>(actionErrors);
}
}
validator.validate(object);
if (fValidator != null) {
if (validatorContext.hasFieldErrors()) {
Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);
if ((errCol != null) && !errCol.equals(errs)) {
LOG.debug("Short-circuiting on field validation");
if (shortcircuitedFields == null) {
shortcircuitedFields = new TreeSet<>();
}
shortcircuitedFields.add(fullFieldName);
}
}
} else if (validatorContext.hasActionErrors()) {
Collection<String> errCol = validatorContext.getActionErrors();
if ((errCol != null) && !errCol.equals(errs)) {
LOG.debug("Short-circuiting");
break;
}
}
continue;
}
validator.validate(object);
}
}
/**
@@ -276,18 +270,18 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
* <p>Note that the validation rules for Thing is never looked for because no class in the
* hierarchy directly implements Thing.</p>
*
* @param clazz the Class to look up validators for.
* @param context the context to use when looking up validators.
* @param clazz the Class to look up validators for.
* @param context the context to use when looking up validators.
* @param checkFile true if the validation config file should be checked to see if it has been
* updated.
* @param checked the set of previously checked class-contexts, null if none have been checked
* updated.
* @param checked the set of previously checked class-contexts, null if none have been checked
* @return a list of validator configs for the given class and context.
*/
private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
protected List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
List<ValidatorConfig> validatorConfigs = new ArrayList<>();
if (checked == null) {
checked = new TreeSet<String>();
checked = new TreeSet<>();
} else if (checked.contains(clazz.getName())) {
return validatorConfigs;
}
@@ -295,7 +289,7 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
if (clazz.isInterface()) {
for (Class anInterface : clazz.getInterfaces()) {
validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
}
}
} else {
if (!clazz.equals(Object.class)) {
validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
@@ -307,37 +301,44 @@ public class DefaultActionValidatorManager implements ActionValidatorManager {
if (checked.contains(anInterface1.getName())) {
continue;
}
validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));
if (context != null) {
validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
}
checked.add(anInterface1.getName());
}
validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
if (context != null) {
validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
}
checked.add(clazz.getName());
return validatorConfigs;
}
private List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
protected List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + "-" + context + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {
String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;
return loadFile(fileName, aClass, checkFile);
}
protected List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
List<ValidatorConfig> retList = Collections.emptyList();
URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);
if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) {
try (InputStream is = fileManager.loadFile(fileUrl)) {
if (is != null) {
retList = new ArrayList<>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
}
} catch (IOException e) {
LOG.error("Caught exception while loading file {}", fileName, e);
LOG.error("Caught exception while closing file {}", fileName, e);
}
validatorFileCache.put(fileName, retList);
@@ -138,7 +138,7 @@ public class VisitorFieldValidator extends FieldValidatorSupport {
stack.push(object);
String visitorContext = (context == null) ? ActionContext.getContext().getName() : context;
String visitorContext = (context == null) ? ActionContext.getContext().getActionName() : context;
if (value instanceof Collection) {
Collection coll = (Collection) value;
@@ -139,9 +139,17 @@ public final class StrutsConstants {
/** A global flag to enable/disable html body escaping in tags, can be overwritten per tag */
public static final String STRUTS_UI_ESCAPE_HTML_BODY = "struts.ui.escapeHtmlBody";
/** The maximize size of a multipart request (file upload) */
/** The maximum size of a multipart request (file upload) */
public static final String STRUTS_MULTIPART_MAXSIZE = "struts.multipart.maxSize";
/** The maximum number of files allowed in a multipart request */
public static final String STRUTS_MULTIPART_MAXFILES = "struts.multipart.maxFiles";
/** The maximum length of a string parameter in a multipart request. */
public static final String STRUTS_MULTIPART_MAX_STRING_LENGTH = "struts.multipart.maxStringLength";
/** The maximum size per file in a multipart request */
public static final String STRUTS_MULTIPART_MAXFILESIZE = "struts.multipart.maxFileSize";
/** The directory to use for storing uploaded files */
public static final String STRUTS_MULTIPART_SAVEDIR = "struts.multipart.saveDir";
@@ -177,9 +185,6 @@ public final class StrutsConstants {
@Deprecated
public static final String STRUTS_OBJECTFACTORY_SPRING_ENABLE_AOP_SUPPORT = "struts.objectFactory.spring.enableAopSupport";
/** Whether or not XSLT templates should not be cached */
public static final String STRUTS_XSLT_NOCACHE = "struts.xslt.nocache";
/** Location of additional configuration properties files to load */
public static final String STRUTS_CUSTOM_PROPERTIES = "struts.custom.properties";
@@ -401,27 +406,26 @@ public final class StrutsConstants {
/** Enables action: prefix */
public static final String STRUTS_MAPPER_ACTION_PREFIX_ENABLED = "struts.mapper.action.prefix.enabled";
/**
* Enables access to actions in other namespaces than current with action: prefix
* @deprecated it will be removed soon, please refactor your application
*/
@Deprecated
public static final String STRUTS_MAPPER_ACTION_PREFIX_CROSSNAMESPACES = "struts.mapper.action.prefix.crossNamespaces";
public static final String DEFAULT_TEMPLATE_TYPE_CONFIG_KEY = "struts.ui.templateSuffix";
/** Allows override default DispatcherErrorHandler */
public static final String STRUTS_DISPATCHER_ERROR_HANDLER = "struts.dispatcher.errorHandler";
/** Comma delimited set of excluded classes and package names which cannot be accessed via expressions */
/** Comma delimited set of excluded classes which cannot be accessed via OGNL expressions. Matching is done on both target and member classes of OGNL expression. Note that superclasses of listed classes are also used for matching. */
public static final String STRUTS_EXCLUDED_CLASSES = "struts.excludedClasses";
/** Comma delimited set of RegEx to match against package names of target and member classes of OGNL expressions. If matched, they cannot be accessed. */
public static final String STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS = "struts.excludedPackageNamePatterns";
/** Comma delimited set of package names, of which all its classes, and all classes in its subpackages, cannot be accessed via OGNL expressions. Matching is done on both target and member classes of OGNL expression. */
public static final String STRUTS_EXCLUDED_PACKAGE_NAMES = "struts.excludedPackageNames";
/** Comma delimited set of exempt classes from matching against excludedPackageNames and excludedPackageNamePatterns. As matching for excluded packages is done on both target and member classes of OGNL expression, an exemption must exist for each match. */
public static final String STRUTS_EXCLUDED_PACKAGE_EXEMPT_CLASSES = "struts.excludedPackageExemptClasses";
/** Comma delimited set of excluded classes and package names which cannot be accessed via expressions in devMode */
public static final String STRUTS_DEV_MODE_EXCLUDED_CLASSES = "struts.devMode.excludedClasses";
public static final String STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS = "struts.devMode.excludedPackageNamePatterns";
public static final String STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES = "struts.devMode.excludedPackageNames";
public static final String STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES = "struts.devMode.excludedPackageExemptClasses";
/** Dedicated services to check if passed string is excluded/accepted */
public static final String STRUTS_EXCLUDED_PATTERNS_CHECKER = "struts.excludedPatterns.checker";
@@ -465,4 +469,7 @@ public final class StrutsConstants {
/** A global flag to set property {@link org.apache.struts2.components.Checkbox#setSubmitUnchecked(String)} */
public static final String STRUTS_UI_CHECKBOX_SUBMIT_UNCHECKED = "struts.ui.checkbox.submitUnchecked";
/** See {@link org.apache.struts2.interceptor.exec.ExecutorProvider} */
public static final String STRUTS_EXECUTOR_PROVIDER = "struts.executor.provider";
}
@@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.action;
import org.apache.struts2.interceptor.csp.CspSettings;
/**
* Implement this interface by an action to provide a custom {@link CspSettings},
* see {@link org.apache.struts2.interceptor.csp.CspInterceptor} for more details
*
* @since Struts 6.2.0
*/
public interface CspSettingsAware {
CspSettings getCspSettings();
}
@@ -16,7 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts2.interceptor;
package org.apache.struts2.action;
/**
* This marker interface should be implemented by actions that do not want any parameters set on
@@ -31,20 +31,18 @@ import org.apache.struts2.ServletActionContext;
import org.apache.struts2.StrutsException;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.dispatcher.HttpParameters;
import org.apache.struts2.dispatcher.RequestMap;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import org.apache.struts2.views.jsp.TagUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
/**
@@ -53,7 +51,7 @@ import java.util.Map;
* namespace. The body content of the tag is used to render the results from the Action. Any result processor defined
* for this action in struts.xml will be ignored, <i>unless</i> the executeResult parameter is specified.</p>
* <!-- END SNIPPET: javadoc -->
*
* <p>
* <!-- START SNIPPET: params -->
* <ul>
* <li>id (String) - the id (if specified) to put the action under stack's context.
@@ -111,9 +109,8 @@ import java.util.Map;
* &lt;s:property value=&quot;#attr.stringByAction&quot; /&gt;
* <!-- END SNIPPET: example -->
* </pre>
*
*/
@StrutsTag(name="action", tldTagClass="org.apache.struts2.views.jsp.ActionTag", description="Execute an action from within a view")
@StrutsTag(name = "action", tldTagClass = "org.apache.struts2.views.jsp.ActionTag", description = "Execute an action from within a view")
public class ActionComponent extends ContextBean {
private static final Logger LOG = LogManager.getLogger(ActionComponent.class);
@@ -143,7 +140,7 @@ public class ActionComponent extends ContextBean {
public void setActionProxyFactory(ActionProxyFactory actionProxyFactory) {
this.actionProxyFactory = actionProxyFactory;
}
@Inject
public void setValueStackFactory(ValueStackFactory valueStackFactory) {
this.valueStackFactory = valueStackFactory;
@@ -163,7 +160,7 @@ public class ActionComponent extends ContextBean {
try {
writer.flush();
} catch (IOException e) {
LOG.warn("error while trying to flush writer ", e);
LOG.warn("error while trying to flush writer ", e);
}
}
executeAction();
@@ -188,11 +185,11 @@ public class ActionComponent extends ContextBean {
Dispatcher du = Dispatcher.getInstance();
Map<String, Object> extraContext = du.createContextMap(
new RequestMap(req),
newParams,
session,
application,
req,
res);
newParams,
session,
application,
req,
res);
ValueStack newStack = valueStackFactory.createValueStack(stack);
@@ -206,7 +203,7 @@ public class ActionComponent extends ContextBean {
/**
* Creates parameters map using parameters from the value stack and component parameters. Any non-String array
* values will be converted into a single-value String array.
*
*
* @return A map of String[] parameters
*/
protected HttpParameters createParametersForContext() {
@@ -233,8 +230,6 @@ public class ActionComponent extends ContextBean {
* attempt to derive a namespace using buildNamespace(). The ActionProxy
* and the namespace will be saved into the instance variables proxy and
* namespace respectively.
*
* @see org.apache.struts2.views.jsp.TagUtils#buildNamespace
*/
protected void executeAction() {
String actualName = findString(name, "name", "Action name is required. Example: updatePerson");
@@ -254,7 +249,7 @@ public class ActionComponent extends ContextBean {
String namespace;
if (this.namespace == null) {
namespace = TagUtils.buildNamespace(actionMapper, getStack(), req);
namespace = getNamespace(getStack());
} else {
namespace = findString(this.namespace);
}
@@ -291,32 +286,32 @@ public class ActionComponent extends ContextBean {
}
}
@StrutsTagAttribute(required=true,description="Name of the action to be executed (without the extension suffix eg. .action)")
@StrutsTagAttribute(required = true, description = "Name of the action to be executed (without the extension suffix eg. .action)")
public void setName(String name) {
this.name = name;
}
@StrutsTagAttribute(description="Namespace for action to call", defaultValue="namespace from where tag is used")
@StrutsTagAttribute(description = "Namespace for action to call", defaultValue = "namespace from where tag is used")
public void setNamespace(String namespace) {
this.namespace = namespace;
}
@StrutsTagAttribute(description="Whether the result of this action (probably a view) should be executed/rendered", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether the result of this action (probably a view) should be executed/rendered", type = "Boolean", defaultValue = "false")
public void setExecuteResult(boolean executeResult) {
this.executeResult = executeResult;
}
@StrutsTagAttribute(description="Whether the request parameters are to be included when the action is invoked", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether the request parameters are to be included when the action is invoked", type = "Boolean", defaultValue = "false")
public void setIgnoreContextParams(boolean ignoreContextParams) {
this.ignoreContextParams = ignoreContextParams;
}
@StrutsTagAttribute(description="Whether the writer should be flush upon end of action component tag, default to true", type="Boolean", defaultValue="true")
@StrutsTagAttribute(description = "Whether the writer should be flush upon end of action component tag, default to true", type = "Boolean", defaultValue = "true")
public void setFlush(boolean flush) {
this.flush = flush;
}
@StrutsTagAttribute(description="Whether an exception should be rethrown, if the target action throws an exception", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether an exception should be rethrown, if the target action throws an exception", type = "Boolean", defaultValue = "false")
public void setRethrowException(boolean rethrowException) {
this.rethrowException = rethrowException;
}
@@ -18,6 +18,8 @@
*/
package org.apache.struts2.components;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.security.NotExcludedAcceptedPatternsChecker;
import com.opensymphony.xwork2.util.TextParseUtil;
@@ -34,7 +36,6 @@ import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.util.ComponentUtils;
import org.apache.struts2.util.FastByteArrayOutputStream;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import org.apache.struts2.views.jsp.TagUtils;
import org.apache.struts2.views.util.UrlHelper;
import javax.servlet.http.HttpServletRequest;
@@ -372,8 +373,8 @@ public class Component {
* evaluating to String.class, else the whole <code>expression</code> is evaluated
* against the stack.
*
* @param expression OGNL expression.
* @param toType the type expected to find.
* @param expression OGNL expression.
* @param toType the type expected to find.
* @return the Object found, or <tt>null</tt> if not found.
*/
protected Object findValue(String expression, Class<?> toType) {
@@ -429,7 +430,7 @@ public class Component {
String result;
if (namespace == null) {
result = TagUtils.buildNamespace(actionMapper, stack, req);
result = getNamespace(stack);
} else {
result = findString(namespace);
}
@@ -441,6 +442,12 @@ public class Component {
return result;
}
protected String getNamespace(ValueStack stack) {
ActionContext context = ActionContext.of(stack.getContext());
ActionInvocation invocation = context.getActionInvocation();
return invocation.getProxy().getNamespace();
}
/**
* Pushes this component's parameter Map as well as the component itself on to the stack
* and then copies the supplied parameters over. Because the component's parameter Map is
@@ -581,12 +588,12 @@ public class Component {
* <em>Note:</em> All Tag classes that extend {@link org.apache.struts2.views.jsp.StrutsBodyTagSupport} must implement a setter for
* this attribute (same name), and it must be defined at the Tag class level.
* Defining a setter in the superclass alone is insufficient (results in "Cannot find a setter method for the attribute").
*
* <p>
* See {@link org.apache.struts2.views.jsp.StrutsBodyTagSupport#clearTagStateForTagPoolingServers() for additional details.
*
* @param performClearTagStateForTagPoolingServers true if tag state should be cleared, false otherwise.
*/
@StrutsTagAttribute(description="Whether to clear all tag state during doEndTag() processing (if applicable)", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether to clear all tag state during doEndTag() processing (if applicable)", type = "Boolean", defaultValue = "false")
public void setPerformClearTagStateForTagPoolingServers(boolean performClearTagStateForTagPoolingServers) {
this.performClearTagStateForTagPoolingServers = performClearTagStateForTagPoolingServers;
}
@@ -609,7 +616,7 @@ public class Component {
}
LOG.warn("Expression [{}] isn't allowed by pattern [{}]! See Accepted / Excluded patterns at\n" +
"https://struts.apache.org/security/", expression, isAllowed.getAllowedPattern());
"https://struts.apache.org/security/", expression, isAllowed.getAllowedPattern());
return false;
}
@@ -32,6 +32,7 @@ import java.io.Writer;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
@@ -294,7 +295,9 @@ public class Date extends ContextBean {
// find the name on the valueStack
Object dateObject = findValue(name);
if (dateObject instanceof java.sql.Date) {
date = ((java.sql.Date) dateObject).toLocalDate().atStartOfDay(tz);
date = ((java.sql.Date) dateObject).toLocalDate().atTime(LocalTime.now(tz)).atZone(tz);
} else if (dateObject instanceof java.sql.Time) {
date = ((java.sql.Time) dateObject).toLocalTime().atDate(ZonedDateTime.now(tz).toLocalDate()).atZone(tz);
} else if (dateObject instanceof java.util.Date) {
date = ((java.util.Date) dateObject).toInstant().atZone(tz);
} else if (dateObject instanceof Calendar) {
@@ -305,6 +308,8 @@ public class Date extends ContextBean {
date = ((LocalDateTime) dateObject).atZone(tz);
} else if (dateObject instanceof LocalDate) {
date = ((LocalDate) dateObject).atStartOfDay(tz);
} else if (dateObject instanceof LocalTime) {
date = ((LocalTime) dateObject).atDate(ZonedDateTime.now(tz).toLocalDate()).atZone(tz);
} else if (dateObject instanceof Instant) {
date = ((Instant) dateObject).atZone(tz);
} else {
@@ -26,13 +26,17 @@ import com.opensymphony.xwork2.config.entities.InterceptorMapping;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptorUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.validator.*;
import com.opensymphony.xwork2.validator.ActionValidatorManager;
import com.opensymphony.xwork2.validator.FieldValidator;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.ValidationInterceptor;
import com.opensymphony.xwork2.validator.Validator;
import com.opensymphony.xwork2.validator.ValidatorContext;
import com.opensymphony.xwork2.validator.validators.VisitorFieldValidator;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.views.annotations.StrutsTag;
import org.apache.struts2.views.annotations.StrutsTagAttribute;
import org.apache.struts2.views.jsp.TagUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -72,7 +76,7 @@ import java.util.Set;
* from it and using UrlHelper to generate the final url.
* </li>
* </ol>
*
* <p>
* <!-- END SNIPPET: javadoc -->
*
* <p><b>Examples</b></p>
@@ -84,13 +88,12 @@ import java.util.Set;
*
* <!-- END SNIPPET: example -->
* </pre>
*
*/
@StrutsTag(
name="form",
tldTagClass="org.apache.struts2.views.jsp.ui.FormTag",
description="Renders an input form",
allowDynamicAttributes=true)
name = "form",
tldTagClass = "org.apache.struts2.views.jsp.ui.FormTag",
description = "Renders an input form",
allowDynamicAttributes = true)
public class Form extends ClosingUIBean {
public static final String OPEN_TEMPLATE = "form";
public static final String TEMPLATE = "form-close";
@@ -147,7 +150,7 @@ public class Form extends ClosingUIBean {
@Inject
public void setUrlRenderer(UrlRenderer urlRenderer) {
this.urlRenderer = urlRenderer;
this.urlRenderer = urlRenderer;
}
@Inject
@@ -157,9 +160,9 @@ public class Form extends ClosingUIBean {
/*
* Revised for Portlet actionURL as form action, and add wwAction as hidden
* field. Refer to template.simple/form.vm
*/
* Revised for Portlet actionURL as form action, and add wwAction as hidden
* field. Refer to template.simple/form.vm
*/
@Override
protected void evaluateExtraParams() {
super.evaluateExtraParams();
@@ -170,9 +173,9 @@ public class Form extends ClosingUIBean {
if (name == null) {
//make the name the same as the id
String id = (String) getParameters().get("id");
if (StringUtils.isNotEmpty(id)) {
if (StringUtils.isNotEmpty(id)) {
addParameter("name", id);
}
}
}
if (onsubmit != null) {
@@ -231,8 +234,9 @@ public class Form extends ClosingUIBean {
/**
* Evaluate client side JavaScript Enablement.
* @param actionName the actioName to check for
* @param namespace the namespace to check for
*
* @param actionName the actioName to check for
* @param namespace the namespace to check for
* @param actionMethod the method to ckeck for
*/
protected void evaluateClientSideJsEnablement(String actionName, String namespace, String actionMethod) {
@@ -275,7 +279,7 @@ public class Form extends ClosingUIBean {
ActionMapping mapping = actionMapper.getMappingFromActionName(formActionValue);
if (mapping == null) {
mapping = actionMapper.getMappingFromActionName((String) getParameters().get("actionName"));
mapping = actionMapper.getMappingFromActionName((String) getParameters().get("actionName"));
}
if (mapping == null) {
@@ -288,7 +292,7 @@ public class Form extends ClosingUIBean {
if (isValidateAnnotatedMethodOnly(actionName)) {
methodName = mapping.getMethod();
}
List<Validator> actionValidators = actionValidatorManager.getValidators(actionClass, actionName, methodName);
List<Validator> validators = new ArrayList<>();
@@ -299,7 +303,7 @@ public class Form extends ClosingUIBean {
private boolean isValidateAnnotatedMethodOnly(String actionName) {
RuntimeConfiguration runtimeConfiguration = configuration.getRuntimeConfiguration();
String actionNamespace = TagUtils.buildNamespace(actionMapper, stack, request);
String actionNamespace = getNamespace(stack);
ActionConfig actionConfig = runtimeConfiguration.getActionConfig(actionNamespace, actionName);
if (actionConfig != null) {
@@ -355,64 +359,84 @@ public class Form extends ClosingUIBean {
public static class FieldVisitorValidatorWrapper implements FieldValidator {
private FieldValidator fieldValidator;
private String namePrefix;
public FieldVisitorValidatorWrapper(FieldValidator fv, String namePrefix) {
this.fieldValidator = fv;
this.namePrefix = namePrefix;
}
public String getValidatorType() {
return "field-visitor";
}
public String getFieldName() {
return namePrefix + fieldValidator.getFieldName();
}
public FieldValidator getFieldValidator() {
return fieldValidator;
}
public void setFieldValidator(FieldValidator fieldValidator) {
this.fieldValidator = fieldValidator;
}
public String getDefaultMessage() {
return fieldValidator.getDefaultMessage();
}
public String getMessage(Object object) {
return fieldValidator.getMessage(object);
}
public String getMessageKey() {
return fieldValidator.getMessageKey();
}
public String[] getMessageParameters() {
return fieldValidator.getMessageParameters();
}
public ValidatorContext getValidatorContext() {
return fieldValidator.getValidatorContext();
}
public void setDefaultMessage(String message) {
fieldValidator.setDefaultMessage(message);
}
public void setFieldName(String fieldName) {
fieldValidator.setFieldName(fieldName);
}
public void setMessageKey(String key) {
fieldValidator.setMessageKey(key);
}
public void setMessageParameters(String[] messageParameters) {
fieldValidator.setMessageParameters(messageParameters);
}
public void setValidatorContext(ValidatorContext validatorContext) {
fieldValidator.setValidatorContext(validatorContext);
}
public void setValidatorType(String type) {
fieldValidator.setValidatorType(type);
}
public void setValueStack(ValueStack stack) {
fieldValidator.setValueStack(stack);
}
public void validate(Object object) throws ValidationException {
fieldValidator.validate(object);
}
public String getNamePrefix() {
return namePrefix;
}
public void setNamePrefix(String namePrefix) {
this.namePrefix = namePrefix;
}
@@ -421,7 +445,7 @@ public class Form extends ClosingUIBean {
/**
* Return type of visited object.
*
* @param actionClass action class
* @param actionClass action class
* @param visitorFieldName field name
* @return type of visited object
*/
@@ -451,68 +475,68 @@ public class Form extends ClosingUIBean {
return sequence++;
}
@StrutsTagAttribute(description="HTML onsubmit attribute")
@StrutsTagAttribute(description = "HTML onsubmit attribute")
public void setOnsubmit(String onsubmit) {
this.onsubmit = onsubmit;
}
@StrutsTagAttribute(description="HTML onreset attribute")
@StrutsTagAttribute(description = "HTML onreset attribute")
public void setOnreset(String onreset) {
this.onreset = onreset;
}
@StrutsTagAttribute(description="Set action name to submit to, without .action suffix", defaultValue="current action")
@StrutsTagAttribute(description = "Set action name to submit to, without .action suffix", defaultValue = "current action")
public void setAction(String action) {
this.action = action;
}
@StrutsTagAttribute(description="HTML form target attribute")
@StrutsTagAttribute(description = "HTML form target attribute")
public void setTarget(String target) {
this.target = target;
}
@StrutsTagAttribute(description="HTML form enctype attribute")
@StrutsTagAttribute(description = "HTML form enctype attribute")
public void setEnctype(String enctype) {
this.enctype = enctype;
}
@StrutsTagAttribute(description="HTML form method attribute")
@StrutsTagAttribute(description = "HTML form method attribute")
public void setMethod(String method) {
this.method = method;
}
@StrutsTagAttribute(description="Namespace for action to submit to", defaultValue="current namespace")
@StrutsTagAttribute(description = "Namespace for action to submit to", defaultValue = "current namespace")
public void setNamespace(String namespace) {
this.namespace = namespace;
}
@StrutsTagAttribute(description="Whether client side/remote validation should be performed. Only" +
" useful with theme xhtml/ajax", type="Boolean", defaultValue="false")
@StrutsTagAttribute(description = "Whether client side/remote validation should be performed. Only" +
" useful with theme xhtml/ajax", type = "Boolean", defaultValue = "false")
public void setValidate(String validate) {
this.validate = validate;
}
@StrutsTagAttribute(description="The portlet mode to display after the form submit")
@StrutsTagAttribute(description = "The portlet mode to display after the form submit")
public void setPortletMode(String portletMode) {
this.portletMode = portletMode;
}
@StrutsTagAttribute(description="The window state to display after the form submit")
@StrutsTagAttribute(description = "The window state to display after the form submit")
public void setWindowState(String windowState) {
this.windowState = windowState;
}
@StrutsTagAttribute(description="The accepted charsets for this form. The values may be comma or blank delimited.")
@StrutsTagAttribute(description = "The accepted charsets for this form. The values may be comma or blank delimited.")
public void setAcceptcharset(String acceptcharset) {
this.acceptcharset = acceptcharset;
}
@StrutsTagAttribute(description="Id of element that will receive the focus when page loads.")
@StrutsTagAttribute(description = "Id of element that will receive the focus when page loads.")
public void setFocusElement(String focusElement) {
this.focusElement = focusElement;
}
@StrutsTagAttribute(description="Whether actual context should be included in URL", type="Boolean", defaultValue="true")
@StrutsTagAttribute(description = "Whether actual context should be included in URL", type = "Boolean", defaultValue = "true")
public void setIncludeContext(boolean includeContext) {
this.includeContext = includeContext;
}
@@ -96,33 +96,33 @@ public abstract class FormButton extends ClosingUIBean {
* </ol>
*/
protected void populateComponentHtmlId(Form form) {
String _tmp_id = "";
String tmpId = "";
if (id != null) {
// this check is needed for backwards compatibility with 2.1.x
_tmp_id = findString(id);
tmpId = findString(id);
} else {
if (form != null && form.getParameters().get("id") != null) {
_tmp_id = _tmp_id + form.getParameters().get("id").toString() + "_";
tmpId = tmpId + form.getParameters().get("id").toString() + "_";
}
if (name != null) {
_tmp_id = _tmp_id + escape(name);
tmpId = tmpId + escape(findString(name));
} else if (action != null || method != null) {
if (action != null) {
_tmp_id = _tmp_id + escape(action);
tmpId = tmpId + escape(findString(action));
}
if (method != null) {
_tmp_id = _tmp_id + "_" + escape(method);
tmpId = tmpId + "_" + escape(findString(method));
}
} else {
// if form is null, this component is used, without a form, i guess
// there's not much we could do then.
if (form != null) {
_tmp_id = _tmp_id + form.getSequence();
tmpId = tmpId + form.getSequence();
}
}
}
addParameter("id", _tmp_id);
addParameter("escapedId", escape(_tmp_id));
addParameter("id", tmpId);
addParameter("escapedId", escape(tmpId));
}
/**
@@ -159,10 +159,10 @@ public class ServletUrlRenderer implements UrlRenderer {
}
}
Map<String, Object> actionParams = null;
QueryStringParser.Result queryStringResult = queryStringParser.empty();
if (action != null && action.indexOf('?') > 0) {
String queryString = action.substring(action.indexOf('?') + 1);
actionParams = queryStringParser.parse(queryString, false);
queryStringResult = queryStringParser.parse(queryString);
action = action.substring(0, action.indexOf('?'));
}
@@ -176,7 +176,7 @@ public class ServletUrlRenderer implements UrlRenderer {
ActionMapping mapping = new ActionMapping(actionName, namespace, actionMethod, formComponent.parameters);
String result = urlHelper.buildUrl(formComponent.actionMapper.getUriFromActionMapping(mapping),
formComponent.request, formComponent.response, actionParams, scheme, formComponent.includeContext, true, false, false);
formComponent.request, formComponent.response, queryStringResult.getQueryParams(), scheme, formComponent.includeContext, true, false, false);
formComponent.addParameter("action", result);
// let's try to get the actual action class and name
@@ -213,7 +213,7 @@ public class ServletUrlRenderer implements UrlRenderer {
LOG.warn("No configuration found for the specified action: '{}' in namespace: '{}'. Form action defaulting to 'action' attribute's literal value.", actionName, namespace);
}
String result = urlHelper.buildUrl(action, formComponent.request, formComponent.response, actionParams, scheme, formComponent.includeContext, true);
String result = urlHelper.buildUrl(action, formComponent.request, formComponent.response, queryStringResult.getQueryParams(), scheme, formComponent.includeContext, true);
formComponent.addParameter("action", result);
// namespace: cut out anything between the start and the last /
@@ -291,7 +291,11 @@ public class ServletUrlRenderer implements UrlRenderer {
private void includeGetParameters(UrlProvider urlComponent) {
String query = extractQueryString(urlComponent);
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), queryStringParser.parse(query, false));
QueryStringParser.Result result = queryStringParser.parse(query);
mergeRequestParameters(urlComponent.getValue(), urlComponent.getParameters(), result.getQueryParams());
if (!result.getQueryFragment().isEmpty()) {
urlComponent.setAnchor(result.getQueryFragment());
}
}
private String extractQueryString(UrlProvider urlComponent) {
@@ -339,7 +343,7 @@ public class ServletUrlRenderer implements UrlRenderer {
if (StringUtils.contains(value, "?")) {
String queryString = value.substring(value.indexOf('?') + 1);
mergedParams = queryStringParser.parse(queryString, false);
mergedParams = new LinkedHashMap<>(queryStringParser.parse(queryString).getQueryParams());
for (Map.Entry<String, ?> entry : contextParameters.entrySet()) {
if (!mergedParams.containsKey(entry.getKey())) {
mergedParams.put(entry.getKey(), entry.getValue());
@@ -34,7 +34,7 @@ public class SimpleDateFormatAdapter implements DateFormatter {
DateFormat df;
Locale locale = ActionContext.getContext().getLocale();
if (format == null) {
df = SimpleDateFormat.getDateInstance(DateFormat.MEDIUM, locale);
df = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, locale);
} else {
df = new SimpleDateFormat(format, locale);
}
@@ -66,6 +66,7 @@ import org.apache.struts2.dispatcher.DispatcherErrorHandler;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
import org.apache.struts2.dispatcher.multipart.MultiPartRequest;
import org.apache.struts2.interceptor.exec.ExecutorProvider;
import org.apache.struts2.url.QueryStringBuilder;
import org.apache.struts2.url.QueryStringParser;
import org.apache.struts2.url.UrlDecoder;
@@ -438,6 +439,8 @@ public class StrutsBeanSelectionProvider extends AbstractBeanSelectionProvider {
alias(UrlEncoder.class, StrutsConstants.STRUTS_URL_ENCODER, builder, props, Scope.SINGLETON);
alias(UrlDecoder.class, StrutsConstants.STRUTS_URL_DECODER, builder, props, Scope.SINGLETON);
alias(ExecutorProvider.class, StrutsConstants.STRUTS_EXECUTOR_PROVIDER, builder, props, Scope.SINGLETON);
switchDevMode(props);
}
@@ -33,7 +33,13 @@ import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static java.util.Collections.unmodifiableMap;
/**
* Override Xwork class so we can use an arbitrary config file
@@ -41,10 +47,25 @@ import java.util.*;
public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
private static final Logger LOG = LogManager.getLogger(StrutsXmlConfigurationProvider.class);
private static final Map<String, String> STRUTS_DTD_MAPPINGS = unmodifiableMap(new HashMap<String, String>() {{
put("-//Apache Software Foundation//DTD Struts Configuration 2.0//EN", "struts-2.0.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 2.1//EN", "struts-2.1.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN", "struts-2.1.7.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd");
put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd");
}});
private File baseDir = null;
private String filename;
private String reloadKey;
private ServletContext servletContext;
private final String filename;
private final String reloadKey;
private final ServletContext servletContext;
/**
* Constructs the Struts configuration provider using the default struts.xml and no ServletContext
*/
public StrutsXmlConfigurationProvider() {
this("struts.xml", null);
}
/**
* Constructs the configuration provider
@@ -53,7 +74,7 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
*/
@Deprecated
public StrutsXmlConfigurationProvider(boolean errorIfMissing) {
this("struts.xml", errorIfMissing, null);
this("struts.xml", null);
}
/**
@@ -62,35 +83,35 @@ public class StrutsXmlConfigurationProvider extends XmlConfigurationProvider {
* @param filename file with Struts configuration
*/
public StrutsXmlConfigurationProvider(String filename) {
this(filename, false, null);
this(filename, null);
}
/**
* Constructs the configuration provider
* Constructs the Struts configuration provider
*
* @param filename The filename to look for
* @param errorIfMissing If we should throw an exception if the file can't be found, @deprecated and should be dropped
* @param ctx Our ServletContext
*/
public StrutsXmlConfigurationProvider(String filename, @Deprecated boolean errorIfMissing, ServletContext ctx) {
super(filename, errorIfMissing);
public StrutsXmlConfigurationProvider(String filename, ServletContext ctx) {
super(filename);
this.servletContext = ctx;
this.filename = filename;
reloadKey = "configurationReload-" + filename;
Map<String,String> dtdMappings = new HashMap<String,String>(getDtdMappings());
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.0//EN", "struts-2.0.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1//EN", "struts-2.1.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.1.7//EN", "struts-2.1.7.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.3//EN", "struts-2.3.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 2.5//EN", "struts-2.5.dtd");
dtdMappings.put("-//Apache Software Foundation//DTD Struts Configuration 6.0//EN", "struts-6.0.dtd");
setDtdMappings(dtdMappings);
this.reloadKey = "configurationReload-" + filename;
setDtdMappings(STRUTS_DTD_MAPPINGS);
File file = new File(filename);
if (file.getParent() != null) {
this.baseDir = file.getParentFile();
}
}
/**
* @deprecated since 6.2.0, use {@link #StrutsXmlConfigurationProvider(String, ServletContext)}
*/
@Deprecated
public StrutsXmlConfigurationProvider(String filename, @Deprecated boolean errorIfMissing, ServletContext ctx) {
this(filename, ctx);
}
/* (non-Javadoc)
* @see com.opensymphony.xwork2.config.providers.XmlConfigurationProvider#register(com.opensymphony.xwork2.inject.ContainerBuilder, java.util.Properties)
*/
@@ -18,6 +18,10 @@
*/
package org.apache.struts2.config.entities;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.StaticContentLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -27,10 +31,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.dispatcher.StaticContentLoader;
public class ConstantConfig {
private Boolean devMode;
private Boolean i18nReload;
@@ -64,6 +64,9 @@ public class ConstantConfig {
private String uiTheme;
private String uiThemeExpansionToken;
private Long multipartMaxSize;
private Long multipartMaxFiles;
private Long multipartMaxFileSize;
private Long multipartMaxStringLength;
private String multipartSaveDir;
private Integer multipartBufferSize;
private BeanConfig multipartParser;
@@ -126,9 +129,11 @@ public class ConstantConfig {
private Set<Class<?>> excludedClasses;
private List<Pattern> excludedPackageNamePatterns;
private Set<String> excludedPackageNames;
private Set<Class<?>> excludedPackageExemptClasses;
private Set<Class<?>> devModeExcludedClasses;
private List<Pattern> devModeExcludedPackageNamePatterns;
private Set<String> devModeExcludedPackageNames;
private Set<Class<?>> devModeExcludedPackageExemptClasses;
private BeanConfig excludedPatternsChecker;
private BeanConfig acceptedPatternsChecker;
private BeanConfig notExcludedAcceptedPatternsChecker;
@@ -195,6 +200,9 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_UI_THEME, uiTheme);
map.put(StrutsConstants.STRUTS_UI_THEME_EXPANSION_TOKEN, uiThemeExpansionToken);
map.put(StrutsConstants.STRUTS_MULTIPART_MAXSIZE, Objects.toString(multipartMaxSize, null));
map.put(StrutsConstants.STRUTS_MULTIPART_MAXFILES, Objects.toString(multipartMaxFiles, null));
map.put(StrutsConstants.STRUTS_MULTIPART_MAXFILESIZE, Objects.toString(multipartMaxFileSize, null));
map.put(StrutsConstants.STRUTS_MULTIPART_MAX_STRING_LENGTH, Objects.toString(multipartMaxStringLength, null));
map.put(StrutsConstants.STRUTS_MULTIPART_SAVEDIR, multipartSaveDir);
map.put(StrutsConstants.STRUTS_MULTIPART_BUFFERSIZE, Objects.toString(multipartBufferSize, null));
map.put(StrutsConstants.STRUTS_MULTIPART_PARSER, beanConfToString(multipartParser));
@@ -204,7 +212,6 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_AUTOWIRE_ALWAYS_RESPECT, Objects.toString(objectFactorySpringAutoWireAlwaysRespect, null));
map.put(StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_USE_CLASS_CACHE, Objects.toString(objectFactorySpringUseClassCache, null));
map.put(StrutsConstants.STRUTS_OBJECTFACTORY_SPRING_ENABLE_AOP_SUPPORT, Objects.toString(objectFactorySpringEnableAopSupport, null));
map.put(StrutsConstants.STRUTS_XSLT_NOCACHE, Objects.toString(xsltNocache, null));
map.put(StrutsConstants.STRUTS_CUSTOM_PROPERTIES, StringUtils.join(customProperties, ','));
map.put(StrutsConstants.STRUTS_CUSTOM_I18N_RESOURCES, StringUtils.join(customI18nResources, ','));
map.put(StrutsConstants.STRUTS_MAPPER_CLASS, beanConfToString(mapperClass));
@@ -251,15 +258,16 @@ public class ConstantConfig {
map.put(StrutsConstants.STRUTS_ALLOWED_METHOD_NAMES, Objects.toString(allowedMethodNames, null));
map.put(StrutsConstants.STRUTS_DEFAULT_METHOD_NAME, defaultMethodName);
map.put(StrutsConstants.STRUTS_MAPPER_ACTION_PREFIX_ENABLED, Objects.toString(mapperActionPrefixEnabled, null));
map.put(StrutsConstants.STRUTS_MAPPER_ACTION_PREFIX_CROSSNAMESPACES, Objects.toString(mapperActionPrefixCrossNamespaces, null));
map.put(StrutsConstants.DEFAULT_TEMPLATE_TYPE_CONFIG_KEY, uiTemplateSuffix);
map.put(StrutsConstants.STRUTS_DISPATCHER_ERROR_HANDLER, beanConfToString(dispatcherErrorHandler));
map.put(StrutsConstants.STRUTS_EXCLUDED_CLASSES, classesToString(excludedClasses));
map.put(StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAME_PATTERNS, StringUtils.join(excludedPackageNamePatterns, ','));
map.put(StrutsConstants.STRUTS_EXCLUDED_PACKAGE_NAMES, StringUtils.join(excludedPackageNames, ','));
map.put(StrutsConstants.STRUTS_EXCLUDED_PACKAGE_EXEMPT_CLASSES, classesToString(excludedPackageExemptClasses));
map.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_CLASSES, classesToString(devModeExcludedClasses));
map.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAME_PATTERNS, StringUtils.join(devModeExcludedPackageNamePatterns, ','));
map.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_NAMES, StringUtils.join(devModeExcludedPackageNames, ','));
map.put(StrutsConstants.STRUTS_DEV_MODE_EXCLUDED_PACKAGE_EXEMPT_CLASSES, classesToString(devModeExcludedPackageExemptClasses));
map.put(StrutsConstants.STRUTS_EXCLUDED_PATTERNS_CHECKER, beanConfToString(excludedPatternsChecker));
map.put(StrutsConstants.STRUTS_ACCEPTED_PATTERNS_CHECKER, beanConfToString(acceptedPatternsChecker));
map.put(StrutsConstants.STRUTS_NOT_EXCLUDED_ACCEPTED_PATTERNS_CHECKER, beanConfToString(notExcludedAcceptedPatternsChecker));
@@ -580,6 +588,30 @@ public class ConstantConfig {
this.multipartMaxSize = multipartMaxSize;
}
public Long getMultipartMaxFiles() {
return multipartMaxFiles;
}
public void setMultipartMaxFiles(Long multipartMaxFiles) {
this.multipartMaxFiles = multipartMaxFiles;
}
public Long getMultipartMaxFileSize() {
return multipartMaxFileSize;
}
public void setMultipartMaxFileSize(Long multipartMaxFileSize) {
this.multipartMaxFileSize = multipartMaxFileSize;
}
public Long getMultipartMaxStringLength() {
return multipartMaxStringLength;
}
public void setMultipartMaxStringLength(Long multipartMaxStringLength) {
this.multipartMaxStringLength = multipartMaxStringLength;
}
public String getMultipartSaveDir() {
return multipartSaveDir;
}
@@ -1176,6 +1208,14 @@ public class ConstantConfig {
this.excludedPackageNames = excludedPackageNames;
}
public Set<Class<?>> getExcludedPackageExemptClasses() {
return excludedPackageExemptClasses;
}
public void setExcludedPackageExemptClasses(Set<Class<?>> excludedPackageExemptClasses) {
this.excludedPackageExemptClasses = excludedPackageExemptClasses;
}
public Set<Class<?>> getDevModeExcludedClasses() {
return devModeExcludedClasses;
}
@@ -1200,6 +1240,14 @@ public class ConstantConfig {
this.devModeExcludedPackageNames = devModeExcludedPackageNames;
}
public Set<Class<?>> getDevModeExcludedPackageExemptClasses() {
return devModeExcludedPackageExemptClasses;
}
public void setDevModeExcludedPackageExemptClasses(Set<Class<?>> devModeExcludedPackageExemptClasses) {
this.devModeExcludedPackageExemptClasses = devModeExcludedPackageExemptClasses;
}
public BeanConfig getExcludedPatternsChecker() {
return excludedPatternsChecker;
}
@@ -18,9 +18,13 @@
*/
package org.apache.struts2.dispatcher;
import javax.servlet.ServletContext;
import java.io.Serializable;
import java.util.*;
import java.util.AbstractMap;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletContext;
/**
* A simple implementation of the {@link java.util.Map} interface to handle a collection of attributes and
@@ -28,12 +32,12 @@ import java.util.*;
* enumerates over all servlet context attributes and init parameters and returns a collection of both.
* Note, this will occur lazily - only when the entry set is asked for.
*/
public class ApplicationMap extends AbstractMap implements Serializable {
public class ApplicationMap extends AbstractMap<String, Object> implements Serializable {
private static final long serialVersionUID = 9136809763083228202L;
private ServletContext context;
private Set<Object> entries;
private Set<Entry<String, Object>> entries;
/**
@@ -41,7 +45,7 @@ public class ApplicationMap extends AbstractMap implements Serializable {
*
* @param ctx the servlet context
*/
public ApplicationMap(ServletContext ctx) {
public ApplicationMap(final ServletContext ctx) {
this.context = ctx;
}
@@ -49,13 +53,14 @@ public class ApplicationMap extends AbstractMap implements Serializable {
/**
* Removes all entries from the Map and removes all attributes from the servlet context.
*/
@Override
public void clear() {
entries = null;
Enumeration e = context.getAttributeNames();
Enumeration<String> e = context.getAttributeNames();
while (e.hasMoreElements()) {
context.removeAttribute(e.nextElement().toString());
context.removeAttribute(e.nextElement());
}
}
@@ -64,39 +69,20 @@ public class ApplicationMap extends AbstractMap implements Serializable {
*
* @return a Set of all servlet context attributes as well as context init parameters.
*/
public Set entrySet() {
@Override
public Set<Entry<String, Object>> entrySet() {
if (entries == null) {
entries = new HashSet<>();
// Add servlet context attributes
Enumeration enumeration = context.getAttributeNames();
Enumeration<String> enumeration = context.getAttributeNames();
while (enumeration.hasMoreElements()) {
final String key = enumeration.nextElement().toString();
final String key = enumeration.nextElement();
final Object value = context.getAttribute(key);
entries.add(new Map.Entry() {
public boolean equals(Object obj) {
if (!(obj instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) obj;
return ((key == null) ? (entry.getKey() == null) : key.equals(entry.getKey())) && ((value == null) ? (entry.getValue() == null) : value.equals(entry.getValue()));
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object obj) {
entries.add(new StringObjectEntry(key, value) {
@Override
public Object setValue(final Object obj) {
context.setAttribute(key, obj);
return value;
@@ -108,31 +94,11 @@ public class ApplicationMap extends AbstractMap implements Serializable {
enumeration = context.getInitParameterNames();
while (enumeration.hasMoreElements()) {
final String key = enumeration.nextElement().toString();
final String key = enumeration.nextElement();
final Object value = context.getInitParameter(key);
entries.add(new Map.Entry() {
public boolean equals(Object obj) {
if (!(obj instanceof Map.Entry)) {
return false;
}
Map.Entry entry = (Map.Entry) obj;
return ((key == null) ? (entry.getKey() == null) : key.equals(entry.getKey())) && ((value == null) ? (entry.getValue() == null) : value.equals(entry.getValue()));
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object obj) {
entries.add(new StringObjectEntry(key, value) {
@Override
public Object setValue(final Object obj) {
context.setAttribute(key, obj);
return value;
@@ -151,13 +117,12 @@ public class ApplicationMap extends AbstractMap implements Serializable {
* @param key the entry key.
* @return the servlet context attribute or init parameter or <tt>null</tt> if the entry is not found.
*/
public Object get(Object key) {
public Object get(final String key) {
// Try context attributes first, then init params
// This gives the proper shadowing effects
String keyString = key.toString();
Object value = context.getAttribute(keyString);
Object value = context.getAttribute(key);
return (value == null) ? context.getInitParameter(keyString) : value;
return (value == null) ? context.getInitParameter(key) : value;
}
/**
@@ -167,10 +132,13 @@ public class ApplicationMap extends AbstractMap implements Serializable {
* @param value the value to set.
* @return the attribute that was just set.
*/
public Object put(Object key, Object value) {
@Override
public Object put(final String key, final Object value) {
Object oldValue = get(key);
entries = null;
context.setAttribute(key.toString(), value);
context.setAttribute(key, value);
return oldValue;
}
@@ -180,11 +148,11 @@ public class ApplicationMap extends AbstractMap implements Serializable {
* @param key the attribute to remove.
* @return the entry that was just removed.
*/
public Object remove(Object key) {
public Object remove(final String key) {
entries = null;
Object value = get(key);
context.removeAttribute(key.toString());
context.removeAttribute(key);
return value;
}
@@ -78,7 +78,6 @@ import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -109,15 +108,17 @@ public class Dispatcher {
public static final String MULTIPART_FORM_DATA_REGEX = "^multipart/form-data(?:\\s*;\\s*boundary=[0-9a-zA-Z'()+_,\\-./:=?]{1,70})?(?:\\s*;\\s*charset=[a-zA-Z\\-0-9]{3,14})?";
private static final String CONFIG_SPLIT_REGEX = "\\s*,\\s*";
/**
* Provide a thread local instance.
*/
private static ThreadLocal<Dispatcher> instance = new ThreadLocal<>();
private static final ThreadLocal<Dispatcher> instance = new ThreadLocal<>();
/**
* Store list of DispatcherListeners.
*/
private static List<DispatcherListener> dispatcherListeners = new CopyOnWriteArrayList<>();
private static final List<DispatcherListener> dispatcherListeners = new CopyOnWriteArrayList<>();
/**
* Store state of StrutsConstants.STRUTS_DEVMODE setting.
@@ -353,7 +354,7 @@ public class Dispatcher {
try {
((ObjectFactoryDestroyable) objectFactory).destroy();
} catch (Exception e) {
// catch any exception that may occurred during destroy() and log it
// catch any exception that may occur during destroy() and log it
LOG.error("Exception occurred while destroying ObjectFactory [{}]", objectFactory.toString(), e);
}
}
@@ -427,24 +428,36 @@ public class Dispatcher {
if (configPaths == null) {
configPaths = DEFAULT_CONFIGURATION_PATHS;
}
String[] files = configPaths.split("\\s*[,]\\s*");
loadConfigPaths(configPaths);
}
private void loadConfigPaths(String configPaths) {
String[] files = configPaths.split(CONFIG_SPLIT_REGEX);
for (String file : files) {
if (file.endsWith(".xml")) {
configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, false, servletContext));
configurationManager.addContainerProvider(createStrutsXmlConfigurationProvider(file, servletContext));
} else {
throw new IllegalArgumentException("Invalid configuration file name");
}
}
}
protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, ServletContext ctx) {
return new StrutsXmlConfigurationProvider(filename, ctx);
}
/**
* @deprecated since 6.2.0, use {@link #createStrutsXmlConfigurationProvider(String, ServletContext)}
*/
@Deprecated
protected XmlConfigurationProvider createStrutsXmlConfigurationProvider(String filename, boolean errorIfMissing, ServletContext ctx) {
return new StrutsXmlConfigurationProvider(filename, errorIfMissing, ctx);
return createStrutsXmlConfigurationProvider(filename, ctx);
}
private void init_JavaConfigurations() {
String configClasses = initParams.get("javaConfigClasses");
if (configClasses != null) {
String[] classes = configClasses.split("\\s*[,]\\s*");
String[] classes = configClasses.split(CONFIG_SPLIT_REGEX);
for (String cname : classes) {
try {
Class<?> cls = ClassLoaderUtil.loadClass(cname, this.getClass());
@@ -468,7 +481,7 @@ public class Dispatcher {
private void init_CustomConfigurationProviders() {
String configProvs = initParams.get("configProviders");
if (configProvs != null) {
String[] classes = configProvs.split("\\s*[,]\\s*");
String[] classes = configProvs.split(CONFIG_SPLIT_REGEX);
for (String cname : classes) {
try {
Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
@@ -513,6 +526,13 @@ public class Dispatcher {
configurationManager.addContainerProvider(new StrutsBeanSelectionProvider());
}
/**
* `struts-deferred.xml` can be used to load configuration which is sensitive to loading order such as 'bean-selection' elements
*/
private void init_DeferredXmlConfigurations() {
loadConfigPaths("struts-deferred.xml");
}
private Container init_PreloadConfiguration() {
return getContainer();
}
@@ -546,6 +566,7 @@ public class Dispatcher {
init_CustomConfigurationProviders(); // [5]
init_FilterInitParameters(); // [6]
init_AliasStandardObjects(); // [7]
init_DeferredXmlConfigurations();
Container container = init_PreloadConfiguration();
container.inject(this);
@@ -649,7 +670,7 @@ public class Dispatcher {
}
}
private ActionProxy prepareActionProxy(Map<String, Object> extraContext, String actionNamespace, String actionName, String actionMethod) {
protected ActionProxy prepareActionProxy(Map<String, Object> extraContext, String actionNamespace, String actionName, String actionMethod) {
ActionProxy proxy;
//check if we are probably in an async resuming
ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
@@ -668,12 +689,12 @@ public class Dispatcher {
return proxy;
}
private ActionProxy createActionProxy(String namespace, String name, String method, Map<String, Object> extraContext) {
protected ActionProxy createActionProxy(String namespace, String name, String method, Map<String, Object> extraContext) {
ActionProxyFactory actionProxyFactory = getContainer().getInstance(ActionProxyFactory.class);
return actionProxyFactory.createActionProxy(namespace, name, method, extraContext, true, false);
}
private boolean isSameAction(ActionProxy actionProxy, String namespace, String actionName, String method) {
protected boolean isSameAction(ActionProxy actionProxy, String namespace, String actionName, String method) {
return Objects.equals(namespace, actionProxy.getNamespace())
&& Objects.equals(actionName, actionProxy.getActionName())
&& Objects.equals(method, actionProxy.getMethod());
@@ -749,7 +770,7 @@ public class Dispatcher {
Map<String, Object> applicationMap,
HttpServletRequest request,
HttpServletResponse response) {
Map<String, Object> extraContext = ActionContext.of(new HashMap<>())
Map<String, Object> extraContext = ActionContext.of()
.withParameters(parameters)
.withSession(sessionMap)
.withApplication(applicationMap)
@@ -18,15 +18,12 @@
*/
package org.apache.struts2.dispatcher;
import org.apache.struts2.interceptor.ParameterAware;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
@@ -81,10 +78,6 @@ public class HttpParameters implements Map<String, Parameter>, Cloneable {
return this;
}
public void applyParameters(ParameterAware parameterAware) {
parameterAware.setParameters(toMap());
}
@Override
public int size() {
return parameters.size();
@@ -47,17 +47,43 @@ public class PrepareOperations {
/**
* Maintains per-request override of devMode configuration.
*/
private static ThreadLocal<Boolean> devModeOverride = new InheritableThreadLocal<>();
private static final ThreadLocal<Boolean> devModeOverride = new InheritableThreadLocal<>();
private Dispatcher dispatcher;
private final Dispatcher dispatcher;
private static final String STRUTS_ACTION_MAPPING_KEY = "struts.actionMapping";
public static final String CLEANUP_RECURSION_COUNTER = "__cleanup_recursion_counter";
private static final String NO_ACTION_MAPPING = "noActionMapping";
private static final String PREPARE_COUNTER = "__prepare_recursion_counter";
private static final String WRAP_COUNTER = "__wrap_recursion_counter";
public PrepareOperations(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
/**
* Should be called by {@link org.apache.struts2.dispatcher.filter.StrutsPrepareFilter} to track how many times this
* request has been filtered.
*/
public void trackRecursion(HttpServletRequest request) {
incrementRecursionCounter(request, PREPARE_COUNTER);
}
/**
* Cleans up request. When paired with {@link #trackRecursion}, only cleans up once the first filter instance has
* completed, preventing cleanup by recursive filter calls - i.e. before the request is completely processed.
*/
public void cleanupRequest(final HttpServletRequest request) {
decrementRecursionCounter(request, PREPARE_COUNTER, () -> {
try {
dispatcher.cleanUpRequest(request);
} finally {
ActionContext.clear();
Dispatcher.setInstance(null);
devModeOverride.remove();
}
});
}
/**
* Creates the action context and initializes the thread local
*
@@ -68,12 +94,6 @@ public class PrepareOperations {
*/
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
ActionContext ctx;
int counter = 1;
Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (oldCounter != null) {
counter = oldCounter + 1;
}
ActionContext oldContext = ActionContext.getContext();
if (oldContext != null) {
// detected existing context, so we are probably in a forward
@@ -86,35 +106,9 @@ public class PrepareOperations {
ctx = ActionContext.of(stack.getContext()).bind();
}
}
request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
return ctx;
}
/**
* Cleans up a request of thread locals
*
* @param request servlet request
*/
public void cleanupRequest(HttpServletRequest request) {
Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
if (counterVal != null) {
counterVal -= 1;
request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal);
if (counterVal > 0 ) {
LOG.debug("skipping cleanup counter={}", counterVal);
return;
}
}
// always clean up the thread request, even if an action hasn't been executed
try {
dispatcher.cleanUpRequest(request);
} finally {
ActionContext.clear();
Dispatcher.setInstance(null);
devModeOverride.remove();
}
}
/**
* Assigns the dispatcher to the dispatcher thread local
*/
@@ -134,14 +128,15 @@ public class PrepareOperations {
/**
* Wraps the request with the Struts wrapper that handles multipart requests better
* Also tracks additional calls to this method on the same request.
*
* @param oldRequest servlet request
* @param request servlet request
*
* @return The new request, if there is one
* @throws ServletException on any servlet related error
*/
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
public HttpServletRequest wrapRequest(HttpServletRequest request) throws ServletException {
incrementRecursionCounter(request, WRAP_COUNTER);
try {
// Wrap request first, just in case it is multipart/form-data
// parameters might not be accessible through before encoding (ww-1278)
@@ -153,6 +148,14 @@ public class PrepareOperations {
return request;
}
/**
* Should be called after whenever {@link #wrapRequest} is called. Ensures the request is only cleaned up at the
* instance it was initially wrapped in the case of multiple wrap calls - i.e. filter recursion.
*/
public void cleanupWrappedRequest(final HttpServletRequest request) {
decrementRecursionCounter(request, WRAP_COUNTER, () -> dispatcher.cleanUpRequest(request));
}
/**
* Finds and optionally creates an {@link ActionMapping}. It first looks in the current request to see if one
* has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the
@@ -172,7 +175,7 @@ public class PrepareOperations {
* has already been found, otherwise, it creates it and stores it in the request. No mapping will be created in the
* case of static resource requests or unidentifiable requests for other servlets, for example.
* @param forceLookup if true, the action mapping will be looked up from the ActionMapper instance, ignoring if there is one
* in the request or not
* in the request or not
*
* @param request servlet request
* @param response servlet response
@@ -180,18 +183,24 @@ public class PrepareOperations {
* @return the action mapping
*/
public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mapping == null || forceLookup) {
ActionMapping mapping = null;
Object mappingAttr = request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
if (mappingAttr == null || forceLookup) {
try {
mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
if (mapping != null) {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
} else {
request.setAttribute(STRUTS_ACTION_MAPPING_KEY, NO_ACTION_MAPPING);
}
} catch (Exception ex) {
if (dispatcher.isHandleException() || dispatcher.isDevMode()) {
dispatcher.sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
}
}
} else if (!NO_ACTION_MAPPING.equals(mappingAttr)) {
mapping = (ActionMapping) mappingAttr;
}
return mapping;
@@ -220,13 +229,14 @@ public class PrepareOperations {
*
* @return <tt>true</tt> if the request URI matches one of the given patterns
*/
public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) {
if (excludedPatterns != null) {
String uri = RequestUtils.getUri(request);
for ( Pattern pattern : excludedPatterns ) {
if (pattern.matcher(uri).matches()) {
return true;
}
public boolean isUrlExcluded(HttpServletRequest request, List<Pattern> excludedPatterns) {
if (excludedPatterns == null) {
return false;
}
String uri = RequestUtils.getUri(request);
for (Pattern pattern : excludedPatterns) {
if (pattern.matcher(uri).matches()) {
return true;
}
}
return false;
@@ -253,7 +263,6 @@ public class PrepareOperations {
/**
* Clear any override of the static devMode value being applied to the current thread.
*
* This can be useful for any situation where {@link #overrideDevMode(boolean)} might be called
* in a flow where {@link #cleanupRequest(javax.servlet.http.HttpServletRequest)} does not get called.
* May be very situational (such as some unit tests), but may have other utility as well.
@@ -262,4 +271,30 @@ public class PrepareOperations {
devModeOverride.remove(); // Remove current thread's value, enxure next read returns it to initialValue (typically null).
}
/**
* Helper method to potentially count recursive executions with a request attribute. Should be used in conjunction
* with {@link #decrementRecursionCounter}.
*/
public static void incrementRecursionCounter(HttpServletRequest request, String attributeName) {
Integer setCounter = (Integer) request.getAttribute(attributeName);
if (setCounter == null) {
setCounter = 0;
}
request.setAttribute(attributeName, ++setCounter);
}
/**
* Helper method to count execution completions with a request attribute, and optionally execute some code
* (e.g. cleanup) once all recursive executions have completed. Should be used in conjunction with
* {@link #incrementRecursionCounter}.
*/
public static void decrementRecursionCounter(HttpServletRequest request, String attributeName, Runnable runnable) {
Integer setCounter = (Integer) request.getAttribute(attributeName);
if (setCounter != null) {
request.setAttribute(attributeName, --setCounter);
}
if ((setCounter == null || setCounter == 0) && runnable != null) {
runnable.run();
}
}
}
@@ -28,11 +28,11 @@ import java.util.Set;
/**
* A simple implementation of the {@link java.util.Map} interface to handle a collection of request attributes.
*/
public class RequestMap extends AbstractMap implements Serializable {
public class RequestMap extends AbstractMap<String, Object> implements Serializable {
private static final long serialVersionUID = -7675640869293787926L;
private Set<Object> entries;
private Set<Entry<String, Object>> entries;
private HttpServletRequest request;
/**
@@ -48,12 +48,13 @@ public class RequestMap extends AbstractMap implements Serializable {
/**
* Removes all attributes from the request as well as clears entries in this map.
*/
@Override
public void clear() {
entries = null;
Enumeration keys = request.getAttributeNames();
Enumeration<String> keys = request.getAttributeNames();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String key = keys.nextElement();
request.removeAttribute(key);
}
}
@@ -63,38 +64,19 @@ public class RequestMap extends AbstractMap implements Serializable {
*
* @return a Set of attributes from the http request.
*/
public Set entrySet() {
@Override
public Set<Entry<String, Object>> entrySet() {
if (entries == null) {
entries = new HashSet<>();
Enumeration enumeration = request.getAttributeNames();
Enumeration<String> enumeration = request.getAttributeNames();
while (enumeration.hasMoreElements()) {
final String key = enumeration.nextElement().toString();
final String key = enumeration.nextElement();
final Object value = request.getAttribute(key);
entries.add(new Entry() {
public boolean equals(Object obj) {
if (!(obj instanceof Entry)) {
return false;
}
Entry entry = (Entry) obj;
return ((key == null) ? (entry.getKey() == null) : key.equals(entry.getKey())) && ((value == null) ? (entry.getValue() == null) : value.equals(entry.getValue()));
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
public Object getKey() {
return key;
}
public Object getValue() {
return value;
}
public Object setValue(Object obj) {
entries.add(new StringObjectEntry(key, value) {
@Override
public Object setValue(final Object obj) {
request.setAttribute(key, obj);
return value;
@@ -112,8 +94,8 @@ public class RequestMap extends AbstractMap implements Serializable {
* @param key the name of the request attribute.
* @return the request attribute or <tt>null</tt> if it doesn't exist.
*/
public Object get(Object key) {
return request.getAttribute(key.toString());
public Object get(final String key) {
return request.getAttribute(key);
}
/**
@@ -123,10 +105,13 @@ public class RequestMap extends AbstractMap implements Serializable {
* @param value the value to set.
* @return the object that was just set.
*/
public Object put(Object key, Object value) {
@Override
public Object put(final String key, final Object value) {
Object oldValue = get(key);
entries = null;
request.setAttribute(key.toString(), value);
request.setAttribute(key, value);
return oldValue;
}
@@ -136,11 +121,11 @@ public class RequestMap extends AbstractMap implements Serializable {
* @param key the name of the attribute to remove.
* @return the value that was removed or <tt>null</tt> if the value was not found (and hence, not removed).
*/
public Object remove(Object key) {
public Object remove(final String key) {
entries = null;
Object value = get(key);
request.removeAttribute(key.toString());
request.removeAttribute(key);
return value;
}
@@ -21,29 +21,33 @@ package org.apache.struts2.dispatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.Serializable;
import java.util.*;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
/**
* A simple implementation of the {@link java.util.Map} interface to handle a collection of HTTP session
* attributes. The {@link #entrySet()} method enumerates over all session attributes and creates a Set of entries.
* Note, this will occur lazily - only when the entry set is asked for.
*/
public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable {
public class SessionMap extends AbstractMap<String, Object> implements Serializable {
private static final long serialVersionUID = 4678843241638046854L;
protected HttpSession session;
protected Set<Map.Entry<K, V>> entries;
protected Set<Entry<String, Object>> entries;
protected HttpServletRequest request;
/**
* Creates a new session map given a http servlet request. Note, ths enumeration of request
* Creates a new session map given a http servlet request. Note, the enumeration of request
* attributes will occur when the map entries are asked for.
*
* @param request the http servlet request object.
*/
public SessionMap(HttpServletRequest request) {
public SessionMap(final HttpServletRequest request) {
// note, holding on to this request and relying on lazy session initalization will not work
// if you are running your action invocation in a background task, such as using the
// "execAndWait" interceptor
@@ -70,7 +74,7 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
* Removes all attributes from the session as well as clears entries in this
* map.
*/
@SuppressWarnings("unchecked")
@Override
public void clear() {
if (session == null) {
return;
@@ -78,7 +82,7 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
synchronized (session.getId().intern()) {
entries = null;
Enumeration<String> attributeNamesEnum = session.getAttributeNames();
final Enumeration<String> attributeNamesEnum = session.getAttributeNames();
while (attributeNamesEnum.hasMoreElements()) {
session.removeAttribute(attributeNamesEnum.nextElement());
}
@@ -91,8 +95,8 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
*
* @return a Set of attributes from the http session.
*/
@SuppressWarnings("unchecked")
public Set<java.util.Map.Entry<K, V>> entrySet() {
@Override
public Set<Entry<String, Object>> entrySet() {
if (session == null) {
return Collections.emptySet();
}
@@ -101,37 +105,17 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
if (entries == null) {
entries = new HashSet<>();
Enumeration<?> enumeration = session.getAttributeNames();
final Enumeration<String> enumeration = session.getAttributeNames();
while (enumeration.hasMoreElements()) {
final String key = enumeration.nextElement().toString();
final String key = enumeration.nextElement();
final Object value = session.getAttribute(key);
entries.add(new Map.Entry<K, V>() {
public boolean equals(Object obj) {
if (!(obj instanceof Map.Entry)) {
return false;
}
Map.Entry<K, V> entry = (Map.Entry<K, V>) obj;
return ((key == null) ? (entry.getKey() == null) : key.equals(entry.getKey())) && ((value == null) ? (entry.getValue() == null) : value.equals(entry.getValue()));
}
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
public K getKey() {
return (K) key;
}
public V getValue() {
return (V) value;
}
public V setValue(Object obj) {
entries.add(new StringObjectEntry(key, value) {
@Override
public Object setValue(final Object obj) {
session.setAttribute(key, obj);
return (V) value;
return value;
}
});
}
@@ -143,18 +127,21 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
/**
* Returns the session attribute associated with the given key or <tt>null</tt> if it doesn't exist.
*
* <b>Note:</b> Must use the same signature as {@link java.util.AbstractMap#get(java.lang.Object)} to ensure the
* expected specialized behaviour is performed here (and not the generic ancestor behaviour).
*
* @param key the name of the session attribute.
* @return the session attribute or <tt>null</tt> if it doesn't exist.
*/
@SuppressWarnings("unchecked")
public V get(Object key) {
@Override
public Object get(final Object key) {
if (session == null) {
return null;
}
synchronized (session.getId().intern()) {
return (V) session.getAttribute(key.toString());
return session.getAttribute(key != null ? key.toString() : null);
}
}
@@ -165,16 +152,17 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
* @param value the value to set.
* @return the object that was just set.
*/
public V put(K key, V value) {
@Override
public Object put(final String key, final Object value) {
synchronized (this) {
if (session == null) {
session = request.getSession(true);
}
}
synchronized (session.getId().intern()) {
V oldValue = get(key);
final Object oldValue = get(key);
entries = null;
session.setAttribute(key.toString(), value);
session.setAttribute(key, value);
return oldValue;
}
}
@@ -182,10 +170,14 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
/**
* Removes the specified session attribute.
*
* <b>Note:</b> Must use the same signature as {@link java.util.AbstractMap#remove(java.lang.Object)} to ensure the
* expected specialized behaviour is performed here (and not the generic ancestor behaviour).
*
* @param key the name of the attribute to remove.
* @return the value that was removed or <tt>null</tt> if the value was not found (and hence, not removed).
*/
public V remove(Object key) {
@Override
public Object remove(final Object key) {
if (session == null) {
return null;
}
@@ -193,8 +185,9 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
synchronized (session.getId().intern()) {
entries = null;
V value = get(key);
session.removeAttribute(key.toString());
final String keyAsString = (key != null ? key.toString() : null);
final Object value = get(keyAsString);
session.removeAttribute(keyAsString);
return value;
}
@@ -204,16 +197,21 @@ public class SessionMap<K, V> extends AbstractMap<K, V> implements Serializable
/**
* Checks if the specified session attribute with the given key exists.
*
* <b>Note:</b> Must use the same signature as {@link java.util.AbstractMap#containsKey(java.lang.Object)} to ensure the
* expected specialized behaviour is performed here (and not the generic ancestor behaviour).
*
* @param key the name of the session attribute.
* @return <tt>true</tt> if the session attribute exits or <tt>false</tt> if it doesn't exist.
*/
public boolean containsKey(Object key) {
@Override
public boolean containsKey(final Object key) {
if (session == null) {
return false;
}
synchronized (session.getId().intern()) {
return (session.getAttribute(key.toString()) != null);
final String keyAsString = (key != null ? key.toString() : null);
return (session.getAttribute(keyAsString) != null);
}
}
}
@@ -0,0 +1,64 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.struts2.dispatcher;
import java.util.Map.Entry;
abstract class StringObjectEntry implements Entry<String, Object> {
private String key;
private Object value;
StringObjectEntry(final String key, final Object value) {
this.key = key;
this.value = value;
}
@Override
public String getKey() {
return key;
}
@Override
public Object getValue() {
return value;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Entry)) {
return false;
}
Entry<?, ?> entry = (Entry<?, ?>) obj;
return keyEquals(entry) && valueEquals(entry);
}
private boolean keyEquals(final Entry<?, ?> entry) {
return (key == null) ? (entry.getKey() == null) : key.equals(entry.getKey());
}
private boolean valueEquals(Entry<?, ?> entry) {
return (value == null) ? (entry.getValue() == null) : value.equals(entry.getValue());
}
@Override
public int hashCode() {
return ((key == null) ? 0 : key.hashCode()) ^ ((value == null) ? 0 : value.hashCode());
}
}
@@ -20,12 +20,17 @@ package org.apache.struts2.dispatcher.filter;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.ExecuteOperations;
import org.apache.struts2.dispatcher.InitOperations;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import javax.servlet.*;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@@ -46,14 +51,43 @@ public class StrutsExecuteFilter implements StrutsStatics, Filter {
protected synchronized void lazyInit() {
if (execute == null) {
InitOperations init = new InitOperations();
InitOperations init = createInitOperations();
Dispatcher dispatcher = init.findDispatcherOnThread();
init.initStaticContentLoader(new FilterHostConfig(filterConfig), dispatcher);
prepare = new PrepareOperations(dispatcher);
execute = new ExecuteOperations(dispatcher);
prepare = createPrepareOperations(dispatcher);
execute = createExecuteOperations(dispatcher);
}
}
/**
* Creates a new instance of {@link InitOperations} to be used during
* initialising {@link Dispatcher}
*
* @return instance of {@link InitOperations}
*/
protected InitOperations createInitOperations() {
return new InitOperations();
}
/**
* Creates a new instance of {@link PrepareOperations} to be used during
* initialising {@link Dispatcher}
*
* @return instance of {@link PrepareOperations}
*/
protected PrepareOperations createPrepareOperations(Dispatcher dispatcher) {
return new PrepareOperations(dispatcher);
}
/**
* Creates a new instance of {@link ExecuteOperations} to be used during
* initialising {@link Dispatcher}
*
* @return instance of {@link ExecuteOperations}
*/
protected ExecuteOperations createExecuteOperations(Dispatcher dispatcher) {
return new ExecuteOperations(dispatcher);
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
@@ -73,22 +107,15 @@ public class StrutsExecuteFilter implements StrutsStatics, Filter {
ActionMapping mapping = prepare.findActionMapping(request, response);
//if recursion counter is > 1, it means we are in a "forward", in that case a mapping will still be
//in the request, if we handle it, it will lead to an infinite loop, see WW-3077
Integer recursionCounter = (Integer) request.getAttribute(PrepareOperations.CLEANUP_RECURSION_COUNTER);
if (mapping == null || recursionCounter > 1) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
if (mapping != null) {
execute.executeAction(request, response, mapping);
} else if (!execute.executeStaticResourceRequest(request, response)) {
chain.doFilter(request, response);
}
}
private boolean excludeUrl(HttpServletRequest request) {
return request.getAttribute(StrutsPrepareFilter.REQUEST_EXCLUDED_FROM_ACTION_MAPPING) != null;
return Boolean.TRUE.equals(request.getAttribute(StrutsPrepareFilter.REQUEST_EXCLUDED_FROM_ACTION_MAPPING));
}
public void destroy() {
@@ -23,10 +23,10 @@ import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
import org.apache.struts2.StrutsStatics;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.ExecuteOperations;
import org.apache.struts2.dispatcher.InitOperations;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -50,7 +50,7 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
protected PrepareOperations prepare;
protected ExecuteOperations execute;
protected List<Pattern> excludedPatterns = null;
protected List<Pattern> excludedPatterns;
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = createInitOperations();
@@ -62,6 +62,7 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
prepare = createPrepareOperations(dispatcher);
execute = createExecuteOperations(dispatcher);
// Note: Currently, excluded patterns are not refreshed following an XWork config reload
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
@@ -106,7 +107,7 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
/**
* Callback for post initialization
*
* @param dispatcher the dispatcher
* @param dispatcher the dispatcher
* @param filterConfig the filter config
*/
protected void postInit(Dispatcher dispatcher, FilterConfig filterConfig) {
@@ -118,34 +119,48 @@ public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
HttpServletResponse response = (HttpServletResponse) res;
try {
prepare.trackRecursion(request);
String uri = RequestUtils.getUri(request);
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", uri);
if (prepare.isUrlExcluded(request, excludedPatterns)) {
LOG.trace("Request: {} is excluded from handling by Struts, passing request to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Checking if {} is a static resource", uri);
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
LOG.trace("Uri {} is not a static resource, assuming action", uri);
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
HttpServletRequest wrappedRequest = prepare.wrapRequest(request);
ActionMapping mapping = prepare.findActionMapping(wrappedRequest, response, true);
if (mapping == null) {
LOG.trace("Cannot find mapping for {}, passing to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Found mapping {} for {}", mapping, uri);
execute.executeAction(wrappedRequest, response, mapping);
}
}
tryHandleRequest(chain, request, response, uri);
}
} finally {
prepare.cleanupRequest(request);
}
}
private void tryHandleRequest(FilterChain chain, HttpServletRequest request, HttpServletResponse response, String uri) throws IOException, ServletException {
LOG.trace("Checking if: {} is a static resource", uri);
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
LOG.trace("Uri: {} is not a static resource, assuming action", uri);
handleRequest(chain, request, response, uri);
}
}
private void handleRequest(FilterChain chain, HttpServletRequest request, HttpServletResponse response, String uri) throws ServletException, IOException {
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
HttpServletRequest wrappedRequest = prepare.wrapRequest(request);
try {
ActionMapping mapping = prepare.findActionMapping(wrappedRequest, response, true);
if (mapping == null) {
LOG.trace("Cannot find mapping for: {}, passing to other filters", uri);
chain.doFilter(request, response);
} else {
LOG.trace("Found mapping: {} for: {}", mapping, uri);
execute.executeAction(wrappedRequest, response, mapping);
}
} finally {
prepare.cleanupWrappedRequest(wrappedRequest);
}
}
public void destroy() {
prepare.cleanupDispatcher();
}
@@ -43,16 +43,17 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
protected static final String REQUEST_EXCLUDED_FROM_ACTION_MAPPING = StrutsPrepareFilter.class.getName() + ".REQUEST_EXCLUDED_FROM_ACTION_MAPPING";
protected PrepareOperations prepare;
protected List<Pattern> excludedPatterns = null;
protected List<Pattern> excludedPatterns;
public void init(FilterConfig filterConfig) throws ServletException {
InitOperations init = new InitOperations();
InitOperations init = createInitOperations();
Dispatcher dispatcher = null;
try {
FilterHostConfig config = new FilterHostConfig(filterConfig);
dispatcher = init.initDispatcher(config);
prepare = new PrepareOperations(dispatcher);
prepare = createPrepareOperations(dispatcher);
// Note: Currently, excluded patterns are not refreshed following an XWork config reload
this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
postInit(dispatcher, filterConfig);
@@ -64,6 +65,26 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
}
}
/**
* Creates a new instance of {@link InitOperations} to be used during
* initialising {@link Dispatcher}
*
* @return instance of {@link InitOperations}
*/
protected InitOperations createInitOperations() {
return new InitOperations();
}
/**
* Creates a new instance of {@link PrepareOperations} to be used during
* initialising {@link Dispatcher}
*
* @return instance of {@link PrepareOperations}
*/
protected PrepareOperations createPrepareOperations(Dispatcher dispatcher) {
return new PrepareOperations(dispatcher);
}
/**
* Callback for post initialization
*
@@ -78,18 +99,25 @@ public class StrutsPrepareFilter implements StrutsStatics, Filter {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
boolean didWrap = false;
try {
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, new Object());
prepare.trackRecursion(request);
if (prepare.isUrlExcluded(request, excludedPatterns)) {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, true);
} else {
request.setAttribute(REQUEST_EXCLUDED_FROM_ACTION_MAPPING, false);
prepare.setEncodingAndLocale(request, response);
prepare.createActionContext(request, response);
prepare.assignDispatcherToThread();
request = prepare.wrapRequest(request);
prepare.findActionMapping(request, response);
didWrap = true;
prepare.findActionMapping(request, response, true);
}
chain.doFilter(request, response);
} finally {
if (didWrap) {
prepare.cleanupWrappedRequest(request);
}
prepare.cleanupRequest(request);
}
}
@@ -132,7 +132,6 @@ public class DefaultActionMapper implements ActionMapper {
protected String defaultMethodName = "execute";
private boolean allowActionPrefix = false;
private boolean allowActionCrossNamespaceAccess = false;
protected List<String> extensions = new ArrayList<String>() {{
add("action");
@@ -162,12 +161,12 @@ public class DefaultActionMapper implements ActionMapper {
}
}
String actionName = cleanupActionName(name);
if (allowSlashesInActionNames && !allowActionCrossNamespaceAccess) {
if (allowSlashesInActionNames) {
if (actionName.startsWith("/")) {
actionName = actionName.substring(1);
}
}
if (!allowSlashesInActionNames && !allowActionCrossNamespaceAccess) {
if (!allowSlashesInActionNames) {
if (actionName.lastIndexOf('/') != -1) {
actionName = actionName.substring(actionName.lastIndexOf('/') + 1);
}
@@ -241,16 +240,6 @@ public class DefaultActionMapper implements ActionMapper {
this.allowActionPrefix = BooleanUtils.toBoolean(allowActionPrefix);
}
/**
* @deprecated since 6.1.0 - please refactor your application to avoid using this functionality
* @param allowActionCrossNamespaceAccess true to enable cross namespace action access
*/
@Deprecated
@Inject(value = StrutsConstants.STRUTS_MAPPER_ACTION_PREFIX_CROSSNAMESPACES)
public void setAllowActionCrossNamespaceAccess(String allowActionCrossNamespaceAccess) {
this.allowActionCrossNamespaceAccess = BooleanUtils.toBoolean(allowActionCrossNamespaceAccess);
}
@Inject
public void setContainer(Container container) {
this.container = container;
@@ -51,8 +51,22 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
/**
* Specifies the maximum size of the entire request.
*/
protected long maxSize;
protected boolean maxSizeProvided;
protected Long maxSize;
/**
* Specifies the maximum number of files in one request.
*/
protected Long maxFiles;
/**
* Specifies the maximum length of a string parameter in a multipart request.
*/
protected Long maxStringLength;
/**
* Specifies the maximum size per file in the request.
*/
protected Long maxFileSize;
/**
* Specifies the buffer size to use during streaming.
@@ -80,14 +94,28 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
}
/**
* @param maxSize Injects the Struts multiple part maximum size.
* @param maxSize Injects the Struts multipart request maximum size.
*/
@Inject(StrutsConstants.STRUTS_MULTIPART_MAXSIZE)
public void setMaxSize(String maxSize) {
this.maxSizeProvided = true;
this.maxSize = Long.parseLong(maxSize);
}
@Inject(StrutsConstants.STRUTS_MULTIPART_MAXFILES)
public void setMaxFiles(String maxFiles) {
this.maxFiles = Long.parseLong(maxFiles);
}
@Inject(value = StrutsConstants.STRUTS_MULTIPART_MAXFILESIZE, required = false)
public void setMaxFileSize(String maxFileSize) {
this.maxFileSize = Long.parseLong(maxFileSize);
}
@Inject(StrutsConstants.STRUTS_MULTIPART_MAX_STRING_LENGTH)
public void setMaxStringLength(String maxStringLength) {
this.maxStringLength = Long.parseLong(maxStringLength);
}
@Inject
public void setLocaleProviderFactory(LocaleProviderFactory localeProviderFactory) {
defaultLocale = localeProviderFactory.createLocaleProvider().getLocale();
@@ -134,9 +162,9 @@ public abstract class AbstractMultiPartRequest implements MultiPartRequest {
int forwardSlash = fileName.lastIndexOf('/');
int backwardSlash = fileName.lastIndexOf('\\');
if (forwardSlash != -1 && forwardSlash > backwardSlash) {
fileName = fileName.substring(forwardSlash + 1, fileName.length());
fileName = fileName.substring(forwardSlash + 1);
} else {
fileName = fileName.substring(backwardSlash + 1, fileName.length());
fileName = fileName.substring(backwardSlash + 1);
}
return fileName;
}
@@ -18,6 +18,7 @@
*/
package org.apache.struts2.dispatcher.multipart;
import org.apache.commons.fileupload.FileCountLimitExceededException;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.FileUploadException;
@@ -35,7 +36,13 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Multipart form data request adapter for Jakarta Commons Fileupload package.
@@ -63,11 +70,17 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
setLocale(request);
processUpload(request, saveDir);
} catch (FileUploadException e) {
LOG.warn("Request exceeded size limit!", e);
LOG.debug("Request exceeded size limit!", e);
LocalizedMessage errorMessage;
if(e instanceof FileUploadBase.SizeLimitExceededException) {
if (e instanceof FileUploadBase.SizeLimitExceededException) {
FileUploadBase.SizeLimitExceededException ex = (FileUploadBase.SizeLimitExceededException) e;
errorMessage = buildErrorMessage(e, new Object[]{ex.getPermittedSize(), ex.getActualSize()});
} else if (e instanceof FileUploadBase.FileSizeLimitExceededException) {
FileUploadBase.FileSizeLimitExceededException ex = (FileUploadBase.FileSizeLimitExceededException) e;
errorMessage = buildErrorMessage(e, new Object[]{ex.getFileName(), ex.getPermittedSize(), ex.getActualSize()});
} else if (e instanceof FileCountLimitExceededException) {
FileCountLimitExceededException ex = (FileCountLimitExceededException) e;
errorMessage = buildErrorMessage(e, new Object[]{ex.getLimit()});
} else {
errorMessage = buildErrorMessage(e, new Object[]{});
}
@@ -76,7 +89,7 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
errors.add(errorMessage);
}
} catch (Exception e) {
LOG.warn("Unable to parse request", e);
LOG.debug("Unable to parse request", e);
LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
@@ -127,8 +140,19 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
values = new ArrayList<>();
}
if (item.getSize() == 0) {
long size = item.getSize();
if (size == 0) {
values.add(StringUtils.EMPTY);
} else if (size > maxStringLength) {
String errorKey = "struts.messages.upload.error.parameter.too.long";
LocalizedMessage localizedMessage = new LocalizedMessage(this.getClass(), errorKey, null,
new Object[] { item.getFieldName(), maxStringLength, size });
if (!errors.contains(localizedMessage)) {
errors.add(localizedMessage);
}
return;
} else if (charset != null) {
values.add(item.getString(charset));
} else {
@@ -150,7 +174,15 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
protected ServletFileUpload createServletFileUpload(DiskFileItemFactory fac) {
ServletFileUpload upload = new ServletFileUpload(fac);
upload.setSizeMax(maxSize);
if (maxSize != null) {
upload.setSizeMax(maxSize);
}
if (maxFiles != null) {
upload.setFileCountMax(maxFiles);
}
if (maxFileSize != null) {
upload.setFileSizeMax(maxFileSize);
}
return upload;
}
@@ -316,14 +348,14 @@ public class JakartaMultiPartRequest extends AbstractMultiPartRequest {
}
/* (non-Javadoc)
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
*/
* @see org.apache.struts2.dispatcher.multipart.MultiPartRequest#cleanUp()
*/
public void cleanUp() {
Set<String> names = files.keySet();
for (String name : names) {
List<FileItem> items = files.get(name);
for (FileItem item : items) {
LOG.debug("Removing file {} {}", name, item );
LOG.debug("Removing file {} {}", name, item);
if (!item.isInMemory()) {
item.delete();
}
@@ -29,16 +29,27 @@ import org.apache.logging.log4j.Logger;
import org.apache.struts2.dispatcher.LocalizedMessage;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.util.*;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Multi-part form data request adapter for Jakarta Commons FileUpload package that
* leverages the streaming API rather than the traditional non-streaming API.
*
* <p>
* For more details see WW-3025
*
* @author Chris Cranford
* @since 2.3.18
*/
public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
@@ -85,7 +96,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
types.add(fileInfo.getContentType());
}
return types.toArray(new String[types.size()]);
return types.toArray(new String[0]);
}
/* (non-Javadoc)
@@ -102,7 +113,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
files.add(new StrutsUploadedFile(fileInfo.getFile()));
}
return files.toArray(new UploadedFile[files.size()]);
return files.toArray(new UploadedFile[0]);
}
/* (non-Javadoc)
@@ -119,7 +130,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
names.add(getCanonicalName(fileInfo.getOriginalName()));
}
return names.toArray(new String[names.size()]);
return names.toArray(new String[0]);
}
/* (non-Javadoc)
@@ -143,7 +154,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
names.add(fileInfo.getFile().getName());
}
return names.toArray(new String[names.size()]);
return names.toArray(new String[0]);
}
/* (non-Javadoc)
@@ -170,7 +181,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
public String[] getParameterValues(String name) {
List<String> values = parameters.get(name);
if (values != null && values.size() > 0) {
return values.toArray(new String[values.size()]);
return values.toArray(new String[0]);
}
return null;
}
@@ -183,7 +194,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
setLocale(request);
processUpload(request, saveDir);
} catch (Exception e) {
LOG.warn("Error occurred during parsing of multi part request", e);
LOG.debug("Error occurred during parsing of multi part request", e);
LocalizedMessage errorMessage = buildErrorMessage(e, new Object[]{});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
@@ -209,9 +220,15 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
// Interface with Commons FileUpload API
// Using the Streaming API
ServletFileUpload servletFileUpload = new ServletFileUpload();
if (maxSizeProvided) {
if (maxSize != null) {
servletFileUpload.setSizeMax(maxSize);
}
if (maxFiles != null) {
servletFileUpload.setFileCountMax(maxFiles);
}
if (maxFileSize != null) {
servletFileUpload.setFileSizeMax(maxFileSize);
}
FileItemIterator i = servletFileUpload.getItemIterator(request);
// Iterate the file items
@@ -232,10 +249,9 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
else {
// prevent processing file field item if request size not allowed.
// also warn user in the logs.
if (!requestSizePermitted) {
addFileSkippedError(itemStream.getName(), request);
LOG.warn("Skipped stream '{}', request maximum size ({}) exceeded.", itemStream.getName(), maxSize);
LOG.debug("Skipped stream '{}', request maximum size ({}) exceeded.", itemStream.getName(), maxSize);
continue;
}
@@ -258,10 +274,9 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
// if maxSize is specified as -1, there is no sanity check and it's
// safe to return true for any request, delegating the failure
// checks later in the upload process.
if (maxSize == -1 || request == null) {
if (maxSize == null || maxSize == -1 || request == null) {
return true;
}
return request.getContentLength() < maxSize;
}
@@ -270,12 +285,7 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* @return the request content length.
*/
protected long getRequestSize(HttpServletRequest request) {
long requestSize = 0;
if (request != null) {
requestSize = request.getContentLength();
}
return requestSize;
return request != null ? request.getContentLength() : 0;
}
/**
@@ -286,8 +296,9 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
*/
protected void addFileSkippedError(String fileName, HttpServletRequest request) {
String exceptionMessage = "Skipped file " + fileName + "; request size limit exceeded.";
FileSizeLimitExceededException exception = new FileUploadBase.FileSizeLimitExceededException(exceptionMessage, getRequestSize(request), maxSize);
LocalizedMessage message = buildErrorMessage(exception, new Object[]{fileName, getRequestSize(request), maxSize});
long allowedMaxSize = maxSize != null ? maxSize : -1;
FileSizeLimitExceededException exception = new FileUploadBase.FileSizeLimitExceededException(exceptionMessage, getRequestSize(request), allowedMaxSize);
LocalizedMessage message = buildErrorMessage(exception, new Object[]{fileName, getRequestSize(request), allowedMaxSize});
if (!errors.contains(message)) {
errors.add(message);
}
@@ -386,12 +397,12 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
* @throws IOException in case of IO errors
*/
protected boolean streamFileToDisk(FileItemStream itemStream, File file) throws IOException {
boolean result = false;
boolean result;
try (InputStream input = itemStream.openStream();
OutputStream output = new BufferedOutputStream(new FileOutputStream(file), bufferSize)) {
OutputStream output = new BufferedOutputStream(Files.newOutputStream(file.toPath()), bufferSize)) {
byte[] buffer = new byte[bufferSize];
LOG.debug("Streaming file using buffer size {}.", bufferSize);
for (int length = 0; ((length = input.read(buffer)) > 0); ) {
for (int length; ((length = input.read(buffer)) > 0); ) {
output.write(buffer, 0, length);
}
result = true;
@@ -433,9 +444,9 @@ public class JakartaStreamMultiPartRequest extends AbstractMultiPartRequest {
private static final long serialVersionUID = 1083158552766906037L;
private File file;
private String contentType;
private String originalName;
private final File file;
private final String contentType;
private final String originalName;
/**
* Default constructor.
@@ -19,11 +19,10 @@
package org.apache.struts2.dispatcher.servlet;
import org.apache.struts2.dispatcher.Dispatcher;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import org.apache.struts2.dispatcher.ExecuteOperations;
import org.apache.struts2.dispatcher.InitOperations;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.servlet.ServletHostConfig;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

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