Merge "Ensure proper unloading of resources associated with the dynamic classloader...
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / inbounds / designer-api / src / main / kotlin / org / onap / ccsdk / cds / blueprintsprocessor / designer / api / handler / BluePrintModelHandler.kt
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Modifications Copyright © 2019 Bell Canada.
4  * Modifications Copyright © 2019 IBM.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 package org.onap.ccsdk.cds.blueprintsprocessor.designer.api.handler
20
21 import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.domain.BlueprintModel
22 import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.domain.BlueprintModelSearch
23 import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.repository.BlueprintModelContentRepository
24 import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.repository.BlueprintModelRepository
25 import org.onap.ccsdk.cds.blueprintsprocessor.db.primary.repository.BlueprintModelSearchRepository
26 import org.onap.ccsdk.cds.blueprintsprocessor.designer.api.utils.BluePrintEnhancerUtils
27 import org.onap.ccsdk.cds.controllerblueprints.core.*
28 import org.onap.ccsdk.cds.controllerblueprints.core.config.BluePrintLoadConfiguration
29 import org.onap.ccsdk.cds.controllerblueprints.core.data.ErrorCode
30 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintCatalogService
31 import org.onap.ccsdk.cds.controllerblueprints.core.interfaces.BluePrintEnhancerService
32 import org.onap.ccsdk.cds.controllerblueprints.core.scripts.BluePrintCompileCache
33 import org.onap.ccsdk.cds.controllerblueprints.core.utils.BluePrintFileUtils
34 import org.springframework.core.io.ByteArrayResource
35 import org.springframework.core.io.Resource
36 import org.springframework.http.HttpHeaders
37 import org.springframework.http.MediaType
38 import org.springframework.http.ResponseEntity
39 import org.springframework.http.codec.multipart.FilePart
40 import org.springframework.stereotype.Service
41 import org.springframework.transaction.annotation.Transactional
42 import java.io.IOException
43 import java.util.*
44
45 /**
46  * BlueprintModelHandler Purpose: Handler service to handle the request from BlurPrintModelRest
47  *
48  * @author Brinda Santh
49  * @version 1.0
50  */
51
52 @Service
53 open class BluePrintModelHandler(private val blueprintsProcessorCatalogService: BluePrintCatalogService,
54                                  private val bluePrintLoadConfiguration: BluePrintLoadConfiguration,
55                                  private val blueprintModelSearchRepository: BlueprintModelSearchRepository,
56                                  private val blueprintModelRepository: BlueprintModelRepository,
57                                  private val blueprintModelContentRepository: BlueprintModelContentRepository,
58                                  private val bluePrintEnhancerService: BluePrintEnhancerService) {
59
60     private val log = logger(BluePrintModelHandler::class)
61
62     /**
63      * This is a getAllBlueprintModel method to retrieve all the BlueprintModel in Database
64      *
65      * @return List<BlueprintModelSearch> list of the controller blueprint archives
66     </BlueprintModelSearch> */
67     open fun allBlueprintModel(): List<BlueprintModelSearch> {
68         return blueprintModelSearchRepository.findAll()
69     }
70
71     /**
72      * This is a saveBlueprintModel method
73      *
74      * @param filePart filePart
75      * @return Mono<BlueprintModelSearch>
76      * @throws BluePrintException BluePrintException
77     </BlueprintModelSearch> */
78     @Throws(BluePrintException::class)
79     open suspend fun saveBlueprintModel(filePart: FilePart): BlueprintModelSearch {
80         try {
81             return upload(filePart, false)
82         } catch (e: IOException) {
83             throw BluePrintException(ErrorCode.IO_FILE_INTERRUPT.value,
84                     "Error in Save CBA: ${e.message}", e)
85         }
86     }
87
88
89     /**
90      * This is a searchBlueprintModels method
91      *
92      * @param tags tags
93      * @return List<BlueprintModelSearch>
94     </BlueprintModelSearch> */
95     open fun searchBlueprintModels(tags: String): List<BlueprintModelSearch> {
96         return blueprintModelSearchRepository.findByTagsContainingIgnoreCase(tags)
97     }
98
99     /**
100      * This is a getBlueprintModelSearchByNameAndVersion method
101      *
102      * @param name name
103      * @param version version
104      * @return BlueprintModelSearch
105      * @throws BluePrintException BluePrintException
106      */
107     @Throws(BluePrintException::class)
108     open fun getBlueprintModelSearchByNameAndVersion(name: String, version: String): BlueprintModelSearch {
109         return blueprintModelSearchRepository.findByArtifactNameAndArtifactVersion(name, version)
110                 ?: throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value,
111                         String.format(BLUEPRINT_MODEL_NAME_VERSION_FAILURE_MSG, name, version))
112
113     }
114
115     /**
116      * This is a downloadBlueprintModelFileByNameAndVersion method to download a Blueprint by Name and Version
117      *
118      * @param name name
119      * @param version version
120      * @return ResponseEntity<Resource>
121      * @throws BluePrintException BluePrintException
122     </Resource> */
123     @Throws(BluePrintException::class)
124     open fun downloadBlueprintModelFileByNameAndVersion(name: String,
125                                                         version: String): ResponseEntity<Resource> {
126         val blueprintModel: BlueprintModel
127         try {
128             blueprintModel = getBlueprintModelByNameAndVersion(name, version)
129         } catch (e: BluePrintException) {
130             throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value,
131                     String.format("Error while " + "downloading the CBA file: %s", e.message), e)
132         }
133
134         val fileName = blueprintModel.id + ".zip"
135         val file = blueprintModel.blueprintModelContent?.content
136                 ?: throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value,
137                         String.format("Error while downloading the CBA file: couldn't get model content"))
138         return prepareResourceEntity(fileName, file)
139     }
140
141     /**
142      * This is a downloadBlueprintModelFile method to find the target file to download and return a file resource
143      *
144      * @return ResponseEntity<Resource>
145      * @throws BluePrintException BluePrintException
146     </Resource> */
147     @Throws(BluePrintException::class)
148     open fun downloadBlueprintModelFile(id: String): ResponseEntity<Resource> {
149         val blueprintModel: BlueprintModel
150         try {
151             blueprintModel = getBlueprintModel(id)
152         } catch (e: BluePrintException) {
153             throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value, String.format("Error while " + "downloading the CBA file: %s", e.message), e)
154         }
155
156         val fileName = blueprintModel.id + ".zip"
157         val file = blueprintModel.blueprintModelContent?.content
158                 ?: throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value,
159                         String.format("Error while downloading the CBA file: couldn't get model content"))
160         return prepareResourceEntity(fileName, file)
161     }
162
163     /**
164      * @return ResponseEntity<Resource>
165     </Resource> */
166     private fun prepareResourceEntity(fileName: String, file: ByteArray): ResponseEntity<Resource> {
167         return ResponseEntity.ok()
168                 .contentType(MediaType.parseMediaType("text/plain"))
169                 .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"$fileName\"")
170                 .body(ByteArrayResource(file))
171     }
172
173     /**
174      * This is a getBlueprintModel method
175      *
176      * @param id id
177      * @return BlueprintModel
178      * @throws BluePrintException BluePrintException
179      */
180     @Throws(BluePrintException::class)
181     open fun getBlueprintModel(id: String): BlueprintModel {
182         val blueprintModel: BlueprintModel
183         val dbBlueprintModel = blueprintModelRepository.findById(id)
184         if (dbBlueprintModel.isPresent) {
185             blueprintModel = dbBlueprintModel.get()
186         } else {
187             val msg = String.format(BLUEPRINT_MODEL_ID_FAILURE_MSG, id)
188             throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value, msg)
189         }
190         return blueprintModel
191     }
192
193     /**
194      * This is a getBlueprintModelByNameAndVersion method
195      *
196      * @param name name
197      * @param version version
198      * @return BlueprintModel
199      * @throws BluePrintException BluePrintException
200      */
201     @Throws(BluePrintException::class)
202     open fun getBlueprintModelByNameAndVersion(name: String, version: String): BlueprintModel {
203         val blueprintModel = blueprintModelRepository
204                 .findByArtifactNameAndArtifactVersion(name, version)
205         if (blueprintModel != null) {
206             return blueprintModel
207         } else {
208             val msg = String.format(BLUEPRINT_MODEL_NAME_VERSION_FAILURE_MSG, name, version)
209             throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value, msg)
210         }
211     }
212
213     /**
214      * This is a getBlueprintModelSearch method
215      *
216      * @param id id
217      * @return BlueprintModelSearch
218      * @throws BluePrintException BluePrintException
219      */
220     @Throws(BluePrintException::class)
221     open fun getBlueprintModelSearch(id: String): BlueprintModelSearch {
222         return blueprintModelSearchRepository.findById(id)
223                 ?: throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value,
224                         String.format(BLUEPRINT_MODEL_ID_FAILURE_MSG, id))
225     }
226
227     /**
228      * This is a deleteBlueprintModel method
229      *
230      * @param id id
231      * @throws BluePrintException BluePrintException
232      */
233     @Transactional
234     @Throws(BluePrintException::class)
235     open fun deleteBlueprintModel(id: String) {
236         val dbBlueprintModel = blueprintModelRepository.findById(id)
237         if (dbBlueprintModel != null && dbBlueprintModel.isPresent) {
238             blueprintModelContentRepository.deleteByBlueprintModel(dbBlueprintModel.get())
239             blueprintModelRepository.delete(dbBlueprintModel.get())
240         } else {
241             val msg = String.format(BLUEPRINT_MODEL_ID_FAILURE_MSG, id)
242             throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value, msg)
243         }
244     }
245
246     open suspend fun deleteBlueprintModel(name: String, version: String) {
247         blueprintsProcessorCatalogService.deleteFromDatabase(name, version)
248     }
249
250     /**
251      * This is a CBA enrichBlueprint method
252      * Save the Zip File in archive location and extract the cba content.
253      * Populate the Enhancement Location
254      * Enhance the CBA content
255      * Compress the Enhanced Content
256      * Return back the the compressed content back to the caller.
257      *
258      * @param filePart filePart
259      * @return ResponseEntity<Resource>
260      * @throws BluePrintException BluePrintException
261      */
262     @Throws(BluePrintException::class)
263     open suspend fun enrichBlueprint(filePart: FilePart): ResponseEntity<Resource> {
264         try {
265             val enhancedByteArray = enrichBlueprintFileSource(filePart)
266             return BluePrintEnhancerUtils.prepareResourceEntity("enhanced-cba.zip", enhancedByteArray)
267         } catch (e: IOException) {
268             throw BluePrintException(ErrorCode.IO_FILE_INTERRUPT.value,
269                     "Error in Enriching CBA: ${e.message}", e)
270         }
271     }
272
273     /**
274      * This is a publishBlueprintModel method to change the status published to YES
275      *
276      * @param filePart filePart
277      * @return BlueprintModelSearch
278      * @throws BluePrintException BluePrintException
279      */
280     @Throws(BluePrintException::class)
281     open suspend fun publishBlueprint(filePart: FilePart): BlueprintModelSearch {
282         try {
283             return upload(filePart, true)
284         } catch (e: Exception) {
285             throw BluePrintException(ErrorCode.IO_FILE_INTERRUPT.value,
286                     "Error in Publishing CBA: ${e.message}", e)
287         }
288     }
289
290     /** Common CBA Save and Publish function for RestController and GRPC Handler, the [fileSource] may be
291      * byteArray or File Part type.*/
292     open suspend fun upload(fileSource: Any, validate: Boolean): BlueprintModelSearch {
293         val saveId = UUID.randomUUID().toString()
294         val blueprintArchive = normalizedPathName(bluePrintLoadConfiguration.blueprintArchivePath, saveId)
295         val blueprintWorking = normalizedPathName(bluePrintLoadConfiguration.blueprintWorkingPath, saveId)
296         try {
297             val compressedFile = normalizedFile(blueprintArchive, "cba.zip")
298             when (fileSource) {
299                 is FilePart -> BluePrintEnhancerUtils.filePartAsFile(fileSource, compressedFile)
300                 is ByteArray -> BluePrintEnhancerUtils.byteArrayAsFile(fileSource, compressedFile)
301             }
302             // Save the Copied file to Database
303             val blueprintId = blueprintsProcessorCatalogService.saveToDatabase(saveId, compressedFile, validate)
304
305             return blueprintModelSearchRepository.findById(blueprintId)
306                     ?: throw BluePrintException(ErrorCode.RESOURCE_NOT_FOUND.value,
307                             String.format(BLUEPRINT_MODEL_ID_FAILURE_MSG, blueprintId))
308
309         } catch (e: IOException) {
310             throw BluePrintException(ErrorCode.IO_FILE_INTERRUPT.value,
311                     "Error in Upload CBA: ${e.message}", e)
312         } finally {
313             // Clean blueprint script cache
314             val cacheKey = BluePrintFileUtils
315                     .compileCacheKey(normalizedPathName(bluePrintLoadConfiguration.blueprintWorkingPath, saveId))
316             BluePrintCompileCache.cleanClassLoader(cacheKey)
317             deleteNBDir(blueprintArchive)
318             deleteNBDir(blueprintWorking)
319         }
320     }
321
322     /** Common CBA Enrich function for RestController and GRPC Handler, the [fileSource] may be
323      * byteArray or File Part type.*/
324     open suspend fun enrichBlueprintFileSource(fileSource: Any): ByteArray {
325         val enhanceId = UUID.randomUUID().toString()
326         val blueprintArchive = normalizedPathName(bluePrintLoadConfiguration.blueprintArchivePath, enhanceId)
327         val blueprintWorkingDir = normalizedPathName(bluePrintLoadConfiguration.blueprintWorkingPath, enhanceId)
328         try {
329             when (fileSource) {
330                 is FilePart -> BluePrintEnhancerUtils
331                         .copyFilePartToEnhanceDir(fileSource, blueprintArchive, blueprintWorkingDir)
332                 is ByteArray -> BluePrintEnhancerUtils
333                         .copyByteArrayToEnhanceDir(fileSource, blueprintArchive, blueprintWorkingDir)
334             }            // Enhance the Blue Prints
335             bluePrintEnhancerService.enhance(blueprintWorkingDir)
336
337             return BluePrintEnhancerUtils.compressEnhanceDirAndReturnByteArray(blueprintWorkingDir, blueprintArchive)
338
339         } catch (e: IOException) {
340             throw BluePrintException(ErrorCode.IO_FILE_INTERRUPT.value,
341                     "Error in Enriching CBA: ${e.message}", e)
342         } finally {
343             BluePrintEnhancerUtils.cleanEnhancer(blueprintArchive, blueprintWorkingDir)
344         }
345     }
346
347     companion object {
348
349         private const val BLUEPRINT_MODEL_ID_FAILURE_MSG = "failed to get blueprint model id(%s) from repo"
350         private const val BLUEPRINT_MODEL_NAME_VERSION_FAILURE_MSG = "failed to get blueprint model by name(%s)" + " and version(%s) from repo"
351     }
352 }