/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import groovy.json.JsonSlurper
import org.apache.commons.io.IOUtils
import org.apache.http.client.config.RequestConfig
import org.apache.http.client.methods.CloseableHttpResponse
import org.apache.http.client.methods.HttpGet
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils

import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Paths
import java.util.concurrent.TimeUnit

buildscript {
    repositories {
        mavenCentral()
        maven {
            url "https://maven.aliyun.com/repository/public"
        }
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }

    dependencies {
        classpath "com.github.spotbugs.snom:spotbugs-gradle-plugin:5.2.5"
        classpath "io.spring.gradle:dependency-management-plugin:1.1.6"
        classpath "com.diffplug.spotless:spotless-plugin-gradle:6.13.0"

        classpath "org.apache.httpcomponents:httpclient:4.5.14"
        classpath "commons-io:commons-io:2.18.0"
    }
}

plugins {
    id 'org.cyclonedx.bom' version '1.8.2'
    id 'com.github.jk1.dependency-license-report' version '2.9'
}

allprojects {
    apply plugin: 'java'
    apply plugin: "eclipse"
    apply plugin: "idea"
    apply plugin: "maven-publish"
    apply plugin: "project-reports"
    apply plugin: "jacoco"
    apply plugin: "pmd"
    apply plugin: "java-library"
    apply plugin: 'signing'
    apply plugin: 'checkstyle'
    apply plugin: 'com.diffplug.spotless'
    apply plugin: "com.github.spotbugs"

    [compileJava, compileTestJava, javadoc]*.options*.encoding = 'UTF-8'

    compileJava.options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"

    java {
        sourceCompatibility = "1.8"
        targetCompatibility = "1.8"
    }

    clean.doFirst {
        delete 'build'
        delete 'dist'
        delete 'out'
    }

    checkstyle {
        toolVersion = '9.0'
        ignoreFailures = false
        showViolations = true
        maxWarnings = 0
        configFile = new File("${rootDir}/style/checkStyle.xml")
        configDirectory = file("${rootDir}/style")
    }

    checkstyleMain.exclude('**/org/apache/eventmesh/client/grpc/protos**')
            .exclude('**/org/apache/eventmesh/common/protocol/grpc/cloudevents**')
            .exclude('**/org/apache/eventmesh/common/protocol/grpc/proto**')
            .exclude('**/org/apache/eventmesh/common/protocol/grpc/adminserver/**')
            .exclude('**/org/apache/eventmesh/connector/openfunction/client/EventMeshGrpcService**')
            .exclude('**/org/apache/eventmesh/connector/openfunction/client/CallbackServiceGrpc**')
            .exclude('**/org/apache/eventmesh/connector/jdbc/antlr**')
            .exclude('**/org/apache/eventmesh/meta/raft/rpc/**')

    dependencies {
        repositories {
            mavenCentral()
            maven {
                url "https://maven.aliyun.com/repository/public"
            }
        }
        testImplementation "org.junit.jupiter:junit-jupiter:5.11.0"
    }

    spotless {
        enforceCheck false
        java {
            target project.fileTree(project.projectDir) {
                include 'src/*/java/**/*.java'
                exclude '**/org/apache/eventmesh/**/protos**'
                exclude '**/org/apache/eventmesh/connector/openfunction/client/EventMeshGrpcService**'
                exclude '**/org/apache/eventmesh/connector/openfunction/client/CallbackServiceGrpc**'
                exclude '**/org/apache/eventmesh/connector/jdbc/antlr**'
                exclude '**/org/apache/eventmesh/common/protocol/grpc**'
                exclude '**/org/apache/eventmesh/connector/jdbc/source/dialect/snapshot/AbstractSnapshotEngine.java'
            }
            importOrder ('\\#org.apache.eventmesh','\\#org.apache','\\#java','\\#javax','\\#org','\\#io','\\#net','\\#junit','\\#com','\\#lombok',
                    'org.apache.eventmesh','org.apache','java','javax','org','io','net','junit','com','lombok')
            licenseHeaderFile rootProject.file('style/checkstyle-header-java.txt')
            eclipse().configFile("${rootDir}/style/task/eventmesh-spotless-formatter.xml")
            removeUnusedImports()
        }
    }
    afterEvaluate {
        tasks.forEach {task ->
            if (task.name.contains("spotlessJava")) {
                task.dependsOn(compileJava, javadoc, compileTestJava, test, processResources, processTestResources)
            }
        }
        spotlessJava.dependsOn(subprojects.collect {it.tasks["spotlessJava"] })
    }

    test {
        useJUnitPlatform()
    }

    // Remove doclint warnings that pollute javadoc logs when building
    if (JavaVersion.current().isJava8()) {
        tasks.withType(Javadoc).tap {
            configureEach {
                options.addStringOption('xdoclint:none', '-quiet')
            }
        }
    }
}

