aboutsummaryrefslogtreecommitdiffstats
path: root/common-parameters/src/main/java/org/onap/policy/common/parameters/GroupValidationResult.java
blob: ad2eb69566cc2fc3bfe2499a6acdf351c7cc242d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/*-
 * ============LICENSE_START=======================================================
 *  Copyright (C) 2018 Ericsson. All rights reserved.
 *  Modifications Copyright (C) 2018-2019 AT&T Intellectual Property. All rights reserved.
 * ================================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * ============LICENSE_END=========================================================
 */

package org.onap.policy.common.parameters;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;

/**
 * This class holds the result of the validation of a parameter group.
 */
public class GroupValidationResult extends CommonGroupValidationResult {
    // The parameter group which the validation result applies
    private final ParameterGroup parameterGroup;

    /**
     * Constructor, create the field validation result with default arguments.
     *
     * @param parameterGroup the parameter group being validated
     */
    public GroupValidationResult(final ParameterGroup parameterGroup) {
        super(ParameterConstants.PARAMETER_GROUP_HAS_STATUS_MESSAGE);

        this.parameterGroup = parameterGroup;

        // Parameter group definitions may be optional
        if (parameterGroup == null) {
            return;
        }

        // Add a validation result for all fields in the declared class
        for (Field field : parameterGroup.getClass().getDeclaredFields()) {
            // Check if a validation result should be added for this declared field
            if (isIncludedField(field)) {
                // Set a validation result for the field
                validationResultMap.put(field.getName(), getSetValidationResult(field, parameterGroup));
            }
        }

        // Add a validation result for protected and public fields in super classes
        for (Field field : getSuperclassFields(parameterGroup.getClass().getSuperclass())) {
            // Check if a validation result should be added for this declared field
            if (isIncludedField(field)) {
                // Set a validation result for the field
                validationResultMap.putIfAbsent(field.getName(), getSetValidationResult(field, parameterGroup));
            }
        }
    }

    /**
     * Construct a validation result for a field, updating "this" status.
     *
     * @param field The parameter field
     * @param ParameterGroup The parameter group containing the field
     * @return the validation result
     * @throws Exception on accessing private fields
     */
    private ValidationResult getSetValidationResult(Field field, ParameterGroup parameterGroup) {
        ValidationResult result = getValidationResult(field, parameterGroup);
        setResult(result.getStatus());

        return result;
    }

    /**
     * Construct a validation result for a field.
     *
     * @param field The parameter field
     * @param ParameterGroup The parameter group containing the field
     * @return the validation result
     * @throws Exception on accessing private fields
     */
    private ValidationResult getValidationResult(final Field field, final ParameterGroup parameterGroup) {
        final String fieldName = field.getName();
        final Class<?> fieldType = field.getType();
        final Object fieldObject = getObjectField(parameterGroup, field);

        // perform null checks
        ParameterValidationResult result = new ParameterValidationResult(field, fieldObject);
        if (!result.isValid()) {
            return result;
        }

        // Nested parameter groups are allowed
        if (ParameterGroup.class.isAssignableFrom(fieldType)) {
            return new GroupValidationResult((ParameterGroup) fieldObject);
        }

        // Nested maps of parameter groups are allowed
        if (Map.class.isAssignableFrom(field.getType())) {
            checkMapIsParameterGroupMap(fieldName, fieldObject);
            return new GroupMapValidationResult(field, fieldObject);
        }

        // Collections of parameter groups are not allowed
        if (Collection.class.isAssignableFrom(field.getType())) {
            checkCollection4ParameterGroups(fieldName, fieldObject);
            return result;
        }

        // It's a regular parameter
        return result;
    }

