Enabling Code Formatter
[ccsdk/cds.git] / ms / blueprintsprocessor / modules / blueprints / 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
40     val log = logger(BluePrintCompileService::class)
41
42     companion object {
43
44         val classPaths = classpathFromClasspathProperty()?.joinToString(File.pathSeparator) {
45             it.absolutePath
46         }
47     }
48
49     /** Compile the [bluePrintSourceCode] and get the [kClassName] instance for the constructor [args] */
50     suspend fun <T> eval(
51         bluePrintSourceCode: BluePrintSourceCode,
52         kClassName: String,
53         args: ArrayList<Any?>?
54     ): T {
55         /** Compile the source code if needed */
56         log.debug("Jar Exists : ${bluePrintSourceCode.targetJarFile.exists()}, Regenerate : ${bluePrintSourceCode.regenerate}")
57         if (!bluePrintSourceCode.targetJarFile.exists() || bluePrintSourceCode.regenerate) {
58             compile(bluePrintSourceCode)
59         }
60
61         val classLoaderWithDependencies = BluePrintCompileCache.classLoader(bluePrintSourceCode.cacheKey)
62
63         /** Create the instance from the class loader */
64         return instance(classLoaderWithDependencies, kClassName, args)
65     }
66
67     /** Compile [bluePrintSourceCode] and put into cache */
68     suspend fun compile(bluePrintSourceCode: BluePrintSourceCode) {
69         // TODO("Include Multiple folders")
70         val sourcePath = bluePrintSourceCode.blueprintKotlinSources.first()
71         val compiledJarFile = bluePrintSourceCode.targetJarFile
72
73         log.info("compiling for cache key(${bluePrintSourceCode.cacheKey})")
74         coroutineScope {
75             val timeTaken = measureTimeMillis {
76                 /** Create compile arguments */
77                 val args = mutableListOf<String>().apply {
78                     add("-no-stdlib")
79                     add("-no-reflect")
80                     add("-module-name")
81                     add(bluePrintSourceCode.moduleName)
82                     add("-cp")
83                     add(classPaths!!)
84                     add(sourcePath)
85                     add("-d")
86                     add(compiledJarFile.absolutePath)
87                 }
88                 val deferredCompile = async {
89                     val k2jvmCompiler = K2JVMCompiler()
90
91                     /** Construct Arguments */
92                     val arguments = k2jvmCompiler.createArguments()
93                     parseCommandLineArguments(args, arguments)
94                     val messageCollector = CompilationMessageCollector()
95
96                     /** Compile with arguments */
97                     val exitCode: ExitCode = k2jvmCompiler.exec(messageCollector, Services.EMPTY, arguments)
98                     when (exitCode) {
99                         ExitCode.OK -> {
100                             checkFileExists(compiledJarFile) { "couldn't generate compiled jar(${compiledJarFile.absolutePath})" }
101                         }
102                         else -> {
103                             throw BluePrintException("$exitCode :${messageCollector.errors().joinToString("\n")}")
104                         }
105                     }
106                 }
107                 deferredCompile.await()
108             }
109             log.info("compiled in ($timeTaken)mSec for cache key(${bluePrintSourceCode.cacheKey})")
110         }
111     }
112
113     /** create class [kClassName] instance from [classLoader] */
114     fun <T> instance(classLoader: URLClassLoader, kClassName: String, args: ArrayList<Any?>? = arrayListOf()): T {
115         val kClazz = classLoader.loadClass(kClassName)
116             ?: throw BluePrintException("failed to load class($kClassName) from current class loader.")
117
118         val instance = if (args.isNullOrEmpty()) {
119             kClazz.newInstance()
120         } else {
121             kClazz.constructors
122                 .single().newInstance(*args.toArray())
123         } ?: throw BluePrintException("failed to create class($kClassName) instance for constructor argument($args).")
124
125         return instance as T
126     }
127 }
128
129 /** Compile source code information */
130 open class BluePrintSourceCode : SourceCode {
131
132     lateinit var blueprintKotlinSources: MutableList<String>
133     lateinit var moduleName: String
134     lateinit var targetJarFile: File
135     lateinit var cacheKey: String
136     var regenerate: Boolean = false
137
138     override val text: String
139         get() = ""
140
141     override val locationId: String? = null
142
143     override val name: String?
144         get() = moduleName
145 }
146
147 /** Class to collect compilation Data */
148 data class CompiledMessageData(
149     val severity: CompilerMessageSeverity,
150     val message: String,
151     val location: CompilerMessageLocation?
152 )
153
154 /** Class to collect compilation results */
155 class CompilationMessageCollector : MessageCollector {
156
157     private val compiledMessages: MutableList<CompiledMessageData> = arrayListOf()
158
159     override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
160         synchronized(compiledMessages) {
161             compiledMessages.add(CompiledMessageData(severity, message, location))
162         }
163     }
164
165     override fun hasErrors() =
166         synchronized(compiledMessages) {
167             compiledMessages.any { it.severity.isError }
168         }
169
170     override fun clear() {
171         synchronized(compiledMessages) {
172             compiledMessages.clear()
173         }
174     }
175
176     fun errors(): List<CompiledMessageData> = compiledMessages.filter { it.severity == CompilerMessageSeverity.ERROR }
177 }