Compare commits

...

142 Commits

Author SHA1 Message Date
Lukasz Lenart 9a63e6504b [maven-release-plugin] prepare release STRUTS_2_5_BETA3 2016-01-21 09:25:54 +01:00
Lukasz Lenart 0835c33adf Cleans up after migrating to Tiles 3 and dropped struts2-tiles3-plugin 2016-01-21 09:20:05 +01:00
Lukasz Lenart 11561cb299 Merge branch 'WW-4590' 2016-01-21 08:45:07 +01:00
Aleksandr Mashchenko 2a46a6586e Result annotation name attribute as an array, removed one of ResultInfo constructors 2016-01-20 18:03:57 +02:00
Aleksandr Mashchenko 930f6e4cf7 Cleaner result name parsing 2016-01-20 17:52:56 +02:00
Lukasz Lenart cae57093b5 Merge branch 'WW-4588' 2016-01-20 16:10:52 +01:00
petersr 7899f10d80 WW-4588: Improve the Struts2 Rest plugin to honor Accept header 2016-01-20 06:34:25 -08:00
Lukasz Lenart 04f5813c90 Merge branch 'WW-4456' 2016-01-20 10:27:00 +01:00
petersr 1fb695a82b WW-4589 do not overwrite explicit method name 2016-01-19 14:02:57 -08:00
Victor Sosa bc8d374647 WW-4456 - Got NPE when File Upload Limitation exceeds in portal env. 2016-01-19 08:54:15 -04:00
victor sosa c167d6c855 Merge pull request #1 from apache/master
update pull
2016-01-19 08:25:29 -04:00
Lukasz Lenart 3163d6c38b WW-4591 Defines new result JSONActionRedirectResult 2016-01-19 08:44:53 +01:00
Aleksandr Mashchenko 087cf610ab WW-4590 - Allow to use multiple names in result 2016-01-18 21:23:46 +02:00
Lukasz Lenart d923c7c4f2 WW-4584 Upgrades Tiles plugin to Tiles 3
Also drops existing Tiles 3 plugin
2016-01-18 15:55:59 +01:00
Lukasz Lenart 93375125ad Simplifies logging 2016-01-17 10:25:36 +01:00
Lukasz Lenart 294394b256 WW-3266 Uses getFieldValue() instead of getDescription() 2016-01-16 10:03:11 +01:00
Lukasz Lenart 328da4e225 Adds support for I18N 2016-01-15 10:12:25 +01:00
Lukasz Lenart a42a1731f3 Adds support to use Struts' Locale 2016-01-15 10:12:15 +01:00
Lukasz Lenart f645c91ee6 Extracts constants representing expression languages 2016-01-15 08:45:54 +01:00
Lukasz Lenart 0e85da6228 Uses existing constants instead of literals 2016-01-15 08:44:25 +01:00
Aleksandr Mashchenko e0003f0471 WW-4585 Struts2 Rest plugin doesn't handle JSESSIONID with DMI 2016-01-14 20:01:57 +02:00
Rene Gielen ee83d4a6db WW-4381 - exclude org.olap4j:olap4j for compile, to fix broken builds behind Maven mirror 2016-01-14 18:04:29 +01:00
Rene Gielen 9f6a0c9c94 WW-4403 - add JDK 8 profile, disabling JavaDoc lint checker when building with JDK8
(cherry picked from commit 7c4bb7e)
2016-01-14 17:47:43 +01:00
Rene Gielen a89bbe22cd WW-4507 - adjust Tomcat url decoding code to Log4j 2 logging used in Struts 2016-01-14 15:24:34 +01:00
Rene Gielen 72471d7075 WW-4507 - clone Tomcat UDecoder and use it for in query string handling
(cherry picked from commit 5421930)
2016-01-14 15:04:38 +01:00
cnenning b0a66b47ac increased visibilty of methods and constants for better extensibility 2016-01-14 09:33:50 +01:00
Lukasz Lenart b5c96a5888 Simplifies logic to fetch HttpServletRequest 2016-01-14 08:17:50 +01:00
Lukasz Lenart 5bd54cf8ac Extends logging 2016-01-13 17:22:36 +01:00
Lukasz Lenart 66d29d42c3 Overrides method to use defined base instead of fetching it again 2016-01-13 12:05:58 +01:00
Lukasz Lenart 334e6ca462 Defines helper method to fetch ActionContext from request 2016-01-13 12:05:03 +01:00
Lukasz Lenart b417894ebc Adds missing dependencies to tiles-request 2016-01-13 12:04:43 +01:00
Lukasz Lenart 6f98d776c3 Renames class to match pattern used in Tiles 2016-01-12 22:16:06 +01:00
cnenning 7bd8dd9559 made methods non static and private, javadoc updates 2016-01-12 14:14:09 +01:00
Lukasz Lenart e56d8b2e07 Merge remote-tracking branch 'upstream/master' into upgrade-tiles-plugin
Conflicts:
	pom.xml
2016-01-12 10:42:04 +01:00
Lukasz Lenart 33edfffbe2 Migrates tiles-portlet to the same version 2016-01-12 10:36:46 +01:00
Lukasz Lenart c07020cb7c Uses new way to access TileContainer 2016-01-12 10:14:11 +01:00
Lukasz Lenart ace6a5d5ee Adjusts code to Tiles 3 2016-01-12 10:13:51 +01:00
Lukasz Lenart 4638b33457 Adds missing Tiles API dependency 2016-01-12 10:13:38 +01:00
Lukasz Lenart b675844a8f Ports solution to resolve problem with EL functions 2016-01-12 08:51:34 +01:00
Lukasz Lenart af82d23c95 Upgrade Tiles dependencies 2016-01-12 08:51:14 +01:00
Lukasz Lenart 47ecd001ec Ports solution from 2.3.x branch 2016-01-12 08:51:03 +01:00
Lukasz Lenart 5fe760dc67 Removes module 2016-01-12 08:19:52 +01:00
Lukasz Lenart 2d0fe9f742 Drops Tiles 3 plugin 2016-01-12 08:19:26 +01:00
Lukasz Lenart 460faa33d8 WW-3731 Uses import to reduce noise in code 2016-01-12 08:11:08 +01:00
cnenning cb11898416 Adds new result 'JSONActionRedirectResult' to json-plugin. Contains tests and example in showcase app. The new result type is intended to be used along with existing JSONValidationInterceptor. It makes it possible to do form validation via ajax and handle form submitting, action execution and redirect evaluation all in one request inside JS context. 2016-01-11 14:36:32 +01:00
Lukasz Lenart 61a7ee2961 Prevents eval expressions at all 2016-01-10 12:00:10 +01:00
Lukasz Lenart 74e26830d2 Adds additional pattern to prevent access to getClass method 2016-01-10 11:47:25 +01:00
Victor Sosa 37f822f9cc Patch WW-3266
getFieldValue() method of ValueStackDataSource uses getDescription() to
get field value may not appropriate
2016-01-07 11:09:58 -04:00
Aleksandr Mashchenko a4f8d49214 WW-4583 JakartaStreamMultiPartRequest fails on short file name and on empty file input 2016-01-06 19:24:56 +02:00
cnenning 4f29d8861a added testcase for WW-4312 / #69 2016-01-05 13:47:15 +01:00
Lukasz Lenart f457c774e9 WW-4312 Allows null in iterator tag 2016-01-05 08:39:43 +01:00
Lukasz Lenart d862d5b96d WW-3710 Adds test to proof support for wildcards in <include/> 2016-01-05 08:16:26 +01:00
Victor Sosa 1e21b7ec1b Merge branch 'patch' of github.com:victorsosa/struts into patch 2016-01-04 18:12:57 -04:00
victor sosa bdc325b03d Merge pull request #2 from apache/master
Merge changes
2016-01-04 18:08:24 -04:00
Victor Sosa e2ae94cc0f Merge branch 'patch' of github.com:victorsosa/struts into patch 2016-01-04 18:07:47 -04:00
victor sosa 7170c13509 Merge pull request #1 from apache/master
Merge changes
2016-01-04 18:06:23 -04:00
Victor Sosa 1d3d7be466 Fix for WW-4312
A problem on Iterator tag


and  WW-3010 s:iterator fails to iterate over collections containing
null
2016-01-04 18:03:47 -04:00
Lukasz Lenart 110d63fed0 WW-4381 Upgrade to JasperReports 6.0 2016-01-04 21:21:00 +01:00
Lukasz Lenart acd55986f4 WW-3945 Sets encoding in JSON result to UTF-8 by default 2016-01-04 21:06:07 +01:00
Victor Sosa 0af9d3b60d fix conflict 2016-01-04 15:31:54 -04:00
Lukasz Lenart 3b38ccea5e Fixes issue with test running under other TZ than GMT 2016-01-04 20:26:03 +01:00
Lukasz Lenart 212bbae0fd WW-3462 Closes connection on report generation end 2016-01-04 20:16:54 +01:00
Lukasz Lenart cb0475d615 WW-4573 Uses CopyOnWrite list to avoid concurrent modifications 2016-01-04 20:03:26 +01:00
Victor Sosa e42954219a Patch WW-4381
upgrade to jasperreports 6.0

to 6.0.3
2016-01-04 13:50:41 -04:00
Victor Sosa a401f3a6ef Merge branch 'jsonencoding_utf-8' of github.com:victorsosa/struts into jsonencoding_utf-8 2016-01-04 13:05:28 -04:00
Victor Sosa 32597350fe Update unit test 2016-01-04 12:55:16 -04:00
victor sosa 4c96d239dd Update JasperReportsResult.java 2016-01-04 12:23:44 -04:00
Victor Sosa b198e3bf3d Patch WW-3945
Set default encoding for JSONResult to UTF-8

Now the default encoding is UTF-8 for JSON as should be.
2016-01-04 12:20:58 -04:00
Victor Sosa f9b7ebb87e Patch WW-3945
Set default encoding for JSONResult to UTF-8

Now the default encoding is UTF-8 for JSON as should be.
2016-01-04 12:14:39 -04:00
donghui 32ec7a669c make test passed for people not using GMT. 2016-01-04 11:23:51 +08:00
Victor Sosa 0e111125d5 A proper way to close the db connection 2016-01-03 08:05:03 -04:00
Victor Sosa 38da3f6705 Merge branch 'master' of git@github.com:victorsosa/struts.git 2016-01-03 07:36:34 -04:00
Victor Sosa 316b8a926e Patch WW-3462
connection open after report generation

Now the connection is close after been used.
2016-01-03 07:34:00 -04:00
Victor Sosa b257250036 fix for WW-4573
NPE/ concurrent modification exception

using a CopyOnWriteArrayList. This is to keep Memory consistency on the
ValueStack.
2016-01-02 15:41:48 -04:00
Victor Sosa cfc6f19ec7 fix for WW-4573
NPE/ concurrent modification exception

using a CopyOnWriteArrayList. This is to keep Memory consistency on the
ValueStack.
2016-01-02 15:38:35 -04:00
Victor Sosa ec5a301b53 fix for WW-4573
NPE/ concurrent modification exception

using a CopyOnWriteArrayList. This is to keep Memory consistency on the
ValueStack.
2016-01-02 15:19:48 -04:00
Victor Sosa 1df5a75bfc fix for WW-4573
NPE/ concurrent modification exception

using a CopyOnWriteArrayList. This is to keep Memory consistency on the
ValueStack.
2016-01-02 15:18:37 -04:00
Lukasz Lenart 8aebdc3e6f WW-4564 Solves issue with postback result rendered as plain/text in Chrome
Closes #62
2016-01-02 18:30:55 +01:00
victor sosa ac4199e0d8 remove empty comments 2016-01-02 13:24:33 -04:00
victor sosa 3480bc3c10 version removed
version removed, it is on the parent already
2016-01-02 11:15:52 -04:00
Victor Sosa d5a6c35589 fix for WW-4564
Postback result is rendered as plain-text rather than html form in
Chrome

the response's content type must be set to text/html in order for Chrome
to properly handle the operation.
2015-12-31 20:37:06 -04:00
Victor Sosa 70b1b2c497 fix for WW-4564
Postback result is rendered as plain-text rather than html form in
Chrome

the response's content type must be set to text/html in order for Chrome
to properly handle the operation.
2015-12-31 18:04:43 -04:00
Victor Sosa 9cc383e436 Fix to ww-4577 and ww-4576
Upgrade OGNL to version 3.1.2
Upgrade to Log4j2 version 2.5
2015-12-31 16:53:38 -04:00
Victor Sosa db483ccd15 fix for WW-4269
blank double results in NumberFormatException being thrown on sending
JSON in request body
2015-12-30 13:36:58 -04:00
Lukasz Lenart 30fd44f0ef WW-4365 Fixes issue with evaluating doubleHeaderValue and doubleHeaderKey
(cherry picked from commit 8bbfb46)
2015-11-30 08:15:37 +01:00
Lukasz Lenart 7f10be5c37 WW-4569 Adds support for .tech 2015-11-28 16:36:04 +01:00
Lukasz Lenart e488353a05 WW-4570 Drops XWork core from min-lib 2015-11-28 16:20:25 +01:00
Lukasz Lenart fa41222859 WW-4570 Drops ASM from minimal set of Jars
(cherry picked from commit 90dbf54)
2015-11-28 16:09:46 +01:00
Lukasz Lenart a20aed381a WW-4570 Uses proper schema for assembly descriptor
(cherry picked from commit b4a18d0)
2015-11-28 16:09:36 +01:00
Lukasz Lenart 103a655f4a WW-4570 Defines a minimal jar set to be used by users
(cherry picked from commit 5d26c6c)
2015-11-28 16:09:27 +01:00
Aleksandr Mashchenko 705915abd2 WW-2069 Replaces 'id' and 'name' with 'var'. 2015-11-27 22:58:50 +02:00
Lukasz Lenart 6622fab25b Fixes dependency clash 2015-11-27 16:09:37 +01:00
Lukasz Lenart 388861ff07 Registers Tiles model 2015-11-27 13:36:52 +01:00
Lukasz Lenart 27ad6cb13e Adds missing header with license 2015-11-25 20:54:39 +01:00
Lukasz Lenart 6edc0ff5e0 Updates Portal Tiles integration 2015-11-25 20:37:42 +01:00
Lukasz Lenart 26f153d262 Replaces call to deprecated api 2015-11-25 20:29:43 +01:00
Lukasz Lenart 24a5bce6c4 Drops unused classes 2015-11-25 20:29:24 +01:00
Lukasz Lenart 70716e9452 Re-implements tiles integration based on new API 2015-11-25 20:29:18 +01:00
Lukasz Lenart 85b219813f Adds required dependencies 2015-11-25 20:22:31 +01:00
Aleksandr Mashchenko ed8dc577b7 WW-4571 Drops deprecated escape attribute in property tag 2015-11-25 09:01:20 +02:00
Lukasz Lenart c3ec2c3390 Merge branch 'upper-case' 2015-11-12 20:52:07 +01:00
Lukasz Lenart 7a06fe47e1 WW-4567 Cleans up poms 2015-11-12 20:45:21 +01:00
Lukasz Racon fb896b6c1c Switch to literal regexp notation, fix url validation 2015-11-12 09:29:47 -06:00
Lukasz Racon ee562666ab Form.getValidators does not respect validateAnnotatedMethodOnly flag
WW-4139 removed method name from getValidators call.  While this works
fine for a case mentioned in that ticket it fails to account for a case
when
dynamicMethodCalls and validateAnnotatedMethodOnly are used. Without
method name actionValidatorManager.getValidators returns all validators
defined for a given field.
Expected behavior for dynamicMethodCalls with
validateAnnotatedMethodOnly case is to return validators for a given
field defined at the method level.
2015-11-09 17:02:02 -06:00
Lukasz Racon 907ce9802e Email validation blocks upper case letters
Fix makes email validation regex case insensitive.
2015-11-09 14:14:07 -06:00
Lukasz Lenart afb0c2a228 WW-4561 Upgrades OGNL to 3.1.1 2015-11-04 18:15:37 +01:00
Aleksandr Mashchenko f8718f9187 WW-2069 Drops "id" and "name" attributes" on the tags where they are deprecated. 2015-11-02 22:14:59 +02:00
Lukasz Lenart 2b4d37908d Drops reference to dropped interceptor 2015-10-30 09:52:09 +01:00
Lukasz Lenart 146b44e2e8 Always use default method if wildcard patter returned null or empty 2015-10-30 09:21:54 +01:00
Lukasz Lenart 0a08f6b673 WW-4134 Checks if Response wasn't committed to allow store messages
(cherry picked from commit 0958077)
2015-10-27 09:12:24 +01:00
Lukasz Lenart ba361aad36 WW-4554 Drops duplicated calls to initialize BeanPostProcessors
It's already done by Spring

(cherry picked from commit 7848534e2f)
2015-10-27 09:09:46 +01:00
Lukasz Lenart 5bf1b62dd3 WW-4556 Extracts tooltips into dedicated template file
(cherry picked from commit c708876)
2015-10-27 09:07:37 +01:00
Lukasz Lenart b44fa2934f WW-4526 Checks if passed in scheme param is valid
(cherry picked from commit 3e2d5da)
2015-10-20 09:37:54 +02:00
Lukasz Lenart 87dfa3f6fb WW-3161 Sets proper ActionContext to allow I18N to work
(cherry picked from commit f65e38b)
2015-10-20 08:43:39 +02:00
Lukasz Lenart 0ba7965637 Drops unused code
(cherry picked from commit 412684b)
2015-10-20 08:42:52 +02:00
Lukasz Lenart b79c6959a4 WW-4536 Checks for patterns in action names to build the same key for actions with patterns
(cherry picked from commit 30d30e07de)

