Java tutorial
/* * Copyright 2012-2015 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.boot.bind; import java.beans.PropertyDescriptor; import java.util.LinkedHashSet; import java.util.Locale; import java.util.Map; import java.util.Properties; 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.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.PropertySources; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.DataBinder; import org.springframework.validation.ObjectError; import org.springframework.validation.Validator; /** * Validate some {@link Properties} (or optionally {@link PropertySources}) by binding * them to an object of a specified type and then optionally running a {@link Validator} * over it. * * @param <T> The target type * @author Dave Syer */ public class PropertiesConfigurationFactory<T> implements FactoryBean<T>, MessageSourceAware, InitializingBean { private static final char[] EXACT_DELIMITERS = { '_', '.', '[' }; private static final char[] TARGET_NAME_DELIMITERS = { '_', '.' }; private final Log logger = LogFactory.getLog(getClass()); private boolean ignoreUnknownFields = true; private boolean ignoreInvalidFields; private boolean exceptionIfInvalid = true; private Properties properties; private PropertySources propertySources; private final T target; private Validator validator; private MessageSource messageSource; private boolean hasBeenBound = false; private boolean ignoreNestedProperties = false; private String targetName; private ConversionService conversionService; /** * Create a new {@link PropertiesConfigurationFactory} instance. * @param target the target object to bind too * @see #PropertiesConfigurationFactory(Class) */ public PropertiesConfigurationFactory(T target) { Assert.notNull(target); this.target = target; } /** * Create a new {@link PropertiesConfigurationFactory} instance. * @param type the target type * @see #PropertiesConfigurationFactory(Class) */ @SuppressWarnings("unchecked") public PropertiesConfigurationFactory(Class<?> type) { Assert.notNull(type); this.target = (T) BeanUtils.instantiate(type); } /** * Flag to disable binding of nested properties (i.e. those with period separators in * their paths). Can be useful to disable this if the name prefix is empty and you * don't want to ignore unknown fields. * @param ignoreNestedProperties the flag to set (default false) */ public void setIgnoreNestedProperties(boolean ignoreNestedProperties) { this.ignoreNestedProperties = ignoreNestedProperties; } /** * Set whether to ignore unknown fields, that is, whether to ignore bind parameters * that do not have corresponding fields in the target object. * <p> * Default is "true". Turn this off to enforce that all bind parameters must have a * matching field in the target object. * @param ignoreUnknownFields if unknown fields should be ignored */ public void setIgnoreUnknownFields(boolean ignoreUnknownFields) { this.ignoreUnknownFields = ignoreUnknownFields; } /** * Set whether to ignore invalid fields, that is, whether to ignore bind parameters * that have corresponding fields in the target object which are not accessible (for * example because of null values in the nested path). * <p> * Default is "false". Turn this on to ignore bind parameters for nested objects in * non-existing parts of the target object graph. * @param ignoreInvalidFields if invalid fields should be ignored */ public void setIgnoreInvalidFields(boolean ignoreInvalidFields) { this.ignoreInvalidFields = ignoreInvalidFields; } /** * Set the target name. * @param targetName the target name */ public void setTargetName(String targetName) { this.targetName = targetName; } /** * Set the message source. * @param messageSource the message source */ @Override public void setMessageSource(MessageSource messageSource) { this.messageSource = messageSource; } /** * Set the properties. * @param properties the properties */ public void setProperties(Properties properties) { this.properties = properties; } /** * Set the property sources. * @param propertySources the property sources */ public void setPropertySources(PropertySources propertySources) { this.propertySources = propertySources; } /** * Set the conversion service. * @param conversionService the conversion service */ public void setConversionService(ConversionService conversionService) { this.conversionService = conversionService; } /** * Set the validator. * @param validator the validator */ public void setValidator(Validator validator) { this.validator = validator; } /** * Set a flag to indicate that an exception should be raised if a Validator is * available and validation fails. * @param exceptionIfInvalid the flag to set */ public void setExceptionIfInvalid(boolean exceptionIfInvalid) { this.exceptionIfInvalid = exceptionIfInvalid; } @Override public void afterPropertiesSet() throws Exception { bindPropertiesToTarget(); } @Override public Class<?> getObjectType() { if (this.target == null) { return Object.class; } return this.target.getClass(); } @Override public boolean isSingleton() { return true; } @Override public T getObject() throws Exception { if (!this.hasBeenBound) { bindPropertiesToTarget(); } return this.target; } public void bindPropertiesToTarget() throws BindException { Assert.state(this.properties != null || this.propertySources != null, "Properties or propertySources should not be null"); try { if (this.logger.isTraceEnabled()) { if (this.properties != null) { this.logger.trace("Properties:\n" + this.properties); } else { this.logger.trace("Property Sources: " + this.propertySources); } } this.hasBeenBound = true; doBindPropertiesToTarget(); } catch (BindException ex) { if (this.exceptionIfInvalid) { throw ex; } this.logger.error("Failed to load Properties validation bean. " + "Your Properties may be invalid.", ex); } } private void doBindPropertiesToTarget() throws BindException { RelaxedDataBinder dataBinder = (this.targetName != null ? new RelaxedDataBinder(this.target, this.targetName) : new RelaxedDataBinder(this.target)); if (this.validator != null) { dataBinder.setValidator(this.validator); } if (this.conversionService != null) { dataBinder.setConversionService(this.conversionService); } dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties); dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields); dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields); customizeBinder(dataBinder); Set<String> names = getNames(); PropertyValues propertyValues = getPropertyValues(names); dataBinder.bind(propertyValues); if (this.validator != null) { validate(dataBinder); } } private Set<String> getNames() { Set<String> names = new LinkedHashSet<String>(); if (this.target != null) { Iterable<String> prefixes = (StringUtils.hasLength(this.targetName) ? new RelaxedNames(this.targetName) : null); PropertyDescriptor[] descriptors = BeanUtils.getPropertyDescriptors(this.target.getClass()); for (PropertyDescriptor descriptor : descriptors) { String name = descriptor.getName(); if (!name.equals("class")) { RelaxedNames relaxedNames = RelaxedNames.forCamelCase(name); if (prefixes == null) { for (String relaxedName : relaxedNames) { names.add(relaxedName); } } else { for (String prefix : prefixes) { for (String relaxedName : relaxedNames) { names.add(prefix + "." + relaxedName); names.add(prefix + "_" + relaxedName); } } } } } } return names; } private PropertyValues getPropertyValues(Set<String> names) { if (this.properties != null) { return new MutablePropertyValues(this.properties); } return getPropertySourcesPropertyValues(names); } private PropertyValues getPropertySourcesPropertyValues(Set<String> names) { PropertyNamePatternsMatcher includes = getPropertyNamePatternsMatcher(names); return new PropertySourcesPropertyValues(this.propertySources, names, includes); } private PropertyNamePatternsMatcher getPropertyNamePatternsMatcher(Set<String> names) { if (this.ignoreUnknownFields && !isMapTarget()) { // Since unknown fields are ignored we can filter them out early to save // unnecessary calls to the PropertySource. return new DefaultPropertyNamePatternsMatcher(EXACT_DELIMITERS, true, names); } if (this.targetName != null) { // We can filter properties to those starting with the target name, but // we can't do a complete filter since we need to trigger the // unknown fields check return new DefaultPropertyNamePatternsMatcher(TARGET_NAME_DELIMITERS, true, this.targetName); } // Not ideal, we basically can't filter anything return PropertyNamePatternsMatcher.ALL; } private boolean isMapTarget() { return this.target != null && Map.class.isAssignableFrom(this.target.getClass()); } private void validate(RelaxedDataBinder dataBinder) throws BindException { dataBinder.validate(); BindingResult errors = dataBinder.getBindingResult(); if (errors.hasErrors()) { this.logger.error("Properties configuration failed validation"); for (ObjectError error : errors.getAllErrors()) { this.logger.error(this.messageSource != null ? this.messageSource.getMessage(error, Locale.getDefault()) + " (" + error + ")" : error); } if (this.exceptionIfInvalid) { throw new BindException(errors); } } } /** * Customize the databinder. * @param dataBinder the data binder that will be used to bind and validate */ protected void customizeBinder(DataBinder dataBinder) { } }