Package org.springframework.springfaces.mvc.bind

Source Code of org.springframework.springfaces.mvc.bind.ReverseDataBinder

/*
* Copyright 2010-2012 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.springfaces.mvc.bind;

import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.util.HashSet;
import java.util.Set;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.PropertyAccessorUtils;
import org.springframework.beans.PropertyEditorRegistrySupport;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.support.ConvertingPropertyEditorAdapter;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.DataBinder;

/**
* Utility class that can be used to perform a reverse bind for a given {@link DataBinder}. This class can be used to
* obtain {@link PropertyValues} for a given a {@link DataBinder} based on the current values of its <tt>target</tt> or
* perform a simple reverse conversion for plain parameter values when the binders <tt>target</tt> is <tt>null</tt>.
*
* @author Phillip Webb
*/
public class ReverseDataBinder {

  Log logger = LogFactory.getLog(getClass());

  /**
   * Set of properties that are always skipped.
   */
  private static final Set<String> SKIPPED_PROPERTIES;
  static {
    SKIPPED_PROPERTIES = new HashSet<String>();
    SKIPPED_PROPERTIES.add("class");
  }

  private DataBinder dataBinder;

  private SimpleTypeConverter simpleTypeConverter;

  private boolean skipDefaultValues = true;

  /**
   * Default constructor.
   * @param dataBinder a non null dataBinder
   */
  public ReverseDataBinder(DataBinder dataBinder) {
    Assert.notNull(dataBinder, "DataBinder must not be null");
    this.dataBinder = dataBinder;
  }

  /**
   * Reverse convert a simple object value.
   * @param value the value to convert
   * @return the converted value
   */
  public String reverseConvert(Object value) {
    if (value == null) {
      return null;
    }
    PropertyEditor propertyEditor = findEditor(null, null, null, value.getClass(), TypeDescriptor.forObject(value));
    return convertToStringUsingPropertyEditor(value, propertyEditor);
  }

  /**
   * Perform the reverse bind on the <tt>dataBinder</tt> provided in the constructor. Note: Calling with method will
   * also trigger a <tt>bind</tt> operation on the <tt>dataBinder</tt>. This method returns {@link PropertyValues}
   * containing a name/value pairs for each property that can be bound. Property values are encoded as Strings using
   * the property editors bound to the original dataBinder.
   * @return property values that could be re-bound using the data binder
   * @throws IllegalStateException if the target object values cannot be bound
   */
  public PropertyValues reverseBind() {
    Assert.notNull(this.dataBinder.getTarget(),
        "ReverseDataBinder.reverseBind can only be used with a DataBinder that has a target object");

    MutablePropertyValues rtn = new MutablePropertyValues();
    BeanWrapper target = PropertyAccessorFactory.forBeanPropertyAccess(this.dataBinder.getTarget());

    ConversionService conversionService = this.dataBinder.getConversionService();
    if (conversionService != null) {
      target.setConversionService(conversionService);
    }

    PropertyDescriptor[] propertyDescriptors = target.getPropertyDescriptors();

    BeanWrapper defaultValues = null;
    if (this.skipDefaultValues) {
      defaultValues = newDefaultTargetValues(this.dataBinder.getTarget());
    }

    for (int i = 0; i < propertyDescriptors.length; i++) {
      PropertyDescriptor property = propertyDescriptors[i];
      String propertyName = PropertyAccessorUtils.canonicalPropertyName(property.getName());
      Object propertyValue = target.getPropertyValue(propertyName);

      if (isSkippedProperty(property)) {
        continue;
      }

      if (!isMutableProperty(property)) {
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Ignoring '" + propertyName + "' due to missing read/write methods");
        }
        continue;
      }

      if (defaultValues != null
          && ObjectUtils.nullSafeEquals(defaultValues.getPropertyValue(propertyName), propertyValue)) {
        if (this.logger.isDebugEnabled()) {
          this.logger.debug("Skipping '" + propertyName + "' as property contains default value");
        }
        continue;
      }

      // Find a property editor
      PropertyEditorRegistrySupport propertyEditorRegistrySupport = null;
      if (target instanceof PropertyEditorRegistrySupport) {
        propertyEditorRegistrySupport = (PropertyEditorRegistrySupport) target;
      }

      PropertyEditor propertyEditor = findEditor(propertyName, propertyEditorRegistrySupport,
          target.getWrappedInstance(), target.getPropertyType(propertyName),
          target.getPropertyTypeDescriptor(propertyName));

      // Convert and store the value
      String convertedPropertyValue = convertToStringUsingPropertyEditor(propertyValue, propertyEditor);
      if (convertedPropertyValue != null) {
        rtn.addPropertyValue(propertyName, convertedPropertyValue);
      }
    }