    /**
     * Get the value of a field in an object using a getter found with reflection.
     *
     * @param targetObject The object on which to read the field value
     * @param fieldName The name of the field
     * @return The field value
     */
    private Object getObjectField(final Object targetObject, final Field field) {
        String getterMethodName;

        // Check for Boolean fields, the convention for boolean getters is that they start with "is"
        // If the field name already starts with "is" then the getter has the field name otherwise
        // the field name is prepended with "is"
        if (boolean.class.equals(field.getType())) {
            if (field.getName().startsWith("is")) {
                getterMethodName = field.getName();
            } else {
                getterMethodName = "is" + StringUtils.capitalize(field.getName());
            }
        } else {
            getterMethodName = "get" + StringUtils.capitalize(field.getName());
        }

        // Look up the getter method for the field
        Method getterMethod;
        try {
            getterMethod = targetObject.getClass().getMethod(getterMethodName, (Class<?>[]) null);
        } catch (NoSuchMethodException | SecurityException e) {
            throw new ParameterRuntimeException("could not get getter method for parameter \"" + field.getName() + "\"",
                e);
        }

        // Invoke the getter
        try {
            return getterMethod.invoke(targetObject, (Object[]) null);
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new ParameterRuntimeException("error calling getter method for parameter \"" + field.getName() + "\"",
                e);
        }
    }

    /**
     * Check if this field is a map of parameter groups indexed by string keys.
     *
     * @param fieldName the name of the collection field.
     * @param mapObject the map object to check
     */
    private void checkMapIsParameterGroupMap(String fieldName, Object mapObject) {
        if (mapObject == null) {
            throw new ParameterRuntimeException("map parameter \"" + fieldName + "\" is null");
        }

        Map<?, ?> incomingMap = (Map<?, ?>) mapObject;

        for (Entry<?, ?> mapEntry : incomingMap.entrySet()) {
            // Check the key is a string
            if (!String.class.isAssignableFrom(mapEntry.getKey().getClass())) {
                throw new ParameterRuntimeException("map entry is not a parameter group keyed by a string, key \""
                    + mapEntry.getKey() + "\" in map \"" + fieldName + "\" is not a string");
            }

            // Check the value is a parameter group
            if (!ParameterGroup.class.isAssignableFrom(mapEntry.getValue().getClass())) {
                throw new ParameterRuntimeException("map entry is not a parameter group keyed by a string, value \""
                    + mapEntry.getValue() + "\" in map \"" + fieldName + "\" is not a parameter group");
            }
        }
    }

    /**
     * Check if this field contains parameter groups.
     *
     * @param fieldName the name of the collection field.
     * @param collectionObject the collection object to check
     */
    private void checkCollection4ParameterGroups(final String fieldName, final Object collectionObject) {
        if (collectionObject == null) {
            throw new ParameterRuntimeException("collection parameter \"" + fieldName + "\" is null");
        }

        Collection<?> collection2Check = (Collection<?>) collectionObject;

        for (Object collectionMember : collection2Check) {
            if (ParameterGroup.class.isAssignableFrom(collectionMember.getClass())) {
                throw new ParameterRuntimeException("collection parameter \"" + fieldName + "\" is illegal,"
                    + " parameter groups are not allowed as collection members");
            }
        }
    }

    /**
     * Gets the parameter group for this validation result.
     *
     * @return the parameter class
     */
    public ParameterGroup getParameterGroup() {
        return parameterGroup;
    }

    /**
     * Gets the name of the parameter group being validated.
     *
     * @return the name
     */
    @Override
    public String getName() {
        return parameterGroup.getName();
    }

    /**
     * Set the validation result on a parameter in a parameter group.
     *
     * @param parameterName The name of the parameter
     * @param status The validation status the field is receiving
     * @param message The validation message explaining the validation status
     */
    public void setResult(final String parameterName, final ValidationStatus status, final String message) {
        ValidationResult validationResult = validationResultMap.get(parameterName);

        if (validationResult == null) {
            throw new ParameterRuntimeException("no parameter field exists for parameter: " + parameterName);
        }

        // Set the status and the message on the result irrespective of validation result type
        validationResult.setResult(status, message);

        // Set the status of this result
        this.setResult(status);
    }

