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