001/**
002 * Copyright 2005-2014 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 */
016package org.kuali.rice.krad.datadictionary.validation.result;
017
018import org.kuali.rice.krad.datadictionary.validation.AttributeValueReader;
019import org.kuali.rice.krad.datadictionary.validation.ErrorLevel;
020import org.kuali.rice.krad.datadictionary.validation.ValidationUtils;
021
022import java.util.Iterator;
023import java.util.LinkedHashMap;
024import java.util.Map;
025
026/**
027 * DictionaryValidationResult holds dictionary validation results
028 *
029 * @author Kuali Rice Team (rice.collab@kuali.org)
030 */
031public 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 validation result
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}