1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.interceptor.validation;
23
24 import java.text.CharacterIterator;
25 import java.text.StringCharacterIterator;
26 import java.util.Collection;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.servlet.http.HttpServletRequest;
31 import javax.servlet.http.HttpServletResponse;
32
33 import org.apache.struts2.ServletActionContext;
34 import org.apache.commons.lang.xwork.StringEscapeUtils;
35
36 import com.opensymphony.xwork2.Action;
37 import com.opensymphony.xwork2.ActionInvocation;
38 import com.opensymphony.xwork2.ModelDriven;
39 import com.opensymphony.xwork2.ValidationAware;
40 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
41 import com.opensymphony.xwork2.util.logging.Logger;
42 import com.opensymphony.xwork2.util.logging.LoggerFactory;
43
44 /***
45 * <p>Serializes validation and action errors into JSON. This interceptor does not
46 * perform any validation, so it must follow the 'validation' interceptor on the stack.
47 * </p>
48 *
49 * <p>This stack (defined in struts-default.xml) shows how to use this interceptor with the
50 * 'validation' interceptor</p>
51 * <pre>
52 * <interceptor-stack name="jsonValidationWorkflowStack">
53 * <interceptor-ref name="basicStack"/>
54 * <interceptor-ref name="validation">
55 * <param name="excludeMethods">input,back,cancel</param>
56 * </interceptor-ref>
57 * <interceptor-ref name="jsonValidation"/>
58 * <interceptor-ref name="workflow"/>
59 * </interceptor-stack>
60 * </pre>
61 * <p>If 'validationFailedStatus' is set it will be used as the Response status
62 * when validation fails.</p>
63 *
64 * <p>If the request has a parameter 'struts.validateOnly' execution will return after
65 * validation (action won't be executed).</p>
66 *
67 * <p>A request parameter named 'enableJSONValidation' must be set to 'true' to
68 * use this interceptor</p>
69 */
70 public class JSONValidationInterceptor extends MethodFilterInterceptor {
71 private static final Logger LOG = LoggerFactory.getLogger(JSONValidationInterceptor.class);
72
73 private static final String VALIDATE_ONLY_PARAM = "struts.validateOnly";
74 private static final String VALIDATE_JSON_PARAM = "struts.enableJSONValidation";
75
76 private int validationFailedStatus = -1;
77
78 /***
79 * HTTP status that will be set in the response if validation fails
80 * @param validationFailedStatus
81 */
82 public void setValidationFailedStatus(int validationFailedStatus) {
83 this.validationFailedStatus = validationFailedStatus;
84 }
85
86 @Override
87 protected String doIntercept(ActionInvocation invocation) throws Exception {
88 HttpServletResponse response = ServletActionContext.getResponse();
89 HttpServletRequest request = ServletActionContext.getRequest();
90
91 Object action = invocation.getAction();
92 String jsonEnabled = request.getParameter(VALIDATE_JSON_PARAM);
93
94 if (jsonEnabled != null && "true".equals(jsonEnabled)) {
95 if (action instanceof ValidationAware) {
96
97 ValidationAware validationAware = (ValidationAware) action;
98 if (validationAware.hasErrors()) {
99 if (validationFailedStatus >= 0)
100 response.setStatus(validationFailedStatus);
101 response.setCharacterEncoding("UTF-8");
102 response.getWriter().print(buildResponse(validationAware));
103 response.setContentType("application/json");
104 return Action.NONE;
105 }
106 }
107
108 String validateOnly = request.getParameter(VALIDATE_ONLY_PARAM);
109 if (validateOnly != null && "true".equals(validateOnly)) {
110
111 response.setCharacterEncoding("UTF-8");
112 response.getWriter().print("/* {} */");
113 response.setContentType("application/json");
114 return Action.NONE;
115 } else {
116 return invocation.invoke();
117 }
118 } else
119 return invocation.invoke();
120 }
121
122 /***
123 * @return JSON string that contains the errors and field errors
124 */
125 @SuppressWarnings("unchecked")
126 protected String buildResponse(ValidationAware validationAware) {
127
128 StringBuilder sb = new StringBuilder();
129 sb.append("/* { ");
130
131 if (validationAware.hasErrors()) {
132
133 if (validationAware.hasActionErrors()) {
134 sb.append("\"errors\":");
135 sb.append(buildArray(validationAware.getActionErrors()));
136 }
137
138
139 if (validationAware.hasFieldErrors()) {
140 if (validationAware.hasActionErrors())
141 sb.append(",");
142 sb.append("\"fieldErrors\": {");
143 Map<String, List<String>> fieldErrors = validationAware
144 .getFieldErrors();
145 for (Map.Entry<String, List<String>> fieldError : fieldErrors
146 .entrySet()) {
147 sb.append("\"");
148
149 sb.append(validationAware instanceof ModelDriven ? fieldError.getKey().substring(6)
150 : fieldError.getKey());
151 sb.append("\":");
152 sb.append(buildArray(fieldError.getValue()));
153 sb.append(",");
154 }
155
156 sb.deleteCharAt(sb.length() - 1);
157 sb.append("}");
158 }
159 }
160
161 sb.append("} */");
162
163
164
165
166
167
168
169
170
171 return sb.toString();
172 }
173
174 private String buildArray(Collection<String> values) {
175 StringBuilder sb = new StringBuilder();
176 sb.append("[");
177 for (String value : values) {
178 sb.append("\"");
179 sb.append(StringEscapeUtils.escapeJavaScript(value));
180 sb.append("\",");
181 }
182 if (values.size() > 0)
183 sb.deleteCharAt(sb.length() - 1);
184 sb.append("]");
185 return sb.toString();
186 }
187 }