Revert "Renaming Files having BluePrint to have Blueprint"
[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.SerializationFeature
23 import com.fasterxml.jackson.databind.node.ArrayNode
24 import com.fasterxml.jackson.databind.node.NullNode
25 import com.fasterxml.jackson.databind.node.ObjectNode
26 import com.fasterxml.jackson.databind.node.TextNode
27 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceAssignmentRuntimeService
28 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants
29 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintConstants
30 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintProcessorException
31 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintTypes
32 import org.onap.ccsdk.cds.controllerblueprints.core.asJsonType
33 import org.onap.ccsdk.cds.controllerblueprints.core.checkFileExists
34 import org.onap.ccsdk.cds.controllerblueprints.core.checkNotEmpty
35 import org.onap.ccsdk.cds.controllerblueprints.core.common.ApplicationConstants.LOG_REDACTED
36 import org.onap.ccsdk.cds.controllerblueprints.core.isComplexType
37 import org.onap.ccsdk.cds.controllerblueprints.core.isNotEmpty
38 import org.onap.ccsdk.cds.controllerblueprints.core.isNullOrMissing
39 import org.onap.ccsdk.cds.controllerblueprints.core.normalizedFile
40 import org.onap.ccsdk.cds.controllerblueprints.core.nullToEmpty
41 import org.onap.ccsdk.cds.controllerblueprints.core.rootFieldsToMap
42 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintRuntimeService
43 import org.onap.ccsdk.cds.controllerblueprints.core.service.BluePrintVelocityTemplateService
44 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonReactorUtils
45 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
46 import org.onap.ccsdk.cds.controllerblueprints.core.utils.PropertyDefinitionUtils.Companion.hasLogProtect
47 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.DictionaryMetadataEntry
48 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.KeyIdentifier
49 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResolutionSummary
50 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
51 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDefinition
52 import org.slf4j.LoggerFactory
53 import java.util.Date
54
55 class ResourceAssignmentUtils {
56     companion object {
57
58         private val logger = LoggerFactory.getLogger(ResourceAssignmentUtils::class.toString())
59
60         suspend fun resourceDefinitions(blueprintBasePath: String): MutableMap<String, ResourceDefinition> {
61             val dictionaryFile = normalizedFile(
62                 blueprintBasePath, BluePrintConstants.TOSCA_DEFINITIONS_DIR,
63                 ResourceResolutionConstants.FILE_NAME_RESOURCE_DEFINITION_TYPES
64             )
65             checkFileExists(dictionaryFile) { "resource definition file(${dictionaryFile.absolutePath}) is missing" }
66             return JacksonReactorUtils.getMapFromFile(dictionaryFile, ResourceDefinition::class.java)
67         }
68
69         @Throws(BluePrintProcessorException::class)
70         fun setResourceDataValue(
71             resourceAssignment: ResourceAssignment,
72             raRuntimeService: ResourceAssignmentRuntimeService,
73             value: Any?
74         ) {
75             // TODO("See if Validation is needed in future with respect to conversion and Types")
76             return setResourceDataValue(resourceAssignment, raRuntimeService, value.asJsonType())
77         }
78
79         @Throws(BluePrintProcessorException::class)
80         fun setResourceDataValue(
81             resourceAssignment: ResourceAssignment,
82             raRuntimeService: ResourceAssignmentRuntimeService,
83             value: JsonNode
84         ) {
85             val resourceProp = checkNotNull(resourceAssignment.property) {
86                 "Failed in setting resource value for resource mapping $resourceAssignment"
87             }
88             checkNotEmpty(resourceAssignment.name) {
89                 "Failed in setting resource value for resource mapping $resourceAssignment"
90             }
91
92             if (resourceAssignment.dictionaryName.isNullOrEmpty()) {
93                 resourceAssignment.dictionaryName = resourceAssignment.name
94                 logger.warn(
95                     "Missing dictionary key, setting with template key (${resourceAssignment.name}) " +
96                         "as dictionary key (${resourceAssignment.dictionaryName})"
97                 )
98             }
99
100             try {
101                 if (resourceProp.type.isNotEmpty()) {
102                     val metadata = resourceAssignment.property!!.metadata
103                     val valueToPrint = getValueToLog(metadata, value)
104                     logger.info(
105                         "Setting Resource Value ($valueToPrint) for Resource Name " +
106                             "(${resourceAssignment.name}), definition(${resourceAssignment.dictionaryName}) " +
107                             "of type (${resourceProp.type})"
108                     )
109                     setResourceValue(resourceAssignment, raRuntimeService, value)
110                     resourceAssignment.updatedDate = Date()
111                     resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
112                     resourceAssignment.status = BluePrintConstants.STATUS_SUCCESS
113                 }
114             } catch (e: Exception) {
115                 throw BluePrintProcessorException(
116                     "Failed in setting value for template key " +
117                         "(${resourceAssignment.name}) and dictionary key (${resourceAssignment.dictionaryName}) of " +
118                         "type (${resourceProp.type}) with error message (${e.message})",
119                     e
120                 )
121             }
122         }
123
124         private fun setResourceValue(
125             resourceAssignment: ResourceAssignment,
126             raRuntimeService: ResourceAssignmentRuntimeService,
127             value: JsonNode
128         ) {
129             // TODO("See if Validation is needed wrt to type before storing")
130             raRuntimeService.putResolutionStore(resourceAssignment.name, value)
131             raRuntimeService.putDictionaryStore(resourceAssignment.dictionaryName!!, value)
132             resourceAssignment.property!!.value = value
133
134             val metadata = resourceAssignment.property?.metadata
135             metadata?.get(ResourceResolutionConstants.METADATA_TRANSFORM_TEMPLATE)
136                 ?.let { if (it.contains("$")) it else null }
137                 ?.let { template ->
138                     val resolutionStore = raRuntimeService.getResolutionStore()
139                         .mapValues { e -> e.value.asText() } as MutableMap<String, Any>
140                     val newValue: JsonNode
141                     try {
142                         newValue = BluePrintVelocityTemplateService
143                             .generateContent(template, null, true, resolutionStore)
144                             .also {
145                                 if (hasLogProtect(metadata))
146                                     logger.info("Transformed value: $resourceAssignment.name")
147                                 else
148                                     logger.info("Transformed value: $value -> $it")
149                             }
150                             .let { v -> v.asJsonType() }
151                     } catch (e: Exception) {
152                         throw BluePrintProcessorException(
153                             "transform-template failed: $template", e
154                         )
155                     }
156                     with(resourceAssignment) {
157                         raRuntimeService.putResolutionStore(this.name, newValue)
158                         raRuntimeService.putDictionaryStore(this.dictionaryName!!, newValue)
159                         this.property!!.value = newValue
160                     }
161                 }
162         }
163
164         fun setFailedResourceDataValue(resourceAssignment: ResourceAssignment, message: String?) {
165             if (isNotEmpty(resourceAssignment.name)) {
166                 resourceAssignment.updatedDate = Date()
167                 resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
168                 resourceAssignment.status = BluePrintConstants.STATUS_FAILURE
169                 resourceAssignment.message = message
170             }
171         }
172
173         @Throws(BluePrintProcessorException::class)
174         fun assertTemplateKeyValueNotNull(resourceAssignment: ResourceAssignment) {
175             val resourceProp = checkNotNull(resourceAssignment.property) {
176                 "Failed to populate mandatory resource resource mapping $resourceAssignment"
177             }
178             if (resourceProp.required != null && resourceProp.required!! && resourceProp.value.isNullOrMissing()) {
179                 logger.error("failed to populate mandatory resource mapping ($resourceAssignment)")
180                 throw BluePrintProcessorException("failed to populate mandatory resource mapping ($resourceAssignment)")
181             }
182         }
183
184         @Throws(BluePrintProcessorException::class)
185         fun generateResourceDataForAssignments(assignments: List<ResourceAssignment>): String {
186             val result: String
187             try {
188                 val mapper = ObjectMapper()
189                 mapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true)
190                 val root: ObjectNode = mapper.createObjectNode()
191
192                 var containsLogProtected = false
193                 assignments.forEach {
194                     if (isNotEmpty(it.name) && it.property != null) {
195                         val rName = it.name
196                         val metadata = it.property!!.metadata
197                         val type = nullToEmpty(it.property?.type).toLowerCase()
198                         val value = useDefaultValueIfNull(it, rName)
199                         val valueToPrint = getValueToLog(metadata, value)
200                         containsLogProtected = hasLogProtect(metadata)
201                         logger.trace("Generating Resource name ($rName), type ($type), value ($valueToPrint)")
202                         root.set<JsonNode>(rName, value)
203                     }
204                 }
205                 result = mapper.writerWithDefaultPrettyPrinter()
206                     .writeValueAsString(mapper.treeToValue(root, Object::class.java))
207
208                 if (!containsLogProtected) {
209                     logger.info("Generated Resource Param Data ($result)")
210                 }
211             } catch (e: Exception) {
212                 throw BluePrintProcessorException("Resource Assignment is failed with $e.message", e)
213             }
214
215             return result
216         }
217
218         @Throws(BluePrintProcessorException::class)
219         fun generateResourceForAssignments(assignments: List<ResourceAssignment>): MutableMap<String, JsonNode> {
220             val data: MutableMap<String, JsonNode> = hashMapOf()
221             assignments.forEach {
222                 if (isNotEmpty(it.name) && it.property != null) {
223                     val rName = it.name
224                     val metadata = it.property!!.metadata
225                     val type = nullToEmpty(it.property?.type).toLowerCase()
226                     val value = useDefaultValueIfNull(it, rName)
227                     val valueToPrint = getValueToLog(metadata, value)
228
229                     logger.trace("Generating Resource name ($rName), type ($type), value ($valueToPrint)")
230                     data[rName] = value
231                 }
232             }
233             return data
234         }
235
236         fun generateResolutionSummaryData(
237             resourceAssignments: List<ResourceAssignment>,
238             resourceDefinitions: Map<String, ResourceDefinition>
239         ): String {
240             val emptyTextNode = TextNode.valueOf("")
241             val resolutionSummaryList = resourceAssignments.map {
242                 val definition = resourceDefinitions[it.name]
243                 val description = definition?.property?.description ?: ""
244                 val value = it.property?.value
245                     ?.let { v -> if (v.isNullOrMissing()) emptyTextNode else v }
246                     ?: emptyTextNode
247
248                 var payload: JsonNode = definition?.sources?.get(it.dictionarySource)
249                     ?.properties?.get("resolved-payload")
250                     ?.let { p -> if (p.isNullOrMissing()) emptyTextNode else p }
251                     ?: emptyTextNode
252
253                 val metadata = definition?.property?.metadata
254                     ?.map { e -> DictionaryMetadataEntry(e.key, e.value) }
255                     ?.toMutableList() ?: mutableListOf()
256
257                 val keyIdentifiers: MutableList<KeyIdentifier> = it.keyIdentifiers.map { k ->
258                     if (k.value.isNullOrMissing()) KeyIdentifier(k.name, emptyTextNode) else k
259                 }.toMutableList()
260
261                 ResolutionSummary(
262                     it.name,
263                     value,
264                     it.property?.required ?: false,
265                     it.property?.type ?: "",
266                     keyIdentifiers,
267                     description,
268                     metadata,
269                     it.dictionaryName ?: "",
270                     it.dictionarySource ?: "",
271                     payload,
272                     it.status ?: "",
273                     it.message ?: ""
274                 )
275             }
276             // Wrapper needed for integration with SDNC
277             val data = mapOf("resolution-summary" to resolutionSummaryList)
278             return JacksonUtils.getJson(data, includeNull = true)
279         }
280
281         private fun useDefaultValueIfNull(
282             resourceAssignment: ResourceAssignment,
283             resourceAssignmentName: String
284         ): JsonNode {
285             if (resourceAssignment.property?.value == null) {
286                 val defaultValue = "\${$resourceAssignmentName}"
287                 return TextNode(defaultValue)
288             } else {
289                 return resourceAssignment.property!!.value!!
290             }
291         }
292
293         fun transformToRARuntimeService(
294             blueprintRuntimeService: BluePrintRuntimeService<*>,
295             templateArtifactName: String
296         ): ResourceAssignmentRuntimeService {
297
298             val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(
299                 blueprintRuntimeService.id(),
300                 blueprintRuntimeService.bluePrintContext()
301             )
302             resourceAssignmentRuntimeService.createUniqueId(templateArtifactName)
303             resourceAssignmentRuntimeService.setExecutionContext(blueprintRuntimeService.getExecutionContext() as MutableMap<String, JsonNode>)
304
305             return resourceAssignmentRuntimeService
306         }
307
308         @Throws(BluePrintProcessorException::class)
309         fun getPropertyType(
310             raRuntimeService: ResourceAssignmentRuntimeService,
311             dataTypeName: String,
312             propertyName: String
313         ): String {
314             lateinit var type: String
315             try {
316                 val dataTypeProps =
317                     checkNotNull(raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties)
318
319                 val propertyDefinition = checkNotNull(dataTypeProps[propertyName])
320                 type = checkNotEmpty(propertyDefinition.type) { "Couldn't get data type ($dataTypeName)" }
321                 logger.trace("Data type({})'s property ({}) is ({})", dataTypeName, propertyName, type)
322             } catch (e: Exception) {
323                 logger.error("couldn't get data type($dataTypeName)'s property ($propertyName), error message $e")
324                 throw BluePrintProcessorException("${e.message}", e)
325             }
326             return type
327         }
328
329         @Throws(BluePrintProcessorException::class)
330         fun parseResponseNode(
331             responseNode: JsonNode,
332             resourceAssignment: ResourceAssignment,
333             raRuntimeService: ResourceAssignmentRuntimeService,
334             outputKeyMapping: MutableMap<String, String>
335         ): JsonNode {
336             val metadata = resourceAssignment.property!!.metadata
337             try {
338                 if ((resourceAssignment.property?.type).isNullOrEmpty()) {
339                     throw BluePrintProcessorException("Couldn't get data dictionary type for dictionary name (${resourceAssignment.name})")
340                 }
341                 val type = resourceAssignment.property!!.type
342                 val valueToPrint = getValueToLog(metadata, responseNode)
343
344                 logger.info("For template key (${resourceAssignment.name}) trying to get value from responseNode ($valueToPrint)")
345                 return when (type) {
346                     in BluePrintTypes.validPrimitiveTypes() -> {
347                         // Primitive Types
348                         parseResponseNodeForPrimitiveTypes(responseNode, resourceAssignment, outputKeyMapping)
349                     }
350                     in BluePrintTypes.validCollectionTypes() -> {
351                         // Array Types
352                         parseResponseNodeForCollection(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
353                     }
354                     else -> {
355                         // Complex Types
356                         parseResponseNodeForComplexType(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
357                     }
358                 }
359             } catch (e: Exception) {
360                 logger.error("Fail to parse response data, error message $e")
361                 throw BluePrintProcessorException("${e.message}", e)
362             }
363         }
364
365         private fun parseResponseNodeForPrimitiveTypes(
366             responseNode: JsonNode,
367             resourceAssignment: ResourceAssignment,
368             outputKeyMapping: MutableMap<String, String>
369         ): JsonNode {
370             // Return responseNode if is not a Complex Type
371             if (!responseNode.isComplexType()) {
372                 return responseNode
373             }
374
375             val outputKey = outputKeyMapping.keys.firstOrNull()
376             var returnNode = if (responseNode is ArrayNode) {
377                 val arrayNode = responseNode.toList()
378                 if (outputKey.isNullOrEmpty()) {
379                     arrayNode.first()
380                 } else {
381                     arrayNode.firstOrNull { element ->
382                         element.isComplexType() && element.has(outputKeyMapping[outputKey])
383                     }
384                 }
385             } else {
386                 responseNode
387             }
388
389             if (returnNode.isNullOrMissing() || returnNode!!.isComplexType() && !returnNode.has(outputKeyMapping[outputKey])) {
390                 throw BluePrintProcessorException("Fail to find output key mapping ($outputKey) in the responseNode.")
391             }
392
393             val returnValue = if (returnNode.isComplexType()) {
394                 returnNode[outputKeyMapping[outputKey]]
395             } else {
396                 returnNode
397             }
398
399             outputKey?.let { KeyIdentifier(it, returnValue) }
400                 ?.let { resourceAssignment.keyIdentifiers.add(it) }
401             return returnValue
402         }
403
404         private fun parseResponseNodeForCollection(
405             responseNode: JsonNode,
406             resourceAssignment: ResourceAssignment,
407             raRuntimeService: ResourceAssignmentRuntimeService,
408             outputKeyMapping: MutableMap<String, String>
409         ): JsonNode {
410             val dName = resourceAssignment.dictionaryName
411             val metadata = resourceAssignment.property!!.metadata
412             var resultNode: JsonNode
413             if ((resourceAssignment.property?.entrySchema?.type).isNullOrEmpty()) {
414                 throw BluePrintProcessorException(
415                     "Couldn't get data type for dictionary type " +
416                         "(${resourceAssignment.property!!.type}) and dictionary name ($dName)"
417                 )
418             }
419             val entrySchemaType = resourceAssignment.property!!.entrySchema!!.type
420
421             var arrayNode = JacksonUtils.objectMapper.createArrayNode()
422             if (outputKeyMapping.isNotEmpty()) {
423                 when (responseNode) {
424                     is ArrayNode -> {
425                         val responseArrayNode = responseNode.toList()
426                         for (responseSingleJsonNode in responseArrayNode) {
427                             val arrayChildNode = parseSingleElementOfArrayResponseNode(
428                                 entrySchemaType, resourceAssignment,
429                                 outputKeyMapping, raRuntimeService, responseSingleJsonNode, metadata
430                             )
431                             arrayNode.add(arrayChildNode)
432                         }
433                         resultNode = arrayNode
434                     }
435                     is ObjectNode -> {
436                         val responseArrayNode = responseNode.rootFieldsToMap()
437                         resultNode =
438                             parseObjectResponseNode(
439                                 resourceAssignment, entrySchemaType, outputKeyMapping,
440                                 responseArrayNode, metadata
441                             )
442                     }
443                     else -> {
444                         throw BluePrintProcessorException("Key-value response expected to match the responseNode.")
445                     }
446                 }
447             } else {
448                 when (responseNode) {
449                     is ArrayNode -> {
450                         responseNode.forEach { elementNode ->
451                             arrayNode.add(elementNode)
452                         }
453                         resultNode = arrayNode
454                     }
455                     is ObjectNode -> {
456                         val responseArrayNode = responseNode.rootFieldsToMap()
457                         for ((key, responseSingleJsonNode) in responseArrayNode) {
458                             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
459                             logKeyValueResolvedResource(metadata, key, responseSingleJsonNode, entrySchemaType)
460                             JacksonUtils.populateJsonNodeValues(
461                                 key,
462                                 responseSingleJsonNode,
463                                 entrySchemaType,
464                                 arrayChildNode
465                             )
466                             arrayNode.add(arrayChildNode)
467                         }
468                         resultNode = arrayNode
469                     }
470                     else -> {
471                         resultNode = responseNode
472                     }
473                 }
474             }
475
476             return resultNode
477         }
478
479         private fun parseSingleElementOfArrayResponseNode(
480             entrySchemaType: String,
481             resourceAssignment: ResourceAssignment,
482             outputKeyMapping: MutableMap<String, String>,
483             raRuntimeService: ResourceAssignmentRuntimeService,
484             responseNode: JsonNode,
485             metadata: MutableMap<String, String>?
486         ): ObjectNode {
487             val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
488             when (entrySchemaType) {
489                 in BluePrintTypes.validPrimitiveTypes() -> {
490                     if (outputKeyMappingHasOnlyOneElement) {
491                         val outputKeyMap = outputKeyMapping.entries.first()
492                         if (resourceAssignment.keyIdentifiers.none { it.name == outputKeyMap.key }) {
493                             resourceAssignment.keyIdentifiers.add(
494                                 KeyIdentifier(outputKeyMap.key, JacksonUtils.objectMapper.createArrayNode())
495                             )
496                         }
497                         return parseSingleElementNodeWithOneOutputKeyMapping(
498                             resourceAssignment,
499                             responseNode,
500                             outputKeyMap.key,
501                             outputKeyMap.value,
502                             entrySchemaType,
503                             metadata
504                         )
505                     } else {
506                         throw BluePrintProcessorException("Expect one entry in output-key-mapping")
507                     }
508                 }
509                 else -> {
510                     return when {
511                         checkOutputKeyMappingAllElementsInDataTypeProperties(
512                             entrySchemaType,
513                             outputKeyMapping,
514                             raRuntimeService
515                         ) -> {
516                             parseSingleElementNodeWithAllOutputKeyMapping(
517                                 resourceAssignment,
518                                 responseNode,
519                                 outputKeyMapping,
520                                 entrySchemaType,
521                                 metadata
522                             )
523                         }
524                         outputKeyMappingHasOnlyOneElement -> {
525                             val outputKeyMap = outputKeyMapping.entries.first()
526                             parseSingleElementNodeWithOneOutputKeyMapping(
527                                 resourceAssignment,
528                                 responseNode,
529                                 outputKeyMap.key,
530                                 outputKeyMap.value,
531                                 entrySchemaType,
532                                 metadata
533                             )
534                         }
535                         else -> {
536                             throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
537                         }
538                     }
539                 }
540             }
541         }
542
543         private fun parseObjectResponseNode(
544             resourceAssignment: ResourceAssignment,
545             entrySchemaType: String,
546             outputKeyMapping: MutableMap<String, String>,
547             responseArrayNode: MutableMap<String, JsonNode>,
548             metadata: MutableMap<String, String>?
549         ): ObjectNode {
550             val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
551             if (outputKeyMappingHasOnlyOneElement) {
552                 val outputKeyMap = outputKeyMapping.entries.first()
553                 val returnValue = parseObjectResponseNodeWithOneOutputKeyMapping(
554                     responseArrayNode, outputKeyMap.key, outputKeyMap.value,
555                     entrySchemaType, metadata
556                 )
557                 resourceAssignment.keyIdentifiers.add(KeyIdentifier(outputKeyMap.key, returnValue))
558                 return returnValue
559             } else {
560                 throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
561             }
562         }
563
564         private fun parseSingleElementNodeWithOneOutputKeyMapping(
565             resourceAssignment: ResourceAssignment,
566             responseSingleJsonNode: JsonNode,
567             outputKeyMappingKey: String,
568             outputKeyMappingValue: String,
569             type: String,
570             metadata: MutableMap<String, String>?
571         ): ObjectNode {
572             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
573
574             val responseKeyValue = if (responseSingleJsonNode.has(outputKeyMappingValue)) {
575                 responseSingleJsonNode.get(outputKeyMappingValue)
576             } else {
577                 NullNode.getInstance()
578             }
579
580             logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseKeyValue, type)
581             JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseKeyValue, type, arrayChildNode)
582             resourceAssignment.keyIdentifiers.find { it.name == outputKeyMappingKey && it.value.isArray }
583                 .let {
584                     if (it != null)
585                         (it.value as ArrayNode).add(responseKeyValue)
586                     else
587                         resourceAssignment.keyIdentifiers.add(
588                             KeyIdentifier(outputKeyMappingKey, responseKeyValue)
589                         )
590                 }
591             return arrayChildNode
592         }
593
594         private fun parseSingleElementNodeWithAllOutputKeyMapping(
595             resourceAssignment: ResourceAssignment,
596             responseSingleJsonNode: JsonNode,
597             outputKeyMapping: MutableMap<String, String>,
598             type: String,
599             metadata: MutableMap<String, String>?
600         ): ObjectNode {
601             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
602             outputKeyMapping.map {
603                 val responseKeyValue = if (responseSingleJsonNode.has(it.value)) {
604                     responseSingleJsonNode.get(it.value)
605                 } else {
606                     NullNode.getInstance()
607                 }
608
609                 logKeyValueResolvedResource(metadata, it.key, responseKeyValue, type)
610                 JacksonUtils.populateJsonNodeValues(it.key, responseKeyValue, type, arrayChildNode)
611                 resourceAssignment.keyIdentifiers.add(KeyIdentifier(it.key, responseKeyValue))
612             }
613             return arrayChildNode
614         }
615
616         private fun parseObjectResponseNodeWithOneOutputKeyMapping(
617             responseArrayNode: MutableMap<String, JsonNode>,
618             outputKeyMappingKey: String,
619             outputKeyMappingValue: String,
620             type: String,
621             metadata: MutableMap<String, String>?
622         ): ObjectNode {
623             val objectNode = JacksonUtils.objectMapper.createObjectNode()
624             val responseSingleJsonNode = responseArrayNode.filterKeys { key ->
625                 key == outputKeyMappingValue
626             }.entries.firstOrNull()
627
628             if (responseSingleJsonNode == null) {
629                 logKeyValueResolvedResource(metadata, outputKeyMappingKey, NullNode.getInstance(), type)
630                 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, NullNode.getInstance(), type, objectNode)
631             } else {
632                 logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseSingleJsonNode.value, type)
633                 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseSingleJsonNode.value, type, objectNode)
634             }
635             return objectNode
636         }
637
638         private fun parseResponseNodeForComplexType(
639             responseNode: JsonNode,
640             resourceAssignment: ResourceAssignment,
641             raRuntimeService: ResourceAssignmentRuntimeService,
642             outputKeyMapping: MutableMap<String, String>
643         ): JsonNode {
644             val entrySchemaType = resourceAssignment.property!!.type
645             val dictionaryName = resourceAssignment.dictionaryName!!
646             val metadata = resourceAssignment.property!!.metadata
647             val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
648
649             if (outputKeyMapping.isNotEmpty()) {
650                 return when {
651                     checkOutputKeyMappingAllElementsInDataTypeProperties(
652                         entrySchemaType,
653                         outputKeyMapping,
654                         raRuntimeService
655                     ) -> {
656                         parseSingleElementNodeWithAllOutputKeyMapping(
657                             resourceAssignment,
658                             responseNode,
659                             outputKeyMapping,
660                             entrySchemaType,
661                             metadata
662                         )
663                     }
664                     outputKeyMappingHasOnlyOneElement -> {
665                         val outputKeyMap = outputKeyMapping.entries.first()
666                         parseSingleElementNodeWithOneOutputKeyMapping(
667                             resourceAssignment, responseNode, outputKeyMap.key,
668                             outputKeyMap.value, entrySchemaType, metadata
669                         )
670                     }
671                     else -> {
672                         throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
673                     }
674                 }
675             } else {
676                 val childNode = JacksonUtils.objectMapper.createObjectNode()
677                 JacksonUtils.populateJsonNodeValues(dictionaryName, responseNode, entrySchemaType, childNode)
678                 return childNode
679             }
680         }
681
682         private fun checkOutputKeyMappingAllElementsInDataTypeProperties(
683             dataTypeName: String,
684             outputKeyMapping: MutableMap<String, String>,
685             raRuntimeService: ResourceAssignmentRuntimeService
686         ): Boolean {
687             val dataTypeProps = raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties
688             var result: String? = null
689             if (dataTypeProps != null)
690                 result = outputKeyMapping.filterKeys { !dataTypeProps.containsKey(it) }.keys.firstOrNull()
691             return result == null
692         }
693
694         private fun logKeyValueResolvedResource(
695             metadata: MutableMap<String, String>?,
696             key: String,
697             value: JsonNode,
698             type: String
699         ) {
700             val valueToPrint = getValueToLog(metadata, value)
701
702             logger.info(
703                 "For List Type Resource: key ($key), value ($valueToPrint), " +
704                     "type  ({$type})"
705             )
706         }
707
708         private fun checkIfOutputKeyMappingProvideOneElement(outputKeyMapping: MutableMap<String, String>): Boolean {
709             return (outputKeyMapping.size == 1)
710         }
711
712         fun getValueToLog(metadata: MutableMap<String, String>?, value: Any): Any =
713             if (hasLogProtect(metadata)) LOG_REDACTED else value
714     }
715 }