mirror of
https://github.com/apache/struts.git
synced 2026-08-09 16:46:55 +00:00
Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a63e6504b | |||
| 0835c33adf | |||
| 11561cb299 | |||
| 2a46a6586e | |||
| 930f6e4cf7 | |||
| cae57093b5 | |||
| 7899f10d80 | |||
| 04f5813c90 | |||
| 1fb695a82b | |||
| bc8d374647 | |||
| c167d6c855 | |||
| 3163d6c38b | |||
| 087cf610ab | |||
| d923c7c4f2 | |||
| 93375125ad | |||
| 294394b256 | |||
| 328da4e225 | |||
| a42a1731f3 | |||
| f645c91ee6 | |||
| 0e85da6228 | |||
| e0003f0471 | |||
| ee83d4a6db | |||
| 9f6a0c9c94 | |||
| a89bbe22cd | |||
| 72471d7075 | |||
| b0a66b47ac | |||
| b5c96a5888 | |||
| 5bd54cf8ac | |||
| 66d29d42c3 | |||
| 334e6ca462 | |||
| b417894ebc | |||
| 6f98d776c3 | |||
| 7bd8dd9559 | |||
| e56d8b2e07 | |||
| 33edfffbe2 | |||
| c07020cb7c | |||
| ace6a5d5ee | |||
| 4638b33457 | |||
| b675844a8f | |||
| af82d23c95 | |||
| 47ecd001ec | |||
| 5fe760dc67 | |||
| 2d0fe9f742 | |||
| 460faa33d8 | |||
| cb11898416 | |||
| 61a7ee2961 | |||
| 74e26830d2 | |||
| 37f822f9cc | |||
| a4f8d49214 | |||
| 4f29d8861a | |||
| f457c774e9 | |||
| d862d5b96d | |||
| 1e21b7ec1b | |||
| bdc325b03d | |||
| e2ae94cc0f | |||
| 7170c13509 | |||
| 1d3d7be466 | |||
| 110d63fed0 | |||
| acd55986f4 | |||
| 0af9d3b60d | |||
| 3b38ccea5e | |||
| 212bbae0fd | |||
| cb0475d615 | |||
| e42954219a | |||
| a401f3a6ef | |||
| 32597350fe | |||
| 4c96d239dd | |||
| b198e3bf3d | |||
| f9b7ebb87e | |||
| 32ec7a669c | |||
| 0e111125d5 | |||
| 38da3f6705 | |||
| 316b8a926e | |||
| b257250036 | |||
| cfc6f19ec7 | |||
| ec5a301b53 | |||
| 1df5a75bfc | |||
| 8aebdc3e6f | |||
| ac4199e0d8 | |||
| 3480bc3c10 | |||
| d5a6c35589 | |||
| 70b1b2c497 | |||
| 9cc383e436 | |||
| db483ccd15 | |||
| 30fd44f0ef | |||
| 7f10be5c37 | |||
| e488353a05 | |||
| fa41222859 | |||
| a20aed381a | |||
| 103a655f4a | |||
| 705915abd2 | |||
| 6622fab25b | |||
| 388861ff07 | |||
| 27ad6cb13e | |||
| 6edc0ff5e0 | |||
| 26f153d262 | |||
| 24a5bce6c4 | |||
| 70716e9452 | |||
| 85b219813f | |||
| ed8dc577b7 | |||
| c3ec2c3390 | |||
| 7a06fe47e1 | |||
| fb896b6c1c | |||
| ee562666ab | |||
| 907ce9802e | |||
| afb0c2a228 | |||
| f8718f9187 | |||
| 2b4d37908d | |||
| 146b44e2e8 | |||
| 0a08f6b673 | |||
| ba361aad36 | |||
| 5bf1b62dd3 | |||
| b44fa2934f | |||
| 87dfa3f6fb | |||
| 0ba7965637 | |||
| b79c6959a4 | |||
| 5dcbe657b0 | |||
| 617bfd23d9 | |||
| 692e763e43 | |||
| ed1b8cc7b3 | |||
| e0f258f718 | |||
| 9d5b948190 | |||
| 4bff5a8748 | |||
| 0dd0f045d2 | |||
| 9c5c568fc5 | |||
| da6b03a0a8 | |||
| a7b840c75a | |||
| 117f5fbacc | |||
| 79a2022b55 | |||
| 41dd070eda | |||
| 366a0b4a58 | |||
| d95fc63992 | |||
| a5812bf0b6 | |||
| f0797388bc | |||
| e28e7f69b9 | |||
| 221ef32e37 | |||
| a6241552b6 | |||
| ecde389ee6 | |||
| 50ae859dab | |||
| 9ad9404bfa | |||
| 9638d7adaa | |||
| 6fbdab1848 |
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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-->
|
||||
|
||||
+152
@@ -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 -->
|
||||
*/
|
||||
|
||||
|
||||
+7
@@ -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>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+1
-1
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+2
-2
@@ -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>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+13
-6
@@ -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) {
|
||||
|
||||
+1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -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);
|
||||
|
||||
+10
-2
@@ -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);
|
||||
}
|
||||
|
||||
+10
@@ -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;
|
||||
*
|
||||
* <result name="proxyRequired" type="httpheader">
|
||||
* <param name="error">305</param>
|
||||
* <param name="errorMessage">this action must be accessed through a prozy</param>
|
||||
* <param name="errorMessage">this action must be accessed through a proxy</param>
|
||||
* </result>
|
||||
* <!-- 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<->byte conversions.
|
||||
*/
|
||||
public Charset getCharset() {
|
||||
return byteC.getCharset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Charset used for string<->byte conversions.
|
||||
*/
|
||||
public void setCharset(Charset charset) {
|
||||
byteC.setCharset(charset);
|
||||
}
|
||||
|
||||
/** Do a char->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
Reference in New Issue
Block a user