001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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    package org.kuali.rice.krad.datadictionary.validation.result;
017    
018    import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
019    import org.kuali.rice.krad.datadictionary.validation.ErrorLevel;
020    import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
021    
022    import java.util.Iterator;
023    import java.util.LinkedHashMap;
024    import java.util.Map;
025    
026    /**
027     * DictionaryValidationResult holds dictionary validation results
028     *
029     * @author Kuali Rice Team (rice.collab@kuali.org)
030     */
031    public class DictionaryValidationResult implements Iterable<ConstraintValidationResult> {
032    
033        private Map<String, EntryValidationResult> entryValidationResultMap;
034        private ErrorLevel errorLevel;
035    
036        private int numberOfErrors;
037        private int numberOfWarnings;
038    
039        private Iterator<ConstraintValidationResult> iterator;
040    
041        /**
042         * default constructor
043         */
044        public DictionaryValidationResult() {
045            this.entryValidationResultMap = new LinkedHashMap<String, EntryValidationResult>();
046            this.errorLevel = ErrorLevel.ERROR;
047            this.numberOfErrors = 0;
048            this.numberOfWarnings = 0;
049        }
050    
051        /**
052         * adds the result of a constraint validation performed on an attribute
053         *
054         * @param attributeValueReader - provides access to the attribute being validated
055         * @param constraintValidationResult - the result of processing a constraint
056         */
057        public void addConstraintValidationResult(AttributeValueReader attributeValueReader,
058                ConstraintValidationResult constraintValidationResult) {
059    
060            // Don't bother to store this if the error level of the constraint validation result is lower than the level this dictionary validation result is tracking
061            if (constraintValidationResult.getStatus().getLevel() < errorLevel.getLevel()) {
062                return;
063            }
064    
065            switch (constraintValidationResult.getStatus()) {
066                case ERROR:
067                    numberOfErrors++;
068                    break;
069                case WARN:
070                    numberOfWarnings++;
071                    break;
072                default:
073                    // Do nothing
074            }
075    
076            // Give the constraint a chance to override the entry and attribute name - important if the attribute name is not the same as the one in the attribute value reader!
077            String entryName = constraintValidationResult.getEntryName();
078            String attributeName = constraintValidationResult.getAttributeName();
079            String attributePath = constraintValidationResult.getAttributePath();
080    
081            if (entryName == null) {
082                entryName = attributeValueReader.getEntryName();
083            }
084    
085            if (attributeName == null) {
086                attributeName = attributeValueReader.getAttributeName();
087            }
088    
089            if (attributePath == null) {
090                attributePath = attributeValueReader.getPath();
091            }
092    
093            constraintValidationResult.setEntryName(entryName);
094            constraintValidationResult.setAttributeName(attributeName);
095            constraintValidationResult.setAttributePath(attributePath);
096    
097            String entryKey = getEntryValidationResultKey(entryName, attributePath);
098            getEntryValidationResult(entryKey).getAttributeValidationResult(attributeName).addConstraintValidationResult(
099                    constraintValidationResult);
100        }
101    
102        /**
103         * provides information used to display error messages to the user concerning a constraint validation
104         *
105         * @param attributeValueReader - provides access to the attribute being validated
106         * @param constraintName - a descriptive name of the current constraint processor
107         * @param errorKey - a key used to fetch an informational message to show the user
108         * @param errorParameters - parameters to substitute into the informational message
109         * @return a constraint validation result encompassing the information provided
110         */
111        public ConstraintValidationResult addError(AttributeValueReader attributeValueReader, String constraintName,
112                String errorKey, String... errorParameters) {
113            ConstraintValidationResult constraintValidationResult = getConstraintValidationResult(
114                    attributeValueReader.getEntryName(), attributeValueReader.getAttributeName(),
115                    attributeValueReader.getPath(), constraintName);
116            constraintValidationResult.setError(errorKey, errorParameters);
117            numberOfErrors++;
118            return constraintValidationResult;
119        }
120    
121        /**
122         * provides information used to display error messages to the user concerning a constraint validation
123         *
124         * @param constraintLabelKey - a key used to fetch an information message to show the user
125         * @param attributeValueReader - provides access to the attribute being validated
126         * @param constraintName - a descriptive name of the current constraint processor
127         * @param errorKey - a key used to fetch an error message to show the user
128         * @param errorParameters - parameters to substitute into the error message
129         * @return a constraint validation result encompassing the information provided
130         */
131        public ConstraintValidationResult addError(String constraintLabelKey, AttributeValueReader attributeValueReader,
132                String constraintName, String errorKey, String... errorParameters) {
133            ConstraintValidationResult constraintValidationResult = getConstraintValidationResult(
134                    attributeValueReader.getEntryName(), attributeValueReader.getAttributeName(),
135                    attributeValueReader.getPath(), constraintName);
136            constraintValidationResult.setError(errorKey, errorParameters);
137            constraintValidationResult.setConstraintLabelKey(constraintLabelKey);
138            numberOfErrors++;
139            return constraintValidationResult;
140        }
141    
142        /**
143         * provides information used to display warning messages to the user concerning a constraint validation
144         *
145         * @param attributeValueReader - provides access to the attribute being validated
146         * @param constraintName - a descriptive name of the current constraint processor
147         * @param errorKey - a key used to fetch a warning message to show the user
148         * @param errorParameters - parameters to substitute into the warning message
149         * @return a constraint validation result encompassing the information provided
150         */
151        public ConstraintValidationResult addWarning(AttributeValueReader attributeValueReader, String constraintName,
152                String errorKey, String... errorParameters) {
153            if (errorLevel.getLevel() > ErrorLevel.WARN.getLevel()) {
154                return new ConstraintValidationResult(constraintName, ErrorLevel.WARN);
155            }
156    
157            ConstraintValidationResult constraintValidationResult = getConstraintValidationResult(
158                    attributeValueReader.getEntryName(), attributeValueReader.getAttributeName(),
159                    attributeValueReader.getPath(), constraintName);
160            constraintValidationResult.setWarning(errorKey, errorParameters);
161            numberOfWarnings++;
162            return constraintValidationResult;
163        }
164    
165        /**
166         * indicates that a constraint validation has succeeded
167         *
168         * @param attributeValueReader - provides access to the attribute being validated
169         * @param constraintName - a descriptive name of the current constraint processor
170         * @return a constraint validation result encompassing the information provided
171         */
172        public ConstraintValidationResult addSuccess(AttributeValueReader attributeValueReader, String constraintName) {
173            if (errorLevel.getLevel() > ErrorLevel.OK.getLevel()) {
174                return new ConstraintValidationResult(constraintName, ErrorLevel.OK);
175            }
176    
177            return getConstraintValidationResult(attributeValueReader.getEntryName(),
178                    attributeValueReader.getAttributeName(), attributeValueReader.getPath(), constraintName);
179        }
180    
181        /**
182         * indicates that a constraint validation has been skipped
183         *
184         * @param attributeValueReader - provides access to the attribute being validated
185         * @param constraintName - a descriptive name of the current constraint processor
186         * @return a constraint validation result encompassing the information provided
187         */
188        public ConstraintValidationResult addSkipped(AttributeValueReader attributeValueReader, String constraintName) {
189            if (errorLevel.getLevel() > ErrorLevel.OK.getLevel()) {
190                return new ConstraintValidationResult(constraintName, ErrorLevel.INAPPLICABLE);
191            }
192    
193            ConstraintValidationResult constraintValidationResult = getConstraintValidationResult(
194                    attributeValueReader.getEntryName(), attributeValueReader.getAttributeName(),
195                    attributeValueReader.getPath(), constraintName);
196            constraintValidationResult.setStatus(ErrorLevel.INAPPLICABLE);
197            return constraintValidationResult;
198        }
199    
200        /**
201         * indicates that a constraint validation processing has been skipped
202         *
203         * @param attributeValueReader - provides access to the attribute being validated
204         * @param constraintName - a descriptive name of the current constraint processor
205         * @return a constraint validation result encompassing the information provided
206         */
207        public ConstraintValidationResult addNoConstraint(AttributeValueReader attributeValueReader,
208                String constraintName) {
209            if (errorLevel.getLevel() > ErrorLevel.OK.getLevel()) {
210                return new ConstraintValidationResult(constraintName, ErrorLevel.NOCONSTRAINT);
211            }
212    
213            ConstraintValidationResult constraintValidationResult = getConstraintValidationResult(
214                    attributeValueReader.getEntryName(), attributeValueReader.getAttributeName(),
215                    attributeValueReader.getPath(), constraintName);
216            constraintValidationResult.setStatus(ErrorLevel.NOCONSTRAINT);
217            return constraintValidationResult;
218        }
219    
220        /**
221         * gets an iterator over the various {@code ConstraintValidationResult}'s contained in this class
222         *
223         * @return an iterator
224         */
225        public Iterator<ConstraintValidationResult> iterator() {
226    
227            iterator = new Iterator<ConstraintValidationResult>() {
228    
229                private Iterator<EntryValidationResult> entryIterator;
230                private Iterator<AttributeValidationResult> attributeIterator;
231                private Iterator<ConstraintValidationResult> constraintIterator;
232    
233                @Override
234                public boolean hasNext() {
235                    Iterator<ConstraintValidationResult> currentConstraintIterator = getCurrentConstraintIterator();
236                    return currentConstraintIterator != null && currentConstraintIterator.hasNext();
237                }
238    
239                @Override
240                public ConstraintValidationResult next() {
241                    Iterator<ConstraintValidationResult> currentConstraintIterator = getCurrentConstraintIterator();
242                    return currentConstraintIterator != null ? currentConstraintIterator.next() : null;
243                }
244    
245                @Override
246                public void remove() {
247                    throw new RuntimeException("Can't remove from this iterator!");
248                }
249    
250                private Iterator<ConstraintValidationResult> getCurrentConstraintIterator() {
251                    if (constraintIterator == null || constraintIterator.hasNext() == false) {
252                        Iterator<AttributeValidationResult> currentAttributeIterator = getCurrentAttributeIterator();
253                        if (currentAttributeIterator != null && currentAttributeIterator.hasNext()) {
254                            AttributeValidationResult currentAttributeValidationResult = currentAttributeIterator.next();
255                            constraintIterator = currentAttributeValidationResult.iterator();
256                        }
257                    }
258                    return constraintIterator;
259                }
260    
261                private Iterator<AttributeValidationResult> getCurrentAttributeIterator() {
262                    if (attributeIterator == null || attributeIterator.hasNext() == false) {
263                        Iterator<EntryValidationResult> currentEntryIterator = getCurrentEntryIterator();
264                        if (currentEntryIterator != null && currentEntryIterator.hasNext()) {
265                            EntryValidationResult currentEntryValidationResult = currentEntryIterator.next();
266                            attributeIterator = currentEntryValidationResult.iterator();
267                        }
268                    }
269                    return attributeIterator;
270                }
271    
272                private Iterator<EntryValidationResult> getCurrentEntryIterator() {
273                    if (entryIterator == null) // || entryIterator.hasNext() == false)
274                    {
275                        entryIterator = entryValidationResultMap.values().iterator();
276                    }
277                    return entryIterator;
278                }
279    
280            };
281    
282            return iterator;
283        }
284    
285        /**
286         * gets an entry validation result for the given {@code entryName}
287         *
288         * @param entryName - the name that the data dictionary uses to store metadata about the attribute
289         * @return the existing {@code EntryValidationResult} for the given {@code entryName} or creates a new one if
290         *         absent
291         */
292        protected EntryValidationResult getEntryValidationResult(String entryName) {
293            EntryValidationResult entryValidationResult = entryValidationResultMap.get(entryName);
294            if (entryValidationResult == null) {
295                entryValidationResult = new EntryValidationResult(entryName);
296                entryValidationResultMap.put(entryName, entryValidationResult);
297            }
298            return entryValidationResult;
299        }
300    
301        /**
302         * composes a {@code ConstraintValidationResult} from the parameters given
303         *
304         * @param entryName - the name that the data dictionary uses to store metadata about the attribute
305         * @param attributeName - the attribute name
306         * @param attributePath - a string representation of specifically which attribute (at some depth) is being accessed
307         * @param constraintName - a descriptive name of the current constraint processor
308         * @return
309         */
310        private ConstraintValidationResult getConstraintValidationResult(String entryName, String attributeName,
311                String attributePath, String constraintName) {
312            String entryKey = getEntryValidationResultKey(entryName, attributePath);
313            ConstraintValidationResult constraintValidationResult = getEntryValidationResult(entryKey)
314                    .getAttributeValidationResult(attributeName).getConstraintValidationResult(constraintName);
315            constraintValidationResult.setEntryName(entryName);
316            constraintValidationResult.setAttributeName(attributeName);
317            constraintValidationResult.setAttributePath(attributePath);
318            return constraintValidationResult;
319        }
320    
321        /**
322         * gets the key to the {@link EntryValidationResult} entry in the EntryValidationResultMap
323         *
324         * <p>Most cases entry key will be the entryName, unless the attribute is part of a collection,
325         * in which case entry key will be suffixed with index of attribute's parent item.</p>
326         *
327         * @param entryName - the name that the data dictionary uses to store metadata about the attribute
328         * @param attributePath - a string representation of specifically which attribute (at some depth) is being accessed
329         * @return a key used to fetch an associated {@code EntryValidationResult}
330         */
331        private String getEntryValidationResultKey(String entryName, String attributePath) {
332            if (attributePath.contains("[")) {
333                return entryName + "[" + ValidationUtils.getLastPathIndex(attributePath) + "]";
334            }
335            return entryName;
336        }
337    
338        /**
339         * @return the errorLevel
340         */
341        public ErrorLevel getErrorLevel() {
342            return this.errorLevel;
343        }
344    
345        /**
346         * @param errorLevel the errorLevel to set
347         */
348        public void setErrorLevel(ErrorLevel errorLevel) {
349            this.errorLevel = errorLevel;
350        }
351    
352        /**
353         * @return the numberOfErrors
354         */
355        public int getNumberOfErrors() {
356            return this.numberOfErrors;
357        }
358    
359        /**
360         * @return the numberOfWarnings
361         */
362        public int getNumberOfWarnings() {
363            return this.numberOfWarnings;
364        }
365    
366    }