117df1e5b875e6c8294992f1b35171c5240bfcd6
[ccsdk/cds.git] / ms / blueprintsprocessor / functions / resource-resolution / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / functions / resource / resolution / utils / ResourceAssignmentUtils.kt
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Modifications Copyright (c) 2019 IBM, Bell Canada.
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
18 package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils
19
20 import com.fasterxml.jackson.databind.JsonNode
21 import com.fasterxml.jackson.databind.ObjectMapper
22 import com.fasterxml.jackson.databind.node.ArrayNode
23 import com.fasterxml.jackson.databind.node.NullNode
24 import com.fasterxml.jackson.databind.node.ObjectNode
25 import com.fasterxml.jackson.databind.node.TextNode
26 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceAssignmentRuntimeService
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
28 import org.onap.ccsdk.cds.controllerblueprints.core.*
29 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
30 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils
31 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
32 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
33 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition
34 import org.slf4j.LoggerFactory
35 import java.util.*
36
37 class ResourceAssignmentUtils {
38     companion object {
39
40         private val logger = LoggerFactory.getLogger(ResourceAssignmentUtils::class.toString())
41
42         suspend fun resourceDefinitions(blueprintBasePath: String): MutableMap<String, ResourceDefinition> {
43             val dictionaryFile = normalizedFile(blueprintBasePath, BluePrintConstants.TOSCA_DEFINITIONS_DIR,
44                     ResourceResolutionConstants.FILE_NAME_RESOURCE_DEFINITION_TYPES)
45             checkFileExists(dictionaryFile) { "resource definition file(${dictionaryFile.absolutePath}) is missing" }
46             return JacksonReactorUtils.getMapFromFile(dictionaryFile, ResourceDefinition::class.java)
47         }
48
49         @Throws(BluePrintProcessorException::class)
50         fun setResourceDataValue(resourceAssignment: ResourceAssignment,
51                                  raRuntimeService: ResourceAssignmentRuntimeService, value: Any?) {
52             // TODO("See if Validation is needed in future with respect to conversion and Types")
53             return setResourceDataValue(resourceAssignment, raRuntimeService, value.asJsonType())
54         }
55
56         @Throws(BluePrintProcessorException::class)
57         fun setResourceDataValue(resourceAssignment: ResourceAssignment,
58                                  raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) {
59             val resourceProp = checkNotNull(resourceAssignment.property) {
60                 "Failed in setting resource value for resource mapping $resourceAssignment"
61             }
62             checkNotEmpty(resourceAssignment.name) {
63                 "Failed in setting resource value for resource mapping $resourceAssignment"
64             }
65
66             if (resourceAssignment.dictionaryName.isNullOrEmpty()) {
67                 resourceAssignment.dictionaryName = resourceAssignment.name
68                 logger.warn("Missing dictionary key, setting with template key (${resourceAssignment.name}) " +
69                         "as dictionary key (${resourceAssignment.dictionaryName})")
70             }
71
72             try {
73                 if (resourceProp.type.isNotEmpty()) {
74                     logger.info("Setting Resource Value ($value) for Resource Name " +
75                             "(${resourceAssignment.name}), definition(${resourceAssignment.dictionaryName}) " +
76                             "of type (${resourceProp.type})")
77                     setResourceValue(resourceAssignment, raRuntimeService, value)
78                     resourceAssignment.updatedDate = Date()
79                     resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
80                     resourceAssignment.status = BluePrintConstants.STATUS_SUCCESS
81                 }
82             } catch (e: Exception) {
83                 throw BluePrintProcessorException("Failed in setting value for template key " +
84                         "(${resourceAssignment.name}) and dictionary key (${resourceAssignment.dictionaryName}) of " +
85                         "type (${resourceProp.type}) with error message (${e.message})", e)
86             }
87         }
88
89         private fun setResourceValue(resourceAssignment: ResourceAssignment,
90                                      raRuntimeService: ResourceAssignmentRuntimeService, value: JsonNode) {
91             // TODO("See if Validation is needed wrt to type before storing")
92             raRuntimeService.putResolutionStore(resourceAssignment.name, value)
93             raRuntimeService.putDictionaryStore(resourceAssignment.dictionaryName!!, value)
94             resourceAssignment.property!!.value = value
95         }
96
97         fun setFailedResourceDataValue(resourceAssignment: ResourceAssignment, message: String?) {
98             if (isNotEmpty(resourceAssignment.name)) {
99                 resourceAssignment.updatedDate = Date()
100                 resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
101                 resourceAssignment.status = BluePrintConstants.STATUS_FAILURE
102                 resourceAssignment.message = message
103             }
104         }
105
106         @Throws(BluePrintProcessorException::class)
107         fun assertTemplateKeyValueNotNull(resourceAssignment: ResourceAssignment) {
108             val resourceProp = checkNotNull(resourceAssignment.property) {
109                 "Failed to populate mandatory resource resource mapping $resourceAssignment"
110             }
111             if (resourceProp.required != null && resourceProp.required!!
112                     && (resourceProp.value == null || resourceProp.value!!.returnNullIfMissing() == null)) {
113                 logger.error("failed to populate mandatory resource mapping ($resourceAssignment)")
114                 throw BluePrintProcessorException("failed to populate mandatory resource mapping ($resourceAssignment)")
115             }
116         }
117
118         @Throws(BluePrintProcessorException::class)
119         fun generateResourceDataForAssignments(assignments: List<ResourceAssignment>): String {
120             val result: String
121             try {
122                 val mapper = ObjectMapper()
123                 val root: ObjectNode = mapper.createObjectNode()
124
125                 assignments.forEach {
126                     if (isNotEmpty(it.name) && it.property != null) {
127                         val rName = it.name
128                         val type = nullToEmpty(it.property?.type).toLowerCase()
129                         val value = useDefaultValueIfNull(it, rName)
130                         logger.info("Generating Resource name ($rName), type ($type), value ($value)")
131                         root.set(rName, value)
132                     }
133                 }
134                 result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)
135                 logger.info("Generated Resource Param Data ($result)")
136             } catch (e: Exception) {
137                 throw BluePrintProcessorException("Resource Assignment is failed with $e.message", e)
138             }
139
140             return result
141         }
142
143         @Throws(BluePrintProcessorException::class)
144         fun generateResourceForAssignments(assignments: List<ResourceAssignment>): MutableMap<String, JsonNode> {
145             val data: MutableMap<String, JsonNode> = hashMapOf()
146             assignments.forEach {
147                 if (isNotEmpty(it.name) && it.property != null) {
148                     val rName = it.name
149                     val type = nullToEmpty(it.property?.type).toLowerCase()
150                     val value = useDefaultValueIfNull(it, rName)
151                     logger.trace("Generating Resource name ($rName), type ($type), value ($value)")
152                     data[rName] = value
153                 }
154             }
155             return data
156         }
157
158         private fun useDefaultValueIfNull(resourceAssignment: ResourceAssignment, resourceAssignmentName: String): JsonNode {
159             if (resourceAssignment.property?.value == null) {
160                 val defaultValue = "\${$resourceAssignmentName}"
161                 return TextNode(defaultValue)
162             } else {
163                 return resourceAssignment.property!!.value!!
164             }
165         }
166
167         fun transformToRARuntimeService(blueprintRuntimeService: BluePrintRuntimeService<*>,
168                                         templateArtifactName: String): ResourceAssignmentRuntimeService {
169
170             val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(blueprintRuntimeService.id(),
171                     blueprintRuntimeService.bluePrintContext())
172             resourceAssignmentRuntimeService.createUniqueId(templateArtifactName)
173             resourceAssignmentRuntimeService.setExecutionContext(blueprintRuntimeService.getExecutionContext() as MutableMap<String, JsonNode>)
174
175             return resourceAssignmentRuntimeService
176         }
177
178         @Throws(BluePrintProcessorException::class)
179         fun getPropertyType(raRuntimeService: ResourceAssignmentRuntimeService, dataTypeName: String,
180                             propertyName: String): String {
181             lateinit var type: String
182             try {
183                 val dataTypeProps = checkNotNull(raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties)
184
185                 val propertyDefinition = checkNotNull(dataTypeProps[propertyName])
186                 type = checkNotEmpty(propertyDefinition.type) { "Couldn't get data type ($dataTypeName)" }
187                 logger.trace("Data type({})'s property ({}) is ({})", dataTypeName, propertyName, type)
188             } catch (e: Exception) {
189                 logger.error("couldn't get data type($dataTypeName)'s property ($propertyName), error message $e")
190                 throw BluePrintProcessorException("${e.message}", e)
191             }
192             return type
193         }
194
195         @Throws(BluePrintProcessorException::class)
196         fun parseResponseNode(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
197                               raRuntimeService: ResourceAssignmentRuntimeService, outputKeyMapping: MutableMap<String, String>): JsonNode {
198             try {
199                 if ((resourceAssignment.property?.type).isNullOrEmpty()) {
200                     throw BluePrintProcessorException("Couldn't get data dictionary type for dictionary name (${resourceAssignment.name})")
201                 }
202                 val type = resourceAssignment.property!!.type
203                 return when (type) {
204                     in BluePrintTypes.validPrimitiveTypes() -> {
205                         parseResponseNodeForPrimitiveTypes(responseNode, resourceAssignment, outputKeyMapping)
206                     }
207                     in BluePrintTypes.validCollectionTypes() -> {
208                         // Array Types
209                         parseResponseNodeForCollection(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
210                     }
211                     else -> {
212                         // Complex Types
213                         parseResponseNodeForComplexType(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
214                     }
215                 }
216             } catch (e: Exception) {
217                 logger.error("Fail to parse response data, error message $e")
218                 throw BluePrintProcessorException("${e.message}", e)
219             }
220         }
221
222         private fun parseResponseNodeForPrimitiveTypes(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
223                                                        outputKeyMapping: MutableMap<String, String>): JsonNode {
224             val dName = resourceAssignment.dictionaryName
225             logger.info("For template key (${resourceAssignment.name}) setting value as ($responseNode)")
226
227             var result: JsonNode? = responseNode
228             if (responseNode.isComplexType()) {
229                 val key = outputKeyMapping.keys.firstOrNull()
230                 var returnNode: JsonNode? = responseNode
231                 if (responseNode is ArrayNode) {
232                     val arrayNode = responseNode.toList()
233                     val firstElement = if (key.isNullOrEmpty()) {
234                         arrayNode.first()
235                     }
236                     else{
237                         arrayNode.firstOrNull { element ->
238                             element.isComplexType() && element.has(outputKeyMapping[key])
239                         }
240                     }
241
242                     if (firstElement.isNull() || (firstElement!!.isComplexType() && !firstElement!!.has(outputKeyMapping[key]))
243                             || (!result!!.isComplexType() && result is NullNode)) {
244                         if (key.isNullOrEmpty()) {
245                             throw BluePrintProcessorException("Fail to find mapping in the responseNode.")
246                         }
247                         else {
248                             throw BluePrintProcessorException("Fail to find response with output key mapping ($key) in result.")
249                         }
250                     }
251                     returnNode = firstElement
252                 }
253                 result = if (returnNode!!.isComplexType()) {
254                     returnNode[outputKeyMapping[key]]
255                 }
256                 else {
257                     returnNode
258                 }
259             }
260             return result!!
261         }
262
263         private fun parseResponseNodeForCollection(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
264                                                    raRuntimeService: ResourceAssignmentRuntimeService,
265                                                    outputKeyMapping: MutableMap<String, String>): JsonNode {
266             val dName = resourceAssignment.dictionaryName
267             if ((resourceAssignment.property?.entrySchema?.type).isNullOrEmpty()) {
268                 throw BluePrintProcessorException("Couldn't get data type for dictionary type " +
269                         "(${resourceAssignment.property!!.type}) and dictionary name ($dName)")
270             }
271             val entrySchemaType = resourceAssignment.property!!.entrySchema!!.type
272
273             var arrayNode = JacksonUtils.objectMapper.createArrayNode()
274
275             if (outputKeyMapping.isNotEmpty()) {
276                 when (responseNode) {
277                     is ArrayNode -> {
278                         val responseArrayNode = responseNode.toList()
279                         for (responseSingleJsonNode in responseArrayNode) {
280                             val arrayChildNode = parseArrayNodeElementWithOutputKeyMapping(raRuntimeService, responseSingleJsonNode,
281                                     outputKeyMapping, entrySchemaType)
282                             arrayNode.add(arrayChildNode)
283                         }
284                     }
285                     is ObjectNode -> {
286                         val responseArrayNode = responseNode.rootFieldsToMap()
287                         val arrayNodeResult = parseObjectNodeWithOutputKeyMapping(responseArrayNode, outputKeyMapping, entrySchemaType)
288                         arrayNode.addAll(arrayNodeResult)
289                     }
290                     else -> {
291                         throw BluePrintProcessorException("Key-value response expected to match the responseNode.")
292                     }
293                 }
294             }
295             else {
296                 when (responseNode) {
297                     is ArrayNode -> {
298                         responseNode.forEach { elementNode ->
299                             arrayNode.add(elementNode)
300                         }
301                     }
302                     is ObjectNode -> {
303                         val responseArrayNode = responseNode.rootFieldsToMap()
304                         for ((key, responseSingleJsonNode) in responseArrayNode) {
305                             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
306                             JacksonUtils.populateJsonNodeValues(key, responseSingleJsonNode, entrySchemaType, arrayChildNode)
307                             arrayNode.add(arrayChildNode)
308                         }
309                     }
310                     else -> {
311                         arrayNode.add(responseNode)
312                     }
313                 }
314             }
315
316             logger.info("For template key (${resourceAssignment.name}) setting value as ($arrayNode)")
317
318             return arrayNode
319         }
320
321         private fun parseResponseNodeForComplexType(responseNode: JsonNode, resourceAssignment: ResourceAssignment,
322                                                     raRuntimeService: ResourceAssignmentRuntimeService,
323                                                     outputKeyMapping: MutableMap<String, String>): JsonNode {
324             val entrySchemaType = resourceAssignment.property!!.type
325             val dictionaryName = resourceAssignment.dictionaryName!!
326
327             var result: ObjectNode
328             if (checkOutputKeyMappingInDataTypeProperties(entrySchemaType, outputKeyMapping, raRuntimeService))
329             {
330                 result = parseArrayNodeElementWithOutputKeyMapping(raRuntimeService, responseNode, outputKeyMapping, entrySchemaType)
331             }
332             else {
333                 val childNode = JacksonUtils.objectMapper.createObjectNode()
334                 if (outputKeyMapping.isNotEmpty()) {
335                     outputKeyMapping.map {
336                         val responseKeyValue = if (responseNode.has(it.key)) {
337                             responseNode.get(it.key)
338                         }
339                         else {
340                             NullNode.getInstance()
341                         }
342
343                         JacksonUtils.populateJsonNodeValues(it.value,
344                                 responseKeyValue, entrySchemaType, childNode)
345                     }
346                 }
347                 else {
348                     JacksonUtils.populateJsonNodeValues(dictionaryName, responseNode, entrySchemaType, childNode)
349                 }
350                 result = childNode
351             }
352             return result
353         }
354
355         private fun parseArrayNodeElementWithOutputKeyMapping(raRuntimeService: ResourceAssignmentRuntimeService,
356                                                               responseSingleJsonNode: JsonNode, outputKeyMapping:
357                                                               MutableMap<String, String>, entrySchemaType: String): ObjectNode {
358             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
359
360             outputKeyMapping.map {
361                 val responseKeyValue = if (responseSingleJsonNode.has(it.key)) {
362                     responseSingleJsonNode.get(it.key)
363                 }
364                 else {
365                     NullNode.getInstance()
366                 }
367                 val propertyTypeForDataType = ResourceAssignmentUtils
368                         .getPropertyType(raRuntimeService, entrySchemaType, it.key)
369
370                 logger.info("For List Type Resource: key (${it.key}), value ($responseKeyValue), " +
371                         "type  ({$propertyTypeForDataType})")
372
373                 JacksonUtils.populateJsonNodeValues(it.value,
374                         responseKeyValue, propertyTypeForDataType, arrayChildNode)
375             }
376
377             return arrayChildNode
378         }
379
380         private fun parseObjectNodeWithOutputKeyMapping(responseArrayNode: MutableMap<String, JsonNode>,
381                                                         outputKeyMapping: MutableMap<String, String>,
382                                                         entrySchemaType: String): ArrayNode {
383             val arrayNode = JacksonUtils.objectMapper.createArrayNode()
384             outputKeyMapping.map {
385                 val objectNode = JacksonUtils.objectMapper.createObjectNode()
386                 val responseSingleJsonNode = responseArrayNode.filterKeys { key -> key == it.key }.entries.firstOrNull()
387
388                 if (responseSingleJsonNode == null) {
389                     JacksonUtils.populateJsonNodeValues(it.value, NullNode.getInstance(), entrySchemaType, objectNode)
390                 }
391                 else
392                 {
393                     JacksonUtils.populateJsonNodeValues(it.value, responseSingleJsonNode.value, entrySchemaType, objectNode)
394                 }
395                 arrayNode.add(objectNode)
396             }
397
398             return arrayNode
399         }
400
401         private fun checkOutputKeyMappingInDataTypeProperties(dataTypeName: String, outputKeyMapping: MutableMap<String, String>,
402                                                               raRuntimeService: ResourceAssignmentRuntimeService): Boolean {
403             val dataTypeProps = raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties
404             val result = outputKeyMapping.filterKeys { !dataTypeProps!!.containsKey(it) }.keys.firstOrNull()
405             return result == null
406         }
407     }
408 }