    /**
     * Set the validation result on a nested parameter group.
     *
     * @param parameterName The name of the parameter field
     * @param nestedValidationResult The validation result from a nested field
     */
    public void setResult(final String parameterName, final ValidationResult nestedValidationResult) {
        GroupValidationResult groupValidationResult;
        try {
            groupValidationResult = (GroupValidationResult) validationResultMap.get(parameterName);
        } catch (ClassCastException e) {
            throw new ParameterRuntimeException("parameter is not a nested group parameter: " + parameterName, e);
        }

        if (groupValidationResult == null) {
            throw new ParameterRuntimeException("no nested parameter field exists for parameter: " + parameterName);
        }

        // Set the status of the parameter group and replace the field result
        validationResultMap.put(parameterName, nestedValidationResult);
        this.setResult(nestedValidationResult.getStatus());
    }

    /**
     * Set the validation result on a nested parameter group map entry.
     *
     * @param parameterName The name of the parameter field
     * @param key The key of the map entry
     * @param nestedMapValidationResult The validation result from a nested map entry
     */
    public void setResult(final String parameterName, final String key,
        final ValidationResult nestedMapValidationResult) {
        GroupMapValidationResult groupMapValidationResult;
        try {
            groupMapValidationResult = (GroupMapValidationResult) validationResultMap.get(parameterName);
        } catch (ClassCastException e) {
            throw new ParameterRuntimeException("parameter is not a nested group map parameter: " + parameterName, e);
        }

        if (groupMapValidationResult == null) {
            throw new ParameterRuntimeException("no group map parameter field exists for parameter: " + parameterName);
        }

        // Set the status of the parameter group and the field
        groupMapValidationResult.setResult(key, nestedMapValidationResult);
        this.setResult(nestedMapValidationResult.getStatus());
    }

    /**
     * Set the validation status on a group map entry.
     *
     * @param parameterName The name of the parameter field
     * @param key The key of the map entry
     * @param status The validation status of the entry
     * @param message The message for the parameter group
     */
    public void setResult(final String parameterName, final String key, final ValidationStatus status,
        final String message) {
        GroupMapValidationResult groupMapValidationResult;
        try {
            groupMapValidationResult = (GroupMapValidationResult) validationResultMap.get(parameterName);
        } catch (ClassCastException e) {
            throw new ParameterRuntimeException("parameter is not a nested group map parameter: " + parameterName, e);
        }

        if (groupMapValidationResult == null) {
            throw new ParameterRuntimeException("no group map parameter field exists for parameter: " + parameterName);
        }

        // Set the status of the parameter group and the field
        groupMapValidationResult.setResult(key, status, message);
        this.setResult(status);
    }

    @Override
    protected void addGroupTypeName(StringBuilder result) {
        result.append("parameter group \"");

        if (parameterGroup != null) {
            result.append(parameterGroup.getName());
            result.append("\" type \"");
            result.append(parameterGroup.getClass().getName());
        } else {
            result.append("UNDEFINED");
        }

        result.append("\" ");
    }


    /**
     * Check if a field should be included for validation.
     *
     * @param field the field to check for inclusion
     * @return true of the field should be included
     */
    private boolean isIncludedField(final Field field) {
        return !field.getName().startsWith("$") && !field.getName().startsWith("_")
            && !Modifier.isStatic(field.getModifiers());
    }

    /**
     * Get the public and protected fields of super classes.
     * @param firstSuperClass the first superclass to check
     *
     * @return a set of the superclass fields
     */
    private List<Field> getSuperclassFields(final Class<?> firstSuperClass) {
        List<Field> superclassFields = new ArrayList<>();

        Class<?> currentClass = firstSuperClass;
        while (currentClass.getSuperclass() != null) {
            for (Field field : currentClass.getDeclaredFields()) {
                // Check if this field is public or protected
                if (Modifier.isPublic(field.getModifiers()) || Modifier.isProtected(field.getModifiers())) {
                    superclassFields.add(field);
                }
            }

            // Check the next super class down
            currentClass = currentClass.getSuperclass();
        }

        return superclassFields;
    }
}