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