Skip to content

Commit b6553e8

Browse files
committed
Try to select an appropriate jar for utbot-spring-analyzer
1 parent aa38f7c commit b6553e8

File tree

1 file changed

+86
-42
lines changed

1 file changed

+86
-42
lines changed

utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import org.utbot.spring.generated.SpringAnalyzerParams
2222
import org.utbot.spring.generated.SpringAnalyzerProcessModel
2323
import org.utbot.spring.generated.springAnalyzerProcessModel
2424
import java.io.File
25+
import java.net.URL
26+
import java.net.URLClassLoader
2527
import java.nio.file.Files
2628

2729
class SpringAnalyzerProcessInstantDeathException :
@@ -30,30 +32,52 @@ class SpringAnalyzerProcessInstantDeathException :
3032
UtSettings.runSpringAnalyzerProcessWithDebug
3133
)
3234

33-
private const val SPRING_ANALYZER_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar"
34-
private const val SPRING_ANALYZER_JAR_PATH = "lib/$SPRING_ANALYZER_JAR_FILENAME"
35+
private const val SPRING_ANALYZER_WITHOUT_SPRINGBOOT_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar"
36+
private const val SPRING_ANALYZER_WITH_SPRINGBOOT_JAR_FILENAME = "utbot-spring-analyzer-with-spring-shadow.jar"
37+
private const val SPRING_ANALYZER_WITHOUT_SPRINBOOT_JAR_PATH = "lib/$SPRING_ANALYZER_WITHOUT_SPRINGBOOT_JAR_FILENAME"
38+
private const val SPRING_ANALYZER_WITH_SPRINBOOT_JAR_PATH = "lib/$SPRING_ANALYZER_WITH_SPRINGBOOT_JAR_FILENAME"
39+
3540
private const val UNKNOWN_MODIFICATION_TIME = 0L
41+
3642
private val logger = KotlinLogging.logger {}
3743
private val rdLogger = UtRdKLogger(logger, "")
3844

39-
private val springAnalyzerJarFile = Files.createDirectories(utBotTempDirectory.toFile().resolve("spring-analyzer").toPath())
40-
.toFile().resolve(SPRING_ANALYZER_JAR_FILENAME).also { jarFile ->
41-
val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_JAR_PATH)
42-
?: error("Unable to find \"$SPRING_ANALYZER_JAR_PATH\" in resources, make sure it's on the classpath")
43-
val resourceConnection = resource.openConnection()
44-
val lastResourceModification = try {
45-
resourceConnection.lastModified
46-
} finally {
47-
resourceConnection.getInputStream().close()
45+
private val springAnalyzerDirectory =
46+
Files.createDirectories(utBotTempDirectory.toFile().resolve("spring-analyzer").toPath()).toFile()
47+
48+
private val springAnalyzerWithoutSpringBootJarFile =
49+
springAnalyzerDirectory
50+
.resolve(SPRING_ANALYZER_WITHOUT_SPRINGBOOT_JAR_FILENAME).also { jarFile ->
51+
val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_WITHOUT_SPRINBOOT_JAR_PATH)
52+
?: error("Unable to find \"$SPRING_ANALYZER_WITHOUT_SPRINBOOT_JAR_PATH\" in resources, make sure it's on the classpath")
53+
updateJarIfRequired(jarFile, resource)
54+
}
55+
56+
private val springAnalyzerWithSpringBootJarFile =
57+
springAnalyzerDirectory
58+
.resolve(SPRING_ANALYZER_WITH_SPRINGBOOT_JAR_FILENAME).also { jarFile ->
59+
val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_WITH_SPRINBOOT_JAR_PATH)
60+
?: error("Unable to find \"$SPRING_ANALYZER_WITH_SPRINBOOT_JAR_PATH\" in resources, make sure it's on the classpath")
61+
updateJarIfRequired(jarFile, resource)
4862
}
49-
if (
50-
!jarFile.exists() ||
51-
jarFile.lastModified() == UNKNOWN_MODIFICATION_TIME ||
52-
lastResourceModification == UNKNOWN_MODIFICATION_TIME ||
53-
jarFile.lastModified() < lastResourceModification
54-
)
55-
FileUtils.copyURLToFile(resource, jarFile)
63+
64+
65+
private fun updateJarIfRequired(jarFile: File, resource: URL) {
66+
val resourceConnection = resource.openConnection()
67+
val lastResourceModification = try {
68+
resourceConnection.lastModified
69+
} finally {
70+
resourceConnection.getInputStream().close()
71+
}
72+
if (
73+
!jarFile.exists() ||
74+
jarFile.lastModified() == UNKNOWN_MODIFICATION_TIME ||
75+
lastResourceModification == UNKNOWN_MODIFICATION_TIME ||
76+
jarFile.lastModified() < lastResourceModification
77+
) {
78+
FileUtils.copyURLToFile(resource, jarFile)
5679
}
80+
}
5781

