74fd8e5fd6d9db4abb3d1918b4e242e492c7463d
[policy/clamp.git] /
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP CLAMP
4  * ================================================================================
5  * Copyright (C) 2020-2021 AT&T Intellectual Property. All rights
6  *                             reserved.
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  * ============LICENSE_END============================================
20  * ===================================================================
21  *
22  */
23
24 package org.onap.policy.clamp.clds.tosca.update.parser;
25
26 import com.google.gson.JsonArray;
27 import com.google.gson.JsonObject;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.LinkedHashMap;
31 import java.util.Map.Entry;
32 import org.onap.policy.clamp.clds.tosca.update.elements.ToscaElement;
33 import org.onap.policy.clamp.clds.tosca.update.elements.ToscaElementProperty;
34 import org.onap.policy.clamp.clds.tosca.update.parser.metadata.ToscaMetadataParser;
35 import org.onap.policy.clamp.clds.tosca.update.templates.JsonTemplate;
36 import org.onap.policy.clamp.loop.service.Service;
37
38 /**
39  * This class can be used to convert a tosca to a json schema.
40  * This class is not supposed to be used directly because it requires the json Schema templates
41  * (template conversion tosca type to json schema entry) but also the supported Tosca main type file.
42  * The class ToscaConverterWithDictionarySupport is more complete for the end user to be used (in the clamp context).
43  *
44  * @see org.onap.policy.clamp.clds.tosca.update.ToscaConverterWithDictionarySupport#convertToscaToJsonSchemaObject
45  * @see org.onap.policy.clamp.clds.tosca.update.parser.ToscaConverterToJsonSchema#getJsonSchemaOfToscaElement
46  */
47 public class ToscaConverterToJsonSchema {
48     private LinkedHashMap<String, ToscaElement> components;
49     private LinkedHashMap<String, JsonTemplate> templates;
50
51     private ToscaMetadataParser metadataParser;
52
53     private Service serviceModel;
54
55     /**
56      * Constructor.
57      *
58      * @param toscaElementsMap    All the tosca elements found (policy type + data types + native tosca datatypes)
59      * @param jsonSchemaTemplates All Json schema templates to use
60      * @param metadataParser      The metadata parser to use for metadata section
61      * @param serviceModel        The service model for clamp enrichment
62      */
63     public ToscaConverterToJsonSchema(LinkedHashMap<String, ToscaElement> toscaElementsMap,
64                                       LinkedHashMap<String, JsonTemplate> jsonSchemaTemplates,
65                                       ToscaMetadataParser metadataParser, Service serviceModel) {
66         this.components = toscaElementsMap;
67         this.templates = jsonSchemaTemplates;
68         this.metadataParser = metadataParser;
69         this.serviceModel = serviceModel;
70     }
71
72     /**
73      * For a given component, launch process to parse it in Json.
74      *
75      * @param toscaElementKey name components
76      * @return return
77      */
78     public JsonObject getJsonSchemaOfToscaElement(String toscaElementKey) {
79         return this.getFieldAsObject(getToscaElement(toscaElementKey));
80     }
81
82     /**
83      * Return the classical/general fields of the component, & launch the properties deployment.
84      *
85      * @param toscaElement the compo
86      * @return a json object
87      */
88     public JsonObject getFieldAsObject(ToscaElement toscaElement) {
89
90         JsonObject globalFields = new JsonObject();
91         if (templates.get("object").hasFields("title")) {
92             globalFields.addProperty("title", toscaElement.getName());
93         }
94         if (templates.get("object").hasFields("type")) {
95             globalFields.addProperty("type", "object");
96         }
97         if (templates.get("object").hasFields("description")) {
98             if (toscaElement.getDescription() != null) {
99                 globalFields.addProperty("description", toscaElement.getDescription());
100             }
101         }
102         if (templates.get("object").hasFields("required")) {
103             globalFields.add("required", this.getRequirements(toscaElement.getName()));
104         }
105         if (templates.get("object").hasFields("properties")) {
106             globalFields.add("properties", this.deploy(toscaElement.getName()));
107         }
108         return globalFields;
109     }
110
111     /**
112      * Get the required properties of the Component, including the parents properties requirements.
113      *
114      * @param nameComponent name component
115      * @return a json array
116      */
117     public JsonArray getRequirements(String nameComponent) {
118         JsonArray requirements = new JsonArray();
119         ToscaElement toParse = components.get(nameComponent);
120         //Check for a father component, and launch the same process
121         if (!"tosca.datatypes.Root".equals(toParse.getDerivedFrom())
122                 && !"tosca.policies.Root".equals(toParse.getDerivedFrom())) {
123             requirements.addAll(getRequirements(toParse.getDerivedFrom()));
124         }
125         //Each property is checked, and add to the requirement array if it's required
126         Collection<ToscaElementProperty> properties = toParse.getProperties().values();
127         for (ToscaElementProperty toscaElementProperty : properties) {
128             if (toscaElementProperty.getItems().containsKey("required")
129                     && toscaElementProperty.getItems().get("required").equals(true)) {
130                 requirements.add(toscaElementProperty.getName());
131             }
132         }
133         return requirements;
134     }
135
136     /**
137      * The beginning of the recursive process. Get the parents (or not) to launch the same process, and otherwise
138      * deploy and parse the properties.
139      *
140      * @param nameComponent name component
141      * @return a json object
142      */
143     public JsonObject deploy(String nameComponent) {
144         JsonObject jsonSchema = new JsonObject();
145         ToscaElement toParse = components.get(nameComponent);
146         //Check for a father component, and launch the same process
147         if (!toParse.getDerivedFrom().equals("tosca.datatypes.Root")
148                 && !toParse.getDerivedFrom().equals("tosca.policies.Root")) {
149             jsonSchema = this.getParent(toParse.getDerivedFrom());
150         }
151         //For each component property, check if its a complex properties (a component) or not. In that case,
152         //launch the analyse of the property.
153         for (Entry<String, ToscaElementProperty> property : toParse.getProperties().entrySet()) {
154             if (getToscaElement((String) property.getValue().getItems().get("type")) != null) {
155                 jsonSchema.add(property.getValue().getName(),
156                         this.getJsonSchemaOfToscaElement((String) property.getValue().getItems().get("type")));
157             } else {
158                 jsonSchema.add(property.getValue().getName(), this.complexParse(property.getValue()));
159             }
160         }
161         return jsonSchema;
162     }
163
164     /**
165      * If a component has a parent, it is deploy in the same way.
166      *
167      * @param nameComponent name component
168      * @return a json object
169      */
170     public JsonObject getParent(String nameComponent) {
171         return deploy(nameComponent);
172     }
173
174     /**
175      * to be done.
176      *
177      * @param toscaElementProperty property
178      * @return a json object
179      */
180     @SuppressWarnings("unchecked")
181     public JsonObject complexParse(ToscaElementProperty toscaElementProperty) {
182         JsonObject propertiesInJson = new JsonObject();
183         JsonTemplate currentPropertyJsonTemplate;
184         String typeProperty = (String) toscaElementProperty.getItems().get("type");
185         if (typeProperty.toLowerCase().equals("list") || typeProperty.toLowerCase().equals("map")) {
186             currentPropertyJsonTemplate = templates.get("object");
187         } else {
188             String propertyType = (String) toscaElementProperty.getItems().get("type");
189             currentPropertyJsonTemplate = templates.get(propertyType.toLowerCase());
190         }
191         //Each "special" field is analysed, and has a specific treatment
192         for (String propertyField : toscaElementProperty.getItems().keySet()) {
193             switch (propertyField) {
194                 case "type":
195                     if (currentPropertyJsonTemplate.hasFields(propertyField)) {
196                         String fieldtype = (String) toscaElementProperty.getItems().get(propertyField);
197                         switch (fieldtype.toLowerCase()) {
198                             case "list":
199                                 propertiesInJson.addProperty("type", "array");
200                                 break;
201                             case "map":
202                                 propertiesInJson.addProperty("type", "object");
203                                 break;
204                             case "scalar-unit.time":
205                             case "scalar-unit.frequency":
206                             case "scalar-unit.size":
207                                 propertiesInJson.addProperty("type", "string");
208                                 break;
209                             case "timestamp":
210                                 propertiesInJson.addProperty("type", "string");
211                                 propertiesInJson.addProperty("format", "date-time");
212                                 break;
213                             case "float":
214                                 propertiesInJson.addProperty("type", "number");
215                                 break;
216                             case "range":
217                                 propertiesInJson.addProperty("type", "integer");
218                                 if (!checkConstraintPresence(toscaElementProperty, "greater_than")
219                                         && currentPropertyJsonTemplate.hasFields("exclusiveMinimum")) {
220                                     propertiesInJson.addProperty("exclusiveMinimum", false);
221                                 }
222                                 if (!checkConstraintPresence(toscaElementProperty, "less_than")
223                                         && currentPropertyJsonTemplate.hasFields("exclusiveMaximum")) {
224                                     propertiesInJson.addProperty("exclusiveMaximum", false);
225                                 }
226                                 break;
227                             default:
228                                 propertiesInJson.addProperty("type", currentPropertyJsonTemplate.getName());
229                                 break;
230                         }
231                     }
232                     break;
233                 case "metadata":
234                     if (metadataParser != null) {
235                         metadataParser.processAllMetadataElement(toscaElementProperty, serviceModel).entrySet()
236                                 .forEach((jsonEntry) -> {
237                                     propertiesInJson.add(jsonEntry.getKey(),
238                                             jsonEntry.getValue());
239
240                                 });
241                     }
242                     break;
243                 case "constraints":
244                     toscaElementProperty.addConstraintsAsJson(propertiesInJson,
245                             (ArrayList<Object>) toscaElementProperty.getItems().get("constraints"),
246                             currentPropertyJsonTemplate);
247                     break;
248                 case "entry_schema":
249                     //Here, a way to check if entry is a component (datatype) or a simple string
250                     if (getToscaElement(this.extractSpecificFieldFromMap(toscaElementProperty, "entry_schema"))
251                             != null) {
252                         String nameComponent = this.extractSpecificFieldFromMap(toscaElementProperty, "entry_schema");
253                         ToscaConverterToJsonSchema child = new ToscaConverterToJsonSchema(components, templates,
254                                 metadataParser, serviceModel);
255                         JsonObject propertiesContainer = new JsonObject();
256
257                         switch ((String) toscaElementProperty.getItems().get("type")) {
258                             case "map": // Get it as an object
259                                 JsonObject componentAsProperty = child.getJsonSchemaOfToscaElement(nameComponent);
260                                 propertiesContainer.add(nameComponent, componentAsProperty);
261                                 if (currentPropertyJsonTemplate.hasFields("properties")) {
262                                     propertiesInJson.add("properties", propertiesContainer);
263                                 }
264                                 break;
265                             default://list : get it as an Array
266                                 JsonObject componentAsItem = child.getJsonSchemaOfToscaElement(nameComponent);
267                                 if (currentPropertyJsonTemplate.hasFields("properties")) {
268                                     propertiesInJson.add("items", componentAsItem);
269                                     propertiesInJson.addProperty("format", "tabs-top");
270                                 }
271                                 break;
272                         }
273
274                     } else if (toscaElementProperty.getItems().get("type").equals("list")) {
275                         // Native cases
276                         JsonObject itemContainer = new JsonObject();
277                         String valueInEntrySchema =
278                             this.extractSpecificFieldFromMap(toscaElementProperty, "entry_schema");
279                         itemContainer.addProperty("type", valueInEntrySchema);
280                         propertiesInJson.add("items", itemContainer);
281                         propertiesInJson.addProperty("format", "tabs-top");
282                     }
283
284                     // MAP Case, for now nothing
285
286                     break;
287                 default:
288                     //Each classical field : type, description, default..
289                     if (currentPropertyJsonTemplate.hasFields(propertyField) && !propertyField.equals("required")) {
290                         toscaElementProperty.addFieldToJson(propertiesInJson, propertyField,
291                                 toscaElementProperty.getItems().get(propertyField));
292                     }
293                     break;
294             }
295         }
296         return propertiesInJson;
297     }
298
299     /**
300      * Look for a matching Component for the name parameter, in the components list.
301      *
302      * @param name the tosca element name to search for
303      * @return a tosca element
304      */
305     public ToscaElement getToscaElement(String name) {
306         ToscaElement correspondingToscaElement = null;
307         if (components == null) {
308             return null;
309         }
310         for (ToscaElement toscaElement : components.values()) {
311             if (toscaElement.getName().equals(name)) {
312                 correspondingToscaElement = toscaElement;
313             }
314         }
315         return correspondingToscaElement;
316     }
317
318     /**
319      * Simple method to extract quickly a type field from particular property item.
320      *
321      * @param toscaElementProperty the property
322      * @param fieldName            the fieldname
323      * @return a string
324      */
325     @SuppressWarnings("unchecked")
326     public String extractSpecificFieldFromMap(ToscaElementProperty toscaElementProperty, String fieldName) {
327         LinkedHashMap<String, String> entrySchemaFields =
328                 (LinkedHashMap<String, String>) toscaElementProperty.getItems().get(fieldName);
329         return entrySchemaFields.get("type");
330     }
331
332     /**
333      * Check if a constraint, for a specific property, is there.
334      *
335      * @param toscaElementProperty property
336      * @param nameConstraint       name constraint
337      * @return a flag boolean
338      */
339     public boolean checkConstraintPresence(ToscaElementProperty toscaElementProperty, String nameConstraint) {
340         boolean presentConstraint = false;
341         if (toscaElementProperty.getItems().containsKey("constraints")) {
342             ArrayList<Object> constraints = (ArrayList) toscaElementProperty.getItems().get("constraints");
343             for (Object constraint : constraints) {
344                 if (constraint instanceof LinkedHashMap) {
345                     if (((LinkedHashMap) constraint).containsKey(nameConstraint)) {
346                         presentConstraint = true;
347                     }
348                 }
349             }
350         }
351         return presentConstraint;
352     }
353 }