Package org.springframework.xd.module.options

Source Code of org.springframework.xd.module.options.PojoModuleOptionsMetadata

/*
* Copyright 2013 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.xd.module.options;

import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.boot.bind.PropertySourcesPropertyValues;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.EnumerablePropertySource;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.DataBinder;
import org.springframework.validation.FieldError;
import org.springframework.validation.beanvalidation.CustomValidatorBean;
import org.springframework.xd.module.options.spi.ProfileNamesProvider;
import org.springframework.xd.module.options.spi.ValidationGroupsProvider;


/**
* An implementation of {@link ModuleOptionsMetadata} that derives its information from a plain old java object:
* <ul>
* <li>public setters are reported as valid options</li>
* <li>the type of the option is derived from the accepted type by the setter</li>
* </ul>
*
* {@link ModuleOptions} for such a POJO will work as follows:
* <ul>
* <li>an instance of the class will be created reflectively and injected with user provided values,</li>
* <li>reported properties are computed from all the getters,</li>
* <li>the POJO may bear JSR303 validation annotations, which will be used to validate the interpolated options,</li>
* <li>if the POJO implements {@link ProfileNamesProvider}, profile names will be gathered from a reflective call to
* {@link ProfileNamesProvider#profilesToActivate()}</li>
* </ul>
*
* @author Eric Bottard
*/
public class PojoModuleOptionsMetadata implements ModuleOptionsMetadata {

  private BeanWrapperImpl beanWrapper;

  private List<ModuleOption> options;

  /**
   * Used to perform conversion from String representation of options to actual arguments of setters.
   */
  private final ConversionService conversionService;

  public PojoModuleOptionsMetadata(Class<?> clazz) {
    this(clazz, null);
  }

  public PojoModuleOptionsMetadata(Class<?> clazz,
      ConversionService conversionService) {
    this.conversionService = conversionService;

    beanWrapper = new BeanWrapperImpl(clazz);
    options = new ArrayList<ModuleOption>();
    for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
      String name = pd.getName();
      if (!beanWrapper.isWritableProperty(name)) {
        continue;
      }
      org.springframework.xd.module.options.spi.ModuleOption annotation = pd.getWriteMethod().getAnnotation(
          org.springframework.xd.module.options.spi.ModuleOption.class);
      Assert.notNull(
          annotation,
          String.format(
              "Setter method for option '%s' needs to bear the @%s annotation and provide a description",
              name,
              org.springframework.xd.module.options.spi.ModuleOption.class.getSimpleName()));

      String description = descriptionFromAnnotation(name, annotation);
      // Don't use pd.getPropertyType(), as it considers the getter first
      // which may be different from the setter type, which is what we semantically want here
      Class<?> type = BeanUtils.getWriteMethodParameter(pd).getParameterType();
      ModuleOption option = new ModuleOption(name, description).withType(type).hidden(annotation.hidden());
      if (beanWrapper.isReadableProperty(name)) {
        option.withDefaultValue(beanWrapper.getPropertyValue(name));
      }
      else {
        option.withDefaultValue(defaultFromAnnotation(annotation));
      }
      options.add(option);
    }
  }


  private Object defaultFromAnnotation(org.springframework.xd.module.options.spi.ModuleOption annotation) {
    String value = annotation.defaultValue();
    return org.springframework.xd.module.options.spi.ModuleOption.NO_DEFAULT.equals(value) ? null : value;
  }

  /**
   * Read the description from the value() attribute of the annotation on the getter.
   */
  private String descriptionFromAnnotation(String optionName,
      org.springframework.xd.module.options.spi.ModuleOption annotation) {
    Assert.hasLength(
        annotation.value(),
        String.format(
            "Setter method for option '%s' needs to bear the @%s annotation and provide a non-empty description",
            optionName,
            org.springframework.xd.module.options.spi.ModuleOption.class.getSimpleName()));
    return annotation.value();
  }

  @Override
  public Iterator<ModuleOption> iterator() {
    return options.iterator();
  }

  @Override
  public ModuleOptions interpolate(Map<String, String> raw) throws BindException {
    bindAndValidate(raw);

    return new ModuleOptions() {

      @Override
      public EnumerablePropertySource<?> asPropertySource() {
        return new EnumerablePropertySource<BeanWrapper>(this.toString(), beanWrapper) {

          @Override
          public String[] getPropertyNames() {
            List<String> result = new ArrayList<String>();
            for (PropertyDescriptor pd : beanWrapper.getPropertyDescriptors()) {
              String name = pd.getName();
              if (beanWrapper.isReadableProperty(name) && !"class".equals(name)) {
                result.add(name);
              }
            }
            return result.toArray(new String[result.size()]);
          }

          @Override
          public Object getProperty(String name) {
            if (Arrays.asList(getPropertyNames()).contains(name)) {
              return beanWrapper.getPropertyValue(name);
            }
            else {
              return null;
            }
          }
        };
      }

      @Override
      public String[] profilesToActivate() {
        if (beanWrapper.getWrappedInstance() instanceof ProfileNamesProvider) {
          return ((ProfileNamesProvider) beanWrapper.getWrappedInstance()).profilesToActivate();
        }
        else {
          return super.profilesToActivate();
        }
      }
    };
  }

  @SuppressWarnings("unchecked")
  private void bindAndValidate(Map<String, String> raw) throws BindException {
    DataBinder dataBinder = new DataBinder(beanWrapper.getWrappedInstance());
    dataBinder.setIgnoreUnknownFields(false);
    dataBinder.setConversionService(conversionService);
    MutablePropertySources mps = new MutablePropertySources();
    mps.addFirst(new MapPropertySource("options", (Map) raw));
    try {
      dataBinder.bind(new PropertySourcesPropertyValues(mps));
    }
    catch (InvalidPropertyException e) {
      dataBinder.getBindingResult().addError(new FieldError("options", e.getPropertyName(), e.getMessage()));
    }

    CustomValidatorBean validator = new CustomValidatorBean();
    validator.afterPropertiesSet();
    dataBinder.setValidator(validator);

    Class<?>[] groups = determineGroupsToUse(beanWrapper.getWrappedInstance());
    dataBinder.validate((Object[]) groups);

    if (dataBinder.getBindingResult().hasErrors()) {
      throw new BindException(dataBinder.getBindingResult());
    }
  }

  private Class<?>[] determineGroupsToUse(Object pojo) {
    if (pojo instanceof ValidationGroupsProvider) {
      ValidationGroupsProvider groupsProvider = (ValidationGroupsProvider) pojo;
      return groupsProvider.groupsToValidate();
    }
    else {
      return ValidationGroupsProvider.DEFAULT_GROUP;
    }
  }

  @Override
  public String toString() {
    return String.format("%s backed by %s, defining options [%s]", getClass().getSimpleName(),
        beanWrapper.getWrappedClass(), options);
  }

}
TOP

Related Classes of org.springframework.xd.module.options.PojoModuleOptionsMetadata

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.