Removed redundant timeout handling for executeCommand
[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 com.google.common.cache.CacheBuilder
20 import com.google.common.cache.CacheLoader
21 import com.google.common.cache.LoadingCache
22 import kotlinx.coroutines.async
23 import kotlinx.coroutines.coroutineScope
24 import kotlinx.coroutines.sync.Mutex
25 import kotlinx.coroutines.sync.withLock
26 import org.jetbrains.kotlin.cli.common.ExitCode
27 import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
28 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
29 import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
30 import org.jetbrains.kotlin.cli.common.messages.MessageCollector
31 import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
32 import org.jetbrains.kotlin.config.Services
33 import org.onap.ccsdk.cds.controllerblueprints.core.BlueprintException
34 import org.onap.ccsdk.cds.controllerblueprints.core.checkFileExists
35 import org.onap.ccsdk.cds.controllerblueprints.core.logger
36 import java.io.File
37 import java.net.URLClassLoader
38 import java.util.ArrayList
39 import kotlin.script.experimental.api.SourceCode
40 import kotlin.script.experimental.jvm.util.classpathFromClasspathProperty
41 import kotlin.system.measureTimeMillis
42
43 open class BlueprintCompileService {
44
45     val log = logger(BlueprintCompileService::class)
46
47     companion object {
48
49         val classPaths = classpathFromClasspathProperty()?.joinToString(File.pathSeparator) {
50             it.absolutePath
51         }
52         val mutexCache: LoadingCache<String, Mutex> = CacheBuilder.newBuilder()
53             .build(CacheLoader.from { s -> Mutex() })
54     }
55
56     /** Compile the [bluePrintSourceCode] and get the [kClassName] instance for the constructor [args] */
57     suspend fun <T> eval(
58         bluePrintSourceCode: BlueprintSourceCode,
59         kClassName: String,
60         args: ArrayList<Any?>?
61     ): T {
62         /** Compile the source code if needed */
63         log.debug("Jar Exists : ${bluePrintSourceCode.targetJarFile.exists()}, Regenerate : ${bluePrintSourceCode.regenerate}")
64
65         mutexCache.get(bluePrintSourceCode.targetJarFile.absolutePath).withLock {
66             if (!bluePrintSourceCode.targetJarFile.exists() || bluePrintSourceCode.regenerate) {
67                 compile(bluePrintSourceCode)
68             }
69         }
70
71         val classLoaderWithDependencies = BlueprintCompileCache.classLoader(bluePrintSourceCode.cacheKey)
72
73         /** Create the instance from the class loader */
74         return instance(classLoaderWithDependencies, kClassName, args)
75     }
76
77     /** Compile [bluePrintSourceCode] and put into cache */
78     suspend fun compile(bluePrintSourceCode: BlueprintSourceCode) {
79         // TODO("Include Multiple folders")
80         val sourcePath = bluePrintSourceCode.blueprintKotlinSources.first()
81         val compiledJarFile = bluePrintSourceCode.targetJarFile
82
83         log.info("compiling for cache key(${bluePrintSourceCode.cacheKey})")
84         coroutineScope {
85             val timeTaken = measureTimeMillis {
86                 /** Create compile arguments */
87                 val args = mutableListOf<String>().apply {
88                     add("-no-stdlib")
89                     add("-no-reflect")
90                     add("-module-name")
91                     add(bluePrintSourceCode.moduleName)
92                     add("-cp")
93                     add(classPaths!!)
94                     add(sourcePath)
95                     add("-d")
96                     add(compiledJarFile.absolutePath)
97                     add("-jvm-target")
98                     add("11")
99                 }
100                 val deferredCompile = async {
101                     val k2jvmCompiler = K2JVMCompiler()
102
103                     /** Construct Arguments */
104                     val arguments = k2jvmCompiler.createArguments()
105                     parseCommandLineArguments(args, arguments)
106                     val messageCollector = CompilationMessageCollector()
107
108                     /** Compile with arguments */
109                     val exitCode: ExitCode = k2jvmCompiler.exec(messageCollector, Services.EMPTY, arguments)
110                     when (exitCode) {
111                         ExitCode.OK -> {
112                             checkFileExists(compiledJarFile) { "couldn't generate compiled jar(${compiledJarFile.absolutePath})" }
113                         }
114                         else -> {
115                             throw BlueprintException("$exitCode :${messageCollector.errors().joinToString("\n")}")
116                         }
117                     }
118                 }
119                 deferredCompile.await()
120             }
121             log.info("compiled in ($timeTaken)mSec for cache key(${bluePrintSourceCode.cacheKey})")
122         }
123     }
124
125     /** create class [kClassName] instance from [classLoader] */
126     fun <T> instance(classLoader: URLClassLoader, kClassName: String, args: ArrayList<Any?>? = arrayListOf()): T {
127         val kClazz = classLoader.loadClass(kClassName)
128             ?: throw BlueprintException("failed to load class($kClassName) from current class loader.")
129
130         val instance = if (args.isNullOrEmpty()) {
131             kClazz.newInstance()
132         } else {
133             kClazz.constructors
134                 .single().newInstance(*args.toArray())
135         } ?: throw BlueprintException("failed to create class($kClassName) instance for constructor argument($args).")
136
137         return instance as T
138     }
139 }
140
141 /** Compile source code information */
142 open class BlueprintSourceCode : SourceCode {
143
144     lateinit var blueprintKotlinSources: MutableList<String>
145     lateinit var moduleName: String
146     lateinit var targetJarFile: File
147     lateinit var cacheKey: String
148     var regenerate: Boolean = false
149
150     override val text: String
151         get() = ""
152
153     override val locationId: String? = null
154
155     override val name: String?
156         get() = moduleName
157 }
158
159 /** Class to collect compilation Data */
160 data class CompiledMessageData(
161     val severity: CompilerMessageSeverity,
162     val message: String,
163     val location: CompilerMessageLocation?
164 )
165
166 /** Class to collect compilation results */
167 class CompilationMessageCollector : MessageCollector {
168
169     private val compiledMessages: MutableList<CompiledMessageData> = arrayListOf()
170
171     override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
172         synchronized(compiledMessages) {
173             compiledMessages.add(CompiledMessageData(severity, message, location))
174         }
175     }
176
177     override fun hasErrors() =
178         synchronized(compiledMessages) {
179             compiledMessages.any { it.severity.isError }
180         }
181
182     override fun clear() {
183         synchronized(compiledMessages) {
184             compiledMessages.clear()
185         }
186     }
187
188     fun errors(): List<CompiledMessageData> = compiledMessages.filter { it.severity == CompilerMessageSeverity.ERROR }
189 }