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