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}