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