001/* 002 * Copyright 2012-2017 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.handler; 018 019import java.util.HashSet; 020import java.util.Set; 021import java.util.TreeSet; 022import java.util.function.Function; 023 024import org.springframework.boot.context.properties.bind.AbstractBindHandler; 025import org.springframework.boot.context.properties.bind.BindContext; 026import org.springframework.boot.context.properties.bind.BindHandler; 027import org.springframework.boot.context.properties.bind.Bindable; 028import org.springframework.boot.context.properties.bind.UnboundConfigurationPropertiesException; 029import org.springframework.boot.context.properties.source.ConfigurationProperty; 030import org.springframework.boot.context.properties.source.ConfigurationPropertyName; 031import org.springframework.boot.context.properties.source.ConfigurationPropertySource; 032import org.springframework.boot.context.properties.source.IterableConfigurationPropertySource; 033 034/** 035 * {@link BindHandler} to enforce that all configuration properties under the root name 036 * have been bound. 037 * 038 * @author Phillip Webb 039 * @author Madhura Bhave 040 * @since 2.0.0 041 */ 042public class NoUnboundElementsBindHandler extends AbstractBindHandler { 043 044 private final Set<ConfigurationPropertyName> boundNames = new HashSet<>(); 045 046 private final Function<ConfigurationPropertySource, Boolean> filter; 047 048 NoUnboundElementsBindHandler() { 049 this(BindHandler.DEFAULT, (configurationPropertySource) -> true); 050 } 051 052 public NoUnboundElementsBindHandler(BindHandler parent) { 053 this(parent, (configurationPropertySource) -> true); 054 } 055 056 public NoUnboundElementsBindHandler(BindHandler parent, 057 Function<ConfigurationPropertySource, Boolean> filter) { 058 super(parent); 059 this.filter = filter; 060 } 061 062 @Override 063 public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, 064 BindContext context, Object result) { 065 this.boundNames.add(name); 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 if (context.getDepth() == 0) { 073 checkNoUnboundElements(name, context); 074 } 075 } 076 077 private void checkNoUnboundElements(ConfigurationPropertyName name, 078 BindContext context) { 079 Set<ConfigurationProperty> unbound = new TreeSet<>(); 080 for (ConfigurationPropertySource source : context.getSources()) { 081 if (source instanceof IterableConfigurationPropertySource 082 && this.filter.apply(source)) { 083 collectUnbound(name, unbound, 084 (IterableConfigurationPropertySource) source); 085 } 086 } 087 if (!unbound.isEmpty()) { 088 throw new UnboundConfigurationPropertiesException(unbound); 089 } 090 } 091 092 private void collectUnbound(ConfigurationPropertyName name, 093 Set<ConfigurationProperty> unbound, 094 IterableConfigurationPropertySource source) { 095 IterableConfigurationPropertySource filtered = source 096 .filter((candidate) -> isUnbound(name, candidate)); 097 for (ConfigurationPropertyName unboundName : filtered) { 098 try { 099 unbound.add(source.filter((candidate) -> isUnbound(name, candidate)) 100 .getConfigurationProperty(unboundName)); 101 } 102 catch (Exception ex) { 103 } 104 } 105 } 106 107 private boolean isUnbound(ConfigurationPropertyName name, 108 ConfigurationPropertyName candidate) { 109 return name.isAncestorOf(candidate) && !this.boundNames.contains(candidate); 110 } 111 112}