laviua/komock

View on GitHub
komock-core/src/main/kotlin/ua/com/lavi/komock/registrar/spring/SpringConfigRegistrar.kt

Summary

Maintainability
A
2 hrs
Test Coverage
package ua.com.lavi.komock.registrar.spring

import com.google.gson.Gson
import org.slf4j.LoggerFactory
import org.yaml.snakeyaml.Yaml
import ua.com.lavi.komock.model.HttpMethod
import ua.com.lavi.komock.model.PropertySource
import ua.com.lavi.komock.model.SpringConfigResponse
import ua.com.lavi.komock.model.config.http.HttpServerProperties
import ua.com.lavi.komock.model.config.http.RouteProperties
import ua.com.lavi.komock.model.config.spring.SpringConfigProperties
import ua.com.lavi.komock.http.server.MockServer
import ua.com.lavi.komock.http.server.SecuredMockServer
import ua.com.lavi.komock.http.server.UnsecuredMockServer
import ua.com.lavi.komock.ext.FileChangeHandler
import ua.com.lavi.komock.ext.FileChangeWatcher
import ua.com.lavi.komock.registrar.Registrar
import java.net.BindException
import java.nio.file.Path
import java.util.*

/**
 * Created by Oleksandr Loushkin
 * Register Spring configuration files as a separate http server instance
 */

class SpringConfigRegistrar: Registrar<SpringConfigProperties> {

    private val log = LoggerFactory.getLogger(this.javaClass)

    private val gson = Gson()

    override fun register(properties: SpringConfigProperties) {
        val httpServerProp: HttpServerProperties = properties.httpServer

        val router = if (httpServerProp.ssl.enabled) {
            SecuredMockServer(httpServerProp)
        } else {
            UnsecuredMockServer(httpServerProp)
        }

        try {
            router.start()
        } catch (e: BindException) {
            log.warn(e.message + ": ${httpServerProp.host}, port: ${httpServerProp.port}", e)
            return
        }

        val profiles = properties.profiles

        val fileList = properties.fileList()
        fileList.forEach({ configFilePath -> registerPath(configFilePath, profiles, router) })

        if (properties.doRefresh()) {
            val fileChangeHandler = object : FileChangeHandler {
                override fun onFileChange(filePath: Path) {
                    unregisterPath(filePath, profiles, router)
                    registerPath(filePath, profiles, router)
                }
            }
            FileChangeWatcher(fileChangeHandler, fileList, properties.refreshPeriod).start()
        }
    }

    private fun unregisterPath(configFilePath: Path, profiles: List<String>, server: MockServer) {
        profiles.forEach({
            val serviceName = extractFilenameFromPath(configFilePath)
            val url = "/$serviceName/$it"
            server.deleteRoute(url, HttpMethod.GET)
        })
        log.info("Unregistered spring config file: $configFilePath")
    }

    private fun registerPath(configFilePath: Path,
                             profiles: List<String>,
                             server: MockServer) {
        val serviceName = extractFilenameFromPath(configFilePath)
        val springConfigResponse = SpringConfigResponse(serviceName, profiles, buildPropertySources(configFilePath))

        profiles.forEach { profile -> server.addRoute(buildRouteProperties(springConfigResponse, serviceName, profile)) }

        log.info("Registered spring config file: $configFilePath")
    }

    private fun buildRouteProperties(springConfigResponse: SpringConfigResponse, serviceName: String, profile: String): RouteProperties {
        val routeServerProperties = RouteProperties()
        routeServerProperties.code = 200
        routeServerProperties.httpMethod = HttpMethod.GET.name
        routeServerProperties.responseBody = gson.toJson(springConfigResponse)
        routeServerProperties.contentType = "application/json"
        routeServerProperties.url = "/$serviceName/$profile"
        return routeServerProperties
    }

    private fun buildPropertySources(springConfigFile: Path): ArrayList<PropertySource> {
        val propertySourceName = "file:" + springConfigFile.toString().replace("\\\\".toRegex(), "/")
        val propertySourceBody = buildFlatMap(springConfigFile.toFile().readText())
        return arrayListOf(PropertySource(propertySourceName, propertySourceBody))
    }

    private fun buildFlatMap(textData: String): Map<String, Any> {
        val map = Yaml().load(textData) as Map<String, Any>
        return convertPropertyMap(map, "")
    }

    private fun convertPropertyMap(inputMap: Map<String, Any>, key: String): Map<String, Any> {
        val res = TreeMap<String, Any>()

        for ((key1, value) in inputMap) {
            val newKey = if (key == "") key1 else key + "." + key1

            if (value is Map<*, *>) {
                res.putAll(convertPropertyMap(value as Map<String, Any>, newKey))
            } else {
                res.put(newKey, value)
            }
        }
        return res
    }

    private fun extractFilenameFromPath(filePath: Path): String {
        val path = filePath.fileName ?: throw RuntimeException("File is not exists")
        return path.toString().replace(".yml".toRegex(), "")
    }
}