tasks.register('dist') {
    subprojects.forEach { subProject ->
        dependsOn("${subProject.path}:jar")
    }
    dependsOn('generateDistLicense', 'generateDistNotice')
    def includedProjects =
            ["eventmesh-common",
             "eventmesh-meta:eventmesh-meta-api",
             "eventmesh-metrics-plugin:eventmesh-metrics-api",
             "eventmesh-openconnect:eventmesh-openconnect-java",
             "eventmesh-openconnect:eventmesh-openconnect-offsetmgmt-plugin:eventmesh-openconnect-offsetmgmt-api",
             "eventmesh-protocol-plugin:eventmesh-protocol-api",
             "eventmesh-registry:eventmesh-registry-api",
             "eventmesh-retry:eventmesh-retry-api",
             "eventmesh-runtime",
             "eventmesh-runtime-v2",
             "eventmesh-security-plugin:eventmesh-security-api",
             "eventmesh-spi",
             "eventmesh-starter",
             "eventmesh-storage-plugin:eventmesh-storage-api",
             "eventmesh-trace-plugin:eventmesh-trace-api"]
    doLast {
        includedProjects.each {
            def subProject = findProject(it)
            logger.lifecycle('Install module: module: {}', subProject.name)
            copy {
                from subProject.jar.archivePath
                into rootProject.file('dist/apps')
            }
            copy {
                from subProject.file('bin')
                into rootProject.file('dist/bin')
            }
            copy {
                from subProject.file('conf')
                from subProject.sourceSets.main.resources.srcDirs
                into rootProject.file('dist/conf')
                duplicatesStrategy = DuplicatesStrategy.EXCLUDE
                exclude 'META-INF'
            }
            copy {
                from subProject.configurations.runtimeClasspath
                into rootProject.file('dist/lib')
                exclude 'eventmesh-*'
            }
        }
        copy {
            from 'tools/dist-license'
            into rootProject.file('dist')
        }
    }
}

tasks.register('dist-admin') {
    subprojects.forEach { subProject ->
        dependsOn("${subProject.path}:jar")
    }
    def includedProjects =
            [
                    "eventmesh-admin-server",
                    "eventmesh-common",
                    "eventmesh-spi",
                    "eventmesh-registry:eventmesh-registry-api",
                    "eventmesh-registry:eventmesh-registry-nacos",
                    "eventmesh-openconnect:eventmesh-openconnect-offsetmgmt-plugin:eventmesh-openconnect-offsetmgmt-api"
            ]
    doLast {
        includedProjects.each {
            def subProject = findProject(it)
            copy {
                from subProject.jar.archivePath
                into rootProject.file('dist/apps')
            }
            copy {
                from subProject.configurations.runtimeClasspath
                into rootProject.file('dist/lib')
                exclude 'eventmesh-*'
            }
            copy {
                from subProject.file('bin')
                into rootProject.file('dist/bin')
            }
            copy {
                from subProject.file('conf')
                from subProject.sourceSets.main.resources.srcDirs
                into rootProject.file('dist/conf')
                duplicatesStrategy = DuplicatesStrategy.EXCLUDE
                exclude 'META-INF'
            }

        }
    }

}

