Open Source Repository

Home /spring/spring-web-servlet-3.0.5 | Repository Home



org/springframework/web/servlet/support/BindStatus.java
/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed 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.springframework.web.servlet.support;

import java.beans.PropertyEditor;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.context.NoSuchMessageException;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.validation.ObjectError;
import org.springframework.web.util.HtmlUtils;

/**
 * Simple adapter to expose the bind status of a field or object.
 * Set as a variable both by the JSP bind tag and Velocity/FreeMarker macros.
 *
 <p>Obviously, object status representations (i.e. errors at the object level
 * rather than the field level) do not have an expression and a value but only
 * error codes and messages. For simplicity's sake and to be able to use the same
 * tags and macros, the same status class is used for both scenarios.
 *
 @author Rod Johnson
 @author Juergen Hoeller
 @author Darren Davison
 @see RequestContext#getBindStatus
 @see org.springframework.web.servlet.tags.BindTag
 @see org.springframework.web.servlet.view.AbstractTemplateView#setExposeSpringMacroHelpers
 */
public class BindStatus {

  private final RequestContext requestContext;

  private final String path;

  private final boolean htmlEscape;

  private final String expression;

  private final Errors errors;

  private BindingResult bindingResult;

  private Object value;

  private Class valueType;

  private Object actualValue;

  private PropertyEditor editor;

  private List objectErrors;

  private String[] errorCodes;

  private String[] errorMessages;


