diff --git a/utbot-spring-analyzer/build.gradle.kts b/utbot-spring-analyzer/build.gradle.kts index 2f07a60fdc..641e064318 100644 --- a/utbot-spring-analyzer/build.gradle.kts +++ b/utbot-spring-analyzer/build.gradle.kts @@ -1,4 +1,3 @@ -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import com.github.jengelman.gradle.plugins.shadow.transformers.Log4j2PluginsCacheFileTransformer import com.github.jengelman.gradle.plugins.shadow.transformers.PropertiesFileTransformer @@ -19,62 +18,34 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -val withoutSpringConfiguration by configurations.creating {} -val withSpringConfiguration by configurations.creating { - extendsFrom(withoutSpringConfiguration) -} -configurations.implementation.get().extendsFrom(withSpringConfiguration) +val shadowJarConfiguration: Configuration by configurations.creating {} +configurations.implementation.get().extendsFrom(shadowJarConfiguration) dependencies { // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot - withSpringConfiguration("org.springframework.boot:spring-boot:$springBootVersion") - + implementation("org.springframework.boot:spring-boot:$springBootVersion") implementation("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") fun ModuleDependency.excludeSlf4jApi() = exclude(group = "org.slf4j", module = "slf4j-api") - withoutSpringConfiguration(project(":utbot-rd")) { excludeSlf4jApi() } - withoutSpringConfiguration(project(":utbot-core")) { excludeSlf4jApi() } - withoutSpringConfiguration(project(":utbot-framework-api")) { excludeSlf4jApi() } - withoutSpringConfiguration("com.jetbrains.rd:rd-framework:$rdVersion") { excludeSlf4jApi() } - withoutSpringConfiguration("com.jetbrains.rd:rd-core:$rdVersion") { excludeSlf4jApi() } - withoutSpringConfiguration("commons-logging:commons-logging:$commonsLoggingVersion") { excludeSlf4jApi() } - withoutSpringConfiguration("commons-io:commons-io:$commonsIOVersion") { excludeSlf4jApi() } + // TODO stop putting dependencies that are only used in SpringAnalyzerProcess into shadow jar + shadowJarConfiguration(project(":utbot-rd")) { excludeSlf4jApi() } + shadowJarConfiguration(project(":utbot-core")) { excludeSlf4jApi() } + shadowJarConfiguration(project(":utbot-framework-api")) { excludeSlf4jApi() } + shadowJarConfiguration("com.jetbrains.rd:rd-framework:$rdVersion") { excludeSlf4jApi() } + shadowJarConfiguration("com.jetbrains.rd:rd-core:$rdVersion") { excludeSlf4jApi() } + shadowJarConfiguration("commons-logging:commons-logging:$commonsLoggingVersion") { excludeSlf4jApi() } + shadowJarConfiguration("commons-io:commons-io:$commonsIOVersion") { excludeSlf4jApi() } } application { mainClass.set("org.utbot.spring.process.SpringAnalyzerProcessMainKt") } -val shadowWithoutSpring by tasks.register("shadowJarWithoutSpring") { - configureShadowJar(withoutSpringConfiguration) - archiveFileName.set("utbot-spring-analyzer-shadow.jar") -} +// see more details -- https://github.com/spring-projects/spring-boot/issues/1828 +tasks.shadowJar { + configurations = listOf(shadowJarConfiguration) -val shadowWithSpring by tasks.register("shadowJarWithSpring") { - configureShadowJar(withSpringConfiguration) - archiveFileName.set("utbot-spring-analyzer-with-spring-shadow.jar") -} - -val springAnalyzerJar: Configuration by configurations.creating { - isCanBeResolved = false - isCanBeConsumed = true -} - -artifacts { - add(springAnalyzerJar.name, shadowWithoutSpring) - add(springAnalyzerJar.name, shadowWithSpring) -} - -fun ShadowJar.configureShadowJar(configuration: Configuration) { - // see more details -- https://github.com/johnrengelman/shadow/blob/master/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy - group = "shadow" - from(sourceSets.main.get().output) - exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "module-info.class") - - configurations = listOf(configuration) - - // see more details -- https://github.com/spring-projects/spring-boot/issues/1828 isZip64 = true // Required for Spring mergeServiceFiles() @@ -87,4 +58,14 @@ fun ShadowJar.configureShadowJar(configuration: Configuration) { }) transform(Log4j2PluginsCacheFileTransformer::class.java) + archiveFileName.set("utbot-spring-analyzer-shadow.jar") +} + +val springAnalyzerJar: Configuration by configurations.creating { + isCanBeResolved = false + isCanBeConsumed = true +} + +artifacts { + add(springAnalyzerJar.name, tasks.shadowJar) } diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt new file mode 100644 index 0000000000..6cd5343b67 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/PureSpringApplicationAnalyzer.kt @@ -0,0 +1,16 @@ +package org.utbot.spring.analyzers + +import org.springframework.context.annotation.AnnotationConfigApplicationContext +import org.springframework.core.env.ConfigurableEnvironment +import org.utbot.spring.exception.UtBotSpringShutdownException + +class PureSpringApplicationAnalyzer : SpringApplicationAnalyzer { + override fun analyze(sources: Array>, environment: ConfigurableEnvironment): List { + val applicationContext = AnnotationConfigApplicationContext() + applicationContext.register(*sources) + applicationContext.environment = environment + return UtBotSpringShutdownException.catch { applicationContext.refresh() }.beanQualifiedNames + } + + override fun canAnalyze() = true +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt index c0c8b86947..0de1723a2e 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzer.kt @@ -1,34 +1,8 @@ package org.utbot.spring.analyzers -import com.jetbrains.rd.util.getLogger -import com.jetbrains.rd.util.info -import org.springframework.boot.builder.SpringApplicationBuilder -import org.springframework.context.ApplicationContextException -import org.utbot.spring.configurators.ApplicationConfigurator -import org.utbot.spring.api.ApplicationData -import org.utbot.spring.postProcessors.UtBotBeanFactoryPostProcessor +import org.springframework.core.env.ConfigurableEnvironment -val logger = getLogger() - -class SpringApplicationAnalyzer(private val applicationData: ApplicationData) { - - fun analyze(): List { - logger.info { "Current Java version is: " + System.getProperty("java.version") } - - val applicationBuilder = SpringApplicationBuilder(SpringApplicationAnalyzer::class.java) - val applicationConfigurator = ApplicationConfigurator(applicationBuilder, applicationData) - - applicationConfigurator.configureApplication() - - try { - applicationBuilder.build() - applicationBuilder.run() - } catch (e: ApplicationContextException) { - // UtBotBeanFactoryPostProcessor destroys bean definitions - // to prevent Spring application from actually starting and - // that causes it to throw ApplicationContextException. - logger.info { "Bean analysis finished successfully" } - } - return UtBotBeanFactoryPostProcessor.beanQualifiedNames - } -} +interface SpringApplicationAnalyzer { + fun analyze(sources: Array>, environment: ConfigurableEnvironment): List + fun canAnalyze(): Boolean +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt new file mode 100644 index 0000000000..705278a27b --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringApplicationAnalyzerFacade.kt @@ -0,0 +1,37 @@ +package org.utbot.spring.analyzers + +import com.jetbrains.rd.util.error +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.springframework.boot.SpringBootVersion +import org.springframework.core.SpringVersion +import org.utbot.spring.api.ApplicationData +import org.utbot.spring.utils.EnvironmentFactory +import org.utbot.spring.utils.SourceFinder + +private val logger = getLogger() + +class SpringApplicationAnalyzerFacade(private val applicationData: ApplicationData) { + + fun analyze(): List { + logger.info { "Current Java version is: " + System.getProperty("java.version") } + logger.info { "Current Spring version is: " + runCatching { SpringVersion.getVersion() }.getOrNull() } + logger.info { "Current Spring Boot version is: " + runCatching { SpringBootVersion.getVersion() }.getOrNull() } + + val sources = SourceFinder(applicationData).findSources() + val environmentFactory = EnvironmentFactory(applicationData) + + for (analyzer in listOf(SpringBootApplicationAnalyzer(), PureSpringApplicationAnalyzer())) { + if (analyzer.canAnalyze()) { + logger.info { "Analyzing with $analyzer" } + try { + return analyzer.analyze(sources, environmentFactory.createEnvironment()) + } catch (e: Throwable) { + logger.error("Analyzer $analyzer failed", e) + } + } + } + logger.error { "All Spring analyzers failed, using empty bean list" } + return emptyList() + } +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt new file mode 100644 index 0000000000..933a3034bc --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/analyzers/SpringBootApplicationAnalyzer.kt @@ -0,0 +1,21 @@ +package org.utbot.spring.analyzers + +import org.springframework.boot.builder.SpringApplicationBuilder +import org.springframework.core.env.ConfigurableEnvironment +import org.utbot.spring.exception.UtBotSpringShutdownException + +class SpringBootApplicationAnalyzer : SpringApplicationAnalyzer { + override fun analyze(sources: Array>, environment: ConfigurableEnvironment): List { + val app = SpringApplicationBuilder(*sources) + .environment(environment) + .build() + return UtBotSpringShutdownException.catch { app.run() }.beanQualifiedNames + } + + override fun canAnalyze(): Boolean = try { + this::class.java.classLoader.loadClass("org.springframework.boot.SpringApplication") + true + } catch (e: ClassNotFoundException) { + false + } +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/ApplicationConfigurator.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/ApplicationConfigurator.kt deleted file mode 100644 index 58d83c51f8..0000000000 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/configurators/ApplicationConfigurator.kt +++ /dev/null @@ -1,67 +0,0 @@ -package org.utbot.spring.configurators - -import com.jetbrains.rd.util.getLogger -import com.jetbrains.rd.util.info -import org.springframework.boot.builder.SpringApplicationBuilder -import org.springframework.core.env.StandardEnvironment -import org.utbot.spring.api.ApplicationData -import org.utbot.spring.config.TestApplicationConfiguration -import org.utbot.spring.utils.ConfigurationManager -import java.io.File -import kotlin.io.path.Path - -const val DEFAULT_PROFILE_NAME = "default" -private val logger = getLogger() - -open class ApplicationConfigurator( - private val applicationBuilder: SpringApplicationBuilder, - private val applicationData: ApplicationData -) { - //private val classLoader: ClassLoader = URLClassLoader(applicationData.classpath) - private val classLoader: ClassLoader = this::class.java.classLoader - - fun configureApplication() { - // TODO: this may help to use file named `application.xml` as a config in Spring Boot. - //applicationBuilder.resourceLoader(DefaultResourceLoader().also { it.classLoader = classLoader }) - - when (findConfigurationType(applicationData)) { - ApplicationConfigurationType.XmlConfiguration -> { - logger.info { "Using xml Spring configuration" } - val configurationManager = ConfigurationManager(classLoader, TestApplicationConfiguration::class.java) - // Put `applicationData.configurationFile` in `@ImportResource` of `TestApplicationConfiguration` - configurationManager.patchImportResourceAnnotation(Path(applicationData.configurationFile).fileName) - applicationBuilder.sources(TestApplicationConfiguration::class.java) - } - else -> { - logger.info { "Using java Spring configuration" } - applicationBuilder.sources( - TestApplicationConfiguration::class.java, - classLoader.loadClass(applicationData.configurationFile) - ) - } - } - - setActiveProfile(applicationData.profileExpression ?: DEFAULT_PROFILE_NAME) - } - - private fun setActiveProfile(profileExpression: String) { - val profilesToActivate = parseProfileExpression(profileExpression) - - val environment = StandardEnvironment() - environment.setActiveProfiles(*profilesToActivate) - - applicationBuilder.environment(environment) - } - - //TODO: implement this, e.g. 'prod|web' -> listOf(prod, web) - private fun parseProfileExpression(profileExpression: String) : Array = arrayOf(profileExpression) - - private fun findConfigurationType(applicationData: ApplicationData): ApplicationConfigurationType { - //TODO: support Spring Boot Applications here. - val fileExtension = File(applicationData.configurationFile).extension - return when (fileExtension) { - "xml" -> ApplicationConfigurationType.XmlConfiguration - else -> ApplicationConfigurationType.JavaConfiguration - } - } -} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt new file mode 100644 index 0000000000..86bdb46a5f --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/exception/UtBotSpringShutdownException.kt @@ -0,0 +1,33 @@ +package org.utbot.spring.exception + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info + +private val logger = getLogger() + +/** + * Use this exception to shutdown the application + * when all required analysis actions are completed. + */ +class UtBotSpringShutdownException( + message: String, + val beanQualifiedNames: List +): RuntimeException(message) { + companion object { + fun catch(block: () -> Unit): UtBotSpringShutdownException { + try { + block() + throw IllegalStateException("UtBotSpringShutdownException has not been thrown") + } catch (e: Throwable) { + // Spring sometimes wraps exceptions in other exceptions, so we go over + // all the causes to determine if UtBotSpringShutdownException was thrown + for(cause in generateSequence(e) { it.cause }) + if (cause is UtBotSpringShutdownException) { + logger.info { "UtBotSpringShutdownException has been successfully caught" } + return cause + } + throw e + } + } + } +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt index 9fd5f67c9c..0c78cb87a1 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/loggers/RDApacheCommonsLogFactory.kt @@ -2,33 +2,45 @@ package org.utbot.spring.loggers import com.jetbrains.rd.util.LogLevel import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info import org.apache.commons.logging.Log import org.apache.commons.logging.impl.LogFactoryImpl +import org.utbot.spring.exception.UtBotSpringShutdownException @Suppress("unused") // used via -Dorg.apache.commons.logging.LogFactory=org.utbot.spring.loggers.RDApacheCommonsLogFactory class RDApacheCommonsLogFactory : LogFactoryImpl() { override fun getInstance(name: String): Log { val logger = getLogger(category = name) return object : Log { - override fun trace(message: Any?) = logger.log(LogLevel.Trace, message, throwable = null) - override fun trace(message: Any?, t: Throwable?) = logger.log(LogLevel.Trace, message, throwable = t) - override fun debug(message: Any?) = logger.log(LogLevel.Debug, message, throwable = null) - override fun debug(message: Any?, t: Throwable?) = logger.log(LogLevel.Debug, message, throwable = t) - override fun info(message: Any?) = logger.log(LogLevel.Info, message, throwable = null) - override fun info(message: Any?, t: Throwable?) = logger.log(LogLevel.Info, message, throwable = t) - override fun warn(message: Any?) = logger.log(LogLevel.Warn, message, throwable = null) - override fun warn(message: Any?, t: Throwable?) = logger.log(LogLevel.Warn, message, throwable = t) - override fun error(message: Any?) = logger.log(LogLevel.Error, message, throwable = null) - override fun error(message: Any?, t: Throwable?) = logger.log(LogLevel.Error, message, throwable = t) - override fun fatal(message: Any?) = logger.log(LogLevel.Fatal, message, throwable = null) - override fun fatal(message: Any?, t: Throwable?) = logger.log(LogLevel.Fatal, message, throwable = t) + private fun log(level: LogLevel, message: Any?, throwable: Throwable?) { + if (throwable is UtBotSpringShutdownException) { + // avoid polluting logs with stack trace of expected exception + logger.info { message } + logger.info { "${throwable::class.java.name}: ${throwable.message}" } + } else + logger.log(level, message, throwable) + } + private fun isEnabled(level: LogLevel) = logger.isEnabled(level) - override fun isTraceEnabled() = logger.isEnabled(LogLevel.Trace) - override fun isDebugEnabled() = logger.isEnabled(LogLevel.Debug) - override fun isInfoEnabled() = logger.isEnabled(LogLevel.Info) - override fun isErrorEnabled() = logger.isEnabled(LogLevel.Error) - override fun isFatalEnabled() = logger.isEnabled(LogLevel.Fatal) - override fun isWarnEnabled() = logger.isEnabled(LogLevel.Warn) + override fun trace(message: Any?) = log(LogLevel.Trace, message, throwable = null) + override fun trace(message: Any?, t: Throwable?) = log(LogLevel.Trace, message, throwable = t) + override fun debug(message: Any?) = log(LogLevel.Debug, message, throwable = null) + override fun debug(message: Any?, t: Throwable?) = log(LogLevel.Debug, message, throwable = t) + override fun info(message: Any?) = log(LogLevel.Info, message, throwable = null) + override fun info(message: Any?, t: Throwable?) = log(LogLevel.Info, message, throwable = t) + override fun warn(message: Any?) = log(LogLevel.Warn, message, throwable = null) + override fun warn(message: Any?, t: Throwable?) = log(LogLevel.Warn, message, throwable = t) + override fun error(message: Any?) = log(LogLevel.Error, message, throwable = null) + override fun error(message: Any?, t: Throwable?) = log(LogLevel.Error, message, throwable = t) + override fun fatal(message: Any?) = log(LogLevel.Fatal, message, throwable = null) + override fun fatal(message: Any?, t: Throwable?) = log(LogLevel.Fatal, message, throwable = t) + + override fun isTraceEnabled() = isEnabled(LogLevel.Trace) + override fun isDebugEnabled() = isEnabled(LogLevel.Debug) + override fun isInfoEnabled() = isEnabled(LogLevel.Info) + override fun isErrorEnabled() = isEnabled(LogLevel.Error) + override fun isFatalEnabled() = isEnabled(LogLevel.Fatal) + override fun isWarnEnabled() = isEnabled(LogLevel.Warn) } } } \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt index d0cf641512..a8a604581b 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/postProcessors/UtBotBeanFactoryPostProcessor.kt @@ -4,18 +4,15 @@ import com.jetbrains.rd.util.getLogger import com.jetbrains.rd.util.info import com.jetbrains.rd.util.warn import org.springframework.beans.factory.BeanCreationException -import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition import org.springframework.beans.factory.config.BeanFactoryPostProcessor import org.springframework.beans.factory.config.ConfigurableListableBeanFactory import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.core.PriorityOrdered +import org.utbot.spring.exception.UtBotSpringShutdownException val logger = getLogger() object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered { - var beanQualifiedNames: List = emptyList() - private set - /** * Sets the priority of post processor to highest to avoid side effects from others. */ @@ -24,14 +21,13 @@ object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered override fun postProcessBeanFactory(beanFactory: ConfigurableListableBeanFactory) { logger.info { "Started post-processing bean factory in UtBot" } - beanQualifiedNames = findBeanClassNames(beanFactory) - logger.info { "Detected Beans: $beanQualifiedNames" } - - // After desired post-processing is completed we destroy bean definitions - // to avoid further possible actions with beans that may be unsafe. - destroyBeanDefinitions(beanFactory) + val beanQualifiedNames = findBeanClassNames(beanFactory) + logger.info { "Detected ${beanQualifiedNames.size} bean qualified names" } logger.info { "Finished post-processing bean factory in UtBot" } + + destroyBeanDefinitions(beanFactory) + throw UtBotSpringShutdownException("Finished post-processing bean factory in UtBot", beanQualifiedNames) } private fun findBeanClassNames(beanFactory: ConfigurableListableBeanFactory): List { @@ -52,4 +48,4 @@ object UtBotBeanFactoryPostProcessor : BeanFactoryPostProcessor, PriorityOrdered beanRegistry.removeBeanDefinition(beanDefinitionName) } } -} +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt index 922dd9d7d4..e98e418025 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcess.kt @@ -23,7 +23,6 @@ import org.utbot.spring.generated.SpringAnalyzerProcessModel import org.utbot.spring.generated.springAnalyzerProcessModel import java.io.File import java.net.URL -import java.net.URLClassLoader import java.nio.file.Files class SpringAnalyzerProcessInstantDeathException : @@ -32,10 +31,8 @@ class SpringAnalyzerProcessInstantDeathException : UtSettings.runSpringAnalyzerProcessWithDebug ) -private const val SPRING_ANALYZER_WITHOUT_SPRINGBOOT_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar" -private const val SPRING_ANALYZER_WITH_SPRINGBOOT_JAR_FILENAME = "utbot-spring-analyzer-with-spring-shadow.jar" -private const val SPRING_ANALYZER_WITHOUT_SPRINBOOT_JAR_PATH = "lib/$SPRING_ANALYZER_WITHOUT_SPRINGBOOT_JAR_FILENAME" -private const val SPRING_ANALYZER_WITH_SPRINBOOT_JAR_PATH = "lib/$SPRING_ANALYZER_WITH_SPRINGBOOT_JAR_FILENAME" +private const val SPRING_ANALYZER_JAR_FILENAME = "utbot-spring-analyzer-shadow.jar" +private const val SPRING_ANALYZER_JAR_PATH = "lib/$SPRING_ANALYZER_JAR_FILENAME" private const val UNKNOWN_MODIFICATION_TIME = 0L @@ -47,23 +44,14 @@ private var classpathArgs = listOf() private val springAnalyzerDirectory = Files.createDirectories(utBotTempDirectory.toFile().resolve("spring-analyzer").toPath()).toFile() -private val springAnalyzerWithoutSpringBootJarFile = +private val springAnalyzerJarFile = springAnalyzerDirectory - .resolve(SPRING_ANALYZER_WITHOUT_SPRINGBOOT_JAR_FILENAME).also { jarFile -> - val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_WITHOUT_SPRINBOOT_JAR_PATH) - ?: error("Unable to find \"$SPRING_ANALYZER_WITHOUT_SPRINBOOT_JAR_PATH\" in resources, make sure it's on the classpath") + .resolve(SPRING_ANALYZER_JAR_FILENAME).also { jarFile -> + val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_JAR_PATH) + ?: error("Unable to find \"$SPRING_ANALYZER_JAR_PATH\" in resources, make sure it's on the classpath") updateJarIfRequired(jarFile, resource) } -private val springAnalyzerWithSpringBootJarFile = - springAnalyzerDirectory - .resolve(SPRING_ANALYZER_WITH_SPRINGBOOT_JAR_FILENAME).also { jarFile -> - val resource = SpringAnalyzerProcess::class.java.classLoader.getResource(SPRING_ANALYZER_WITH_SPRINBOOT_JAR_PATH) - ?: error("Unable to find \"$SPRING_ANALYZER_WITH_SPRINBOOT_JAR_PATH\" in resources, make sure it's on the classpath") - updateJarIfRequired(jarFile, resource) - } - - private fun updateJarIfRequired(jarFile: File, resource: URL) { val resourceConnection = resource.openConnection() val lastResourceModification = try { @@ -97,8 +85,7 @@ class SpringAnalyzerProcess private constructor( suspend operator fun invoke(classpathItems: List): SpringAnalyzerProcess = LifetimeDefinition().terminateOnException { lifetime -> - val requiredSpringAnalyzerJarPath = findRequiredSpringAnalyzerJarPath(classpathItems) - val extendedClasspath = listOf(requiredSpringAnalyzerJarPath) + classpathItems + val extendedClasspath = listOf(springAnalyzerJarFile.path) + classpathItems val rdProcess = startUtProcessWithRdServer(lifetime) { port -> classpathArgs = listOf( @@ -122,25 +109,6 @@ class SpringAnalyzerProcess private constructor( proc.loggerModel.setup(rdLogger, proc.lifetime) return proc } - - /** - * Finds the required version of `utbot-spring-analyzer`. - * - * If user project type is SpringBootApplication, we use his `spring-boot` version. - * If it is a "pure Spring" project, we have to add dependencies on `spring-boot` - * to manage to create our internal SpringBootApplication for bean definitions analysis. - */ - private fun findRequiredSpringAnalyzerJarPath(classpathItems: List): String { - val testClassLoader = URLClassLoader(classpathItems.map { File(it).toURI().toURL() }.toTypedArray(), null) - try { - testClassLoader.loadClass("org.springframework.boot.builder.SpringApplicationBuilder") - } catch (e: ClassNotFoundException) { - return springAnalyzerWithSpringBootJarFile.path - } - - // TODO: think about using different spring-boot versions depending on spring version in user project - return springAnalyzerWithoutSpringBootJarFile.path - } } private val springAnalyzerModel: SpringAnalyzerProcessModel = onSchedulerBlocking { protocol.springAnalyzerProcessModel } diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt index 5a19d80bb6..ab0c2378ce 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/process/SpringAnalyzerProcessMain.kt @@ -13,7 +13,7 @@ import org.utbot.rd.RdSettingsContainerFactory import org.utbot.rd.generated.loggerModel import org.utbot.rd.generated.settingsModel import org.utbot.rd.loggers.UtRdRemoteLoggerFactory -import org.utbot.spring.analyzers.SpringApplicationAnalyzer +import org.utbot.spring.analyzers.SpringApplicationAnalyzerFacade import org.utbot.spring.api.ApplicationData import org.utbot.spring.generated.SpringAnalyzerProcessModel import org.utbot.spring.generated.SpringAnalyzerResult @@ -47,7 +47,7 @@ private fun SpringAnalyzerProcessModel.setup(watchdog: IdleWatchdog, realProtoco params.profileExpression, ) SpringAnalyzerResult( - SpringApplicationAnalyzer(applicationData).analyze().toTypedArray() + SpringApplicationAnalyzerFacade(applicationData).analyze().toTypedArray() ) } } \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/EnvironmentFactory.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/EnvironmentFactory.kt new file mode 100644 index 0000000000..baec9cb353 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/EnvironmentFactory.kt @@ -0,0 +1,26 @@ +package org.utbot.spring.utils + +import org.springframework.core.env.ConfigurableEnvironment +import org.springframework.core.env.StandardEnvironment +import org.utbot.spring.api.ApplicationData + +class EnvironmentFactory( + private val applicationData: ApplicationData +) { + companion object { + const val DEFAULT_PROFILE_NAME = "default" + } + + fun createEnvironment(): ConfigurableEnvironment { + val profilesToActivate = parseProfileExpression(applicationData.profileExpression ?: DEFAULT_PROFILE_NAME) + + val environment = StandardEnvironment() + environment.setActiveProfiles(*profilesToActivate) + + return environment + } + + //TODO: implement this, e.g. 'prod|web' -> listOf(prod, web) + private fun parseProfileExpression(profileExpression: String) : Array = arrayOf(profileExpression) + +} \ No newline at end of file diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt new file mode 100644 index 0000000000..463ba5e260 --- /dev/null +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/SourceFinder.kt @@ -0,0 +1,41 @@ +package org.utbot.spring.utils + +import com.jetbrains.rd.util.getLogger +import com.jetbrains.rd.util.info +import org.utbot.spring.api.ApplicationData +import org.utbot.spring.config.TestApplicationConfiguration +import org.utbot.spring.configurators.ApplicationConfigurationType +import java.io.File +import kotlin.io.path.Path + +private val logger = getLogger() + +open class SourceFinder( + private val applicationData: ApplicationData +) { + private val classLoader: ClassLoader = this::class.java.classLoader + + fun findSources(): Array> = when (configurationType) { + ApplicationConfigurationType.XmlConfiguration -> { + logger.info { "Using xml Spring configuration" } + val configurationManager = ConfigurationManager(classLoader, TestApplicationConfiguration::class.java) + // Put `applicationData.configurationFile` in `@ImportResource` of `TestApplicationConfiguration` + configurationManager.patchImportResourceAnnotation(Path(applicationData.configurationFile).fileName) + arrayOf(TestApplicationConfiguration::class.java) + } + else -> { + logger.info { "Using java Spring configuration" } + arrayOf( + TestApplicationConfiguration::class.java, + classLoader.loadClass(applicationData.configurationFile) + ) + } + } + + //TODO: support Spring Boot Applications here. + private val configurationType: ApplicationConfigurationType + get() = when (File(applicationData.configurationFile).extension) { + "xml" -> ApplicationConfigurationType.XmlConfiguration + else -> ApplicationConfigurationType.JavaConfiguration + } +} diff --git a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/FakeFileManager.kt b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/TempFileManager.kt similarity index 88% rename from utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/FakeFileManager.kt rename to utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/TempFileManager.kt index 5b8ab23572..14c8e96a85 100644 --- a/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/FakeFileManager.kt +++ b/utbot-spring-analyzer/src/main/kotlin/org/utbot/spring/utils/TempFileManager.kt @@ -5,9 +5,9 @@ import com.jetbrains.rd.util.info import java.io.File import java.io.IOException -val logger = getLogger() +private val logger = getLogger() -class FakeFileManager(private val fakeFilesList: List) { +class TempFileManager(private val fakeFilesList: List) { fun createTempFiles() { for (fileName in fakeFilesList) {