# Conflicts:
#	core/src/main/java/com/opensymphony/xwork2/validator/AnnotationActionValidatorManager.java
2015-10-19 20:51:48 +02:00
Lukasz Lenart 5dcbe657b0 WW-4536 Drops static to allow redefine the key
(cherry picked from commit 755ad3fadb)
2015-10-19 20:49:49 +02:00
Lukasz Lenart 617bfd23d9 WW-2891 Cleans up poms and update Freemarker to the latest available version 2015-10-13 19:46:30 +02:00
Lukasz Lenart 692e763e43 WW-2891 Replaces deprecated Freemarker's operators 2015-10-13 19:46:30 +02:00
cnenning ed1b8cc7b3 refactored testEntrySet() to be not dependent on order of entries, fixes test executio wiht jdk8 2015-10-13 13:27:57 +02:00
cnenning e0f258f718 made spring-core an optional compile dependency, as that is required for javadoc of jdk8 2015-10-13 13:06:35 +02:00
cnenning 9d5b948190 Revert "Cleaned up project to be buildable with jdk8. Moved StrutsPortletTestCase to src/test where it belongs! Declared dependencies -junit-plugin and junit as scope test, as it should be."
This reverts commit 4bff5a8748.
2015-10-13 12:52:54 +02:00
cnenning 4bff5a8748 Cleaned up project to be buildable with jdk8. Moved StrutsPortletTestCase to src/test where it belongs! Declared dependencies -junit-plugin and junit as scope test, as it should be. 2015-10-13 10:21:43 +02:00
Lukasz Lenart 0dd0f045d2 Drops deprecation interceptor 2015-10-13 08:38:09 +02:00
Lukasz Lenart 9c5c568fc5 Upgrades to master-10 and cleans up plugins 2015-10-13 06:54:12 +02:00
Christoph Nenning da6b03a0a8 WW-4553 Merges #55 which moves creation of interceptors list and iterator to a factory method to allow overriding it 2015-10-12 12:54:51 +02:00
Christoph Nenning a7b840c75a fixed compile error by adding 'new' 2015-10-12 12:47:21 +02:00
Jasper 117f5fbacc Make it easier to extend to override interceptor stack
Just as createAction is split out from init, it would be great to split out creating the interceptor iterator creation so it can be overridden without having to override all of init().  (Needed this to always prepend an i18n interceptor to all stacks).
2015-10-09 07:41:05 -04:00
Jasper 79a2022b55 Make it easier to extend to use own ActionInvocation
I needed to extend DefaultActionProxyFactory to return my own ActionInvocation, but I had to override all of createActionProxy() to do so.  Seems like a safe change to break out the ActionInvocation creation to make it easier to just override that piece.
2015-10-09 07:34:31 -04:00
cnenning 41dd070eda WW-4551 merges #52 which adds support for rfc3339 date-only date-format 2015-10-09 11:13:25 +02:00
cnenning 366a0b4a58 added tests for rfc3339 date formats 2015-10-09 11:11:34 +02:00
Andrea Ligios d95fc63992 Added support for RFC3339 with date part only
https://issues.apache.org/jira/browse/WW-4551
2015-10-07 16:17:38 +02:00
Lukasz Lenart a5812bf0b6 Fixes expected test result 2015-10-06 15:36:28 +02:00
Lukasz Lenart f0797388bc Fixes failing tests under JDK8 2015-10-06 15:27:04 +02:00
Lukasz Lenart e28e7f69b9 Includes global-allowed-methods instead of overriding them 2015-10-06 13:27:53 +02:00
Lukasz Lenart 221ef32e37 WW-4402 Updates struts-annotations to support JDK8 2015-10-06 08:21:28 +02:00
Johannes Geppert a6241552b6 WW-4540 Enable Strict DMI be default
- Add to global allowed methods to rest showcase configuration
2015-10-05 21:25:06 +02:00
Johannes Geppert ecde389ee6 WW-4540 Enable Strict DMI be default
- Extend global allowed methods in rest plugin with rest plugin specific default methods
2015-10-05 20:15:51 +02:00
Johannes Geppert 50ae859dab WW-4505 Add plugin to support bean validation
- Remove pre configured interceptor stack to use bean validation with rest plugin because this causes conflicts when rest plugin is not used
2015-10-05 20:09:55 +02:00
Andrea Ligios 9ad9404bfa Changing an erroneous variable's name
RFC 3399 does not exist. RFC 3339 is the one used here: https://tools.ietf.org/html/rfc3339
2015-10-05 11:09:48 +02:00
Andrea Ligios 9638d7adaa Correcting typos...
https://issues.apache.org/jira/browse/WW-4550?focusedCommentId=14934912&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14934912
2015-09-29 16:59:30 +02:00
Lukasz Lenart 6fbdab1848 [maven-release-plugin] prepare for next development iteration 2015-09-28 21:15:50 +02:00
245 changed files with 7092 additions and 1788 deletions
-1
View File
@@ -54,7 +54,6 @@ plugins/portlet-tiles/target
plugins/portlet/target
plugins/sitegraph/target
plugins/sitemesh/target
plugins/tiles3/target
plugins/javatemplates/target
bundles/demo/target
plugins/rest/target
+1 -1
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-apps</artifactId>
<packaging>pom</packaging>
+1 -5
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-portlet</artifactId>
@@ -133,10 +133,6 @@
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
<!-- Logging -->
<dependency>
@@ -1,4 +1,4 @@
Hello World from velocity!
<p />
#surl ("id=url" "action=index")
#surl ("var=url" "action=index")
<a href="${url}">Back to the front page</a>
@@ -1,5 +1,5 @@
<hr/>
<@s.url id="home" action="index" namespace="/view" />
<@s.url var="home" action="index" namespace="/view" />
<div>
<p>Powered by Struts2 Portlet/Tiles Plugin | <@s.a href="${home}">Back to front page</@s.a></p>
</div>
@@ -1,6 +1,6 @@
<%@ taglib prefix="s" uri="/struts-tags" %>
<hr/>
<s:url id="home" action="index" namespace="/view" />
<s:url var="home" action="index" namespace="/view" />
<div>
<p>Powered by Struts2 Portlet/Tiles Plugin | <s:a href="%{home}">Back to front page</s:a></p>
</div>
+1 -1
View File
@@ -5,7 +5,7 @@
<h4>Features</h4>
<ul>
<li>
<s:url id="freemarker" namespace="/tiles" action="processTilesFreemarkerExample" method="input" />
<s:url var="freemarker" namespace="/tiles" action="processTilesFreemarkerExample" method="input" />
<s:a href="%{freemarker}">View FreeMarker Example</s:a>
</li>
</ul>
+2 -2
View File
@@ -26,12 +26,12 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-rest-showcase</artifactId>
<packaging>war</packaging>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
<name>Struts 2 Rest Showcase Webapp</name>
<description>Struts 2 Rest Showcase Example</description>
@@ -21,16 +21,20 @@
* under the License.
*/
-->
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<!-- Overwrite Convention -->
<constant name="struts.convention.action.suffix" value="Controller"/>
<constant name="struts.convention.action.mapAllMatches" value="true"/>
<constant name="struts.convention.default.parent.package" value="rest-default"/>
<constant name="struts.convention.default.parent.package" value="rest-showcase"/>
<constant name="struts.convention.package.locators" value="example"/>
<package name="rest-showcase" extends="rest-default">
<global-allowed-methods>index,show,create,update,destroy,deleteConfirm,edit,editNew</global-allowed-methods>
</package>
</struts>
+1 -6
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-apps</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-showcase</artifactId>
@@ -171,11 +171,6 @@
<artifactId>hibernate-validator</artifactId>
<version>5.1.3.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
<!-- The Servlet API mocks in Spring Framework 4.x only supports Servlet 3.0 and higher.
This is only necessary in tests-->
@@ -0,0 +1,152 @@
package org.apache.struts2.showcase.validation;
import java.sql.Date;
import com.opensymphony.xwork2.validator.annotations.DateRangeFieldValidator;
import com.opensymphony.xwork2.validator.annotations.EmailValidator;
import com.opensymphony.xwork2.validator.annotations.FieldExpressionValidator;
import com.opensymphony.xwork2.validator.annotations.IntRangeFieldValidator;
import com.opensymphony.xwork2.validator.annotations.RegexFieldValidator;
import com.opensymphony.xwork2.validator.annotations.RequiredFieldValidator;
import com.opensymphony.xwork2.validator.annotations.RequiredStringValidator;
import com.opensymphony.xwork2.validator.annotations.StringLengthFieldValidator;
import com.opensymphony.xwork2.validator.annotations.UrlValidator;
/**
* <!-- START SNIPPET: ajaxFormSubmit -->
*/
/**
* Example Action that shows how forms can be validated and submitted via AJAX
* only. Form-submit-and-page-reload functionality of browsers is not used for
* this action.
* <p>Some things to note:
* <ul>
* <li>Depends on <code>json-plugin</code>.</li>
* <li>Requires <code>jsonValidationInterceptor</code> to be on stack.</li>
* <li>Uses result type <code>jsonActionRedirect</code>.</li>
* <li>Uses http parameters <code>struts.enableJSONValidation=true</code> and <code>struts.validateOnly=false</code>.</li>
* <li>Uses a customized theme to make sure html elements required as error containers are always present and easily selectable in JS.</li>
* <li>Uses some custom JS code depending on jQuery to issue AJAX request and to render errors in html.</li>
* <li>Shows visual feedback while waiting for AJAX response.</li>
* </ul>
* </p>
*
*/
public class AjaxFormSubmitAction extends AbstractValidationActionSupport {
private String requiredValidatorField = null;
private String requiredStringValidatorField = null;
private Integer integerValidatorField = null;
private Date dateValidatorField = null;
private String emailValidatorField = null;
private String urlValidatorField = null;
private String stringLengthValidatorField = null;
private String regexValidatorField = null;
private String fieldExpressionValidatorField = null;
@Override
public void validate() {
if (hasFieldErrors()) {
addActionError("Errors present!");
}
}
public Date getDateValidatorField() {
return dateValidatorField;
}
@DateRangeFieldValidator(
min="01/01/1990",
max="01/01/2000",
message="must be a min 01-01-1990 max 01-01-2000 if supplied")
public void setDateValidatorField(Date dateValidatorField) {
this.dateValidatorField = dateValidatorField;
}
public String getEmailValidatorField() {
return emailValidatorField;
}
@EmailValidator(message="must be a valid email if supplied")
public void setEmailValidatorField(String emailValidatorField) {
this.emailValidatorField = emailValidatorField;
}
public Integer getIntegerValidatorField() {
return integerValidatorField;
}
@IntRangeFieldValidator(min="1", max="10", message="must be integer min 1 max 10 if supplied")
public void setIntegerValidatorField(Integer integerValidatorField) {
this.integerValidatorField = integerValidatorField;
}
public String getRegexValidatorField() {
return regexValidatorField;
}
@RegexFieldValidator(
regex="[^<>]+",
message="regexValidatorField must match a regexp (.*\\.txt) if specified")
public void setRegexValidatorField(String regexValidatorField) {
this.regexValidatorField = regexValidatorField;
}
public String getRequiredStringValidatorField() {
return requiredStringValidatorField;
}
@RequiredStringValidator(trim=true, message="required and must be string")
public void setRequiredStringValidatorField(String requiredStringValidatorField) {
this.requiredStringValidatorField = requiredStringValidatorField;
}
public String getRequiredValidatorField() {
return requiredValidatorField;
}
@RequiredFieldValidator(message="required")
public void setRequiredValidatorField(String requiredValidatorField) {
this.requiredValidatorField = requiredValidatorField;
}
public String getStringLengthValidatorField() {
return stringLengthValidatorField;
}
@StringLengthFieldValidator(
minLength="2",
maxLength="4",
trim=true,
message="must be a String of a specific greater than 1 less than 5 if specified")
public void setStringLengthValidatorField(String stringLengthValidatorField) {
this.stringLengthValidatorField = stringLengthValidatorField;
}
public String getFieldExpressionValidatorField() {
return fieldExpressionValidatorField;
}
@FieldExpressionValidator(
expression = "(fieldExpressionValidatorField == requiredValidatorField)",
message = "must be the same as the Required Validator Field if specified")
public void setFieldExpressionValidatorField(
String fieldExpressionValidatorField) {
this.fieldExpressionValidatorField = fieldExpressionValidatorField;
}
public String getUrlValidatorField() {
return urlValidatorField;
}
@UrlValidator(message="must be a valid url if supplied")
public void setUrlValidatorField(String urlValidatorField) {
this.urlValidatorField = urlValidatorField;
}
}
/**
* <!-- END SNIPPET: ajaxFormSubmit -->
*/
@@ -0,0 +1,7 @@
package org.apache.struts2.showcase.validation;
public class AjaxFormSubmitSuccessAction {
public String execute() {
return "success";
}
}
@@ -37,6 +37,19 @@
<result name="input">quiz-ajax.jsp</result>
<result>quiz-success.jsp</result>
</action>
<!-- =========================================== -->
<!-- === ajax form submit === -->
<!-- =========================================== -->
<action name="ajaxFormSubmit" class="org.apache.struts2.showcase.validation.AjaxFormSubmitAction">
<interceptor-ref name="jsonValidationWorkflowStack" />
<result name="input">/WEB-INF/validation/ajaxFormSubmit.jsp</result>
<result type="jsonActionRedirect">ajaxFormSubmitSuccess</result>
</action>
<action name="ajaxFormSubmitSuccess" class="org.apache.struts2.showcase.validation.AjaxFormSubmitSuccessAction">
<result>/WEB-INF/validation/ajaxFormSubmitSuccess.jsp</result>
</action>
</package>
@@ -0,0 +1,46 @@
<#--
/*
* $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.
*/
-->
<#--
Make sure element is always present. To be filled later via JS.
-->
<ul<#rt/>
<#if parameters.id??>
id="${parameters.id?html}"<#rt/>
</#if>
<#if parameters.cssClass??>
class="${parameters.cssClass?html}"<#rt/>
<#else>
class="errorMessage"<#rt/>
</#if>
<#if parameters.cssStyle??>
style="${parameters.cssStyle?html}"<#rt/>
</#if>
>
<#if (actionErrors?? && actionErrors?size > 0)>
<#list actionErrors as error>
<#if error??>
<li><span><#if parameters.escape>${error!?html}<#else>${error!}</#if></span><#rt/></li><#rt/>
</#if>
</#list>
</#if>
</ul>
@@ -0,0 +1,39 @@
<#--
/*
* $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.
*/
-->
${parameters.after!}<#t/>
</td><#lt/>
</tr>
<#if (parameters.errorposition!"top") == 'bottom'>
<#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/>
<#if hasFieldErrors>
<tr errorFor="${parameters.id}">
<td class="tdErrorMessage" colspan="2"><#rt/>
<#if hasFieldErrors>
<#list fieldErrors[parameters.name] as error>
<div class="errorMessage">${error?html}</div><#t/>
</#list>
</#if>
</td><#lt/>
</tr>
</#if>
</#if>
@@ -0,0 +1,80 @@
<#--
/*
* $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.
*/
-->
<#--
Always include elements to show errors. They may be filled later via AJAX.
-->
<#assign hasFieldErrors = parameters.name?? && fieldErrors?? && fieldErrors[parameters.name]??/>
<#if (parameters.errorposition!"top") == 'top'>
<tr errorFor="${parameters.id}">
<td class="tdErrorMessage" colspan="2" data-error-for-fieldname="${parameters.name}"><#rt/>
<#if hasFieldErrors>
<#list fieldErrors[parameters.name] as error>
<div class="errorMessage">${error?html}</div><#t/>
</#list>
</#if>
</td><#lt/>
</tr>
</#if>
<#if !parameters.labelposition?? && (parameters.form.labelposition)??>
<#assign labelpos = parameters.form.labelposition/>
<#elseif parameters.labelposition??>
<#assign labelpos = parameters.labelposition/>
</#if>
<#--
if the label position is top,
then give the label it's own row in the table
-->
<tr>
<#if (labelpos!"") == 'top'>
<td class="tdLabelTop" colspan="2"><#rt/>
<#else>
<td class="tdLabel"><#rt/>
</#if>
<#if parameters.label??>
<label <#t/>
<#if parameters.id??>
for="${parameters.id?html}" <#t/>
</#if>
<#if hasFieldErrors>
class="errorLabel"<#t/>
<#else>
class="label"<#t/>
</#if>
><#t/>
<#if parameters.required!false && parameters.requiredPosition!"right" != 'right'>
<span class="required">*</span><#t/>
</#if>
${parameters.label?html}<#t/>
<#if parameters.required!false && parameters.requiredPosition!"right" == 'right'>
<span class="required">*</span><#t/>
</#if>
${parameters.labelseparator!":"?html}<#t/>
<#include "/${parameters.templateDir}/${parameters.expandTheme}/tooltip.ftl" />
</label><#t/>
</#if>
</td><#lt/>
<#-- add the extra row -->
<#if (labelpos!"") == 'top'>
</tr>
<tr>
</#if>
@@ -0,0 +1,21 @@
#
# $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.
#
parent = xhtml
@@ -188,6 +188,7 @@
<s:url var="clientSideValidationUrl" action="clientSideValidationExample" namespace="/validation"/>
<s:url var="storeMessageAcrossRequestExample" namespace="/validation" action="storeErrorsAcrossRequestExample"/>
<s:url var="beanValidationUrl" action="bean-validation" namespace="/bean-validation"/>
<s:url var="ajaxFormSubmitUrl" action="ajaxFormSubmit" namespace="/validation" method="input"/>
<li><s:a href="%{beanValidationUrl}">Bean Validation</s:a></li>
<li><s:a href="%{fieldValidatorUrl}">Field Validators</s:a></li>
<li><s:a href="%{clientSideValidationUrl}">Field Validators with client-side JavaScript</s:a></li>
@@ -197,6 +198,7 @@
<li><s:a href="%{quizClient}">Validation (client)</s:a></li>
<li><s:a href="%{quizClientCss}">Validation (client using css_xhtml theme)</s:a></li>
<li><s:a href="%{visitorValidatorUrl}">Visitor Validator</s:a></li>
<li><s:a href="%{ajaxFormSubmitUrl}">AJAX Form Submit</s:a></li>
</ul>
</li>
<li class="dropdown">
@@ -2,11 +2,11 @@
<html>
<head>
<s:if test="currentSkill!=null">
<s:set name="submitType" value="'update'"/>
<s:text id="title" name="item.edit"><s:param><s:text name="skill"/></s:param></s:text>
<s:set var="submitType" value="'update'"/>
<s:text var="title" name="item.edit"><s:param><s:text name="skill"/></s:param></s:text>
</s:if>
<s:else>
<s:set name="submitType" value="'create'"/>
<s:set var="submitType" value="'create'"/>
<s:text var="title" name="item.create"><s:param><s:text name="skill"/></s:param></s:text>
</s:else>
<title>Struts2 Showcase - CRUD Example - <s:property value="#title"/></title>
@@ -13,7 +13,7 @@
<table>
<tr>
<td>
<@s.url id="url" value="/hangman/images/hangman.png" />
<@s.url var="url" value="/hangman/images/hangman.png" />
<img alt="Hangman" src="<@s.property value="%{#url}" />"
width="197" height="50" border="0"/>
</td>
@@ -21,15 +21,15 @@
<#-- Guesses Left -->
<div id="updateGuessLeftDiv">
<#if (hangman.guessLeft() >= 0)>
<@s.set name="guessLeftImageName" value="%{'Chalkboard_'+hangman.guessLeft()+'.png'}" />
<@s.url id="url" value="%{'/hangman/images/'+#guessLeftImageName}" />
<@s.set var="guessLeftImageName" value="%{'Chalkboard_'+hangman.guessLeft()+'.png'}" />
<@s.url var="url" value="%{'/hangman/images/'+#guessLeftImageName}" />
<img alt="No. Guesses Left"
src="<@s.property value="%{#url}"/>" width="20" height="20" border="0" />
</#if>
</div>
</td>
<td>
<@s.url id="url" value="/hangman/images/guesses-left.png" />
<@s.url var="url" value="/hangman/images/guesses-left.png" />
<img alt="Guesses Left"
src="<@s.property value="%{#url}" />" width="164" height="11" border="0"/>
</td>
@@ -39,8 +39,8 @@
<td align="left">
<#-- Display Scaffold -->
<div id="updateScaffoldDiv">
<@s.set name="scaffoldImageName" value="%{'scaffold_'+hangman.guessLeft()+'.png'}" />
<@s.url id="url" value="%{'/hangman/images/'+#scaffoldImageName}" />
<@s.set var="scaffoldImageName" value="%{'scaffold_'+hangman.guessLeft()+'.png'}" />
<@s.url var="url" value="%{'/hangman/images/'+#scaffoldImageName}" />
<img src="<@s.property value="%{#url}" />" border="0"/>
</div>
</td>
@@ -49,7 +49,7 @@
<tr>
<td width="160">
<p align="right">
<@s.url id="url" value="/hangman/images/guess.png" />
<@s.url var="url" value="/hangman/images/guess.png" />
<img alt="Current Guess" src="<@s.property value="%{#url}" />"
align="MIDDLE" width="127" height="20" border="0"/></p>
</td>
@@ -57,20 +57,20 @@
<#-- Display Vacab -->
<div id="updateVocabDiv">
<#if hangman.gameEnded()>
<@s.iterator id="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<@s.url id="url" value="%{'/hangman/images/Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<@s.iterator var="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<@s.url var="url" value="%{'/hangman/images/Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<img height="36" alt="<@s.property value="%{#currentCharacter}" />"
src="<@s.property value="%{#url}" />" width="36" border="0" />
</@s.iterator>
<#else>
<@s.iterator id="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<@s.iterator var="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<#if hangman.characterGuessedBefore(currentCharacter)>
<@s.set name="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<@s.url id="url" value="%{'/hangman/images/'+#chalkboardImageName}" />
<@s.set var="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<@s.url var="url" value="%{'/hangman/images/'+#chalkboardImageName}" />
<img height="36" alt="<@s.property value="%{#currentCharacter}" />"
src="<@s.property value="%{#url}" />" width="36" border="0"/>
<#else>
<@s.url id="url" value="/hangman/images/Chalkboard_underscroll.png" />
<@s.url var="url" value="/hangman/images/Chalkboard_underscroll.png" />
<img height="36" alt="_"
src="<@s.property value="%{#url}" />" width="36" border="0"/>
</#if>
@@ -82,7 +82,7 @@
<tr>
<td valign="top">
<p align="right">
<@s.url id="url" value="/hangman/images/choose.png" />
<@s.url var="url" value="/hangman/images/choose.png" />
<img alt="Choose" src="<@s.property value="%{#url}" />"
height="20" width="151" border="0"/>
</p>
@@ -92,13 +92,13 @@
<#-- Show Characters Available -->
<div id="updateCharacterAvailableDiv">
<#if hangman.gameEnded()>
<@s.set name="winImageName" value="%{'you-win.png'}" />
<@s.set name="looseImageName" value="%{'you-lose.png'}" />
<@s.set name="startImageName" value="%{'start.png'}" />
<@s.url id="winImageUrl" value="%{'/hangman/images/'+#winImageName}" />
<@s.url id="looseImageUrl" value="%{'/hangman/images/'+#looseImageName}" />
<@s.url id="startImageUrl" value="%{'/hangman/images/'+#startImageName}" />
<@s.url id="startHref" action="hangmanNonAjax" namespace="/hangman" />
<@s.set var="winImageName" value="%{'you-win.png'}" />
<@s.set var="looseImageName" value="%{'you-lose.png'}" />
<@s.set var="startImageName" value="%{'start.png'}" />
<@s.url var="winImageUrl" value="%{'/hangman/images/'+#winImageName}" />
<@s.url var="looseImageUrl" value="%{'/hangman/images/'+#looseImageName}" />
<@s.url var="startImageUrl" value="%{'/hangman/images/'+#startImageName}" />
<@s.url var="startHref" action="hangmanNonAjax" namespace="/hangman" />
<#if hangman.isWin()>
<img src="<@s.property value="%{#winImageUrl}" />" width="341" height="44" />
@@ -109,11 +109,11 @@
<img src="<@s.property value="%{#startImageUrl}" />" width="250" height="43" />
</@s.a>
<#else>
<@s.iterator id="currentCharacter" value="%{hangman.charactersAvailable}" status="stat">
<@s.set name="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter+'.png'}" />
<@s.url id="chalkboardImageUrl" value="%{'/hangman/images/'+#chalkboardImageName}" />
<@s.url id="spacerUrl" value="/hangman/images/letter-spacer.png" />
<@s.url id="url" action="guessCharacterNonAjax" namespace="/hangman">
<@s.iterator var="currentCharacter" value="%{hangman.charactersAvailable}" status="stat">
<@s.set var="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter+'.png'}" />
<@s.url var="chalkboardImageUrl" value="%{'/hangman/images/'+#chalkboardImageName}" />
<@s.url var="spacerUrl" value="/hangman/images/letter-spacer.png" />
<@s.url var="url" action="guessCharacterNonAjax" namespace="/hangman">
<@s.param name="character" value="%{#currentCharacter}" />
</@s.url>
@@ -1,11 +1,11 @@
<#if hangman.gameEnded()>
<@s.set name="winImageName" value="%{'you-win.png'}" />
<@s.set name="looseImageName" value="%{'you-lose.png'}" />
<@s.set name="startImageName" value="%{'start.png'}" />
<@s.url id="winImageUrl" value="%{'/hangman/images/'+#winImageName}" />
<@s.url id="looseImageUrl" value="%{'/hangman/images/'+#looseImageName}" />
<@s.url id="startImageUrl" value="%{'/hangman/images/'+#startImageName}" />
<@s.url id="startHref" action="hangmanAjax" namespace="/hangman" />
<@s.set var="winImageName" value="%{'you-win.png'}" />
<@s.set var="looseImageName" value="%{'you-lose.png'}" />
<@s.set var="startImageName" value="%{'start.png'}" />
<@s.url var="winImageUrl" value="%{'/hangman/images/'+#winImageName}" />
<@s.url var="looseImageUrl" value="%{'/hangman/images/'+#looseImageName}" />
<@s.url var="startImageUrl" value="%{'/hangman/images/'+#startImageName}" />
<@s.url var="startHref" action="hangmanAjax" namespace="/hangman" />
<#if hangman.isWin()>
<img src="<@s.property value="%{#winImageUrl}" />" width="341" height="44" />
@@ -16,11 +16,11 @@
<img src="<@s.property value="%{#startImageUrl}" />" width="250" height="43" />
</@s.a>
<#else>
<@s.iterator id="currentCharacter" value="%{hangman.charactersAvailable}" status="stat">
<@s.set name="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter+'.png'}" />
<@s.url id="chalkboardImageUrl" value="%{'/hangman/images/'+#chalkboardImageName}" />
<@s.url id="spacerUrl" value="/hangman/images/letter-spacer.png" />
<@s.url id="blankUrl" value="ajax/blank.action" includeContext="false" />
<@s.iterator var="currentCharacter" value="%{hangman.charactersAvailable}" status="stat">
<@s.set var="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter+'.png'}" />
<@s.url var="chalkboardImageUrl" value="%{'/hangman/images/'+#chalkboardImageName}" />
<@s.url var="spacerUrl" value="/hangman/images/letter-spacer.png" />
<@s.url var="blankUrl" value="ajax/blank.action" includeContext="false" />
<@sx.a id="%{#currentCharacter}"
afterNotifyTopics="topicGuessMade"
@@ -1,6 +1,6 @@
<#if (hangman.guessLeft() >= 0)>
<@s.set name="guessLeftImageName" value="%{'Chalkboard_'+hangman.guessLeft()+'.png'}" />
<@s.url id="url" value="%{'/hangman/images/'+#guessLeftImageName}" />
<@s.set var="guessLeftImageName" value="%{'Chalkboard_'+hangman.guessLeft()+'.png'}" />
<@s.url var="url" value="%{'/hangman/images/'+#guessLeftImageName}" />
<img alt="No. Guesses Left"
src="<@s.property value="%{#url}"/>" width="20" height="20" border="0" />
</#if>
@@ -1,3 +1,3 @@
<@s.set name="scaffoldImageName" value="%{'scaffold_'+hangman.guessLeft()+'.png'}" />
<@s.url id="url" value="%{'/hangman/images/'+#scaffoldImageName}" />
<@s.set var="scaffoldImageName" value="%{'scaffold_'+hangman.guessLeft()+'.png'}" />
<@s.url var="url" value="%{'/hangman/images/'+#scaffoldImageName}" />
<img src="<@s.property value="%{#url}" />" border="0"/>
@@ -1,18 +1,18 @@
<#if hangman.gameEnded()>
<@s.iterator id="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<@s.url id="url" value="%{'/hangman/images/Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<@s.iterator var="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<@s.url var="url" value="%{'/hangman/images/Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<img height="36" alt="<@s.property value="%{#currentCharacter}" />"
src="<@s.property value="%{#url}" />" width="36" border="0" />
</@s.iterator>
<#else>
<@s.iterator id="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<@s.iterator var="currentCharacter" value="%{hangman.vocab.inCharacters()}" stat="stat">
<#if hangman.characterGuessedBefore(currentCharacter)>
<@s.set name="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<@s.url id="url" value="%{'/hangman/images/'+#chalkboardImageName}" />
<@s.set var="chalkboardImageName" value="%{'Chalkboard_'+#currentCharacter.toString()+'.png'}" />
<@s.url var="url" value="%{'/hangman/images/'+#chalkboardImageName}" />
<img height="36" alt="<@s.property value="%{#currentCharacter}" />"
src="<@s.property value="%{#url}" />" width="36" border="0"/>
<#else>
<@s.url id="url" value="/hangman/images/Chalkboard_underscroll.png" />
<@s.url var="url" value="/hangman/images/Chalkboard_underscroll.png" />
<img height="36" alt="_"
src="<@s.property value="%{#url}" />" width="36" border="0"/>
</#if>
@@ -12,11 +12,11 @@
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs nav-stacked">
<s:url id="listpeopleurl" action="list-people" namespace="/person" />
<s:url var="listpeopleurl" action="list-people" namespace="/person" />
<li><s:a href="%{listpeopleurl}">List all people</s:a> </li>
<s:url id="editpersonurl" action="edit-person" namespace="/person" />
<s:url var="editpersonurl" action="edit-person" namespace="/person" />
<li class="active"><s:a href="%{editpersonurl}">Edit people</s:a></li>
<s:url id="newpersonurl" action="new-person" namespace="/person" method="input"/>
<s:url var="newpersonurl" action="new-person" namespace="/person" method="input"/>
<li><s:a href="%{newpersonurl}">Create a new person</s:a></li>
</ul>
</div>
@@ -11,11 +11,11 @@
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs nav-stacked">
<@s.url id="listpeopleurl" action="list-people" />
<@s.url var="listpeopleurl" action="list-people" />
<li class="active"><@s.a href="%{listpeopleurl}">List all people</@s.a></li>
<@s.url id="editpersonurl" action="edit-person" />
<@s.url var="editpersonurl" action="edit-person" />
<li><@s.a href="%{editpersonurl}">Edit people</@s.a></li>
<@s.url id="newpersonurl" action="new-person" method="input" />
<@s.url var="newpersonurl" action="new-person" method="input" />
<li><@s.a href="%{newpersonurl}">Create a new person</@s.a></li>
</ul>
</div>
@@ -11,11 +11,11 @@
<div class="row">
<div class="col-md-3">
<ul class="nav nav-tabs nav-stacked">
<@s.url id="listpeopleurl" action="list-people" />
<@s.url var="listpeopleurl" action="list-people" />
<li><@s.a href="%{listpeopleurl}">List all people</@s.a></li>
<@s.url id="editpersonurl" action="edit-person" />
<@s.url var="editpersonurl" action="edit-person" />
<li><@s.a href="%{editpersonurl}">Edit people</@s.a></li>
<@s.url id="newpersonurl" action="new-person" />
<@s.url var="newpersonurl" action="new-person" />
<li class="active"><@s.a href="%{newpersonurl}">Create a new person</@s.a></li>
</ul>
</div>
@@ -13,7 +13,7 @@
<p>You have come to this page because you used an <strong>action</strong> prefix.<p/>
<p>The text you've entered is ${text?default('')?html}<p/>
<p>The text you've entered is ${text!''?html}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
@@ -29,7 +29,7 @@
redirect (a new request is issue from the client), the text entered will be lost.<p/>
<@s.url id="url" action="viewSource" namespace="/tags/non-ui/actionPrefix" />
<@s.url var="url" action="viewSource" namespace="/tags/non-ui/actionPrefix" />
The JSP code can be read <@s.a href="%{#url}">here</@s.a>.
@@ -13,7 +13,7 @@
<p>You have come to this page because you used an <strong>method</strong> prefix.<p/>
<p>The text you've enter is ${text?default('')?html}<p/>
<p>The text you've enter is ${text!''?html}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
@@ -16,7 +16,7 @@
<p>Because this is a <strong>redirect-action</strong>, the text will be lost, due to a redirection
implies a new request being issued from the client.<p/>
The text you've enter is ${text?default('')?html}<p/>
The text you've enter is ${text!''?html}<p/>
<@s.a href="javascript:history.back();" cssClass="btn btn-info"><i class="icon icon-arrow-left"></i> Back</@s.a>
</div>
@@ -16,7 +16,7 @@
<s:generator var="iterator1" separator="," val="%{iteratorValue1}" />
<s:generator var="iterator2" separator="," val="%{iteratorValue2}" />
<s:append id="appendedIterator">
<s:append var="appendedIterator">
<s:param value="%{#attr.iterator1}" />
<s:param value="%{#attr.iterator2}" />
</s:append>
@@ -0,0 +1,177 @@
<%@taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Struts2 Showcase - Validation - AJAX Form Submit</title>
<s:head theme="xhtml"/>
<style type="text/css">
/* see comment of script element below! */
.ajaxVisualFeedback {
width: 16px;
height: 16px;
background-image: url('../images/indicator.gif');
background-repeat: no-repeat;
float: right;
}
</style>
</head>
<body>
<div class="page-header">
<h1>AJAX Form Submit</h1>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<!-- START SNIPPET: ajaxFormSubmit -->
<h3>Action Errors Will Appear Here</h3>
<s:actionerror theme="ajaxErrorContainers"/>
<hr/>
<s:form method="POST" theme="xhtml">
<s:textfield label="Required Validator Field" name="requiredValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="Required String Validator Field" name="requiredStringValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="Integer Validator Field" name="integerValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="Date Validator Field" name="dateValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="Email Validator Field" name="emailValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="URL Validator Field" name="urlValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="String Length Validator Field" name="stringLengthValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="Regex Validator Field" name="regexValidatorField" theme="ajaxErrorContainers"/>
<s:textfield label="Field Expression Validator Field" name="fieldExpressionValidatorField" theme="ajaxErrorContainers"/>
<s:submit label="Submit" cssClass="btn btn-primary"/>
</s:form>
<!-- END SNIPPET: ajaxFormSubmit -->
</div>
</div>
</div>
<script type="text/javascript">
/********************************************************************
* JS just used on this page.
* Usually this would be placed in a JS file
* but as this showcase app is already hard to follow
* I place it here so it is easier to find.
*
* note: this requires jQuery.
*******************************************************************/
/**
* Validates given form per AJAX. To be called as onSubmit handler.
*
* @param event onSubmit event
*/
function ajaxFormValidation(event) {
event.preventDefault();
_removeValidationErrors();
var _form = $(event.target);
var _formData = _form.serialize(true);
// prepare visual feedback
// you may want to use other elements here
var originalButton = _form.find('.btn-primary');
// note: jQuery returns an array-like object
if (originalButton && originalButton.length && originalButton.length > 0) {
originalButton.hide();
var feedbackElement = $('<div class="ajaxVisualFeedback"></div>').insertAfter(originalButton);
var restoreFunction = function() {
originalButton.show();
feedbackElement.remove();
}
}
var options = {
data: 'struts.enableJSONValidation=true&struts.validateOnly=false&' + _formData,
async: true,
processData: false,
type: 'POST',
success: function (response, statusText, xhr) {
if (response.location) {
// no validation errors
// action has been executed and sent a redirect URL wrapped as JSON
// cannot use a normal http-redirect (status-code 3xx) as this would be followed by browsers and would not be available here
// follow JSON-redirect
window.location.href = response.location;
} else {
if (restoreFunction) {
restoreFunction();
}
_handleValidationResult(_form, response);
}
},
error: function(xhr, textStatus, errorThrown) {
if (restoreFunction) {
restoreFunction();
}
// show user an error message
_handleValidationResult(_form, {errors: ['Network or server error!']})
}
}
// send request, after delay to make sure everybody notices the visual feedback :)
window.setTimeout(function() {
var url = _form[0].action;
jQuery.ajax(url, options);
}, 1000);
}
/**
* Removes validation errors from HTML DOM.
*/
function _removeValidationErrors() {
// action errors
// you might want to use a custom ID here
$('ul.errorMessage li').remove();
// field errors
$('div.errorMessage').remove();
}
/**
* Incorporates validation errors in HTML DOM.
*
* @param form Form containing errors.
* @param errors Errors from server.
* @returns {Boolean} True if form can be submitted.
*/
function _handleValidationResult(form, errors) {
// action errors
if (errors.errors) {
// you might want to use a custom ID here
var errorContainer = $('ul.errorMessage');
$.each(errors.errors, function(index, errorMsg) {
var li = $('<li><span></span></li>');
li.text(errorMsg);
errorContainer.append(li);
});
}
// field errors
if (errors.fieldErrors) {
$.each(errors.fieldErrors, function(fieldName, errorMsg) {
var td = $('td[data-error-for-fieldname="' + fieldName + '"]');
if (td) {
var div = $('<div class="errorMessage"></div>');
div.text(errorMsg);
td.append(div);
}
});
}
}
// register onSubmit handler
$(window).bind('load', function() {
$('form').bind('submit', ajaxFormValidation);
});
</script>
</body>
</html>
@@ -0,0 +1,17 @@
<%@taglib prefix="s" uri="/struts-tags" %>
<html>
<head>
<title>Struts2 Showcase - Validation - Success Field Validators Example</title>
<s:head/>
</head>
<body>
<div class="page-header">
<h1>Success !</h1>
</div>
<div class="container-fluid">
Form has been submitted.
</div>
</body>
</html>
+1 -1
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-archetypes</artifactId>
@@ -2,7 +2,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -11,13 +11,13 @@
<h3>Languages</h3>
<ul>
<li>
<s:url id="url" action="HelloWorld">
<s:url var="url" action="HelloWorld">
<s:param name="request_locale">en</s:param>
</s:url>
<s:a href="%{url}">English</s:a>
</li>
<li>
<s:url id="url" action="HelloWorld">
<s:url var="url" action="HelloWorld">
<s:param name="request_locale">es</s:param>
</s:url>
<s:a href="%{url}">Espanol</s:a>
@@ -2,7 +2,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -11,13 +11,13 @@
<h3>Languages</h3>
<ul>
<li>
<s:url id="url" action="hello">
<s:url var="url" action="hello">
<s:param name="request_locale">en</s:param>
</s:url>
<s:a href="%{url}">English</s:a>
</li>
<li>
<s:url id="url" action="hello">
<s:url var="url" action="hello">
<s:param name="request_locale">es</s:param>
</s:url>
<s:a href="%{url}">Espanol</s:a>
@@ -2,7 +2,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -8,7 +8,7 @@ This a simple table showing the database data:
<th>Name</th>
<th>Value</th>
</tr>
<s:iterator id="row" value="%{data}">
<s:iterator var="row" value="%{data}">
<tr>
<td><s:property value="%{STR_COL}" /></td>
<td><s:property value="%{NUM_COL}" /></td>
+1 -1
View File
@@ -2,7 +2,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -2,7 +2,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-archetypes</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
+7 -6
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-assembly</artifactId>
@@ -111,6 +111,7 @@
<descriptors>
<descriptor>src/main/assembly/all.xml</descriptor>
<descriptor>src/main/assembly/lib.xml</descriptor>
<descriptor>src/main/assembly/min-lib.xml</descriptor>
<descriptor>src/main/assembly/apps.xml</descriptor>
<descriptor>src/main/assembly/src.xml</descriptor>
<descriptor>src/main/assembly/docs.xml</descriptor>
@@ -125,6 +126,11 @@
<dependencies>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-bean-validation-plugin</artifactId>
@@ -213,11 +219,6 @@
<artifactId>struts2-tiles-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-tiles3-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-oval-plugin</artifactId>
+4 -5
View File
@@ -21,7 +21,10 @@
*/
-->
<assembly>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>all</id>
<formats>
<format>zip</format>
@@ -224,10 +227,6 @@
<directory>../plugins/tiles/target/apidocs</directory>
<outputDirectory>docs/struts2-plugins/struts2-tiles-plugin/apidocs</outputDirectory>
</fileSet>
<fileSet>
<directory>../plugins/tiles3/target/apidocs</directory>
<outputDirectory>docs/struts2-plugins/struts2-tiles3-plugin/apidocs</outputDirectory>
</fileSet>
<!-- bundles -->
<fileSet>
+4 -1
View File
@@ -20,7 +20,10 @@
* under the License.
*/
-->
<assembly>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>apps</id>
<formats>
<format>zip</format>
+3 -5
View File
@@ -21,7 +21,9 @@
*/
-->
<assembly>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>docs</id>
<formats>
@@ -148,10 +150,6 @@
<directory>../plugins/tiles/target/apidocs</directory>
<outputDirectory>docs/struts2-plugins/struts2-tiles-plugin/apidocs</outputDirectory>
</fileSet>
<fileSet>
<directory>../plugins/tiles3/target/apidocs</directory>
<outputDirectory>docs/struts2-plugins/struts2-tiles3-plugin/apidocs</outputDirectory>
</fileSet>
</fileSets>
</assembly>
+4 -1
View File
@@ -21,7 +21,10 @@
*/
-->
<assembly>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>lib</id>
<formats>
<format>zip</format>
+49
View File
@@ -0,0 +1,49 @@
<!--
/*
* $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.
*/
-->
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>min-lib</id>
<formats>
<format>zip</format>
</formats>
<dependencySets>
<dependencySet>
<useProjectArtifact>true</useProjectArtifact>
<useTransitiveDependencies>true</useTransitiveDependencies>
<outputDirectory>lib</outputDirectory>
<includes>
<include>org.apache.struts:struts2-core</include>
<include>org.freemarker:freemarker</include>
<include>org.apache.commons:commons-lang3</include>
<include>ognl:ognl</include>
<include>javassist:javassist</include>
<include>commons-fileupload:commons-fileupload</include>
<include>commons-io:commons-io</include>
<include>commons-logging:commons-logging</include>
</includes>
</dependencySet>
</dependencySets>
</assembly>
+4 -1
View File
@@ -21,7 +21,10 @@
*/
-->
<assembly>
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
<id>src</id>
<formats>
<format>zip</format>
+3 -8
View File
@@ -10,7 +10,7 @@
</parent>
<artifactId>struts2-bom</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
<packaging>pom</packaging>
<name>Struts 2 Bill of Materials</name>
@@ -25,7 +25,7 @@
</licenses>
<properties>
<struts-version.version>2.5-BETA2</struts-version.version>
<struts-version.version>2.5-BETA3</struts-version.version>
</properties>
<build>
@@ -173,15 +173,10 @@
<artifactId>struts2-tiles-plugin</artifactId>
<version>${struts-version.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-tiles3-plugin</artifactId>
<version>${struts-version.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<scm>
<tag>STRUTS_2_5_BETA2</tag>
<tag>STRUTS_2_5_BETA3</tag>
</scm>
</project>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-bundles</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-osgi-admin-bundle</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-osgi-bundles</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-osgi-demo-bundle</artifactId>
+1 -1
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-osgi-bundles</artifactId>
+54 -35
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-parent</artifactId>
<version>2.5-BETA2</version>
<version>2.5-BETA3</version>
</parent>
<artifactId>struts2-core</artifactId>
<packaging>jar</packaging>
@@ -40,38 +40,12 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.myfaces.tobago</groupId>
<artifactId>maven-apt-plugin</artifactId>
<version>1.0.15</version>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<A>uri=/struts-tags,tlibVersion=${tlib.version},jspVersion=2.0,shortName=s,displayName=Struts Tags,
outFile=${basedir}/target/classes/META-INF/struts-tags.tld,
description="To make it easier to access dynamic data;
the Apache Struts framework includes a library of custom tags.
The tags interact with the framework's validation and internationalization features;
to ensure that input is correct and output is localized.
The Struts Tags can be used with JSP FreeMarker or Velocity.",
outTemplatesDir=${basedir}/src/site/resources/tags
</A>
<resourceTargetPath>target</resourceTargetPath>
<fork>false</fork>
<force>true</force>
<nocompile>true</nocompile>
<showWarnings>true</showWarnings>
<factory>org.apache.struts.annotations.taglib.apt.TLDAnnotationProcessorFactory</factory>
<target>1.5</target>
<includes>
<include>**/*.java</include>
</includes>
<source>1.7</source>
<target>1.7</target>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>execute</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@@ -133,6 +107,45 @@
</build>
<profiles>
<profile>
<id>build-maven</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgs>
<arg>-Auri=/struts-tags</arg>
<arg>-AtlibVersion=${tlib.version}</arg>
<arg>-AjspVersion=2.0</arg>
<arg>-AshortName=s</arg>
<arg>-AdisplayName=Struts Tags</arg>
<arg>-AoutFile=${basedir}/target/classes/META-INF/struts-tags.tld</arg>
<arg>-Adescription="To make it easier to access dynamic data;
the Apache Struts framework includes a library of custom tags.
The tags interact with the framework's validation and
internationalization features;
to ensure that input is correct and output is localized.
The Struts Tags can be used with JSP FreeMarker or Velocity."</arg>
<arg>-AoutTemplatesDir=${basedir}/src/site/resources/tags</arg>
</compilerArgs>
</configuration>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>alljars</id>
<build>
@@ -324,9 +337,9 @@
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>test</scope>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<optional>true</optional>
</dependency>
<dependency>
@@ -336,6 +349,12 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mockobjects</groupId>
<artifactId>mockobjects-jdk1.3</artifactId>
@@ -390,7 +409,7 @@
</dependency>
<!-- These jars have to be in the compile scope in order to work with apt plugin
(struts-annotations) and javadoc (the rest to process xwork sources). Marking
(struts-annotations) and javadoc (the rest to process xwork sources). Marking
them optional to exclude them from transitive dependency resolution -->
<dependency>
@@ -396,7 +396,11 @@ public class DefaultActionInvocation implements ActionInvocation {
invocationContext = new ActionContext(contextMap);
invocationContext.setName(proxy.getActionName());
// get a new List so we don't get problems with the iterator if someone changes the list
createInterceptors(proxy);
}
protected void createInterceptors(ActionProxy proxy) {
// Get a new List so we don't get problems with the iterator if someone changes the original list
List<InterceptorMapping> interceptorList = new ArrayList<>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();
}
@@ -53,11 +53,15 @@ public class DefaultActionProxyFactory implements ActionProxyFactory {
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
ActionInvocation inv = new DefaultActionInvocation(extraContext, true);
ActionInvocation inv = createActionInvocation(extraContext, true);
container.inject(inv);
return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);
}
protected ActionInvocation createActionInvocation(Map<String, Object> extraContext, boolean pushAction) {
return new DefaultActionInvocation(extraContext, pushAction);
}
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, boolean executeResult, boolean cleanupContext) {
return createActionProxy(inv, namespace, actionName, null, executeResult, cleanupContext);
@@ -21,7 +21,7 @@ import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig;
import com.opensymphony.xwork2.config.entities.ResultConfig;
import com.opensymphony.xwork2.util.PatternMatcher;
import com.opensymphony.xwork2.util.WildcardHelper;
import org.apache.commons.lang3.StringUtils;
import java.io.Serializable;
import java.util.ArrayList;
@@ -77,7 +77,12 @@ public class ActionConfigMatcher extends AbstractMatcher<ActionConfig> implement
Map<String, String> vars) {
String methodName = convertParam(orig.getMethodName(), vars);
if (methodName != null && !orig.isAllowedMethod(methodName)) {
if (StringUtils.isEmpty(methodName)) {
methodName = ActionConfig.DEFAULT_METHOD;
}
if (!orig.isAllowedMethod(methodName)) {
return null;
}
@@ -777,11 +777,18 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
}
params.putAll(resultParams);
ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass)
.addParams(params)
.location(DomHelper.getLocationObject(element))
.build();
results.put(resultConfig.getName(), resultConfig);
Set<String> resultNamesSet = TextParseUtil.commaDelimitedStringToSet(resultName);
if (resultNamesSet.isEmpty()) {
resultNamesSet.add(resultName);
}
for (String name : resultNamesSet) {
ResultConfig resultConfig = new ResultConfig.Builder(name, resultClass)
.addParams(params)
.location(DomHelper.getLocationObject(element))
.build();
results.put(resultConfig.getName(), resultConfig);
}
}
}
@@ -860,7 +867,7 @@ public class XmlConfigurationProvider implements ConfigurationProvider {
if (n != null) {
String s = n.getNodeValue().trim();
if (s.length() > 0) {
allowedMethods = TextParseUtil.commaDelimitedStringToSet(s);
allowedMethods.addAll(TextParseUtil.commaDelimitedStringToSet(s));
}
}
}
@@ -93,9 +93,10 @@ public class DateConverter extends DefaultTypeConverter {
DateFormat d2 = DateFormat.getDateInstance(DateFormat.MEDIUM, locale);
DateFormat d3 = DateFormat.getDateInstance(DateFormat.LONG, locale);
DateFormat rfc3399 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
DateFormat rfc3339 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
DateFormat rfc3339dateOnly = new SimpleDateFormat("yyyy-MM-dd");
return new DateFormat[]{dt1, dt2, dt3, rfc3399, d1, d2, d3};
return new DateFormat[]{dt1, dt2, dt3, rfc3339, d1, d2, d3, rfc3339dateOnly};
}
}
@@ -273,14 +273,10 @@ public class OgnlUtil {
*
* @throws OgnlException in case of ognl errors
*/
public void setValue(String name, Map<String, Object> context, Object root, Object value) throws OgnlException {
setValue(name, context, root, value, true);
}
protected void setValue(String name, final Map<String, Object> context, final Object root, final Object value, final boolean evalName) throws OgnlException {
public void setValue(final String name, final Map<String, Object> context, final Object root, final Object value) throws OgnlException {
compileAndExecute(name, context, new OgnlTask<Void>() {
public Void execute(Object tree) throws OgnlException {
if (!evalName && isEvalExpression(tree, context)) {
if (isEvalExpression(tree, context)) {
throw new OgnlException("Eval expression cannot be used as parameter name");
}
Ognl.setValue(tree, context, root, value);
@@ -148,7 +148,7 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
* @see com.opensymphony.xwork2.util.ValueStack#setParameter(String, Object)
*/
public void setParameter(String expr, Object value) {
setValue(expr, value, devMode, false);
setValue(expr, value, devMode);
}
/**
@@ -164,13 +164,9 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
* @see com.opensymphony.xwork2.util.ValueStack#setValue(java.lang.String, java.lang.Object, boolean)
*/
public void setValue(String expr, Object value, boolean throwExceptionOnFailure) {
setValue(expr, value, throwExceptionOnFailure, true);
}
private void setValue(String expr, Object value, boolean throwExceptionOnFailure, boolean evalExpression) {
Map<String, Object> context = getContext();
try {
trySetValue(expr, value, throwExceptionOnFailure, context, evalExpression);
trySetValue(expr, value, throwExceptionOnFailure, context);
} catch (OgnlException e) {
handleOgnlException(expr, value, throwExceptionOnFailure, e);
} catch (RuntimeException re) { //XW-281
@@ -180,10 +176,10 @@ public class OgnlValueStack implements Serializable, ValueStack, ClearableValueS
}
}
private void trySetValue(String expr, Object value, boolean throwExceptionOnFailure, Map<String, Object> context, boolean evalExpression) throws OgnlException {
private void trySetValue(String expr, Object value, boolean throwExceptionOnFailure, Map<String, Object> context) throws OgnlException {
context.put(XWorkConverter.CONVERSION_PROPERTY_FULLNAME, expr);
context.put(REPORT_ERRORS_ON_NO_PROP, (throwExceptionOnFailure) ? Boolean.TRUE : Boolean.FALSE);
ognlUtil.setValue(expr, context, root, value, evalExpression);
ognlUtil.setValue(expr, context, root, value);
}
private void cleanUpContext(Map<String, Object> context) {
@@ -17,6 +17,7 @@ public class DefaultExcludedPatternsChecker implements ExcludedPatternsChecker {
public static final String[] EXCLUDED_PATTERNS = {
"(^|\\%\\{)((#?)(top(\\.|\\['|\\[\")|\\[\\d\\]\\.)?)(dojo|struts|session|request|response|application|servlet(Request|Response|Context)|parameters|context|_memberAccess)(\\.|\\[).*",
".*(^|\\.|\\[|\\'|\"|get)class(\\(\\.|\\[|\\'|\").*",
"^(action|method):.*"
};
@@ -190,9 +190,7 @@ public class SpringObjectFactory extends ObjectFactory implements ApplicationCon
return bean;
} else {
bean = autoWiringFactory.autowire(clazz, AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR, false);
bean = autoWiringFactory.applyBeanPostProcessorsBeforeInitialization(bean, bean.getClass().getName());
bean = autoWiringFactory.initializeBean(bean, bean.getClass().getName());
bean = autoWiringFactory.applyBeanPostProcessorsAfterInitialization(bean, bean.getClass().getName());
return autoWireBean(bean, autoWiringFactory);
}
} catch (UnsatisfiedDependencyException e) {
@@ -15,6 +15,8 @@
*/
package com.opensymphony.xwork2.util;
import org.apache.commons.lang3.ArrayUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
@@ -42,8 +44,6 @@ public class AnnotationUtils {
private static final Pattern SETTER_PATTERN = Pattern.compile("set([A-Z][A-Za-z0-9]*)$");
private static final Pattern GETTER_PATTERN = Pattern.compile("(get|is|has)([A-Z][A-Za-z0-9]*)$");
/**
* Adds all fields with the specified Annotation of class clazz and its superclasses to allFields
*
@@ -122,9 +122,9 @@ public class AnnotationUtils {
Collection<Method> toReturn = new HashSet<>();
for (Method m : clazz.getMethods()) {
if (org.apache.commons.lang3.ArrayUtils.isNotEmpty(annotation) && isAnnotatedBy(m, annotation)) {
if (ArrayUtils.isNotEmpty(annotation) && isAnnotatedBy(m, annotation)) {
toReturn.add(m);
} else if (org.apache.commons.lang3.ArrayUtils.isEmpty(annotation) && org.apache.commons.lang3.ArrayUtils.isNotEmpty(m.getAnnotations())) {
} else if (ArrayUtils.isEmpty(annotation) && ArrayUtils.isNotEmpty(m.getAnnotations())) {
toReturn.add(m);
}
}
@@ -140,7 +140,7 @@ public class AnnotationUtils {
* @see AnnotatedElement
*/
public static boolean isAnnotatedBy(AnnotatedElement annotatedElement, Class<? extends Annotation>... annotation) {
if (org.apache.commons.lang3.ArrayUtils.isEmpty(annotation)) {
if (ArrayUtils.isEmpty(annotation)) {
return false;
}
@@ -15,8 +15,9 @@
*/
package com.opensymphony.xwork2.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -25,12 +26,14 @@ import java.util.List;
* @author plightbo
* @version $Revision$
*/
public class CompoundRoot extends ArrayList {
public class CompoundRoot extends CopyOnWriteArrayList<Object> {
private static final long serialVersionUID = 8563229069192473995L;
public CompoundRoot() {
}
public CompoundRoot(List list) {
public CompoundRoot(List<?> list) {
super(list);
}
@@ -210,7 +210,7 @@ public class AnnotationActionValidatorManager implements ActionValidatorManager
* @param context context
* @return a validator key which is the class name plus context.
*/
protected static String buildValidatorKey(Class clazz, String context) {
protected String buildValidatorKey(Class clazz, String context) {
ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
ActionProxy proxy = invocation.getProxy();
ActionConfig config = proxy.getConfig();
@@ -231,8 +231,12 @@ public class AnnotationActionValidatorManager implements ActionValidatorManager
// 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
String configName = config.getName();
if (configName.contains(ActionConfig.WILDCARD)) {
if (configName.contains(ActionConfig.WILDCARD) || (configName.contains("{") && configName.contains("}"))) {
sb.append(configName);
sb.append("|");
sb.append(proxy.getMethod());
@@ -80,7 +80,7 @@ public class EmailValidator extends RegexFieldValidator {
// see XW-371
public static final String EMAIL_ADDRESS_PATTERN =
"\\b^['_a-z0-9-\\+]+(\\.['_a-z0-9-\\+]+)*@[a-z0-9-]+(\\.[a-z0-9-]+)*\\.([a-z]{2}|aero|arpa|asia|biz|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|nato|net|org|pro|tel|travel|xxx)$\\b";
"\\b^['_a-z0-9-\\+]+(\\.['_a-z0-9-\\+]+)*@[a-z0-9-]+(\\.[a-z0-9-]+)*\\.([a-z]{2}|aero|arpa|asia|biz|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|nato|net|org|pro|tel|travel|xxx|tech)$\\b";
public EmailValidator() {
setRegex(EMAIL_ADDRESS_PATTERN);
@@ -59,7 +59,7 @@ import java.util.List;
*
* <!-- START SNIPPET: params -->
* <ul>
* <li>id (String) - the id of which if supplied will have the resultant
* <li>var (String) - the name of which if supplied will have the resultant
* appended iterator stored under in the stack's context</li>
* </ul>
* <!-- END SNIPPET: params -->
@@ -48,17 +48,6 @@ public abstract class ContextBean extends Component {
}
}
/**
* To keep backward compatibility
* TODO remove after 2.1
*
* @param id the given id
*/
@StrutsTagAttribute(description="Deprecated. Use 'var' instead")
public void setId(String id) {
setVar(id);
}
protected String getVar() {
return this.var;
}
@@ -35,6 +35,7 @@ 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;
@@ -277,7 +278,12 @@ public class Form extends ClosingUIBean {
ActionMapping mapping = actionMapper.getMappingFromActionName(formActionValue);
String actionName = mapping.getName();
List<Validator> actionValidators = actionValidatorManager.getValidators(actionClass, actionName);
String methodName = null;
if (isValidateAnnotatedMethodOnly(actionName)) {
methodName = mapping.getMethod();
}
List<Validator> actionValidators = actionValidatorManager.getValidators(actionClass, actionName, methodName);
List<Validator> validators = new ArrayList<>();
findFieldValidators(name, actionClass, actionName, actionValidators, validators, "");
@@ -285,6 +291,23 @@ public class Form extends ClosingUIBean {
return validators;
}
private boolean isValidateAnnotatedMethodOnly(String actionName) {
RuntimeConfiguration runtimeConfiguration = configuration.getRuntimeConfiguration();
String actionNamespace = TagUtils.buildNamespace(actionMapper, stack, request);
ActionConfig actionConfig = runtimeConfiguration.getActionConfig(actionNamespace, actionName);
if (actionConfig != null) {
List<InterceptorMapping> interceptors = actionConfig.getInterceptors();
for (InterceptorMapping interceptorMapping : interceptors) {
if (ValidationInterceptor.class.isInstance(interceptorMapping.getInterceptor())) {
ValidationInterceptor validationInterceptor = (ValidationInterceptor) interceptorMapping.getInterceptor();
return validationInterceptor.isValidateAnnotatedMethodOnly();
}
}
}
return false;
}
private void findFieldValidators(String name, Class actionClass, String actionName,
List<Validator> validatorList, List<Validator> resultValidators, String prefix) {
@@ -304,7 +304,7 @@ public class IteratorComponent extends ContextBean {
String var = getVar();
if ((var != null) && (currentValue != null)) {
if ((var != null)) {
putInContext(currentValue);
}
@@ -61,7 +61,7 @@ import java.util.List;
*
* <!-- START SNIPPET: params -->
* <ul>
* <li>id (String) - the id where the resultant merged iterator will be stored in the stack's context</li>
* <li>var (String) - the name where the resultant merged iterator will be stored in the stack's context</li>
* </ul>
* <!-- END SNIPPET: params -->
*
@@ -43,7 +43,10 @@ import java.io.Writer;
*
* <ul>
* <li>default (String) - The default value to be used if <u>value</u> attribute is null</li>
* <li>escape (Boolean) - Escape HTML. Default to true</li>
* <li>escapeCsv (Boolean) - Escape CSV. Defaults to false</li>
* <li>escapeHtml (Boolean) - Escape HTML. Defaults to true</li>
* <li>escapeJavaScript (Boolean) - Escape JavaScript. Defaults to false</li>
* <li>escapeXml (Boolean) - Escape XML. Defaults to false</li>
* <li>value (Object) - value to be displayed</li>
* </ul>
*
@@ -102,11 +105,6 @@ public class Property extends Component {
this.defaultValue = defaultValue;
}
@StrutsTagAttribute(description="Deprecated. Use 'escapeHtml'. Whether to escape HTML", type="Boolean", defaultValue="true")
public void setEscape(boolean escape) {
this.escapeHtml = escape;
}
@StrutsTagAttribute(description="Whether to escape HTML", type="Boolean", defaultValue="true")
public void setEscapeHtml(boolean escape) {
this.escapeHtml = escape;
@@ -58,7 +58,7 @@ import com.opensymphony.xwork2.util.ValueStack;
*
* <ul>
*
* <li>name* (String): The name of the new variable that is assigned the value of <i>value</i></li>
* <li>var* (String): The name of the new variable that is assigned the value of <i>value</i></li>
*
* <li>value (Object): The value that is assigned to the variable named <i>name</i></li>
*
@@ -123,19 +123,11 @@ public class Set extends ContextBean {
return super.end(writer, body);
}
/*
* TODO: set required=true when 'id' is dropped after 2.1
*/
@StrutsTagAttribute(description="Name used to reference the value pushed into the Value Stack")
@StrutsTagAttribute(required=true, description="Name used to reference the value pushed into the Value Stack")
public void setVar(String var) {
super.setVar(var);
}
@StrutsTagAttribute(description="Deprecated. Use 'var' instead")
public void setName(String name) {
setVar(name);
}
@StrutsTagAttribute(description="The scope in which to assign the variable. Can be <b>application</b>" +
", <b>session</b>, <b>request</b>, <b>page</b>, or <b>action</b>.", defaultValue="action")
public void setScope(String scope) {
@@ -27,9 +27,9 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.util.URLDecoderUtil;
import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.StringTokenizer;
@@ -132,10 +132,10 @@ public class Restful2ActionMapper extends DefaultActionMapper {
while (st.hasMoreTokens()) {
if (isNameTok) {
paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
isNameTok = false;
} else {
paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
if ((paramName != null) && (paramName.length() > 0)) {
parameters.put(paramName, paramValue);
@@ -25,9 +25,9 @@ import com.opensymphony.xwork2.config.ConfigurationManager;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.RequestUtils;
import org.apache.struts2.util.URLDecoderUtil;
import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
@@ -67,10 +67,10 @@ public class RestfulActionMapper implements ActionMapper {
while (st.hasMoreTokens()) {
if (isNameTok) {
paramName = URLDecoder.decode(st.nextToken(), "UTF-8");
paramName = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
isNameTok = false;
} else {
paramValue = URLDecoder.decode(st.nextToken(), "UTF-8");
paramValue = URLDecoderUtil.decode(st.nextToken(), "UTF-8");
if ((paramName != null) && (paramName.length() > 0)) {
parameters.put(paramName, paramValue);
@@ -83,9 +83,17 @@ public class JakartaMultiPartRequest implements MultiPartRequest {
try {
setLocale(request);
processUpload(request, saveDir);
} catch (FileUploadBase.SizeLimitExceededException e) {
} catch (FileUploadException e) {
LOG.warn("Request exceeded size limit!", e);
String errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()});
String errorMessage = null;
if(e instanceof FileUploadBase.SizeLimitExceededException) {
FileUploadBase.SizeLimitExceededException ex = (FileUploadBase.SizeLimitExceededException) e;
errorMessage = buildErrorMessage(e, new Object[]{ex.getPermittedSize(), ex.getActualSize()});
} else {
errorMessage = buildErrorMessage(e, new Object[]{});
}
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
@@ -384,6 +384,12 @@ public class JakartaStreamMultiPartRequest implements MultiPartRequest {
* @param location location
*/
private void processFileItemStreamAsFileField(FileItemStream itemStream, String location) {
// Skip file uploads that don't have a file name - meaning that no file was selected.
if (itemStream.getName() == null || itemStream.getName().trim().length() < 1) {
LOG.debug("No file has been uploaded for the field: {}", itemStream.getFieldName());
return;
}
File file = null;
try {
// Create the temporary upload file.
@@ -424,6 +430,10 @@ public class JakartaStreamMultiPartRequest implements MultiPartRequest {
suffix = name.substring(name.lastIndexOf('.'));
}
if (prefix.length() < 3) {
prefix = UUID.randomUUID().toString();
}
File file = File.createTempFile(prefix + "_", suffix, new File(location));
LOG.debug("Creating temporary file '{}' (originally '{}').", file.getName(), fileName);
return file;
@@ -23,6 +23,7 @@ package org.apache.struts2.interceptor;
import java.io.Serializable;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
/**
@@ -78,6 +79,7 @@ public class BackgroundProcess implements Serializable {
* @throws Exception any exception thrown will be thrown, in turn, by the ExecuteAndWaitInterceptor
*/
protected void beforeInvocation() throws Exception {
ActionContext.setContext(invocation.getInvocationContext());
}
/**
@@ -88,6 +90,7 @@ public class BackgroundProcess implements Serializable {
* @throws Exception any exception thrown will be thrown, in turn, by the ExecuteAndWaitInterceptor
*/
protected void afterInvocation() throws Exception {
ActionContext.setContext(null);
}
/**
@@ -1,106 +0,0 @@
package org.apache.struts2.interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.XWorkConstants;
import com.opensymphony.xwork2.inject.Container;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Set;
/**
* <!-- START SNIPPET: description -->
* In devMode checks if application uses deprecated or unknown constants and displays warning
* when logging level is set to DEBUG
* <!-- END SNIPPET: description -->
*
* <!-- START SNIPPET: parameters -->
* no special parameters yet
* <!-- END SNIPPET: parameters -->
*/
public class DeprecationInterceptor extends AbstractInterceptor {
private static final Logger LOG = LogManager.getLogger(DeprecationInterceptor.class);
private Container container;
private boolean devMode;
@Override
public String intercept(ActionInvocation invocation) throws Exception {
if (devMode) {
String message = validate();
if (message != null) {
LOG.debug(message);
}
}
return invocation.invoke();
}
/**
* Validates constants. Validation goes on only if devMode is set.
*
* @throws Exception
*/
private String validate() throws Exception {
Set<String> constants = new HashSet<>();
readConstants(constants, StrutsConstants.class);
readConstants(constants, XWorkConstants.class);
Set<String> applicationConstants = container.getInstanceNames(String.class);
String message = null;
if (!constants.containsAll(applicationConstants)) {
Set<String> deprecated = new HashSet<>(applicationConstants);
deprecated.removeAll(constants);
message = prepareMessage(deprecated);
}
return message;
}
private void readConstants(Set<String> constants, Class clazz) throws IllegalAccessException {
for (Field field : clazz.getDeclaredFields()) {
if (String.class.equals(field.getType())) {
constants.add((String) field.get(clazz));
}
}
}
/**
* Prepares message to display
*
* @param deprecated A set with deprecated/unknown constants
*/
private String prepareMessage(Set<String> deprecated) {
StringBuilder sb = new StringBuilder("\n");
sb.append("*******************************************************************************\n");
sb.append("** **\n");
sb.append("** WARNING **\n");
sb.append("** YOU USE DEPRECATED / UNKNOWN CONSTANTS **\n");
sb.append("** **\n");
for (String dep : deprecated) {
sb.append(String.format("** -> %-69s **\n", dep));
}
sb.append("*******************************************************************************\n");
return sb.toString();
}
@Inject(StrutsConstants.STRUTS_DEVMODE)
public void setDevMode(String state) {
this.devMode = BooleanUtils.toBoolean(state);
}
@Inject
public void setContainer(Container container) {
this.container = container;
}
}
@@ -196,12 +196,6 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
this.container = container;
}
/* (non-Javadoc)
* @see com.opensymphony.xwork2.interceptor.Interceptor#init()
*/
public void init() {
}
/**
* Creates a new background process
*
@@ -306,13 +300,6 @@ public class ExecuteAndWaitInterceptor extends MethodFilterInterceptor {
}
}
/* (non-Javadoc)
* @see com.opensymphony.xwork2.interceptor.Interceptor#destroy()
*/
public void destroy() {
}
/**
* <p>
* Performs the initial delay.
@@ -27,6 +27,8 @@ import com.opensymphony.xwork2.interceptor.ValidationAware;
import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.struts2.ServletActionContext;
import org.apache.struts2.result.ServletRedirectResult;
import java.util.ArrayList;
@@ -269,14 +271,16 @@ public class MessageStoreInterceptor extends AbstractInterceptor {
String reqOperationMode = getRequestOperationMode(invocation);
boolean isRedirect = invocation.getResult() instanceof ServletRedirectResult;
boolean isCommitted = ServletActionContext.getResponse().isCommitted();
if (STORE_MODE.equalsIgnoreCase(reqOperationMode) ||
STORE_MODE.equalsIgnoreCase(operationMode) ||
(AUTOMATIC_MODE.equalsIgnoreCase(operationMode) && isRedirect)) {
Object action = invocation.getAction();
if (action instanceof ValidationAware) {
if (action instanceof ValidationAware && !isCommitted) {
// store error / messages into session
Map session = (Map) invocation.getInvocationContext().get(ActionContext.SESSION);
Map<String, Object> session = invocation.getInvocationContext().getSession();
if (session == null) {
LOG.debug("Could not store action [{}] error/messages into session, because session hasn't been opened yet.", action);
@@ -289,13 +293,17 @@ public class MessageStoreInterceptor extends AbstractInterceptor {
session.put(actionErrorsSessionKey, validationAwareAction.getActionErrors());
session.put(actionMessagesSessionKey, validationAwareAction.getActionMessages());
session.put(fieldErrorsSessionKey, validationAwareAction.getFieldErrors());
} else {
LOG.debug("Action [{}] is not ValidationAware, no message / error that are storeable", action);
if (isCommitted) {
LOG.debug("Response was already committed, cannot store messages!");
} else {
LOG.debug("Action [{}] is not ValidationAware, no message / error that are storeable", action);
}
}
}
}
/**
* Get the operationMode through request parameter, if <code>allowRequestParameterSwitch</code>
* is 'true', else it simply returns 'NONE', meaning its neither in the 'STORE_MODE' nor
@@ -39,7 +39,7 @@ import java.util.Map;
* <!-- START SNIPPET: description -->
* <p>
* A custom Result type for setting HTTP headers and status by optionally evaluating against the ValueStack.
* This result can also be used to send and error to the client. All the parameters can be evaluated against the ValueStack.
* This result can also be used to send an error to the client. All the parameters can be evaluated against the ValueStack.
* </p>
*
* <!-- END SNIPPET: description -->
@@ -74,7 +74,7 @@ import java.util.Map;
*
* &lt;result name="proxyRequired" type="httpheader"&gt;
* &lt;param name="error"&gt;305&lt;/param&gt;
* &lt;param name="errorMessage"&gt;this action must be accessed through a prozy&lt;/param&gt;
* &lt;param name="errorMessage"&gt;this action must be accessed through a proxy&lt;/param&gt;
* &lt;/result&gt;
* <!-- END SNIPPET: example --></pre>
*/
@@ -76,6 +76,8 @@ import java.util.Map;
*/
public class PostbackResult extends StrutsResultSupport {
private static final long serialVersionUID = -2283504349296877429L;
private String actionName;
private String namespace;
private String method;
@@ -97,6 +99,9 @@ public class PostbackResult extends StrutsResultSupport {
response.setDateHeader("Expires", 0); // Proxies
}
//set contenttype @see ww-4564
response.setContentType("text/html");
// Render
PrintWriter pw = new PrintWriter(response.getOutputStream());
pw.write("<!DOCTYPE html><html><body><form action=\"" + finalLocation + "\" method=\"POST\">");
@@ -0,0 +1,22 @@
package org.apache.struts2.util;
import org.apache.struts2.util.tomcat.buf.UDecoder;
/**
* URLDecoderUtil serves as a facade for a correct URL decoding implementation.
* As of Struts 2.3.25 it uses Tomcat URLDecoder functionality rather than the one found in java.io.
*/
public class URLDecoderUtil {
/**
* Decodes a <code>x-www-form-urlencoded</code> string.
* @param sequence the String to decode
* @param charset The name of a supported character encoding.
* @return the newly decoded <code>String</code>
* @exception IllegalArgumentException If the encoding is not valid
*/
public static String decode(String sequence, String charset) {
return UDecoder.URLDecode(sequence, charset);
}
}
@@ -0,0 +1,255 @@
/*
* 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.util.tomcat.buf;
/**
* This class implements some basic ASCII character handling functions.
*
* @author dac@eng.sun.com
* @author James Todd [gonzo@eng.sun.com]
*/
public final class Ascii {
/*
* Character translation tables.
*/
private static final byte[] toUpper = new byte[256];
private static final byte[] toLower = new byte[256];
/*
* Character type tables.
*/
private static final boolean[] isAlpha = new boolean[256];
private static final boolean[] isUpper = new boolean[256];
private static final boolean[] isLower = new boolean[256];
private static final boolean[] isWhite = new boolean[256];
private static final boolean[] isDigit = new boolean[256];
private static final long OVERFLOW_LIMIT = Long.MAX_VALUE / 10;
/*
* Initialize character translation and type tables.
*/
static {
for (int i = 0; i < 256; i++) {
toUpper[i] = (byte)i;
toLower[i] = (byte)i;
}
for (int lc = 'a'; lc <= 'z'; lc++) {
int uc = lc + 'A' - 'a';
toUpper[lc] = (byte)uc;
toLower[uc] = (byte)lc;
isAlpha[lc] = true;
isAlpha[uc] = true;
isLower[lc] = true;
isUpper[uc] = true;
}
isWhite[ ' '] = true;
isWhite['\t'] = true;
isWhite['\r'] = true;
isWhite['\n'] = true;
isWhite['\f'] = true;
isWhite['\b'] = true;
for (int d = '0'; d <= '9'; d++) {
isDigit[d] = true;
}
}
/**
* Returns the upper case equivalent of the specified ASCII character.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int toUpper(int c) {
return toUpper[c & 0xff] & 0xff;
}
/**
* Returns the lower case equivalent of the specified ASCII character.
*/
public static int toLower(int c) {
return toLower[c & 0xff] & 0xff;
}
/**
* Returns true if the specified ASCII character is upper or lower case.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isAlpha(int c) {
return isAlpha[c & 0xff];
}
/**
* Returns true if the specified ASCII character is upper case.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isUpper(int c) {
return isUpper[c & 0xff];
}
/**
* Returns true if the specified ASCII character is lower case.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isLower(int c) {
return isLower[c & 0xff];
}
/**
* Returns true if the specified ASCII character is white space.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static boolean isWhite(int c) {
return isWhite[c & 0xff];
}
/**
* Returns true if the specified ASCII character is a digit.
*/
public static boolean isDigit(int c) {
return isDigit[c & 0xff];
}
/**
* Parses an unsigned integer from the specified subarray of bytes.
* @param b the bytes to parse
* @param off the start offset of the bytes
* @param len the length of the bytes
* @exception NumberFormatException if the integer format was invalid
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int parseInt(byte[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
int n = c - '0';
while (--len > 0) {
if (!isDigit(c = b[off++])) {
throw new NumberFormatException();
}
n = n * 10 + c - '0';
}
return n;
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int parseInt(char[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
int n = c - '0';
while (--len > 0) {
if (!isDigit(c = b[off++])) {
throw new NumberFormatException();
}
n = n * 10 + c - '0';
}
return n;
}
/**
* Parses an unsigned long from the specified subarray of bytes.
* @param b the bytes to parse
* @param off the start offset of the bytes
* @param len the length of the bytes
* @exception NumberFormatException if the long format was invalid
*/
public static long parseLong(byte[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
long n = c - '0';
while (--len > 0) {
if (isDigit(c = b[off++]) &&
(n < OVERFLOW_LIMIT || (n == OVERFLOW_LIMIT && (c - '0') < 8))) {
n = n * 10 + c - '0';
} else {
throw new NumberFormatException();
}
}
return n;
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static long parseLong(char[] b, int off, int len)
throws NumberFormatException
{
int c;
if (b == null || len <= 0 || !isDigit(c = b[off++])) {
throw new NumberFormatException();
}
long n = c - '0';
long m;
while (--len > 0) {
if (!isDigit(c = b[off++])) {
throw new NumberFormatException();
}
m = n * 10 + c - '0';
if (m < n) {
// Overflow
throw new NumberFormatException();
} else {
n = m;
}
}
return n;
}
}
@@ -0,0 +1,201 @@
/*
* 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.util.tomcat.buf;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* NIO based character decoder.
*/
public class B2CConverter {
private static final Map<String, Charset> encodingToCharsetCache =
new HashMap<String, Charset>();
public static final Charset ISO_8859_1;
public static final Charset UTF_8;
// Protected so unit tests can use it
protected static final int LEFTOVER_SIZE = 9;
static {
for (Charset charset: Charset.availableCharsets().values()) {
encodingToCharsetCache.put(
charset.name().toLowerCase(Locale.ENGLISH), charset);
for (String alias : charset.aliases()) {
encodingToCharsetCache.put(
alias.toLowerCase(Locale.ENGLISH), charset);
}
}
Charset iso88591 = null;
Charset utf8 = null;
try {
iso88591 = getCharset("ISO-8859-1");
utf8 = getCharset("UTF-8");
} catch (UnsupportedEncodingException e) {
// Impossible. All JVMs must support these.
e.printStackTrace();
}
ISO_8859_1 = iso88591;
UTF_8 = utf8;
}
public static Charset getCharset(String enc)
throws UnsupportedEncodingException {
// Encoding names should all be ASCII
String lowerCaseEnc = enc.toLowerCase(Locale.ENGLISH);
return getCharsetLower(lowerCaseEnc);
}
/**
* Only to be used when it is known that the encoding name is in lower case.
*/
public static Charset getCharsetLower(String lowerCaseEnc)
throws UnsupportedEncodingException {
Charset charset = encodingToCharsetCache.get(lowerCaseEnc);
if (charset == null) {
// Pre-population of the cache means this must be invalid
throw new UnsupportedEncodingException("The character encoding " + lowerCaseEnc + " is not supported");
}
return charset;
}
private final CharsetDecoder decoder;
private ByteBuffer bb = null;
private CharBuffer cb = null;
/**
* Leftover buffer used for incomplete characters.
*/
private final ByteBuffer leftovers;
public B2CConverter(String encoding) throws IOException {
this(encoding, false);
}
public B2CConverter(String encoding, boolean replaceOnError)
throws IOException {
byte[] left = new byte[LEFTOVER_SIZE];
leftovers = ByteBuffer.wrap(left);
CodingErrorAction action;
if (replaceOnError) {
action = CodingErrorAction.REPLACE;
} else {
action = CodingErrorAction.REPORT;
}
Charset charset = getCharset(encoding);
// Special case. Use the Apache Harmony based UTF-8 decoder because it
// - a) rejects invalid sequences that the JVM decoder does not
// - b) fails faster for some invalid sequences
if (charset.equals(UTF_8)) {
decoder = new Utf8Decoder();
} else {
decoder = charset.newDecoder();
}
decoder.onMalformedInput(action);
decoder.onUnmappableCharacter(action);
}
/**
* Reset the decoder state.
*/
public void recycle() {
decoder.reset();
leftovers.position(0);
}
/**
* Convert the given bytes to characters.
*
* @param bc byte input
* @param cc char output
* @param endOfInput Is this all of the available data
*/
public void convert(ByteChunk bc, CharChunk cc, boolean endOfInput)
throws IOException {
if ((bb == null) || (bb.array() != bc.getBuffer())) {
// Create a new byte buffer if anything changed
bb = ByteBuffer.wrap(bc.getBuffer(), bc.getStart(), bc.getLength());
} else {
// Initialize the byte buffer
bb.limit(bc.getEnd());
bb.position(bc.getStart());
}
if ((cb == null) || (cb.array() != cc.getBuffer())) {
// Create a new char buffer if anything changed
cb = CharBuffer.wrap(cc.getBuffer(), cc.getEnd(),
cc.getBuffer().length - cc.getEnd());
} else {
// Initialize the char buffer
cb.limit(cc.getBuffer().length);
cb.position(cc.getEnd());
}
CoderResult result = null;
// Parse leftover if any are present
if (leftovers.position() > 0) {
int pos = cb.position();
// Loop until one char is decoded or there is a decoder error
do {
leftovers.put(bc.substractB());
leftovers.flip();
result = decoder.decode(leftovers, cb, endOfInput);
leftovers.position(leftovers.limit());
leftovers.limit(leftovers.array().length);
} while (result.isUnderflow() && (cb.position() == pos));
if (result.isError() || result.isMalformed()) {
result.throwException();
}
bb.position(bc.getStart());
leftovers.position(0);
}
// Do the decoding and get the results into the byte chunk and the char
// chunk
result = decoder.decode(bb, cb, endOfInput);
if (result.isError() || result.isMalformed()) {
result.throwException();
} else if (result.isOverflow()) {
// Propagate current positions to the byte chunk and char chunk, if
// this continues the char buffer will get resized
bc.setOffset(bb.position());
cc.setEnd(cb.position());
} else if (result.isUnderflow()) {
// Propagate current positions to the byte chunk and char chunk
bc.setOffset(bb.position());
cc.setEnd(cb.position());
// Put leftovers in the leftovers byte buffer
if (bc.getLength() > 0) {
leftovers.limit(leftovers.array().length);
leftovers.position(bc.getLength());
bc.substract(leftovers.array(), 0, bc.getLength());
}
}
}
}
@@ -0,0 +1,935 @@
/*
* 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.util.tomcat.buf;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
/*
* In a server it is very important to be able to operate on
* the original byte[] without converting everything to chars.
* Some protocols are ASCII only, and some allow different
* non-UNICODE encodings. The encoding is not known beforehand,
* and can even change during the execution of the protocol.
* ( for example a multipart message may have parts with different
* encoding )
*
* For HTTP it is not very clear how the encoding of RequestURI
* and mime values can be determined, but it is a great advantage
* to be able to parse the request without converting to string.
*/
// TODO: This class could either extend ByteBuffer, or better a ByteBuffer
// inside this way it could provide the search/etc on ByteBuffer, as a helper.
/**
* This class is used to represent a chunk of bytes, and
* utilities to manipulate byte[].
*
* The buffer can be modified and used for both input and output.
*
* There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
* or ByteOutputChannel, which will be used when the buffer is empty (on input)
* or filled (on output).
* For output, it can also grow. This operating mode is selected by calling
* setLimit() or allocate(initial, limit) with limit != -1.
*
* Various search and append method are defined - similar with String and
* StringBuffer, but operating on bytes.
*
* This is important because it allows processing the http headers directly on
* the received bytes, without converting to chars and Strings until the strings
* are needed. In addition, the charset is determined later, from headers or
* user code.
*
* @author dac@sun.com
* @author James Todd [gonzo@sun.com]
* @author Costin Manolache
* @author Remy Maucherat
*/
public final class ByteChunk implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/** Input interface, used when the buffer is empty
*
* Same as java.nio.channel.ReadableByteChannel
*/
public static interface ByteInputChannel {
/**
* Read new bytes ( usually the internal conversion buffer ).
* The implementation is allowed to ignore the parameters,
* and mutate the chunk if it wishes to implement its own buffering.
*/
public int realReadBytes(byte cbuf[], int off, int len)
throws IOException;
}
/** Same as java.nio.channel.WrittableByteChannel.
*/
public static interface ByteOutputChannel {
/**
* Send the bytes ( usually the internal conversion buffer ).
* Expect 8k output if the buffer is full.
*/
public void realWriteBytes(byte cbuf[], int off, int len)
throws IOException;
}
// --------------------
/** Default encoding used to convert to strings. It should be UTF8,
as most standards seem to converge, but the servlet API requires
8859_1, and this object is used mostly for servlets.
*/
public static final Charset DEFAULT_CHARSET = B2CConverter.ISO_8859_1;
// byte[]
private byte[] buff;
private int start=0;
private int end;
private Charset charset;
private boolean isSet=false; // XXX
// How much can it grow, when data is added
private int limit=-1;
private ByteInputChannel in = null;
private ByteOutputChannel out = null;
private boolean optimizedWrite=true;
/**
* Creates a new, uninitialized ByteChunk object.
*/
public ByteChunk() {
// NO-OP
}
public ByteChunk( int initial ) {
allocate( initial, -1 );
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public ByteChunk getClone() {
try {
return (ByteChunk)this.clone();
} catch( Exception ex) {
return null;
}
}
public boolean isNull() {
return ! isSet; // buff==null;
}
/**
* Resets the message buff to an uninitialized state.
*/
public void recycle() {
// buff = null;
charset=null;
start=0;
end=0;
isSet=false;
}
public void reset() {
buff=null;
}
// -------------------- Setup --------------------
public void allocate( int initial, int limit ) {
if( buff==null || buff.length < initial ) {
buff=new byte[initial];
}
this.limit=limit;
start=0;
end=0;
isSet=true;
}
/**
* Sets the message bytes to the specified subarray of bytes.
*
* @param b the ascii bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
buff = b;
start = off;
end = start+ len;
isSet=true;
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public void setOptimizedWrite(boolean optimizedWrite) {
this.optimizedWrite = optimizedWrite;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public Charset getCharset() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
return charset;
}
/**
* Returns the message bytes.
*/
public byte[] getBytes() {
return getBuffer();
}
/**
* Returns the message bytes.
*/
public byte[] getBuffer() {
return buff;
}
/**
* Returns the start offset of the bytes.
* For output this is the end of the buffer.
*/
public int getStart() {
return start;
}
public int getOffset() {
return start;
}
public void setOffset(int off) {
if (end < off ) {
end=off;
}
start=off;
}
/**
* Returns the length of the bytes.
* XXX need to clean this up
*/
public int getLength() {
return end-start;
}
/** Maximum amount of data in this buffer.
*
* If -1 or not set, the buffer will grow indefinitely.
* Can be smaller than the current buffer size ( which will not shrink ).
* When the limit is reached, the buffer will be flushed ( if out is set )
* or throw exception.
*/
public void setLimit(int limit) {
this.limit=limit;
}
public int getLimit() {
return limit;
}
/**
* When the buffer is empty, read the data from the input channel.
*/
public void setByteInputChannel(ByteInputChannel in) {
this.in = in;
}
/** When the buffer is full, write the data to the output channel.
* Also used when large amount of data is appended.
*
* If not set, the buffer will grow to the limit.
*/
public void setByteOutputChannel(ByteOutputChannel out) {
this.out=out;
}
public int getEnd() {
return end;
}
public void setEnd( int i ) {
end=i;
}
// -------------------- Adding data to the buffer --------------------
/** Append a char, by casting it to byte. This IS NOT intended for unicode.
*
* @param c
* @throws IOException
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public void append( char c )
throws IOException
{
append( (byte)c);
}
public void append( byte b )
throws IOException
{
makeSpace( 1 );
// couldn't make space
if( limit >0 && end >= limit ) {
flushBuffer();
}
buff[end++]=b;
}
public void append( ByteChunk src )
throws IOException
{
append( src.getBytes(), src.getStart(), src.getLength());
}
/** Add data to the buffer
*/
public void append( byte src[], int off, int len )
throws IOException
{
// will grow, up to limit
makeSpace( len );
// if we don't have limit: makeSpace can grow as it wants
if( limit < 0 ) {
// assert: makeSpace made enough space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// Optimize on a common case.
// If the buffer is empty and the source is going to fill up all the
// space in buffer, may as well write it directly to the output,
// and avoid an extra copy
if ( optimizedWrite && len == limit && end == start && out != null ) {
out.realWriteBytes( src, off, len );
return;
}
// if we have limit and we're below
if( len <= limit - end ) {
// makeSpace will grow the buffer to the limit,
// so we have space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// need more space than we can afford, need to flush
// buffer
// the buffer is already at ( or bigger than ) limit
// We chunk the data into slices fitting in the buffer limit, although
// if the data is written directly if it doesn't fit
int avail=limit-end;
System.arraycopy(src, off, buff, end, avail);
end += avail;
flushBuffer();
int remain = len - avail;
while (remain > (limit - end)) {
out.realWriteBytes( src, (off + len) - remain, limit - end );
remain = remain - (limit - end);
}
System.arraycopy(src, (off + len) - remain, buff, end, remain);
end += remain;
}
// -------------------- Removing data from the buffer --------------------
public int substract()
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0) {
return -1;
}
}
return (buff[start++] & 0xFF);
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public int substract(ByteChunk src)
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0) {
return -1;
}
}
int len = getLength();
src.append(buff, start, len);
start = end;
return len;
}
public byte substractB()
throws IOException {
if ((end - start) == 0) {
if (in == null)
return -1;
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0)
return -1;
}
return (buff[start++]);
}
public int substract( byte src[], int off, int len )
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadBytes( buff, 0, buff.length );
if (n < 0) {
return -1;
}
}
int n = len;
if (len > getLength()) {
n = getLength();
}
System.arraycopy(buff, start, src, off, n);
start += n;
return n;
}
/**
* Send the buffer to the sink. Called by append() when the limit is
* reached. You can also call it explicitly to force the data to be written.
*
* @throws IOException
*/
public void flushBuffer()
throws IOException
{
//assert out!=null
if( out==null ) {
throw new IOException( "Buffer overflow, no sink " + limit + " " +
buff.length );
}
out.realWriteBytes( buff, start, end-start );
end=start;
}
/**
* Make space for len chars. If len is small, allocate a reserve space too.
* Never grow bigger than limit.
*/
public void makeSpace(int count) {
byte[] tmp = null;
int newSize;
int desiredSize=end + count;
// Can't grow above the limit
if( limit > 0 &&
desiredSize > limit) {
desiredSize=limit;
}
if( buff==null ) {
if( desiredSize < 256 )
{
desiredSize=256; // take a minimum
}
buff=new byte[desiredSize];
}
// limit < buf.length ( the buffer is already big )
// or we already have space XXX
if( desiredSize <= buff.length ) {
return;
}
// grow in larger chunks
if( desiredSize < 2 * buff.length ) {
newSize= buff.length * 2;
if( limit >0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new byte[newSize];
} else {
newSize= buff.length * 2 + count ;
if( limit > 0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new byte[newSize];
}
System.arraycopy(buff, start, tmp, 0, end-start);
buff = tmp;
tmp = null;
end=end-start;
start=0;
}
// -------------------- Conversion and getters --------------------
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end-start == 0) {
return "";
}
return StringCache.toString(this);
}
public String toStringInternal() {
if (charset == null) {
charset = DEFAULT_CHARSET;
}
// new String(byte[], int, int, Charset) takes a defensive copy of the
// entire byte array. This is expensive if only a small subset of the
// bytes will be used. The code below is from Apache Harmony.
CharBuffer cb;
cb = charset.decode(ByteBuffer.wrap(buff, start, end-start));
return new String(cb.array(), cb.arrayOffset(), cb.length());
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public int getInt()
{
return Ascii.parseInt(buff, start,end-start);
}
public long getLong() {
return Ascii.parseLong(buff, start,end-start);
}
// -------------------- equals --------------------
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equals(String s) {
// XXX ENCODING - this only works if encoding is UTF8-compat
// ( ok for tomcat, where we compare ascii - header names, etc )!!!
byte[] b = buff;
int blen = end-start;
if (b == null || blen != s.length()) {
return false;
}
int boff = start;
for (int i = 0; i < blen; i++) {
if (b[boff++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equalsIgnoreCase(String s) {
byte[] b = buff;
int blen = end-start;
if (b == null || blen != s.length()) {
return false;
}
int boff = start;
for (int i = 0; i < blen; i++) {
if (Ascii.toLower(b[boff++]) != Ascii.toLower(s.charAt(i))) {
return false;
}
}
return true;
}
public boolean equals( ByteChunk bb ) {
return equals( bb.getBytes(), bb.getStart(), bb.getLength());
}
public boolean equals( byte b2[], int off2, int len2) {
byte b1[]=buff;
if( b1==null && b2==null ) {
return true;
}
int len=end-start;
if ( len2 != len || b1==null || b2==null ) {
return false;
}
int off1 = start;
while ( len-- > 0) {
if (b1[off1++] != b2[off2++]) {
return false;
}
}
return true;
}
public boolean equals( CharChunk cc ) {
return equals( cc.getChars(), cc.getStart(), cc.getLength());
}
public boolean equals( char c2[], int off2, int len2) {
// XXX works only for enc compatible with ASCII/UTF !!!
byte b1[]=buff;
if( c2==null && b1==null ) {
return true;
}
if (b1== null || c2==null || end-start != len2 ) {
return false;
}
int off1 = start;
int len=end-start;
while ( len-- > 0) {
if ( (char)b1[off1++] != c2[off2++]) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public boolean startsWith(String s) {
// Works only if enc==UTF
byte[] b = buff;
int blen = s.length();
if (b == null || blen > end-start) {
return false;
}
int boff = start;
for (int i = 0; i < blen; i++) {
if (b[boff++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes start with the specified byte array.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public boolean startsWith(byte[] b2) {
byte[] b1 = buff;
if (b1 == null && b2 == null) {
return true;
}
int len = end - start;
if (b1 == null || b2 == null || b2.length > len) {
return false;
}
for (int i = start, j = 0; i < end && j < b2.length;) {
if (b1[i++] != b2[j++]) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
* @param pos The position
*/
public boolean startsWithIgnoreCase(String s, int pos) {
byte[] b = buff;
int len = s.length();
if (b == null || len+pos > end-start) {
return false;
}
int off = start+pos;
for (int i = 0; i < len; i++) {
if (Ascii.toLower( b[off++] ) != Ascii.toLower( s.charAt(i))) {
return false;
}
}
return true;
}
public int indexOf( String src, int srcOff, int srcLen, int myOff ) {
char first=src.charAt( srcOff );
// Look for first char
int srcEnd = srcOff + srcLen;
mainLoop:
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
if( buff[i] != first ) {
continue;
}
// found first char, now look for a match
int myPos=i+1;
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
if( buff[myPos++] != src.charAt( srcPos++ )) {
continue mainLoop;
}
}
return i-start; // found it
}
return -1;
}
// -------------------- Hash code --------------------
// normal hash.
public int hash() {
return hashBytes( buff, start, end-start);
}
/**
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public int hashIgnoreCase() {
return hashBytesIC( buff, start, end-start );
}
private static int hashBytes( byte buff[], int start, int bytesLen ) {
int max=start+bytesLen;
byte bb[]=buff;
int code=0;
for (int i = start; i < max ; i++) {
code = code * 37 + bb[i];
}
return code;
}
private static int hashBytesIC( byte bytes[], int start,
int bytesLen )
{
int max=start+bytesLen;
byte bb[]=bytes;
int code=0;
for (int i = start; i < max ; i++) {
code = code * 37 + Ascii.toLower(bb[i]);
}
return code;
}
/**
* Returns the first instance of the given character in this ByteChunk
* starting at the specified byte. If the character is not found, -1 is
* returned.
* <br/>
* NOTE: This only works for characters in the range 0-127.
*
* @param c The character
* @param starting The start position
* @return The position of the first instance of the character or
* -1 if the character is not found.
*/
public int indexOf(char c, int starting) {
int ret = indexOf(buff, start + starting, end, c);
return (ret >= start) ? ret - start : -1;
}
/**
* Returns the first instance of the given character in the given byte array
* between the specified start and end.
* <br/>
* NOTE: This only works for characters in the range 0-127.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param c The character to search for
* @return The position of the first instance of the character or -1
* if the character is not found.
*/
public static int indexOf(byte bytes[], int start, int end, char c) {
int offset = start;
while (offset < end) {
byte b=bytes[offset];
if (b == c) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the first instance of the given byte in the byte array between
* the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The byte to search for
* @return The position of the first instance of the byte or -1 if the
* byte is not found.
*/
public static int findByte(byte bytes[], int start, int end, byte b) {
int offset = start;
while (offset < end) {
if (bytes[offset] == b) {
return offset;
}
offset++;
}
return -1;
}
/**
* Returns the first instance of any of the given bytes in the byte array
* between the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The array of bytes to search for
* @return The position of the first instance of the byte or -1 if the
* byte is not found.
*/
public static int findBytes(byte bytes[], int start, int end, byte b[]) {
int blen = b.length;
int offset = start;
while (offset < end) {
for (int i = 0; i < blen; i++) {
if (bytes[offset] == b[i]) {
return offset;
}
}
offset++;
}
return -1;
}
/**
* Returns the first instance of any byte that is not one of the given bytes
* in the byte array between the specified start and end.
*
* @param bytes The byte array to search
* @param start The point to start searching from in the byte array
* @param end The point to stop searching in the byte array
* @param b The list of bytes to search for
* @return The position of the first instance a byte that is not
* in the list of bytes to search for or -1 if no such byte
* is found.
* @deprecated Unused. Will be removed in Tomcat 8.0.x onwards.
*/
@Deprecated
public static int findNotBytes(byte bytes[], int start, int end, byte b[]) {
int blen = b.length;
int offset = start;
boolean found;
while (offset < end) {
found = true;
for (int i = 0; i < blen; i++) {
if (bytes[offset] == b[i]) {
found=false;
break;
}
}
if (found) {
return offset;
}
offset++;
}
return -1;
}
/**
* Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
* chars will be truncated.
*
* @param value to convert to byte array
* @return the byte array value
*/
public static final byte[] convertToBytes(String value) {
byte[] result = new byte[value.length()];
for (int i = 0; i < value.length(); i++) {
result[i] = (byte) value.charAt(i);
}
return result;
}
}
@@ -0,0 +1,700 @@
/*
* 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.util.tomcat.buf;
import java.io.IOException;
import java.io.Serializable;
/**
* Utilities to manipulate char chunks. While String is
* the easiest way to manipulate chars ( search, substrings, etc),
* it is known to not be the most efficient solution - Strings are
* designed as immutable and secure objects.
*
* @author dac@sun.com
* @author James Todd [gonzo@sun.com]
* @author Costin Manolache
* @author Remy Maucherat
*/
public final class CharChunk implements Cloneable, Serializable, CharSequence {
private static final long serialVersionUID = 1L;
// Input interface, used when the buffer is emptied.
public static interface CharInputChannel {
/**
* Read new bytes ( usually the internal conversion buffer ).
* The implementation is allowed to ignore the parameters,
* and mutate the chunk if it wishes to implement its own buffering.
*/
public int realReadChars(char cbuf[], int off, int len)
throws IOException;
}
/**
* When we need more space we'll either
* grow the buffer ( up to the limit ) or send it to a channel.
*/
public static interface CharOutputChannel {
/** Send the bytes ( usually the internal conversion buffer ).
* Expect 8k output if the buffer is full.
*/
public void realWriteChars(char cbuf[], int off, int len)
throws IOException;
}
// --------------------
private int hashCode = 0;
// did we compute the hashcode ?
private boolean hasHashCode = false;
// char[]
private char buff[];
private int start;
private int end;
private boolean isSet=false; // XXX
// -1: grow indefinitely
// maximum amount to be cached
private int limit=-1;
private CharInputChannel in = null;
private CharOutputChannel out = null;
private boolean optimizedWrite=true;
/**
* Creates a new, uninitialized CharChunk object.
*/
public CharChunk() {
}
public CharChunk(int size) {
allocate( size, -1 );
}
// --------------------
public boolean isNull() {
if( end > 0 ) {
return false;
}
return !isSet; //XXX
}
/**
* Resets the message bytes to an uninitialized state.
*/
public void recycle() {
// buff=null;
isSet=false; // XXX
hasHashCode = false;
start=0;
end=0;
}
// -------------------- Setup --------------------
public void allocate( int initial, int limit ) {
if( buff==null || buff.length < initial ) {
buff=new char[initial];
}
this.limit=limit;
start=0;
end=0;
isSet=true;
hasHashCode = false;
}
public void setOptimizedWrite(boolean optimizedWrite) {
this.optimizedWrite = optimizedWrite;
}
public void setChars( char[] c, int off, int len ) {
buff=c;
start=off;
end=start + len;
isSet=true;
hasHashCode = false;
}
/** Maximum amount of data in this buffer.
*
* If -1 or not set, the buffer will grow indefinitely.
* Can be smaller than the current buffer size ( which will not shrink ).
* When the limit is reached, the buffer will be flushed ( if out is set )
* or throw exception.
*/
public void setLimit(int limit) {
this.limit=limit;
}
public int getLimit() {
return limit;
}
/**
* When the buffer is empty, read the data from the input channel.
*/
public void setCharInputChannel(CharInputChannel in) {
this.in = in;
}
/** When the buffer is full, write the data to the output channel.
* Also used when large amount of data is appended.
*
* If not set, the buffer will grow to the limit.
*/
public void setCharOutputChannel(CharOutputChannel out) {
this.out=out;
}
// compat
public char[] getChars()
{
return getBuffer();
}
public char[] getBuffer()
{
return buff;
}
/**
* Returns the start offset of the bytes.
* For output this is the end of the buffer.
*/
public int getStart() {
return start;
}
public int getOffset() {
return start;
}
/**
* Returns the start offset of the bytes.
*/
public void setOffset(int off) {
start=off;
}
/**
* Returns the length of the bytes.
*/
public int getLength() {
return end-start;
}
public int getEnd() {
return end;
}
public void setEnd( int i ) {
end=i;
}
// -------------------- Adding data --------------------
public void append( char b )
throws IOException
{
makeSpace( 1 );
// couldn't make space
if( limit >0 && end >= limit ) {
flushBuffer();
}
buff[end++]=b;
}
public void append( CharChunk src )
throws IOException
{
append( src.getBuffer(), src.getOffset(), src.getLength());
}
/** Add data to the buffer
*/
public void append( char src[], int off, int len )
throws IOException
{
// will grow, up to limit
makeSpace( len );
// if we don't have limit: makeSpace can grow as it wants
if( limit < 0 ) {
// assert: makeSpace made enough space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// Optimize on a common case.
// If the source is going to fill up all the space in buffer, may
// as well write it directly to the output, and avoid an extra copy
if ( optimizedWrite && len == limit && end == start && out != null ) {
out.realWriteChars( src, off, len );
return;
}
// if we have limit and we're below
if( len <= limit - end ) {
// makeSpace will grow the buffer to the limit,
// so we have space
System.arraycopy( src, off, buff, end, len );
end+=len;
return;
}
// need more space than we can afford, need to flush
// buffer
// the buffer is already at ( or bigger than ) limit
// Optimization:
// If len-avail < length ( i.e. after we fill the buffer with
// what we can, the remaining will fit in the buffer ) we'll just
// copy the first part, flush, then copy the second part - 1 write
// and still have some space for more. We'll still have 2 writes, but
// we write more on the first.
if( len + end < 2 * limit ) {
/* If the request length exceeds the size of the output buffer,
flush the output buffer and then write the data directly.
We can't avoid 2 writes, but we can write more on the second
*/
int avail=limit-end;
System.arraycopy(src, off, buff, end, avail);
end += avail;
flushBuffer();
System.arraycopy(src, off+avail, buff, end, len - avail);
end+= len - avail;
} else { // len > buf.length + avail
// long write - flush the buffer and write the rest
// directly from source
flushBuffer();
out.realWriteChars( src, off, len );
}
}
/** Append a string to the buffer
*/
public void append(String s) throws IOException {
append(s, 0, s.length());
}
/** Append a string to the buffer
*/
public void append(String s, int off, int len) throws IOException {
if (s==null) {
return;
}
// will grow, up to limit
makeSpace( len );
// if we don't have limit: makeSpace can grow as it wants
if( limit < 0 ) {
// assert: makeSpace made enough space
s.getChars(off, off+len, buff, end );
end+=len;
return;
}
int sOff = off;
int sEnd = off + len;
while (sOff < sEnd) {
int d = min(limit - end, sEnd - sOff);
s.getChars( sOff, sOff+d, buff, end);
sOff += d;
end += d;
if (end >= limit) {
flushBuffer();
}
}
}
// -------------------- Removing data from the buffer --------------------
public int substract()
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadChars(buff, end, buff.length - end);
if (n < 0) {
return -1;
}
}
return (buff[start++]);
}
public int substract( char src[], int off, int len )
throws IOException {
if ((end - start) == 0) {
if (in == null) {
return -1;
}
int n = in.realReadChars( buff, end, buff.length - end);
if (n < 0) {
return -1;
}
}
int n = len;
if (len > getLength()) {
n = getLength();
}
System.arraycopy(buff, start, src, off, n);
start += n;
return n;
}
public void flushBuffer()
throws IOException
{
//assert out!=null
if( out==null ) {
throw new IOException( "Buffer overflow, no sink " + limit + " " +
buff.length );
}
out.realWriteChars( buff, start, end - start );
end=start;
}
/** Make space for len chars. If len is small, allocate
* a reserve space too. Never grow bigger than limit.
*/
public void makeSpace(int count)
{
char[] tmp = null;
int newSize;
int desiredSize=end + count;
// Can't grow above the limit
if( limit > 0 &&
desiredSize > limit) {
desiredSize=limit;
}
if( buff==null ) {
if( desiredSize < 256 )
{
desiredSize=256; // take a minimum
}
buff=new char[desiredSize];
}
// limit < buf.length ( the buffer is already big )
// or we already have space XXX
if( desiredSize <= buff.length) {
return;
}
// grow in larger chunks
if( desiredSize < 2 * buff.length ) {
newSize= buff.length * 2;
if( limit >0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new char[newSize];
} else {
newSize= buff.length * 2 + count ;
if( limit > 0 &&
newSize > limit ) {
newSize=limit;
}
tmp=new char[newSize];
}
System.arraycopy(buff, 0, tmp, 0, end);
buff = tmp;
tmp = null;
}
// -------------------- Conversion and getters --------------------
@Override
public String toString() {
if (null == buff) {
return null;
} else if (end-start == 0) {
return "";
}
return StringCache.toString(this);
}
public String toStringInternal() {
return new String(buff, start, end-start);
}
// -------------------- equals --------------------
@Override
public boolean equals(Object obj) {
if (obj instanceof CharChunk) {
return equals((CharChunk) obj);
}
return false;
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equals(String s) {
char[] c = buff;
int len = end-start;
if (c == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equalsIgnoreCase(String s) {
char[] c = buff;
int len = end-start;
if (c == null || len != s.length()) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
return false;
}
}
return true;
}
public boolean equals(CharChunk cc) {
return equals( cc.getChars(), cc.getOffset(), cc.getLength());
}
public boolean equals(char b2[], int off2, int len2) {
char b1[]=buff;
if( b1==null && b2==null ) {
return true;
}
if (b1== null || b2==null || end-start != len2) {
return false;
}
int off1 = start;
int len=end-start;
while ( len-- > 0) {
if (b1[off1++] != b2[off2++]) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
*/
public boolean startsWith(String s) {
char[] c = buff;
int len = s.length();
if (c == null || len > end-start) {
return false;
}
int off = start;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
*/
public boolean startsWithIgnoreCase(String s, int pos) {
char[] c = buff;
int len = s.length();
if (c == null || len+pos > end-start) {
return false;
}
int off = start+pos;
for (int i = 0; i < len; i++) {
if (Ascii.toLower( c[off++] ) != Ascii.toLower( s.charAt(i))) {
return false;
}
}
return true;
}
/**
* Returns true if the message bytes end with the specified string.
* @param s the string
*/
public boolean endsWith(String s) {
char[] c = buff;
int len = s.length();
if (c == null || len > end-start) {
return false;
}
int off = end - len;
for (int i = 0; i < len; i++) {
if (c[off++] != s.charAt(i)) {
return false;
}
}
return true;
}
// -------------------- Hash code --------------------
@Override
public int hashCode() {
if (hasHashCode) {
return hashCode;
}
int code = 0;
code = hash();
hashCode = code;
hasHashCode = true;
return code;
}
// normal hash.
public int hash() {
int code=0;
for (int i = start; i < start + end-start; i++) {
code = code * 37 + buff[i];
}
return code;
}
public int indexOf(char c) {
return indexOf( c, start);
}
/**
* Returns true if the message bytes starts with the specified string.
* @param c the character
*/
public int indexOf(char c, int starting) {
int ret = indexOf( buff, start+starting, end, c );
return (ret >= start) ? ret - start : -1;
}
public static int indexOf( char chars[], int off, int cend, char qq )
{
while( off < cend ) {
char b=chars[off];
if( b==qq ) {
return off;
}
off++;
}
return -1;
}
public int indexOf(String src, int srcOff, int srcLen, int myOff ) {
char first=src.charAt( srcOff );
// Look for first char
int srcEnd = srcOff + srcLen;
for( int i=myOff+start; i <= (end - srcLen); i++ ) {
if( buff[i] != first ) {
continue;
}
// found first char, now look for a match
int myPos=i+1;
for( int srcPos=srcOff + 1; srcPos< srcEnd;) {
if( buff[myPos++] != src.charAt( srcPos++ )) {
break;
}
if( srcPos==srcEnd )
{
return i-start; // found it
}
}
}
return -1;
}
// -------------------- utils
private int min(int a, int b) {
if (a < b) {
return a;
}
return b;
}
// Char sequence impl
public char charAt(int index) {
return buff[index + start];
}
public CharSequence subSequence(int start, int end) {
try {
CharChunk result = (CharChunk) this.clone();
result.setOffset(this.start + start);
result.setEnd(this.start + end);
return result;
} catch (CloneNotSupportedException e) {
// Cannot happen
return null;
}
}
public int length() {
return end - start;
}
}
@@ -0,0 +1,113 @@
/*
* 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.util.tomcat.buf;
/**
* Tables useful when converting byte arrays to and from strings of hexadecimal
* digits.
* Code from Ajp11, from Apache's JServ.
*
* @author Craig R. McClanahan
*/
public final class HexUtils {
// -------------------------------------------------------------- Constants
/**
* Table for HEX to DEC byte translation.
*/
private static final int[] DEC = {
00, 01, 02, 03, 04, 05, 06, 07, 8, 9, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, 10, 11, 12, 13, 14, 15,
};
/**
* Table for DEC to HEX byte translation.
*/
private static final byte[] HEX =
{ (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
(byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
/**
* Table for byte to hex string translation.
*/
private static final char[] hex = "0123456789abcdef".toCharArray();
// --------------------------------------------------------- Static Methods
public static int getDec(int index) {
// Fast for correct values, slower for incorrect ones
try {
return DEC[index - '0'];
} catch (ArrayIndexOutOfBoundsException ex) {
return -1;
}
}
public static byte getHex(int index) {
return HEX[index];
}
public static String toHexString(byte[] bytes) {
if (null == bytes) {
return null;
}
StringBuilder sb = new StringBuilder(bytes.length << 1);
for(int i = 0; i < bytes.length; ++i) {
sb.append(hex[(bytes[i] & 0xf0) >> 4])
.append(hex[(bytes[i] & 0x0f)])
;
}
return sb.toString();
}
public static byte[] fromHexString(String input) {
if (input == null) {
return null;
}
if ((input.length() & 1) == 1) {
// Odd number of characters
throw new IllegalArgumentException("The input must consist of an even number of hex digits");
}
char[] inputChars = input.toCharArray();
byte[] result = new byte[input.length() >> 1];
for (int i = 0; i < result.length; i++) {
int upperNibble = getDec(inputChars[2*i]);
int lowerNibble = getDec(inputChars[2*i + 1]);
if (upperNibble < 0 || lowerNibble < 0) {
// Non hex character
throw new IllegalArgumentException("The input must consist only of hex digits");
}
result[i] = (byte) ((upperNibble << 4) + lowerNibble);
}
return result;
}
}
@@ -0,0 +1,546 @@
/*
* 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.util.tomcat.buf;
import java.io.IOException;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
/**
* This class is used to represent a subarray of bytes in an HTTP message.
* It represents all request/response elements. The byte/char conversions are
* delayed and cached. Everything is recyclable.
*
* The object can represent a byte[], a char[], or a (sub) String. All
* operations can be made in case sensitive mode or not.
*
* @author dac@eng.sun.com
* @author James Todd [gonzo@eng.sun.com]
* @author Costin Manolache
*/
public final class MessageBytes implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
// primary type ( whatever is set as original value )
private int type = T_NULL;
public static final int T_NULL = 0;
/** getType() is T_STR if the the object used to create the MessageBytes
was a String */
public static final int T_STR = 1;
/** getType() is T_STR if the the object used to create the MessageBytes
was a byte[] */
public static final int T_BYTES = 2;
/** getType() is T_STR if the the object used to create the MessageBytes
was a char[] */
public static final int T_CHARS = 3;
private int hashCode=0;
// did we compute the hashcode ?
private boolean hasHashCode=false;
// Internal objects to represent array + offset, and specific methods
private final ByteChunk byteC=new ByteChunk();
private final CharChunk charC=new CharChunk();
// String
private String strValue;
// true if a String value was computed. Probably not needed,
// strValue!=null is the same
private boolean hasStrValue=false;
/**
* Creates a new, uninitialized MessageBytes object.
* Use static newInstance() in order to allow
* future hooks.
*/
private MessageBytes() {
}
/** Construct a new MessageBytes instance
*/
public static MessageBytes newInstance() {
return factory.newInstance();
}
public boolean isNull() {
// should we check also hasStrValue ???
return byteC.isNull() && charC.isNull() && ! hasStrValue;
// bytes==null && strValue==null;
}
/**
* Resets the message bytes to an uninitialized (NULL) state.
*/
public void recycle() {
type=T_NULL;
byteC.recycle();
charC.recycle();
strValue=null;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Sets the content to the specified subarray of bytes.
*
* @param b the bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setBytes(byte[] b, int off, int len) {
byteC.setBytes( b, off, len );
type=T_BYTES;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Sets the content to be a char[]
*
* @param c the bytes
* @param off the start offset of the bytes
* @param len the length of the bytes
*/
public void setChars( char[] c, int off, int len ) {
charC.setChars( c, off, len );
type=T_CHARS;
hasStrValue=false;
hasHashCode=false;
hasLongValue=false;
}
/**
* Set the content to be a string
*/
public void setString( String s ) {
strValue=s;
hasHashCode=false;
hasLongValue=false;
if (s == null) {
hasStrValue=false;
type=T_NULL;
} else {
hasStrValue=true;
type=T_STR;
}
}
// -------------------- Conversion and getters --------------------
/** Compute the string value
*/
@Override
public String toString() {
if( hasStrValue ) {
return strValue;
}
switch (type) {
case T_CHARS:
strValue=charC.toString();
hasStrValue=true;
return strValue;
case T_BYTES:
strValue=byteC.toString();
hasStrValue=true;
return strValue;
}
return null;
}
//----------------------------------------
/** Return the type of the original content. Can be
* T_STR, T_BYTES, T_CHARS or T_NULL
*/
public int getType() {
return type;
}
/**
* Returns the byte chunk, representing the byte[] and offset/length.
* Valid only if T_BYTES or after a conversion was made.
*/
public ByteChunk getByteChunk() {
return byteC;
}
/**
* Returns the char chunk, representing the char[] and offset/length.
* Valid only if T_CHARS or after a conversion was made.
*/
public CharChunk getCharChunk() {
return charC;
}
/**
* Returns the string value.
* Valid only if T_STR or after a conversion was made.
*/
public String getString() {
return strValue;
}
/**
* Get the Charset used for string&lt;-&gt;byte conversions.
*/
public Charset getCharset() {
return byteC.getCharset();
}
/**
* Set the Charset used for string&lt;-&gt;byte conversions.
*/
public void setCharset(Charset charset) {
byteC.setCharset(charset);
}
/** Do a char-&gt;byte conversion.
*/
public void toBytes() {
if (!byteC.isNull()) {
type=T_BYTES;
return;
}
toString();
type=T_BYTES;
Charset charset = byteC.getCharset();
ByteBuffer result = charset.encode(strValue);
byteC.setBytes(result.array(), result.arrayOffset(), result.limit());
}
/** Convert to char[] and fill the CharChunk.
* XXX Not optimized - it converts to String first.
*/
public void toChars() {
if( ! charC.isNull() ) {
type=T_CHARS;
return;
}
// inefficient
toString();
type=T_CHARS;
char cc[]=strValue.toCharArray();
charC.setChars(cc, 0, cc.length);
}
/**
* Returns the length of the original buffer.
* Note that the length in bytes may be different from the length
* in chars.
*/
public int getLength() {
if(type==T_BYTES) {
return byteC.getLength();
}
if(type==T_CHARS) {
return charC.getLength();
}
if(type==T_STR) {
return strValue.length();
}
toString();
if( strValue==null ) {
return 0;
}
return strValue.length();
}
// -------------------- equals --------------------
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equals(String s) {
switch (type) {
case T_STR:
if (strValue == null) {
return s == null;
}
return strValue.equals( s );
case T_CHARS:
return charC.equals( s );
case T_BYTES:
return byteC.equals( s );
default:
return false;
}
}
/**
* Compares the message bytes to the specified String object.
* @param s the String to compare
* @return true if the comparison succeeded, false otherwise
*/
public boolean equalsIgnoreCase(String s) {
switch (type) {
case T_STR:
if (strValue == null) {
return s == null;
}
return strValue.equalsIgnoreCase( s );
case T_CHARS:
return charC.equalsIgnoreCase( s );
case T_BYTES:
return byteC.equalsIgnoreCase( s );
default:
return false;
}
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MessageBytes) {
return equals((MessageBytes) obj);
}
return false;
}
public boolean equals(MessageBytes mb) {
switch (type) {
case T_STR:
return mb.equals( strValue );
}
if( mb.type != T_CHARS &&
mb.type!= T_BYTES ) {
// it's a string or int/date string value
return equals( mb.toString() );
}
// mb is either CHARS or BYTES.
// this is either CHARS or BYTES
// Deal with the 4 cases ( in fact 3, one is symmetric)
if( mb.type == T_CHARS && type==T_CHARS ) {
return charC.equals( mb.charC );
}
if( mb.type==T_BYTES && type== T_BYTES ) {
return byteC.equals( mb.byteC );
}
if( mb.type== T_CHARS && type== T_BYTES ) {
return byteC.equals( mb.charC );
}
if( mb.type== T_BYTES && type== T_CHARS ) {
return mb.byteC.equals( charC );
}
// can't happen
return true;
}
/**
* Returns true if the message bytes starts with the specified string.
* @param s the string
* @param pos The start position
*/
public boolean startsWithIgnoreCase(String s, int pos) {
switch (type) {
case T_STR:
if( strValue==null ) {
return false;
}
if( strValue.length() < pos + s.length() ) {
return false;
}
for( int i=0; i<s.length(); i++ ) {
if( Ascii.toLower( s.charAt( i ) ) !=
Ascii.toLower( strValue.charAt( pos + i ))) {
return false;
}
}
return true;
case T_CHARS:
return charC.startsWithIgnoreCase( s, pos );
case T_BYTES:
return byteC.startsWithIgnoreCase( s, pos );
default:
return false;
}
}
// -------------------- Hash code --------------------
@Override
public int hashCode() {
if( hasHashCode ) {
return hashCode;
}
int code = 0;
code=hash();
hashCode=code;
hasHashCode=true;
return code;
}
// normal hash.
private int hash() {
int code=0;
switch (type) {
case T_STR:
// We need to use the same hash function
for (int i = 0; i < strValue.length(); i++) {
code = code * 37 + strValue.charAt( i );
}
return code;
case T_CHARS:
return charC.hash();
case T_BYTES:
return byteC.hash();
default:
return 0;
}
}
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s, int starting) {
toString();
return strValue.indexOf( s, starting );
}
// Inefficient initial implementation. Will be replaced on the next
// round of tune-up
public int indexOf(String s) {
return indexOf( s, 0 );
}
public int indexOfIgnoreCase(String s, int starting) {
toString();
String upper=strValue.toUpperCase(Locale.ENGLISH);
String sU=s.toUpperCase(Locale.ENGLISH);
return upper.indexOf( sU, starting );
}
/** Copy the src into this MessageBytes, allocating more space if
* needed
*/
public void duplicate( MessageBytes src ) throws IOException
{
switch( src.getType() ) {
case MessageBytes.T_BYTES:
type=T_BYTES;
ByteChunk bc=src.getByteChunk();
byteC.allocate( 2 * bc.getLength(), -1 );
byteC.append( bc );
break;
case MessageBytes.T_CHARS:
type=T_CHARS;
CharChunk cc=src.getCharChunk();
charC.allocate( 2 * cc.getLength(), -1 );
charC.append( cc );
break;
case MessageBytes.T_STR:
type=T_STR;
String sc=src.getString();
this.setString( sc );
break;
}
}
// -------------------- Deprecated code --------------------
// efficient long
// XXX used only for headers - shouldn't be stored here.
private long longValue;
private boolean hasLongValue=false;
/** Set the buffer to the representation of an long
*/
public void setLong(long l) {
byteC.allocate(32, 64);
long current = l;
byte[] buf = byteC.getBuffer();
int start = 0;
int end = 0;
if (l == 0) {
buf[end++] = (byte) '0';
}
if (l < 0) {
current = -l;
buf[end++] = (byte) '-';
}
while (current > 0) {
int digit = (int) (current % 10);
current = current / 10;
buf[end++] = HexUtils.getHex(digit);
}
byteC.setOffset(0);
byteC.setEnd(end);
// Inverting buffer
end--;
if (l < 0) {
start++;
}
while (end > start) {
byte temp = buf[start];
buf[start] = buf[end];
buf[end] = temp;
start++;
end--;
}
longValue=l;
hasStrValue=false;
hasHashCode=false;
hasLongValue=true;
type=T_BYTES;
}
// Used for headers conversion
/** Convert the buffer to an long, cache the value
*/
public long getLong() {
if( hasLongValue ) {
return longValue;
}
switch (type) {
case T_BYTES:
longValue=byteC.getLong();
break;
default:
longValue= Long.parseLong(toString());
}
hasLongValue=true;
return longValue;
}
// -------------------- Future may be different --------------------
private static final MessageBytesFactory factory=new MessageBytesFactory();
private static class MessageBytesFactory {
protected MessageBytesFactory() {
}
public MessageBytes newInstance() {
return new MessageBytes();
}
}
}
@@ -0,0 +1,695 @@
/*
* 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.util.tomcat.buf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.TreeMap;
/**
* This class implements a String cache for ByteChunk and CharChunk.
*
* @author Remy Maucherat
*/
public class StringCache {
private static final Logger log = LogManager.getLogger(StringCache.class);
// ------------------------------------------------------- Static Variables
/**
* Enabled ?
*/
protected static boolean byteEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.byte.enabled", "false")));
protected static boolean charEnabled = ("true".equals(System.getProperty(
"tomcat.util.buf.StringCache.char.enabled", "false")));
protected static int trainThreshold = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.trainThreshold", "20000"));
protected static int cacheSize = Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.cacheSize", "200"));
protected static final int maxStringSize =
Integer.parseInt(System.getProperty(
"tomcat.util.buf.StringCache.maxStringSize", "128"));
/**
* Statistics hash map for byte chunk.
*/
protected static final HashMap<ByteEntry,int[]> bcStats =
new HashMap<ByteEntry, int[]>(cacheSize);
/**
* toString count for byte chunk.
*/
protected static int bcCount = 0;
/**
* Cache for byte chunk.
*/
protected static ByteEntry[] bcCache = null;
/**
* Statistics hash map for char chunk.
*/
protected static final HashMap<CharEntry,int[]> ccStats =
new HashMap<CharEntry, int[]>(cacheSize);
/**
* toString count for char chunk.
*/
protected static int ccCount = 0;
/**
* Cache for char chunk.
*/
protected static CharEntry[] ccCache = null;
/**
* Access count.
*/
protected static int accessCount = 0;
/**
* Hit count.
*/
protected static int hitCount = 0;
// ------------------------------------------------------------ Properties
/**
* @return Returns the cacheSize.
*/
public int getCacheSize() {
return cacheSize;
}
/**
* @param cacheSize The cacheSize to set.
*/
public void setCacheSize(int cacheSize) {
StringCache.cacheSize = cacheSize;
}
/**
* @return Returns the enabled.
*/
public boolean getByteEnabled() {
return byteEnabled;
}
/**
* @param byteEnabled The enabled to set.
*/
public void setByteEnabled(boolean byteEnabled) {
StringCache.byteEnabled = byteEnabled;
}
/**
* @return Returns the enabled.
*/
public boolean getCharEnabled() {
return charEnabled;
}
/**
* @param charEnabled The enabled to set.
*/
public void setCharEnabled(boolean charEnabled) {
StringCache.charEnabled = charEnabled;
}
/**
* @return Returns the trainThreshold.
*/
public int getTrainThreshold() {
return trainThreshold;
}
/**
* @param trainThreshold The trainThreshold to set.
*/
public void setTrainThreshold(int trainThreshold) {
StringCache.trainThreshold = trainThreshold;
}
/**
* @return Returns the accessCount.
*/
public int getAccessCount() {
return accessCount;
}
/**
* @return Returns the hitCount.
*/
public int getHitCount() {
return hitCount;
}
// -------------------------------------------------- Public Static Methods
public void reset() {
hitCount = 0;
accessCount = 0;
synchronized (bcStats) {
bcCache = null;
bcCount = 0;
}
synchronized (ccStats) {
ccCache = null;
ccCount = 0;
}
}
public static String toString(ByteChunk bc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (bcCache == null) {
String value = bc.toStringInternal();
if (byteEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (bcStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (bcCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (bcCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<ByteEntry>> tempMap =
new TreeMap<Integer, ArrayList<ByteEntry>>();
for (Entry<ByteEntry,int[]> item : bcStats.entrySet()) {
ByteEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<ByteEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<ByteEntry>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = bcStats.size();
if (size > cacheSize) {
size = cacheSize;
}
ByteEntry[] tempbcCache = new ByteEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
ByteChunk tempChunk = new ByteChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<ByteEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
ByteEntry entry = list.get(i);
tempChunk.setBytes(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempbcCache, n);
if (insertPos == n) {
tempbcCache[n + 1] = entry;
} else {
System.arraycopy(tempbcCache, insertPos + 1,
tempbcCache, insertPos + 2,
n - insertPos - 1);
tempbcCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
bcCount = 0;
bcStats.clear();
bcCache = tempbcCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("ByteCache generation time: " +
(t2 - t1) + "ms");
}
} else {
bcCount++;
// Allocate new ByteEntry for the lookup
ByteEntry entry = new ByteEntry();
entry.value = value;
int[] count = bcStats.get(entry);
if (count == null) {
int end = bc.getEnd();
int start = bc.getStart();
// Create byte array and copy bytes
entry.name = new byte[bc.getLength()];
System.arraycopy(bc.getBuffer(), start, entry.name,
0, end - start);
// Set encoding
entry.charset = bc.getCharset();
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
bcStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(bc);
if (result == null) {
return bc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
public static String toString(CharChunk cc) {
// If the cache is null, then either caching is disabled, or we're
// still training
if (ccCache == null) {
String value = cc.toStringInternal();
if (charEnabled && (value.length() < maxStringSize)) {
// If training, everything is synced
synchronized (ccStats) {
// If the cache has been generated on a previous invocation
// while waiting for the lock, just return the toString
// value we just calculated
if (ccCache != null) {
return value;
}
// Two cases: either we just exceeded the train count, in
// which case the cache must be created, or we just update
// the count for the string
if (ccCount > trainThreshold) {
long t1 = System.currentTimeMillis();
// Sort the entries according to occurrence
TreeMap<Integer,ArrayList<CharEntry>> tempMap =
new TreeMap<Integer, ArrayList<CharEntry>>();
for (Entry<CharEntry,int[]> item : ccStats.entrySet()) {
CharEntry entry = item.getKey();
int[] countA = item.getValue();
Integer count = Integer.valueOf(countA[0]);
// Add to the list for that count
ArrayList<CharEntry> list = tempMap.get(count);
if (list == null) {
// Create list
list = new ArrayList<CharEntry>();
tempMap.put(count, list);
}
list.add(entry);
}
// Allocate array of the right size
int size = ccStats.size();
if (size > cacheSize) {
size = cacheSize;
}
CharEntry[] tempccCache = new CharEntry[size];
// Fill it up using an alphabetical order
// and a dumb insert sort
CharChunk tempChunk = new CharChunk();
int n = 0;
while (n < size) {
Object key = tempMap.lastKey();
ArrayList<CharEntry> list = tempMap.get(key);
for (int i = 0; i < list.size() && n < size; i++) {
CharEntry entry = list.get(i);
tempChunk.setChars(entry.name, 0,
entry.name.length);
int insertPos = findClosest(tempChunk,
tempccCache, n);
if (insertPos == n) {
tempccCache[n + 1] = entry;
} else {
System.arraycopy(tempccCache, insertPos + 1,
tempccCache, insertPos + 2,
n - insertPos - 1);
tempccCache[insertPos + 1] = entry;
}
n++;
}
tempMap.remove(key);
}
ccCount = 0;
ccStats.clear();
ccCache = tempccCache;
if (log.isDebugEnabled()) {
long t2 = System.currentTimeMillis();
log.debug("CharCache generation time: " +
(t2 - t1) + "ms");
}
} else {
ccCount++;
// Allocate new CharEntry for the lookup
CharEntry entry = new CharEntry();
entry.value = value;
int[] count = ccStats.get(entry);
if (count == null) {
int end = cc.getEnd();
int start = cc.getStart();
// Create char array and copy chars
entry.name = new char[cc.getLength()];
System.arraycopy(cc.getBuffer(), start, entry.name,
0, end - start);
// Initialize occurrence count to one
count = new int[1];
count[0] = 1;
// Set in the stats hash map
ccStats.put(entry, count);
} else {
count[0] = count[0] + 1;
}
}
}
}
return value;
} else {
accessCount++;
// Find the corresponding String
String result = find(cc);
if (result == null) {
return cc.toStringInternal();
}
// Note: We don't care about safety for the stats
hitCount++;
return result;
}
}
// ----------------------------------------------------- Protected Methods
/**
* Compare given byte chunk with byte array.
* Return -1, 0 or +1 if inferior, equal, or superior to the String.
*/
protected static final int compare(ByteChunk name, byte[] compareTo) {
int result = 0;
byte[] b = name.getBuffer();
int start = name.getStart();
int end = name.getEnd();
int len = compareTo.length;
if ((end - start) < len) {
len = end - start;
}
for (int i = 0; (i < len) && (result == 0); i++) {
if (b[i + start] > compareTo[i]) {
result = 1;
} else if (b[i + start] < compareTo[i]) {
result = -1;
}
}
if (result == 0) {
if (compareTo.length > (end - start)) {
result = -1;
} else if (compareTo.length < (end - start)) {
result = 1;
}
}
return result;
}
/**
* Find an entry given its name in the cache and return the associated
* String.
*/
protected static final String find(ByteChunk name) {
int pos = findClosest(name, bcCache, bcCache.length);
if ((pos < 0) || (compare(name, bcCache[pos].name) != 0)
|| !(name.getCharset().equals(bcCache[pos].charset))) {
return null;
} else {
return bcCache[pos].value;
}
}
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
* given array.
*/
protected static final int findClosest(ByteChunk name, ByteEntry[] array,
int len) {
int a = 0;
int b = len - 1;
// Special cases: -1 and 0
if (b == -1) {
return -1;
}
if (compare(name, array[0].name) < 0) {
return -1;
}
if (b == 0) {
return 0;
}
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, array[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, array[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
/**
* Compare given char chunk with char array.
* Return -1, 0 or +1 if inferior, equal, or superior to the String.
*/
protected static final int compare(CharChunk name, char[] compareTo) {
int result = 0;
char[] c = name.getBuffer();
int start = name.getStart();
int end = name.getEnd();
int len = compareTo.length;
if ((end - start) < len) {
len = end - start;
}
for (int i = 0; (i < len) && (result == 0); i++) {
if (c[i + start] > compareTo[i]) {
result = 1;
} else if (c[i + start] < compareTo[i]) {
result = -1;
}
}
if (result == 0) {
if (compareTo.length > (end - start)) {
result = -1;
} else if (compareTo.length < (end - start)) {
result = 1;
}
}
return result;
}
/**
* Find an entry given its name in the cache and return the associated
* String.
*/
protected static final String find(CharChunk name) {
int pos = findClosest(name, ccCache, ccCache.length);
if ((pos < 0) || (compare(name, ccCache[pos].name) != 0)) {
return null;
} else {
return ccCache[pos].value;
}
}
/**
* Find an entry given its name in a sorted array of map elements.
* This will return the index for the closest inferior or equal item in the
* given array.
*/
protected static final int findClosest(CharChunk name, CharEntry[] array,
int len) {
int a = 0;
int b = len - 1;
// Special cases: -1 and 0
if (b == -1) {
return -1;
}
if (compare(name, array[0].name) < 0 ) {
return -1;
}
if (b == 0) {
return 0;
}
int i = 0;
while (true) {
i = (b + a) >>> 1;
int result = compare(name, array[i].name);
if (result == 1) {
a = i;
} else if (result == 0) {
return i;
} else {
b = i;
}
if ((b - a) == 1) {
int result2 = compare(name, array[b].name);
if (result2 < 0) {
return a;
} else {
return b;
}
}
}
}
// -------------------------------------------------- ByteEntry Inner Class
private static class ByteEntry {
private byte[] name = null;
private Charset charset = null;
private String value = null;
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof ByteEntry) {
return value.equals(((ByteEntry) obj).value);
}
return false;
}
}
// -------------------------------------------------- CharEntry Inner Class
private static class CharEntry {
private char[] name = null;
private String value = null;
@Override
public String toString() {
return value;
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof CharEntry) {
return value.equals(((CharEntry) obj).value);
}
return false;
}
}
}
@@ -0,0 +1,421 @@
/*
* 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.util.tomcat.buf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.CharConversionException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
/**
* All URL decoding happens here. This way we can reuse, review, optimize
* without adding complexity to the buffers.
*
* The conversion will modify the original buffer.
*
* @author Costin Manolache
*/
public final class UDecoder {
private static final Logger log = LogManager.getLogger(UDecoder.class);
public static final boolean ALLOW_ENCODED_SLASH =
Boolean.parseBoolean(System.getProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "false"));
private static class DecodeException extends CharConversionException {
private static final long serialVersionUID = 1L;
public DecodeException(String s) {
super(s);
}
@Override
public synchronized Throwable fillInStackTrace() {
// This class does not provide a stack trace
return this;
}
}
/** Unexpected end of data. */
private static final IOException EXCEPTION_EOF = new DecodeException("EOF");
/** %xx with not-hex digit */
private static final IOException EXCEPTION_NOT_HEX_DIGIT = new DecodeException(
"isHexDigit");
/** %-encoded slash is forbidden in resource path */
private static final IOException EXCEPTION_SLASH = new DecodeException(
"noSlash");
public UDecoder()
{
}
/** URLDecode, will modify the source.
*/
public void convert( ByteChunk mb, boolean query )
throws IOException
{
int start=mb.getOffset();
byte buff[]=mb.getBytes();
int end=mb.getEnd();
int idx= ByteChunk.findByte( buff, start, end, (byte) '%' );
int idx2=-1;
if( query ) {
idx2= ByteChunk.findByte( buff, start, (idx >= 0 ? idx : end), (byte) '+' );
}
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive index ( first % or + )
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
idx=idx2;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
for( int j=idx; j<end; j++, idx++ ) {
if( buff[ j ] == '+' && query) {
buff[idx]= (byte)' ' ;
} else if( buff[ j ] != '%' ) {
buff[idx]= buff[j];
} else {
// read next 2 digits
if( j+2 >= end ) {
throw EXCEPTION_EOF;
}
byte b1= buff[j+1];
byte b2=buff[j+2];
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw EXCEPTION_NOT_HEX_DIGIT;
}
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
throw EXCEPTION_SLASH;
}
buff[idx]=(byte)res;
}
}
mb.setEnd( idx );
return;
}
// -------------------- Additional methods --------------------
// XXX What do we do about charset ????
/** In-buffer processing - the buffer will be modified
*/
public void convert( CharChunk mb, boolean query )
throws IOException
{
// log( "Converting a char chunk ");
int start=mb.getOffset();
char buff[]=mb.getBuffer();
int cend=mb.getEnd();
int idx= CharChunk.indexOf( buff, start, cend, '%' );
int idx2=-1;
if( query ) {
idx2= CharChunk.indexOf( buff, start, (idx >= 0 ? idx : cend), '+' );
}
if( idx<0 && idx2<0 ) {
return;
}
// idx will be the smallest positive index ( first % or + )
if( (idx2 >= 0 && idx2 < idx) || idx < 0 ) {
idx=idx2;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
for( int j=idx; j<cend; j++, idx++ ) {
if( buff[ j ] == '+' && query ) {
buff[idx]=( ' ' );
} else if( buff[ j ] != '%' ) {
buff[idx]=buff[j];
} else {
// read next 2 digits
if( j+2 >= cend ) {
// invalid
throw EXCEPTION_EOF;
}
char b1= buff[j+1];
char b2=buff[j+2];
if( !isHexDigit( b1 ) || ! isHexDigit(b2 )) {
throw EXCEPTION_NOT_HEX_DIGIT;
}
j+=2;
int res=x2c( b1, b2 );
if (noSlash && (res == '/')) {
throw EXCEPTION_SLASH;
}
buff[idx]=(char)res;
}
}
mb.setEnd( idx );
}
/** URLDecode, will modify the source
*/
public void convert(MessageBytes mb, boolean query)
throws IOException
{
switch (mb.getType()) {
case MessageBytes.T_STR:
String strValue=mb.toString();
if( strValue==null ) {
return;
}
try {
mb.setString( convert( strValue, query ));
} catch (RuntimeException ex) {
throw new DecodeException(ex.getMessage());
}
break;
case MessageBytes.T_CHARS:
CharChunk charC=mb.getCharChunk();
convert( charC, query );
break;
case MessageBytes.T_BYTES:
ByteChunk bytesC=mb.getByteChunk();
convert( bytesC, query );
break;
}
}
// XXX Old code, needs to be replaced !!!!
//
public final String convert(String str, boolean query)
{
if (str == null) {
return null;
}
if( (!query || str.indexOf( '+' ) < 0) && str.indexOf( '%' ) < 0 ) {
return str;
}
final boolean noSlash = !(ALLOW_ENCODED_SLASH || query);
StringBuilder dec = new StringBuilder(); // decoded string output
int strPos = 0;
int strLen = str.length();
dec.ensureCapacity(str.length());
while (strPos < strLen) {
int laPos; // lookahead position
// look ahead to next URLencoded metacharacter, if any
for (laPos = strPos; laPos < strLen; laPos++) {
char laChar = str.charAt(laPos);
if ((laChar == '+' && query) || (laChar == '%')) {
break;
}
}
// if there were non-metacharacters, copy them all as a block
if (laPos > strPos) {
dec.append(str.substring(strPos,laPos));
strPos = laPos;
}
// shortcut out of here if we're at the end of the string
if (strPos >= strLen) {
break;
}
// process next metacharacter
char metaChar = str.charAt(strPos);
if (metaChar == '+') {
dec.append(' ');
strPos++;
continue;
} else if (metaChar == '%') {
// We throw the original exception - the super will deal with
// it
// try {
char res = (char) Integer.parseInt(
str.substring(strPos + 1, strPos + 3), 16);
if (noSlash && (res == '/')) {
throw new IllegalArgumentException("noSlash");
}
dec.append(res);
strPos += 3;
}
}
return dec.toString();
}
/**
* Decode and return the specified URL-encoded String.
* When the byte array is converted to a string, the system default
* character encoding is used... This may be different than some other
* servers. It is assumed the string is not a query string.
*
* @param str The url-encoded string
*
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str) {
return URLDecode(str, null);
}
/**
* Decode and return the specified URL-encoded String. It is assumed the
* string is not a query string.
*
* @param str The url-encoded string
* @param enc The encoding to use; if null, the default encoding is used. If
* an unsupported encoding is specified null will be returned
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, String enc) {
return URLDecode(str, enc, false);
}
/**
* Decode and return the specified URL-encoded String.
*
* @param str The url-encoded string
* @param enc The encoding to use; if null, the default encoding is used. If
* an unsupported encoding is specified null will be returned
* @param isQuery Is this a query string being processed
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(String str, String enc, boolean isQuery) {
if (str == null)
return (null);
// use the specified encoding to extract bytes out of the
// given string so that the encoding is not lost. If an
// encoding is not specified, use ISO-8859-1
byte[] bytes = null;
try {
if (enc == null) {
bytes = str.getBytes("ISO-8859-1");
} else {
bytes = str.getBytes(B2CConverter.getCharset(enc));
}
} catch (UnsupportedEncodingException uee) {
if (log.isDebugEnabled()) {
log.debug("Unable to URL decode the specified input since the encoding "+ enc + " is not supported.", uee);
}
}
return URLDecode(bytes, enc, isQuery);
}
/**
* Decode and return the specified URL-encoded byte array.
*
* @param bytes The url-encoded byte array
* @param enc The encoding to use; if null, the default encoding is used. If
* an unsupported encoding is specified null will be returned
* @param isQuery Is this a query string being processed
* @exception IllegalArgumentException if a '%' character is not followed
* by a valid 2-digit hexadecimal number
*/
public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
if (bytes == null)
return null;
int len = bytes.length;
int ix = 0;
int ox = 0;
while (ix < len) {
byte b = bytes[ix++]; // Get byte to test
if (b == '+' && isQuery) {
b = (byte)' ';
} else if (b == '%') {
if (ix + 2 > len) {
throw new IllegalArgumentException(
"The % character must be followed by two hexademical digits");
}
b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
+ convertHexDigit(bytes[ix++]));
}
bytes[ox++] = b;
}
if (enc != null) {
try {
return new String(bytes, 0, ox, B2CConverter.getCharset(enc));
} catch (UnsupportedEncodingException uee) {
if (log.isDebugEnabled()) {
log.debug("Unable to URL decode the specified input since the encoding " + enc + " is not supported.", uee);
}
return null;
}
}
return new String(bytes, 0, ox);
}
private static byte convertHexDigit( byte b ) {
if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
throw new IllegalArgumentException(((char) b) + " is not a hexadecimal digit");
}
private static boolean isHexDigit( int c ) {
return ( ( c>='0' && c<='9' ) ||
( c>='a' && c<='f' ) ||
( c>='A' && c<='F' ));
}
private static int x2c( byte b1, byte b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
digit*=16;
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
(b2 -'0');
return digit;
}
private static int x2c( char b1, char b2 ) {
int digit= (b1>='A') ? ( (b1 & 0xDF)-'A') + 10 :
(b1 -'0');
digit*=16;
digit +=(b2>='A') ? ( (b2 & 0xDF)-'A') + 10 :
(b2 -'0');
return digit;
}
}
@@ -0,0 +1,293 @@
/*
* 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.util.tomcat.buf;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
/**
* Decodes bytes to UTF-8. Extracted from Apache Harmony and modified to reject
* code points from U+D800 to U+DFFF as per RFC3629. The standard Java decoder
* does not reject these. It has also been modified to reject code points
* greater than U+10FFFF which the standard Java decoder rejects but the harmony
* one does not.
*/
public class Utf8Decoder extends CharsetDecoder {
// The next table contains information about UTF-8 charset and
// correspondence of 1st byte to the length of sequence
// For information please visit http://www.ietf.org/rfc/rfc3629.txt
//
// Please note, o means 0, actually.
// -------------------------------------------------------------------
// 0 1 2 3 Value
// -------------------------------------------------------------------
// oxxxxxxx 00000000 00000000 0xxxxxxx
// 11oyyyyy 1oxxxxxx 00000000 00000yyy yyxxxxxx
// 111ozzzz 1oyyyyyy 1oxxxxxx 00000000 zzzzyyyy yyxxxxxx
// 1111ouuu 1ouuzzzz 1oyyyyyy 1oxxxxxx 000uuuuu zzzzyyyy yyxxxxxx
private static final int remainingBytes[] = {
// 1owwwwww
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
// 11oyyyyy
-1, -1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
// 111ozzzz
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
// 1111ouuu
3, 3, 3, 3, 3, -1, -1, -1,
// > 11110111
-1, -1, -1, -1, -1, -1, -1, -1};
private static final int remainingNumbers[] = {0, // 0 1 2 3
4224, // (01o00000b << 6)+(1o000000b)
401536, // (011o0000b << 12)+(1o000000b << 6)+(1o000000b)
29892736 // (0111o000b << 18)+(1o000000b << 12)+(1o000000b <<
// 6)+(1o000000b)
};
private static final int lowerEncodingLimit[] = {-1, 0x80, 0x800, 0x10000};
public Utf8Decoder() {
super(B2CConverter.UTF_8, 1.0f, 1.0f);
}
@Override
protected CoderResult decodeLoop(ByteBuffer in, CharBuffer out) {
if (in.hasArray() && out.hasArray()) {
return decodeHasArray(in, out);
}
return decodeNotHasArray(in, out);
}
private CoderResult decodeNotHasArray(ByteBuffer in, CharBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
try {
while (pos < limit) {
if (outRemaining == 0) {
return CoderResult.OVERFLOW;
}
int jchar = in.get();
if (jchar < 0) {
jchar = jchar & 0x7F;
int tail = remainingBytes[jchar];
if (tail == -1) {
return CoderResult.malformedForLength(1);
}
if (limit - pos < 1 + tail) {
// No early test for invalid sequences here as peeking
// at the next byte is harder
return CoderResult.UNDERFLOW;
}
int nextByte;
for (int i = 0; i < tail; i++) {
nextByte = in.get() & 0xFF;
if ((nextByte & 0xC0) != 0x80) {
return CoderResult.malformedForLength(1 + i);
}
jchar = (jchar << 6) + nextByte;
}
jchar -= remainingNumbers[tail];
if (jchar < lowerEncodingLimit[tail]) {
// Should have been encoded in a fewer octets
return CoderResult.malformedForLength(1);
}
pos += tail;
}
// Apache Tomcat added test
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
return CoderResult.unmappableForLength(3);
}
// Apache Tomcat added test
if (jchar > 0x10FFFF) {
return CoderResult.unmappableForLength(4);
}
if (jchar <= 0xffff) {
out.put((char) jchar);
outRemaining--;
} else {
if (outRemaining < 2) {
return CoderResult.OVERFLOW;
}
out.put((char) ((jchar >> 0xA) + 0xD7C0));
out.put((char) ((jchar & 0x3FF) + 0xDC00));
outRemaining -= 2;
}
pos++;
}
return CoderResult.UNDERFLOW;
} finally {
in.position(pos);
}
}
private CoderResult decodeHasArray(ByteBuffer in, CharBuffer out) {
int outRemaining = out.remaining();
int pos = in.position();
int limit = in.limit();
final byte[] bArr = in.array();
final char[] cArr = out.array();
final int inIndexLimit = limit + in.arrayOffset();
int inIndex = pos + in.arrayOffset();
int outIndex = out.position() + out.arrayOffset();
// if someone would change the limit in process,
// he would face consequences
for (; inIndex < inIndexLimit && outRemaining > 0; inIndex++) {
int jchar = bArr[inIndex];
if (jchar < 0) {
jchar = jchar & 0x7F;
// If first byte is invalid, tail will be set to -1
int tail = remainingBytes[jchar];
if (tail == -1) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// Additional checks to detect invalid sequences ASAP
// Checks derived from Unicode 6.2, Chapter 3, Table 3-7
// Check 2nd byte
int tailAvailable = inIndexLimit - inIndex - 1;
if (tailAvailable > 0) {
// First byte C2..DF, second byte 80..BF
if (jchar > 0x41 && jchar < 0x60 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte E0, second byte A0..BF
if (jchar == 0x60 && (bArr[inIndex + 1] & 0xE0) != 0xA0) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte E1..EC, second byte 80..BF
if (jchar > 0x60 && jchar < 0x6D &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte ED, second byte 80..9F
if (jchar == 0x6D && (bArr[inIndex + 1] & 0xE0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte EE..EF, second byte 80..BF
if (jchar > 0x6D && jchar < 0x70 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F0, second byte 90..BF
if (jchar == 0x70 &&
((bArr[inIndex + 1] & 0xFF) < 0x90 ||
(bArr[inIndex + 1] & 0xFF) > 0xBF)) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F1..F3, second byte 80..BF
if (jchar > 0x70 && jchar < 0x74 &&
(bArr[inIndex + 1] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
// First byte F4, second byte 80..8F
if (jchar == 0x74 &&
(bArr[inIndex + 1] & 0xF0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
}
// Check third byte if present and expected
if (tailAvailable > 1 && tail > 1) {
if ((bArr[inIndex + 2] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(2);
}
}
// Check fourth byte if present and expected
if (tailAvailable > 2 && tail > 2) {
if ((bArr[inIndex + 3] & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(3);
}
}
if (tailAvailable < tail) {
break;
}
for (int i = 0; i < tail; i++) {
int nextByte = bArr[inIndex + i + 1] & 0xFF;
if ((nextByte & 0xC0) != 0x80) {
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1 + i);
}
jchar = (jchar << 6) + nextByte;
}
jchar -= remainingNumbers[tail];
if (jchar < lowerEncodingLimit[tail]) {
// Should have been encoded in fewer octets
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return CoderResult.malformedForLength(1);
}
inIndex += tail;
}
// Apache Tomcat added test
if (jchar >= 0xD800 && jchar <= 0xDFFF) {
return CoderResult.unmappableForLength(3);
}
// Apache Tomcat added test
if (jchar > 0x10FFFF) {
return CoderResult.unmappableForLength(4);
}
if (jchar <= 0xffff) {
cArr[outIndex++] = (char) jchar;
outRemaining--;
} else {
if (outRemaining < 2) {
return CoderResult.OVERFLOW;
}
cArr[outIndex++] = (char) ((jchar >> 0xA) + 0xD7C0);
cArr[outIndex++] = (char) ((jchar & 0x3FF) + 0xDC00);
outRemaining -= 2;
}
}
in.position(inIndex - in.arrayOffset());
out.position(outIndex - out.arrayOffset());
return (outRemaining == 0 && inIndex < inIndexLimit) ?
CoderResult.OVERFLOW :
CoderResult.UNDERFLOW;
}
}
@@ -350,6 +350,7 @@ public class FreemarkerManager {
servletContext.setAttribute(ATTR_APPLICATION_MODEL, servletContextModel);
}
TaglibFactory taglibs = new TaglibFactory(servletContext);
taglibs.setObjectWrapper(wrapper);
servletContext.setAttribute(ATTR_JSP_TAGLIBS_MODEL, taglibs);
}
model.put(KEY_APPLICATION, servletContextModel);
@@ -37,12 +37,4 @@ public abstract class ContextBeanTag extends ComponentTagSupport {
public void setVar(String var) {
this.var = var;
}
/**
* To keep backward compatibility
* TODO remove after 2.1
*/
public void setId(String id) {
setVar(id);
}
}
@@ -53,8 +53,8 @@ public class PropertyTag extends ComponentTagSupport {
Property tag = (Property) component;
tag.setDefault(defaultValue);
tag.setValue(value);
tag.setEscape(escapeHtml);
tag.setValue(value);
tag.setEscapeHtml(escapeHtml);
tag.setEscapeJavaScript(escapeJavaScript);
tag.setEscapeXml(escapeXml);
tag.setEscapeCsv(escapeCsv);
@@ -64,10 +64,6 @@ public class PropertyTag extends ComponentTagSupport {
this.defaultValue = defaultValue;
}
public void setEscape(boolean escape) {
this.escapeHtml = escape;
}
public void setEscapeHtml(boolean escapeHtml) {
this.escapeHtml = escapeHtml;
}

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