    this.dataBinder.bind(rtn);
    BindingResult bindingResult = this.dataBinder.getBindingResult();
    if (bindingResult.hasErrors()) {
      throw new IllegalStateException("Unable to reverse bind from target '" + this.dataBinder.getObjectName()
          + "', the properties '" + rtn + "' will result in binding errors when re-bound "
          + bindingResult.getAllErrors());
    }
    return rtn;
  }

  /**
   * Find a property editor by searching custom editors or falling back to default editors.
   * @param propertyName the property name or <tt>null</tt> if looking for an editor for all properties of the given
   * type
   * @param propertyEditorRegistrySupport an optional {@link PropertyEditorRegistrySupport} instance. If <tt>null</tt>
   * a {@link SimpleTypeConverter} instance will be used
   * @param targetObject the target object or <tt>null</tt>
   * @param requiredType the required type.
   * @param typeDescriptor the type descriptor
   * @return the corresponding editor, or <code>null</code> if none
   */
  protected PropertyEditor findEditor(String propertyName,
      PropertyEditorRegistrySupport propertyEditorRegistrySupport, Object targetObject, Class<?> requiredType,
      TypeDescriptor typeDescriptor) {

    Assert.notNull(requiredType, "RequiredType must not be null");
    Assert.notNull(typeDescriptor, "TypeDescription must not be null");

    // Use the custom editor if there is one
    PropertyEditor editor = this.dataBinder.findCustomEditor(requiredType, propertyName);
    if (editor != null) {
      return editor;
    }

    // Use the conversion service
    ConversionService conversionService = this.dataBinder.getConversionService();
    if (conversionService != null) {
      if (conversionService.canConvert(TypeDescriptor.valueOf(String.class), typeDescriptor)) {
        return new ConvertingPropertyEditorAdapter(conversionService, typeDescriptor);
      }
    }

    // Fall back to default editors
    if (propertyEditorRegistrySupport == null) {
      propertyEditorRegistrySupport = getSimpleTypeConverter();
    }
    return findDefaultEditor(propertyEditorRegistrySupport, targetObject, requiredType, typeDescriptor);
  }

  /**
   * Gets the {@link SimpleTypeConverter} that should be used for conversion.
   * @return the simple type converter
   */
  protected SimpleTypeConverter getSimpleTypeConverter() {
    if (this.simpleTypeConverter == null) {
      this.simpleTypeConverter = new SimpleTypeConverter();
    }
    return this.simpleTypeConverter;
  }

  /**
   * Find a default editor for the given type. This code is based on <tt>TypeConverterDelegate.findDefaultEditor</tt>.
   * @param requiredType the type to find an editor for
   * @param typeDescriptor the type description of the property
   * @return the corresponding editor, or <code>null</code> if none
   *
   * @param propertyEditorRegistry
   * @param targetObject
   *
   * @author Juergen Hoeller
   * @author Rob Harrop
   */
  protected PropertyEditor findDefaultEditor(PropertyEditorRegistrySupport propertyEditorRegistry,
      Object targetObject, Class<?> requiredType, TypeDescriptor typeDescriptor) {
    PropertyEditor editor = null;
    if (requiredType != null) {
      // No custom editor -> check BeanWrapperImpl's default editors.
      editor = propertyEditorRegistry.getDefaultEditor(requiredType);
      if (editor == null && !String.class.equals(requiredType)) {
        // No BeanWrapper default editor -> check standard JavaBean editor.
        editor = BeanUtils.findEditorByConvention(requiredType);
      }
    }
    return editor;
  }

  /**
   * Utility method to convert a given value into a string using a property editor.
   * @param value the value to convert (can be <tt>null</tt>)
   * @param propertyEditor the property editor or <tt>null</tt> if no suitable property editor exists
   * @return the converted value
   */
  private String convertToStringUsingPropertyEditor(Object value, PropertyEditor propertyEditor) {
    if (propertyEditor != null) {
      propertyEditor.setValue(value);
      return propertyEditor.getAsText();
    }
    if (value instanceof String) {
      return value == null ? null : value.toString();
    }
    return null;
  }

  private BeanWrapper newDefaultTargetValues(Object target) {
    try {
      Object defaultValues = target.getClass().newInstance();
      return PropertyAccessorFactory.forBeanPropertyAccess(defaultValues);
    } catch (Exception e) {
      this.logger.warn("Unable to construct default values target instance for class " + target.getClass()
          + ", default values will not be skipped");
      return null;
    }
  }

  /**
   * Determine if a property should be skipped. Used to ignore object properties.
   * @param property the property descriptor
   * @return <tt>true</tt> if the property is skipped
   */
  private boolean isSkippedProperty(PropertyDescriptor property) {
    return SKIPPED_PROPERTIES.contains(property.getName());
  }

  /**
   * Determine if a property contains both read and write methods.
   * @param descriptor the property descriptor
   * @return <tt>true</tt> if the property is mutable
   */
  private boolean isMutableProperty(PropertyDescriptor descriptor) {
    return descriptor.getReadMethod() != null && descriptor.getWriteMethod() != null;
  }

  /**
   * Skip any bound values when the current value is identical to the value of a newly constructed instance. This
   * setting can help to reduce the number of superfluous bound properties. Note: If the target object class does not
   * have a default (no-args) constructor this setting will be ignored. The default setting is <tt>true</tt>.
   * @param skipDefaultValues <tt>true</tt> if default properties should be ignored, otherwise <tt>false</tt>
   */
  public void setSkipDefaultValues(boolean skipDefaultValues) {
    this.skipDefaultValues = skipDefaultValues;
  }
}
TOP

Related Classes of org.springframework.springfaces.mvc.bind.ReverseDataBinder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.