Fix - ResolutionSummary should contain resolved request-payload
[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.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
50 import java.util.Date
51
52 class ResourceAssignmentUtils {
53     companion object {
54
55         private val logger = LoggerFactory.getLogger(ResourceAssignmentUtils::class.toString())
56
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
61             )
62             checkFileExists(dictionaryFile) { "resource definition file(${dictionaryFile.absolutePath}) is missing" }
63             return JacksonReactorUtils.getMapFromFile(dictionaryFile, ResourceDefinition::class.java)
64         }
65
66         @Throws(BluePrintProcessorException::class)
67         fun setResourceDataValue(
68             resourceAssignment: ResourceAssignment,
69             raRuntimeService: ResourceAssignmentRuntimeService,
70             value: Any?
71         ) {
72             // TODO("See if Validation is needed in future with respect to conversion and Types")
73             return setResourceDataValue(resourceAssignment, raRuntimeService, value.asJsonType())
74         }
75
76         @Throws(BluePrintProcessorException::class)
77         fun setResourceDataValue(
78             resourceAssignment: ResourceAssignment,
79             raRuntimeService: ResourceAssignmentRuntimeService,
80             value: JsonNode
81         ) {
82             val resourceProp = checkNotNull(resourceAssignment.property) {
83                 "Failed in setting resource value for resource mapping $resourceAssignment"
84             }
85             checkNotEmpty(resourceAssignment.name) {
86                 "Failed in setting resource value for resource mapping $resourceAssignment"
87             }
88
89             if (resourceAssignment.dictionaryName.isNullOrEmpty()) {
90                 resourceAssignment.dictionaryName = resourceAssignment.name
91                 logger.warn(
92                     "Missing dictionary key, setting with template key (${resourceAssignment.name}) " +
93                             "as dictionary key (${resourceAssignment.dictionaryName})"
94                 )
95             }
96
97             try {
98                 if (resourceProp.type.isNotEmpty()) {
99                     val metadata = resourceAssignment.property!!.metadata
100                     val valueToPrint = getValueToLog(metadata, value)
101                     logger.info(
102                         "Setting Resource Value ($valueToPrint) for Resource Name " +
103                                 "(${resourceAssignment.name}), definition(${resourceAssignment.dictionaryName}) " +
104                                 "of type (${resourceProp.type})"
105                     )
106                     setResourceValue(resourceAssignment, raRuntimeService, value)
107                     resourceAssignment.updatedDate = Date()
108                     resourceAssignment.updatedBy = BluePrintConstants.USER_SYSTEM
109                     resourceAssignment.status = BluePrintConstants.STATUS_SUCCESS
110                 }
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
116                 )
117             }
118         }
119
120         private fun setResourceValue(
121             resourceAssignment: ResourceAssignment,
122             raRuntimeService: ResourceAssignmentRuntimeService,
123             value: JsonNode
124         ) {
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
129         }
130
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
137             }
138         }
139
140         @Throws(BluePrintProcessorException::class)
141         fun assertTemplateKeyValueNotNull(resourceAssignment: ResourceAssignment) {
142             val resourceProp = checkNotNull(resourceAssignment.property) {
143                 "Failed to populate mandatory resource resource mapping $resourceAssignment"
144             }
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)")
148             }
149         }
150
151         @Throws(BluePrintProcessorException::class)
152         fun generateResourceDataForAssignments(assignments: List<ResourceAssignment>): String {
153             val result: String
154             try {
155                 val mapper = ObjectMapper()
156                 val root: ObjectNode = mapper.createObjectNode()
157
158                 var containsLogProtected = false
159                 assignments.forEach {
160                     if (isNotEmpty(it.name) && it.property != null) {
161                         val rName = it.name
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)
169                     }
170                 }
171                 result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(root)
172
173                 if (!containsLogProtected) {
174                     logger.info("Generated Resource Param Data ($result)")
175                 }
176             } catch (e: Exception) {
177                 throw BluePrintProcessorException("Resource Assignment is failed with $e.message", e)
178             }
179
180             return result
181         }
182
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) {
188                     val rName = it.name
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)
193
194                     logger.trace("Generating Resource name ($rName), type ($type), value ($valueToPrint)")
195                     data[rName] = value
196                 }
197             }
198             return data
199         }
200
201         fun generateResolutionSummaryData(
202             resourceAssignments: List<ResourceAssignment>,
203             resourceDefinitions: Map<String, ResourceDefinition>
204         ): String {
205             val resolutionSummaryList = resourceAssignments.map {
206                 val payload = resourceDefinitions[it.name]
207                         ?.sources?.get(it.dictionarySource)?.properties?.get("resolved-payload")
208                 ResolutionSummary(
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
212                 )
213             }
214             return JacksonUtils.getJson(resolutionSummaryList, includeNull = true)
215         }
216
217         private fun useDefaultValueIfNull(
218             resourceAssignment: ResourceAssignment,
219             resourceAssignmentName: String
220         ): JsonNode {
221             if (resourceAssignment.property?.value == null) {
222                 val defaultValue = "\${$resourceAssignmentName}"
223                 return TextNode(defaultValue)
224             } else {
225                 return resourceAssignment.property!!.value!!
226             }
227         }
228
229         fun transformToRARuntimeService(
230             blueprintRuntimeService: BluePrintRuntimeService<*>,
231             templateArtifactName: String
232         ): ResourceAssignmentRuntimeService {
233
234             val resourceAssignmentRuntimeService = ResourceAssignmentRuntimeService(
235                 blueprintRuntimeService.id(),
236                 blueprintRuntimeService.bluePrintContext()
237             )
238             resourceAssignmentRuntimeService.createUniqueId(templateArtifactName)
239             resourceAssignmentRuntimeService.setExecutionContext(blueprintRuntimeService.getExecutionContext() as MutableMap<String, JsonNode>)
240
241             return resourceAssignmentRuntimeService
242         }
243
244         @Throws(BluePrintProcessorException::class)
245         fun getPropertyType(
246             raRuntimeService: ResourceAssignmentRuntimeService,
247             dataTypeName: String,
248             propertyName: String
249         ): String {
250             lateinit var type: String
251             try {
252                 val dataTypeProps =
253                     checkNotNull(raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties)
254
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)
261             }
262             return type
263         }
264
265         @Throws(BluePrintProcessorException::class)
266         fun parseResponseNode(
267             responseNode: JsonNode,
268             resourceAssignment: ResourceAssignment,
269             raRuntimeService: ResourceAssignmentRuntimeService,
270             outputKeyMapping: MutableMap<String, String>
271         ): JsonNode {
272             val metadata = resourceAssignment.property!!.metadata
273             try {
274                 if ((resourceAssignment.property?.type).isNullOrEmpty()) {
275                     throw BluePrintProcessorException("Couldn't get data dictionary type for dictionary name (${resourceAssignment.name})")
276                 }
277                 val type = resourceAssignment.property!!.type
278                 val valueToPrint = getValueToLog(metadata, responseNode)
279
280                 logger.info("For template key (${resourceAssignment.name}) trying to get value from responseNode ($valueToPrint)")
281                 return when (type) {
282                     in BluePrintTypes.validPrimitiveTypes() -> {
283                         // Primitive Types
284                         parseResponseNodeForPrimitiveTypes(responseNode, resourceAssignment, outputKeyMapping)
285                     }
286                     in BluePrintTypes.validCollectionTypes() -> {
287                         // Array Types
288                         parseResponseNodeForCollection(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
289                     }
290                     else -> {
291                         // Complex Types
292                         parseResponseNodeForComplexType(responseNode, resourceAssignment, raRuntimeService, outputKeyMapping)
293                     }
294                 }
295             } catch (e: Exception) {
296                 logger.error("Fail to parse response data, error message $e")
297                 throw BluePrintProcessorException("${e.message}", e)
298             }
299         }
300
301         private fun parseResponseNodeForPrimitiveTypes(
302             responseNode: JsonNode,
303             resourceAssignment: ResourceAssignment,
304             outputKeyMapping: MutableMap<String, String>
305         ): JsonNode {
306             // Return responseNode if is not a Complex Type
307             if (!responseNode.isComplexType()) {
308                 return responseNode
309             }
310
311             val outputKey = outputKeyMapping.keys.firstOrNull()
312             var returnNode = if (responseNode is ArrayNode) {
313                 val arrayNode = responseNode.toList()
314                 if (outputKey.isNullOrEmpty()) {
315                     arrayNode.first()
316                 } else {
317                     arrayNode.firstOrNull { element ->
318                         element.isComplexType() && element.has(outputKeyMapping[outputKey])
319                     }
320                 }
321             } else {
322                 responseNode
323             }
324
325             if (returnNode.isNullOrMissing() || returnNode!!.isComplexType() && !returnNode.has(outputKeyMapping[outputKey])) {
326                 throw BluePrintProcessorException("Fail to find output key mapping ($outputKey) in the responseNode.")
327             }
328
329             val returnValue = if (returnNode.isComplexType()) {
330                 returnNode[outputKeyMapping[outputKey]]
331             } else {
332                 returnNode
333             }
334
335             outputKey?.let { KeyIdentifier(it, returnValue) }
336                 ?.let { resourceAssignment.keyIdentifiers.add(it) }
337             return returnValue
338         }
339
340         private fun parseResponseNodeForCollection(
341             responseNode: JsonNode,
342             resourceAssignment: ResourceAssignment,
343             raRuntimeService: ResourceAssignmentRuntimeService,
344             outputKeyMapping: MutableMap<String, String>
345         ): JsonNode {
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)"
353                 )
354             }
355             val entrySchemaType = resourceAssignment.property!!.entrySchema!!.type
356
357             var arrayNode = JacksonUtils.objectMapper.createArrayNode()
358             if (outputKeyMapping.isNotEmpty()) {
359                 when (responseNode) {
360                     is ArrayNode -> {
361                         val responseArrayNode = responseNode.toList()
362                         for (responseSingleJsonNode in responseArrayNode) {
363                             val arrayChildNode = parseSingleElementOfArrayResponseNode(
364                                 entrySchemaType, resourceAssignment,
365                                 outputKeyMapping, raRuntimeService, responseSingleJsonNode, metadata
366                             )
367                             arrayNode.add(arrayChildNode)
368                         }
369                         resultNode = arrayNode
370                     }
371                     is ObjectNode -> {
372                         val responseArrayNode = responseNode.rootFieldsToMap()
373                         resultNode =
374                             parseObjectResponseNode(
375                                 resourceAssignment, entrySchemaType, outputKeyMapping,
376                                 responseArrayNode, metadata
377                             )
378                     }
379                     else -> {
380                         throw BluePrintProcessorException("Key-value response expected to match the responseNode.")
381                     }
382                 }
383             } else {
384                 when (responseNode) {
385                     is ArrayNode -> {
386                         responseNode.forEach { elementNode ->
387                             arrayNode.add(elementNode)
388                         }
389                         resultNode = arrayNode
390                     }
391                     is ObjectNode -> {
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(
397                                 key,
398                                 responseSingleJsonNode,
399                                 entrySchemaType,
400                                 arrayChildNode
401                             )
402                             arrayNode.add(arrayChildNode)
403                         }
404                         resultNode = arrayNode
405                     }
406                     else -> {
407                         resultNode = responseNode
408                     }
409                 }
410             }
411
412             return resultNode
413         }
414
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>?
422         ): ObjectNode {
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())
431                             )
432                         }
433                         return parseSingleElementNodeWithOneOutputKeyMapping(
434                             resourceAssignment,
435                             responseNode,
436                             outputKeyMap.key,
437                             outputKeyMap.value,
438                             entrySchemaType,
439                             metadata
440                         )
441                     } else {
442                         throw BluePrintProcessorException("Expect one entry in output-key-mapping")
443                     }
444                 }
445                 else -> {
446                     return when {
447                         checkOutputKeyMappingAllElementsInDataTypeProperties(
448                             entrySchemaType,
449                             outputKeyMapping,
450                             raRuntimeService
451                         ) -> {
452                             parseSingleElementNodeWithAllOutputKeyMapping(
453                                 resourceAssignment,
454                                 responseNode,
455                                 outputKeyMapping,
456                                 entrySchemaType,
457                                 metadata
458                             )
459                         }
460                         outputKeyMappingHasOnlyOneElement -> {
461                             val outputKeyMap = outputKeyMapping.entries.first()
462                             parseSingleElementNodeWithOneOutputKeyMapping(
463                                 resourceAssignment,
464                                 responseNode,
465                                 outputKeyMap.key,
466                                 outputKeyMap.value,
467                                 entrySchemaType,
468                                 metadata
469                             )
470                         }
471                         else -> {
472                             throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
473                         }
474                     }
475                 }
476             }
477         }
478
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>?
485         ): ObjectNode {
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
492                 )
493                 resourceAssignment.keyIdentifiers.add(KeyIdentifier(outputKeyMap.key, returnValue))
494                 return returnValue
495             } else {
496                 throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
497             }
498         }
499
500         private fun parseSingleElementNodeWithOneOutputKeyMapping(
501             resourceAssignment: ResourceAssignment,
502             responseSingleJsonNode: JsonNode,
503             outputKeyMappingKey: String,
504             outputKeyMappingValue: String,
505             type: String,
506             metadata: MutableMap<String, String>?
507         ): ObjectNode {
508             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
509
510             val responseKeyValue = if (responseSingleJsonNode.has(outputKeyMappingValue)) {
511                 responseSingleJsonNode.get(outputKeyMappingValue)
512             } else {
513                 NullNode.getInstance()
514             }
515
516             logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseKeyValue, type)
517             JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseKeyValue, type, arrayChildNode)
518             resourceAssignment.keyIdentifiers.find { it.name == outputKeyMappingKey && it.value.isArray }
519                     .let {
520                         if (it != null)
521                             (it.value as ArrayNode).add(responseKeyValue)
522                         else
523                             resourceAssignment.keyIdentifiers.add(
524                                     KeyIdentifier(outputKeyMappingKey, responseKeyValue))
525                     }
526             return arrayChildNode
527         }
528
529         private fun parseSingleElementNodeWithAllOutputKeyMapping(
530             resourceAssignment: ResourceAssignment,
531             responseSingleJsonNode: JsonNode,
532             outputKeyMapping: MutableMap<String, String>,
533             type: String,
534             metadata: MutableMap<String, String>?
535         ): ObjectNode {
536             val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
537             outputKeyMapping.map {
538                 val responseKeyValue = if (responseSingleJsonNode.has(it.value)) {
539                     responseSingleJsonNode.get(it.value)
540                 } else {
541                     NullNode.getInstance()
542                 }
543
544                 logKeyValueResolvedResource(metadata, it.key, responseKeyValue, type)
545                 JacksonUtils.populateJsonNodeValues(it.key, responseKeyValue, type, arrayChildNode)
546                 resourceAssignment.keyIdentifiers.add(KeyIdentifier(it.key, responseKeyValue))
547             }
548             return arrayChildNode
549         }
550
551         private fun parseObjectResponseNodeWithOneOutputKeyMapping(
552             responseArrayNode: MutableMap<String, JsonNode>,
553             outputKeyMappingKey: String,
554             outputKeyMappingValue: String,
555             type: String,
556             metadata: MutableMap<String, String>?
557         ): ObjectNode {
558             val objectNode = JacksonUtils.objectMapper.createObjectNode()
559             val responseSingleJsonNode = responseArrayNode.filterKeys { key ->
560                 key == outputKeyMappingValue
561             }.entries.firstOrNull()
562
563             if (responseSingleJsonNode == null) {
564                 logKeyValueResolvedResource(metadata, outputKeyMappingKey, NullNode.getInstance(), type)
565                 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, NullNode.getInstance(), type, objectNode)
566             } else {
567                 logKeyValueResolvedResource(metadata, outputKeyMappingKey, responseSingleJsonNode.value, type)
568                 JacksonUtils.populateJsonNodeValues(outputKeyMappingKey, responseSingleJsonNode.value, type, objectNode)
569             }
570             return objectNode
571         }
572
573         private fun parseResponseNodeForComplexType(
574             responseNode: JsonNode,
575             resourceAssignment: ResourceAssignment,
576             raRuntimeService: ResourceAssignmentRuntimeService,
577             outputKeyMapping: MutableMap<String, String>
578         ): JsonNode {
579             val entrySchemaType = resourceAssignment.property!!.type
580             val dictionaryName = resourceAssignment.dictionaryName!!
581             val metadata = resourceAssignment.property!!.metadata
582             val outputKeyMappingHasOnlyOneElement = checkIfOutputKeyMappingProvideOneElement(outputKeyMapping)
583
584             if (outputKeyMapping.isNotEmpty()) {
585                 return when {
586                     checkOutputKeyMappingAllElementsInDataTypeProperties(
587                         entrySchemaType,
588                         outputKeyMapping,
589                         raRuntimeService
590                     ) -> {
591                         parseSingleElementNodeWithAllOutputKeyMapping(
592                             resourceAssignment,
593                             responseNode,
594                             outputKeyMapping,
595                             entrySchemaType,
596                             metadata
597                         )
598                     }
599                     outputKeyMappingHasOnlyOneElement -> {
600                         val outputKeyMap = outputKeyMapping.entries.first()
601                         parseSingleElementNodeWithOneOutputKeyMapping(
602                             resourceAssignment, responseNode, outputKeyMap.key,
603                             outputKeyMap.value, entrySchemaType, metadata
604                         )
605                     }
606                     else -> {
607                         throw BluePrintProcessorException("Output-key-mapping do not map the Data Type $entrySchemaType")
608                     }
609                 }
610             } else {
611                 val childNode = JacksonUtils.objectMapper.createObjectNode()
612                 JacksonUtils.populateJsonNodeValues(dictionaryName, responseNode, entrySchemaType, childNode)
613                 return childNode
614             }
615         }
616
617         private fun checkOutputKeyMappingAllElementsInDataTypeProperties(
618             dataTypeName: String,
619             outputKeyMapping: MutableMap<String, String>,
620             raRuntimeService: ResourceAssignmentRuntimeService
621         ): Boolean {
622             val dataTypeProps = raRuntimeService.bluePrintContext().dataTypeByName(dataTypeName)?.properties
623             val result = outputKeyMapping.filterKeys { !dataTypeProps!!.containsKey(it) }.keys.firstOrNull()
624             return result == null
625         }
626
627         private fun logKeyValueResolvedResource(
628             metadata: MutableMap<String, String>?,
629             key: String,
630             value: JsonNode,
631             type: String
632         ) {
633             val valueToPrint = getValueToLog(metadata, value)
634
635             logger.info(
636                 "For List Type Resource: key ($key), value ($valueToPrint), " +
637                         "type  ({$type})"
638             )
639         }
640
641         private fun checkIfOutputKeyMappingProvideOneElement(outputKeyMapping: MutableMap<String, String>): Boolean {
642             return (outputKeyMapping.size == 1)
643         }
644
645         fun getValueToLog(metadata: MutableMap<String, String>?, value: Any): Any =
646                 if (hasLogProtect(metadata)) LOG_REDACTED else value
647     }
648 }