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.ArrayList;
020import java.util.Collections;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Set;
024
025import org.springframework.boot.context.properties.source.ConfigurationProperty;
026import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
027import org.springframework.boot.context.properties.source.ConfigurationPropertyName.Form;
028import org.springframework.boot.origin.Origin;
029import org.springframework.boot.origin.OriginProvider;
030import org.springframework.util.Assert;
031import org.springframework.validation.FieldError;
032import org.springframework.validation.ObjectError;
033
034/**
035 * A collection of {@link ObjectError ObjectErrors} caused by bind validation failures.
036 * Where possible, included {@link FieldError FieldErrors} will be OriginProvider.
037 *
038 * @author Phillip Webb
039 * @author Madhura Bhave
040 * @since 2.0.0
041 */
042public class ValidationErrors implements Iterable<ObjectError> {
043
044        private final ConfigurationPropertyName name;
045
046        private final Set<ConfigurationProperty> boundProperties;
047
048        private final List<ObjectError> errors;
049
050        ValidationErrors(ConfigurationPropertyName name,
051                        Set<ConfigurationProperty> boundProperties, List<ObjectError> errors) {
052                Assert.notNull(name, "Name must not be null");
053                Assert.notNull(boundProperties, "BoundProperties must not be null");
054                Assert.notNull(errors, "Errors must not be null");
055                this.name = name;
056                this.boundProperties = Collections.unmodifiableSet(boundProperties);
057                this.errors = convertErrors(name, boundProperties, errors);
058        }
059
060        private List<ObjectError> convertErrors(ConfigurationPropertyName name,
061                        Set<ConfigurationProperty> boundProperties, List<ObjectError> errors) {
062                List<ObjectError> converted = new ArrayList<>(errors.size());
063                for (ObjectError error : errors) {
064                        converted.add(convertError(name, boundProperties, error));
065                }
066                return Collections.unmodifiableList(converted);
067        }
068
069        private ObjectError convertError(ConfigurationPropertyName name,
070                        Set<ConfigurationProperty> boundProperties, ObjectError error) {
071                if (error instanceof FieldError) {
072                        return convertFieldError(name, boundProperties, (FieldError) error);
073                }
074                return error;
075        }
076
077        private FieldError convertFieldError(ConfigurationPropertyName name,
078                        Set<ConfigurationProperty> boundProperties, FieldError error) {
079                if (error instanceof OriginProvider) {
080                        return error;
081                }
082                return OriginTrackedFieldError.of(error,
083                                findFieldErrorOrigin(name, boundProperties, error));
084        }
085
086        private Origin findFieldErrorOrigin(ConfigurationPropertyName name,
087                        Set<ConfigurationProperty> boundProperties, FieldError error) {
088                for (ConfigurationProperty boundProperty : boundProperties) {
089                        if (isForError(name, boundProperty.getName(), error)) {
090                                return Origin.from(boundProperty);
091                        }
092                }
093                return null;
094        }
095
096        private boolean isForError(ConfigurationPropertyName name,
097                        ConfigurationPropertyName boundPropertyName, FieldError error) {
098                return name.isParentOf(boundPropertyName) && boundPropertyName
099                                .getLastElement(Form.UNIFORM).equalsIgnoreCase(error.getField());
100        }
101
102        /**
103         * Return the name of the item that was being validated.
104         * @return the name of the item
105         */
106        public ConfigurationPropertyName getName() {
107                return this.name;
108        }
109
110        /**
111         * Return the properties that were bound before validation failed.
112         * @return the boundProperties
113         */
114        public Set<ConfigurationProperty> getBoundProperties() {
115                return this.boundProperties;
116        }
117
118        public boolean hasErrors() {
119                return !this.errors.isEmpty();
120        }
121
122        /**
123         * Return the list of all validation errors.
124         * @return the errors
125         */
126        public List<ObjectError> getAllErrors() {
127                return this.errors;
128        }
129
130        @Override
131        public Iterator<ObjectError> iterator() {
132                return this.errors.iterator();
133        }
134
135}