| /* |
| * 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.json; |
| |
| import com.opensymphony.xwork2.Action; |
| import com.opensymphony.xwork2.ActionInvocation; |
| import com.opensymphony.xwork2.ModelDriven; |
| import com.opensymphony.xwork2.interceptor.ValidationAware; |
| import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor; |
| import org.apache.logging.log4j.Logger; |
| import org.apache.logging.log4j.LogManager; |
| import org.apache.commons.text.StringEscapeUtils; |
| import org.apache.struts2.ServletActionContext; |
| |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| import java.io.IOException; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * Serializes validation and action errors into JSON. This interceptor does not |
| * perform any validation, so it must follow the 'validation' interceptor on the stack. |
| */ |
| public class JSONValidationInterceptor extends MethodFilterInterceptor { |
| |
| private static final Logger LOG = LogManager.getLogger(JSONValidationInterceptor.class); |
| |
| public static final String VALIDATE_ONLY_PARAM = "struts.validateOnly"; |
| public static final String VALIDATE_JSON_PARAM = "struts.enableJSONValidation"; |
| public static final String NO_ENCODING_SET_PARAM = "struts.JSONValidation.no.encoding"; |
| |
| public static final String DEFAULT_ENCODING = "UTF-8"; |
| |
| private int validationFailedStatus = HttpServletResponse.SC_BAD_REQUEST; |
| private String validateOnlyParam = VALIDATE_ONLY_PARAM; |
| private String validateJsonParam = VALIDATE_JSON_PARAM; |
| private String noEncodingSetParam = NO_ENCODING_SET_PARAM; |
| |
| @Override |
| protected String doIntercept(ActionInvocation invocation) throws Exception { |
| HttpServletResponse response = ServletActionContext.getResponse(); |
| HttpServletRequest request = ServletActionContext.getRequest(); |
| |
| Object action = invocation.getAction(); |
| |
| if (isJsonEnabled(request)) { |
| if (action instanceof ValidationAware) { |
| // generate json |
| ValidationAware validationAware = (ValidationAware) action; |
| if (validationAware.hasErrors()) { |
| return generateJSON(request, response, validationAware); |
| } |
| } |
| if (isValidateOnly(request)) { |
| //there were no errors |
| setupEncoding(response, request); |
| response.getWriter().print("{}"); |
| response.setContentType("application/json"); |
| return Action.NONE; |
| } else { |
| return invocation.invoke(); |
| } |
| } else |
| return invocation.invoke(); |
| } |
| |
| private void setupEncoding(HttpServletResponse response, HttpServletRequest request) { |
| if (isSetEncoding(request)) { |
| LOG.debug("Default encoding not set!"); |
| } else { |
| LOG.debug("Setting up encoding to: [{}]!", DEFAULT_ENCODING); |
| response.setCharacterEncoding(DEFAULT_ENCODING); |
| } |
| } |
| |
| private String generateJSON(HttpServletRequest request, HttpServletResponse response, ValidationAware validationAware) |
| throws IOException { |
| if (validationFailedStatus >= 0) { |
| response.setStatus(validationFailedStatus); |
| } |
| setupEncoding(response, request); |
| response.getWriter().print(buildResponse(validationAware)); |
| response.setContentType("application/json"); |
| return Action.NONE; |
| } |
| |
| public boolean isJsonEnabled(HttpServletRequest request) { |
| return Boolean.parseBoolean(request.getParameter(validateJsonParam)); |
| } |
| |
| public boolean isValidateOnly(HttpServletRequest request) { |
| return Boolean.parseBoolean(request.getParameter(validateOnlyParam)); |
| } |
| |
| public boolean isSetEncoding(HttpServletRequest request) { |
| return Boolean.parseBoolean(request.getParameter(noEncodingSetParam)); |
| } |
| |
| /** |
| * @param validationAware the validation aware object |
| * @return JSON string that contains the errors and field errors |
| */ |
| @SuppressWarnings("unchecked") |
| protected String buildResponse(ValidationAware validationAware) { |
| //should we use FreeMarker here? |
| StringBuilder sb = new StringBuilder(); |
| sb.append("{ "); |
| |
| if (validationAware.hasErrors()) { |
| //action errors |
| if (validationAware.hasActionErrors()) { |
| sb.append("\"errors\":"); |
| sb.append(buildArray(validationAware.getActionErrors())); |
| } |
| |
| //field errors |
| if (validationAware.hasFieldErrors()) { |
| if (validationAware.hasActionErrors()) |
| sb.append(","); |
| sb.append("\"fieldErrors\": {"); |
| Map<String, List<String>> fieldErrors = validationAware |
| .getFieldErrors(); |
| for (Map.Entry<String, List<String>> fieldError : fieldErrors |
| .entrySet()) { |
| sb.append("\""); |
| //if it is model driven, remove "model." see WW-2721 |
| String fieldErrorKey = fieldError.getKey(); |
| sb.append(((validationAware instanceof ModelDriven) && fieldErrorKey.startsWith("model."))? fieldErrorKey.substring(6) |
| : fieldErrorKey); |
| sb.append("\":"); |
| sb.append(buildArray(fieldError.getValue())); |
| sb.append(","); |
| } |
| //remove trailing comma, IE creates an empty object, duh |
| sb.deleteCharAt(sb.length() - 1); |
| sb.append("}"); |
| } |
| } |
| |
| sb.append("}"); |
| /*response should be something like: |
| * { |
| * "errors": ["this", "that"], |
| * "fieldErrors": { |
| * field1: "this", |
| * field2: "that" |
| * } |
| * } |
| */ |
| return sb.toString(); |
| } |
| |
| private String buildArray(Collection<String> values) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("["); |
| for (String value : values) { |
| sb.append("\""); |
| sb.append(StringEscapeUtils.escapeJson(value)); |
| sb.append("\","); |
| } |
| if (values.size() > 0) |
| sb.deleteCharAt(sb.length() - 1); |
| sb.append("]"); |
| return sb.toString(); |
| } |
| |
| /** |
| * HTTP status that will be set in the response if validation fails |
| * |
| * @param validationFailedStatus validation failed status |
| */ |
| public void setValidationFailedStatus(int validationFailedStatus) { |
| this.validationFailedStatus = validationFailedStatus; |
| } |
| |
| /** |
| * Overrides 'struts.validateOnly' param name |
| * |
| * @param validateOnlyParam new param name |
| */ |
| public void setValidateOnlyParam(String validateOnlyParam) { |
| this.validateOnlyParam = validateOnlyParam; |
| } |
| |
| /** |
| * Overrides 'struts.enableJSONValidation' param name |
| * |
| * @param validateJsonParam new param name |
| */ |
| public void setValidateJsonParam(String validateJsonParam) { |
| this.validateJsonParam = validateJsonParam; |
| } |
| |
| /** |
| * Overrides 'struts.JSONValidation.no.encoding' param name |
| * |
| * @param noEncodingSetParam new param name |
| */ |
| public void setNoEncodingSetParam(String noEncodingSetParam) { |
| this.noEncodingSetParam = noEncodingSetParam; |
| } |
| } |