mirror of
https://github.com/apache/struts.git
synced 2026-08-07 15:46:57 +00:00
WW-4599: backport JSONActionRedirectResult to struts 2.3
This commit is contained in:
+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 a special json redirect result type.</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";
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,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
|
||||
@@ -246,7 +246,6 @@
|
||||
</li>
|
||||
<li><s:a value="/interactive/index.action">Interactive Demo</s:a></li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav pull-right">
|
||||
<li class="dropdown last">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown"><i class="icon-flag"></i> Help<b
|
||||
|
||||
@@ -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>
|
||||
@@ -31,6 +31,7 @@
|
||||
<s:url var="clientSideValidationUrl" action="clientSideValidationExample" namespace="/validation"/>
|
||||
<s:url var="backToShowcase" action="showcase" namespace="/"/>
|
||||
<s:url var="storeMessageAcrossRequestExample" namespace="/validation" action="storeErrorsAcrossRequestExample"/>
|
||||
<s:url var="ajaxFormSubmitAction" namespace="/validation" action="ajaxFormSubmit!input"/>
|
||||
|
||||
<ul>
|
||||
<li><s:a href="%{fieldValidatorUrl}">Field Validators</s:a></li>
|
||||
@@ -42,6 +43,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="%{ajaxFormSubmitAction}">AJAX Form Submit</s:a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.apache.struts2.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.struts2.ServletActionContext;
|
||||
import org.apache.struts2.dispatcher.ServletActionRedirectResult;
|
||||
|
||||
/**
|
||||
* Specialized form of {@link ServletActionRedirectResult} which takes care of
|
||||
* situation that browser has a JS/AJAX context, there are no validation errors
|
||||
* and action is executed. In this case a http redirect is harmful as browsers
|
||||
* don't pass them to JS handlers. So this result produces a JSON response
|
||||
* containing redirect data.
|
||||
*
|
||||
*<p>
|
||||
* To be used along with {@link JSONValidationInterceptor}.
|
||||
*</p>
|
||||
*<p>
|
||||
* Response JSON looks like this:
|
||||
* <pre>{"location": "$redirect url$"}</pre>
|
||||
*</p>
|
||||
*
|
||||
*/
|
||||
public class JSONActionRedirectResult extends ServletActionRedirectResult {
|
||||
|
||||
private static final long serialVersionUID = 3107276294073879542L;
|
||||
|
||||
@Override
|
||||
protected void sendRedirect(HttpServletResponse response, String finalLocation) throws IOException {
|
||||
if (sendJsonInsteadOfRedirect()) {
|
||||
printJson(response, finalLocation);
|
||||
} else {
|
||||
super.sendRedirect(response, finalLocation);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If browser has called action in a JS/AJAX context we cannot send a
|
||||
* redirect as response.
|
||||
*
|
||||
* @return true if a JSON response shall be generated, false if a redirect
|
||||
* shall be sent.
|
||||
*/
|
||||
static boolean sendJsonInsteadOfRedirect() {
|
||||
HttpServletRequest request = ServletActionContext.getRequest();
|
||||
return isJsonEnabled(request) && !isValidateOnly(request);
|
||||
}
|
||||
|
||||
static void printJson(HttpServletResponse response, String finalLocation) throws IOException {
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType("application/json");
|
||||
response.setHeader("Location", finalLocation);
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write("{\"location\": \"");
|
||||
writer.write(finalLocation);
|
||||
writer.write("\"}");
|
||||
writer.close();
|
||||
}
|
||||
|
||||
private static boolean isJsonEnabled(HttpServletRequest request) {
|
||||
return "true".equals(request.getParameter(JSONValidationInterceptor.VALIDATE_JSON_PARAM));
|
||||
}
|
||||
|
||||
private static boolean isValidateOnly(HttpServletRequest request) {
|
||||
return "true".equals(request.getParameter(JSONValidationInterceptor.VALIDATE_ONLY_PARAM));
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,8 @@ import java.util.Map;
|
||||
* <p>If the request has a parameter 'struts.validateOnly' execution will return after
|
||||
* validation (action won't be executed).</p>
|
||||
*
|
||||
* <p>If 'struts.validateOnly' is set to false you may want to use {@link JSONActionRedirectResult}.</p>
|
||||
*
|
||||
* <p>A request parameter named 'struts.enableJSONValidation' must be set to 'true' to
|
||||
* use this interceptor</p>
|
||||
*
|
||||
@@ -72,8 +74,8 @@ public class JSONValidationInterceptor extends MethodFilterInterceptor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JSONValidationInterceptor.class);
|
||||
|
||||
private static final String VALIDATE_ONLY_PARAM = "struts.validateOnly";
|
||||
private static final String VALIDATE_JSON_PARAM = "struts.enableJSONValidation";
|
||||
static final String VALIDATE_ONLY_PARAM = "struts.validateOnly";
|
||||
static final String VALIDATE_JSON_PARAM = "struts.enableJSONValidation";
|
||||
private static final String NO_ENCODING_SET_PARAM = "struts.JSONValidation.no.encoding";
|
||||
|
||||
private static final String DEFAULT_ENCODING = "UTF-8";
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<result-types>
|
||||
<result-type name="json" class="org.apache.struts2.json.JSONResult"/>
|
||||
<result-type name="jsonActionRedirect" class="org.apache.struts2.json.JSONActionRedirectResult"/>
|
||||
</result-types>
|
||||
|
||||
<interceptors>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
package org.apache.struts2.json;
|
||||
|
||||
import org.apache.struts2.StrutsStatics;
|
||||
import org.apache.struts2.StrutsTestCase;
|
||||
import org.apache.struts2.dispatcher.mapper.DefaultActionMapper;
|
||||
import org.apache.struts2.views.util.DefaultUrlHelper;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
|
||||
import com.opensymphony.xwork2.ActionContext;
|
||||
import com.opensymphony.xwork2.config.entities.ActionConfig;
|
||||
import com.opensymphony.xwork2.mock.MockActionInvocation;
|
||||
import com.opensymphony.xwork2.mock.MockActionProxy;
|
||||
import com.opensymphony.xwork2.util.ValueStack;
|
||||
|
||||
public class JSONActionRedirectResultTest extends StrutsTestCase {
|
||||
|
||||
MockActionInvocation invocation;
|
||||
MockHttpServletResponse response;
|
||||
MockServletContext servletContext;
|
||||
ActionContext context;
|
||||
ValueStack stack;
|
||||
MockHttpServletRequest request;
|
||||
|
||||
public void testNormalRedirect() throws Exception {
|
||||
JSONActionRedirectResult result = new JSONActionRedirectResult();
|
||||
result.setActionName("targetAction");
|
||||
result.setActionMapper(new DefaultActionMapper());
|
||||
result.setUrlHelper(new DefaultUrlHelper());
|
||||
|
||||
Object action = new Object();
|
||||
stack.push(action);
|
||||
|
||||
this.invocation.setAction(action);
|
||||
result.execute(this.invocation);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertEquals("", content);
|
||||
String location = response.getHeader("Location");
|
||||
assertEquals("/targetAction.action", location);
|
||||
assertEquals(302, response.getStatus());
|
||||
}
|
||||
|
||||
public void testJsonRedirect() throws Exception {
|
||||
JSONActionRedirectResult result = new JSONActionRedirectResult();
|
||||
result.setActionName("targetAction");
|
||||
result.setActionMapper(new DefaultActionMapper());
|
||||
result.setUrlHelper(new DefaultUrlHelper());
|
||||
|
||||
request.setParameter("struts.enableJSONValidation", "true");
|
||||
request.setParameter("struts.validateOnly", "false");
|
||||
|
||||
Object action = new Object();
|
||||
stack.push(action);
|
||||
|
||||
this.invocation.setAction(action);
|
||||
result.execute(this.invocation);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertEquals("{\"location\": \"/targetAction.action\"}", content);
|
||||
assertEquals(200, response.getStatus());
|
||||
}
|
||||
|
||||
public void testValidateOnlyFalse() throws Exception {
|
||||
JSONActionRedirectResult result = new JSONActionRedirectResult();
|
||||
result.setActionName("targetAction");
|
||||
result.setActionMapper(new DefaultActionMapper());
|
||||
result.setUrlHelper(new DefaultUrlHelper());
|
||||
|
||||
request.setParameter("struts.enableJSONValidation", "true");
|
||||
request.setParameter("struts.validateOnly", "true");
|
||||
|
||||
Object action = new Object();
|
||||
stack.push(action);
|
||||
|
||||
this.invocation.setAction(action);
|
||||
result.execute(this.invocation);
|
||||
|
||||
String content = response.getContentAsString();
|
||||
assertEquals("", content);
|
||||
String location = response.getHeader("Location");
|
||||
assertEquals("/targetAction.action", location);
|
||||
assertEquals(302, response.getStatus());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
this.response = new MockHttpServletResponse();
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.context = ActionContext.getContext();
|
||||
this.context.put(StrutsStatics.HTTP_RESPONSE, this.response);
|
||||
this.context.put(StrutsStatics.HTTP_REQUEST, this.request);
|
||||
this.stack = context.getValueStack();
|
||||
this.servletContext = new MockServletContext();
|
||||
this.context.put(StrutsStatics.SERVLET_CONTEXT, this.servletContext);
|
||||
this.invocation = new MockActionInvocation();
|
||||
this.invocation.setInvocationContext(this.context);
|
||||
this.invocation.setStack(this.stack);
|
||||
MockActionProxy mockActionProxy = new MockActionProxy();
|
||||
mockActionProxy.setConfig(new ActionConfig.Builder(null, null, null).build());
|
||||
this.invocation.setProxy(mockActionProxy);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user