Use annotations to do validation
[policy/models.git] / models-base / src / main / java / org / onap / policy / models / base / Validated.java
1 /*
2  * ============LICENSE_START=======================================================
3  *  Copyright (C) 2019-2020 AT&T Intellectual Property. All rights reserved.
4  * ================================================================================
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  * SPDX-License-Identifier: Apache-2.0
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.policy.models.base;
22
23 import com.google.re2j.Pattern;
24 import java.util.Collection;
25 import java.util.Map;
26 import java.util.Map.Entry;
27 import java.util.function.BiFunction;
28 import java.util.function.Function;
29 import lombok.NonNull;
30 import org.apache.commons.lang3.StringUtils;
31 import org.onap.policy.common.parameters.BeanValidationResult;
32 import org.onap.policy.common.parameters.ObjectValidationResult;
33 import org.onap.policy.common.parameters.ValidationResult;
34 import org.onap.policy.common.parameters.ValidationStatus;
35
36 /**
37  * Classes that can be validated. This can be used as a super class or as a stand-alone
38  * utility class.
39  */
40 public class Validated {
41     public static final String IS_BLANK = "is blank";
42     public static final String IS_A_NULL_KEY = "is a null key";
43     public static final String IS_NULL = "is null";
44     public static final String NOT_DEFINED = "not defined";
45     public static final String NOT_FOUND = "not found";
46
47     public static final String KEY_TOKEN = "key";
48     public static final String VALUE_TOKEN = "value";
49
50     /**
51      * Validates the fields of the object. The default method uses a {@link PfValidator}
52      * to validate the object.
53      *
54      * @param fieldName name of the field containing this
55      * @return the result, or {@code null}
56      */
57     public BeanValidationResult validate(@NonNull String fieldName) {
58         return new PfValidator().validateTop(fieldName, this);
59     }
60
61     /**
62      * Adds a result indicating that a value is invalid.
63      *
64      * @param result where to put the result
65      * @param fieldName name of the field containing the value
66      * @param value the field's value
67      * @param errorMessage the error message
68      */
69     public static void addResult(@NonNull BeanValidationResult result, @NonNull String fieldName, Object value,
70                     @NonNull String errorMessage) {
71         result.addResult(
72                         new ObjectValidationResult(fieldName, getKeyId(value), ValidationStatus.INVALID, errorMessage));
73     }
74
75     /**
76      * Makes a result that indicates a value is invalid, because it is null.
77      *
78      * @param fieldName name of the field containing the value
79      * @param value the field's value
80      * @return a result indicating the value is invalid
81      */
82     public static ValidationResult makeNullResult(@NonNull String fieldName, Object value) {
83         return new ObjectValidationResult(fieldName, getKeyId(value), ValidationStatus.INVALID, IS_NULL);
84     }
85
86     /**
87      * Validates a value, if is not {@code null}, by invoking it's validate() method.
88      *
89      * @param result where to put the result
90      * @param fieldName name of the field containing the value
91      * @param value the field's value
92      */
93     public static void validateOptional(@NonNull BeanValidationResult result, @NonNull String fieldName,
94                     Validated value) {
95         if (value != null) {
96             result.addResult(value.validate(fieldName));
97         }
98     }
99
100     /**
101      * Validates that a value is not {@code null}. If the value is a subclass of this
102      * class, then it's {@link #validate(String)} method is invoked, too.
103      *
104      * @param fieldName name of the field containing the value
105      * @param value the field's value
106      * @return a result, or {@code null}
107      */
108     public static ValidationResult validateNotNull(@NonNull String fieldName, Object value) {
109         if (value == null) {
110             return new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID, IS_NULL);
111         }
112
113         if (value instanceof Validated) {
114             return ((Validated) value).validate(fieldName);
115         }
116
117         return null;
118     }
119
120     /**
121      * Validates that a value is not "blank" (i.e., empty). value.
122      *
123      * @param fieldName name of the field containing the value
124      * @param value the field's value
125      * @param checkNull {@code true} if to validate that the value is not {@code null}
126      * @return a result, or {@code null}
127      */
128     public static ValidationResult validateNotBlank(@NonNull String fieldName, String value, boolean checkNull) {
129         if (value == null && !checkNull) {
130             return null;
131         }
132
133         if (StringUtils.isBlank(value)) {
134             return new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID, IS_BLANK);
135         }
136
137         return null;
138     }
139
140     /**
141      * Validates that a value matches regular expression.
142      *
143      * @param fieldName name of the field containing the value
144      * @param value the field's value
145      * @param pattern regular expression to be matched
146      * @return a result, or {@code null}
147      */
148     public static ValidationResult validateRegex(@NonNull String fieldName, String value, @NonNull String pattern) {
149         if (value == null) {
150             return makeNullResult(fieldName, value);
151         }
152
153         if (!Pattern.matches(pattern, value)) {
154             return new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID,
155                             "does not match regular expression " + pattern);
156         }
157
158         return null;
159     }
160
161     /**
162      * Validates a key, ensuring that it isn't null and that it's structurally sound.
163      *
164      * @param fieldName name of the field containing the key
165      * @param key the field's value
166      * @return a result, or {@code null}
167      */
168     public static ValidationResult validateKeyNotNull(@NonNull String fieldName, PfKey key) {
169         if (key == null) {
170             return new ObjectValidationResult(fieldName, key, ValidationStatus.INVALID, IS_A_NULL_KEY);
171         }
172
173         if (key.isNullKey()) {
174             return new ObjectValidationResult(fieldName, key.getId(), ValidationStatus.INVALID, IS_A_NULL_KEY);
175         }
176
177         return key.validate(fieldName);
178     }
179
180     /**
181      * Validates a key's version, ensuring that it isn't null.
182      *
183      * @param fieldName name of the field containing the key
184      * @param key the field's value
185      * @return a result, or {@code null}
186      */
187     public static BeanValidationResult validateKeyVersionNotNull(@NonNull String fieldName, PfConceptKey key) {
188         if (key != null && key.isNullVersion()) {
189             BeanValidationResult result = new BeanValidationResult(fieldName, key);
190             result.addResult(makeNullResult(PfKeyImpl.VERSION_TOKEN, key.getVersion()));
191             return result;
192         }
193
194         return null;
195     }
196
197     /**
198      * Generates a function to validate that a value is not below a minimum.
199      *
200      * @param min minimum value allowed
201      * @param allowedValue {@code null} or an allowed value outside the range
202      * @param checkRef {@code true} to generate an error if the value is {@code null}
203      * @return a function to validate that a value is not below a minimum
204      */
205     public static BiFunction<String, Integer, ValidationResult> validateMin(int min, Integer allowedValue,
206                     boolean checkRef) {
207         return (name, value) -> validateMin(name, value, min, allowedValue, checkRef);
208     }
209
210     /**
211      * Validates that a value is not below a minimum.
212      *
213      * @param fieldName name of the field containing the key
214      * @param value the field's value
215      * @param min minimum value allowed
216      * @param allowedValue {@code null} or an allowed value outside the range
217      * @param checkRef {@code true} to generate an error if the value is {@code null}
218      * @return a result, or {@code null}
219      */
220     public static ValidationResult validateMin(@NonNull String fieldName, Integer value, int min, Integer allowedValue,
221                     boolean checkRef) {
222         if (value == null) {
223             if (checkRef) {
224                 return makeNullResult(fieldName, value);
225             }
226
227             return null;
228         }
229
230         if (value < min && !value.equals(allowedValue)) {
231             return new ObjectValidationResult(fieldName, value, ValidationStatus.INVALID,
232                             "is below the minimum value: " + min);
233         }
234
235         return null;
236     }
237
238     /**
239      * Validates the items in a list.
240      *
241      * @param result where to add the results
242      * @param fieldName name of the field containing the list
243      * @param list the field's list (may be {@code null})
244      * @param checker function to validate in individual item in the list
245      */
246     public static <T> void validateList(@NonNull BeanValidationResult result, @NonNull String fieldName,
247                     Collection<T> list, @NonNull BiFunction<String, T, ValidationResult> checker) {
248         if (list == null) {
249             return;
250         }
251
252         BeanValidationResult result2 = new BeanValidationResult(fieldName, list);
253
254         int count = 0;
255         for (T value : list) {
256             result2.addResult(checker.apply(String.valueOf(count++), value));
257         }
258
259         if (!result2.isClean()) {
260             result.addResult(result2);
261         }
262     }
263
264     /**
265      * Validates the items in a map.
266      *
267      * @param result where to add the results
268      * @param fieldName name of the field containing the list
269      * @param map the field's map (may be {@code null})
270      * @param checker function to validate in individual item in the list
271      */
272     public static <T> void validateMap(@NonNull BeanValidationResult result, @NonNull String fieldName,
273                     Map<String, T> map, @NonNull Function<Map.Entry<String, T>, ValidationResult> checker) {
274         if (map == null) {
275             return;
276         }
277
278         BeanValidationResult result2 = new BeanValidationResult(fieldName, map);
279
280         for (Entry<String, T> entry : map.entrySet()) {
281             result2.addResult(checker.apply(entry));
282         }
283
284         if (!result2.isClean()) {
285             result.addResult(result2);
286         }
287     }
288
289     /**
290      * Validates a Map entry, ensuring that neither the key nor the value are "blank"
291      * (i.e., empty or {@code null}).
292      *
293      * @param entry entry to be validated
294      * @return a result, or {@code null}
295      */
296     public static BeanValidationResult validateEntryNotBlankNotBlank(Map.Entry<String, String> entry) {
297         BeanValidationResult result = new BeanValidationResult("" + entry.getKey(), entry.getKey());
298
299         if (StringUtils.isBlank(entry.getKey())) {
300             Validated.addResult(result, KEY_TOKEN, entry.getKey(), IS_BLANK);
301         }
302
303         if (StringUtils.isBlank(entry.getValue())) {
304             Validated.addResult(result, VALUE_TOKEN, entry.getValue(), IS_BLANK);
305         }
306
307         return (result.isClean() ? null : result);
308     }
309
310     /**
311      * Validates a Map entry, ensuring that the key is not "blank" (i.e., empty or
312      * {@code null}) and the value is not {@code null}.
313      *
314      * @param entry entry to be validated
315      * @return a result, or {@code null}
316      */
317     public static BeanValidationResult validateEntryNotBlankNotNull(Map.Entry<String, String> entry) {
318         BeanValidationResult result = new BeanValidationResult("" + entry.getKey(), entry.getKey());
319
320         if (StringUtils.isBlank(entry.getKey())) {
321             Validated.addResult(result, KEY_TOKEN, entry.getKey(), IS_BLANK);
322         }
323
324         if (entry.getValue() == null) {
325             result.addResult(makeNullResult(VALUE_TOKEN, entry.getValue()));
326         }
327
328         return (result.isClean() ? null : result);
329     }
330
331     /**
332      * Validates a Map entry, ensuring that neither the key nor the value are
333      * {@code null}. If the value is a subclass of this class, then it's
334      * {@link #validate(String)} method is invoked.
335      *
336      * @param entry entry to be validated
337      * @return a result, or {@code null}
338      */
339     public static <V> BeanValidationResult validateEntryValueNotNull(Map.Entry<String, V> entry) {
340         BeanValidationResult result = new BeanValidationResult("" + entry.getKey(), entry.getKey());
341
342         if (entry.getKey() == null) {
343             result.addResult(makeNullResult(KEY_TOKEN, entry.getKey()));
344         }
345
346         V value = entry.getValue();
347         if (value == null) {
348             result.addResult(makeNullResult(VALUE_TOKEN, value));
349         } else if (value instanceof Validated) {
350             result.addResult(((Validated) value).validate(VALUE_TOKEN));
351         }
352
353         return (result.isClean() ? null : result);
354     }
355
356     /**
357      * Gets a key's ID, if the value is a {@link PfKey}.
358      *
359      * @param value value from which to get the ID
360      * @return the value's ID, if it's a key, the original value otherwise
361      */
362     private static Object getKeyId(Object value) {
363         return (value instanceof PfKey ? ((PfKey) value).getId() : value);
364     }
365 }