001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.context.properties.bind.validation; 018 019import java.util.Arrays; 020import java.util.Deque; 021import java.util.LinkedHashSet; 022import java.util.LinkedList; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import org.springframework.boot.context.properties.bind.AbstractBindHandler; 027import org.springframework.boot.context.properties.bind.BindContext; 028import org.springframework.boot.context.properties.bind.BindHandler; 029import org.springframework.boot.context.properties.bind.Bindable; 030import org.springframework.boot.context.properties.source.ConfigurationProperty; 031import org.springframework.boot.context.properties.source.ConfigurationPropertyName; 032import org.springframework.validation.BeanPropertyBindingResult; 033import org.springframework.validation.BindingResult; 034import org.springframework.validation.Validator; 035 036/** 037 * {@link BindHandler} to apply {@link Validator Validators} to bound results. 038 * 039 * @author Phillip Webb 040 * @author Madhura Bhave 041 * @since 2.0.0 042 */ 043public class ValidationBindHandler extends AbstractBindHandler { 044 045 private final Validator[] validators; 046 047 private final Set<ConfigurationProperty> boundProperties = new LinkedHashSet<>(); 048 049 private final Deque<BindValidationException> exceptions = new LinkedList<>(); 050 051 public ValidationBindHandler(Validator... validators) { 052 this.validators = validators; 053 } 054 055 public ValidationBindHandler(BindHandler parent, Validator... validators) { 056 super(parent); 057 this.validators = validators; 058 } 059 060 @Override 061 public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, 062 BindContext context, Object result) { 063 if (context.getConfigurationProperty() != null) { 064 this.boundProperties.add(context.getConfigurationProperty()); 065 } 066 return super.onSuccess(name, target, context, result); 067 } 068 069 @Override 070 public void onFinish(ConfigurationPropertyName name, Bindable<?> target, 071 BindContext context, Object result) throws Exception { 072 validate(name, target, context, result); 073 if (context.getDepth() == 0 && !this.exceptions.isEmpty()) { 074 throw this.exceptions.pop(); 075 } 076 super.onFinish(name, target, context, result); 077 } 078 079 private void validate(ConfigurationPropertyName name, Bindable<?> target, 080 BindContext context, Object result) { 081 Object validationTarget = getValidationTarget(target, context, result); 082 Class<?> validationType = target.getBoxedType().resolve(); 083 validate(name, validationTarget, validationType); 084 } 085 086 private Object getValidationTarget(Bindable<?> target, BindContext context, 087 Object result) { 088 if (result != null) { 089 return result; 090 } 091 if (context.getDepth() == 0 && target.getValue() != null) { 092 return target.getValue().get(); 093 } 094 return null; 095 } 096 097 private void validate(ConfigurationPropertyName name, Object target, Class<?> type) { 098 if (target != null) { 099 BindingResult errors = new BeanPropertyBindingResult(target, name.toString()); 100 Arrays.stream(this.validators).filter((validator) -> validator.supports(type)) 101 .forEach((validator) -> validator.validate(target, errors)); 102 if (errors.hasErrors()) { 103 this.exceptions.push(getBindValidationException(name, errors)); 104 } 105 } 106 } 107 108 private BindValidationException getBindValidationException( 109 ConfigurationPropertyName name, BindingResult errors) { 110 Set<ConfigurationProperty> boundProperties = this.boundProperties.stream() 111 .filter((property) -> name.isAncestorOf(property.getName())) 112 .collect(Collectors.toCollection(LinkedHashSet::new)); 113 ValidationErrors validationErrors = new ValidationErrors(name, boundProperties, 114 errors.getAllErrors()); 115 return new BindValidationException(validationErrors); 116 } 117 118}