blob: 4e59389047415a7f37837258a30d757faa44a2ae [file] [log] [blame]
/*
* 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;
}
}