Open Source Repository

Home /struts2/struts2-core-2.1.8 | Repository Home



org/apache/struts2/interceptor/validation/JSONValidationInterceptor.java
/*
 * $Id: JSONValidationInterceptor.java 756478 2009-03-20 14:19:46Z musachy $
 *
 * 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.interceptor.validation;

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts2.ServletActionContext;
import org.apache.commons.lang.xwork.StringEscapeUtils;

import com.opensymphony.xwork2.Action;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.ValidationAware;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
 <p>Serializes validation and action errors into JSON. This interceptor does not
 * perform any validation, so it must follow the 'validation' interceptor on the stack.
 </p>
 *
 <p>This stack (defined in struts-default.xml) shows how to use this interceptor with the
 * 'validation' interceptor</p>
 <pre>
 * &lt;interceptor-stack name="jsonValidationWorkflowStack"&gt;
 *      &lt;interceptor-ref name="basicStack"/&gt;
 *      &lt;interceptor-ref name="validation"&gt;
 *            &lt;param name="excludeMethods"&gt;input,back,cancel&lt;/param&gt;
 *      &lt;/interceptor-ref&gt;
 *      &lt;interceptor-ref name="jsonValidation"/&gt;
 *      &lt;interceptor-ref name="workflow"/&gt;
 * &lt;/interceptor-stack&gt;
 </pre>
 <p>If 'validationFailedStatus' is set it will be used as the Response status
 * when validation fails.</p>
 *
 <p>If the request has a parameter 'struts.validateOnly' execution will return after
 * validation (action won't be executed).</p>
 *
 <p>A request parameter named 'enableJSONValidation' must be set to 'true' to
 * use this interceptor</p>
 */
public class JSONValidationInterceptor extends MethodFilterInterceptor {
    private static final Logger LOG = LoggerFactory.getLogger(JSONValidationInterceptor.class);

    private static final String VALIDATE_ONLY_PARAM = "struts.validateOnly";
    private static final String VALIDATE_JSON_PARAM = "struts.enableJSONValidation";

    private int validationFailedStatus = -1;

    /**
     * HTTP status that will be set in the response if validation fails
     @param validationFailedStatus
     */
    public void setValidationFailedStatus(int validationFailedStatus) {
        this.validationFailedStatus = validationFailedStatus;
    }

    @Override
    protected String doIntercept(ActionInvocation invocationthrows Exception {
        HttpServletResponse response = ServletActionContext.getResponse();
        HttpServletRequest request = ServletActionContext.getRequest();

        Object action = invocation.getAction();
        String jsonEnabled = request.getParameter(VALIDATE_JSON_PARAM);

        if (jsonEnabled != null && "true".equals(jsonEnabled)) {
            if (action instanceof ValidationAware) {
                // generate json
                ValidationAware validationAware = (ValidationAwareaction;
                if (validationAware.hasErrors()) {
                    if (validationFailedStatus >= 0)
                        response.setStatus(validationFailedStatus);
                    response.setCharacterEncoding("UTF-8");
                    response.getWriter().print(buildResponse(validationAware));
                    response.setContentType("application/json");
                    return Action.NONE;
                }
            }

            String validateOnly = request.getParameter(VALIDATE_ONLY_PARAM);
            if (validateOnly != null && "true".equals(validateOnly)) {
                //there were no errors
                response.setCharacterEncoding("UTF-8");
                response.getWriter().print("/* {} */");
                response.setContentType("application/json");
                return Action.NONE;
            else {
                return invocation.invoke();
            }
        else
            return invocation.invoke();
    }

    /**
     @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
                    sb.append(validationAware instanceof ModelDriven ? fieldError.getKey().substring(6)
                            : fieldError.getKey());
                    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.escapeJavaScript(value));
            sb.append("\",");
        }
        if (values.size() 0)
            sb.deleteCharAt(sb.length() 1);
        sb.append("]");
        return sb.toString();
    }
}