All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.ermadmi78.kobby.GenerateKotlinMojo.kt Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
package io.github.ermadmi78.kobby

import io.github.ermadmi78.kobby.generator.kotlin.*
import io.github.ermadmi78.kobby.generator.kotlin.KotlinTypes.PREDEFINED_SCALARS
import io.github.ermadmi78.kobby.model.Decoration
import io.github.ermadmi78.kobby.model.KobbyDirective
import io.github.ermadmi78.kobby.model.PluginUtils.contextName
import io.github.ermadmi78.kobby.model.PluginUtils.extractCommonPrefix
import io.github.ermadmi78.kobby.model.PluginUtils.forEachPackage
import io.github.ermadmi78.kobby.model.PluginUtils.pathIterator
import io.github.ermadmi78.kobby.model.PluginUtils.removePrefixOrEmpty
import io.github.ermadmi78.kobby.model.PluginUtils.toPackageName
import io.github.ermadmi78.kobby.model._capitalize
import io.github.ermadmi78.kobby.model.parseSchema
import org.apache.maven.plugin.AbstractMojo
import org.apache.maven.plugins.annotations.LifecyclePhase
import org.apache.maven.plugins.annotations.Mojo
import org.apache.maven.plugins.annotations.Parameter
import org.apache.maven.plugins.annotations.ResolutionScope
import org.apache.maven.project.MavenProject
import org.codehaus.plexus.util.DirectoryScanner
import java.io.File
import java.io.FileReader

/**
 * Generate Kotlin DSL
 *
 * Created on 17.07.2021
 *
 * @author Dmitry Ermakov ([email protected])
 */
@Mojo(
    name = "generate-kotlin",
    defaultPhase = LifecyclePhase.GENERATE_SOURCES,
    requiresDependencyCollection = ResolutionScope.COMPILE
)
@Suppress("unused")
class GenerateKotlinMojo : AbstractMojo() {
    /**
     * The current Maven project.
     */
    @Parameter(property = "project", required = true, readonly = true)
    private lateinit var project: MavenProject

    @Parameter
    private var schema: SchemaConfig = SchemaConfig()

    @Parameter
    private var kotlin: KotlinConfig = KotlinConfig()

