Formatting Code base with ktlint
[ccsdk/cds.git] / ms / controllerblueprints / modules / blueprint-core / src / main / kotlin / org / onap / ccsdk / cds / controllerblueprints / core / scripts / BluePrintCompileService.kt
1 /*
2  * Copyright © 2019 IBM.
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *  Unless required by applicable law or agreed to in writing, software
11  *  distributed under the License is distributed on an "AS IS" BASIS,
12  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *  See the License for the specific language governing permissions and
14  *  limitations under the License.
15  */
16
17 package org.onap.ccsdk.cds.controllerblueprints.core.scripts
18
19 import kotlinx.coroutines.async
20 import kotlinx.coroutines.coroutineScope
21 import org.jetbrains.kotlin.cli.common.ExitCode
22 import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
23 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
24 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
25 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
26 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
27 import org.jetbrains.kotlin.config.Services
28 import org.onap.ccsdk.cds.controllerblueprints.core.BluePrintException
29 import org.onap.ccsdk.cds.controllerblueprints.core.checkFileExists
30 import org.onap.ccsdk.cds.controllerblueprints.core.logger
31 import java.io.File
32 import java.net.URLClassLoader
33 import java.util.ArrayList
34 import kotlin.script.experimental.api.SourceCode
35 import kotlin.script.experimental.jvm.util.classpathFromClasspathProperty
36 import kotlin.system.measureTimeMillis
37
38 open class BluePrintCompileService {
39     val log = logger(BluePrintCompileService::class)
40
41     companion object {
42         val classPaths = classpathFromClasspathProperty()?.joinToString(File.pathSeparator) {
43             it.absolutePath
44         }
45     }
46
47     /** Compile the [bluePrintSourceCode] and get the [kClassName] instance for the constructor [args] */
48     suspend fun <T> eval(
49         bluePrintSourceCode: BluePrintSourceCode,
50         kClassName: String,
51         args: ArrayList<Any?>?
52     ): T {
53         /** Compile the source code */
54         compile(bluePrintSourceCode)
55         /** Get the class loader with compiled jar */
56         val classLoaderWithDependencies = BluePrintCompileCache.classLoader(bluePrintSourceCode.cacheKey)
57         /* Create the instance from the class loader */
58         return instance(classLoaderWithDependencies, kClassName, args)
59     }
60
61     /** Compile [bluePrintSourceCode] and put into cache */
62     suspend fun compile(bluePrintSourceCode: BluePrintSourceCode) {
63         // TODO("Include Multiple folders")
64         val sourcePath = bluePrintSourceCode.blueprintKotlinSources.first()
65         val compiledJarFile = bluePrintSourceCode.targetJarFile
66
67         /** Check cache is present for the blueprint scripts */
68         val hasCompiledCache = BluePrintCompileCache.hasClassLoader(bluePrintSourceCode.cacheKey)
69
70         log.debug(
71             "Jar Exists : ${compiledJarFile.exists()}, Regenerate : ${bluePrintSourceCode.regenerate}," +
72                     " Compiled hash(${bluePrintSourceCode.cacheKey}) : $hasCompiledCache"
73         )
74
75         if (!compiledJarFile.exists() || bluePrintSourceCode.regenerate || !hasCompiledCache) {
76             log.info("compiling for cache key(${bluePrintSourceCode.cacheKey})")
77             coroutineScope {
78                 val timeTaken = measureTimeMillis {
79                     /** Create compile arguments */
80                     val args = mutableListOf<String>().apply {
81                         add("-no-stdlib")
82                         add("-no-reflect")
83                         add("-module-name")
84                         add(bluePrintSourceCode.moduleName)
85                         add("-cp")
86                         add(classPaths!!)
87                         add(sourcePath)
88                         add("-d")
89                         add(compiledJarFile.absolutePath)
90                     }
91                     val deferredCompile = async {
92                         val k2jvmCompiler = K2JVMCompiler()
93                         /** Construct Arguments */
94                         val arguments = k2jvmCompiler.createArguments()
95                         parseCommandLineArguments(args, arguments)
96                         val messageCollector = CompilationMessageCollector()
97                         /** Compile with arguments */
98                         val exitCode: ExitCode = k2jvmCompiler.exec(messageCollector, Services.EMPTY, arguments)
99                         when (exitCode) {
100                             ExitCode.OK -> {
101                                 checkFileExists(compiledJarFile) { "couldn't generate compiled jar(${compiledJarFile.absolutePath})" }
102                             }
103                             else -> {
104                                 throw BluePrintException("$exitCode :${messageCollector.errors().joinToString("\n")}")
105                             }
106                         }
107                     }
108                     deferredCompile.await()
109                 }
110                 log.info("compiled in ($timeTaken)mSec for cache key(${bluePrintSourceCode.cacheKey})")
111             }
112         }
113     }
114
115     /** create class [kClassName] instance from [classLoader] */
116     fun <T> instance(classLoader: URLClassLoader, kClassName: String, args: ArrayList<Any?>? = arrayListOf()): T {
117         val kClazz = classLoader.loadClass(kClassName)
118             ?: throw BluePrintException("failed to load class($kClassName) from current class loader.")
119
120         val instance = if (args.isNullOrEmpty()) {
121             kClazz.newInstance()
122         } else {
123             kClazz.constructors
124                 .single().newInstance(*args.toArray())
125         } ?: throw BluePrintException("failed to create class($kClassName) instance for constructor argument($args).")
126         return instance as T
127     }
128 }
129
130 /** Compile source code information */
131 open class BluePrintSourceCode : SourceCode {
132
133     lateinit var blueprintKotlinSources: MutableList<String>
134     lateinit var moduleName: String
135     lateinit var targetJarFile: File
136     lateinit var cacheKey: String
137     var regenerate: Boolean = false
138
139     override val text: String
140         get() = ""
141
142     override val locationId: String? = null
143
144     override val name: String?
145         get() = moduleName
146 }
147
148 /** Class to collect compilation Data */
149 data class CompiledMessageData(
150     val severity: CompilerMessageSeverity,
151     val message: String,
152     val location: CompilerMessageLocation?
153 )
154
155 /** Class to collect compilation results */
156 class CompilationMessageCollector : MessageCollector {
157
158     private val compiledMessages: MutableList<CompiledMessageData> = arrayListOf()
159
160     override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
161         synchronized(compiledMessages) {
162             compiledMessages.add(CompiledMessageData(severity, message, location))
163         }
164     }
165
166     override fun hasErrors() =
167         synchronized(compiledMessages) {
168             compiledMessages.any { it.severity.isError }
169         }
170
171     override fun clear() {
172         synchronized(compiledMessages) {
173             compiledMessages.clear()
174         }
175     }
176
177     fun errors(): List<CompiledMessageData> = compiledMessages.filter { it.severity == CompilerMessageSeverity.ERROR }
178 }