Fixing Blueprint Typo's and docs
[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                 }
98                 val deferredCompile = async {
99                     val k2jvmCompiler = K2JVMCompiler()
100
101                     /** Construct Arguments */
102                     val arguments = k2jvmCompiler.createArguments()
103                     parseCommandLineArguments(args, arguments)
104                     val messageCollector = CompilationMessageCollector()
105
106                     /** Compile with arguments */
107                     val exitCode: ExitCode = k2jvmCompiler.exec(messageCollector, Services.EMPTY, arguments)
108                     when (exitCode) {
109                         ExitCode.OK -> {
110                             checkFileExists(compiledJarFile) { "couldn't generate compiled jar(${compiledJarFile.absolutePath})" }
111                         }
112                         else -> {
113                             throw BlueprintException("$exitCode :${messageCollector.errors().joinToString("\n")}")
114                         }
115                     }
116                 }
117                 deferredCompile.await()
118             }
119             log.info("compiled in ($timeTaken)mSec for cache key(${bluePrintSourceCode.cacheKey})")
120         }
121     }
122
123     /** create class [kClassName] instance from [classLoader] */
124     fun <T> instance(classLoader: URLClassLoader, kClassName: String, args: ArrayList<Any?>? = arrayListOf()): T {
125         val kClazz = classLoader.loadClass(kClassName)
126             ?: throw BlueprintException("failed to load class($kClassName) from current class loader.")
127
128         val instance = if (args.isNullOrEmpty()) {
129             kClazz.newInstance()
130         } else {
131             kClazz.constructors
132                 .single().newInstance(*args.toArray())
133         } ?: throw BlueprintException("failed to create class($kClassName) instance for constructor argument($args).")
134
135         return instance as T
136     }
137 }
138
139 /** Compile source code information */
140 open class BlueprintSourceCode : SourceCode {
141
142     lateinit var blueprintKotlinSources: MutableList<String>
143     lateinit var moduleName: String
144     lateinit var targetJarFile: File
145     lateinit var cacheKey: String
146     var regenerate: Boolean = false
147
148     override val text: String
149         get() = ""
150
151     override val locationId: String? = null
152
153     override val name: String?
154         get() = moduleName
155 }
156
157 /** Class to collect compilation Data */
158 data class CompiledMessageData(
159     val severity: CompilerMessageSeverity,
160     val message: String,
161     val location: CompilerMessageLocation?
162 )
163
164 /** Class to collect compilation results */
165 class CompilationMessageCollector : MessageCollector {
166
167     private val compiledMessages: MutableList<CompiledMessageData> = arrayListOf()
168
169     override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
170         synchronized(compiledMessages) {
171             compiledMessages.add(CompiledMessageData(severity, message, location))
172         }
173     }
174
175     override fun hasErrors() =
176         synchronized(compiledMessages) {
177             compiledMessages.any { it.severity.isError }
178         }
179
180     override fun clear() {
181         synchronized(compiledMessages) {
182             compiledMessages.clear()
183         }
184     }
185
186     fun errors(): List<CompiledMessageData> = compiledMessages.filter { it.severity == CompilerMessageSeverity.ERROR }
187 }