5882
class SpringAnalyzerProcess private constructor(
5983
rdProcess: ProcessWithRdServer
@@ -71,31 +95,51 @@ class SpringAnalyzerProcess private constructor(
7195

7296
fun createBlocking(classpath: List<String>) = runBlocking { SpringAnalyzerProcess(classpath) }
7397

74-
suspend operator fun invoke(classpathItems: List<String>): SpringAnalyzerProcess = LifetimeDefinition().terminateOnException { lifetime ->
75-
val extendedClasspath = listOf(springAnalyzerJarFile.path) + classpathItems
76-
val rdProcess = startUtProcessWithRdServer(lifetime) { port ->
77-
// TODO put right version of the Spring Boot jar on the
78-
// classpath if user uses Spring without Spring Boot
79-
classpathArgs = listOf(
80-
"-cp",
81-
"\"${extendedClasspath.joinToString(File.pathSeparator)}\"",
82-
"org.utbot.spring.process.SpringAnalyzerProcessMainKt"
83-
)
84-
val cmd = obtainProcessCommandLine(port)
85-
val process = ProcessBuilder(cmd)
86-
.directory(Files.createTempDirectory(utBotTempDirectory, "spring-analyzer").toFile())
87-
.start()
88-
89-
logger.info { "Spring Analyzer process started with PID = ${process.getPid}" }
90-
91-
if (!process.isAlive) throw SpringAnalyzerProcessInstantDeathException()
92-
93-
process
98+
suspend operator fun invoke(classpathItems: List<String>): SpringAnalyzerProcess =
99+
LifetimeDefinition().terminateOnException { lifetime ->
100+
val requiredSpringAnalyzerJarPath = findRequiredSpringAnalyzerJarPath(classpathItems)
101+
val extendedClasspath = listOf(requiredSpringAnalyzerJarPath) + classpathItems
102+
103+
val rdProcess = startUtProcessWithRdServer(lifetime) { port ->
104+
classpathArgs = listOf(
105+
"-cp",
106+
"\"${extendedClasspath.joinToString(File.pathSeparator)}\"",
107+
"org.utbot.spring.process.SpringAnalyzerProcessMainKt"
108+
)
109+
val cmd = obtainProcessCommandLine(port)
110+
val process = ProcessBuilder(cmd)
111+
.directory(Files.createTempDirectory(utBotTempDirectory, "spring-analyzer").toFile())
112+
.start()
113+
114+
logger.info { "Spring Analyzer process started with PID = ${process.getPid}" }
115+
116+
if (!process.isAlive) throw SpringAnalyzerProcessInstantDeathException()
117+
118+
process
119+
}
120+
rdProcess.awaitProcessReady()
121+
val proc = SpringAnalyzerProcess(rdProcess)
122+
proc.loggerModel.setup(rdLogger, proc.lifetime)
123+
return proc
124+
}
125+
126+
/**
127+
* Finds the required version of `utbot-spring-analyzer`.
128+
*
129+
* If user project type is SpringBootApplication, we use his `spring-boot` version.
130+
* If it is a "pure Spring" project, we have to add dependencies on `spring-boot`
131+
* to manage to create our internal SpringBootApplication for bean definitions analysis.
132+
*/
133+
private fun findRequiredSpringAnalyzerJarPath(classpathItems: List<String>): String {
134+
val testClassLoader = URLClassLoader(classpathItems.map { File(it).toURI().toURL() }.toTypedArray())
135+
try {
136+
testClassLoader.loadClass("org.springframework.boot.builder.SpringApplicationBuilder")
137+
} catch (e: ClassNotFoundException) {
138+
return springAnalyzerWithSpringBootJarFile.path
94139
}
95-
rdProcess.awaitProcessReady()
96-
val proc = SpringAnalyzerProcess(rdProcess)
97-
proc.loggerModel.setup(rdLogger, proc.lifetime)
98-
return proc
140+
141+
// TODO: think about using different spring-boot versions depending on spring version in user project
142+
return springAnalyzerWithoutSpringBootJarFile.path
99143
}
100144
}
101145

0 commit comments

Comments
 (0)