tasks.register('installPlugin') {
    var pluginProjects = subprojects.findAll {
        it.file('gradle.properties').exists()
                && it.properties.containsKey('pluginType')
                && it.properties.containsKey('pluginName')
    }
    doLast {
        String[] libJars = java.util.Optional.ofNullable(file('dist/lib').list()).orElse(new String[0])
        pluginProjects.forEach(subProject -> {
            var pluginType = subProject.properties.get('pluginType')
            var pluginName = subProject.properties.get('pluginName')
            logger.lifecycle('Install plugin: pluginType: {}, pluginInstanceName: {}, module: {}', pluginType,
                    pluginName, subProject.name)
            copy {
                from subProject.jar.archivePath
                into rootProject.file("dist/plugin/${pluginType}/${pluginName}")
            }
            copy {
                from subProject.configurations.runtimeClasspath
                into rootProject.file("dist/plugin/${pluginType}/${pluginName}")
                exclude(libJars)
            }
            copy {
                from subProject.file('conf')
                from subProject.sourceSets.main.resources.srcDirs
                into rootProject.file("dist/conf")
                exclude 'META-INF'
            }
        })
    }
}

tasks.register('tar', Tar) {
    archiveBaseName.set(project.name)
    archiveVersion.set(project.version.toString())
    archiveExtension.set('tar.gz')
    compression = Compression.GZIP
    destinationDirectory = new File(projectDir, 'build')
    into('/') {
        from 'dist'
    }
}

tasks.register('zip', Zip) {
    archiveBaseName.set(project.name)
    archiveVersion.set(project.version.toString())
    archiveExtension.set('zip')
    destinationDirectory = new File(projectDir, 'build')
    into('/') {
        from 'dist'
    }
}

tasks.register('printProjects') {
    getAllprojects().forEach(subProject -> {
        if ("EventMesh".equals(subProject.getName())) {
            return
        }
        println String.format("%s-%s.jar", subProject.getName(), subProject.getVersion())
    })
}

cyclonedxBom {
    includeConfigs = ["runtimeClasspath"]
}

tasks.register('generateDistLicense') {
    dependsOn('cyclonedxBom') // Task from 'org.cyclonedx.bom' plugin
    doLast {
        // Inputs
        def bomFile = file("build/reports/bom.json")
        def bom = new JsonSlurper().parseText(bomFile.text)
        def projectLicenseText = file('LICENSE').text

        // Outputs
        def distLicenseFile = file('tools/dist-license/LICENSE')
        def licensesDir = file('tools/dist-license/licenses/java/')
        if (licensesDir.exists()) {
            licensesDir.eachFile { it.delete() }
        } else {
            licensesDir.mkdirs()
        }

        List<Map<String, String>> thirdPartyArtifacts = new ArrayList<Map<String, String>>()
        // Parse BOM
        bom.components.each { component ->
            // Exclude project modules
            if (!component.group.startsWith('org.apache.eventmesh')) {
                component.licenses.each { artifactLicense ->
                    if (artifactLicense.license != null) {
                        Map<String, String> artifact = new HashMap<String, String>()
                        artifact.put("name", component.name)
                        artifact.put("version", component.version)
                        if (artifactLicense.license.id != null) {
                            artifact.put("license", artifactLicense.license.id)
                            if (artifactLicense.license.text != null) {
                                artifact.put("licenseContent", new String(artifactLicense.license.text.content.decodeBase64()))
                            }
                        } else {
                            artifact.put("license", artifactLicense.license.name)
                            artifact.put("licenseContent", artifactLicense.license.url)
                        }
                        thirdPartyArtifacts.add(artifact)
                    }
                }
            }
        }
        thirdPartyArtifacts.sort { a, b ->
            def nameComparison = a.name <=> b.name
            if (nameComparison == 0) {
                return a.version <=> b.version
            } else {
                return nameComparison
            }
        }

        def distLicenseText = projectLicenseText + "\n=======================================================================\n" +
                "This distribution contains the following third-party artifacts:\n\n"
        thirdPartyArtifacts.each { artifact ->
            // Write licenses
            def artifactLicenseFilename = artifact.license.replaceAll("/", "-") + ".txt"
            def artifactLicenseFile = new File(licensesDir, artifactLicenseFilename)
            if (artifact.licenseContent != null) {
                artifactLicenseFile.text = artifact.licenseContent
                if (isURL(artifact.licenseContent)) {
                    def licenseUrlFilename = artifact.licenseContent.substring(artifact.licenseContent.lastIndexOf("/") + 1)
                    def downloadedLicenseFilename = artifact.license.replaceAll("/", "-") + "-downloaded-" + licenseUrlFilename
                    def downloadedLicenseFile = new File(licensesDir, downloadedLicenseFilename)
                    downloadFileFromURL(artifact.licenseContent, downloadedLicenseFile.path)
                }
            } else {
                artifactLicenseFile.text = "No license content provided by the artifact."
                logger.warn("No '${artifact.license}' license content provided by ${artifact.name} ${artifact.version}. Please add manually.")
            }

            // Assemble LICENSE
            distLicenseText += "${artifact.name} ${artifact.version} licensed under '${artifact.license}'. " +
                    "For details see: licenses/${artifactLicenseFilename}\n"
        }
        distLicenseFile.text = distLicenseText
    }
}