    override fun execute() {
        if (!kotlin.enabled) {
            log.warn("Kobby: Kotlin DSL generation is disabled")
            return
        }

        schema.files = mutableSetOf().let { schemaFiles ->
            if (schema.files.isEmpty()) {
                File(project.basedir, schema.scan.dir).scanGraphqls(schema.scan).forEach {
                    schemaFiles += it
                }
            } else {
                schema.files.asSequence().map { it.absoluteFile }.forEach { configFile ->
                    if (configFile.isFile) {
                        schemaFiles += configFile
                    } else if (configFile.isDirectory) {
                        configFile.scanGraphqls(schema.scan).forEach {
                            schemaFiles += it
                        }
                    }
                }
            }

            schemaFiles.toList()
        }

        if (schema.files.isEmpty()) {
            "GraphQL schema files not found".throwIt()
        }

        val dependencies: Set = mutableSetOf().also { set ->
            project.dependencies?.forEach {
                set += "${it.groupId}:${it.artifactId}"
            }
        }

        val scalars: Map = PREDEFINED_SCALARS + kotlin.scalars.mapValues {
            it.value.toKotlinType()
        }

        if (kotlin.outputDirectory == null) {
            kotlin.outputDirectory = File(project.build.directory, "generated-sources/kobby-kotlin").absoluteFile
        }
        val targetDirectory = kotlin.outputDirectory!!
        if (!targetDirectory.isDirectory && !targetDirectory.mkdirs()) {
            "Failed to create directory for generated sources: $targetDirectory".throwIt()
        }
        project.addCompileSourceRoot(targetDirectory.path)

        val context = kotlin.context
        val dto = kotlin.dto
        val entity = kotlin.entity
        val impl = kotlin.impl
        val ktor = kotlin.adapter.ktor

        dto.serialization.apply {
            if (enabled == null) {
                enabled = "org.jetbrains.kotlinx:kotlinx-serialization-json" in dependencies
            }
        }
        dto.jackson.apply {
            if (enabled == null) {
                enabled = "com.fasterxml.jackson.core:jackson-annotations" in dependencies
            }
        }
        ktor.apply {
            if (simpleEnabled == null || compositeEnabled == null) {
                val ktorClientCioEnabled = "io.ktor:ktor-client-cio-jvm" in dependencies
                if (simpleEnabled == null) {
                    simpleEnabled = ktorClientCioEnabled
                }
                if (compositeEnabled == null) {
                    compositeEnabled = ktorClientCioEnabled
                }
            }
        }

        log.info("[Kobby] Kotlin DSL generating...")

        if (log.isDebugEnabled) {
            log.debug("[Kobby] Plugin Configuration:\n$this")
        }

        val directiveLayout = mapOf(
            KobbyDirective.PRIMARY_KEY to schema.directive.primaryKey,
            KobbyDirective.REQUIRED to schema.directive.required,
            KobbyDirective.DEFAULT to schema.directive.default,
            KobbyDirective.SELECTION to schema.directive.selection
        )

        val contextName = (context.name ?: schema.files.singleOrNull()?.contextName)
            ?.filter { it.isJavaIdentifierPart() }
            ?.takeIf { it.firstOrNull()?.isJavaIdentifierStart() ?: false }
            ?: "graphql"
        val capitalizedContextName = contextName._capitalize()

        val rootPackage: List = mutableListOf().also { list ->
            if (kotlin.relativePackage) {
                schema.files
                    .map { it.parent.pathIterator() }
                    .extractCommonPrefix()
                    .removePrefixOrEmpty(File(project.basedir, schema.scan.dir).absoluteFile.path.pathIterator())
                    .forEach {
                        list += it
                    }
            }
            kotlin.packageName?.forEachPackage { list += it }
        }

        val contextPackage: List = mutableListOf().also { list ->
            list += rootPackage
            context.packageName?.forEachPackage { list += it }
        }

        val dtoPackage: List = mutableListOf().also { list ->
            list += rootPackage
            dto.packageName?.forEachPackage { list += it }
        }

        val dtoGraphQLPackage: List = mutableListOf().also { list ->
            list += dtoPackage
            dto.graphQL.packageName?.forEachPackage { list += it }
        }

        val entityPackage: List = mutableListOf().also { list ->
            list += rootPackage
            entity.packageName?.forEachPackage { list += it }
        }

        val implPackage: List = mutableListOf().also { list ->
            list += rootPackage
            impl.packageName?.forEachPackage { list += it }
        }

        val adapterKtorPackage: List = mutableListOf().also { list ->
            list += rootPackage
            ktor.packageName?.forEachPackage { list += it }
        }

        val layout = KotlinLayout(
            scalars,
            KotlinContextLayout(
                contextPackage.toPackageName(),
                contextName,
                Decoration(context.prefix ?: capitalizedContextName, context.postfix),
                context.query,
                context.mutation,
                context.subscription,
                context.commitEnabled
            ),
            KotlinDtoLayout(
                dtoPackage.toPackageName(),
                Decoration(dto.prefix, dto.postfix),
                Decoration(dto.enumPrefix, dto.enumPostfix),
                Decoration(dto.inputPrefix, dto.inputPostfix),
                dto.applyPrimaryKeys,
                dto.maxNumberOfFieldsForImmutableDtoClass,
                dto.maxNumberOfFieldsForImmutableInputClass,
                KotlinDtoSerialization(
                    dto.serialization.enabled!!,
                    dto.serialization.classDiscriminator,
                    dto.serialization.ignoreUnknownKeys,
                    dto.serialization.encodeDefaults,
                    dto.serialization.prettyPrint
                ),
                KotlinDtoJacksonLayout(
                    dto.jackson.enabled!!,
                    dto.jackson.typeInfoUse,
                    dto.jackson.typeInfoInclude,
                    dto.jackson.typeInfoProperty,
                    dto.jackson.jsonInclude
                ),
                KotlinDtoBuilderLayout(
                    dto.builder.enabled,
                    Decoration(dto.builder.prefix, dto.builder.postfix),
                    dto.builder.toBuilderFun,
                    dto.builder.toDtoFun,
                    dto.builder.toInputFun,
                    dto.builder.copyFun
                ),
                KotlinDtoGraphQLLayout(
                    dto.graphQL.enabled,
                    dtoGraphQLPackage.toPackageName(),
                    Decoration(
                        dto.graphQL.prefix?.trim() ?: capitalizedContextName,
                        dto.graphQL.postfix
                    )
                )
            ),
            KotlinEntityLayout(
                entity.enabled,
                entityPackage.toPackageName(),
                Decoration(entity.prefix, entity.postfix),
                entity.contextFunEnabled,
                entity.contextFunName,
                entity.withCurrentProjectionFun,
                KotlinEntityProjectionLayout(
                    Decoration(entity.projection.projectionPrefix, entity.projection.projectionPostfix),
                    entity.projection.projectionArgument,
                    Decoration(entity.projection.withPrefix, entity.projection.withPostfix),
                    Decoration(entity.projection.withoutPrefix, entity.projection.withoutPostfix),
                    entity.projection.minimizeFun,
                    Decoration(entity.projection.qualificationPrefix, entity.projection.qualificationPostfix),
                    Decoration(
                        entity.projection.qualifiedProjectionPrefix,
                        entity.projection.qualifiedProjectionPostfix
                    ),
                    Decoration(entity.projection.onPrefix, entity.projection.onPostfix)
                ),
                KotlinEntitySelectionLayout(
                    Decoration(entity.selection.selectionPrefix, entity.selection.selectionPostfix),
                    entity.selection.selectionArgument,
                    Decoration(entity.selection.queryPrefix, entity.selection.queryPostfix),
                    entity.selection.queryArgument
                )
            ),
            KotlinImplLayout(
                implPackage.toPackageName(),
                Decoration(impl.prefix, impl.postfix),
                impl.internal,
                Decoration(impl.innerPrefix, impl.innerPostfix)
            ),
            KotlinAdapterLayout(
                KotlinAdapterKtorLayout(
                    ktor.simpleEnabled!!,
                    ktor.compositeEnabled!!,
                    adapterKtorPackage.toPackageName(),
                    Decoration(
                        ktor.prefix?.trim() ?: capitalizedContextName,
                        ktor.postfix
                    ),
                    (ktor.receiveTimeoutMillis ?: 10_000L).takeIf { it > 0L }
                )
            )
        )

        if (layout.dto.run { serialization.enabled && jackson.enabled }) {
            log.warn("[kobby] Kotlinx serialization and Jackson serialization are not supported simultaneously.")
        }

        val schema = try {
            parseSchema(directiveLayout, *schema.files.map { FileReader(it) }.toTypedArray())
        } catch (e: Exception) {
            "Schema parsing failed.".throwIt(e)
        }

        try {
            schema.validate().forEach { warning ->
                log.warn(warning)
            }
        } catch (e: Exception) {
            "Schema validation failed.".throwIt(e)
        }

        val output = try {
            generateKotlin(schema, layout)
        } catch (e: Exception) {
            "Kotlin DSL generation failed.".throwIt(e)
        }

        output.forEach {
            it.writeTo(targetDirectory)
        }

        log.info("[Kobby] Kotlin DSL generated $targetDirectory")
    }

    private fun File.scanGraphqls(scan: ScanConfig): Sequence = DirectoryScanner().run {
        basedir = this@scanGraphqls
        setIncludes(scan.includes.toTypedArray())
        setExcludes(scan.excludes.toTypedArray())
        setFollowSymlinks(false)
        scan()
        includedFiles
    }.asSequence().map { File(this, it).absoluteFile }.filter { it.isFile }

    override fun toString(): String {
        return "GenerateKotlinMojo(" +
                "\n  schema=$schema, " +
                "\n  kotlin=$kotlin" +
                "\n)"
    }

    private fun String.throwIt(cause: Throwable? = null): Nothing {
        val message = "[kobby] $this${if (cause == null) "" else " " + cause.message}"
        if (cause == null) {
            log.error(message)
        } else {
            log.error(message, cause)
        }

        if (cause == null) {
            throw IllegalStateException(message)
        } else {
            throw IllegalStateException(message, cause)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy