Merge "Integration Test - Component Executor"
[ccsdk/cds.git] / ms / blueprintsprocessor / functions / resource-resolution / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / functions / resource / resolution / processor / RestResourceResolutionProcessor.kt
1 /*
2  *  Copyright © 2018 IBM.
3  *  Modifications Copyright © 2017-2019 AT&T, 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.processor
19
20 import com.fasterxml.jackson.databind.node.ArrayNode
21 import com.fasterxml.jackson.databind.node.MissingNode
22 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.ResourceResolutionConstants.PREFIX_RESOURCE_RESOLUTION_PROCESSOR
23 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.RestResourceSource
24 import org.onap.ccsdk.cds.blueprintsprocessor.functions.resource.resolution.utils.ResourceAssignmentUtils
25 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BluePrintRestLibPropertyService
26 import org.onap.ccsdk.cds.blueprintsprocessor.rest.service.BlueprintWebClientService
27 import org.onap.ccsdk.cds.controllerblueprints.core.*
28 import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
29 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceAssignment
30 import org.onap.ccsdk.cds.controllerblueprints.resource.dict.ResourceDictionaryConstants
31 import org.slf4j.LoggerFactory
32 import org.springframework.beans.factory.config.ConfigurableBeanFactory
33 import org.springframework.context.annotation.Scope
34 import org.springframework.stereotype.Service
35
36 /**
37  * RestResourceResolutionProcessor
38  *
39  * @author Kapil Singal
40  */
41 @Service("${PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-rest")
42 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
43 open class RestResourceResolutionProcessor(private val blueprintRestLibPropertyService: BluePrintRestLibPropertyService)
44     : ResourceAssignmentProcessor() {
45
46     private val logger = LoggerFactory.getLogger(RestResourceResolutionProcessor::class.java)
47
48     override fun getName(): String {
49         return "${PREFIX_RESOURCE_RESOLUTION_PROCESSOR}source-rest"
50     }
51
52     override suspend fun processNB(resourceAssignment: ResourceAssignment) {
53         try {
54             validate(resourceAssignment)
55
56             // Check if It has Input
57             val value = getFromInput(resourceAssignment)
58             if (value == null || value is MissingNode) {
59                 val dName = resourceAssignment.dictionaryName
60                 val dSource = resourceAssignment.dictionarySource
61                 val resourceDefinition = resourceDictionaries[dName]
62                         ?: throw BluePrintProcessorException("couldn't get resource dictionary definition for $dName")
63
64                 val resourceSource = resourceDefinition.sources[dSource]
65                         ?: throw BluePrintProcessorException("couldn't get resource definition $dName source($dSource)")
66
67                 val resourceSourceProperties =
68                         checkNotNull(resourceSource.properties) { "failed to get source properties for $dName " }
69
70                 val sourceProperties =
71                         JacksonUtils.getInstanceFromMap(resourceSourceProperties, RestResourceSource::class.java)
72
73                 val path = nullToEmpty(sourceProperties.path)
74                 val inputKeyMapping =
75                     checkNotNull(sourceProperties.inputKeyMapping) { "failed to get input-key-mappings for $dName under $dSource properties" }
76                 val resolvedInputKeyMapping = resolveInputKeyMappingVariables(inputKeyMapping).toMutableMap()
77
78                 // Resolving content Variables
79                 val payload = resolveFromInputKeyMapping(nullToEmpty(sourceProperties.payload), resolvedInputKeyMapping)
80                 val urlPath =
81                         resolveFromInputKeyMapping(checkNotNull(sourceProperties.urlPath), resolvedInputKeyMapping)
82                 val verb = resolveFromInputKeyMapping(nullToEmpty(sourceProperties.verb), resolvedInputKeyMapping)
83
84                 logger.info("$dSource dictionary information : ($urlPath), ($inputKeyMapping), (${sourceProperties.outputKeyMapping})")
85                 // Get the Rest Client Service
86                 val restClientService = blueprintWebClientService(resourceAssignment, sourceProperties)
87
88                 val response = restClientService.exchangeResource(verb, urlPath, payload)
89                 if (response.isBlank()) {
90                     logger.warn("Failed to get $dSource result for dictionary name ($dName) using urlPath ($urlPath)")
91                 } else {
92                     populateResource(resourceAssignment, sourceProperties, response, path)
93                 }
94             }
95             // Check the value has populated for mandatory case
96             ResourceAssignmentUtils.assertTemplateKeyValueNotNull(resourceAssignment)
97         } catch (e: Exception) {
98             ResourceAssignmentUtils.setFailedResourceDataValue(resourceAssignment, e.message)
99             throw BluePrintProcessorException("Failed in template key ($resourceAssignment) assignments with: ${e.message}",
100                     e)
101         }
102     }
103
104     fun blueprintWebClientService(resourceAssignment: ResourceAssignment,
105                                           restResourceSource: RestResourceSource): BlueprintWebClientService {
106         return if (isNotEmpty(restResourceSource.endpointSelector)) {
107             val restPropertiesJson = raRuntimeService.resolveDSLExpression(restResourceSource.endpointSelector!!)
108             blueprintRestLibPropertyService.blueprintWebClientService(restPropertiesJson)
109         } else {
110             blueprintRestLibPropertyService.blueprintWebClientService(resourceAssignment.dictionarySource!!)
111         }
112     }
113
114     @Throws(BluePrintProcessorException::class)
115     private fun populateResource(resourceAssignment: ResourceAssignment, sourceProperties: RestResourceSource,
116                                  restResponse: String, path: String) {
117         val dName = resourceAssignment.dictionaryName
118         val dSource = resourceAssignment.dictionarySource
119         val type = nullToEmpty(resourceAssignment.property?.type)
120         lateinit var entrySchemaType: String
121
122         val outputKeyMapping = checkNotNull(sourceProperties.outputKeyMapping) {
123             "failed to get output-key-mappings for $dName under $dSource properties"
124         }
125         logger.info("Response processing type($type)")
126
127         val responseNode = checkNotNull(JacksonUtils.jsonNode(restResponse).at(path)) {
128             "Failed to find path ($path) in response ($restResponse)"
129         }
130         logger.info("populating value for output mapping ($outputKeyMapping), from json ($responseNode)")
131
132
133         when (type) {
134             in BluePrintTypes.validPrimitiveTypes() -> {
135                 logger.info("For template key (${resourceAssignment.name}) setting value as ($responseNode)")
136                 ResourceAssignmentUtils.setResourceDataValue(resourceAssignment, raRuntimeService, responseNode)
137             }
138             in BluePrintTypes.validCollectionTypes() -> {
139                 // Array Types
140                 entrySchemaType = checkNotEmpty(resourceAssignment.property?.entrySchema?.type) {
141                     "Entry schema is not defined for dictionary ($dName) info"
142                 }
143                 val arrayNode = responseNode as ArrayNode
144
145                 if (entrySchemaType !in BluePrintTypes.validPrimitiveTypes()) {
146
147                     val responseArrayNode = responseNode.toList()
148                     for (responseSingleJsonNode in responseArrayNode) {
149
150                         val arrayChildNode = JacksonUtils.objectMapper.createObjectNode()
151
152                         outputKeyMapping.map {
153                             val responseKeyValue = responseSingleJsonNode.get(it.key)
154                             val propertyTypeForDataType = ResourceAssignmentUtils
155                                     .getPropertyType(raRuntimeService, entrySchemaType, it.key)
156
157                             logger.info("For List Type Resource: key (${it.key}), value ($responseKeyValue), " +
158                                     "type  ({$propertyTypeForDataType})")
159
160                             JacksonUtils.populateJsonNodeValues(it.value,
161                                     responseKeyValue, propertyTypeForDataType, arrayChildNode)
162                         }
163                         arrayNode.add(arrayChildNode)
164                     }
165                 }
166                 logger.info("For template key (${resourceAssignment.name}) setting value as ($arrayNode)")
167                 // Set the List of Complex Values
168                 ResourceAssignmentUtils.setResourceDataValue(resourceAssignment, raRuntimeService, arrayNode)
169             }
170             else -> {
171                 // Complex Types
172                 entrySchemaType = checkNotEmpty(resourceAssignment.property?.type) {
173                     "Entry schema is not defined for dictionary ($dName) info"
174                 }
175                 val objectNode = JacksonUtils.objectMapper.createObjectNode()
176                 outputKeyMapping.map {
177                     val responseKeyValue = responseNode.get(it.key)
178                     val propertyTypeForDataType = ResourceAssignmentUtils
179                             .getPropertyType(raRuntimeService, entrySchemaType, it.key)
180
181                     logger.info("For List Type Resource: key (${it.key}), value ($responseKeyValue), type  ({$propertyTypeForDataType})")
182                     JacksonUtils.populateJsonNodeValues(it.value, responseKeyValue, propertyTypeForDataType, objectNode)
183                 }
184
185                 logger.info("For template key (${resourceAssignment.name}) setting value as ($objectNode)")
186                 // Set the List of Complex Values
187                 ResourceAssignmentUtils.setResourceDataValue(resourceAssignment, raRuntimeService, objectNode)
188             }
189         }
190     }
191
192     @Throws(BluePrintProcessorException::class)
193     private fun validate(resourceAssignment: ResourceAssignment) {
194         checkNotEmpty(resourceAssignment.name) { "resource assignment template key is not defined" }
195         checkNotEmpty(resourceAssignment.dictionaryName) {
196             "resource assignment dictionary name is not defined for template key (${resourceAssignment.name})"
197         }
198         checkNotEmpty(resourceAssignment.dictionarySource) {
199             "resource assignment dictionary source is not defined for template key (${resourceAssignment.name})"
200         }
201     }
202
203     override suspend fun recoverNB(runtimeException: RuntimeException, resourceAssignment: ResourceAssignment) {
204         raRuntimeService.getBluePrintError().addError(runtimeException.message!!)
205     }
206
207 }