2 * Copyright © 2017-2018 AT&T Intellectual Property.
3 * Modifications Copyright (c) 2019 IBM, Bell Canada.
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils
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.BluePrintConstants
29 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintTypes
31 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonType
32 import org.onap.ccsdk.cds.controllerblueprints.core.checkFileExists
33 import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty
34 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants.LOG_REDACTED
35 import org.onap.ccsdk.cds.controllerblueprints.core.isComplexType
36 import org.onap.ccsdk.cds.controllerblueprints.core.isNotEmpty
37 import org.onap.ccsdk.cds.controllerblueprints.core.isNullOrMissing
38 import org.onap.ccsdk.cds.controllerblueprints.core.normalizedFile
39 import org.onap.ccsdk.cds.controllerblueprints.core.nullToEmpty
40 import org.onap.ccsdk.cds.controllerblueprints.core.rootFieldsToMap
41 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
42 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils
43 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
44 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils.Companion.hasLogProtect
45 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.KeyIdentifier
46 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResolutionSummary
47 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
48 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition
49 import org.slf4j.LoggerFactory
52 class ResourceAssignmentUtils {
55 private val logger = LoggerFactory.getLogger(ResourceAssignmentUtils::class.toString())
57 suspend fun resourceDefinitions(blueprintBasePath: String): MutableMap<String, ResourceDefinition> {
58 val dictionaryFile = normalizedFile(
59 blueprintBasePath, BluePrintConstants.TOSCA_DEFINITIONS_DIR,
60 ResourceResolutionConstants.FILE_NAME_RESOURCE_DEFINITION_TYPES
62 checkFileExists(dictionaryFile) { "resource definition file(${dictionaryFile.absolutePath}) is missing" }
63 return JacksonReactorUtils.getMapFromFile(dictionaryFile, ResourceDefinition::class.java)
66 @Throws(BluePrintProcessorException::class)
67 fun setResourceDataValue(
68 resourceAssignment: ResourceAssignment,
69 raRuntimeService: ResourceAssignmentRuntimeService,
72 // TODO("See if Validation is needed in future with respect to conversion and Types")
73 return setResourceDataValue(resourceAssignment, raRuntimeService, value.asJsonType())
76 @Throws(BluePrintProcessorException::class)
77 fun setResourceDataValue(
78 resourceAssignment: ResourceAssignment,
79 raRuntimeService: ResourceAssignmentRuntimeService,
82 val resourceProp = checkNotNull(resourceAssignment.property) {
83 "Failed in setting resource value for resource mapping $resourceAssignment"
85 checkNotEmpty(resourceAssignment.name) {
86 "Failed in setting resource value for resource mapping $resourceAssignment"
89 if (resourceAssignment.dictionaryName.isNullOrEmpty()) {
90 resourceAssignment.dictionaryName = resourceAssignment.name
92 "Missing dictionary key, setting with template key (${resourceAssignment.name}) " +
93 "as dictionary key (${resourceAssignment.dictionaryName})"
98 if (resourceProp.type.isNotEmpty()) {
99 val metadata = resourceAssignment.property!!.metadata
100 val valueToPrint = getValueToLog(metadata, value)
102 "Setting Resource Value ($valueToPrint) for Resource Name " +
103 "(${resourceAssignment.name}), definition(${resourceAssignment.dictionaryName}) " +
104 "of type (${resourceProp.type})"
106 setResourceValue(resourceAssignment, raRuntimeService, value)
107 resourceAssignment.updatedDate = Date()
108 resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
109 resourceAssignment.status = BluePrintConstants.STATUS_SUCCESS
111 } catch (e: Exception) {
112 throw BluePrintProcessorException(
113 "Failed in setting value for template key " +
114 "(${resourceAssignment.name}) and dictionary key (${resourceAssignment.dictionaryName}) of " +
115 "type (${resourceProp.type}) with error message (${e.message})", e
120 private fun setResourceValue(
121 resourceAssignment: ResourceAssignment,
122 raRuntimeService: ResourceAssignmentRuntimeService,
125 // TODO("See if Validation is needed wrt to type before storing")
126 raRuntimeService.putResolutionStore(resourceAssignment.name, value)
127 raRuntimeService.putDictionaryStore(resourceAssignment.dictionaryName!!, value)
128 resourceAssignment.property!!.value = value
131 fun setFailedResourceDataValue(resourceAssignment: ResourceAssignment, message: String?) {
132 if (isNotEmpty(resourceAssignment.name)) {
133 resourceAssignment.updatedDate = Date()
134 resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
135 resourceAssignment.status = BluePrintConstants.STATUS_FAILURE
136 resourceAssignment.message = message
140 @Throws(BluePrintProcessorException::class)
141 fun assertTemplateKeyValueNotNull(resourceAssignment: ResourceAssignment) {
142 val resourceProp = checkNotNull(resourceAssignment.property) {
143 "Failed to populate mandatory resource resource mapping $resourceAssignment"
145 if (resourceProp.required != null && resourceProp.required!! && resourceProp.value.isNullOrMissing()) {
146 logger.error("failed to populate mandatory resource mapping ($resourceAssignment)")
147 throw BluePrintProcessorException("failed to populate mandatory resource mapping ($resourceAssignment)")
151 @Throws(BluePrintProcessorException::class)
152 fun generateResourceDataForAssignments(assignments: List<ResourceAssignment>): String {
155 val mapper = ObjectMapper()
156 val root: ObjectNode = mapper.createObjectNode()
158 var containsLogProtected = false
159 assignments.forEach {
160 if (isNotEmpty(it.name) && it.property != null) {
162 val metadata = it.property!!.metadata
163 val type = nullToEmpty(it.property?.type).toLowerCase()
164 val value = useDefaultValueIfNull(it, rName)
165 val valueToPrint = getValueToLog(metadata, value)
166 containsLogProtected = hasLogProtect(metadata)
167 logger.trace("Generating Resource name ($rName), type ($type), value ($valueToPrint)")
168 root.set<JsonNode>(rName, value)
171 result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)
173 if (!containsLogProtected) {
174 logger.info("Generated Resource Param Data ($result)")
176 } catch (e: Exception) {
177 throw BluePrintProcessorException("Resource Assignment is failed with $e.message", e)
183 @Throws(BluePrintProcessorException::class)
184 fun generateResourceForAssignments(assignments: List<ResourceAssignment>): MutableMap<String, JsonNode> {
185 val data: MutableMap<String, JsonNode> = hashMapOf()
186 assignments.forEach {
187 if (isNotEmpty(it.name) && it.property != null) {
189 val metadata = it.property!!.metadata
190 val type = nullToEmpty(it.property?.type).toLowerCase()
191 val value = useDefaultValueIfNull(it, rName)
192 val valueToPrint = getValueToLog(metadata, value)
194 logger.trace("Generating Resource name ($rName), type ($type), value ($valueToPrint)")
201 fun generateResolutionSummaryData(
202 resourceAssignments: List<ResourceAssignment>,
203 resourceDefinitions: Map<String, ResourceDefinition>
205 val resolutionSummaryList = resourceAssignments.map {
206 val payload = resourceDefinitions[it.name]
207 ?.sources?.get(it.dictionarySource)?.properties?.get("payload")
209 it.name, it.property?.value, it.property?.required,
210 it.property?.type, it.keyIdentifiers, it.dictionaryName,
211 payload, it.dictionarySource, it.status, it.message
214 return JacksonUtils.getJson(resolutionSummaryList, includeNull = true)
217 private fun useDefaultValueIfNull(
218 resourceAssignment: ResourceAssignment,
219 resourceAssignmentName: String
221 if (resourceAssignment.property?.value == null) {
222 val defaultValue = "\${$resourceAssignmentName}"
223 return TextNode(defaultValue)
225 return resourceAssignment.property!!.value!!
229 fun transformToRARuntimeService(
230 blueprintRuntimeService: BluePrintRuntimeService<*>,
231 templateArtifactName: String
232 ): ResourceAssignmentRuntimeService {
234 val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(
235 blueprintRuntimeService.id(),
236 blueprintRuntimeService.bluePrintContext()
238 resourceAssignmentRuntimeService.createUniqueId(templateArtifactName)
239 resourceAssignmentRuntimeService.setExecutionContext(blueprintRuntimeService.getExecutionContext() as MutableMap<String, JsonNode>)
241 return resourceAssignmentRuntimeService
244 @Throws(BluePrintProcessorException::class)
246 raRuntimeService: ResourceAssignmentRuntimeService,
247 dataTypeName: String,
250 lateinit var type: String
253 checkNotNull(raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties)
255 val propertyDefinition = checkNotNull(dataTypeProps[propertyName])
256 type = checkNotEmpty(propertyDefinition.type) { "Couldn't get data type ($dataTypeName)" }
257 logger.trace("Data type({})'s property ({}) is ({})", dataTypeName, propertyName, type)
258 } catch (e: Exception) {
259 logger.error("couldn't get data type($dataTypeName)'s property ($propertyName), error message $e")
260 throw BluePrintProcessorException("${e.message}", e)
265 @Throws(BluePrintProcessorException::class)
266 fun parseResponseNode(
267 responseNode: JsonNode,
268 resourceAssignment: ResourceAssignment,
269 raRuntimeService: ResourceAssignmentRuntimeService,
270 outputKeyMapping: MutableMap<String, String>
272 val metadata = resourceAssignment.property!!.metadata
274 if ((resourceAssignment.property?.type).isNullOrEmpty()) {
275 throw BluePrintProcessorException("Couldn't get data dictionary type for dictionary name (${resourceAssignment.name})")
277 val type = resourceAssignment.property!!.type
278 val valueToPrint = getValueToLog(metadata, responseNode)
280 logger.info("For template key (${resourceAssignment.name}) trying to get value from responseNode ($valueToPrint)")
282 in BluePrintTypes.validPrimitiveTypes() -> {
284 parseResponseNodeForPrimitiveTypes(responseNode, resourceAssignment, outputKeyMapping)
286 in BluePrintTypes.validCollectionTypes() -> {
288 parseResponseNodeForCollection(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
292 parseResponseNodeForComplexType(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
295 } catch (e: Exception) {
296 logger.error("Fail to parse response data, error message $e")
297 throw BluePrintProcessorException("${e.message}", e)
301 private fun parseResponseNodeForPrimitiveTypes(
302 responseNode: JsonNode,
303 resourceAssignment: ResourceAssignment,
304 outputKeyMapping: MutableMap<String, String>
306 // Return responseNode if is not a Complex Type
307 if (!responseNode.isComplexType()) {
311 val outputKey = outputKeyMapping.keys.firstOrNull()
312 var returnNode = if (responseNode is ArrayNode) {
313 val arrayNode = responseNode.toList()
314 if (outputKey.isNullOrEmpty()) {
317 arrayNode.firstOrNull { element ->
318 element.isComplexType() && element.has(outputKeyMapping[outputKey])
325 if (returnNode.isNullOrMissing() || returnNode!!.isComplexType() && !returnNode.has(outputKeyMapping[outputKey])) {
326 throw BluePrintProcessorException("Fail to find output key mapping ($outputKey) in the responseNode.")
329 val returnValue = if (returnNode.isComplexType()) {
330 returnNode[outputKeyMapping[outputKey]]
335 outputKey?.let { KeyIdentifier(it, returnValue) }
336 ?.let { resourceAssignment.keyIdentifiers.add(it) }
340 private fun parseResponseNodeForCollection(
341 responseNode: JsonNode,
342 resourceAssignment: ResourceAssignment,
343 raRuntimeService: ResourceAssignmentRuntimeService,
344 outputKeyMapping: MutableMap<String, String>
346 val dName = resourceAssignment.dictionaryName
347 val metadata = resourceAssignment.property!!.metadata
348 var resultNode: JsonNode
349 if ((resourceAssignment.property?.entrySchema?.type).isNullOrEmpty()) {
350 throw BluePrintProcessorException(
351 "Couldn't get data type for dictionary type " +
352 "(${resourceAssignment.property!!.type}) and dictionary name ($dName)"
355 val entrySchemaType = resourceAssignment.property!!.entrySchema!!.type
357 var arrayNode = JacksonUtils.objectMapper.createArrayNode()
358 if (outputKeyMapping.isNotEmpty()) {
359 when (responseNode) {
361 val responseArrayNode = responseNode.toList()
362 for (responseSingleJsonNode in responseArrayNode) {
363 val arrayChildNode = parseSingleElementOfArrayResponseNode(
364 entrySchemaType, resourceAssignment,
365 outputKeyMapping, raRuntimeService, responseSingleJsonNode, metadata
367 arrayNode.add(arrayChildNode)
369 resultNode = arrayNode
372 val responseArrayNode = responseNode.rootFieldsToMap()
374 parseObjectResponseNode(
375 resourceAssignment, entrySchemaType, outputKeyMapping,
376 responseArrayNode, metadata
380 throw BluePrintProcessorException("Key-value response expected to match the responseNode.")
384 when (responseNode) {
386 responseNode.forEach { elementNode ->
387 arrayNode.add(elementNode)
389 resultNode = arrayNode
392 val responseArrayNode = responseNode.rootFieldsToMap()
393 for ((key, responseSingleJsonNode) in responseArrayNode) {
394 val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
395 logKeyValueResolvedResource(metadata, key, responseSingleJsonNode, entrySchemaType)
396 JacksonUtils.populateJsonNodeValues(
398 responseSingleJsonNode,
402 arrayNode.add(arrayChildNode)
404 resultNode = arrayNode
407 resultNode = responseNode
415 private fun parseSingleElementOfArrayResponseNode(
416 entrySchemaType: String,
417 resourceAssignment: ResourceAssignment,
418 outputKeyMapping: MutableMap<String, String>,
419 raRuntimeService: ResourceAssignmentRuntimeService,
420 responseNode: JsonNode,
421 metadata: MutableMap<String, String>?
423 val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
424 when (entrySchemaType) {
425 in BluePrintTypes.validPrimitiveTypes() -> {
426 if (outputKeyMappingHasOnlyOneElement) {
427 val outputKeyMap = outputKeyMapping.entries.first()
428 if (resourceAssignment.keyIdentifiers.none { it.name == outputKeyMap.key }) {
429 resourceAssignment.keyIdentifiers.add(
430 KeyIdentifier(outputKeyMap.key, JacksonUtils.objectMapper.createArrayNode())
433 return parseSingleElementNodeWithOneOutputKeyMapping(
442 throw BluePrintProcessorException("Expect one entry in output-key-mapping")
447 checkOutputKeyMappingAllElementsInDataTypeProperties(
452 parseSingleElementNodeWithAllOutputKeyMapping(
460 outputKeyMappingHasOnlyOneElement -> {
461 val outputKeyMap = outputKeyMapping.entries.first()
462 parseSingleElementNodeWithOneOutputKeyMapping(
472 throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
479 private fun parseObjectResponseNode(
480 resourceAssignment: ResourceAssignment,
481 entrySchemaType: String,
482 outputKeyMapping: MutableMap<String, String>,
483 responseArrayNode: MutableMap<String, JsonNode>,
484 metadata: MutableMap<String, String>?
486 val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
487 if (outputKeyMappingHasOnlyOneElement) {
488 val outputKeyMap = outputKeyMapping.entries.first()
489 val returnValue = parseObjectResponseNodeWithOneOutputKeyMapping(
490 responseArrayNode, outputKeyMap.key, outputKeyMap.value,
491 entrySchemaType, metadata
493 resourceAssignment.keyIdentifiers.add(KeyIdentifier(outputKeyMap.key, returnValue))
496 throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
500 private fun parseSingleElementNodeWithOneOutputKeyMapping(
501 resourceAssignment: ResourceAssignment,
502 responseSingleJsonNode: JsonNode,
503 outputKeyMappingKey: String,
504 outputKeyMappingValue: String,
506 metadata: MutableMap<String, String>?
508 val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
510 val responseKeyValue = if (responseSingleJsonNode.has(outputKeyMappingValue)) {
511 responseSingleJsonNode.get(outputKeyMappingValue)
513 NullNode.getInstance()
516 logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseKeyValue, type)
517 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseKeyValue, type, arrayChildNode)
518 resourceAssignment.keyIdentifiers.find { it.name == outputKeyMappingKey && it.value.isArray }
521 (it.value as ArrayNode).add(responseKeyValue)
523 resourceAssignment.keyIdentifiers.add(
524 KeyIdentifier(outputKeyMappingKey, responseKeyValue))
526 return arrayChildNode
529 private fun parseSingleElementNodeWithAllOutputKeyMapping(
530 resourceAssignment: ResourceAssignment,
531 responseSingleJsonNode: JsonNode,
532 outputKeyMapping: MutableMap<String, String>,
534 metadata: MutableMap<String, String>?
536 val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
537 outputKeyMapping.map {
538 val responseKeyValue = if (responseSingleJsonNode.has(it.value)) {
539 responseSingleJsonNode.get(it.value)
541 NullNode.getInstance()
544 logKeyValueResolvedResource(metadata, it.key, responseKeyValue, type)
545 JacksonUtils.populateJsonNodeValues(it.key, responseKeyValue, type, arrayChildNode)
546 resourceAssignment.keyIdentifiers.add(KeyIdentifier(it.key, responseKeyValue))
548 return arrayChildNode
551 private fun parseObjectResponseNodeWithOneOutputKeyMapping(
552 responseArrayNode: MutableMap<String, JsonNode>,
553 outputKeyMappingKey: String,
554 outputKeyMappingValue: String,
556 metadata: MutableMap<String, String>?
558 val objectNode = JacksonUtils.objectMapper.createObjectNode()
559 val responseSingleJsonNode = responseArrayNode.filterKeys { key ->
560 key == outputKeyMappingValue
561 }.entries.firstOrNull()
563 if (responseSingleJsonNode == null) {
564 logKeyValueResolvedResource(metadata, outputKeyMappingKey, NullNode.getInstance(), type)
565 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, NullNode.getInstance(), type, objectNode)
567 logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseSingleJsonNode.value, type)
568 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseSingleJsonNode.value, type, objectNode)
573 private fun parseResponseNodeForComplexType(
574 responseNode: JsonNode,
575 resourceAssignment: ResourceAssignment,
576 raRuntimeService: ResourceAssignmentRuntimeService,
577 outputKeyMapping: MutableMap<String, String>
579 val entrySchemaType = resourceAssignment.property!!.type
580 val dictionaryName = resourceAssignment.dictionaryName!!
581 val metadata = resourceAssignment.property!!.metadata
582 val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
584 if (outputKeyMapping.isNotEmpty()) {
586 checkOutputKeyMappingAllElementsInDataTypeProperties(
591 parseSingleElementNodeWithAllOutputKeyMapping(
599 outputKeyMappingHasOnlyOneElement -> {
600 val outputKeyMap = outputKeyMapping.entries.first()
601 parseSingleElementNodeWithOneOutputKeyMapping(
602 resourceAssignment, responseNode, outputKeyMap.key,
603 outputKeyMap.value, entrySchemaType, metadata
607 throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
611 val childNode = JacksonUtils.objectMapper.createObjectNode()
612 JacksonUtils.populateJsonNodeValues(dictionaryName, responseNode, entrySchemaType, childNode)
617 private fun checkOutputKeyMappingAllElementsInDataTypeProperties(
618 dataTypeName: String,
619 outputKeyMapping: MutableMap<String, String>,
620 raRuntimeService: ResourceAssignmentRuntimeService
622 val dataTypeProps = raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties
623 val result = outputKeyMapping.filterKeys { !dataTypeProps!!.containsKey(it) }.keys.firstOrNull()
624 return result == null
627 private fun logKeyValueResolvedResource(
628 metadata: MutableMap<String, String>?,
633 val valueToPrint = getValueToLog(metadata, value)
636 "For List Type Resource: key ($key), value ($valueToPrint), " +
641 private fun checkIfOutputKeyMappingProvideOneElement(outputKeyMapping: MutableMap<String, String>): Boolean {
642 return (outputKeyMapping.size == 1)
645 fun getValueToLog(metadata: MutableMap<String, String>?, value: Any): Any =
646 if (hasLogProtect(metadata)) LOG_REDACTED else value