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
35 import com.opensymphony.xwork2.Action;
36 import com.opensymphony.xwork2.ActionInvocation;
37 import com.opensymphony.xwork2.ValidationAware;
38 import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
39 import com.opensymphony.xwork2.util.logging.Logger;
40 import com.opensymphony.xwork2.util.logging.LoggerFactory;
41
42 /***
43 * <p>Serializes validation and action errors into JSON. This interceptor does not
44 * perform any validation, so it must follow the 'validation' interceptor on the stack.
45 * </p>
46 *
47 * <p>This stack (defined in struts-default.xml) shows how to use this interceptor with the
48 * 'validation' interceptor</p>
49 * <pre>
50 * <interceptor-stack name="jsonValidationWorkflowStack">
51 * <interceptor-ref name="basicStack"/>
52 * <interceptor-ref name="validation">
53 * <param name="excludeMethods">input,back,cancel</param>
54 * </interceptor-ref>
55 * <interceptor-ref name="jsonValidation"/>
56 * <interceptor-ref name="workflow"/>
57 * </interceptor-stack>
58 * </pre>
59 * <p>If 'validationFailedStatus' is set it will be used as the Response status
60 * when validation fails.</p>
61 *
62 * <p>If the request has a parameter 'struts.validateOnly' execution will return after
63 * validation (action won't be executed).</p>
64 *
65 * <p>A request parameter named 'enableJSONValidation' must be set to 'true' to
66 * use this interceptor</p>
67 */
68 public class JSONValidationInterceptor extends MethodFilterInterceptor {
69 private static final Logger LOG = LoggerFactory.getLogger(JSONValidationInterceptor.class);
70
71 private static final String VALIDATE_ONLY_PARAM = "struts.validateOnly";
72 private static final String VALIDATE_JSON_PARAM = "struts.enableJSONValidation";
73
74 static char[] hex = "0123456789ABCDEF".toCharArray();
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.getWriter().print(buildResponse(validationAware));
102 response.setContentType("application/json");
103 return Action.NONE;
104 }
105 }
106
107 String validateOnly = request.getParameter(VALIDATE_ONLY_PARAM);
108 if (validateOnly != null && "true".equals(validateOnly)) {
109
110 response.getWriter().print("/* {} */");
111 response.setContentType("application/json");
112 return Action.NONE;
113 } else {
114 return invocation.invoke();
115 }
116 } else
117 return invocation.invoke();
118 }
119
120 /***
121 * @return JSON string that contains the errors and field errors
122 */
123 @SuppressWarnings("unchecked")
124 protected String buildResponse(ValidationAware validationAware) {
125
126 StringBuilder sb = new StringBuilder();
127 sb.append("/* { ");
128
129 if (validationAware.hasErrors()) {
130
131 if (validationAware.hasActionErrors()) {
132 sb.append("\"errors\":");
133 sb.append(buildArray(validationAware.getActionErrors()));
134 }
135
136
137 if (validationAware.hasFieldErrors()) {
138 if (validationAware.hasActionErrors())
139 sb.append(",");
140 sb.append("\"fieldErrors\": {");
141 Map<String, List<String>> fieldErrors = validationAware
142 .getFieldErrors();
143 for (Map.Entry<String, List<String>> fieldError : fieldErrors
144 .entrySet()) {
145 sb.append("\"");
146 sb.append(fieldError.getKey());
147 sb.append("\":");
148 sb.append(buildArray(fieldError.getValue()));
149 sb.append(",");
150 }
151
152 sb.deleteCharAt(sb.length() - 1);
153 sb.append("}");
154 }
155 }
156
157 sb.append("} */");
158
159
160
161
162
163
164
165
166
167 return sb.toString();
168 }
169
170 private String buildArray(Collection<String> values) {
171 StringBuilder sb = new StringBuilder();
172 sb.append("[");
173 for (String value : values) {
174 sb.append("\"");
175 sb.append(escapeJSON(value));
176 sb.append("\",");
177 }
178 if (values.size() > 0)
179 sb.deleteCharAt(sb.length() - 1);
180 sb.append("]");
181 return sb.toString();
182 }
183
184 private String escapeJSON(Object obj) {
185 StringBuilder sb = new StringBuilder();
186
187 CharacterIterator it = new StringCharacterIterator(obj.toString());
188
189 for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
190 if (c == '"') {
191 sb.append("//\"");
192 } else if (c == '//') {
193 sb.append("////");
194 } else if (c == '/') {
195 sb.append("///");
196 } else if (c == '\b') {
197 sb.append("//b");
198 } else if (c == '\f') {
199 sb.append("//f");
200 } else if (c == '\n') {
201 sb.append("//n");
202 } else if (c == '\r') {
203 sb.append("//r");
204 } else if (c == '\t') {
205 sb.append("//t");
206 } else if (Character.isISOControl(c)) {
207 sb.append(unicode(c));
208 } else {
209 sb.append(c);
210 }
211 }
212 return sb.toString();
213 }
214
215 /***
216 * Represent as unicode
217 * @param c character to be encoded
218 */
219 private String unicode(char c) {
220 StringBuilder sb = new StringBuilder();
221 sb.append("//u");
222
223 int n = c;
224
225 for (int i = 0; i < 4; ++i) {
226 int digit = (n & 0xf000) >> 12;
227
228 sb.append(hex[digit]);
229 n <<= 4;
230 }
231 return sb.toString();
232 }
233
234 }