static boolean isURL(String urlString) {
    if (!urlString.startsWith("http")) {
        return false
    }
    try {
        new URL(urlString)
        return true
    } catch (MalformedURLException e) {
        return false
    }
}

void downloadFileFromURL(String urlString, String destinationPath) throws Exception {
    int timeout = 5 * 1000
    RequestConfig config = RequestConfig.custom().setConnectTimeout(timeout).setConnectionRequestTimeout(timeout).setSocketTimeout(timeout).build()
    CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(config).build()

    HttpGet httpGet = new HttpGet(urlString)
    CloseableHttpResponse response
    try {
        response = httpClient.execute(httpGet)
    } catch (Exception e) {
        logger.error("Error when downloading " + urlString + " : " + e.getMessage() + ". Please check network connection or add license content manually.")
        return
    }

    if (response.getStatusLine().getStatusCode() == 200) {
        try (InputStream is = response.getEntity().getContent()) {
            String respContent = IOUtils.toString(is, StandardCharsets.UTF_8)
            if (respContent.startsWith("../")) {
                // Follow GitHub symlink
                URL baseUrl = new URL(urlString);
                URL absoluteUrl = new URL(baseUrl, respContent);
                downloadFileFromURL(absoluteUrl.toString(), destinationPath);
            } else {
                Files.write(Paths.get(destinationPath), respContent.getBytes(StandardCharsets.UTF_8))
            }
        }
    } else {
        logger.warn("Failed to download " + urlString + " : " + response.getStatusLine() + ". Please add license content manually.")
    }

    EntityUtils.consume(response.getEntity())
    response.close()
}

tasks.register('checkDeniedLicense') {
    dependsOn('generateDistLicense')
    doLast {
        def deniedLicenses = [
                "MS-LPL", "BUSL-1.1",
                "CC-BY-NC-1.0", "CC-BY-NC-2.0", "CC-BY-NC-2.5", "CC-BY-NC-3.0", "CC-BY-NC-4.0",
                "GPL-1.0", "GPL-2.0", "GPL-3.0", "AGPL-3.0", "LGPL-2.0", "LGPL-2.1", "LGPL-3.0",
                "GPL-1.0-only", "GPL-2.0-only", "GPL-3.0-only", "AGPL-3.0-only", "LGPL-2.0-only", "LGPL-2.1-only", "LGPL-3.0-only",
                "QPL-1.0", "Sleepycat", "SSPL-1.0", "CPOL-1.02",
                "BSD-4-Clause", "BSD-4-Clause-UC", "NPL-1.0", "NPL-1.1", "JSON",
                "The GNU General Public License, v2 with Universal FOSS Exception, v1.0"
        ]
        // Update exemptions according to https://github.com/apache/eventmesh/issues/4842
        def allowedArtifacts = ["amqp-client", "stax-api", "javassist", "ST4", "xsdlib", "jsqlparser"]

        def licenseFile = file('tools/dist-license/LICENSE')
        def lines = licenseFile.readLines()
        def hasFailed = false

        lines.each { line ->
            deniedLicenses.each { deniedLicense ->
                if (line.contains("'${deniedLicense}'")) {
                    def isAllowed = allowedArtifacts.any { allowedArtifact ->
                        line.contains(allowedArtifact)
                    }
                    if (!isAllowed) {
                        logger.warn("Incompatible license '${deniedLicense}' found in line: ${line}")
                        hasFailed = true
                    }
                }
            }
        }

        if (hasFailed) {
            throw new GradleException("Check failed due to incompatible licenses found. Please remove these dependencies or add exemptions.")
        } else {
            logger.lifecycle("Check passed, no incompatible licenses found.")
        }
    }
}

