c0cbb77ff6092a8b3636afbcf035394db99b6b73
[policy/xacml-pdp.git] / applications / common / src / main / java / org / onap / policy / pdp / xacml / application / common / matchable / MatchablePolicyType.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP
4  * ================================================================================
5  * Copyright (C) 2020 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2020-2021 Nordix Foundation.
7  * ================================================================================
8  * Licensed under the Apache License, Version 2.0 (the "License");
9  * you may not use this file except in compliance with the License.
10  * You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing, software
15  * distributed under the License is distributed on an "AS IS" BASIS,
16  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17  * See the License for the specific language governing permissions and
18  * limitations under the License.
19  *
20  * SPDX-License-Identifier: Apache-2.0
21  * ============LICENSE_END=========================================================
22  */
23
24 package org.onap.policy.pdp.xacml.application.common.matchable;
25
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.function.Function;
30 import lombok.Getter;
31 import lombok.NonNull;
32 import org.onap.policy.models.tosca.authorative.concepts.ToscaConceptIdentifier;
33 import org.onap.policy.models.tosca.authorative.concepts.ToscaDataType;
34 import org.onap.policy.models.tosca.authorative.concepts.ToscaPolicyType;
35 import org.onap.policy.models.tosca.authorative.concepts.ToscaProperty;
36 import org.onap.policy.models.tosca.authorative.concepts.ToscaSchemaDefinition;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 @Getter
41 public class MatchablePolicyType {
42     private static final Logger LOGGER = LoggerFactory.getLogger(MatchablePolicyType.class);
43
44     public static final String TOSCA_PRIMITIVE_STRING = "string";
45     public static final String TOSCA_PRIMITIVE_INTEGER = "integer";
46     public static final String TOSCA_PRIMITIVE_FLOAT = "float";
47     public static final String TOSCA_PRIMITIVE_BOOLEAN = "boolean";
48     public static final String TOSCA_PRIMITIVE_TIMESTAMP = "timestamp";
49     public static final String TOSCA_TYPE_LIST = "list";
50     public static final String TOSCA_TYPE_MAP = "map";
51
52     //@formatter:off
53     private static final Map<String, Function<ToscaProperty, MatchablePropertyTypeBase<?>>>
54         mapPrimitivesProperty = Map.of(
55             TOSCA_PRIMITIVE_STRING, MatchablePropertyTypeString::new,
56             TOSCA_PRIMITIVE_INTEGER, MatchablePropertyTypeInteger::new,
57             TOSCA_PRIMITIVE_FLOAT, MatchablePropertyTypeFloat::new,
58             TOSCA_PRIMITIVE_BOOLEAN, MatchablePropertyTypeBoolean::new,
59             TOSCA_PRIMITIVE_TIMESTAMP, MatchablePropertyTypeTimestamp::new
60             );
61
62     private static final Map<String, Function<ToscaSchemaDefinition, MatchablePropertyTypeBase<?>>>
63         mapPrimitivesSchema = Map.of(
64             TOSCA_PRIMITIVE_STRING, MatchablePropertyTypeString::new,
65             TOSCA_PRIMITIVE_INTEGER, MatchablePropertyTypeInteger::new,
66             TOSCA_PRIMITIVE_FLOAT, MatchablePropertyTypeFloat::new,
67             TOSCA_PRIMITIVE_BOOLEAN, MatchablePropertyTypeBoolean::new,
68             TOSCA_PRIMITIVE_TIMESTAMP, MatchablePropertyTypeTimestamp::new
69             );
70     //@formatter:on
71
72     ToscaConceptIdentifier policyId;
73     Map<String, MatchableProperty> matchables = new HashMap<>();
74
75     public MatchablePolicyType(@NonNull ToscaPolicyType policyType, @NonNull MatchableCallback callback) {
76         this.policyId = new ToscaConceptIdentifier(policyType.getName(), policyType.getVersion());
77         scanPolicyType(policyType, callback);
78     }
79
80     public MatchableProperty get(@NonNull String property) {
81         return this.matchables.get(property);
82     }
83
84     protected void scanPolicyType(@NonNull ToscaPolicyType inPolicyType, @NonNull MatchableCallback callback) {
85         ToscaPolicyType policyType = inPolicyType;
86         while (policyType != null) {
87             LOGGER.info("Scanning PolicyType {}:{}", policyType.getName(), policyType.getVersion());
88             //
89             // Scan for all the matchable properties
90             //
91             scanProperties(policyType.getProperties(), matchables, callback);
92             //
93             // Does this PolicyType derive from another Policy Type?
94             //
95             if ("tosca.policies.Root".equals(policyType.getDerivedFrom())) {
96                 //
97                 // No we are finished
98                 //
99                 LOGGER.info("Found root - done scanning");
100                 break;
101             }
102             //
103             // Move to the parent policy and scan it for matchables.
104             //
105             policyType = callback.retrievePolicyType(policyType.getDerivedFrom());
106         }
107     }
108
109     /**
110      * Scans properties for matchables.
111      *
112      * @param properties Map of ToscaProperties to scan
113      * @param matchables Found matchables will be put into this list
114      * @param callback   Callback routine for finding Policy Types and Data Types
115      */
116     public static void scanProperties(Map<String, ToscaProperty> properties, Map<String, MatchableProperty> matchables,
117             MatchableCallback callback) {
118         for (Entry<String, ToscaProperty> entrySet : properties.entrySet()) {
119             final String property = entrySet.getKey();
120             final ToscaProperty toscaProperty = entrySet.getValue();
121             //
122             // Most likely case is its a primitive
123             //
124             if (isPrimitive(toscaProperty.getType())) {
125                 MatchableProperty primitiveProperty = handlePrimitive(property, toscaProperty);
126                 if (primitiveProperty != null) {
127                     matchables.put(property, primitiveProperty);
128                 }
129             } else if (TOSCA_TYPE_LIST.equals(toscaProperty.getType())) {
130                 MatchableProperty listProperty = handleList(property, toscaProperty, matchables, callback);
131                 if (listProperty != null) {
132                     matchables.put(property, listProperty);
133                 }
134             } else if (TOSCA_TYPE_MAP.equals(toscaProperty.getType())) {
135                 MatchableProperty mapProperty = handleMap(property, toscaProperty, matchables, callback);
136                 if (mapProperty != null) {
137                     matchables.put(property, mapProperty);
138                 }
139             } else {
140                 scanDatatype(toscaProperty, matchables, callback);
141             }
142         }
143     }
144
145     /**
146      * handlePrimitive - handles a primitive type only if its matchable.
147      *
148      * @param property      String containing property name
149      * @param toscaProperty ToscaProperty object
150      * @return MatchableProperty object
151      */
152     public static MatchableProperty handlePrimitive(String property, ToscaProperty toscaProperty) {
153         if (!isMatchable(toscaProperty)) {
154             return null;
155         }
156         Function<ToscaProperty, MatchablePropertyTypeBase<?>> function =
157                 mapPrimitivesProperty.get(toscaProperty.getType());
158         if (function != null) {
159             return new MatchableProperty(property, function.apply(toscaProperty));
160         }
161         throw new IllegalArgumentException("Not a primitive " + toscaProperty.getType());
162     }
163
164     /**
165      * handlePrimitive from a schema. Note that a ToscaEntrySchema does NOT have a metadata section
166      * so you cannot check if its matchable.
167      *
168      * @param property    String containing property name
169      * @param toscaSchema ToscaSchema
170      * @return MatchableProperty object
171      */
172     public static MatchableProperty handlePrimitive(String property, ToscaSchemaDefinition toscaSchema) {
173         Function<ToscaSchemaDefinition, MatchablePropertyTypeBase<?>> function =
174             mapPrimitivesSchema.get(toscaSchema.getType());
175         if (function != null) {
176             return new MatchableProperty(property, function.apply(toscaSchema));
177         }
178         throw new IllegalArgumentException("Not a primitive " + toscaSchema.getType());
179     }
180
181     /**
182      * handleList - iterates a list looking for matchables.
183      *
184      * @param property      String containing property name
185      * @param toscaProperty ToscaProperty object
186      * @param matchables    list of matchables to add to
187      * @param callback      MatchableCallback to retrieve Data Types
188      * @return MatchableProperty object
189      */
190     public static MatchableProperty handleList(@NonNull String property, @NonNull ToscaProperty toscaProperty,
191                                                Map<String, MatchableProperty> matchables,
192                                                @NonNull MatchableCallback callback) {
193         if (!TOSCA_TYPE_LIST.equals(toscaProperty.getType())) {
194             throw new IllegalArgumentException("must be a list");
195         }
196         //
197         // Is it a simple or complex list?
198         //
199         if (isPrimitive(toscaProperty.getEntrySchema().getType())) {
200             //
201             // Only if the list is matchable
202             //
203             if (!isMatchable(toscaProperty)) {
204                 return null;
205             }
206             //
207             // Simple list of primitives
208             //
209             return new MatchableProperty(property, new MatchablePropertyTypeList(toscaProperty));
210         }
211         //
212         // Scan the datatype for matchables
213         //
214         scanDatatype(toscaProperty.getEntrySchema(), matchables, callback);
215         //
216         // Return nothing - scanning the datatype should find the matchables
217         //
218         return null;
219     }
220
221     /**
222      * handleMap - iterates a map looking for matchables.
223      *
224      * @param property      String containing property name
225      * @param toscaProperty ToscaProperty object
226      * @param matchables    list of matchables to add to
227      * @param callback      MatchableCallback to retrieve Data Types
228      * @return MatchableProperty object
229      */
230     public static MatchableProperty handleMap(@NonNull String property, @NonNull ToscaProperty toscaProperty,
231                                               Map<String, MatchableProperty> matchables,
232                                               @NonNull MatchableCallback callback) {
233         if (!TOSCA_TYPE_MAP.equals(toscaProperty.getType())) {
234             throw new IllegalArgumentException("must be a map");
235         }
236         //
237         // Is it a simple or complex map?
238         //
239         if (isPrimitive(toscaProperty.getEntrySchema().getType())) {
240             //
241             // Only if the map is matchable
242             //
243             if (!isMatchable(toscaProperty)) {
244                 return null;
245             }
246             //
247             // Simple map of primitives
248             //
249             return new MatchableProperty(property, new MatchablePropertyTypeMap(toscaProperty));
250         }
251         //
252         // Scan the datatype for matchables
253         //
254         scanDatatype(toscaProperty.getEntrySchema(), matchables, callback);
255         //
256         // Return nothing - scanning the datatype should find the matchables
257         //
258         return null;
259     }
260
261     /**
262      * scanDatatype - scans a datatypes schema properties for matchables.
263      *
264      * @param toscaProperty ToscaProperty object
265      * @param matchables    list of matchables to add to
266      * @param callback      MatchableCallback to retrieve Data Types
267      */
268     public static void scanDatatype(@NonNull ToscaProperty toscaProperty, Map<String, MatchableProperty> matchables,
269                                     @NonNull MatchableCallback callback) {
270         //
271         // Don't check for matchable property, as that does not make sense to support right now. But we need to
272         // scan the datatype for matchable properties.
273         //
274         LOGGER.info("Retrieving datatype {}", toscaProperty.getType());
275         //
276         // Try to retrieve the datatype
277         //
278         ToscaDataType dataType = callback.retrieveDataType(toscaProperty.getType());
279         if (dataType == null) {
280             LOGGER.error("Failed to retrieve datatype {}", toscaProperty.getType());
281             return;
282         }
283         //
284         // Now scan the properties in the datatype
285         //
286         scanProperties(dataType.getProperties(), matchables, callback);
287     }
288
289     /**
290      * scanDatatypes - scans a datatype schema for matchables.
291      *
292      * @param toscaSchema ToscaEntrySchema
293      * @param matchables  list of matchables to add to
294      * @param callback    MatchableCallback object for retrieving other data types
295      */
296     public static void scanDatatype(@NonNull ToscaSchemaDefinition toscaSchema,
297                                     Map<String, MatchableProperty> matchables,
298                                     @NonNull MatchableCallback callback) {
299         //
300         // Don't check for matchable property, as that does not make sense to support right now. But we need to
301         // scan the datatype for matchable properties.
302         //
303         LOGGER.info("Retrieving datatype {}", toscaSchema.getType());
304         //
305         // Try to retrieve the datatype
306         //
307         ToscaDataType dataType = callback.retrieveDataType(toscaSchema.getType());
308         if (dataType == null) {
309             LOGGER.error("Failed to retrieve datatype {}", toscaSchema.getType());
310             return;
311         }
312         //
313         // Now scan the properties in the datatype
314         //
315         scanProperties(dataType.getProperties(), matchables, callback);
316     }
317
318     /**
319      * isPrimitive.
320      *
321      * @param type String containing type
322      * @return true if it is a primitive
323      */
324     public static boolean isPrimitive(@NonNull String type) {
325         return mapPrimitivesProperty.containsKey(type);
326     }
327
328     /**
329      * isMatchable - scans metadata for matchable field set to true.
330      *
331      * @param property ToscaProperty object
332      * @return true if matchable
333      */
334     public static boolean isMatchable(@NonNull ToscaProperty property) {
335         if (property.getMetadata() == null) {
336             return false;
337         }
338         String isMatchable = property.getMetadata().get("matchable");
339         if (isMatchable == null) {
340             return false;
341         }
342         return "true".equalsIgnoreCase(isMatchable);
343     }
344
345 }