  /**
   * Create a new BindStatus instance, representing a field or object status.
   @param requestContext the current RequestContext
   @param path the bean and property path for which values and errors
   * will be resolved (e.g. "customer.address.street")
   @param htmlEscape whether to HTML-escape error messages and string values
   @throws IllegalStateException if no corresponding Errors object found
   */
  public BindStatus(RequestContext requestContext, String path, boolean htmlEscape)
      throws IllegalStateException {

    this.requestContext = requestContext;
    this.path = path;
    this.htmlEscape = htmlEscape;

    // determine name of the object and property
    String beanName = null;
    int dotPos = path.indexOf('.');
    if (dotPos == -1) {
      // property not set, only the object itself
      beanName = path;
      this.expression = null;
    }
    else {
      beanName = path.substring(0, dotPos);
      this.expression = path.substring(dotPos + 1);
    }

    this.errors = requestContext.getErrors(beanName, false);

    if (this.errors != null) {
      // Usual case: A BindingResult is available as request attribute.
      // Can determine error codes and messages for the given expression.
      // Can use a custom PropertyEditor, as registered by a form controller.
      if (this.expression != null) {
        if ("*".equals(this.expression)) {
          this.objectErrors = this.errors.getAllErrors();
        }
        else if (this.expression.endsWith("*")) {
          this.objectErrors = this.errors.getFieldErrors(this.expression);
        }
        else {
          this.objectErrors = this.errors.getFieldErrors(this.expression);
          this.value = this.errors.getFieldValue(this.expression);
          this.valueType = this.errors.getFieldType(this.expression);
          if (this.errors instanceof BindingResult) {
            this.bindingResult = (BindingResultthis.errors;
            this.actualValue = this.bindingResult.getRawFieldValue(this.expression);
            this.editor = this.bindingResult.findEditor(this.expression, null);
          }
        }
      }
      else {
        this.objectErrors = this.errors.getGlobalErrors();
      }
      initErrorCodes();
    }

    else {
      // No BindingResult available as request attribute:
      // Probably forwarded directly to a form view.
      // Let's do the best we can: extract a plain target if appropriate.
      Object target = requestContext.getModelObject(beanName);
      if (target == null) {
        throw new IllegalStateException("Neither BindingResult nor plain target object for bean name '" +
            beanName + "' available as request attribute");
      }
      if (this.expression != null && !"*".equals(this.expression&& !this.expression.endsWith("*")) {
        BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(target);
        this.valueType = bw.getPropertyType(this.expression);
        this.value = bw.getPropertyValue(this.expression);
      }
      this.errorCodes = new String[0];
      this.errorMessages = new String[0];
    }

    if (htmlEscape && this.value instanceof String) {
      this.value = HtmlUtils.htmlEscape((Stringthis.value);
    }
  }

  /**
   * Extract the error codes from the ObjectError list.
   */
  private void initErrorCodes() {
    this.errorCodes = new String[this.objectErrors.size()];
    for (int i = 0; i < this.objectErrors.size(); i++) {
      ObjectError error = (ObjectErrorthis.objectErrors.get(i);
      this.errorCodes[i= error.getCode();
    }
  }

  /**
   * Extract the error messages from the ObjectError list.
   */
  private void initErrorMessages() throws NoSuchMessageException {
    if (this.errorMessages == null) {
      this.errorMessages = new String[this.objectErrors.size()];
      for (int i = 0; i < this.objectErrors.size(); i++) {
        ObjectError error = (ObjectErrorthis.objectErrors.get(i);
        this.errorMessages[ithis.requestContext.getMessage(error, this.htmlEscape);
      }
    }
  }


  /**
   * Return the bean and property path for which values and errors
   * will be resolved (e.g. "customer.address.street").
   */
  public String getPath() {
    return this.path;
  }

  /**
   * Return a bind expression that can be used in HTML forms as input name
   * for the respective field, or <code>null</code> if not field-specific.
   <p>Returns a bind path appropriate for resubmission, e.g. "address.street".
   * Note that the complete bind path as required by the bind tag is
   * "customer.address.street", if bound to a "customer" bean.
   */
  public String getExpression() {
    return this.expression;
  }

  /**
   * Return the current value of the field, i.e. either the property value
   * or a rejected update, or <code>null</code> if not field-specific.
   <p>This value will be an HTML-escaped String if the original value
   * already was a String.
   */
  public Object getValue() {
    return this.value;
  }

  /**
   * Get the '<code>Class</code>' type of the field. Favor this instead of
   * '<code>getValue().getClass()</code>' since '<code>getValue()</code>' may
   * return '<code>null</code>'.
   */
  public Class getValueType() {
    return this.valueType;
  }

  /**
   * Return the actual value of the field, i.e. the raw property value,
   * or <code>null</code> if not available.
   */
  public Object getActualValue() {
    return this.actualValue;
  }

  /**
   * Return a suitable display value for the field, i.e. the stringified
   * value if not null, and an empty string in case of a null value.
   <p>This value will be an HTML-escaped String if the original value
   * was non-null: the <code>toString</code> result of the original value
   * will get HTML-escaped.
   */
  public String getDisplayValue() {
    if (this.value instanceof String) {
      return (Stringthis.value;
    }
    if (this.value != null) {
      return (this.htmlEscape ? HtmlUtils.htmlEscape(this.value.toString()) this.value.toString());
    }
    return "";
  }

  /**
   * Return if this status represents a field or object error.
   */
  public boolean isError() {
    return (this.errorCodes != null && this.errorCodes.length > 0);
  }

  /**
   * Return the error codes for the field or object, if any.
   * Returns an empty array instead of null if none.
   */
  public String[] getErrorCodes() {
    return this.errorCodes;
  }

  /**
   * Return the first error codes for the field or object, if any.
   */
  public String getErrorCode() {
    return (this.errorCodes.length > this.errorCodes[0"");
  }

  /**
   * Return the resolved error messages for the field or object,
   * if any. Returns an empty array instead of null if none.
   */
  public String[] getErrorMessages() {
    initErrorMessages();
    return this.errorMessages;
  }

  /**
   * Return the first error message for the field or object, if any.
   */
  public String getErrorMessage() {
    initErrorMessages();
    return (this.errorMessages.length > this.errorMessages[0"");
  }

  /**
   * Return an error message string, concatenating all messages
   * separated by the given delimiter.
   @param delimiter separator string, e.g. ", " or "<br>"
   @return the error message string
   */
  public String getErrorMessagesAsString(String delimiter) {
    initErrorMessages();
    return StringUtils.arrayToDelimitedString(this.errorMessages, delimiter);
  }

  /**
   * Return the Errors instance (typically a BindingResult) that this
   * bind status is currently associated with.
   @return the current Errors instance, or <code>null</code> if none
   @see org.springframework.validation.BindingResult
   */
  public Errors getErrors() {
    return this.errors;
  }

  /**
   * Return the PropertyEditor for the property that this bind status
   * is currently bound to.
   @return the current PropertyEditor, or <code>null</code> if none
   */
  public PropertyEditor getEditor() {
    return this.editor;
  }

  /**
   * Find a PropertyEditor for the given value class, associated with
   * the property that this bound status is currently bound to.
   @param valueClass the value class that an editor is needed for
   @return the associated PropertyEditor, or <code>null</code> if none
   */
  public PropertyEditor findEditor(Class valueClass) {
    return (this.bindingResult != null this.bindingResult.findEditor(this.expression, valueClassnull);
  }


  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder("BindStatus: ");
    sb.append("expression=[").append(this.expression).append("]; ");
    sb.append("value=[").append(this.value).append("]");
    if (isError()) {
      sb.append("; errorCodes=").append(Arrays.asList(this.errorCodes));
    }
    return sb.toString();
  }

}