tasks.register('generateDistNotice') {
    dependsOn('generateLicenseReport') // Task from 'com.github.jk1.dependency-license-report' plugin
    doLast {
        // Inputs
        def reportsDir = file("build/reports/dependency-license/")
        def projectNoticeText = file('NOTICE').text

        // Outputs
        def distNoticeFile = file('tools/dist-license/NOTICE')

        def distNoticeText = projectNoticeText
        reportsDir.eachDir { dir ->
            dir.eachFileRecurse (groovy.io.FileType.FILES) { file ->
                // Find NOTICE files
                if (file.name.length() >= 6 && file.name.substring(0, 6).equalsIgnoreCase("NOTICE")) {
                    def artifactName = dir.name.replace(".jar", "")
                    distNoticeText += "\n=======================================================================\n\n" +
                            "${artifactName} NOTICE\n" + "\n=======================================================================\n\n"
                    distNoticeText += file.text
                }
            }
        }
        distNoticeFile.text = distNoticeText
    }
}

subprojects {

    apply plugin: "io.spring.dependency-management"

    sourceSets {
        main {
            java.srcDirs = ['src/main/java']
        }
        test {
            java.srcDirs = ['src/test/java']
        }
    }

    clean.doFirst {
        delete 'build'
        delete 'dist'
    }

    // Print all dependencies trees, useful for finding artifacts
    tasks.register('printAllDependencyTrees', DependencyReportTask) {}

    jacoco {
        toolVersion = "0.8.6"
    }

    jacocoTestReport {
        reports {
            xml.required = true
            csv.required = false
            html.required = false
        }
    }

    spotbugs {
        ignoreFailures = true
        showProgress = true
        showStackTraces = true
        effort = 'default'
        reportsDir = file("build/reports/spotbugs")
        projectName = rootProject.name
        release = version
        extraArgs = ['-nested:false']
        maxHeapSize = '256m'

    }

    spotbugsMain {
        reports {
            xml.required = false
            html {
                required = true
                outputLocation = file("build/reports/spotbugs/main/spotbugs.html")
                stylesheet = 'fancy-hist.xsl'
            }
        }
    }

    spotbugsTest {
        reports {
            xml.enabled = false
            html {
                enabled = true
                destination = file("build/reports/spotbugs/test/spotbugs.html")
                stylesheet = 'fancy-hist.xsl'
            }
        }
    }

    pmd {
        consoleOutput = true
        toolVersion = "6.23.0"
        rulesMinimumPriority = 5
        ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"]
        ignoreFailures = true
        pmdMain {
            excludes = ["**/org/apache/eventmesh/connector/jdbc/antlr4/autogeneration/**"]
        }
    }

    jar {
        manifest {
            attributes("Specification-Version": project.version,
                    "Specification-Vendor": "Apache",
                    "Specification-Title": project.name,
                    "Implementation-Version": project.version,
                    "Implementation-Vendor": "Apache",
                    "Implementation-Title": project.name,
                    "Build-Jdk": project.findProperty("jdk")
            )
        }
    }

    javadoc {
        source = sourceSets.main.java
        destinationDir = reporting.file("javadoc")
        options.encoding = "UTF-8"
    }

    tasks.register('packageJavadoc', Jar) {
        dependsOn('javadoc')
        from project.javadoc.destinationDir
        archiveClassifier.set('javadoc')
    }

    tasks.register('packageSources', Jar) {
        from project.sourceSets.main.allSource
        archiveClassifier.set('sources')
    }

    artifacts {
        archives jar
        archives packageJavadoc
        archives packageSources
    }

    if (!Boolean.valueOf(signEnabled)) {
        tasks.configureEach { task ->
            if (task.name.contains("sign")) {
                task.enabled = false
            }
        }
    }

    repositories {
        mavenCentral()
        maven { url "https://maven.aliyun.com/repository/public" }
    }

    configurations.configureEach {
        resolutionStrategy.cacheChangingModulesFor 0, TimeUnit.SECONDS
        resolutionStrategy.cacheDynamicVersionsFor 0, TimeUnit.SECONDS
    }

    publishing {
        publications {
            mavenJava(MavenPublication) {
                from components.java
                artifact packageSources
                artifact packageJavadoc
                versionMapping {
                    usage('java-api') {
                        fromResolutionOf('runtimeClasspath')
                    }
                    usage('java-runtime') {
                        fromResolutionResult()
                    }
                }
                pom {
                    name = 'EventMesh'
                    description = 'Apache EventMesh'
                    url = 'https://github.com/apache/eventmesh'
                    licenses {
                        license {
                            name = 'The Apache License, Version 2.0'
                            url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
                        }
                    }
                    developers {
                        developer {
                            id = 'Apache EventMesh'
                            name = 'Apache EventMesh of ASF'
                            url = 'https://eventmesh.apache.org/'
                        }
                    }
                    scm {
                        connection = 'scm:git:git@github.com:apache/eventmesh.git'
                        developerConnection = 'scm:git:git@github.com:apache/eventmesh.git'
                        url = 'https://github.com/apache/eventmesh'
                    }
                }
            }
        }
        repositories {
            maven {
                def releasesRepoUrl = 'https://repository.apache.org/service/local/staging/deploy/maven2'
                def snapshotsRepoUrl = 'https://repository.apache.org/content/repositories/snapshots/'
                url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
                credentials {
                    username apacheUserName
                    password apachePassWord
                }
            }
        }
    }

    signing {
        sign publishing.publications.mavenJava
    }

    def grpcVersion = '1.68.0'
    def log4jVersion = '2.24.1'
    def jacksonVersion = '2.18.0'
    def dropwizardMetricsVersion = '4.2.26'
    def opentelemetryVersion = '1.36.0'
    def cloudeventsVersion = '3.0.0'
    def curatorVersion = '5.7.0'
    def mockitoVersion = '4.11.0'

    dependencyManagement {
        dependencies {
            dependency "org.apache.commons:commons-lang3:3.17.0"
            dependency "org.apache.commons:commons-collections4:4.4"
            dependency "org.apache.commons:commons-text:1.12.0"
            dependency "commons-io:commons-io:2.18.0"
            dependency "commons-validator:commons-validator:1.9.0"
            dependency "com.google.guava:guava:33.3.0-jre"

            dependency "org.slf4j:slf4j-api:2.0.13"
            dependency "org.apache.logging.log4j:log4j-api:${log4jVersion}"
            dependency "org.apache.logging.log4j:log4j-core:${log4jVersion}"
            dependency "org.apache.logging.log4j:log4j-slf4j2-impl:${log4jVersion}"

            dependency "com.lmax:disruptor:3.4.4"

            dependency "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
            dependency "com.fasterxml.jackson.core:jackson-core:${jacksonVersion}"
            dependency "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}"
            dependency "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:${jacksonVersion}"
            dependency "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${jacksonVersion}"

            dependency "com.squareup.okhttp3:okhttp:3.14.9"

            dependency "org.asynchttpclient:async-http-client:2.12.3"
            dependency "org.apache.httpcomponents:httpclient:4.5.14"

            dependency "io.netty:netty-all:4.1.112.Final"

            dependency "io.dropwizard.metrics:metrics-core:${dropwizardMetricsVersion}"
            dependency "io.dropwizard.metrics:metrics-healthchecks:${dropwizardMetricsVersion}"
            dependency "io.dropwizard.metrics:metrics-annotation:${dropwizardMetricsVersion}"
            dependency "io.dropwizard.metrics:metrics-json:${dropwizardMetricsVersion}"

            dependency "io.opentelemetry:opentelemetry-api:${opentelemetryVersion}"
            dependency "io.opentelemetry:opentelemetry-sdk:${opentelemetryVersion}"
            dependency "io.opentelemetry:opentelemetry-sdk-metrics:${opentelemetryVersion}"
            dependency "io.opentelemetry:opentelemetry-exporter-prometheus:${opentelemetryVersion}-alpha"
            dependency "io.opentelemetry:opentelemetry-exporter-zipkin:${opentelemetryVersion}"
            dependency "io.opentelemetry:opentelemetry-semconv:1.30.1-alpha"
            dependency "io.opentelemetry:opentelemetry-exporter-jaeger:1.34.1"

            dependency "io.openmessaging:openmessaging-api:2.2.1-pubsub"

            dependency "com.h3xstream.findsecbugs:findsecbugs-plugin:1.13.0"
            dependency "com.mebigfatguy.fb-contrib:fb-contrib:7.6.4"
            dependency "com.jayway.jsonpath:json-path:2.9.0"

            dependency "org.springframework.boot:spring-boot-starter-web:2.7.18"
            dependency "io.openmessaging:registry-server:0.0.1"

            dependency "org.junit.jupiter:junit-jupiter:5.11.0"
            dependency "org.junit-pioneer:junit-pioneer:1.9.1"
            dependency "org.assertj:assertj-core:3.26.3"

            dependency "org.mockito:mockito-core:${mockitoVersion}"
            dependency "org.mockito:mockito-inline:${mockitoVersion}"
            dependency "org.mockito:mockito-junit-jupiter:${mockitoVersion}"

            dependency "io.cloudevents:cloudevents-core:${cloudeventsVersion}"
            dependency "io.cloudevents:cloudevents-json-jackson:${cloudeventsVersion}"
            dependency "io.cloudevents:cloudevents-protobuf:${cloudeventsVersion}"

            dependency "io.grpc:grpc-core:${grpcVersion}"
            dependency "io.grpc:grpc-protobuf:${grpcVersion}"
            dependency "io.grpc:grpc-stub:${grpcVersion}"
            dependency "io.grpc:grpc-netty:${grpcVersion}"
            dependency "io.grpc:grpc-netty-shaded:${grpcVersion}"

            dependency "javax.annotation:javax.annotation-api:1.3.2"

            dependency "com.github.seancfoley:ipaddress:5.5.0"
            dependency "com.google.code.gson:gson:2.11.0"

            dependency "org.javassist:javassist:3.30.2-GA"

            dependency "com.alibaba.nacos:nacos-client:2.4.1"

            dependency 'org.apache.zookeeper:zookeeper:3.9.2'
            dependency "org.apache.curator:curator-client:${curatorVersion}"
            dependency "org.apache.curator:curator-framework:${curatorVersion}"
            dependency "org.apache.curator:curator-recipes:${curatorVersion}"
            dependency "org.apache.curator:curator-test:${curatorVersion}"

            dependency "org.projectlombok:lombok:1.18.34"
            dependency "javax.annotation:javax.annotation-api:1.3.2"
            dependency "com.alibaba.fastjson2:fastjson2:2.0.52"

            dependency "software.amazon.awssdk:s3:2.29.5"
            dependency "com.github.rholder:guava-retrying:2.0.0"

            dependency "com.alibaba:druid-spring-boot-starter:1.2.23"
            dependency "com.baomidou:mybatis-plus-boot-starter:3.5.7"
            dependency "com.mysql:mysql-connector-j:8.4.0"
            dependency "org.springframework.boot:spring-boot-starter-jetty:2.7.18"
            dependency "org.locationtech.jts:jts-core:1.20.0"
        }
    }
}
