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