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