diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index ac469121..fc47e6dc 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -34,7 +34,7 @@ jobs: # GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} # GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} run: | - ./gradlew clean build --rerun-tasks -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" + ./gradlew clean build --continue --scan - name: Capture Test Results if: failure() uses: actions/upload-artifact@v3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4eb1db6a..485c5f48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,8 @@ on: - main paths-ignore: - '.github/**' + schedule: + - cron: '0 10 * * *' # Once per day at 10am UTC workflow_dispatch: env: @@ -44,11 +46,17 @@ jobs: # GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} # GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} run: | - ./gradlew clean build --refresh-dependencies --stacktrace -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" + ./gradlew clean build --continue -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" - name: Deploy artifacts # env: +# ORG_GRADLE_PROJECT_signingKey: ${{ secrets.GPG_PRIVATE_KEY }} +# ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.GPG_PASSPHRASE }} +# OSSRH_TOKEN_USERNAME: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} +# OSSRH_TOKEN_PASSWORD: ${{ secrets.OSSRH_S01_TOKEN_PASSWORD }} +# ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }} +# ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }} # GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }} # GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }} # GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }} run: | - ./gradlew artifactoryPublish --stacktrace -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" + ./gradlew publishArtifact -PartifactoryUsername="$ARTIFACTORY_USERNAME" -PartifactoryPassword="$ARTIFACTORY_PASSWORD" -PossrhUsername="$OSSRH_TOKEN_USERNAME" -PossrhPassword="$OSSRH_TOKEN_PASSWORD" --stacktrace diff --git a/README.adoc b/README.adoc index cedccde5..0a657d33 100644 --- a/README.adoc +++ b/README.adoc @@ -22,16 +22,16 @@ Most of the ideas in this project are borrowed from the Spring for Apache Kafka ./gradlew clean build ``` -The build will produce two artifacts -- `spring-pulsar` and `spring-pulsar-boot-autoconfigure` +The build will produce two artifacts -- `spring-pulsar` and `spring-pulsar-spring-boot-autoconfigure` ### Spring Boot Auto Configuration -We recommend using the library `spring-pulsar` in association with Spring Boot and therefore should also use `spring-pulsar-boot-autoconfigure`. -If you simply use the module `spring-pulsar-boot-autoconfigure`, then that will transitively include `spring-pulsar`. +We recommend using the library `spring-pulsar` in association with Spring Boot and therefore should also use `spring-pulsar-spring-boot-autoconfigure`. +If you simply use the module `spring-pulsar-spring-boot-autoconfigure`, then that will transitively include `spring-pulsar`. #### Pulsar Client -When using `spring-pulsar-boot-autoconfigure`, you get the `PulsarClient` auto-configured. +When using `spring-pulsar-spring-boot-autoconfigure`, you get the `PulsarClient` auto-configured. This is done through a factory bean called `PulsarClientFactoryBean`, which takes a configuration object `PulsarClientConfiguration`. By default, the application tries to connect a local Pulsar instance available at `pulsar://localhost:6650`. @@ -403,8 +403,3 @@ On the producer side also, for the Java primitive types, the framework can infer #### PulsarTemplate API details ### More support to come -- stay tuned... - - - - - diff --git a/build.gradle b/build.gradle index 259bb70b..e6237fb0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,10 @@ plugins { - id 'base' - id 'project-report' - id 'idea' - id 'org.sonarqube' version '2.8' + id 'io.spring.nohttp' + id 'org.springframework.pulsar.root-project' id 'org.ajoberstar.grgit' version '4.0.1' apply false - id 'io.spring.nohttp' version '0.0.5.RELEASE' - id 'io.spring.dependency-management' version '1.0.10.RELEASE' apply false - id 'com.jfrog.artifactory' version '4.18.2' apply false } -apply plugin: 'io.spring.nohttp' +description = 'Spring for Apache Pulsar' def gitPresent = new File('.git').exists() @@ -17,357 +12,64 @@ if (gitPresent) { apply plugin: 'org.ajoberstar.grgit' } -description = 'Spring for Apache Pulsar' - ext { - linkHomepage = 'https://github.com/spring-projects-experimental/spring-pulsar' - linkIssue = 'https://github.com/spring-projects-experimental/spring-pulsar/issues' - linkScmUrl = 'https://github.com/spring-projects-experimental/spring-pulsar' - linkScmConnection = 'https://github.com/spring-projects-experimental/spring-pulsar.git' - linkScmDevConnection = 'git@github.com:spring-projects-experimental/spring-pulsar.git' - if (gitPresent) { - modifiedFiles = - files(grgit.status().unstaged.modified).filter{ f -> f.name.endsWith('.java') } + modifiedFiles = files(grgit.status().unstaged.modified).filter{ f -> f.name.endsWith('.java') } } - - assertjVersion = '3.22.0' - awaitilityVersion = '4.2.0' - googleJsr305Version = '3.0.2' - hamcrestVersion = '2.2' - hibernateValidationVersion = '7.0.4.Final' - jacksonBomVersion = '2.13.3' - jaywayJsonPathVersion = '2.6.0' - junitJupiterVersion = '5.8.2' - pulsarVersion = '2.10.0' - log4jVersion = '2.17.2' - mockitoVersion = '4.5.1' - reactorVersion = '2020.0.17' - springBootVersion = '3.0.0-SNAPSHOT' // docs module - springRetryVersion = '1.3.3' - springVersion = '6.0.0-SNAPSHOT' - caffeineVersion = '3.1.1' - idPrefix = 'pulsar' -} - -nohttp { - source.include '**/src/**' - source.exclude '**/*.gif', '**/*.ks' } allprojects { group = 'org.springframework.pulsar' - - apply plugin: 'io.spring.dependency-management' - - dependencyManagement { - resolutionStrategy { - cacheChangingModulesFor 0, 'seconds' - } - applyMavenExclusions = false - generatedPomCustomization { - enabled = false - } - - imports { - mavenBom "com.fasterxml.jackson:jackson-bom:$jacksonBomVersion" - mavenBom "org.junit:junit-bom:$junitJupiterVersion" - mavenBom "org.springframework:spring-framework-bom:$springVersion" - mavenBom "io.projectreactor:reactor-bom:$reactorVersion" - mavenBom "org.apache.logging.log4j:log4j-bom:$log4jVersion" - } + configurations.all { + resolutionStrategy.cacheChangingModulesFor 0, "minutes" } +} - repositories { - mavenCentral() - maven { url 'https://repo.spring.io/release' } - maven { url 'https://repo.spring.io/milestone' } - if (version.endsWith('SNAPSHOT')) { - maven { url 'https://repo.spring.io/snapshot' } - } -// maven { url 'https://repository.apache.org/content/groups/staging/' } +if (hasProperty('buildScan')) { + buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' } +} +nohttp { + allowlistFile = project.file('src/nohttp/allowlist.lines') + source.exclude "**/bin/**" + source.exclude "**/build/**" + source.exclude "**/out/**" + source.exclude "**/target/**" +} + +check { + dependsOn checkstyleNohttp } subprojects { subproject -> - if (!['spring-pulsar-docs'].contains(subproject.name)) { - apply plugin: 'java-library' - apply plugin: 'java' - apply from: "${rootProject.projectDir}/gradle/publish-artifactory.gradle" - apply plugin: 'eclipse' - apply plugin: 'idea' - apply plugin: 'jacoco' - apply plugin: 'checkstyle' - - java { - withJavadocJar() - withSourcesJar() - registerFeature('optional') { - usingSourceSet(sourceSets.main) - } - registerFeature('provided') { - usingSourceSet(sourceSets.main) - } - } - - compileJava { - sourceCompatibility = 17 - targetCompatibility = 17 - } - - compileTestJava { - sourceCompatibility = 17 - options.encoding = 'UTF-8' - } - - eclipse.project.natures += 'org.springframework.ide.eclipse.core.springnature' - - jacoco { - toolVersion = '0.8.7' - } - - // dependencies that are common across all java projects - dependencies { - implementation "com.google.code.findbugs:jsr305:$googleJsr305Version" - testImplementation 'org.junit.jupiter:junit-jupiter-api' - testImplementation 'org.junit.jupiter:junit-jupiter-params' - testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - // To avoid compiler warnings about @API annotations in JUnit code - testCompileOnly 'org.apiguardian:apiguardian-api:1.0.0' - - testRuntimeOnly 'org.apache.logging.log4j:log4j-core' - testRuntimeOnly 'org.apache.logging.log4j:log4j-jcl' - - testImplementation("org.awaitility:awaitility:$awaitilityVersion") { - exclude group: 'org.hamcrest' - } - testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion" - optionalApi "org.assertj:assertj-core:$assertjVersion" - - testImplementation "org.testcontainers:pulsar:1.17.2" - - testImplementation "org.springframework:spring-test" - - } - - // enable all compiler warnings; individual projects may customize further - [compileJava, compileTestJava]*.options*.compilerArgs = ['-Xlint:all,-options'] - - test { - testLogging { - events "skipped", "failed" - showStandardStreams = project.hasProperty("showStandardStreams") ?: false - showExceptions = true - showStackTraces = true - exceptionFormat = 'full' - } - - maxHeapSize = '1536m' -// jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8111' - jacoco { - destinationFile = file("$buildDir/jacoco.exec") - } - useJUnitPlatform() - - if (System.properties['sonar.host.url']) { - finalizedBy jacocoTestReport - } - } - - checkstyle { - configDirectory.set(rootProject.file("src/checkstyle")) - toolVersion = '9.0' - } - - jacocoTestReport { - reports { - xml.enabled true - csv.enabled false - html.enabled false - xml.destination file("${buildDir}/reports/jacoco/test/jacocoTestReport.xml") - } - } - - publishing { - publications { - mavenJava(MavenPublication) { - suppressAllPomMetadataWarnings() - from components.java - pom.withXml { - def pomDeps = asNode().dependencies.first() - subproject.configurations.providedImplementation.allDependencies.each { dep -> - pomDeps.remove(pomDeps.'*'.find { it.artifactId.text() == dep.name }) - pomDeps.appendNode('dependency').with { - it.appendNode('groupId', dep.group) - it.appendNode('artifactId', dep.name) - it.appendNode('version', dep.version) - it.appendNode('scope', 'provided') + task updateCopyrights { + onlyIf { gitPresent && !System.getenv('GITHUB_ACTION') } + inputs.files(modifiedFiles.filter { f -> f.path.contains(subproject.name) }) + outputs.dir('build') + doLast { + def now = Calendar.instance.get(Calendar.YEAR) as String + inputs.files.each { file -> + def line + file.withReader { reader -> + while (line = reader.readLine()) { + def matcher = line =~ /Copyright (20\d\d)-?(20\d\d)?/ + if (matcher.count) { + def beginningYear = matcher[0][1] + if (now != beginningYear && now != matcher[0][2]) { + def years = "$beginningYear-$now" + def sourceCode = file.text + sourceCode = sourceCode.replaceFirst(/20\d\d(-20\d\d)?/, years) + file.write(sourceCode) + println "Copyright updated for file: $file" } + break } } } } } - - task updateCopyrights { - onlyIf { gitPresent && !System.getenv('GITHUB_ACTION') } - if (gitPresent) { - inputs.files(modifiedFiles.filter { f -> f.path.contains(subproject.name) }) - } - outputs.dir('build') - - doLast { - def now = Calendar.instance.get(Calendar.YEAR) as String - inputs.files.each { file -> - def line - file.withReader { reader -> - while (line = reader.readLine()) { - def matcher = line =~ /Copyright (20\d\d)-?(20\d\d)?/ - if (matcher.count) { - def beginningYear = matcher[0][1] - if (now != beginningYear && now != matcher[0][2]) { - def years = "$beginningYear-$now" - def sourceCode = file.text - sourceCode = sourceCode.replaceFirst(/20\d\d(-20\d\d)?/, years) - file.write(sourceCode) - println "Copyright updated for file: $file" - } - break - } - } - } - } - } - } - - jar { - manifest { - attributes( - 'Implementation-Version': archiveVersion, - 'Created-By': "JDK ${System.properties['java.version']} (${System.properties['java.specification.vendor']})", - 'Implementation-Title': subproject.name, - 'Implementation-Vendor-Id': subproject.group, - 'Implementation-Vendor': 'Pivotal Software, Inc.', - 'Implementation-URL': linkHomepage, - 'Automatic-Module-Name': subproject.name.replace('-', '.') // for Jigsaw - ) - } - - from("${rootProject.projectDir}/src/dist") { - include 'license.txt' - include 'notice.txt' - into 'META-INF' - expand(copyright: new Date().format('yyyy'), version: project.version) - } - from("${rootProject.projectDir}") { - include 'LICENSE.txt' - into 'META-INF' - } - } - - tasks.withType(Javadoc) { - options.addBooleanOption('Xdoclint:syntax', true) // only check syntax with doclint - options.addBooleanOption('Werror', true) // fail build on Javadoc warnings - } - } -} - -project ('spring-pulsar') { - description = 'Spring Pulsar Support' - - dependencies { - api 'org.springframework:spring-context' - api 'org.springframework:spring-messaging' - api 'org.springframework:spring-tx' - api ("org.springframework.retry:spring-retry:$springRetryVersion") { - exclude group: 'org.springframework' - } - api "org.apache.pulsar:pulsar-client:$pulsarVersion" - api "org.apache.pulsar:pulsar-client-admin:$pulsarVersion" - api "org.apache.pulsar:pulsar-client-admin-api:$pulsarVersion" - - api "com.github.ben-manes.caffeine:caffeine:$caffeineVersion" - - optionalApi 'com.fasterxml.jackson.core:jackson-core' - optionalApi 'com.fasterxml.jackson.core:jackson-databind' - optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' - optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' - optionalApi 'com.fasterxml.jackson.datatype:jackson-datatype-joda' - optionalApi ('com.fasterxml.jackson.module:jackson-module-kotlin') { - exclude group: 'org.jetbrains.kotlin' - } - - optionalApi "com.jayway.jsonpath:json-path:$jaywayJsonPathVersion" - - optionalApi 'io.projectreactor:reactor-core' - - testImplementation 'io.projectreactor:reactor-test' - testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" - testImplementation "org.hibernate.validator:hibernate-validator:$hibernateValidationVersion" - } -} - -project ('spring-pulsar-boot-autoconfigure') { - description = 'Spring Boot Pulsar Autoconfiguration' - - apply plugin: 'org.springframework.pulsar.configuration-properties' - - dependencies { - annotationProcessor "org.springframework.boot:spring-boot-configuration-processor:$springBootVersion" - - api "org.springframework.boot:spring-boot:$springBootVersion" - api "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion" - api "org.springframework.boot:spring-boot-starter:$springBootVersion" - api "org.springframework.boot:spring-boot-starter-validation:$springBootVersion" - api project (':spring-pulsar') - - testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion" - } -} - -project ('spring-pulsar-sample-apps') { - description = 'Spring Pulsar Sample Applications' - - dependencies { - api project (':spring-pulsar-boot-autoconfigure') - } - - project.afterEvaluate { - project.tasks.artifactoryPublish.enabled(false) - } -} - -sonarqube { - properties { - property 'sonar.links.homepage', linkHomepage - property 'sonar.links.ci', linkCi - property 'sonar.links.issue', linkIssue - property 'sonar.links.scm', linkScmUrl - property 'sonar.links.scm_dev', linkScmDevConnection - } -} - -// skip publishing the root module -artifactoryPublish.skip = true - -if (project.hasProperty('artifactoryUsername')) { - artifactory { - contextUrl = 'https://repo.spring.io' - publish { - repository { - repoKey = 'libs-snapshot-local' - username = "${artifactoryUsername}" - password = "${artifactoryPassword}" - } - defaults { - publications('mavenJava') - properties { - mavenJava '*:*:*:docs@zip', 'zip.name': 'spring-pulsar', 'zip.displayname': 'Spring Pulsar', 'zip.type': 'docs', 'zip.deployed': false - } - } - } } } diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index de9887b0..ae6efb25 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -1,7 +1,7 @@ plugins { id "java-gradle-plugin" - id "io.spring.javaformat" version "${javaFormatVersion}" - id "checkstyle" + id "java" + id "groovy" } repositories { @@ -12,43 +12,98 @@ repositories { if (version.endsWith('SNAPSHOT')) { maven { url 'https://repo.spring.io/snapshot' } } +// +// maven { url 'https://repo.spring.io/plugins-release/' } +// maven { url 'https://plugins.gradle.org/m2/' } } -sourceCompatibility = 17 -targetCompatibility = 17 +sourceCompatibility = JavaVersion.VERSION_17 +targetCompatibility = JavaVersion.VERSION_17 + +sourceSets { + main { + java { srcDirs = [] } + groovy { srcDirs += ['src/main/java'] } + } +} + +configurations { + implementation { + exclude module: 'groovy-all' + } +} dependencies { - checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${javaFormatVersion}" - implementation("org.asciidoctor:asciidoctor-gradle-jvm:3.3.2") - implementation(platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}")) - implementation("org.springframework:spring-core") - implementation("org.springframework:spring-web") - implementation("com.fasterxml.jackson.core:jackson-databind") - implementation("org.gradle:test-retry-gradle-plugin:${testRetryVersion}") + implementation localGroovy() + implementation 'commons-codec:commons-codec' + implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation 'io.github.gradle-nexus:publish-plugin:1.1.0' implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}") - testImplementation("org.assertj:assertj-core") - testImplementation("org.apache.logging.log4j:log4j-core") - testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") + implementation 'io.spring.nohttp:nohttp-gradle:0.0.10' + implementation "org.apache.maven:maven-embedder:3.6.2" + implementation "org.asciidoctor:asciidoctor-gradle-jvm:3.3.2" + implementation 'org.codehaus.groovy:groovy-all:2.5.17' + implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0' + + implementation "org.gradle:test-retry-gradle-plugin:${testRetryVersion}" + implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1' + implementation(platform("org.springframework.boot:spring-boot-dependencies:${springBootVersion}")) + implementation 'org.springframework:spring-core' + implementation 'org.springframework:spring-web' + + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.apache.logging.log4j:log4j-core' + testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } -checkstyle { - toolVersion = "${checkstyleToolVersion}" +tasks.named('test', Test).configure { + onlyIf { !project.hasProperty("buildSrc.skipTests") } + useJUnitPlatform() + jvmArgs( + '--add-opens', 'java.base/java.lang=ALL-UNNAMED', + '--add-opens', 'java.base/java.util=ALL-UNNAMED' + ) } gradlePlugin { plugins { - configurationProperties { - id = 'org.springframework.pulsar.configuration-properties' - implementationClass = 'org.springframework.pulsar.build.docs.configprops.ConfigurationPropertiesPlugin' + configurationPropertiesPlugin { + id = "org.springframework.pulsar.configuration-properties" + implementationClass = 'org.springframework.pulsar.gradle.docs.configprops.ConfigurationPropertiesPlugin' } - conventionsPlugin { - id = "org.springframework.pulsar.conventions" - implementationClass = "org.springframework.pulsar.build.ConventionsPlugin" + jacocoConventionsPlugin { + id = "org.springframework.pulsar.jacoco" + implementationClass = "org.springframework.pulsar.gradle.JacocoConventionsPlugin" + } + optionalDependenciesPlugin { + id = "org.springframework.pulsar.optional-dependencies" + implementationClass = "org.springframework.boot.gradle.optional.OptionalDependenciesPlugin" + } + rootProjectPlugin { + id = "org.springframework.pulsar.root-project" + implementationClass = "org.springframework.pulsar.gradle.RootProjectPlugin" + } + sonarQubeConventionsPlugin { + id = "org.springframework.pulsar.sonarqube" + implementationClass = "org.springframework.pulsar.gradle.SonarQubeConventionsPlugin" + } + springDocsModulePlugin { + id = "org.springframework.pulsar.spring-docs-module" + implementationClass = "org.springframework.pulsar.gradle.SpringDocsModulePlugin" + } + springModulePlugin { + id = "org.springframework.pulsar.spring-module" + implementationClass = "org.springframework.pulsar.gradle.SpringModulePlugin" + } + // groovy plugins + artifactoryPlugin { + id = "io.spring.convention.artfiactory" + implementationClass = "io.spring.gradle.convention.ArtifactoryPlugin" + } + repositoryConventionPlugin { + id = "io.spring.convention.repository" + implementationClass = "io.spring.gradle.convention.RepositoryConventionPlugin" } } } - -test { - useJUnitPlatform() -} diff --git a/buildSrc/gradle.properties b/buildSrc/gradle.properties index 12845b1e..1838149e 100644 --- a/buildSrc/gradle.properties +++ b/buildSrc/gradle.properties @@ -1,4 +1,5 @@ +apolloGraphqlVersion=2.4.5 checkstyleToolVersion=8.11 javaFormatVersion=0.0.34 -testRetryVersion=1.4.0 springBootVersion=3.0.0-M4 +testRetryVersion=1.4.0 diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy new file mode 100644 index 00000000..502cc2fc --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/ArtifactoryPlugin.groovy @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ +package io.spring.gradle.convention + +import org.gradle.api.Plugin +import org.gradle.api.Project + +import org.springframework.pulsar.gradle.ProjectUtils + +class ArtifactoryPlugin implements Plugin { + + @Override + void apply(Project project) { + project.plugins.apply('com.jfrog.artifactory') + boolean isSnapshot = ProjectUtils.isSnapshot(project); + boolean isMilestone = ProjectUtils.isMilestone(project); + project.artifactory { + contextUrl = 'https://repo.spring.io' + publish { + repository { + repoKey = isSnapshot ? 'libs-snapshot-local' : + (isMilestone ? 'libs-milestone-local' : 'libs-release-local') + if(project.hasProperty('artifactoryUsername')) { + username = artifactoryUsername + password = artifactoryPassword + } + } + defaults { + publications('mavenJava') + properties { + mavenJava '*:*:*:docs@zip', 'zip.name': 'spring-pulsar', 'zip.displayname': 'Spring Pulsar', 'zip.type': 'docs', 'zip.deployed': false + } + } + } + } + } +} diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy new file mode 100644 index 00000000..5ea5d2d3 --- /dev/null +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/RepositoryConventionPlugin.groovy @@ -0,0 +1,76 @@ +/* + * Copyright 2016-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package io.spring.gradle.convention; + +import org.gradle.api.Plugin +import org.gradle.api.Project + +import org.springframework.pulsar.gradle.ProjectUtils + +class RepositoryConventionPlugin implements Plugin { + + @Override + void apply(Project project) { + String[] forceMavenRepositories = ((String) project.findProperty("forceMavenRepositories"))?.split(',') + boolean isImplicitSnapshotRepository = forceMavenRepositories == null && ProjectUtils.isSnapshot(project) + boolean isImplicitMilestoneRepository = forceMavenRepositories == null && ProjectUtils.isMilestone(project) + boolean isSnapshot = isImplicitSnapshotRepository || forceMavenRepositories?.contains('snapshot') + boolean isMilestone = isImplicitMilestoneRepository || forceMavenRepositories?.contains('milestone') + + project.repositories { + if (forceMavenRepositories?.contains('local')) { + mavenLocal() + } + mavenCentral() + if (isSnapshot) { + maven { + name = 'artifactory-snapshot' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username project.artifactoryUsername + password project.artifactoryPassword + } + } + url = 'https://repo.spring.io/snapshot/' + } + } + if (isSnapshot || isMilestone) { + maven { + name = 'artifactory-milestone' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username project.artifactoryUsername + password project.artifactoryPassword + } + } + url = 'https://repo.spring.io/milestone/' + } + } + maven { + name = 'artifactory-release' + if (project.hasProperty('artifactoryUsername')) { + credentials { + username project.artifactoryUsername + password project.artifactoryPassword + } + } + url = 'https://repo.spring.io/release/' + } + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/AsciidoctorConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/AsciidoctorConventionsPlugin.java new file mode 100644 index 00000000..05615c1e --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/AsciidoctorConventionsPlugin.java @@ -0,0 +1,163 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle; + +import java.io.File; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask; +import org.asciidoctor.gradle.jvm.AsciidoctorJExtension; +import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; +import org.asciidoctor.gradle.jvm.AsciidoctorTask; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.Sync; + +import org.springframework.boot.gradle.classpath.artifactory.ArtifactoryRepository; +import org.springframework.util.StringUtils; + +/** + * Conventions that are applied in the presence of the {@link AsciidoctorJPlugin}. When + * the plugin is applied: + * + * + * + * @author Andy Wilkinson + */ +public class AsciidoctorConventionsPlugin implements Plugin { + + private static final String ASCIIDOCTORJ_VERSION = "2.4.3"; + + private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> { + configureDocumentationDependenciesRepository(project); + makeAllWarningsFatal(project); + upgradeAsciidoctorJVersion(project); + createAsciidoctorExtensionsConfiguration(project); + project.getTasks().withType(AbstractAsciidoctorTask.class, + (asciidoctorTask) -> configureAsciidoctorTask(project, asciidoctorTask)); + }); + } + + private void configureDocumentationDependenciesRepository(Project project) { + project.getRepositories().maven((mavenRepo) -> { + mavenRepo.setUrl(URI.create("https://repo.spring.io/release")); + mavenRepo.mavenContent((mavenContent) -> { + mavenContent.includeGroup("io.spring.asciidoctor"); + mavenContent.includeGroup("io.spring.asciidoctor.backends"); + mavenContent.includeGroup("io.spring.docresources"); + }); + }); + } + + private void makeAllWarningsFatal(Project project) { + project.getExtensions().getByType(AsciidoctorJExtension.class).fatalWarnings(".*"); + } + + private void upgradeAsciidoctorJVersion(Project project) { + project.getExtensions().getByType(AsciidoctorJExtension.class).setVersion(ASCIIDOCTORJ_VERSION); + } + + private void createAsciidoctorExtensionsConfiguration(Project project) { + project.getConfigurations().create(EXTENSIONS_CONFIGURATION_NAME, (configuration) -> { + project.getConfigurations().matching((candidate) -> "dependencyManagement".equals(candidate.getName())) + .all(configuration::extendsFrom); + configuration.getDependencies().add(project.getDependencies() + .create("io.spring.asciidoctor.backends:spring-asciidoctor-backends:0.0.3")); + configuration.getDependencies() + .add(project.getDependencies().create("org.asciidoctor:asciidoctorj-pdf:1.5.3")); + }); + } + + private void configureAsciidoctorTask(Project project, AbstractAsciidoctorTask asciidoctorTask) { + asciidoctorTask.configurations(EXTENSIONS_CONFIGURATION_NAME); + configureCommonAttributes(project, asciidoctorTask); + configureOptions(asciidoctorTask); + asciidoctorTask.baseDirFollowsSourceDir(); + createSyncDocumentationSourceTask(project, asciidoctorTask); + if (asciidoctorTask instanceof AsciidoctorTask task) { + boolean pdf = task.getName().toLowerCase().contains("pdf"); + String backend = (!pdf) ? "spring-html" : "spring-pdf"; + task.outputOptions((outputOptions) -> outputOptions.backends(backend)); + } + } + + private void configureCommonAttributes(Project project, AbstractAsciidoctorTask asciidoctorTask) { + Map attributes = new HashMap<>(); + attributes.put("attribute-missing", "warn"); + attributes.put("github-tag", determineGitHubTag(project)); + attributes.put("spring-boot-artifactory-repo", ArtifactoryRepository.forProject(project)); + attributes.put("revnumber", null); + asciidoctorTask.attributes(attributes); + } + + private String determineGitHubTag(Project project) { + String version = "v" + project.getVersion(); + return (version.endsWith("-SNAPSHOT")) ? "main" : version; + } + + private void configureOptions(AbstractAsciidoctorTask asciidoctorTask) { + asciidoctorTask.options(Collections.singletonMap("doctype", "book")); + } + + private Sync createSyncDocumentationSourceTask(Project project, AbstractAsciidoctorTask asciidoctorTask) { + Sync syncDocumentationSource = project.getTasks() + .create("syncDocumentationSourceFor" + StringUtils.capitalize(asciidoctorTask.getName()), Sync.class); + File syncedSource = new File(project.getBuildDir(), "docs/src/" + asciidoctorTask.getName()); + syncDocumentationSource.setDestinationDir(syncedSource); + syncDocumentationSource.from("src/docs/"); + asciidoctorTask.dependsOn(syncDocumentationSource); + asciidoctorTask.getInputs().dir(syncedSource).withPathSensitivity(PathSensitivity.RELATIVE) + .withPropertyName("synced source"); + asciidoctorTask.setSourceDir(project.relativePath(new File(syncedSource, "asciidoc/"))); + return syncDocumentationSource; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/ExtractResources.java b/buildSrc/src/main/java/org/springframework/boot/gradle/ExtractResources.java new file mode 100644 index 00000000..b75dfabb --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/ExtractResources.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Task; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.util.FileCopyUtils; +import org.springframework.util.PropertyPlaceholderHelper; + +/** + * {@link Task} to extract resources from the classpath and write them to disk. + * + * @author Andy Wilkinson + */ +public class ExtractResources extends DefaultTask { + + private final PropertyPlaceholderHelper propertyPlaceholderHelper = new PropertyPlaceholderHelper("${", "}"); + + private final Map properties = new HashMap<>(); + + private final DirectoryProperty destinationDirectory; + + private List resourceNames = new ArrayList<>(); + + public ExtractResources() { + this.destinationDirectory = getProject().getObjects().directoryProperty(); + } + + @Input + public List getResourceNames() { + return this.resourceNames; + } + + public void setResourcesNames(List resourceNames) { + this.resourceNames = resourceNames; + } + + @OutputDirectory + public DirectoryProperty getDestinationDirectory() { + return this.destinationDirectory; + } + + public void property(String name, String value) { + this.properties.put(name, value); + } + + @Input + public Map getProperties() { + return this.properties; + } + + @TaskAction + void extractResources() throws IOException { + for (String resourceName : this.resourceNames) { + InputStream resourceStream = getClass().getClassLoader().getResourceAsStream(resourceName); + if (resourceStream == null) { + throw new GradleException("Resource '" + resourceName + "' does not exist"); + } + String resource = FileCopyUtils.copyToString(new InputStreamReader(resourceStream, StandardCharsets.UTF_8)); + resource = this.propertyPlaceholderHelper.replacePlaceholders(resource, this.properties::get); + FileCopyUtils.copy(resource, + new FileWriter(this.destinationDirectory.file(resourceName).get().getAsFile())); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/JavaConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/JavaConventionsPlugin.java new file mode 100644 index 00000000..bf748541 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/JavaConventionsPlugin.java @@ -0,0 +1,285 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import io.spring.javaformat.gradle.SpringJavaFormatPlugin; +import io.spring.javaformat.gradle.tasks.CheckFormat; +import io.spring.javaformat.gradle.tasks.Format; +import org.gradle.api.JavaVersion; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.DependencySet; +import org.gradle.api.plugins.JavaBasePlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.plugins.quality.Checkstyle; +import org.gradle.api.plugins.quality.CheckstyleExtension; +import org.gradle.api.plugins.quality.CheckstylePlugin; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.bundling.Jar; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.api.tasks.testing.Test; +import org.gradle.api.tasks.testing.logging.TestExceptionFormat; +import org.gradle.api.tasks.testing.logging.TestLogEvent; +import org.gradle.external.javadoc.CoreJavadocOptions; +import org.gradle.testretry.TestRetryPlugin; +import org.gradle.testretry.TestRetryTaskExtension; + +import org.springframework.boot.gradle.classpath.CheckClasspathForProhibitedDependencies; +import org.springframework.boot.gradle.optional.OptionalDependenciesPlugin; +import org.springframework.boot.gradle.testing.TestFailuresPlugin; +import org.springframework.boot.gradle.toolchain.ToolchainPlugin; +import org.springframework.pulsar.gradle.classpath.LenientCheckClasspathForProhibitedDependencies; +import org.springframework.util.StringUtils; + +/** + * Conventions that are applied in the presence of the {@link JavaBasePlugin}. When the + * plugin is applied: + * + *
    + *
  • The project is configured with source and target compatibility of 17 + *
  • {@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin + * Checkstyle}, {@link TestFailuresPlugin Test Failures}, and {@link TestRetryPlugin Test + * Retry} plugins are applied + *
  • {@link Test} tasks are configured: + *
      + *
    • to use JUnit Platform + *
    • with a max heap of 1024M + *
    • to run after any Checkstyle and format checking tasks + *
    + *
  • A {@code testRuntimeOnly} dependency upon + * {@code org.junit.platform:junit-platform-launcher} is added to projects with the + * {@link JavaPlugin} applied + *
  • {@link JavaCompile}, {@link Javadoc}, and {@link Format} tasks are configured to + * use UTF-8 encoding + *
  • {@link JavaCompile} tasks are configured to: + *
      + *
    • Use {@code -parameters}. + *
    • Treat warnings as errors + *
    • Enable {@code unchecked}, {@code deprecation}, {@code rawtypes}, and {@code varags} + * warnings + *
    + *
  • {@link Jar} tasks are configured to produce jars with LICENSE.txt and NOTICE.txt + * files and the following manifest entries: + *
      + *
    • {@code Automatic-Module-Name} + *
    • {@code Build-Jdk-Spec} + *
    • {@code Built-By} + *
    • {@code Implementation-Title} + *
    • {@code Implementation-Version} + *
    + *
  • {@code spring-pulsar-dependencies} is used for dependency management
  • + *
+ * + *

+ * + * @author Andy Wilkinson + * @author Christoph Dreis + * @author Mike Smithson + * @author Scott Frederick + * @author Chris Bono + */ +public class JavaConventionsPlugin implements Plugin { + + private static final String SOURCE_AND_TARGET_COMPATIBILITY = "17"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaBasePlugin.class, (java) -> { + project.getPlugins().apply(TestFailuresPlugin.class); + configureSpringJavaFormat(project); + configureJavaConventions(project); + configureJavadocConventions(project); + configureTestConventions(project); + configureJarManifestConventions(project); + configureDependencyManagement(project); + configureToolchain(project); + configureProhibitedDependencyChecks(project); + }); + } + + private void configureJarManifestConventions(Project project) { + ExtractResources extractLegalResources = project.getTasks().create("extractLegalResources", + ExtractResources.class); + extractLegalResources.getDestinationDirectory().set(project.getLayout().getBuildDirectory().dir("legal")); + extractLegalResources.setResourcesNames(Arrays.asList("LICENSE.txt", "NOTICE.txt")); + extractLegalResources.property("version", project.getVersion().toString()); + SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); + Set sourceJarTaskNames = sourceSets.stream().map(SourceSet::getSourcesJarTaskName) + .collect(Collectors.toSet()); + Set javadocJarTaskNames = sourceSets.stream().map(SourceSet::getJavadocJarTaskName) + .collect(Collectors.toSet()); + project.getTasks().withType(Jar.class, (jar) -> project.afterEvaluate((evaluated) -> { + jar.metaInf((metaInf) -> metaInf.from(extractLegalResources)); + jar.manifest((manifest) -> { + Map attributes = new TreeMap<>(); + attributes.put("Automatic-Module-Name", project.getName().replace("-", ".")); + attributes.put("Build-Jdk-Spec", SOURCE_AND_TARGET_COMPATIBILITY); + attributes.put("Built-By", "Spring"); + attributes.put("Implementation-Title", + determineImplementationTitle(project, sourceJarTaskNames, javadocJarTaskNames, jar)); + attributes.put("Implementation-Version", project.getVersion()); + manifest.attributes(attributes); + }); + })); + } + + private String determineImplementationTitle(Project project, Set sourceJarTaskNames, + Set javadocJarTaskNames, Jar jar) { + if (sourceJarTaskNames.contains(jar.getName())) { + return "Source for " + project.getName(); + } + if (javadocJarTaskNames.contains(jar.getName())) { + return "Javadoc for " + project.getName(); + } + return project.getDescription(); + } + + private void configureTestConventions(Project project) { + project.getTasks().withType(Test.class, (test) -> { + test.useJUnitPlatform(); + test.setMaxHeapSize("1024M"); + test.testLogging(testLoggingContainer -> { + testLoggingContainer.setEvents(Set.of(TestLogEvent.SKIPPED, TestLogEvent.FAILED)); + testLoggingContainer.setShowStandardStreams(project.hasProperty("showStandardStreams")); + testLoggingContainer.setShowExceptions(true); + testLoggingContainer.setShowStackTraces(true); + testLoggingContainer.setExceptionFormat(TestExceptionFormat.FULL); + }); + project.getTasks().withType(Checkstyle.class, test::mustRunAfter); + project.getTasks().withType(CheckFormat.class, test::mustRunAfter); + }); + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> project.getDependencies() + .add(JavaPlugin.TEST_RUNTIME_ONLY_CONFIGURATION_NAME, "org.junit.platform:junit-platform-launcher")); + project.getPlugins().apply(TestRetryPlugin.class); + project.getTasks().withType(Test.class, + (test) -> project.getPlugins().withType(TestRetryPlugin.class, (testRetryPlugin) -> { + TestRetryTaskExtension testRetry = test.getExtensions().getByType(TestRetryTaskExtension.class); + testRetry.getFailOnPassedAfterRetry().set(true); + testRetry.getMaxRetries().set(isCi() ? 3 : 0); + })); + } + + private boolean isCi() { + return Boolean.parseBoolean(System.getenv("CI")); + } + + private void configureJavadocConventions(Project project) { + project.getTasks().withType(Javadoc.class, (javadoc) -> { + CoreJavadocOptions options = (CoreJavadocOptions) javadoc.getOptions(); + options.source("17"); + options.encoding("UTF-8"); + options.addStringOption("Xdoclint:none", "-quiet"); + }); + } + + private void configureJavaConventions(Project project) { + if (!project.hasProperty("toolchainVersion")) { + JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); + javaPluginExtension.setSourceCompatibility(JavaVersion.toVersion(SOURCE_AND_TARGET_COMPATIBILITY)); + } + project.getTasks().withType(JavaCompile.class, (compile) -> { + compile.getOptions().setEncoding("UTF-8"); + List args = compile.getOptions().getCompilerArgs(); + if (!args.contains("-parameters")) { + args.add("-parameters"); + } + if (project.hasProperty("toolchainVersion")) { + compile.setSourceCompatibility(SOURCE_AND_TARGET_COMPATIBILITY); + compile.setTargetCompatibility(SOURCE_AND_TARGET_COMPATIBILITY); + } + else if (buildingWithJava17(project)) { + args.addAll(Arrays.asList("-Werror", "-Xlint:unchecked", "-Xlint:deprecation", "-Xlint:rawtypes", + "-Xlint:varargs")); + } + }); + } + + private boolean buildingWithJava17(Project project) { + return !project.hasProperty("toolchainVersion") && JavaVersion.current() == JavaVersion.VERSION_17; + } + + private void configureSpringJavaFormat(Project project) { + project.getPlugins().apply(SpringJavaFormatPlugin.class); + project.getTasks().withType(Format.class, (Format) -> Format.setEncoding("UTF-8")); + project.getPlugins().apply(CheckstylePlugin.class); + CheckstyleExtension checkstyle = project.getExtensions().getByType(CheckstyleExtension.class); + checkstyle.setToolVersion("8.45.1"); + checkstyle.getConfigDirectory().set(project.getRootProject().file("src/checkstyle")); + String version = SpringJavaFormatPlugin.class.getPackage().getImplementationVersion(); + DependencySet checkstyleDependencies = project.getConfigurations().getByName("checkstyle").getDependencies(); + checkstyleDependencies + .add(project.getDependencies().create("io.spring.javaformat:spring-javaformat-checkstyle:" + version)); + } + + private void configureDependencyManagement(Project project) { + ConfigurationContainer configurations = project.getConfigurations(); + Configuration dependencyManagement = configurations.create("dependencyManagement", (configuration) -> { + configuration.setVisible(false); + configuration.setCanBeConsumed(false); + configuration.setCanBeResolved(false); + }); + configurations + .matching((c) -> c.getName().endsWith("Classpath") || c.getName().toLowerCase().endsWith("annotationprocessor")) + .all((c) -> c.extendsFrom(dependencyManagement)); + Dependency springBootParent = project.getDependencies().enforcedPlatform(project.getDependencies() + .project(Collections.singletonMap("path", ":spring-pulsar-dependencies"))); + dependencyManagement.getDependencies().add(springBootParent); + project.getPlugins().withType(OptionalDependenciesPlugin.class, (optionalDependencies) -> configurations + .getByName(OptionalDependenciesPlugin.OPTIONAL_CONFIGURATION_NAME).extendsFrom(dependencyManagement)); + } + + private void configureToolchain(Project project) { + project.getPlugins().apply(ToolchainPlugin.class); + } + + private void configureProhibitedDependencyChecks(Project project) { + SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class); + sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project, + sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName())); + } + + private void createProhibitedDependenciesChecks(Project project, String... configurationNames) { + ConfigurationContainer configurations = project.getConfigurations(); + for (String configurationName : configurationNames) { + Configuration configuration = configurations.getByName(configurationName); + createProhibitedDependenciesCheck(configuration, project); + } + } + + private void createProhibitedDependenciesCheck(Configuration classpath, Project project) { + CheckClasspathForProhibitedDependencies checkClasspathForProhibitedDependencies = project.getTasks().create( + "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies"), + LenientCheckClasspathForProhibitedDependencies.class); + checkClasspathForProhibitedDependencies.setClasspath(classpath); + project.getTasks().getByName(JavaBasePlugin.CHECK_TASK_NAME).dependsOn(checkClasspathForProhibitedDependencies); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/AutoConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/AutoConfigurationMetadata.java new file mode 100644 index 00000000..b674ab69 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/AutoConfigurationMetadata.java @@ -0,0 +1,193 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.autoconfigure; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.Callable; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Task; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.asm.ClassReader; +import org.springframework.asm.Opcodes; +import org.springframework.core.CollectionFactory; +import org.springframework.util.StringUtils; + +/** + * A {@link Task} for generating metadata describing a project's auto-configuration + * classes. + * + * @author Andy Wilkinson + */ +public class AutoConfigurationMetadata extends DefaultTask { + + private static final String COMMENT_START = "#"; + + private SourceSet sourceSet; + + private File outputFile; + + public AutoConfigurationMetadata() { + getInputs() + .file((Callable) () -> new File(this.sourceSet.getOutput().getResourcesDir(), + "META-INF/spring.factories")) + .withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("spring.factories"); + getInputs() + .file((Callable) () -> new File(this.sourceSet.getOutput().getResourcesDir(), + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports")) + .withPathSensitivity(PathSensitivity.RELATIVE) + .withPropertyName("org.springframework.boot.autoconfigure.AutoConfiguration"); + + dependsOn((Callable) () -> this.sourceSet.getProcessResourcesTaskName()); + getProject().getConfigurations() + .maybeCreate(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME); + } + + public void setSourceSet(SourceSet sourceSet) { + this.sourceSet = sourceSet; + } + + @OutputFile + public File getOutputFile() { + return this.outputFile; + } + + public void setOutputFile(File outputFile) { + this.outputFile = outputFile; + } + + @TaskAction + void documentAutoConfiguration() throws IOException { + Properties autoConfiguration = readAutoConfiguration(); + getOutputFile().getParentFile().mkdirs(); + try (FileWriter writer = new FileWriter(getOutputFile())) { + autoConfiguration.store(writer, null); + } + } + + private Properties readAutoConfiguration() throws IOException { + Properties autoConfiguration = CollectionFactory.createSortedProperties(true); + Set classNames = new LinkedHashSet<>(); + classNames.addAll(readSpringFactories()); + classNames.addAll(readAutoConfigurationsFile()); + Set publicClassNames = new LinkedHashSet<>(); + for (String className : classNames) { + File classFile = findClassFile(className); + if (classFile == null) { + throw new IllegalStateException("Auto-configuration class '" + className + "' not found."); + } + try (InputStream in = new FileInputStream(classFile)) { + int access = new ClassReader(in).getAccess(); + if ((access & Opcodes.ACC_PUBLIC) == Opcodes.ACC_PUBLIC) { + publicClassNames.add(className); + } + } + } + autoConfiguration.setProperty("autoConfigurationClassNames", String.join(",", publicClassNames)); + autoConfiguration.setProperty("module", getProject().getName()); + return autoConfiguration; + } + + /** + * Reads auto-configurations from META-INF/spring.factories. + * @return auto-configurations + */ + private Set readSpringFactories() throws IOException { + File file = new File(this.sourceSet.getOutput().getResourcesDir(), "META-INF/spring.factories"); + if (!file.exists()) { + return Collections.emptySet(); + } + Properties springFactories = readSpringFactories(file); + String enableAutoConfiguration = springFactories + .getProperty("org.springframework.boot.autoconfigure.EnableAutoConfiguration"); + return StringUtils.commaDelimitedListToSet(enableAutoConfiguration); + } + + /** + * Reads auto-configurations from + * META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. + * @return auto-configurations + */ + private List readAutoConfigurationsFile() throws IOException { + File file = new File(this.sourceSet.getOutput().getResourcesDir(), + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"); + if (!file.exists()) { + return Collections.emptyList(); + } + // Nearly identical copy of + // org.springframework.boot.context.annotation.ImportCandidates.load + try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { + List autoConfigurations = new ArrayList<>(); + String line; + while ((line = reader.readLine()) != null) { + line = stripComment(line); + line = line.trim(); + if (line.isEmpty()) { + continue; + } + autoConfigurations.add(line); + } + return autoConfigurations; + } + } + + private String stripComment(String line) { + int commentStart = line.indexOf(COMMENT_START); + if (commentStart == -1) { + return line; + } + return line.substring(0, commentStart); + } + + private File findClassFile(String className) { + String classFileName = className.replace(".", "/") + ".class"; + for (File classesDir : this.sourceSet.getOutput().getClassesDirs()) { + File classFile = new File(classesDir, classFileName); + if (classFile.isFile()) { + return classFile; + } + } + return null; + } + + private Properties readSpringFactories(File file) throws IOException { + Properties springFactories = new Properties(); + try (Reader in = new FileReader(file)) { + springFactories.load(in); + } + return springFactories; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/AutoConfigurationPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/AutoConfigurationPlugin.java new file mode 100644 index 00000000..bc667dbe --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/AutoConfigurationPlugin.java @@ -0,0 +1,77 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.autoconfigure; + +import java.io.File; +import java.util.Collections; +import java.util.concurrent.Callable; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; + +import org.springframework.boot.gradle.context.properties.ConfigurationPropertiesPlugin; + +/** + * {@link Plugin} for projects that define auto-configuration. It reacts to the presence of the + * {@link JavaPlugin} by: + * + *

    + *
  • Applying the {@link ConfigurationPropertiesPlugin}. + *
  • Adding a dependency on the auto-configuration annotation processor. + *
  • Defining a task that produces metadata describing the auto-configuration. The + * metadata is made available as an artifact in the + *
+ * + * @author Andy Wilkinson + */ +public class AutoConfigurationPlugin implements Plugin { + + /** + * Name of the {@link Configuration} that holds the auto-configuration metadata + * artifact. + */ + public static final String AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME = "autoConfigurationMetadata"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { + project.getPlugins().apply(ConfigurationPropertiesPlugin.class); + Configuration annotationProcessors = project.getConfigurations() + .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); + annotationProcessors.getDependencies() + .add(project.getDependencies().project(Collections.singletonMap("path", + ":spring-boot-project:spring-boot-tools:spring-boot-autoconfigure-processor"))); + annotationProcessors.getDependencies() + .add(project.getDependencies().project(Collections.singletonMap("path", + ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor"))); + project.getTasks().create("autoConfigurationMetadata", AutoConfigurationMetadata.class, (task) -> { + SourceSet main = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + task.setSourceSet(main); + task.dependsOn(main.getClassesTaskName()); + task.setOutputFile(new File(project.getBuildDir(), "auto-configuration-metadata.properties")); + project.getArtifacts().add(AutoConfigurationPlugin.AUTO_CONFIGURATION_METADATA_CONFIGURATION_NAME, + project.provider((Callable) task::getOutputFile), (artifact) -> artifact.builtBy(task)); + }); + }); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/DocumentAutoConfigurationClasses.java b/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/DocumentAutoConfigurationClasses.java new file mode 100644 index 00000000..cd55c010 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/autoconfigure/DocumentAutoConfigurationClasses.java @@ -0,0 +1,136 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.autoconfigure; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.util.Properties; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.util.StringUtils; + +/** + * {@link Task} used to document auto-configuration classes. + * + * @author Andy Wilkinson + */ +public class DocumentAutoConfigurationClasses extends DefaultTask { + + private FileCollection autoConfiguration; + + private File outputDir; + + @InputFiles + public FileCollection getAutoConfiguration() { + return this.autoConfiguration; + } + + public void setAutoConfiguration(FileCollection autoConfiguration) { + this.autoConfiguration = autoConfiguration; + } + + @OutputDirectory + public File getOutputDir() { + return this.outputDir; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + @TaskAction + void documentAutoConfigurationClasses() throws IOException { + for (File metadataFile : this.autoConfiguration) { + Properties metadata = new Properties(); + try (Reader reader = new FileReader(metadataFile)) { + metadata.load(reader); + } + AutoConfiguration autoConfiguration = new AutoConfiguration(metadata.getProperty("module"), new TreeSet<>( + StringUtils.commaDelimitedListToSet(metadata.getProperty("autoConfigurationClassNames")))); + writeTable(autoConfiguration); + } + } + + private void writeTable(AutoConfiguration autoConfigurationClasses) throws IOException { + this.outputDir.mkdirs(); + try (PrintWriter writer = new PrintWriter( + new FileWriter(new File(this.outputDir, autoConfigurationClasses.module + ".adoc")))) { + writer.println("[cols=\"4,1\"]"); + writer.println("|==="); + writer.println("| Configuration Class | Links"); + + for (AutoConfigurationClass autoConfigurationClass : autoConfigurationClasses.classes) { + writer.println(); + writer.printf("| {spring-boot-code}/spring-boot-project/%s/src/main/java/%s.java[`%s`]%n", + autoConfigurationClasses.module, autoConfigurationClass.path, autoConfigurationClass.name); + writer.printf("| {spring-boot-api}/%s.html[javadoc]%n", autoConfigurationClass.path); + } + + writer.println("|==="); + } + } + + private static final class AutoConfiguration { + + private final String module; + + private final SortedSet classes; + + private AutoConfiguration(String module, Set classNames) { + this.module = module; + this.classes = classNames.stream().map((className) -> { + String path = className.replace('.', '/'); + String name = className.substring(className.lastIndexOf('.') + 1); + return new AutoConfigurationClass(name, path); + }).collect(Collectors.toCollection(TreeSet::new)); + } + + } + + private static final class AutoConfigurationClass implements Comparable { + + private final String name; + + private final String path; + + private AutoConfigurationClass(String name, String path) { + this.name = name; + this.path = path; + } + + @Override + public int compareTo(AutoConfigurationClass other) { + return this.name.compareTo(other.name); + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/classpath/CheckClasspathForProhibitedDependencies.java b/buildSrc/src/main/java/org/springframework/boot/gradle/classpath/CheckClasspathForProhibitedDependencies.java new file mode 100644 index 00000000..398b01c5 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/classpath/CheckClasspathForProhibitedDependencies.java @@ -0,0 +1,110 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.classpath; + +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ModuleVersionIdentifier; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.Classpath; +import org.gradle.api.tasks.TaskAction; + +/** + * A {@link Task} for checking the classpath for prohibited dependencies. + * + * @author Andy Wilkinson + * @author Chris Bono + */ +public class CheckClasspathForProhibitedDependencies extends DefaultTask { + + private Configuration classpath; + + public CheckClasspathForProhibitedDependencies() { + getOutputs().upToDateWhen((task) -> true); + } + + public void setClasspath(Configuration classpath) { + this.classpath = classpath; + } + + @Classpath + public FileCollection getClasspath() { + return this.classpath; + } + + @TaskAction + public void checkForProhibitedDependencies() { + TreeSet prohibited = this.classpath.getResolvedConfiguration().getResolvedArtifacts().stream() + .map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited) + .map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new)); + if (!prohibited.isEmpty()) { + StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies:%n")); + for (String dependency : prohibited) { + message.append(String.format(" %s%n", dependency)); + } + throw new GradleException(message.toString()); + } + } + + private boolean prohibited(ModuleVersionIdentifier id) { + return prohibitedByDefault(id) ? !overrideProhibited(id) : false; + } + + private boolean prohibitedByDefault(ModuleVersionIdentifier id) { + String group = id.getGroup(); + if (group.equals("javax.batch")) { + return false; + } + if (group.equals("javax.cache")) { + return false; + } + if (group.equals("javax.money")) { + return false; + } + if (group.startsWith("javax")) { + return true; + } + if (group.equals("org.codehaus.groovy")) { + return true; + } + if (group.equals("org.eclipse.jetty.toolchain")) { + return true; + } + if (group.equals("commons-logging")) { + return true; + } + if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) { + return true; + } + if (group.startsWith("org.jboss.spec")) { + return true; + } + if (group.equals("org.apache.geronimo.specs")) { + return true; + } + return false; + } + + protected boolean overrideProhibited(ModuleVersionIdentifier id) { + return false; + } +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/classpath/artifactory/ArtifactoryRepository.java b/buildSrc/src/main/java/org/springframework/boot/gradle/classpath/artifactory/ArtifactoryRepository.java new file mode 100644 index 00000000..5d18c3dd --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/classpath/artifactory/ArtifactoryRepository.java @@ -0,0 +1,60 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.classpath.artifactory; + +import org.gradle.api.Project; + +/** + * An Artifactory repository to which a build of Spring Boot can be published. + * + * @author Andy Wilkinson + */ +public final class ArtifactoryRepository { + + private final String name; + + private ArtifactoryRepository(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + @Override + public String toString() { + return this.name; + } + + public static ArtifactoryRepository forProject(Project project) { + return new ArtifactoryRepository(determineArtifactoryRepo(project)); + } + + private static String determineArtifactoryRepo(Project project) { + String version = project.getVersion().toString(); + int modifierIndex = version.lastIndexOf('-'); + if (modifierIndex == -1) { + return "release"; + } + String type = version.substring(modifierIndex + 1); + if (type.startsWith("M") || type.startsWith("RC")) { + return "milestone"; + } + return "snapshot"; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Asciidoc.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Asciidoc.java similarity index 95% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Asciidoc.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Asciidoc.java index 4c349914..d0f86323 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Asciidoc.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Asciidoc.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; /** * Simple builder to help construct Asciidoc markup. diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/CheckAdditionalSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/CheckAdditionalSpringConfigurationMetadata.java similarity index 98% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/CheckAdditionalSpringConfigurationMetadata.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/CheckAdditionalSpringConfigurationMetadata.java index 032e1e17..f9fc5044 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/CheckAdditionalSpringConfigurationMetadata.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/CheckAdditionalSpringConfigurationMetadata.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.io.File; import java.io.IOException; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/CompoundRow.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/CompoundRow.java similarity index 96% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/CompoundRow.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/CompoundRow.java index fbf92334..47fa8dbc 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/CompoundRow.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/CompoundRow.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.util.Set; import java.util.TreeSet; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationProperties.java similarity index 97% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationProperties.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationProperties.java index 09507f94..5e4945cc 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.io.File; import java.io.IOException; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationPropertiesPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationPropertiesPlugin.java similarity index 91% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationPropertiesPlugin.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationPropertiesPlugin.java index 78cda624..b8489d42 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationPropertiesPlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationPropertiesPlugin.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; +import java.util.Collections; import java.util.stream.Collectors; import org.gradle.api.Plugin; @@ -66,19 +67,19 @@ public class ConfigurationPropertiesPlugin implements Plugin { @Override public void apply(Project project) { project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { - // TODO addConfigurationProcessorDependency(project); + addConfigurationProcessorDependency(project); configureAdditionalMetadataLocationsCompilerArgument(project); registerCheckAdditionalMetadataTask(project); addMetadataArtifact(project); }); } - // private void addConfigurationProcessorDependency(Project project) { - // Configuration annotationProcessors = project.getConfigurations() - // .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); - // annotationProcessors.getDependencies().add(project.getDependencies().project(Collections.singletonMap("path", - // ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor"))); - // } + private void addConfigurationProcessorDependency(Project project) { + Configuration annotationProcessors = project.getConfigurations() + .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); + annotationProcessors.getDependencies().add(project.getDependencies().project(Collections.singletonMap("path", + ":spring-boot-project:spring-boot-tools:spring-boot-configuration-processor"))); + } private void addMetadataArtifact(Project project) { SourceSet mainSourceSet = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets() diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationProperty.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationProperty.java similarity index 97% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationProperty.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationProperty.java index 5ae832a1..44ab1426 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/ConfigurationProperty.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/ConfigurationProperty.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.util.Map; diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/DocumentConfigurationProperties.java new file mode 100644 index 00000000..410e35e6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/DocumentConfigurationProperties.java @@ -0,0 +1,220 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.context.properties; + +import java.io.File; +import java.io.IOException; + +import org.gradle.api.DefaultTask; +import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.TaskAction; + +import org.springframework.boot.gradle.context.properties.Snippet.Config; + +/** + * {@link Task} used to document auto-configuration classes. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +public class DocumentConfigurationProperties extends DefaultTask { + + private FileCollection configurationPropertyMetadata; + + private File outputDir; + + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public FileCollection getConfigurationPropertyMetadata() { + return this.configurationPropertyMetadata; + } + + public void setConfigurationPropertyMetadata(FileCollection configurationPropertyMetadata) { + this.configurationPropertyMetadata = configurationPropertyMetadata; + } + + @OutputDirectory + public File getOutputDir() { + return this.outputDir; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + @TaskAction + void documentConfigurationProperties() throws IOException { + Snippets snippets = new Snippets(this.configurationPropertyMetadata); + snippets.add("application-properties.core", "Core Properties", this::corePrefixes); + snippets.add("application-properties.cache", "Cache Properties", this::cachePrefixes); + snippets.add("application-properties.mail", "Mail Properties", this::mailPrefixes); + snippets.add("application-properties.json", "JSON Properties", this::jsonPrefixes); + snippets.add("application-properties.data", "Data Properties", this::dataPrefixes); + snippets.add("application-properties.transaction", "Transaction Properties", this::transactionPrefixes); + snippets.add("application-properties.data-migration", "Data Migration Properties", this::dataMigrationPrefixes); + snippets.add("application-properties.integration", "Integration Properties", this::integrationPrefixes); + snippets.add("application-properties.web", "Web Properties", this::webPrefixes); + snippets.add("application-properties.templating", "Templating Properties", this::templatePrefixes); + snippets.add("application-properties.server", "Server Properties", this::serverPrefixes); + snippets.add("application-properties.security", "Security Properties", this::securityPrefixes); + snippets.add("application-properties.rsocket", "RSocket Properties", this::rsocketPrefixes); + snippets.add("application-properties.actuator", "Actuator Properties", this::actuatorPrefixes); + snippets.add("application-properties.devtools", "Devtools Properties", this::devtoolsPrefixes); + snippets.add("application-properties.testing", "Testing Properties", this::testingPrefixes); + snippets.writeTo(this.outputDir.toPath()); + } + + private void corePrefixes(Config config) { + config.accept("debug"); + config.accept("trace"); + config.accept("logging"); + config.accept("spring.aop"); + config.accept("spring.application"); + config.accept("spring.autoconfigure"); + config.accept("spring.banner"); + config.accept("spring.beaninfo"); + config.accept("spring.codec"); + config.accept("spring.config"); + config.accept("spring.info"); + config.accept("spring.jmx"); + config.accept("spring.lifecycle"); + config.accept("spring.main"); + config.accept("spring.messages"); + config.accept("spring.pid"); + config.accept("spring.profiles"); + config.accept("spring.quartz"); + config.accept("spring.reactor"); + config.accept("spring.task"); + config.accept("spring.mandatory-file-encoding"); + config.accept("info"); + config.accept("spring.output.ansi.enabled"); + } + + private void cachePrefixes(Config config) { + config.accept("spring.cache"); + } + + private void mailPrefixes(Config config) { + config.accept("spring.mail"); + config.accept("spring.sendgrid"); + } + + private void jsonPrefixes(Config config) { + config.accept("spring.jackson"); + config.accept("spring.gson"); + } + + private void dataPrefixes(Config config) { + config.accept("spring.couchbase"); + config.accept("spring.elasticsearch"); + config.accept("spring.h2"); + config.accept("spring.influx"); + config.accept("spring.ldap"); + config.accept("spring.mongodb"); + config.accept("spring.neo4j"); + config.accept("spring.redis"); + config.accept("spring.dao"); + config.accept("spring.data"); + config.accept("spring.datasource"); + config.accept("spring.jooq"); + config.accept("spring.jdbc"); + config.accept("spring.jpa"); + config.accept("spring.r2dbc"); + config.accept("spring.datasource.oracleucp", + "Oracle UCP specific settings bound to an instance of Oracle UCP's PoolDataSource"); + config.accept("spring.datasource.dbcp2", + "Commons DBCP2 specific settings bound to an instance of DBCP2's BasicDataSource"); + config.accept("spring.datasource.tomcat", + "Tomcat datasource specific settings bound to an instance of Tomcat JDBC's DataSource"); + config.accept("spring.datasource.hikari", + "Hikari specific settings bound to an instance of Hikari's HikariDataSource"); + + } + + private void transactionPrefixes(Config prefix) { + prefix.accept("spring.jta"); + prefix.accept("spring.transaction"); + } + + private void dataMigrationPrefixes(Config prefix) { + prefix.accept("spring.flyway"); + prefix.accept("spring.liquibase"); + prefix.accept("spring.sql.init"); + } + + private void integrationPrefixes(Config prefix) { + prefix.accept("spring.activemq"); + prefix.accept("spring.artemis"); + prefix.accept("spring.batch"); + prefix.accept("spring.integration"); + prefix.accept("spring.jms"); + prefix.accept("spring.kafka"); + prefix.accept("spring.rabbitmq"); + prefix.accept("spring.hazelcast"); + prefix.accept("spring.webservices"); + } + + private void webPrefixes(Config prefix) { + prefix.accept("spring.hateoas"); + prefix.accept("spring.http"); + prefix.accept("spring.servlet"); + prefix.accept("spring.mvc"); + prefix.accept("spring.netty"); + prefix.accept("spring.resources"); + prefix.accept("spring.session"); + prefix.accept("spring.web"); + prefix.accept("spring.webflux"); + } + + private void templatePrefixes(Config prefix) { + prefix.accept("spring.freemarker"); + prefix.accept("spring.groovy"); + prefix.accept("spring.mustache"); + prefix.accept("spring.thymeleaf"); + prefix.accept("spring.groovy.template.configuration", "See GroovyMarkupConfigurer"); + } + + private void serverPrefixes(Config prefix) { + prefix.accept("server"); + } + + private void securityPrefixes(Config prefix) { + prefix.accept("spring.security"); + } + + private void rsocketPrefixes(Config prefix) { + prefix.accept("spring.rsocket"); + } + + private void actuatorPrefixes(Config prefix) { + prefix.accept("management"); + } + + private void devtoolsPrefixes(Config prefix) { + prefix.accept("spring.devtools"); + } + + private void testingPrefixes(Config prefix) { + prefix.accept("spring.test"); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Row.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Row.java similarity index 95% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Row.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Row.java index dd39d4a7..2b5fbf8a 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Row.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Row.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; /** * Abstract class for rows in {@link Table}. diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/SingleRow.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/SingleRow.java similarity index 97% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/SingleRow.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/SingleRow.java index b3bff452..1de34c37 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/SingleRow.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/SingleRow.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.util.Arrays; import java.util.stream.Collectors; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Snippet.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Snippet.java similarity index 97% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Snippet.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Snippet.java index a4fce85e..b4a2877f 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Snippet.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Snippet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.util.LinkedHashMap; import java.util.LinkedHashSet; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Snippets.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Snippets.java similarity index 98% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Snippets.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Snippets.java index 3594d328..23885d98 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Snippets.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Snippets.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.io.IOException; import java.io.OutputStream; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Table.java b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Table.java similarity index 95% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Table.java rename to buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Table.java index 434b08c5..dddb8ac1 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/Table.java +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/context/properties/Table.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.boot.gradle.context.properties; import java.util.Set; import java.util.TreeSet; diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/optional/OptionalDependenciesPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/optional/OptionalDependenciesPlugin.java new file mode 100644 index 00000000..4b7d938e --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/optional/OptionalDependenciesPlugin.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.optional; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSetContainer; + +/** + * A {@code Plugin} that adds support for Maven-style optional dependencies. Creates a new + * {@code optional} configuration. The {@code optional} configuration is part of the + * project's compile and runtime classpaths but does not affect the classpath of dependent + * projects. + * + * @author Andy Wilkinson + */ +public class OptionalDependenciesPlugin implements Plugin { + + /** + * Name of the {@code optional} configuration. + */ + public static final String OPTIONAL_CONFIGURATION_NAME = "optional"; + + @Override + public void apply(Project project) { + Configuration optional = project.getConfigurations().create("optional"); + optional.setCanBeConsumed(false); + optional.setCanBeResolved(false); + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { + SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class) + .getSourceSets(); + sourceSets.all((sourceSet) -> { + project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()) + .extendsFrom(optional); + project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()) + .extendsFrom(optional); + }); + }); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/test/IntegrationTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/test/IntegrationTestPlugin.java new file mode 100644 index 00000000..59c06aa8 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/test/IntegrationTestPlugin.java @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.test; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.testing.Test; +import org.gradle.language.base.plugins.LifecycleBasePlugin; +import org.gradle.plugins.ide.eclipse.EclipsePlugin; +import org.gradle.plugins.ide.eclipse.model.EclipseModel; + +/** + * A {@link Plugin} to configure integration testing support in a {@link Project}. + * + * @author Andy Wilkinson + */ +public class IntegrationTestPlugin implements Plugin { + + /** + * Name of the {@code intTest} task. + */ + public static String INT_TEST_TASK_NAME = "intTest"; + + /** + * Name of the {@code intTest} source set. + */ + public static String INT_TEST_SOURCE_SET_NAME = "intTest"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureIntegrationTesting(project)); + } + + private void configureIntegrationTesting(Project project) { + SourceSet intTestSourceSet = createSourceSet(project); + Test intTest = createTestTask(project, intTestSourceSet); + project.getTasks().getByName(LifecycleBasePlugin.CHECK_TASK_NAME).dependsOn(intTest); + project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { + EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); + eclipse.classpath((classpath) -> classpath.getPlusConfigurations().add( + project.getConfigurations().getByName(intTestSourceSet.getRuntimeClasspathConfigurationName()))); + }); + } + + private SourceSet createSourceSet(Project project) { + SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); + SourceSet intTestSourceSet = sourceSets.create(INT_TEST_SOURCE_SET_NAME); + SourceSet main = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + intTestSourceSet.setCompileClasspath(intTestSourceSet.getCompileClasspath().plus(main.getOutput())); + intTestSourceSet.setRuntimeClasspath(intTestSourceSet.getRuntimeClasspath().plus(main.getOutput())); + return intTestSourceSet; + } + + private Test createTestTask(Project project, SourceSet intTestSourceSet) { + Test intTest = project.getTasks().create(INT_TEST_TASK_NAME, Test.class); + intTest.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + intTest.setDescription("Runs integration tests."); + intTest.setTestClassesDirs(intTestSourceSet.getOutput().getClassesDirs()); + intTest.setClasspath(intTestSourceSet.getRuntimeClasspath()); + intTest.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); + return intTest; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/test/SystemTestPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/test/SystemTestPlugin.java new file mode 100644 index 00000000..4c1e9718 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/test/SystemTestPlugin.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.test; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.specs.Spec; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.testing.Test; +import org.gradle.language.base.plugins.LifecycleBasePlugin; +import org.gradle.plugins.ide.eclipse.EclipsePlugin; +import org.gradle.plugins.ide.eclipse.model.EclipseModel; + +/** + * A {@link Plugin} to configure system testing support in a {@link Project}. + * + * @author Andy Wilkinson + * @author Scott Frederick + */ +public class SystemTestPlugin implements Plugin { + + private static final Spec NEVER = (task) -> false; + + /** + * Name of the {@code systemTest} task. + */ + public static String SYSTEM_TEST_TASK_NAME = "systemTest"; + + /** + * Name of the {@code systemTest} source set. + */ + public static String SYSTEM_TEST_SOURCE_SET_NAME = "systemTest"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> configureSystemTesting(project)); + } + + private void configureSystemTesting(Project project) { + SourceSet systemTestSourceSet = createSourceSet(project); + createTestTask(project, systemTestSourceSet); + project.getPlugins().withType(EclipsePlugin.class, (eclipsePlugin) -> { + EclipseModel eclipse = project.getExtensions().getByType(EclipseModel.class); + eclipse.classpath((classpath) -> classpath.getPlusConfigurations().add( + project.getConfigurations().getByName(systemTestSourceSet.getRuntimeClasspathConfigurationName()))); + }); + } + + private SourceSet createSourceSet(Project project) { + SourceSetContainer sourceSets = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets(); + SourceSet systemTestSourceSet = sourceSets.create(SYSTEM_TEST_SOURCE_SET_NAME); + SourceSet mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME); + systemTestSourceSet + .setCompileClasspath(systemTestSourceSet.getCompileClasspath().plus(mainSourceSet.getOutput())); + systemTestSourceSet + .setRuntimeClasspath(systemTestSourceSet.getRuntimeClasspath().plus(mainSourceSet.getOutput())); + return systemTestSourceSet; + } + + private void createTestTask(Project project, SourceSet systemTestSourceSet) { + Test systemTest = project.getTasks().create(SYSTEM_TEST_TASK_NAME, Test.class); + systemTest.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + systemTest.setDescription("Runs system tests."); + systemTest.setTestClassesDirs(systemTestSourceSet.getOutput().getClassesDirs()); + systemTest.setClasspath(systemTestSourceSet.getRuntimeClasspath()); + systemTest.shouldRunAfter(JavaPlugin.TEST_TASK_NAME); + if (isCi()) { + systemTest.getOutputs().upToDateWhen(NEVER); + } + } + + private boolean isCi() { + return Boolean.parseBoolean(System.getenv("CI")); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/testing/TestFailuresPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/testing/TestFailuresPlugin.java new file mode 100644 index 00000000..8fa045e8 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/testing/TestFailuresPlugin.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.testing; + +import java.util.ArrayList; +import java.util.List; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.testing.Test; +import org.gradle.api.tasks.testing.TestDescriptor; +import org.gradle.api.tasks.testing.TestListener; +import org.gradle.api.tasks.testing.TestResult; + +/** + * Plugin for recording test failures and reporting them at the end of the build. + * + * @author Andy Wilkinson + */ +public class TestFailuresPlugin implements Plugin { + + @Override + public void apply(Project project) { + Provider testResultsOverview = project.getGradle().getSharedServices() + .registerIfAbsent("testResultsOverview", TestResultsOverview.class, (spec) -> { + }); + project.getTasks().withType(Test.class, + (test) -> test.addTestListener(new FailureRecordingTestListener(testResultsOverview, test))); + } + + private final class FailureRecordingTestListener implements TestListener { + + private final List failures = new ArrayList<>(); + + private final Provider testResultsOverview; + + private final Test test; + + private FailureRecordingTestListener(Provider testResultOverview, Test test) { + this.testResultsOverview = testResultOverview; + this.test = test; + } + + @Override + public void afterSuite(TestDescriptor descriptor, TestResult result) { + if (!this.failures.isEmpty()) { + this.testResultsOverview.get().addFailures(this.test, this.failures); + } + } + + @Override + public void afterTest(TestDescriptor descriptor, TestResult result) { + if (result.getFailedTestCount() > 0) { + this.failures.add(descriptor); + } + } + + @Override + public void beforeSuite(TestDescriptor descriptor) { + + } + + @Override + public void beforeTest(TestDescriptor descriptor) { + + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/testing/TestResultsOverview.java b/buildSrc/src/main/java/org/springframework/boot/gradle/testing/TestResultsOverview.java new file mode 100644 index 00000000..a15796f1 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/testing/TestResultsOverview.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.testing; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; +import org.gradle.api.tasks.testing.Test; +import org.gradle.api.tasks.testing.TestDescriptor; +import org.gradle.tooling.events.FinishEvent; +import org.gradle.tooling.events.OperationCompletionListener; + +/** + * {@link BuildService} that provides an overview of all the test failures in the build. + * + * @author Andy Wilkinson + */ +public abstract class TestResultsOverview + implements BuildService, OperationCompletionListener, AutoCloseable { + + private final Map> testFailures = new TreeMap<>( + (one, two) -> one.getPath().compareTo(two.getPath())); + + private final Object monitor = new Object(); + + void addFailures(Test test, List failureDescriptors) { + List testFailures = failureDescriptors.stream().map(TestFailure::new).sorted() + .collect(Collectors.toList()); + synchronized (this.monitor) { + this.testFailures.put(test, testFailures); + } + } + + @Override + public void onFinish(FinishEvent event) { + // OperationCompletionListener is implemented to defer close until the build ends + } + + @Override + public void close() { + synchronized (this.monitor) { + if (this.testFailures.isEmpty()) { + return; + } + System.err.println(); + System.err.println("Found test failures in " + this.testFailures.size() + " test task" + + ((this.testFailures.size() == 1) ? ":" : "s:")); + this.testFailures.forEach((task, failures) -> { + System.err.println(); + System.err.println(task.getPath()); + failures.forEach((failure) -> System.err + .println(" " + failure.descriptor.getClassName() + " > " + failure.descriptor.getName())); + }); + } + } + + private static final class TestFailure implements Comparable { + + private final TestDescriptor descriptor; + + private TestFailure(TestDescriptor descriptor) { + this.descriptor = descriptor; + } + + @Override + public int compareTo(TestFailure other) { + int comparison = this.descriptor.getClassName().compareTo(other.descriptor.getClassName()); + if (comparison == 0) { + comparison = this.descriptor.getName().compareTo(other.descriptor.getName()); + } + return comparison; + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/toolchain/ToolchainExtension.java b/buildSrc/src/main/java/org/springframework/boot/gradle/toolchain/ToolchainExtension.java new file mode 100644 index 00000000..b90b9591 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/toolchain/ToolchainExtension.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.toolchain; + +import org.gradle.api.Project; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.Property; +import org.gradle.jvm.toolchain.JavaLanguageVersion; + +/** + * DSL extension for {@link ToolchainPlugin}. + * + * @author Christoph Dreis + */ +public class ToolchainExtension { + + private final Property maximumCompatibleJavaVersion; + + private final ListProperty testJvmArgs; + + private final JavaLanguageVersion javaVersion; + + public ToolchainExtension(Project project) { + this.maximumCompatibleJavaVersion = project.getObjects().property(JavaLanguageVersion.class); + this.testJvmArgs = project.getObjects().listProperty(String.class); + String toolchainVersion = (String) project.findProperty("toolchainVersion"); + this.javaVersion = (toolchainVersion != null) ? JavaLanguageVersion.of(toolchainVersion) : null; + } + + public Property getMaximumCompatibleJavaVersion() { + return this.maximumCompatibleJavaVersion; + } + + public ListProperty getTestJvmArgs() { + return this.testJvmArgs; + } + + JavaLanguageVersion getJavaVersion() { + return this.javaVersion; + } + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/gradle/toolchain/ToolchainPlugin.java b/buildSrc/src/main/java/org/springframework/boot/gradle/toolchain/ToolchainPlugin.java new file mode 100644 index 00000000..169da5af --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/gradle/toolchain/ToolchainPlugin.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.boot.gradle.toolchain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.javadoc.Javadoc; +import org.gradle.api.tasks.testing.Test; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.jvm.toolchain.JavaToolchainSpec; + +/** + * {@link Plugin} for customizing Gradle's toolchain support. + * + * @author Christoph Dreis + */ +public class ToolchainPlugin implements Plugin { + + @Override + public void apply(Project project) { + configureToolchain(project); + } + + private void configureToolchain(Project project) { + ToolchainExtension toolchain = project.getExtensions().create("toolchain", ToolchainExtension.class, project); + JavaLanguageVersion toolchainVersion = toolchain.getJavaVersion(); + if (toolchainVersion != null) { + project.afterEvaluate((evaluated) -> configure(evaluated, toolchain)); + } + } + + private void configure(Project project, ToolchainExtension toolchain) { + if (!isJavaVersionSupported(toolchain, toolchain.getJavaVersion())) { + disableToolchainTasks(project); + } + else { + JavaToolchainSpec toolchainSpec = project.getExtensions().getByType(JavaPluginExtension.class) + .getToolchain(); + toolchainSpec.getLanguageVersion().set(toolchain.getJavaVersion()); + configureTestToolchain(project, toolchain); + } + } + + private boolean isJavaVersionSupported(ToolchainExtension toolchain, JavaLanguageVersion toolchainVersion) { + return toolchain.getMaximumCompatibleJavaVersion().map((version) -> version.canCompileOrRun(toolchainVersion)) + .getOrElse(true); + } + + private void disableToolchainTasks(Project project) { + project.getTasks().withType(JavaCompile.class, (task) -> task.setEnabled(false)); + project.getTasks().withType(Javadoc.class, (task) -> task.setEnabled(false)); + project.getTasks().withType(Test.class, (task) -> task.setEnabled(false)); + } + + private void configureTestToolchain(Project project, ToolchainExtension toolchain) { + List jvmArgs = new ArrayList<>(); + jvmArgs.addAll(toolchain.getTestJvmArgs().getOrElse(Collections.emptyList())); + project.getTasks().withType(Test.class, (test) -> test.jvmArgs(jvmArgs)); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/ConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/build/ConventionsPlugin.java deleted file mode 100644 index 77b24ec2..00000000 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/ConventionsPlugin.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2012-2021 the original author or authors. - * - * Licensed 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 - * - * https://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. - */ - -package org.springframework.pulsar.build; - -import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; -import org.gradle.api.Plugin; -import org.gradle.api.Project; - -import org.springframework.pulsar.build.docs.asciidoc.AsciidoctorConventions; - -/** - * Plugin to apply conventions to projects that are part of Spring Pulsar's build. - * Conventions are applied in response to various plugins being applied. - * - * When the {@link AsciidoctorJPlugin} is applied, the conventions in - * {@link AsciidoctorConventions} are applied. - * - * @author Chris Bono - */ -public class ConventionsPlugin implements Plugin { - - @Override - public void apply(Project project) { - new AsciidoctorConventions().apply(project); - } - -} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/JacocoConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/JacocoConventionsPlugin.java new file mode 100644 index 00000000..91db2844 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/JacocoConventionsPlugin.java @@ -0,0 +1,33 @@ +package org.springframework.pulsar.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.tasks.testing.Test; +import org.gradle.testing.jacoco.plugins.JacocoPlugin; +import org.gradle.testing.jacoco.plugins.JacocoPluginExtension; +import org.gradle.testing.jacoco.tasks.JacocoReport; + +/** + * Adds a version of jacoco to use and makes check depend on jacocoTestReport. + * + * @author Chris Bono + */ +public class JacocoConventionsPlugin implements Plugin { + + @Override + public void apply(final Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { + project.getPluginManager().apply(JacocoPlugin.class); + project.getExtensions().configure(JacocoPluginExtension.class, (jacocoExtension) -> { + jacocoExtension.setToolVersion("0.8.7"); + }); + project.getTasks().withType(Test.class, test -> { + project.getTasks().withType(JacocoReport.class, jacocoReport -> { + test.finalizedBy(jacocoReport); + jacocoReport.dependsOn(test); + }); + }); + }); + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/ProjectLinks.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/ProjectLinks.java new file mode 100644 index 00000000..a9f41061 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/ProjectLinks.java @@ -0,0 +1,21 @@ +package org.springframework.pulsar.gradle; + +public enum ProjectLinks { + + HOMEPAGE("https://github.com/spring-projects-experimental/spring-pulsar"), + ISSUES("https://github.com/spring-projects-experimental/spring-pulsar/issues"), + CI("https://github.com/spring-projects-experimental/spring-pulsar/actions"), + SCM_URL("https://github.com/spring-projects-experimental/spring-pulsar"), + SCM_CONNECTION("https://github.com/spring-projects-experimental/spring-pulsar.git"), + SCM_DEV_CONNECTION("git@github.com:spring-projects-experimental/spring-pulsar.git"); + + private final String link; + + ProjectLinks(String link) { + this.link = link; + } + + public String link() { + return this.link; + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/ProjectUtils.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/ProjectUtils.java new file mode 100644 index 00000000..71c27b88 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/ProjectUtils.java @@ -0,0 +1,26 @@ +package org.springframework.pulsar.gradle; + +import org.gradle.api.Project; + +public final class ProjectUtils { + + private ProjectUtils() { + } + + public static boolean isSnapshot(Project project) { + return projectVersion(project).endsWith("-SNAPSHOT"); + } + + public static boolean isMilestone(Project project) { + String projectVersion = projectVersion(project); + return projectVersion.matches("^.*[.-]M\\d+$") || projectVersion.matches("^.*[.-]RC\\d+$"); + } + + public static boolean isRelease(Project project) { + return !(isSnapshot(project) || isMilestone(project)); + } + + private static String projectVersion(Project project) { + return String.valueOf(project.getVersion()); + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/RootProjectPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/RootProjectPlugin.java new file mode 100644 index 00000000..e9338478 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/RootProjectPlugin.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.BasePlugin; +import org.gradle.api.plugins.PluginManager; + +import org.springframework.pulsar.gradle.publish.SpringNexusPublishPlugin; + +/** + * @author Chris Bono + */ +public class RootProjectPlugin implements Plugin { + + @Override + public void apply(final Project project) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(BasePlugin.class); + pluginManager.apply(SpringNexusPublishPlugin.class); + project.getRepositories().mavenCentral(); + pluginManager.apply(SonarQubeConventionsPlugin.class); + + Task finalizeDeployArtifacts = project.task("finalizeDeployArtifacts"); + if (ProjectUtils.isRelease(project) && project.hasProperty("ossrhUsername")) { + finalizeDeployArtifacts.dependsOn(project.getTasks().findByName("closeAndReleaseOssrhStagingRepository")); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/SonarQubeConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/SonarQubeConventionsPlugin.java new file mode 100644 index 00000000..2da51f3c --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/SonarQubeConventionsPlugin.java @@ -0,0 +1,29 @@ +package org.springframework.pulsar.gradle; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.sonarqube.gradle.SonarQubeExtension; +import org.sonarqube.gradle.SonarQubePlugin; + +/** + * Adds a version of SonarQube to use and configures it. + * + * @author Chris Bono + */ +public class SonarQubeConventionsPlugin implements Plugin { + + @Override + public void apply(final Project project) { + project.getPluginManager().apply(SonarQubePlugin.class); + project.getExtensions().configure(SonarQubeExtension.class, (sonarExtension) -> + sonarExtension.properties((sonarProperties) -> { + sonarProperties.property("sonar.projectName", project.getName()); + sonarProperties.property("sonar.jacoco.reportPath", project.getBuildDir().getName() + "/jacoco.exec"); + sonarProperties.property("sonar.links.homepage", ProjectLinks.HOMEPAGE.link()); + sonarProperties.property("sonar.links.ci", ProjectLinks.CI.link()); + sonarProperties.property("sonar.links.issue", ProjectLinks.ISSUES.link()); + sonarProperties.property("sonar.links.scm", ProjectLinks.SCM_CONNECTION.link()); + sonarProperties.property("sonar.links.scm_dev", ProjectLinks.SCM_DEV_CONNECTION.link()); + })); + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/SpringDocsModulePlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/SpringDocsModulePlugin.java new file mode 100644 index 00000000..c6a21a4a --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/SpringDocsModulePlugin.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle; + +import io.spring.gradle.convention.RepositoryConventionPlugin; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.plugins.JavaLibraryPlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.PluginManager; + +import org.springframework.boot.gradle.JavaConventionsPlugin; +import org.springframework.boot.gradle.optional.OptionalDependenciesPlugin; +import org.springframework.pulsar.gradle.docs.asciidoc.AsciidoctorConventionsPlugin; +import org.springframework.pulsar.gradle.publish.SpringPublishPlugin; + +/** + * @author Rob Winch + * @author Chris Bono + */ +public class SpringDocsModulePlugin implements Plugin { + + @Override + public void apply(final Project project) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(JavaPlugin.class); + pluginManager.apply(RepositoryConventionPlugin.class); + pluginManager.apply(JavaLibraryPlugin.class); + pluginManager.apply(JavaConventionsPlugin.class); + pluginManager.apply(AsciidoctorConventionsPlugin.class); + pluginManager.apply(SpringPublishPlugin.class); + pluginManager.apply(OptionalDependenciesPlugin.class); + + Task deployArtifacts = project.task("deployArtifacts"); + deployArtifacts.setGroup("Deploy tasks"); + deployArtifacts.setDescription("Deploys the artifacts to either Artifactory or Maven Central"); + if (!ProjectUtils.isRelease(project)) { + deployArtifacts.dependsOn(project.getTasks().getByName("artifactoryPublish")); + } + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/SpringModulePlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/SpringModulePlugin.java new file mode 100644 index 00000000..563e634f --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/SpringModulePlugin.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle; + +import org.gradle.api.Project; +import org.gradle.api.plugins.PluginManager; + +import org.springframework.pulsar.gradle.publish.PublishAllJavaComponentsPlugin; + +/** + * @author Chris Bono + */ +public class SpringModulePlugin extends SpringDocsModulePlugin { + + @Override + public void apply(final Project project) { + super.apply(project); + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(PublishAllJavaComponentsPlugin.class); + pluginManager.apply(JacocoConventionsPlugin.class); + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/classpath/LenientCheckClasspathForProhibitedDependencies.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/classpath/LenientCheckClasspathForProhibitedDependencies.java new file mode 100644 index 00000000..9be26042 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/classpath/LenientCheckClasspathForProhibitedDependencies.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.classpath; + +import java.util.Set; + +import org.gradle.api.Task; +import org.gradle.api.artifacts.ModuleVersionIdentifier; + +import org.springframework.boot.gradle.classpath.CheckClasspathForProhibitedDependencies; + +/** + * Extends the Spring Boot {@link Task} for checking the classpath for prohibited dependencies in a more lenient fashion + * and allows the PulsarClient to bring in some of the {@code javax.*} dependencies. + * + * @author Chris Bono + */ +public class LenientCheckClasspathForProhibitedDependencies extends CheckClasspathForProhibitedDependencies { + + private static Set OVERRIDE_PROHIBITED_DEPENDENCIES = Set.of( + "javax.validation:validation-api", + "javax.ws.rs:javax.ws.rs-api", + "javax.xml.bind:jaxb-api", + "commons-logging:commons-logging"); + + @Override + protected boolean overrideProhibited(ModuleVersionIdentifier id) { + return OVERRIDE_PROHIBITED_DEPENDENCIES.contains(id.getGroup() + ":" + id.getName()); + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/asciidoc/AsciidoctorConventions.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/asciidoc/AsciidoctorConventionsPlugin.java similarity index 97% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/asciidoc/AsciidoctorConventions.java rename to buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/asciidoc/AsciidoctorConventionsPlugin.java index 2029ed11..f1f75ec1 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/asciidoc/AsciidoctorConventions.java +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/asciidoc/AsciidoctorConventionsPlugin.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.asciidoc; +package org.springframework.pulsar.gradle.docs.asciidoc; import java.io.File; import java.net.URI; @@ -26,6 +26,7 @@ import org.asciidoctor.gradle.jvm.AbstractAsciidoctorTask; import org.asciidoctor.gradle.jvm.AsciidoctorJExtension; import org.asciidoctor.gradle.jvm.AsciidoctorJPlugin; import org.asciidoctor.gradle.jvm.AsciidoctorTask; +import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.Sync; @@ -66,12 +67,13 @@ import org.springframework.util.StringUtils; * * @author Andy Wilkinson */ -public class AsciidoctorConventions { +public class AsciidoctorConventionsPlugin implements Plugin { private static final String ASCIIDOCTORJ_VERSION = "2.4.3"; private static final String EXTENSIONS_CONFIGURATION_NAME = "asciidoctorExtensions"; + @Override public void apply(Project project) { project.getPlugins().withType(AsciidoctorJPlugin.class, (asciidoctorPlugin) -> { configureDocumentationDependenciesRepository(project); diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Asciidoc.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Asciidoc.java new file mode 100644 index 00000000..581c8f8a --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Asciidoc.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +/** + * Simple builder to help construct Asciidoc markup. + * + * @author Phillip Webb + */ +class Asciidoc { + + private final StringBuilder content; + + Asciidoc() { + this.content = new StringBuilder(); + } + + Asciidoc appendWithHardLineBreaks(Object... items) { + for (Object item : items) { + appendln("`+", item, "+` +"); + } + return this; + } + + Asciidoc appendln(Object... items) { + return append(items).newLine(); + } + + Asciidoc append(Object... items) { + for (Object item : items) { + this.content.append(item); + } + return this; + } + + Asciidoc newLine() { + return append(System.lineSeparator()); + } + + @Override + public String toString() { + return this.content.toString(); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/CheckAdditionalSpringConfigurationMetadata.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/CheckAdditionalSpringConfigurationMetadata.java new file mode 100644 index 00000000..191ce382 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/CheckAdditionalSpringConfigurationMetadata.java @@ -0,0 +1,166 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.gradle.api.GradleException; +import org.gradle.api.file.FileTree; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SourceTask; +import org.gradle.api.tasks.TaskAction; + +/** + * {@link SourceTask} that checks additional Spring configuration metadata files. + * + * @author Andy Wilkinson + */ +public class CheckAdditionalSpringConfigurationMetadata extends SourceTask { + + private final RegularFileProperty reportLocation; + + public CheckAdditionalSpringConfigurationMetadata() { + this.reportLocation = getProject().getObjects().fileProperty(); + } + + @OutputFile + public RegularFileProperty getReportLocation() { + return this.reportLocation; + } + + @Override + @InputFiles + @PathSensitive(PathSensitivity.RELATIVE) + public FileTree getSource() { + return super.getSource(); + } + + @TaskAction + void check() throws JsonParseException, IOException { + Report report = createReport(); + File reportFile = getReportLocation().get().getAsFile(); + Files.write(reportFile.toPath(), report, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + if (report.hasProblems()) { + throw new GradleException( + "Problems found in additional Spring configuration metadata. See " + reportFile + " for details."); + } + } + + @SuppressWarnings("unchecked") + private Report createReport() throws IOException, JsonParseException, JsonMappingException { + ObjectMapper objectMapper = new ObjectMapper(); + Report report = new Report(); + for (File file : getSource().getFiles()) { + Analysis analysis = report.analysis(getProject().getProjectDir().toPath().relativize(file.toPath())); + Map json = objectMapper.readValue(file, Map.class); + check("groups", json, analysis); + check("properties", json, analysis); + check("hints", json, analysis); + } + return report; + } + + @SuppressWarnings("unchecked") + private void check(String key, Map json, Analysis analysis) { + List> groups = (List>) json.get(key); + List names = groups.stream().map((group) -> (String) group.get("name")).collect(Collectors.toList()); + List sortedNames = sortedCopy(names); + for (int i = 0; i < names.size(); i++) { + String actual = names.get(i); + String expected = sortedNames.get(i); + if (!actual.equals(expected)) { + analysis.problems.add("Wrong order at $." + key + "[" + i + "].name - expected '" + expected + + "' but found '" + actual + "'"); + } + } + } + + private List sortedCopy(Collection original) { + List copy = new ArrayList<>(original); + Collections.sort(copy); + return copy; + } + + private static final class Report implements Iterable { + + private final List analyses = new ArrayList<>(); + + private Analysis analysis(Path path) { + Analysis analysis = new Analysis(path); + this.analyses.add(analysis); + return analysis; + } + + private boolean hasProblems() { + for (Analysis analysis : this.analyses) { + if (!analysis.problems.isEmpty()) { + return true; + } + } + return false; + } + + @Override + public Iterator iterator() { + List lines = new ArrayList<>(); + for (Analysis analysis : this.analyses) { + lines.add(analysis.source.toString()); + lines.add(""); + if (analysis.problems.isEmpty()) { + lines.add("No problems found."); + } + else { + lines.addAll(analysis.problems); + } + lines.add(""); + } + return lines.iterator(); + } + + } + + private static final class Analysis { + + private final List problems = new ArrayList<>(); + + private final Path source; + + private Analysis(Path source) { + this.source = source; + } + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/CompoundRow.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/CompoundRow.java new file mode 100644 index 00000000..0f33f308 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/CompoundRow.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.util.Set; +import java.util.TreeSet; + +/** + * Table row regrouping a list of configuration properties sharing the same description. + * + * @author Brian Clozel + * @author Phillip Webb + */ +class CompoundRow extends Row { + + private final Set propertyNames; + + private final String description; + + CompoundRow(Snippet snippet, String prefix, String description) { + super(snippet, prefix); + this.description = description; + this.propertyNames = new TreeSet<>(); + } + + void addProperty(ConfigurationProperty property) { + this.propertyNames.add(property.getDisplayName()); + } + + @Override + void write(Asciidoc asciidoc) { + asciidoc.append("|"); + asciidoc.append("[[" + getAnchor() + "]]"); + asciidoc.append("<<" + getAnchor() + ","); + this.propertyNames.forEach(asciidoc::appendWithHardLineBreaks); + asciidoc.appendln(">>"); + asciidoc.appendln("|+++", this.description, "+++"); + asciidoc.appendln("|"); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationProperties.java new file mode 100644 index 00000000..0a539b1f --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationProperties.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Configuration properties read from one or more + * {@code META-INF/spring-configuration-metadata.json} files. + * + * @author Andy Wilkinson + * @author Phillip Webb + */ +final class ConfigurationProperties { + + private final Map byName; + + private ConfigurationProperties(List properties) { + Map byName = new LinkedHashMap<>(); + for (ConfigurationProperty property : properties) { + byName.put(property.getName(), property); + } + this.byName = Collections.unmodifiableMap(byName); + } + + ConfigurationProperty get(String propertyName) { + return this.byName.get(propertyName); + } + + Stream stream() { + return this.byName.values().stream(); + } + + @SuppressWarnings("unchecked") + static ConfigurationProperties fromFiles(Iterable files) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + List properties = new ArrayList<>(); + for (File file : files) { + Map json = objectMapper.readValue(file, Map.class); + for (Map property : (List>) json.get("properties")) { + properties.add(ConfigurationProperty.fromJsonProperties(property)); + } + } + return new ConfigurationProperties(properties); + } + catch (IOException ex) { + throw new RuntimeException("Failed to load configuration metadata", ex); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationPropertiesPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationPropertiesPlugin.java new file mode 100644 index 00000000..d0bc788d --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationPropertiesPlugin.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.util.stream.Collectors; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +import org.springframework.util.StringUtils; + +/** + * {@link Plugin} for projects that define {@code @ConfigurationProperties}. When applied, + * the plugin reacts to the presence of the {@link JavaPlugin} by: + * + *
    + *
  • Adding a dependency on the configuration properties annotation processor. + *
  • Configuring the additional metadata locations annotation processor compiler + * argument. + *
  • Adding the outputs of the processResources task as inputs of the compileJava task + * to ensure that the additional metadata is available when the annotation processor runs. + *
  • Registering a {@link CheckAdditionalSpringConfigurationMetadata} task and + * configuring the {@code check} task to depend upon it. + *
  • Defining an artifact for the resulting configuration property metadata so that it + * can be consumed by downstream projects. + *
+ * + * @author Andy Wilkinson + * @author Chris Bono + */ +public class ConfigurationPropertiesPlugin implements Plugin { + + // TODO extend the one in boot and delete most of this + + /** + * Name of the {@link Configuration} that holds the configuration property metadata + * artifact. + */ + public static final String CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME = "configurationPropertiesMetadata"; + + /** + * Name of the {@link CheckAdditionalSpringConfigurationMetadata} task. + */ + public static final String CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME = "checkAdditionalSpringConfigurationMetadata"; + + @Override + public void apply(Project project) { + project.getPlugins().withType(JavaPlugin.class, (javaPlugin) -> { + addConfigurationProcessorDependency(project); + configureAdditionalMetadataLocationsCompilerArgument(project); + registerCheckAdditionalMetadataTask(project); + addMetadataArtifact(project); + }); + } + + private void addConfigurationProcessorDependency(Project project) { + Configuration annotationProcessors = project.getConfigurations() + .getByName(JavaPlugin.ANNOTATION_PROCESSOR_CONFIGURATION_NAME); +// Object bootVersion = project.findProperty("springBootVersion"); +// if (bootVersion == null) { +// bootVersion = project.findProperty("spring-boot.version"); +// } +// Assert.notNull(bootVersion, "Unable to determine Spring Boot version"); + annotationProcessors.getDependencies().add(project.getDependencies().create( + "org.springframework.boot:spring-boot-configuration-processor")); + } + + private void addMetadataArtifact(Project project) { + SourceSet mainSourceSet = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + project.getConfigurations().maybeCreate(CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME); + project.afterEvaluate((evaluatedProject) -> evaluatedProject.getArtifacts().add( + CONFIGURATION_PROPERTIES_METADATA_CONFIGURATION_NAME, + mainSourceSet.getJava().getDestinationDirectory().dir("META-INF/spring-configuration-metadata.json"), + (artifact) -> artifact + .builtBy(evaluatedProject.getTasks().getByName(mainSourceSet.getClassesTaskName())))); + } + + private void configureAdditionalMetadataLocationsCompilerArgument(Project project) { + JavaCompile compileJava = project.getTasks().withType(JavaCompile.class) + .getByName(JavaPlugin.COMPILE_JAVA_TASK_NAME); + ((Task) compileJava).getInputs().files(project.getTasks().getByName(JavaPlugin.PROCESS_RESOURCES_TASK_NAME)) + .withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("processed resources"); + SourceSet mainSourceSet = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + compileJava.getOptions().getCompilerArgs() + .add("-Aorg.springframework.boot.configurationprocessor.additionalMetadataLocations=" + StringUtils + .collectionToCommaDelimitedString(mainSourceSet.getResources().getSourceDirectories().getFiles() + .stream().map(project.getRootProject()::relativePath).collect(Collectors.toSet()))); + } + + private void registerCheckAdditionalMetadataTask(Project project) { + TaskProvider checkConfigurationMetadata = project.getTasks() + .register(CHECK_ADDITIONAL_SPRING_CONFIGURATION_METADATA_TASK_NAME, + CheckAdditionalSpringConfigurationMetadata.class); + checkConfigurationMetadata.configure((check) -> { + SourceSet mainSourceSet = project.getExtensions().getByType(JavaPluginExtension.class).getSourceSets() + .getByName(SourceSet.MAIN_SOURCE_SET_NAME); + check.setSource(mainSourceSet.getResources()); + check.include("META-INF/additional-spring-configuration-metadata.json"); + check.getReportLocation().set(project.getLayout().getBuildDirectory() + .file("reports/additional-spring-configuration-metadata/check.txt")); + }); + project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME) + .configure((check) -> check.dependsOn(checkConfigurationMetadata)); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationProperty.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationProperty.java new file mode 100644 index 00000000..91cd51c9 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationProperty.java @@ -0,0 +1,88 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.util.Map; + +/** + * A configuration property. + * + * @author Andy Wilkinson + */ +class ConfigurationProperty { + + private final String name; + + private final String type; + + private final Object defaultValue; + + private final String description; + + private final boolean deprecated; + + ConfigurationProperty(String name, String type) { + this(name, type, null, null, false); + } + + ConfigurationProperty(String name, String type, Object defaultValue, String description, boolean deprecated) { + this.name = name; + this.type = type; + this.defaultValue = defaultValue; + this.description = description; + this.deprecated = deprecated; + } + + String getName() { + return this.name; + } + + String getDisplayName() { + return (getType() != null && getType().startsWith("java.util.Map")) ? getName() + ".*" : getName(); + } + + String getType() { + return this.type; + } + + Object getDefaultValue() { + return this.defaultValue; + } + + String getDescription() { + return this.description; + } + + boolean isDeprecated() { + return this.deprecated; + } + + @Override + public String toString() { + return "ConfigurationProperty [name=" + this.name + ", type=" + this.type + "]"; + } + + static ConfigurationProperty fromJsonProperties(Map property) { + String name = (String) property.get("name"); + String type = (String) property.get("type"); + Object defaultValue = property.get("defaultValue"); + String description = (String) property.get("description"); + boolean deprecated = property.containsKey("deprecated"); + return new ConfigurationProperty(name, type, defaultValue, description, deprecated); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/DocumentConfigurationProperties.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/DocumentConfigurationProperties.java similarity index 97% rename from buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/DocumentConfigurationProperties.java rename to buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/DocumentConfigurationProperties.java index 1e8222cb..5e73f004 100644 --- a/buildSrc/src/main/java/org/springframework/pulsar/build/docs/configprops/DocumentConfigurationProperties.java +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/DocumentConfigurationProperties.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.pulsar.gradle.docs.configprops; import java.io.File; import java.io.IOException; diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Row.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Row.java new file mode 100644 index 00000000..285d7967 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Row.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +/** + * Abstract class for rows in {@link Table}. + * + * @author Brian Clozel + * @author Phillip Webb + */ +abstract class Row implements Comparable { + + private final Snippet snippet; + + private final String id; + + protected Row(Snippet snippet, String id) { + this.snippet = snippet; + this.id = id; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Row other = (Row) obj; + return this.id.equals(other.id); + } + + @Override + public int hashCode() { + return this.id.hashCode(); + } + + @Override + public int compareTo(Row other) { + return this.id.compareTo(other.id); + } + + String getAnchor() { + return this.snippet.getAnchor() + "." + this.id; + } + + abstract void write(Asciidoc asciidoc); + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/SingleRow.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/SingleRow.java new file mode 100644 index 00000000..56ee6e55 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/SingleRow.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * Table row containing a single configuration property. + * + * @author Brian Clozel + * @author Phillip Webb + */ +class SingleRow extends Row { + + private final String displayName; + + private final String description; + + private final String defaultValue; + + SingleRow(Snippet snippet, ConfigurationProperty property) { + super(snippet, property.getName()); + this.displayName = property.getDisplayName(); + this.description = property.getDescription(); + this.defaultValue = getDefaultValue(property.getDefaultValue()); + } + + private String getDefaultValue(Object defaultValue) { + if (defaultValue == null) { + return null; + } + if (defaultValue.getClass().isArray()) { + return Arrays.stream((Object[]) defaultValue).map(Object::toString) + .collect(Collectors.joining("," + System.lineSeparator())); + } + return defaultValue.toString(); + } + + @Override + void write(Asciidoc asciidoc) { + asciidoc.append("|"); + asciidoc.append("[[" + getAnchor() + "]]"); + asciidoc.appendln("<<" + getAnchor() + ",`+", this.displayName, "+`>>"); + writeDescription(asciidoc); + writeDefaultValue(asciidoc); + } + + private void writeDescription(Asciidoc builder) { + if (this.description == null || this.description.isEmpty()) { + builder.appendln("|"); + } + else { + String cleanedDescription = this.description.replace("|", "\\|").replace("<", "<").replace(">", ">"); + builder.appendln("|+++", cleanedDescription, "+++"); + } + } + + private void writeDefaultValue(Asciidoc builder) { + String defaultValue = (this.defaultValue != null) ? this.defaultValue : ""; + if (defaultValue.isEmpty()) { + builder.appendln("|"); + } + else { + defaultValue = defaultValue.replace("\\", "\\\\").replace("|", "\\|"); + builder.appendln("|`+", defaultValue, "+`"); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Snippet.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Snippet.java new file mode 100644 index 00000000..dff8adf8 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Snippet.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * A configuration properties snippet. + * + * @author Brian Clozed + * @author Phillip Webb + */ +class Snippet { + + private final String anchor; + + private final String title; + + private final Set prefixes; + + private final Map overrides; + + Snippet(String anchor, String title, Consumer config) { + Set prefixes = new LinkedHashSet<>(); + Map overrides = new LinkedHashMap<>(); + if (config != null) { + config.accept(new Config() { + + @Override + public void accept(String prefix) { + prefixes.add(prefix); + } + + @Override + public void accept(String prefix, String description) { + overrides.put(prefix, description); + } + + }); + } + this.anchor = anchor; + this.title = title; + this.prefixes = prefixes; + this.overrides = overrides; + } + + String getAnchor() { + return this.anchor; + } + + String getTitle() { + return this.title; + } + + void forEachPrefix(Consumer action) { + this.prefixes.forEach(action); + } + + void forEachOverride(BiConsumer action) { + this.overrides.forEach(action); + } + + /** + * Callback to configure the snippet. + */ + interface Config { + + /** + * Accept the given prefix using the meta-data description. + * @param prefix the prefix to accept + */ + void accept(String prefix); + + /** + * Accept the given prefix with a defined description. + * @param prefix the prefix to accept + * @param description the description to use + */ + void accept(String prefix, String description); + + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Snippets.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Snippets.java new file mode 100644 index 00000000..c5e16ab2 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Snippets.java @@ -0,0 +1,131 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.gradle.api.file.FileCollection; + +/** + * Configuration properties snippets. + * + * @author Brian Clozed + * @author Phillip Webb + */ +class Snippets { + + private final ConfigurationProperties properties; + + private final List snippets = new ArrayList<>(); + + Snippets(FileCollection configurationPropertyMetadata) { + this.properties = ConfigurationProperties.fromFiles(configurationPropertyMetadata); + } + + void add(String anchor, String title, Consumer config) { + this.snippets.add(new Snippet(anchor, title, config)); + } + + void writeTo(Path outputDirectory) throws IOException { + createDirectory(outputDirectory); + Set remaining = this.properties.stream().filter((property) -> !property.isDeprecated()) + .map(ConfigurationProperty::getName).collect(Collectors.toSet()); + for (Snippet snippet : this.snippets) { + Set written = writeSnippet(outputDirectory, snippet, remaining); + remaining.removeAll(written); + } + if (!remaining.isEmpty()) { + throw new IllegalStateException( + "The following keys were not written to the documentation: " + String.join(", ", remaining)); + } + } + + private Set writeSnippet(Path outputDirectory, Snippet snippet, Set remaining) throws IOException { + Table table = new Table(); + Set added = new HashSet<>(); + snippet.forEachOverride((prefix, description) -> { + CompoundRow row = new CompoundRow(snippet, prefix, description); + remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> { + if (added.add(name)) { + row.addProperty(this.properties.get(name)); + } + }); + table.addRow(row); + }); + snippet.forEachPrefix((prefix) -> { + remaining.stream().filter((candidate) -> candidate.startsWith(prefix)).forEach((name) -> { + if (added.add(name)) { + table.addRow(new SingleRow(snippet, this.properties.get(name))); + } + }); + }); + Asciidoc asciidoc = getAsciidoc(snippet, table); + writeAsciidoc(outputDirectory, snippet, asciidoc); + return added; + } + + private Asciidoc getAsciidoc(Snippet snippet, Table table) { + Asciidoc asciidoc = new Asciidoc(); + // We have to prepend 'appendix.' as a section id here, otherwise the + // spring-asciidoctor-extensions:section-id asciidoctor extension complains + asciidoc.appendln("[[appendix." + snippet.getAnchor() + "]]"); + asciidoc.appendln("== ", snippet.getTitle()); + table.write(asciidoc); + return asciidoc; + } + + private void writeAsciidoc(Path outputDirectory, Snippet snippet, Asciidoc asciidoc) throws IOException { + String[] parts = (snippet.getAnchor()).split("\\."); + Path path = outputDirectory; + for (int i = 0; i < parts.length; i++) { + String name = (i < parts.length - 1) ? parts[i] : parts[i] + ".adoc"; + path = path.resolve(name); + } + createDirectory(path.getParent()); + Files.deleteIfExists(path); + try (OutputStream outputStream = Files.newOutputStream(path)) { + outputStream.write(asciidoc.toString().getBytes(StandardCharsets.UTF_8)); + } + } + + private void createDirectory(Path path) throws IOException { + assertValidOutputDirectory(path); + if (!Files.exists(path)) { + Files.createDirectory(path); + } + } + + private void assertValidOutputDirectory(Path path) { + if (path == null) { + throw new IllegalArgumentException("Directory path should not be null"); + } + if (Files.exists(path) && !Files.isDirectory(path)) { + throw new IllegalArgumentException("Path already exists and is not a directory"); + } + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Table.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Table.java new file mode 100644 index 00000000..7f58b483 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/docs/configprops/Table.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.docs.configprops; + +import java.util.Set; +import java.util.TreeSet; + +/** + * Asciidoctor table listing configuration properties sharing to a common theme. + * + * @author Brian Clozel + */ +class Table { + + private final Set rows = new TreeSet<>(); + + void addRow(Row row) { + this.rows.add(row); + } + + void write(Asciidoc asciidoc) { + asciidoc.appendln("[cols=\"4,3,3\", options=\"header\"]"); + asciidoc.appendln("|==="); + asciidoc.appendln("|Name|Description|Default Value"); + asciidoc.appendln(); + this.rows.forEach((entry) -> { + entry.write(asciidoc); + asciidoc.appendln(); + }); + asciidoc.appendln("|==="); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/MavenPublishingConventionsPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/MavenPublishingConventionsPlugin.java new file mode 100644 index 00000000..9edf5fd6 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/MavenPublishingConventionsPlugin.java @@ -0,0 +1,166 @@ +/* + * Copyright 2012-2022 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.publish; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.attributes.Usage; +import org.gradle.api.component.AdhocComponentWithVariants; +import org.gradle.api.component.ConfigurationVariantDetails; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.VariantVersionMappingStrategy; +import org.gradle.api.publish.maven.MavenPom; +import org.gradle.api.publish.maven.MavenPomDeveloperSpec; +import org.gradle.api.publish.maven.MavenPomIssueManagement; +import org.gradle.api.publish.maven.MavenPomLicenseSpec; +import org.gradle.api.publish.maven.MavenPomOrganization; +import org.gradle.api.publish.maven.MavenPomScm; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +import org.springframework.pulsar.gradle.ProjectLinks; + +/** + * Conventions that are applied in the presence of the {@link MavenPublishPlugin}. When + * the plugin is applied: + * + *
    + *
  • If the {@code deploymentRepository} property has been set, a Maven artifact repository + * is configured to publish to it + * it. + *
  • The poms of all {@link MavenPublication Maven publications} are customized to meet + * Maven Central's requirements. + *
  • If the {@link JavaPlugin Java plugin} has also been applied: + *
      + *
    • Creation of Javadoc and source jars is enabled. + *
    • Publication metadata (poms and Gradle module metadata) is configured to use + * resolved versions. + *
    + *
+ * + * @author Andy Wilkinson + * @author Christoph Dreis + * @author Mike Smithson + * @author Chris Bono + */ +class MavenPublishingConventionsPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().withType(MavenPublishPlugin.class).all((mavenPublish) -> { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + if (project.hasProperty("deploymentRepository")) { + publishing.getRepositories().maven((mavenRepository) -> { + mavenRepository.setUrl(project.property("deploymentRepository")); + mavenRepository.setName("deployment"); + }); + } + publishing.getPublications().withType(MavenPublication.class) + .all((mavenPublication) -> customizeMavenPublication(mavenPublication, project)); + project.getPlugins().withType(JavaPlugin.class).all((javaPlugin) -> { + JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class); + extension.withJavadocJar(); + extension.withSourcesJar(); + }); + }); + } + + private void customizeMavenPublication(MavenPublication publication, Project project) { + customizePom(publication.getPom(), project); + project.getPlugins().withType(JavaPlugin.class) + .all((javaPlugin) -> customizeJavaMavenPublication(publication, project)); + suppressMavenOptionalFeatureWarnings(publication); + } + + private void customizePom(MavenPom pom, Project project) { + pom.getUrl().set(ProjectLinks.HOMEPAGE.link()); + pom.getName().set(project.provider(project::getName)); + pom.getDescription().set(project.provider(project::getDescription)); + pom.organization(this::customizeOrganization); + pom.licenses(this::customizeLicences); + pom.developers(this::customizeDevelopers); + pom.scm(this::customizeScm); + pom.issueManagement(this::customizeIssueManagement); + } + + private void customizeJavaMavenPublication(MavenPublication publication, Project project) { + addMavenOptionalFeature(project); + publication.versionMapping((strategy) -> strategy.usage(Usage.JAVA_API, (mappingStrategy) -> mappingStrategy + .fromResolutionOf(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME))); + publication.versionMapping( + (strategy) -> strategy.usage(Usage.JAVA_RUNTIME, VariantVersionMappingStrategy::fromResolutionResult)); + } + + /** + * Add a feature that allows maven plugins to declare optional dependencies that + * appear in the POM. This is required to make m2e in Eclipse happy. + * @param project the project to add the feature to + */ + private void addMavenOptionalFeature(Project project) { + JavaPluginExtension extension = project.getExtensions().getByType(JavaPluginExtension.class); + extension.registerFeature("mavenOptional", + (feature) -> feature.usingSourceSet(extension.getSourceSets().getByName("main"))); + AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.getComponents() + .findByName("java"); + javaComponent.addVariantsFromConfiguration( + project.getConfigurations().findByName("mavenOptionalRuntimeElements"), + ConfigurationVariantDetails::mapToOptional); + } + + private void suppressMavenOptionalFeatureWarnings(MavenPublication publication) { + publication.suppressPomMetadataWarningsFor("mavenOptionalApiElements"); + publication.suppressPomMetadataWarningsFor("mavenOptionalRuntimeElements"); + } + + private void customizeOrganization(MavenPomOrganization organization) { + organization.getName().set("Pivotal Software, Inc."); + organization.getUrl().set("https://spring.io"); + } + + private void customizeLicences(MavenPomLicenseSpec licences) { + licences.license((licence) -> { + licence.getName().set("Apache License, Version 2.0"); + licence.getUrl().set("https://www.apache.org/licenses/LICENSE-2.0"); + }); + } + + private void customizeDevelopers(MavenPomDeveloperSpec developers) { + developers.developer((developer) -> { + developer.getId().set("schacko"); + developer.getName().set("Soby Chacko"); + developer.getEmail().set("chackos@vmware.com"); + }); + developers.developer((developer) -> { + developer.getId().set("onobc"); + developer.getName().set("Chris Bono"); + developer.getEmail().set("cbono@vmware.com"); + }); + } + + private void customizeScm(MavenPomScm scm) { + scm.getConnection().set(ProjectLinks.SCM_CONNECTION.link()); + scm.getDeveloperConnection().set(ProjectLinks.SCM_DEV_CONNECTION.link()); + scm.getUrl().set(ProjectLinks.SCM_URL.link()); + } + + private void customizeIssueManagement(MavenPomIssueManagement issueManagement) { + issueManagement.getSystem().set("GitHub"); + issueManagement.getUrl().set(ProjectLinks.ISSUES.link()); + } +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishAllJavaComponentsPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishAllJavaComponentsPlugin.java new file mode 100644 index 00000000..071d561f --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishAllJavaComponentsPlugin.java @@ -0,0 +1,28 @@ +package org.springframework.pulsar.gradle.publish; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.JavaPlatformPlugin; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +public class PublishAllJavaComponentsPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().withType(MavenPublishPlugin.class).all((mavenPublish) -> { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + publishing.getPublications().create("mavenJava", MavenPublication.class, maven -> { + project.getPlugins().withType(JavaPlugin.class, (plugin) -> { + maven.from(project.getComponents().getByName("java")); + }); + project.getPlugins().withType(JavaPlatformPlugin.class, (plugin) -> { + maven.from(project.getComponents().getByName("javaPlatform")); + }); + }); + }); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishArtifactsPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishArtifactsPlugin.java new file mode 100644 index 00000000..67d48dc5 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishArtifactsPlugin.java @@ -0,0 +1,24 @@ +package org.springframework.pulsar.gradle.publish; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +import org.springframework.pulsar.gradle.ProjectUtils; + +public class PublishArtifactsPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getTasks().register("publishArtifacts", publishArtifacts -> { + publishArtifacts.setGroup("Publishing"); + publishArtifacts.setDescription("Publish the artifacts to either Artifactory or Maven Central based on the version"); + if (ProjectUtils.isRelease(project)) { + publishArtifacts.dependsOn("publishToOssrh"); + } + else { + publishArtifacts.dependsOn("artifactoryPublish"); + } + }); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishLocalPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishLocalPlugin.java new file mode 100644 index 00000000..b3a18f9a --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/PublishLocalPlugin.java @@ -0,0 +1,23 @@ +package org.springframework.pulsar.gradle.publish; + +import java.io.File; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +public class PublishLocalPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().withType(MavenPublishPlugin.class).all(mavenPublish -> { + PublishingExtension publishing = project.getExtensions().getByType(PublishingExtension.class); + publishing.getRepositories().maven(maven -> { + maven.setName("local"); + maven.setUrl(new File(project.getRootProject().getBuildDir(), "publications/repos")); + }); + }); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringNexusPublishPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringNexusPublishPlugin.java new file mode 100644 index 00000000..a9c3e29c --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringNexusPublishPlugin.java @@ -0,0 +1,28 @@ +package org.springframework.pulsar.gradle.publish; + +import java.net.URI; +import java.time.Duration; + +import io.github.gradlenexus.publishplugin.NexusPublishExtension; +import io.github.gradlenexus.publishplugin.NexusPublishPlugin; +import io.github.gradlenexus.publishplugin.NexusRepository; +import org.gradle.api.Action; +import org.gradle.api.Plugin; +import org.gradle.api.Project; + +public class SpringNexusPublishPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPlugins().apply(NexusPublishPlugin.class); + NexusPublishExtension nexusPublishing = project.getExtensions().findByType(NexusPublishExtension.class); + nexusPublishing.getRepositories().create("ossrh", nexusRepository -> { + nexusRepository.getNexusUrl().set(URI.create("https://s01.oss.sonatype.org/service/local/")); + nexusRepository.getSnapshotRepositoryUrl().set( + URI.create("https://s01.oss.sonatype.org/content/repositories/snapshots/")); + }); + nexusPublishing.getConnectTimeout().set(Duration.ofMinutes(3)); + nexusPublishing.getClientTimeout().set(Duration.ofMinutes(3)); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringPublishPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringPublishPlugin.java new file mode 100644 index 00000000..d80b7175 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringPublishPlugin.java @@ -0,0 +1,22 @@ +package org.springframework.pulsar.gradle.publish; + +import io.spring.gradle.convention.ArtifactoryPlugin; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.plugins.PluginManager; +import org.gradle.api.publish.maven.plugins.MavenPublishPlugin; + +public class SpringPublishPlugin implements Plugin { + + @Override + public void apply(Project project) { + PluginManager pluginManager = project.getPluginManager(); + pluginManager.apply(MavenPublishPlugin.class); + pluginManager.apply(SpringSigningPlugin.class); + pluginManager.apply(MavenPublishingConventionsPlugin.class); + pluginManager.apply(PublishLocalPlugin.class); + pluginManager.apply(PublishArtifactsPlugin.class); + pluginManager.apply(ArtifactoryPlugin.class); + } + +} diff --git a/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringSigningPlugin.java b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringSigningPlugin.java new file mode 100644 index 00000000..d3a4b5c5 --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/pulsar/gradle/publish/SpringSigningPlugin.java @@ -0,0 +1,61 @@ +/* + * Copyright 2016-2019 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +package org.springframework.pulsar.gradle.publish; + +import java.util.concurrent.Callable; + +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.publish.Publication; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.plugins.signing.SigningExtension; +import org.gradle.plugins.signing.SigningPlugin; + +public class SpringSigningPlugin implements Plugin { + + @Override + public void apply(Project project) { + project.getPluginManager().apply(SigningPlugin.class); + project.getPlugins().withType(SigningPlugin.class).all(signingPlugin -> { + boolean hasSigningKey = project.hasProperty("signing.keyId") || project.hasProperty("signingKey"); + if (hasSigningKey) { + sign(project); + } + }); + } + + private void sign(Project project) { + SigningExtension signing = project.getExtensions().findByType(SigningExtension.class); + signing.setRequired((Callable) () -> project.getGradle().getTaskGraph().hasTask("publishArtifacts")); + String signingKeyId = (String) project.findProperty("signingKeyId"); + String signingKey = (String) project.findProperty("signingKey"); + String signingPassword = (String) project.findProperty("signingPassword"); + if (signingKeyId != null) { + signing.useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword); + } + else { + signing.useInMemoryPgpKeys(signingKey, signingPassword); + } + project.getPlugins().withType(PublishAllJavaComponentsPlugin.class) + .all(publishingPlugin -> { + PublishingExtension publishing = project.getExtensions().findByType(PublishingExtension.class); + Publication maven = publishing.getPublications().getByName("mavenJava"); + signing.sign(maven); + }); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/CompoundRowTests.java b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/CompoundRowTests.java similarity index 96% rename from buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/CompoundRowTests.java rename to buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/CompoundRowTests.java index 31398596..cd5237f4 100644 --- a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/CompoundRowTests.java +++ b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/CompoundRowTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.pulsar.gradle.docs.configprops; import org.junit.jupiter.api.Test; diff --git a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/ConfigurationPropertiesTests.java b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationPropertiesTests.java similarity index 95% rename from buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/ConfigurationPropertiesTests.java rename to buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationPropertiesTests.java index 2a67e794..5948aa20 100644 --- a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/ConfigurationPropertiesTests.java +++ b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/ConfigurationPropertiesTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.pulsar.gradle.docs.configprops; import java.io.File; import java.util.Arrays; diff --git a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/SingleRowTests.java b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/SingleRowTests.java similarity index 98% rename from buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/SingleRowTests.java rename to buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/SingleRowTests.java index 11ca68af..78cc8f13 100644 --- a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/SingleRowTests.java +++ b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/SingleRowTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.pulsar.gradle.docs.configprops; import org.junit.jupiter.api.Test; diff --git a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/TableTests.java b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/TableTests.java similarity index 97% rename from buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/TableTests.java rename to buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/TableTests.java index 9dc69fa1..6866aea6 100644 --- a/buildSrc/src/test/java/org/springframework/pulsar/build/docs/configprops/TableTests.java +++ b/buildSrc/src/test/java/org/springframework/pulsar/gradle/docs/configprops/TableTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.pulsar.build.docs.configprops; +package org.springframework.pulsar.gradle.docs.configprops; import org.junit.jupiter.api.Test; diff --git a/settings.gradle b/settings.gradle index ff6a9d42..5bffd44c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -22,6 +22,7 @@ settings.gradle.projectsLoaded { rootProject.name = 'spring-pulsar-dist' include 'spring-pulsar' -include 'spring-pulsar-boot-autoconfigure' +include 'spring-pulsar-dependencies' +include 'spring-pulsar-spring-boot-autoconfigure' include 'spring-pulsar-sample-apps' include 'spring-pulsar-docs' diff --git a/spring-pulsar-dependencies/build.gradle b/spring-pulsar-dependencies/build.gradle new file mode 100644 index 00000000..2d2a0a34 --- /dev/null +++ b/spring-pulsar-dependencies/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'java-platform' +} + +javaPlatform { + allowDependencies() +} + +ext { + assertjVersion = '3.22.0' + awaitilityVersion = '4.2.0' + googleJsr305Version = '3.0.2' + hamcrestVersion = '2.2' + hibernateValidationVersion = '7.0.4.Final' + jacksonBomVersion = '2.13.3' + jaywayJsonPathVersion = '2.6.0' + junitJupiterVersion = '5.8.2' + pulsarVersion = '2.10.0' + log4jVersion = '2.17.2' + mockitoVersion = '4.5.1' + reactorVersion = '2020.0.17' + pulsarTestcontainersVersion = '1.17.2' + springBootVersion = '3.0.0-SNAPSHOT' + springRetryVersion = '1.3.3' + springVersion = '6.0.0-SNAPSHOT' + caffeineVersion = '3.1.1' +} + +dependencies { + api platform("org.springframework:spring-framework-bom:$springVersion") + api platform("io.projectreactor:reactor-bom:$reactorVersion") + api platform("org.junit:junit-bom:$junitJupiterVersion") + api platform("com.fasterxml.jackson:jackson-bom:$jacksonBomVersion") + api platform("org.apache.logging.log4j:log4j-bom:$log4jVersion") + + constraints { + // spring-pulsar + api "com.github.ben-manes.caffeine:caffeine:$caffeineVersion" + api "org.apache.pulsar:pulsar-client:$pulsarVersion" + api "org.apache.pulsar:pulsar-client-admin:$pulsarVersion" + api "org.apache.pulsar:pulsar-client-admin-api:$pulsarVersion" + api "org.springframework.retry:spring-retry:$springRetryVersion" + api "com.jayway.jsonpath:json-path:$jaywayJsonPathVersion" + api "org.mockito:mockito-junit-jupiter:$mockitoVersion" + api "org.hibernate.validator:hibernate-validator:$hibernateValidationVersion" + // spring-pulsar-spring-boot-autoconfigure + api "org.springframework.boot:spring-boot-autoconfigure-processor:$springBootVersion" + api "org.springframework.boot:spring-boot-configuration-processor:$springBootVersion" + api "org.springframework.boot:spring-boot:$springBootVersion" + api "org.springframework.boot:spring-boot-autoconfigure:$springBootVersion" + api "org.springframework.boot:spring-boot-starter:$springBootVersion" + api "org.springframework.boot:spring-boot-starter-validation:$springBootVersion" + api "org.springframework.boot:spring-boot-starter-test:$springBootVersion" + // Common to all + api "com.google.code.findbugs:jsr305:$googleJsr305Version" + api 'org.apiguardian:apiguardian-api:1.0.0' + api "org.awaitility:awaitility:$awaitilityVersion" + api "org.hamcrest:hamcrest-core:$hamcrestVersion" + api "org.assertj:assertj-core:$assertjVersion" + api "org.testcontainers:pulsar:$pulsarTestcontainersVersion" + } +} diff --git a/spring-pulsar-docs/build.gradle b/spring-pulsar-docs/build.gradle index 1f6892ca..cb99919b 100644 --- a/spring-pulsar-docs/build.gradle +++ b/spring-pulsar-docs/build.gradle @@ -1,12 +1,8 @@ plugins { - id 'java' - id 'java-library' - id 'org.springframework.pulsar.conventions' + id 'org.springframework.pulsar.spring-docs-module' id 'org.asciidoctor.jvm.convert' } -apply from: "${rootProject.projectDir}/gradle/publish-artifactory.gradle" - description = 'Spring Pulsar Docs' configurations { @@ -14,9 +10,9 @@ configurations { } dependencies { - api "org.springframework.boot:spring-boot-starter:$springBootVersion" + api 'org.springframework.boot:spring-boot-starter' api project (':spring-pulsar') - configurationProperties(project(path: ":spring-pulsar-boot-autoconfigure", configuration: "configurationPropertiesMetadata")) + configurationProperties(project(path: ":spring-pulsar-spring-boot-autoconfigure", configuration: "configurationPropertiesMetadata")) } task aggregatedJavadoc(type: Javadoc) { @@ -52,7 +48,7 @@ task aggregatedJavadoc(type: Javadoc) { } } -task documentConfigurationProperties(type: org.springframework.pulsar.build.docs.configprops.DocumentConfigurationProperties) { +task documentConfigurationProperties(type: org.springframework.pulsar.gradle.docs.configprops.DocumentConfigurationProperties) { configurationPropertyMetadata = configurations.configurationProperties outputDir = file("${buildDir}/docs/generated/") } @@ -71,7 +67,7 @@ asciidoctor { task asciidoctorPdf(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { sources { - include "*.singleadoc" + include "*.adoc" } } diff --git a/spring-pulsar-docs/src/main/asciidoc/pulsar.adoc b/spring-pulsar-docs/src/main/asciidoc/pulsar.adoc index 39a10851..c58e0aee 100644 --- a/spring-pulsar-docs/src/main/asciidoc/pulsar.adoc +++ b/spring-pulsar-docs/src/main/asciidoc/pulsar.adoc @@ -7,7 +7,7 @@ For a quick but less detailed introduction, see <>. [[pulsar-client]] === Pulsar Client -When using `spring-pulsar-boot-autoconfigure`, you get the `PulsarClient` auto-configured. +When using `spring-pulsar-spring-boot-autoconfigure`, you get the `PulsarClient` auto-configured. This is done through a factory bean called `PulsarClientFactoryBean`, which takes a configuration object `PulsarClientConfiguration`. By default, the application tries to connect a local Pulsar instance available at `pulsar://localhost:6650`. @@ -130,7 +130,7 @@ Note that, when using a `MessageRouter, you must set the `spring.pulsar.producer === Pulsar Producer Factory `PulsarTemplate` uses a `PulsarProducerFactory` for creating the underlying Pulsar producer. -When using Spring Boot through `spring-pulsar-boot-autoconfigure`, then it automatically autoconfigures a `PulsarProducerFactory`. +When using Spring Boot through `spring-pulsar-spring-boot-autoconfigure`, then it automatically autoconfigures a `PulsarProducerFactory`. Any producer properties mentioned above (using the prefix, `spring.pulsar.producer`) is passed along to the backing producer factory implementation along with a Pulsar client. You can disable the autoconfigured producer factory by providing your own bean definition for `DefaultPulsarProducerFactory` in the application. diff --git a/spring-pulsar-docs/src/main/asciidoc/quick-tour.adoc b/spring-pulsar-docs/src/main/asciidoc/quick-tour.adoc index f3af3e2d..331757b9 100644 --- a/spring-pulsar-docs/src/main/asciidoc/quick-tour.adoc +++ b/spring-pulsar-docs/src/main/asciidoc/quick-tour.adoc @@ -27,7 +27,7 @@ Use the following command to do a full build of the project. The build will produce the following artifacts. * spring-pulsar -* spring-pulsar-boot-autoconfigure +* spring-pulsar-spring-boot-autoconfigure ### Maven Coordinates @@ -102,4 +102,3 @@ Behind the scenes, it creates a message listener container which creates and man As with a regular Pulsar consumer, the default subscription type when using `PulsarListener` is the `Exclusive` mode. As records are published in to the `hello-pulsar` topic, the `Pulsarlistener` consumes them and prints them on the console. Here also, the framework infers the schema type used from the data type that the `PulsarListner` method uses as the payload - `String` in this case. - diff --git a/spring-pulsar-docs/src/main/java/foo/Bar.java b/spring-pulsar-docs/src/main/java/foo/Bar.java deleted file mode 100644 index b6512a4d..00000000 --- a/spring-pulsar-docs/src/main/java/foo/Bar.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * - * Licensed 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 - * - * https://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. - */ - -package foo; - -public class Bar { -} diff --git a/spring-pulsar-sample-apps/build.gradle b/spring-pulsar-sample-apps/build.gradle new file mode 100644 index 00000000..05f8530a --- /dev/null +++ b/spring-pulsar-sample-apps/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'org.springframework.pulsar.spring-module' +} + +description = 'Spring Pulsar Sample Applications' + +dependencies { + api project(':spring-pulsar-spring-boot-autoconfigure') + implementation 'com.google.code.findbugs:jsr305' +} + +project.afterEvaluate { + project.tasks.artifactoryPublish.enabled(false) +} diff --git a/spring-pulsar-sample-apps/src/main/java/app1/SpringPulsarBootApp.java b/spring-pulsar-sample-apps/src/main/java/app1/SpringPulsarBootApp.java index cce8ae1b..1b406f0d 100644 --- a/spring-pulsar-sample-apps/src/main/java/app1/SpringPulsarBootApp.java +++ b/spring-pulsar-sample-apps/src/main/java/app1/SpringPulsarBootApp.java @@ -40,8 +40,8 @@ public class SpringPulsarBootApp { } /* - * Basic publisher using PulsarTemplate and a PulsarListener using - * an exclusive subscription to consume. + * Basic publisher using PulsarTemplate and a PulsarListener using an + * exclusive subscription to consume. */ @Bean public ApplicationRunner runner1(PulsarTemplate pulsarTemplate) { @@ -61,8 +61,8 @@ public class SpringPulsarBootApp { } /* - * Basic publisher using PulsarTemplate and a PulsarListener using - * an exclusive subscription to consume. + * Basic publisher using PulsarTemplate and a PulsarListener using an + * exclusive subscription to consume. */ @Bean public ApplicationRunner runner2(PulsarTemplate pulsarTemplate) { @@ -82,8 +82,8 @@ public class SpringPulsarBootApp { } /* - * Demonstrating more complex types for publishing using JSON schema and the associated - * PulsarListener using an exclusive subscription. + * Demonstrating more complex types for publishing using JSON schema and the + * associated PulsarListener using an exclusive subscription. */ @Bean public ApplicationRunner runner3(PulsarTemplate pulsarTemplate) { @@ -97,7 +97,8 @@ public class SpringPulsarBootApp { }; } - @PulsarListener(subscriptionName = "subscription-3", topics = "hello-pulsar-exclusive-3", schemaType = SchemaType.JSON) + @PulsarListener(subscriptionName = "subscription-3", topics = "hello-pulsar-exclusive-3", + schemaType = SchemaType.JSON) public void listen3(Foo message) { this.logger.info("Message received :" + message); } @@ -117,7 +118,8 @@ public class SpringPulsarBootApp { }; } - @PulsarListener(subscriptionName = "subscription-4", topics = "hello-pulsar-exclusive-4", schemaType = SchemaType.JSON, batch = true) + @PulsarListener(subscriptionName = "subscription-4", topics = "hello-pulsar-exclusive-4", + schemaType = SchemaType.JSON, batch = true) public void listen4(List messages) { this.logger.info("records received :" + messages.size()); for (Foo message : messages) { @@ -128,10 +130,7 @@ public class SpringPulsarBootApp { record Foo(String foo, String bar) { @Override public String toString() { - return "Foo{" + - "foo='" + this.foo + '\'' + - ", bar='" + this.bar + '\'' + - '}'; + return "Foo{" + "foo='" + this.foo + '\'' + ", bar='" + this.bar + '\'' + '}'; } } diff --git a/spring-pulsar-sample-apps/src/main/java/app2/FailoverConsumerApp.java b/spring-pulsar-sample-apps/src/main/java/app2/FailoverConsumerApp.java index b9d62f75..ff7a30d4 100644 --- a/spring-pulsar-sample-apps/src/main/java/app2/FailoverConsumerApp.java +++ b/spring-pulsar-sample-apps/src/main/java/app2/FailoverConsumerApp.java @@ -53,22 +53,26 @@ public class FailoverConsumerApp { }; } - @PulsarListener(subscriptionName = "failover-subscription-demo", topics = "failover-demo-topic", subscriptionType = "failover") + @PulsarListener(subscriptionName = "failover-subscription-demo", topics = "failover-demo-topic", + subscriptionType = "failover") public void listen1(String foo) { this.logger.info("failover-listen1 : " + foo); } - @PulsarListener(subscriptionName = "failover-subscription-demo", topics = "failover-demo-topic", subscriptionType = "failover") + @PulsarListener(subscriptionName = "failover-subscription-demo", topics = "failover-demo-topic", + subscriptionType = "failover") public void listen2(String foo) { this.logger.info("failover-listen2 : " + foo); } - @PulsarListener(subscriptionName = "failover-subscription-demo", topics = "failover-demo-topic", subscriptionType = "failover") + @PulsarListener(subscriptionName = "failover-subscription-demo", topics = "failover-demo-topic", + subscriptionType = "failover") public void listen(String foo) { this.logger.info("failover-listen3 : " + foo); } static class FooRouter implements MessageRouter { + @Serial private static final long serialVersionUID = -1L; @@ -76,9 +80,11 @@ public class FailoverConsumerApp { public int choosePartition(Message msg, TopicMetadata metadata) { return 0; } + } static class BarRouter implements MessageRouter { + @Serial private static final long serialVersionUID = -1L; @@ -86,9 +92,11 @@ public class FailoverConsumerApp { public int choosePartition(Message msg, TopicMetadata metadata) { return 1; } + } static class BuzzRouter implements MessageRouter { + @Serial private static final long serialVersionUID = -1L; @@ -96,5 +104,7 @@ public class FailoverConsumerApp { public int choosePartition(Message msg, TopicMetadata metadata) { return 2; } + } + } diff --git a/spring-pulsar-spring-boot-autoconfigure/build.gradle b/spring-pulsar-spring-boot-autoconfigure/build.gradle new file mode 100644 index 00000000..34c1d27f --- /dev/null +++ b/spring-pulsar-spring-boot-autoconfigure/build.gradle @@ -0,0 +1,28 @@ +plugins { + id 'org.springframework.pulsar.spring-module' + id 'org.springframework.pulsar.configuration-properties' +} + +description = 'Spring Pulsar Spring Boot Auto-configuration' + +dependencies { + annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor' + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' + + api project (':spring-pulsar') + api 'org.springframework.boot:spring-boot' + api 'org.springframework.boot:spring-boot-autoconfigure' + api 'org.springframework.boot:spring-boot-starter' + api 'org.springframework.boot:spring-boot-starter-validation' + implementation 'com.google.code.findbugs:jsr305' + + // TODO remove unused dependencies + + // TODO do I really need this??? To avoid compiler warnings about @API annotations in JUnit code + testCompileOnly 'org.apiguardian:apiguardian-api' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.testcontainers:pulsar' + // TODO why do I care about these? + testRuntimeOnly 'org.apache.logging.log4j:log4j-core' + testRuntimeOnly 'org.apache.logging.log4j:log4j-jcl' +} diff --git a/spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAnnotationDrivenConfiguration.java b/spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAnnotationDrivenConfiguration.java similarity index 100% rename from spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAnnotationDrivenConfiguration.java rename to spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAnnotationDrivenConfiguration.java diff --git a/spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfiguration.java b/spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfiguration.java similarity index 99% rename from spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfiguration.java rename to spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfiguration.java index 7a552be1..cfa912a8 100644 --- a/spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfiguration.java +++ b/spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfiguration.java @@ -93,4 +93,5 @@ public class PulsarAutoConfiguration { public PulsarConsumerFactory pulsarConsumerFactory(PulsarClient pulsarClient) { return new DefaultPulsarConsumerFactory<>(pulsarClient, this.properties.buildConsumerProperties()); } + } diff --git a/spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarProperties.java b/spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarProperties.java similarity index 97% rename from spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarProperties.java rename to spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarProperties.java index 577b5d9d..03e71e8e 100644 --- a/spring-pulsar-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarProperties.java +++ b/spring-pulsar-spring-boot-autoconfigure/src/main/java/org/springframework/pulsar/autoconfigure/PulsarProperties.java @@ -328,11 +328,11 @@ public class PulsarProperties { map.from(this::getTopicsPattern).as(Pattern::compile).to(properties.in("topicsPattern")); map.from(this::getSubscriptionName).to(properties.in("subscriptionName")); map.from(this::getSubscriptionType).to(properties.in("subscriptionType")); - map.from(this::getReceiverQueueSize) - .to(properties.in("receiverQueueSize")); + map.from(this::getReceiverQueueSize).to(properties.in("receiverQueueSize")); map.from(this::getAcknowledgementsGroupTimeMicros).to(properties.in("acknowledgementsGroupTimeMicros")); map.from(this::getNegativeAckRedeliveryDelayMicros).to(properties.in("negativeAckRedeliveryDelayMicros")); - map.from(this::getMaxTotalReceiverQueueSizeAcrossPartitions).to(properties.in("maxTotalReceiverQueueSizeAcrossPartitions")); + map.from(this::getMaxTotalReceiverQueueSizeAcrossPartitions) + .to(properties.in("maxTotalReceiverQueueSizeAcrossPartitions")); map.from(this::getConsumerName).to(properties.in("consumerName")); map.from(this::getAckTimeoutMillis).to(properties.in("ackTimeoutMillis")); map.from(this::getTickDurationMillis).to(properties.in("tickDurationMillis")); @@ -345,9 +345,11 @@ public class PulsarProperties { map.from(this::getRegexSubscriptionMode).to(properties.in("regexSubscriptionMode")); map.from(this::isAutoUpdatePartitions).to(properties.in("autoUpdatePartitions")); map.from(this::isReplicateSubscriptionState).to(properties.in("replicateSubscriptionState")); - map.from(this::isAutoAckOldestChunkedMessageOnQueueFull).to(properties.in("autoAckOldestChunkedMessageOnQueueFull")); + map.from(this::isAutoAckOldestChunkedMessageOnQueueFull) + .to(properties.in("autoAckOldestChunkedMessageOnQueueFull")); map.from(this::getMaxPendingChunkedMessage).to(properties.in("maxPendingChunkedMessage")); - map.from(this::getExpireTimeOfIncompleteChunkedMessageMillis).to(properties.in("expireTimeOfIncompleteChunkedMessageMillis")); + map.from(this::getExpireTimeOfIncompleteChunkedMessageMillis) + .to(properties.in("expireTimeOfIncompleteChunkedMessageMillis")); return properties; } @@ -531,7 +533,8 @@ public class PulsarProperties { map.from(this::getSendTimeoutMs).to(properties.in("sendTimeoutMs")); map.from(this::isBlockIfQueueFull).to(properties.in("blockIfQueueFull")); map.from(this::getMaxPendingMessages).to(properties.in("maxPendingMessages")); - map.from(this::getMaxPendingMessagesAcrossPartitions).to(properties.in("maxPendingMessagesAcrossPartitions")); + map.from(this::getMaxPendingMessagesAcrossPartitions) + .to(properties.in("maxPendingMessagesAcrossPartitions")); map.from(this::getMessageRoutingMode).to(properties.in("messageRoutingMode")); map.from(this::getHashingScheme).to(properties.in("hashingScheme")); map.from(this::getCryptoFailureAction).to(properties.in("cryptoFailureAction")); @@ -582,6 +585,7 @@ public class PulsarProperties { public void setInitialCapacity(Integer initialCapacity) { this.initialCapacity = initialCapacity; } + } public static class Client { @@ -794,7 +798,8 @@ public class PulsarProperties { map.from(this::isTlsHostnameVerificationEnable).to(properties.in("tlsHostnameVerificationEnable")); map.from(this::getConcurrentLookupRequest).to(properties.in("concurrentLookupRequest")); map.from(this::getMaxLookupRequest).to(properties.in("maxLookupRequest")); - map.from(this::getMaxNumberOfRejectedRequestPerConnection).to(properties.in("maxNumberOfRejectedRequestPerConnection")); + map.from(this::getMaxNumberOfRejectedRequestPerConnection) + .to(properties.in("maxNumberOfRejectedRequestPerConnection")); map.from(this::getKeepAliveIntervalSeconds).to(properties.in("keepAliveIntervalSeconds")); map.from(this::getConnectionTimeoutMs).to(properties.in("connectionTimeoutMs")); map.from(this::getRequestTimeoutMs).to(properties.in("requestTimeoutMs")); @@ -803,6 +808,7 @@ public class PulsarProperties { return properties; } + } public static class Listener { @@ -826,6 +832,7 @@ public class PulsarProperties { public void setSchemaType(SchemaType schemaType) { this.schemaType = schemaType; } + } @SuppressWarnings("serial") @@ -834,5 +841,7 @@ public class PulsarProperties { java.util.function.Consumer in(String key) { return (value) -> put(key, value); } + } + } diff --git a/spring-pulsar-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-pulsar-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports similarity index 100% rename from spring-pulsar-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports rename to spring-pulsar-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports diff --git a/spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/AbstractContainerBaseTests.java b/spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/AbstractContainerBaseTests.java similarity index 100% rename from spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/AbstractContainerBaseTests.java rename to spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/AbstractContainerBaseTests.java diff --git a/spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfigurationTests.java b/spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfigurationTests.java similarity index 72% rename from spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfigurationTests.java rename to spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfigurationTests.java index fc8fed8e..4c0c6edd 100644 --- a/spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfigurationTests.java +++ b/spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarAutoConfigurationTests.java @@ -57,8 +57,7 @@ class PulsarAutoConfigurationTests { @Test void autoConfigurationSkippedWhenPulsarTemplateNotOnClasspath() { this.contextRunner.withClassLoader(new FilteredClassLoader(PulsarTemplate.class)) - .run((context) -> assertThat(context).hasNotFailed() - .doesNotHaveBean(PulsarAutoConfiguration.class)); + .run((context) -> assertThat(context).hasNotFailed().doesNotHaveBean(PulsarAutoConfiguration.class)); } @Test @@ -71,8 +70,8 @@ class PulsarAutoConfigurationTests { @Test void bootstrapConfigurationSkippedWhenCustomPulsarListenerAnnotationProcessorDefined() { this.contextRunner - .withBean("org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor", - String.class, () -> "someFauxBean") + .withBean("org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor", String.class, + () -> "someFauxBean") .run((context) -> assertThat(context).hasNotFailed() .doesNotHaveBean(PulsarBootstrapConfiguration.class)); } @@ -80,28 +79,27 @@ class PulsarAutoConfigurationTests { @Test void defaultBeansAreAutoConfigured() { this.contextRunner.run((context) -> assertThat(context).hasNotFailed() - .hasSingleBean(PulsarClientConfiguration.class) - .hasSingleBean(PulsarClientFactoryBean.class) - .hasSingleBean(PulsarProducerFactory.class) - .hasSingleBean(PulsarTemplate.class) - .hasSingleBean(PulsarConsumerFactory.class) - .hasSingleBean(DefaultPulsarListenerContainerFactory.class) + .hasSingleBean(PulsarClientConfiguration.class).hasSingleBean(PulsarClientFactoryBean.class) + .hasSingleBean(PulsarProducerFactory.class).hasSingleBean(PulsarTemplate.class) + .hasSingleBean(PulsarConsumerFactory.class).hasSingleBean(DefaultPulsarListenerContainerFactory.class) .hasSingleBean(PulsarListenerAnnotationBeanPostProcessor.class) .hasSingleBean(PulsarListenerEndpointRegistry.class)); } @Test void customPulsarClientConfigurationIsRespected() { - PulsarClientConfiguration clientConfig = new PulsarClientConfiguration(new PulsarProperties().buildClientProperties()); + PulsarClientConfiguration clientConfig = new PulsarClientConfiguration( + new PulsarProperties().buildClientProperties()); this.contextRunner .withBean("customPulsarClientConfiguration", PulsarClientConfiguration.class, () -> clientConfig) - .run((context) -> assertThat(context).hasNotFailed() - .getBean(PulsarClientConfiguration.class).isSameAs(clientConfig)); + .run((context) -> assertThat(context).hasNotFailed().getBean(PulsarClientConfiguration.class) + .isSameAs(clientConfig)); } @Test void customPulsarClientFactoryBeanIsRespected() { - PulsarClientConfiguration clientConfig = new PulsarClientConfiguration(new PulsarProperties().buildClientProperties()); + PulsarClientConfiguration clientConfig = new PulsarClientConfiguration( + new PulsarProperties().buildClientProperties()); PulsarClientFactoryBean clientFactoryBean = new PulsarClientFactoryBean(clientConfig); this.contextRunner .withBean("customPulsarClientFactoryBean", PulsarClientFactoryBean.class, () -> clientFactoryBean) @@ -113,47 +111,47 @@ class PulsarAutoConfigurationTests { @Test void customPulsarProducerFactoryIsRespected() { PulsarProducerFactory producerFactory = mock(PulsarProducerFactory.class); - this.contextRunner - .withBean("customPulsarProducerFactory", PulsarProducerFactory.class, () -> producerFactory) - .run((context) -> assertThat(context).hasNotFailed() - .getBean(PulsarProducerFactory.class).isSameAs(producerFactory)); + this.contextRunner.withBean("customPulsarProducerFactory", PulsarProducerFactory.class, () -> producerFactory) + .run((context) -> assertThat(context).hasNotFailed().getBean(PulsarProducerFactory.class) + .isSameAs(producerFactory)); } @Test void customPulsarTemplateIsRespected() { PulsarTemplate template = mock(PulsarTemplate.class); - this.contextRunner - .withBean("customPulsarTemplate", PulsarTemplate.class, () -> template) - .run((context) -> assertThat(context).hasNotFailed() - .getBean(PulsarTemplate.class).isSameAs(template)); + this.contextRunner.withBean("customPulsarTemplate", PulsarTemplate.class, () -> template) + .run((context) -> assertThat(context).hasNotFailed().getBean(PulsarTemplate.class).isSameAs(template)); } @Test void customPulsarConsumerFactoryIsRespected() { PulsarConsumerFactory consumerFactory = mock(PulsarConsumerFactory.class); - this.contextRunner - .withBean("customPulsarConsumerFactory", PulsarConsumerFactory.class, () -> consumerFactory) - .run((context) -> assertThat(context).hasNotFailed() - .getBean(PulsarConsumerFactory.class).isSameAs(consumerFactory)); + this.contextRunner.withBean("customPulsarConsumerFactory", PulsarConsumerFactory.class, () -> consumerFactory) + .run((context) -> assertThat(context).hasNotFailed().getBean(PulsarConsumerFactory.class) + .isSameAs(consumerFactory)); } @Test void customPulsarListenerContainerFactoryIsRespected() { - PulsarListenerContainerFactory> listenerContainerFactory = mock(PulsarListenerContainerFactory.class); + PulsarListenerContainerFactory> listenerContainerFactory = mock( + PulsarListenerContainerFactory.class); this.contextRunner - .withBean("pulsarListenerContainerFactory", PulsarListenerContainerFactory.class, () -> listenerContainerFactory) - .run((context) -> assertThat(context).hasNotFailed() - .getBean(PulsarListenerContainerFactory.class).isSameAs(listenerContainerFactory)); + .withBean("pulsarListenerContainerFactory", PulsarListenerContainerFactory.class, + () -> listenerContainerFactory) + .run((context) -> assertThat(context).hasNotFailed().getBean(PulsarListenerContainerFactory.class) + .isSameAs(listenerContainerFactory)); } @Test void customPulsarListenerAnnotationBeanPostProcessorIsRespected() { - PulsarListenerAnnotationBeanPostProcessor listenerAnnotationBeanPostProcessor = mock(PulsarListenerAnnotationBeanPostProcessor.class); + PulsarListenerAnnotationBeanPostProcessor listenerAnnotationBeanPostProcessor = mock( + PulsarListenerAnnotationBeanPostProcessor.class); this.contextRunner .withBean("org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor", PulsarListenerAnnotationBeanPostProcessor.class, () -> listenerAnnotationBeanPostProcessor) .run((context) -> assertThat(context).hasNotFailed() - .getBean(PulsarListenerAnnotationBeanPostProcessor.class).isSameAs(listenerAnnotationBeanPostProcessor)); + .getBean(PulsarListenerAnnotationBeanPostProcessor.class) + .isSameAs(listenerAnnotationBeanPostProcessor)); } @Nested @@ -178,24 +176,22 @@ class PulsarAutoConfigurationTests { @Test void cachingProducerFactoryCanBeConfigured() { - contextRunner.withPropertyValues( - "spring.pulsar.producer.cache.expire-after-access=100s", + contextRunner + .withPropertyValues("spring.pulsar.producer.cache.expire-after-access=100s", "spring.pulsar.producer.cache.maximum-size=5150", "spring.pulsar.producer.cache.initial-capacity=200") - .run((context -> assertThat(context) - .hasNotFailed() - .getBean(PulsarProducerFactory.class) - .extracting("producerCache") - .extracting("cache") + .run((context -> assertThat(context).hasNotFailed().getBean(PulsarProducerFactory.class) + .extracting("producerCache").extracting("cache") .hasFieldOrPropertyWithValue("maximum", 5150L) .hasFieldOrPropertyWithValue("expiresAfterAccessNanos", TimeUnit.SECONDS.toNanos(100)))); } - private void assertHasProducerFactoryOfType(Class producerFactoryType, AssertableApplicationContext context) { - assertThat(context).hasNotFailed() - .hasSingleBean(PulsarProducerFactory.class).getBean(PulsarProducerFactory.class) - .isExactlyInstanceOf(producerFactoryType); + private void assertHasProducerFactoryOfType(Class producerFactoryType, + AssertableApplicationContext context) { + assertThat(context).hasNotFailed().hasSingleBean(PulsarProducerFactory.class) + .getBean(PulsarProducerFactory.class).isExactlyInstanceOf(producerFactoryType); } + } } diff --git a/spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarListenerTests.java b/spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarListenerTests.java similarity index 90% rename from spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarListenerTests.java rename to spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarListenerTests.java index 9dbd35e6..856906fe 100644 --- a/spring-pulsar-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarListenerTests.java +++ b/spring-pulsar-spring-boot-autoconfigure/src/test/java/org/springframework/pulsar/autoconfigure/PulsarListenerTests.java @@ -48,7 +48,8 @@ class PulsarListenerTests extends AbstractContainerBaseTests { SpringApplication app = new SpringApplication(BasicListenerConfig.class); app.setWebApplicationType(WebApplicationType.NONE); - try (ConfigurableApplicationContext context = app.run("--spring.pulsar.client.serviceUrl=" + AbstractContainerBaseTests.getPulsarBrokerUrl())) { + try (ConfigurableApplicationContext context = app + .run("--spring.pulsar.client.serviceUrl=" + AbstractContainerBaseTests.getPulsarBrokerUrl())) { @SuppressWarnings("unchecked") final PulsarTemplate pulsarTemplate = context.getBean(PulsarTemplate.class); pulsarTemplate.send("hello-pulsar-exclusive", "John Doe"); @@ -62,7 +63,8 @@ class PulsarListenerTests extends AbstractContainerBaseTests { SpringApplication app = new SpringApplication(BatchListenerConfig.class); app.setWebApplicationType(WebApplicationType.NONE); - try (ConfigurableApplicationContext context = app.run("--spring.pulsar.client.serviceUrl=" + AbstractContainerBaseTests.getPulsarBrokerUrl())) { + try (ConfigurableApplicationContext context = app + .run("--spring.pulsar.client.serviceUrl=" + AbstractContainerBaseTests.getPulsarBrokerUrl())) { @SuppressWarnings("unchecked") final PulsarTemplate pulsarTemplate = context.getBean(PulsarTemplate.class); for (int i = 0; i < 10; i++) { @@ -81,6 +83,7 @@ class PulsarListenerTests extends AbstractContainerBaseTests { public void listen(String foo) { latch1.countDown(); } + } @Configuration @@ -91,5 +94,7 @@ class PulsarListenerTests extends AbstractContainerBaseTests { public void listen(List foo) { foo.forEach(t -> latch2.countDown()); } + } + } diff --git a/spring-pulsar/build.gradle b/spring-pulsar/build.gradle new file mode 100644 index 00000000..1368eac1 --- /dev/null +++ b/spring-pulsar/build.gradle @@ -0,0 +1,50 @@ +plugins { + id 'org.springframework.pulsar.spring-module' +} + +description = 'Spring Pulsar Support' + +dependencies { + api 'org.springframework:spring-context' + api 'org.springframework:spring-messaging' + api 'org.springframework:spring-tx' + api ('org.springframework.retry:spring-retry') { + exclude group: 'org.springframework' + } + api 'org.apache.pulsar:pulsar-client' + api 'org.apache.pulsar:pulsar-client-admin' + api 'org.apache.pulsar:pulsar-client-admin-api' + api 'com.github.ben-manes.caffeine:caffeine' + + optional 'com.fasterxml.jackson.core:jackson-core' + optional 'com.fasterxml.jackson.core:jackson-databind' + optional 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' + optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' + optional 'com.fasterxml.jackson.datatype:jackson-datatype-joda' + optional 'com.jayway.jsonpath:json-path' + optional 'io.projectreactor:reactor-core' + + testImplementation 'io.projectreactor:reactor-test' + testImplementation 'org.mockito:mockito-junit-jupiter' + testImplementation 'org.hibernate.validator:hibernate-validator' + + // TODO remove unused dependencies + + // Common to all ?? + implementation 'com.google.code.findbugs:jsr305' + testImplementation 'org.junit.jupiter:junit-jupiter-api' + testImplementation 'org.junit.jupiter:junit-jupiter-params' + testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // To avoid compiler warnings about @API annotations in JUnit code + testCompileOnly 'org.apiguardian:apiguardian-api' + testRuntimeOnly 'org.apache.logging.log4j:log4j-core' + testRuntimeOnly 'org.apache.logging.log4j:log4j-jcl' + testImplementation("org.awaitility:awaitility") { + exclude group: 'org.hamcrest' + } + testImplementation 'org.hamcrest:hamcrest-core' + testImplementation 'org.assertj:assertj-core' + testImplementation 'org.testcontainers:pulsar' + testImplementation 'org.springframework:spring-test' +} diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/PulsarException.java b/spring-pulsar/src/main/java/org/springframework/pulsar/PulsarException.java index e6302754..e6a48a41 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/PulsarException.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/PulsarException.java @@ -33,4 +33,5 @@ public class PulsarException extends NestedRuntimeException { public PulsarException(String msg, Throwable cause) { super(msg, cause); } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/EnablePulsar.java b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/EnablePulsar.java index 14c0fbe6..7771719b 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/EnablePulsar.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/EnablePulsar.java @@ -25,7 +25,8 @@ import java.lang.annotation.Target; import org.springframework.context.annotation.Import; /** - * Enables detection of {@link PulsarListener} annotations on any Spring-managed bean in the container. + * Enables detection of {@link PulsarListener} annotations on any Spring-managed bean in + * the container. * * @author Soby Chacko * @author Chris Bono @@ -35,4 +36,5 @@ import org.springframework.context.annotation.Import; @Documented @Import(PulsarListenerConfigurationSelector.class) public @interface EnablePulsar { + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarBootstrapConfiguration.java b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarBootstrapConfiguration.java index 65c7c553..4cfe91c9 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarBootstrapConfiguration.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarBootstrapConfiguration.java @@ -24,16 +24,17 @@ import org.springframework.pulsar.config.PulsarListenerBeanNames; import org.springframework.pulsar.config.PulsarListenerEndpointRegistry; /** - * An {@link ImportBeanDefinitionRegistrar} class that registers a {@link PulsarListenerAnnotationBeanPostProcessor} - * bean capable of processing Spring's @{@link PulsarListener} annotation. Also register - * a default {@link PulsarListenerEndpointRegistry}. + * An {@link ImportBeanDefinitionRegistrar} class that registers a + * {@link PulsarListenerAnnotationBeanPostProcessor} bean capable of processing + * Spring's @{@link PulsarListener} annotation. Also register a default + * {@link PulsarListenerEndpointRegistry}. * - *

This configuration class is automatically imported when using the @{@link EnablePulsar} + *

+ * This configuration class is automatically imported when using the @{@link EnablePulsar} * annotation. * * @author Soby Chacko * @author Chris Bono - * * @see PulsarListenerAnnotationBeanPostProcessor * @see PulsarListenerEndpointRegistry * @see EnablePulsar diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListener.java b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListener.java index f5a05855..bccd1408 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListener.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListener.java @@ -33,10 +33,10 @@ import org.springframework.pulsar.config.PulsarListenerEndpointRegistry; * specified topics. * * The {@link #containerFactory()} identifies the - * {@link org.springframework.pulsar.config.PulsarListenerContainerFactory} to use to build the Pulsar listener container. - * If not set, a default container factory is assumed to be available with a bean name - * of {@code pulsarListenerContainerFactory} unless an explicit default has been provided - * through configuration. + * {@link org.springframework.pulsar.config.PulsarListenerContainerFactory} to use to + * build the Pulsar listener container. If not set, a default container factory + * is assumed to be available with a bean name of {@code pulsarListenerContainerFactory} + * unless an explicit default has been provided through configuration. * *

* Processing of {@code @PulsarListener} annotations is performed by registering a @@ -55,8 +55,10 @@ public @interface PulsarListener { /** * The unique identifier of the container for this listener. - *

If none is specified an auto-generated id is used. - *

SpEL {@code #{...}} and property place holders {@code ${...}} are supported. + *

+ * If none is specified an auto-generated id is used. + *

+ * SpEL {@code #{...}} and property place holders {@code ${...}} are supported. * @return the {@code id} for the container managing for this endpoint. * @see PulsarListenerEndpointRegistry#getListenerContainer(String) */ @@ -77,8 +79,8 @@ public @interface PulsarListener { SchemaType schemaType() default SchemaType.NONE; /** - * The bean name of the {@link PulsarListenerContainerFactory} - * to use to create the message listener container responsible to serve this endpoint. + * The bean name of the {@link PulsarListenerContainerFactory} to use to create the + * message listener container responsible to serve this endpoint. *

* If not specified, the default container factory is used, if any. If a SpEL * expression is provided ({@code #{...}}), the expression can either evaluate to a @@ -89,14 +91,12 @@ public @interface PulsarListener { /** * Topics to listen to. - * * @return a comma separated list of topics to listen from. */ String[] topics() default {}; /** * Topic patten to listen to. - * * @return topic pattern to listen to. */ String topicPattern() default ""; @@ -106,23 +106,22 @@ public @interface PulsarListener { * be a property placeholder or SpEL expression that evaluates to a {@link Boolean} or * a {@link String}, in which case the {@link Boolean#parseBoolean(String)} is used to * obtain the value. - *

SpEL {@code #{...}} and property place holders {@code ${...}} are supported. + *

+ * SpEL {@code #{...}} and property place holders {@code ${...}} are supported. * @return true to auto start, false to not auto start. */ String autoStartup() default ""; /** * Activate batch consumption. - * * @return whether this listener is in batch mode or not. */ boolean batch() default false; /** - * A pseudo bean name used in SpEL expressions within this annotation to reference - * the current bean within which this listener is defined. This allows access to - * properties and methods within the enclosing bean. - * Default '__listener'. + * A pseudo bean name used in SpEL expressions within this annotation to reference the + * current bean within which this listener is defined. This allows access to + * properties and methods within the enclosing bean. Default '__listener'. *

* @return the pseudo bean name. */ @@ -130,23 +129,27 @@ public @interface PulsarListener { /** * Pulsar consumer properties; they will supersede any properties with the same name - * defined in the consumer factory (if the consumer factory supports property overrides). + * defined in the consumer factory (if the consumer factory supports property + * overrides). *

* Supported Syntax - *

The supported syntax for key-value pairs is the same as the - * syntax defined for entries in a Java - * {@linkplain java.util.Properties#load(java.io.Reader) properties file}: + *

+ * The supported syntax for key-value pairs is the same as the syntax defined for + * entries in a Java {@linkplain java.util.Properties#load(java.io.Reader) properties + * file}: *

    *
  • {@code key=value}
  • *
  • {@code key:value}
  • *
  • {@code key value}
  • *
* {@code group.id} and {@code client.id} are ignored. - *

SpEL {@code #{...}} and property place holders {@code ${...}} are supported. - * SpEL expressions must resolve to a {@link String}, a @{link String[]} or a + *

+ * SpEL {@code #{...}} and property place holders {@code ${...}} are supported. SpEL + * expressions must resolve to a {@link String}, a @{link String[]} or a * {@code Collection} where each member of the array or collection is a * property name + value with the above formats. * @return the properties. */ String[] properties() default {}; + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerAnnotationBeanPostProcessor.java b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerAnnotationBeanPostProcessor.java index fde119f5..8e4b2716 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerAnnotationBeanPostProcessor.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerAnnotationBeanPostProcessor.java @@ -89,27 +89,26 @@ import org.springframework.util.StringUtils; import org.springframework.validation.Validator; /** - * Bean post-processor that registers methods annotated with {@link PulsarListener} - * to be invoked by a Pulsar message listener container created under the covers - * by a {@link PulsarListenerContainerFactory} - * according to the parameters of the annotation. + * Bean post-processor that registers methods annotated with {@link PulsarListener} to be + * invoked by a Pulsar message listener container created under the covers by a + * {@link PulsarListenerContainerFactory} according to the parameters of the annotation. * - *

Annotated methods can use flexible arguments as defined by {@link PulsarListener}. + *

+ * Annotated methods can use flexible arguments as defined by {@link PulsarListener}. * - *

This post-processor is automatically registered by the {@link EnablePulsar} - * annotation. + *

+ * This post-processor is automatically registered by the {@link EnablePulsar} annotation. * - *

Auto-detect any {@link PulsarListenerConfigurer} instances in the container, - * allowing for customization of the registry to be used, the default container - * factory or for fine-grained control over endpoints registration. See - * {@link EnablePulsar} Javadoc for complete usage details. + *

+ * Auto-detect any {@link PulsarListenerConfigurer} instances in the container, allowing + * for customization of the registry to be used, the default container factory or for + * fine-grained control over endpoints registration. See {@link EnablePulsar} Javadoc for + * complete usage details. * * @param the key type. * @param the value type. - * * @author Soby Chacko * @author Chris Bono - * * @see PulsarListener * @see EnablePulsar * @see PulsarListenerConfigurer @@ -118,12 +117,14 @@ import org.springframework.validation.Validator; * @see PulsarListenerEndpoint * @see MethodPulsarListenerEndpoint */ -public class PulsarListenerAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered, ApplicationContextAware, InitializingBean, SmartInitializingSingleton { +public class PulsarListenerAnnotationBeanPostProcessor + implements BeanPostProcessor, Ordered, ApplicationContextAware, InitializingBean, SmartInitializingSingleton { private final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass())); /** - * The bean name of the default {@link org.springframework.pulsar.config.PulsarListenerContainerFactory}. + * The bean name of the default + * {@link org.springframework.pulsar.config.PulsarListenerContainerFactory}. */ public static final String DEFAULT_PULSAR_LISTENER_CONTAINER_FACTORY_BEAN_NAME = "pulsarListenerContainerFactory"; @@ -157,12 +158,10 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost private final ListenerScope listenerScope = new ListenerScope(); - private AnnotationEnhancer enhancer; private final AtomicInteger counter = new AtomicInteger(); - @Override public int getOrder() { return LOWEST_PRECEDENCE; @@ -189,9 +188,7 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost private void buildEnhancer() { if (this.applicationContext != null) { List enhancers = this.applicationContext - .getBeanProvider(AnnotationEnhancer.class, false) - .orderedStream() - .toList(); + .getBeanProvider(AnnotationEnhancer.class, false).orderedStream().toList(); if (!enhancers.isEmpty()) { this.enhancer = (attrs, element) -> { for (AnnotationEnhancer enh : enhancers) { @@ -225,7 +222,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost this.registrar.setContainerFactoryBeanName(this.defaultContainerFactoryBeanName); } - // Set the custom handler method factory once resolved by the configurer - otherwise register default formatters + // Set the custom handler method factory once resolved by the configurer - + // otherwise register default formatters MessageHandlerMethodFactory handlerMethodFactory = this.registrar.getMessageHandlerMethodFactory(); if (handlerMethodFactory != null) { this.messageHandlerMethodFactory.setHandlerMethodFactory(handlerMethodFactory); @@ -283,8 +281,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost this.listenerScope.removeListener(beanRef); } - protected void processListener(MethodPulsarListenerEndpoint endpoint, PulsarListener PulsarListener, - Object bean, String beanName, String[] topics) { + protected void processListener(MethodPulsarListenerEndpoint endpoint, PulsarListener PulsarListener, Object bean, + String beanName, String[] topics) { processPulsarListenerAnnotation(endpoint, PulsarListener, bean, topics); @@ -297,7 +295,7 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost @Nullable private PulsarListenerContainerFactory resolveContainerFactory(PulsarListener PulsarListener, - Object factoryTarget, String beanName) { + Object factoryTarget, String beanName) { String containerFactory = PulsarListener.containerFactory(); if (!StringUtils.hasText(containerFactory)) { @@ -310,17 +308,15 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost if (resolved instanceof PulsarListenerContainerFactory) { return (PulsarListenerContainerFactory) resolved; } - String containerFactoryBeanName = resolveExpressionAsString(containerFactory, - "containerFactory"); + String containerFactoryBeanName = resolveExpressionAsString(containerFactory, "containerFactory"); if (StringUtils.hasText(containerFactoryBeanName)) { assertBeanFactory(); try { factory = this.beanFactory.getBean(containerFactoryBeanName, PulsarListenerContainerFactory.class); } catch (NoSuchBeanDefinitionException ex) { - throw new BeanInitializationException( - noBeanFoundMessage(factoryTarget, beanName, containerFactoryBeanName, - PulsarListenerContainerFactory.class), ex); + throw new BeanInitializationException(noBeanFoundMessage(factoryTarget, beanName, + containerFactoryBeanName, PulsarListenerContainerFactory.class), ex); } } return factory; @@ -331,15 +327,15 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } protected String noBeanFoundMessage(Object target, String listenerBeanName, String requestedBeanName, - Class expectedClass) { + Class expectedClass) { - return "Could not register Pulsar listener endpoint on [" - + target + "] for bean " + listenerBeanName + ", no '" + expectedClass.getSimpleName() + "' with id '" - + requestedBeanName + "' was found in the application context"; + return "Could not register Pulsar listener endpoint on [" + target + "] for bean " + listenerBeanName + ", no '" + + expectedClass.getSimpleName() + "' with id '" + requestedBeanName + + "' was found in the application context"; } private void processPulsarListenerAnnotation(MethodPulsarListenerEndpoint endpoint, - PulsarListener pulsarListener, Object bean, String[] topics) { + PulsarListener pulsarListener, Object bean, String[] topics) { endpoint.setBean(bean); endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory); @@ -397,8 +393,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } } else { - throw new IllegalStateException("'properties' must resolve to a String, a String[] or " - + "Collection"); + throw new IllegalStateException( + "'properties' must resolve to a String, a String[] or " + "Collection"); } } endpoint.setConsumerProperties(properties); @@ -452,8 +448,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost return (String) resolved; } else if (resolved != null) { - throw new IllegalStateException(THE_LEFT + attribute + "] must resolve to a String. " - + RESOLVED_TO_LEFT + resolved.getClass() + RIGHT_FOR_LEFT + value + "]"); + throw new IllegalStateException(THE_LEFT + attribute + "] must resolve to a String. " + RESOLVED_TO_LEFT + + resolved.getClass() + RIGHT_FOR_LEFT + value + "]"); } return null; } @@ -497,8 +493,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } } else { - throw new IllegalArgumentException(String.format( - "@PulsarListener can't resolve '%s' as a String", resolvedValue)); + throw new IllegalArgumentException( + String.format("@PulsarListener can't resolve '%s' as a String", resolvedValue)); } } @@ -506,7 +502,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost Method method = methodArg; if (AopUtils.isJdkDynamicProxy(bean)) { try { - // Found a @PulsarListener method on the target class for this JDK proxy -> + // Found a @PulsarListener method on the target class for this JDK proxy + // -> // is it also present on the proxy itself? method = bean.getClass().getMethod(method.getName(), method.getParameterTypes()); Class[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces(); @@ -525,12 +522,11 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } catch (NoSuchMethodException ex) { throw new IllegalStateException(String.format( - "@PulsarListener method '%s' found on bean target class '%s', " + - "but not found in any interface(s) for bean JDK proxy. Either " + - "pull the method up to an interface or switch to subclass (CGLIB) " + - "proxies by setting proxy-target-class/proxyTargetClass " + - "attribute to 'true'", method.getName(), - method.getDeclaringClass().getSimpleName()), ex); + "@PulsarListener method '%s' found on bean target class '%s', " + + "but not found in any interface(s) for bean JDK proxy. Either " + + "pull the method up to an interface or switch to subclass (CGLIB) " + + "proxies by setting proxy-target-class/proxyTargetClass " + "attribute to 'true'", + method.getName(), method.getDeclaringClass().getSimpleName()), ex); } } return method; @@ -545,9 +541,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } PulsarListeners anns = AnnotationUtils.findAnnotation(clazz, PulsarListeners.class); if (anns != null) { - listeners.addAll(Arrays.stream(anns.value()) - .map(anno -> enhance(clazz, anno)) - .collect(Collectors.toList())); + listeners + .addAll(Arrays.stream(anns.value()).map(anno -> enhance(clazz, anno)).collect(Collectors.toList())); } return listeners; } @@ -561,9 +556,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } PulsarListeners anns = AnnotationUtils.findAnnotation(method, PulsarListeners.class); if (anns != null) { - listeners.addAll(Arrays.stream(anns.value()) - .map(anno -> enhance(method, anno)) - .collect(Collectors.toList())); + listeners.addAll( + Arrays.stream(anns.value()).map(anno -> enhance(method, anno)).collect(Collectors.toList())); } return listeners; } @@ -574,11 +568,11 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } else { return AnnotationUtils.synthesizeAnnotation( - this.enhancer.apply(AnnotationUtils.getAnnotationAttributes(ann), element), PulsarListener.class, null); + this.enhancer.apply(AnnotationUtils.getAnnotationAttributes(ann), element), PulsarListener.class, + null); } } - private void addFormatters(FormatterRegistry registry) { this.beanFactory.getBeanProvider(Converter.class).forEach(registry::addConverter); this.beanFactory.getBeanProvider(GenericConverter.class).forEach(registry::addConverter); @@ -607,8 +601,7 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost private class PulsarHandlerMethodFactoryAdapter implements MessageHandlerMethodFactory { - private final DefaultFormattingConversionService defaultFormattingConversionService = - new DefaultFormattingConversionService(); + private final DefaultFormattingConversionService defaultFormattingConversionService = new DefaultFormattingConversionService(); private MessageHandlerMethodFactory handlerMethodFactory; @@ -635,17 +628,19 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost defaultFactory.setValidator(validator); } defaultFactory.setBeanFactory(PulsarListenerAnnotationBeanPostProcessor.this.beanFactory); - this.defaultFormattingConversionService.addConverter( - new BytesToStringConverter(PulsarListenerAnnotationBeanPostProcessor.this.charset)); + this.defaultFormattingConversionService + .addConverter(new BytesToStringConverter(PulsarListenerAnnotationBeanPostProcessor.this.charset)); this.defaultFormattingConversionService.addConverter(new BytesToNumberConverter()); defaultFactory.setConversionService(this.defaultFormattingConversionService); - GenericMessageConverter messageConverter = new GenericMessageConverter(this.defaultFormattingConversionService); + GenericMessageConverter messageConverter = new GenericMessageConverter( + this.defaultFormattingConversionService); defaultFactory.setMessageConverter(messageConverter); - List customArgumentsResolver = - new ArrayList<>(PulsarListenerAnnotationBeanPostProcessor.this.registrar.getCustomMethodArgumentResolvers()); + List customArgumentsResolver = new ArrayList<>( + PulsarListenerAnnotationBeanPostProcessor.this.registrar.getCustomMethodArgumentResolvers()); // Has to be at the end - look at PayloadMethodArgumentResolver documentation - //customArgumentsResolver.add(new PulsarNullAwarePayloadArgumentResolver(messageConverter, validator)); + // customArgumentsResolver.add(new + // PulsarNullAwarePayloadArgumentResolver(messageConverter, validator)); defaultFactory.setCustomArgumentResolvers(customArgumentsResolver); defaultFactory.afterPropertiesSet(); @@ -657,7 +652,6 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost private static class BytesToStringConverter implements Converter { - private final Charset charset; BytesToStringConverter(Charset charset) { @@ -770,10 +764,8 @@ public class PulsarListenerAnnotationBeanPostProcessor implements BeanPost } - public interface AnnotationEnhancer extends BiFunction, AnnotatedElement, Map> { } - } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerConfigurer.java b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerConfigurer.java index 11075f15..714c985a 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerConfigurer.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListenerConfigurer.java @@ -22,26 +22,25 @@ import org.springframework.pulsar.config.PulsarListenerEndpointRegistrar; import org.springframework.pulsar.config.PulsarListenerEndpointRegistry; /** - * Optional interface to be implemented by Spring managed bean willing to - * customize how Pulsar listener endpoints are configured. Typically used - * to define the default {@link PulsarListenerContainerFactory} to use or - * for registering Pulsar endpoints in a programmatic fashion as - * opposed to the declarative approach of using the - * {@link PulsarListener} annotation. + * Optional interface to be implemented by Spring managed bean willing to customize how + * Pulsar listener endpoints are configured. Typically used to define the default + * {@link PulsarListenerContainerFactory} to use or for registering Pulsar endpoints in a + * programmatic fashion as opposed to the declarative approach of using + * the {@link PulsarListener} annotation. * * @author Soby Chacko * @author Chris Bono - * * @see PulsarListenerEndpointRegistrar */ public interface PulsarListenerConfigurer { /** * Callback allowing a {@link PulsarListenerEndpointRegistry} and specific - * {@link PulsarListenerEndpoint} instances to be registered against the - * given {@link PulsarListenerEndpointRegistrar}. The default + * {@link PulsarListenerEndpoint} instances to be registered against the given + * {@link PulsarListenerEndpointRegistrar}. The default * {@link PulsarListenerContainerFactory} can also be customized. * @param registrar the registrar to be configured */ void configurePulsarListeners(PulsarListenerEndpointRegistrar registrar); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListeners.java b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListeners.java index 8dc83547..0da3bde9 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListeners.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/annotation/PulsarListeners.java @@ -25,10 +25,10 @@ import java.lang.annotation.Target; /** * Container annotation that aggregates several {@link PulsarListener} annotations. *

- * Can be used natively, declaring several nested {@link PulsarListener} annotations. - * Can also be used in conjunction with Java 8's support for repeatable annotations, - * where {@link PulsarListener} can simply be declared several times on the same method - * (or class), implicitly generating this container annotation. + * Can be used natively, declaring several nested {@link PulsarListener} annotations. Can + * also be used in conjunction with Java 8's support for repeatable annotations, where + * {@link PulsarListener} can simply be declared several times on the same method (or + * class), implicitly generating this container annotation. * * @author Soby Chacko * @author Chris Bono diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/AbstractPulsarListenerContainerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/AbstractPulsarListenerContainerFactory.java index 3a4a7ec2..75ca563b 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/AbstractPulsarListenerContainerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/AbstractPulsarListenerContainerFactory.java @@ -37,14 +37,14 @@ import org.springframework.pulsar.support.MessageConverter; * * @param the {@link AbstractPulsarMessageListenerContainer} implementation type. * @param Message payload type. - * * @author Soby Chacko */ public abstract class AbstractPulsarListenerContainerFactory, T> implements PulsarListenerContainerFactory, ApplicationEventPublisherAware, InitializingBean, ApplicationContextAware { - protected final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass())); // NOSONAR protected + protected final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass())); // NOSONAR + // protected private final PulsarContainerProperties containerProperties = new PulsarContainerProperties(); @@ -67,7 +67,6 @@ public abstract class AbstractPulsarListenerContainerFactory consumerFactory) { this.consumerFactory = consumerFactory; } @@ -76,12 +75,10 @@ public abstract class AbstractPulsarListenerContainerFactory) endpoint); } endpoint.setupListenerContainer(instance, this.messageConverter); initializeContainer(instance, endpoint); - //customizeContainer(instance); + // customizeContainer(instance); return instance; } @@ -140,20 +133,19 @@ public abstract class AbstractPulsarListenerContainerFactory aplEndpoint) { if (aplEndpoint.getBatchListener() == null) { - JavaUtils.INSTANCE - .acceptIfNotNull(this.batchListener, aplEndpoint::setBatchListener); + JavaUtils.INSTANCE.acceptIfNotNull(this.batchListener, aplEndpoint::setBatchListener); } } protected void initializeContainer(C instance, PulsarListenerEndpoint endpoint) { PulsarContainerProperties properties = instance.getPulsarContainerProperties(); -// BeanUtils.copyProperties(this.containerProperties, properties, "topics", "messageListener", -// "batchListener", "subscriptionName", "subscriptionType", "schema"); + // BeanUtils.copyProperties(this.containerProperties, properties, "topics", + // "messageListener", + // "batchListener", "subscriptionName", "subscriptionType", "schema"); if (properties.getSchema() == null) { properties.setSchema(Schema.BYTES); } - Boolean autoStart = endpoint.getAutoStartup(); if (autoStart != null) { instance.setAutoStartup(autoStart); @@ -162,8 +154,7 @@ public abstract class AbstractPulsarListenerContainerFactory Message payload type. - * * @author Soby Chacko */ -public abstract class AbstractPulsarListenerEndpoint implements PulsarListenerEndpoint, BeanFactoryAware, InitializingBean { +public abstract class AbstractPulsarListenerEndpoint + implements PulsarListenerEndpoint, BeanFactoryAware, InitializingBean { private final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass())); @@ -72,7 +72,9 @@ public abstract class AbstractPulsarListenerEndpoint implements PulsarListene private BeanResolver beanResolver; private Boolean autoStartup; + private Properties consumerProperties; + private Boolean batchListener; @Override @@ -156,25 +158,24 @@ public abstract class AbstractPulsarListenerEndpoint implements PulsarListene @Override public void setupListenerContainer(PulsarMessageListenerContainer listenerContainer, - @Nullable MessageConverter messageConverter) { + @Nullable MessageConverter messageConverter) { setupMessageListener(listenerContainer, messageConverter); } @SuppressWarnings("unchecked") private void setupMessageListener(PulsarMessageListenerContainer container, - @Nullable MessageConverter messageConverter) { + @Nullable MessageConverter messageConverter) { PulsarMessagingMessageListenerAdapter adapter = createMessageListener(container, messageConverter); Object messageListener = adapter; boolean isBatchListener = isBatchListener(); - Assert.state(messageListener != null, - () -> "Endpoint [" + this + "] must provide a non null message listener"); + Assert.state(messageListener != null, () -> "Endpoint [" + this + "] must provide a non null message listener"); container.setupMessageListener(messageListener); } - protected abstract PulsarMessagingMessageListenerAdapter createMessageListener(PulsarMessageListenerContainer container, - @Nullable MessageConverter messageConverter); + protected abstract PulsarMessagingMessageListenerAdapter createMessageListener( + PulsarMessageListenerContainer container, @Nullable MessageConverter messageConverter); public void setConsumerProperties(Properties consumerProperties) { this.consumerProperties = consumerProperties; @@ -185,7 +186,6 @@ public abstract class AbstractPulsarListenerEndpoint implements PulsarListene return this.batchListener; } - public void setBatchListener(boolean batchListener) { this.batchListener = batchListener; } @@ -194,7 +194,6 @@ public abstract class AbstractPulsarListenerEndpoint implements PulsarListene return this.batchListener == null ? false : this.batchListener; } - public SubscriptionType getSubscriptionType() { return this.subscriptionType; } @@ -210,4 +209,5 @@ public abstract class AbstractPulsarListenerEndpoint implements PulsarListene public void setSchemaType(SchemaType schemaType) { this.schemaType = schemaType; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/DefaultPulsarListenerContainerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/DefaultPulsarListenerContainerFactory.java index ce9971a5..fcef2bbf 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/DefaultPulsarListenerContainerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/DefaultPulsarListenerContainerFactory.java @@ -30,11 +30,11 @@ import org.springframework.util.StringUtils; * * @param container implementation type. * @param message type in the listener. - * * @author Soby Chacko * @author Chris Bono */ -public class DefaultPulsarListenerContainerFactory extends AbstractPulsarListenerContainerFactory, T> { +public class DefaultPulsarListenerContainerFactory + extends AbstractPulsarListenerContainerFactory, T> { @Override protected DefaultPulsarMessageListenerContainer createContainerInstance(PulsarListenerEndpoint endpoint) { @@ -67,7 +67,7 @@ public class DefaultPulsarListenerContainerFactory extends AbstractPulsarL @Override protected void initializeContainer(DefaultPulsarMessageListenerContainer instance, - PulsarListenerEndpoint endpoint) { + PulsarListenerEndpoint endpoint) { super.initializeContainer(instance, endpoint); } @@ -84,7 +84,8 @@ public class DefaultPulsarListenerContainerFactory extends AbstractPulsarL }; DefaultPulsarMessageListenerContainer container = createContainerInstance(endpoint); initializeContainer(container, endpoint); - //customizeContainer(container); + // customizeContainer(container); return container; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/MethodPulsarListenerEndpoint.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/MethodPulsarListenerEndpoint.java index 8c39760e..55e6249f 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/MethodPulsarListenerEndpoint.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/MethodPulsarListenerEndpoint.java @@ -51,21 +51,20 @@ import org.springframework.pulsar.support.converter.PulsarRecordMessageConverter import org.springframework.util.Assert; /** - * A {@link PulsarListenerEndpoint} providing the method to invoke to process - * an incoming message for this endpoint. + * A {@link PulsarListenerEndpoint} providing the method to invoke to process an incoming + * message for this endpoint. * * @param Message payload type - * * @author Soby Chacko */ public class MethodPulsarListenerEndpoint extends AbstractPulsarListenerEndpoint { - private final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass())); private Object bean; private Method method; + private MessageHandlerMethodFactory messageHandlerMethodFactory; private SmartMessageConverter messagingConverter; @@ -80,7 +79,6 @@ public class MethodPulsarListenerEndpoint extends AbstractPulsarListenerEndpo /** * Set the method to invoke to process a message managed by this endpoint. - * * @param method the target method for the {@link #bean}. */ public void setMethod(Method method) { @@ -97,30 +95,33 @@ public class MethodPulsarListenerEndpoint extends AbstractPulsarListenerEndpo @Override protected PulsarMessagingMessageListenerAdapter createMessageListener(PulsarMessageListenerContainer container, - @Nullable MessageConverter messageConverter) { + @Nullable MessageConverter messageConverter) { Assert.state(this.messageHandlerMethodFactory != null, "Could not create message listener - MessageHandlerMethodFactory not set"); PulsarMessagingMessageListenerAdapter messageListener = createMessageListenerInstance(messageConverter); final HandlerAdapter handlerMethod = configureListenerAdapter(messageListener); messageListener.setHandlerMethod(handlerMethod); - //Since we have access to the handler method here, check if we can type infer the Schema used. + // Since we have access to the handler method here, check if we can type infer the + // Schema used. - //TODO: filter out the payload type by excluding Consumer, Message, Messages etc. + // TODO: filter out the payload type by excluding Consumer, Message, Messages etc. final MethodParameter[] methodParameters = handlerMethod.getInvokerHandlerMethod().getMethodParameters(); MethodParameter methodParameter = null; - final Optional parameter = Arrays.stream(methodParameters).filter( - methodParameter1 -> !methodParameter1.getParameterType().equals(Consumer.class) - || !methodParameter1.getParameterType().equals(Acknowledgement.class)).findFirst(); - final long count = Arrays.stream(methodParameters).filter(methodParameter1 -> !methodParameter1.getParameterType().equals(Consumer.class) - && !methodParameter1.getParameterType().equals(Acknowledgement.class)).count(); + final Optional parameter = Arrays.stream(methodParameters) + .filter(methodParameter1 -> !methodParameter1.getParameterType().equals(Consumer.class) + || !methodParameter1.getParameterType().equals(Acknowledgement.class)) + .findFirst(); + final long count = Arrays.stream(methodParameters) + .filter(methodParameter1 -> !methodParameter1.getParameterType().equals(Consumer.class) + && !methodParameter1.getParameterType().equals(Acknowledgement.class)) + .count(); Assert.isTrue(count == 1, "More than 1 expected payload types found"); if (parameter.isPresent()) { methodParameter = parameter.get(); } - final DefaultPulsarMessageListenerContainer containerInstance = (DefaultPulsarMessageListenerContainer) container; final PulsarContainerProperties pulsarContainerProperties = containerInstance.getPulsarContainerProperties(); final SchemaType schemaType = pulsarContainerProperties.getSchemaType(); @@ -187,7 +188,8 @@ public class MethodPulsarListenerEndpoint extends AbstractPulsarListenerEndpo return messageListener; } - private Schema getRequiredSchema(MethodParameter methodParameter, PulsarContainerProperties pulsarContainerProperties) { + private Schema getRequiredSchema(MethodParameter methodParameter, + PulsarContainerProperties pulsarContainerProperties) { ResolvableType resolvableType = ResolvableType.forMethodParameter(methodParameter); final Class rawClass = resolvableType.getRawClass(); if (rawClass != null && isContainerType(rawClass)) { @@ -198,16 +200,17 @@ public class MethodPulsarListenerEndpoint extends AbstractPulsarListenerEndpo } private boolean isContainerType(Class rawClass) { - return rawClass.isAssignableFrom(List.class) || rawClass.isAssignableFrom(Message.class) || rawClass.isAssignableFrom(Messages.class); + return rawClass.isAssignableFrom(List.class) || rawClass.isAssignableFrom(Message.class) + || rawClass.isAssignableFrom(Messages.class); } protected HandlerAdapter configureListenerAdapter(PulsarMessagingMessageListenerAdapter messageListener) { - InvocableHandlerMethod invocableHandlerMethod = - this.messageHandlerMethodFactory.createInvocableHandlerMethod(getBean(), getMethod()); + InvocableHandlerMethod invocableHandlerMethod = this.messageHandlerMethodFactory + .createInvocableHandlerMethod(getBean(), getMethod()); return new HandlerAdapter(invocableHandlerMethod); } - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) protected PulsarMessagingMessageListenerAdapter createMessageListenerInstance( @Nullable MessageConverter messageConverter) { diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientConfiguration.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientConfiguration.java index 1f129908..acbb6e5e 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientConfiguration.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientConfiguration.java @@ -43,4 +43,5 @@ public class PulsarClientConfiguration { public Map getConfigs() { return this.configs; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientFactoryBean.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientFactoryBean.java index 6fd56e99..c65c874d 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientFactoryBean.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarClientFactoryBean.java @@ -46,9 +46,7 @@ public class PulsarClientFactoryBean extends AbstractFactoryBean { @Override protected PulsarClient createInstance() throws Exception { - return PulsarClient.builder() - .loadConf(this.pulsarClientConfiguration.getConfigs()) - .build(); + return PulsarClient.builder().loadConf(this.pulsarClientConfiguration.getConfigs()).build(); } @Override diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerBeanNames.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerBeanNames.java index 479ab906..08d2be55 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerBeanNames.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerBeanNames.java @@ -27,13 +27,11 @@ public abstract class PulsarListenerBeanNames { /** * The bean name of the internally managed Pulsar listener annotation processor. */ - public static final String PULSAR_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME = - "org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor"; + public static final String PULSAR_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME = "org.springframework.pulsar.config.internalPulsarListenerAnnotationProcessor"; /** * The bean name of the internally managed Pulsar listener endpoint registry. */ - public static final String PULSAR_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME = - "org.springframework.pulsar.config.internalPulsarListenerEndpointRegistry"; + public static final String PULSAR_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME = "org.springframework.pulsar.config.internalPulsarListenerEndpointRegistry"; } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerContainerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerContainerFactory.java index ecd8447d..730af85f 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerContainerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerContainerFactory.java @@ -22,7 +22,6 @@ import org.springframework.pulsar.listener.PulsarMessageListenerContainer; * Factory for Pulsar message listener containers. * * @param message listener container type. - * * @author Soby Chacko */ public interface PulsarListenerContainerFactory { diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpoint.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpoint.java index 00410113..dcff1531 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpoint.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpoint.java @@ -27,8 +27,8 @@ import org.springframework.pulsar.support.MessageConverter; /** * Model for a Pulsar listener endpoint. Can be used against a - * {@link org.springframework.pulsar.annotation.PulsarListenerConfigurer} - * to register endpoints programmatically. + * {@link org.springframework.pulsar.annotation.PulsarListenerConfigurer} to register + * endpoints programmatically. * * @author Soby Chacko */ @@ -49,9 +49,10 @@ public interface PulsarListenerEndpoint { Boolean getAutoStartup(); void setupListenerContainer(PulsarMessageListenerContainer listenerContainer, - @Nullable MessageConverter messageConverter); + @Nullable MessageConverter messageConverter); boolean isBatchListener(); SchemaType getSchemaType(); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointAdapter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointAdapter.java index b700b472..8f5038f5 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointAdapter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointAdapter.java @@ -58,7 +58,8 @@ public class PulsarListenerEndpointAdapter implements PulsarListenerEndpoint { } @Override - public void setupListenerContainer(PulsarMessageListenerContainer listenerContainer, MessageConverter messageConverter) { + public void setupListenerContainer(PulsarMessageListenerContainer listenerContainer, + MessageConverter messageConverter) { } @@ -71,4 +72,5 @@ public class PulsarListenerEndpointAdapter implements PulsarListenerEndpoint { public SchemaType getSchemaType() { return null; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistrar.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistrar.java index 0c8eafb9..35a2aac1 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistrar.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistrar.java @@ -31,8 +31,8 @@ import org.springframework.util.Assert; import org.springframework.validation.Validator; /** - * Helper bean for registering {@link PulsarListenerEndpoint} with - * a {@link PulsarListenerEndpointRegistry}. + * Helper bean for registering {@link PulsarListenerEndpoint} with a + * {@link PulsarListenerEndpointRegistry}. * * @author Soby Chacko */ @@ -74,8 +74,7 @@ public class PulsarListenerEndpointRegistrar implements BeanFactoryAware, Initia } public void setMessageHandlerMethodFactory(MessageHandlerMethodFactory PulsarHandlerMethodFactory) { - Assert.isNull(this.validator, - "A validator cannot be provided with a custom message handler factory"); + Assert.isNull(this.validator, "A validator cannot be provided with a custom message handler factory"); this.messageHandlerMethodFactory = PulsarHandlerMethodFactory; } @@ -84,7 +83,6 @@ public class PulsarListenerEndpointRegistrar implements BeanFactoryAware, Initia return this.messageHandlerMethodFactory; } - public void setContainerFactory(PulsarListenerContainerFactory containerFactory) { this.containerFactory = containerFactory; } @@ -117,10 +115,10 @@ public class PulsarListenerEndpointRegistrar implements BeanFactoryAware, Initia protected void registerAllEndpoints() { synchronized (this.endpointDescriptors) { for (PulsarListenerEndpointDescriptor descriptor : this.endpointDescriptors) { - this.endpointRegistry.registerListenerContainer( - descriptor.endpoint, resolveContainerFactory(descriptor)); + this.endpointRegistry.registerListenerContainer(descriptor.endpoint, + resolveContainerFactory(descriptor)); } - this.startImmediately = true; // trigger immediate startup + this.startImmediately = true; // trigger immediate startup } } @@ -133,21 +131,23 @@ public class PulsarListenerEndpointRegistrar implements BeanFactoryAware, Initia } else if (this.containerFactoryBeanName != null) { Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name"); - this.containerFactory = this.beanFactory.getBean( - this.containerFactoryBeanName, PulsarListenerContainerFactory.class); - return this.containerFactory; // Consider changing this if live change of the factory is required + this.containerFactory = this.beanFactory.getBean(this.containerFactoryBeanName, + PulsarListenerContainerFactory.class); + return this.containerFactory; // Consider changing this if live change of the + // factory is required } else { - throw new IllegalStateException("Could not resolve the " + - PulsarListenerContainerFactory.class.getSimpleName() + " to use for [" + - descriptor.endpoint + "] no factory was given and no default is set."); + throw new IllegalStateException( + "Could not resolve the " + PulsarListenerContainerFactory.class.getSimpleName() + " to use for [" + + descriptor.endpoint + "] no factory was given and no default is set."); } } public void registerEndpoint(PulsarListenerEndpoint endpoint, @Nullable PulsarListenerContainerFactory factory) { Assert.notNull(endpoint, "Endpoint must be set"); Assert.hasText(endpoint.getSubscriptionName(), "Endpoint id must be set"); - // Factory may be null, we defer the resolution right before actually creating the container + // Factory may be null, we defer the resolution right before actually creating the + // container PulsarListenerEndpointDescriptor descriptor = new PulsarListenerEndpointDescriptor(endpoint, factory); synchronized (this.endpointDescriptors) { if (this.startImmediately) { // Register and start immediately @@ -160,7 +160,6 @@ public class PulsarListenerEndpointRegistrar implements BeanFactoryAware, Initia } } - private static final class PulsarListenerEndpointDescriptor { private final PulsarListenerEndpoint endpoint; @@ -168,11 +167,12 @@ public class PulsarListenerEndpointRegistrar implements BeanFactoryAware, Initia private final PulsarListenerContainerFactory containerFactory; private PulsarListenerEndpointDescriptor(PulsarListenerEndpoint endpoint, - @Nullable PulsarListenerContainerFactory containerFactory) { + @Nullable PulsarListenerContainerFactory containerFactory) { this.endpoint = endpoint; this.containerFactory = containerFactory; } } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistry.java b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistry.java index ef87511b..2879e83c 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistry.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/config/PulsarListenerEndpointRegistry.java @@ -44,16 +44,16 @@ import org.springframework.util.Assert; /** * Creates the necessary {@link PulsarMessageListenerContainer} instances for the - * registered {@linkplain PulsarListenerEndpoint endpoints}. Also manages the - * lifecycle of the listener containers, in particular within the lifecycle - * of the application context. + * registered {@linkplain PulsarListenerEndpoint endpoints}. Also manages the lifecycle of + * the listener containers, in particular within the lifecycle of the application context. * - *

Contrary to {@link PulsarMessageListenerContainer}s created manually, listener - * containers managed by registry are not beans in the application context and - * are not candidates for autowiring. Use {@link #getListenerContainers()} if - * you need to access this registry's listener containers for management purposes. - * If you need to access to a specific message listener container, use - * {@link #getListenerContainer(String)} with the id of the endpoint. + *

+ * Contrary to {@link PulsarMessageListenerContainer}s created manually, listener + * containers managed by registry are not beans in the application context and are not + * candidates for autowiring. Use {@link #getListenerContainers()} if you need to access + * this registry's listener containers for management purposes. If you need to access to a + * specific message listener container, use {@link #getListenerContainer(String)} with the + * id of the endpoint. * * @author Soby Chacko */ @@ -68,7 +68,6 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe private boolean contextRefreshed; - private volatile boolean running; @Override @@ -99,7 +98,8 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe public Collection getAllListenerContainers() { List containers = new ArrayList<>(); containers.addAll(getListenerContainers()); - containers.addAll(this.applicationContext.getBeansOfType(PulsarMessageListenerContainer.class, true, false).values()); + containers.addAll( + this.applicationContext.getBeansOfType(PulsarMessageListenerContainer.class, true, false).values()); return containers; } @@ -108,7 +108,7 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe } public void registerListenerContainer(PulsarListenerEndpoint endpoint, PulsarListenerContainerFactory factory, - boolean startImmediately) { + boolean startImmediately) { Assert.notNull(endpoint, "Endpoint must not be null"); Assert.notNull(factory, "Factory must not be null"); @@ -127,7 +127,7 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe } protected PulsarMessageListenerContainer createListenerContainer(PulsarListenerEndpoint endpoint, - PulsarListenerContainerFactory factory) { + PulsarListenerContainerFactory factory) { if (endpoint instanceof MethodPulsarListenerEndpoint) { MethodPulsarListenerEndpoint mkle = (MethodPulsarListenerEndpoint) endpoint; @@ -151,8 +151,11 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe } int containerPhase = listenerContainer.getPhase(); - if (listenerContainer.isAutoStartup() && - containerPhase != AbstractPulsarMessageListenerContainer.DEFAULT_PHASE) { // a custom phase value + if (listenerContainer.isAutoStartup() + && containerPhase != AbstractPulsarMessageListenerContainer.DEFAULT_PHASE) { // a + // custom + // phase + // value if (this.phase != AbstractPulsarMessageListenerContainer.DEFAULT_PHASE && this.phase != containerPhase) { throw new IllegalStateException("Encountered phase mismatch between container " + "factory definitions: " + this.phase + " vs " + containerPhase); @@ -170,7 +173,6 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe } } - // Delegating implementation of SmartLifecycle @Override @@ -225,7 +227,6 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe return this.running; } - @Override public void onApplicationEvent(ContextRefreshedEvent event) { if (event.getApplicationContext().equals(this.applicationContext)) { @@ -233,7 +234,6 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe } } - private void startIfNecessary(PulsarMessageListenerContainer listenerContainer) { if (this.contextRefreshed || listenerContainer.isAutoStartup()) { listenerContainer.start(); @@ -260,5 +260,4 @@ public class PulsarListenerEndpointRegistry implements PulsarListenerContainerRe } - } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/CachingPulsarProducerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/CachingPulsarProducerFactory.java index ce0028c4..8656be0e 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/CachingPulsarProducerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/CachingPulsarProducerFactory.java @@ -46,18 +46,17 @@ import com.github.benmanes.caffeine.cache.RemovalListener; import com.github.benmanes.caffeine.cache.Scheduler; /** - * A {@link PulsarProducerFactory} that extends the {@link DefaultPulsarProducerFactory default implementation} - * by caching the created producers. + * A {@link PulsarProducerFactory} that extends the {@link DefaultPulsarProducerFactory + * default implementation} by caching the created producers. *

- * The created producer is wrapped in a proxy so that calls to {@link Producer#close()} do not actually close it. - * The actual close occurs when the producer is evicted from the cache or when {@link DisposableBean#destroy()} is - * invoked. + * The created producer is wrapped in a proxy so that calls to {@link Producer#close()} do + * not actually close it. The actual close occurs when the producer is evicted from the + * cache or when {@link DisposableBean#destroy()} is invoked. *

- * The proxied producer is cached in an LRU fashion and evicted when it has not been used within a configured time - * period. + * The proxied producer is cached in an LRU fashion and evicted when it has not been used + * within a configured time period. * * @param producer type. - * * @author Chris Bono */ public class CachingPulsarProducerFactory extends DefaultPulsarProducerFactory implements DisposableBean { @@ -67,8 +66,8 @@ public class CachingPulsarProducerFactory extends DefaultPulsarProducerFactor private final Cache, Producer> producerCache; /** - * Construct a caching producer factory with the specified values for the cache configuration. - * + * Construct a caching producer factory with the specified values for the cache + * configuration. * @param pulsarClient the client used to create the producers * @param producerConfig the configuration to use when creating a producer * @param cacheExpireAfterAccess time period to expire unused entries in the cache @@ -78,16 +77,15 @@ public class CachingPulsarProducerFactory extends DefaultPulsarProducerFactor public CachingPulsarProducerFactory(PulsarClient pulsarClient, Map producerConfig, Duration cacheExpireAfterAccess, Long cacheMaximumSize, Integer cacheInitialCapacity) { super(pulsarClient, producerConfig); - this.producerCache = Caffeine.newBuilder() - .expireAfterAccess(cacheExpireAfterAccess) - .maximumSize(cacheMaximumSize) - .initialCapacity(cacheInitialCapacity) - .scheduler(Scheduler.systemScheduler()) - .evictionListener((RemovalListener, Producer>) (producerCacheKey, producer, cause) -> { - this.logger.debug(() -> String.format("Producer %s evicted from cache due to %s", - ProducerUtils.formatProducer(producer), cause)); - closeProducer(producer); - }).build(); + this.producerCache = Caffeine.newBuilder().expireAfterAccess(cacheExpireAfterAccess) + .maximumSize(cacheMaximumSize).initialCapacity(cacheInitialCapacity) + .scheduler(Scheduler.systemScheduler()).evictionListener( + (RemovalListener, Producer>) (producerCacheKey, producer, cause) -> { + this.logger.debug(() -> String.format("Producer %s evicted from cache due to %s", + ProducerUtils.formatProducer(producer), cause)); + closeProducer(producer); + }) + .build(); } @Override @@ -105,10 +103,12 @@ public class CachingPulsarProducerFactory extends DefaultPulsarProducerFactor } @Override - protected Producer doCreateProducer(String topic, Schema schema, MessageRouter messageRouter) throws PulsarClientException { + protected Producer doCreateProducer(String topic, Schema schema, MessageRouter messageRouter) + throws PulsarClientException { Producer producer = super.doCreateProducer(topic, schema, messageRouter); - return wrapProducerWithCloseCallback(producer, (p) -> this.logger.trace(() -> - String.format("Client closed producer %s but will skip actual closing", ProducerUtils.formatProducer(producer)))); + return wrapProducerWithCloseCallback(producer, + (p) -> this.logger.trace(() -> String.format("Client closed producer %s but will skip actual closing", + ProducerUtils.formatProducer(producer)))); } @SuppressWarnings("unchecked") @@ -159,13 +159,15 @@ public class CachingPulsarProducerFactory extends DefaultPulsarProducerFactor static class ProducerCacheKey { private final Schema schema; + private final SchemaHash schemaHash; + private final String topic; + private final MessageRouter router; /** * Constructs an instance. - * * @param schema the schema the producer is configured to use * @param topic the topic the producer is configured to send to * @param router the custom message router the producer is configured to use @@ -188,14 +190,15 @@ public class CachingPulsarProducerFactory extends DefaultPulsarProducerFactor return false; } ProducerCacheKey that = (ProducerCacheKey) o; - return this.topic.equals(that.topic) && - this.schemaHash.equals(that.schemaHash) && - Objects.equals(this.router, that.router); + return this.topic.equals(that.topic) && this.schemaHash.equals(that.schemaHash) + && Objects.equals(this.router, that.router); } @Override public int hashCode() { return this.topic.hashCode() + this.schemaHash.hashCode() + Objects.hashCode(this.router); } + } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarConsumerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarConsumerFactory.java index 00574430..6688c5f5 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarConsumerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarConsumerFactory.java @@ -34,7 +34,6 @@ import org.springframework.util.CollectionUtils; * Default implementation for {@link PulsarConsumerFactory}. * * @param underlying payload type for the consumer. - * * @author Soby Chacko */ public class DefaultPulsarConsumerFactory implements PulsarConsumerFactory { @@ -53,7 +52,8 @@ public class DefaultPulsarConsumerFactory implements PulsarConsumerFactory } @Override - public Consumer createConsumer(Schema schema, Map propertiesToOverride) throws PulsarClientException { + public Consumer createConsumer(Schema schema, Map propertiesToOverride) + throws PulsarClientException { final ConsumerBuilder consumerBuilder = this.pulsarClient.newConsumer(schema); @@ -69,7 +69,8 @@ public class DefaultPulsarConsumerFactory implements PulsarConsumerFactory } @Override - public Consumer createConsumer(Schema schema, BatchReceivePolicy batchReceivePolicy, Map propertiesToOverride) throws PulsarClientException { + public Consumer createConsumer(Schema schema, BatchReceivePolicy batchReceivePolicy, + Map propertiesToOverride) throws PulsarClientException { final ConsumerBuilder consumerBuilder = this.pulsarClient.newConsumer(schema); final Map properties = new HashMap<>(this.consumerConfig); diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarProducerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarProducerFactory.java index 4090aaf3..59a939f7 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarProducerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/DefaultPulsarProducerFactory.java @@ -34,7 +34,6 @@ import org.springframework.util.CollectionUtils; * Default implementation of {@link PulsarProducerFactory}. * * @param producer type. - * * @author Soby Chacko * @author Chris Bono */ @@ -59,11 +58,13 @@ public class DefaultPulsarProducerFactory implements PulsarProducerFactory } @Override - public Producer createProducer(String topic, Schema schema, MessageRouter messageRouter) throws PulsarClientException { + public Producer createProducer(String topic, Schema schema, MessageRouter messageRouter) + throws PulsarClientException { return doCreateProducer(topic, schema, messageRouter); } - protected Producer doCreateProducer(String topic, Schema schema, MessageRouter messageRouter) throws PulsarClientException { + protected Producer doCreateProducer(String topic, Schema schema, MessageRouter messageRouter) + throws PulsarClientException { final String resolvedTopic = ProducerUtils.resolveTopicName(topic, this); this.logger.trace(() -> String.format("Creating producer for '%s' topic", resolvedTopic)); final ProducerBuilder producerBuilder = this.pulsarClient.newProducer(schema); @@ -81,4 +82,5 @@ public class DefaultPulsarProducerFactory implements PulsarProducerFactory public Map getProducerConfig() { return this.producerConfig; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/ProducerUtils.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/ProducerUtils.java index f32fb516..02d6712c 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/ProducerUtils.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/ProducerUtils.java @@ -41,9 +41,9 @@ final class ProducerUtils { if (StringUtils.hasText(userSpecifiedTopic)) { return userSpecifiedTopic; } - return Optional.ofNullable(producerFactory.getProducerConfig().get("topicName")) - .map(Object::toString) - .orElseThrow(() -> new IllegalArgumentException("Topic must be specified when no default topic is configured")); + return Optional.ofNullable(producerFactory.getProducerConfig().get("topicName")).map(Object::toString) + .orElseThrow(() -> new IllegalArgumentException( + "Topic must be specified when no default topic is configured")); } static void closeProducerAsync(Producer producer, LogAccessor logger) { @@ -52,4 +52,5 @@ final class ProducerUtils { return null; }); } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarConsumerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarConsumerFactory.java index 308e8aa8..ca378aaf 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarConsumerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarConsumerFactory.java @@ -27,14 +27,15 @@ import org.apache.pulsar.client.api.Schema; * Pulsar consumer factory interface. * * @param payload type for the consumer. - * * @author Soby Chacko */ public interface PulsarConsumerFactory { Consumer createConsumer(Schema schema, Map propertiesToOverride) throws PulsarClientException; - Consumer createConsumer(Schema schema, BatchReceivePolicy batchReceivePolicy, Map propertiesToOverride) throws PulsarClientException; + Consumer createConsumer(Schema schema, BatchReceivePolicy batchReceivePolicy, + Map propertiesToOverride) throws PulsarClientException; Map getConsumerConfig(); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarOperations.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarOperations.java index ec9b7c97..5d54b567 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarOperations.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarOperations.java @@ -26,7 +26,6 @@ import org.apache.pulsar.client.api.PulsarClientException; * The basic Pulsar operations contract. * * @param the message payload type - * * @author Chris Bono * @author Alexander Preuß */ @@ -44,7 +43,8 @@ public interface PulsarOperations { /** * Sends a message to the specified topic in a blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @return the id of the sent message * @throws PulsarClientException if an error occurs @@ -60,7 +60,8 @@ public interface PulsarOperations { * @return the id of the sent message * @throws PulsarClientException if an error occurs */ - default MessageId send(T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) throws PulsarClientException { + default MessageId send(T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) + throws PulsarClientException { return send(message, typedMessageBuilderCustomizer, null); } @@ -77,7 +78,8 @@ public interface PulsarOperations { /** * Sends a message to the specified topic in a blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @param messageRouter the optional message router to use * @return the id of the sent message @@ -95,32 +97,37 @@ public interface PulsarOperations { * @return the id of the sent message * @throws PulsarClientException if an error occurs */ - default MessageId send(T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) throws PulsarClientException { + default MessageId send(T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, + MessageRouter messageRouter) throws PulsarClientException { return send(null, message, typedMessageBuilderCustomizer, messageRouter); } /** * Sends a message to the specified topic in a blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @param typedMessageBuilderCustomizer the TypeMessageBuilder customizer * @return the id of the sent message * @throws PulsarClientException if an error occurs */ - default MessageId send(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) throws PulsarClientException { + default MessageId send(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) + throws PulsarClientException { return send(topic, message, typedMessageBuilderCustomizer, null); } /** * Sends a message to the specified topic in a blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @param typedMessageBuilderCustomizer the optional TypedMessageBuilder customizer * @param messageRouter the optional message router to use * @return the id of the sent message * @throws PulsarClientException if an error occurs */ - MessageId send(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) throws PulsarClientException; + MessageId send(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, + MessageRouter messageRouter) throws PulsarClientException; /** * Sends a message to the default topic in a non-blocking manner. @@ -134,7 +141,8 @@ public interface PulsarOperations { /** * Sends a message to the specified topic in a non-blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs @@ -150,7 +158,8 @@ public interface PulsarOperations { * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs */ - default CompletableFuture sendAsync(T message, MessageRouter messageRouter) throws PulsarClientException { + default CompletableFuture sendAsync(T message, MessageRouter messageRouter) + throws PulsarClientException { return sendAsync(null, message, messageRouter); } @@ -161,19 +170,22 @@ public interface PulsarOperations { * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs */ - default CompletableFuture sendAsync(T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) throws PulsarClientException { + default CompletableFuture sendAsync(T message, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) throws PulsarClientException { return sendAsync(null, message, typedMessageBuilderCustomizer); } /** * Sends a message to the specified topic in a non-blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @param typedMessageBuilderCustomizer the TypedMessageBuilder customizer * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs */ - default CompletableFuture sendAsync(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) throws PulsarClientException { + default CompletableFuture sendAsync(String topic, T message, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer) throws PulsarClientException { return sendAsync(topic, message, typedMessageBuilderCustomizer, null); } @@ -185,30 +197,38 @@ public interface PulsarOperations { * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs */ - default CompletableFuture sendAsync(T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) throws PulsarClientException { + default CompletableFuture sendAsync(T message, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) + throws PulsarClientException { return sendAsync(null, message, typedMessageBuilderCustomizer, messageRouter); } /** * Sends a message to the specified topic in a non-blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @param messageRouter the optional message router to use * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs */ - default CompletableFuture sendAsync(String topic, T message, MessageRouter messageRouter) throws PulsarClientException { + default CompletableFuture sendAsync(String topic, T message, MessageRouter messageRouter) + throws PulsarClientException { return sendAsync(topic, message, null, messageRouter); } /** * Sends a message to the specified topic in a non-blocking manner. - * @param topic the topic to send the message to or {@code null} to send to the default topic + * @param topic the topic to send the message to or {@code null} to send to the + * default topic * @param message the message to send * @param typedMessageBuilderCustomizer the optional TypedMessageBuilder customizer * @param messageRouter the optional message router to use * @return a future that holds the id of the sent message * @throws PulsarClientException if an error occurs */ - CompletableFuture sendAsync(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) throws PulsarClientException; + CompletableFuture sendAsync(String topic, T message, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) + throws PulsarClientException; + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarProducerFactory.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarProducerFactory.java index 584c0e35..83766971 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarProducerFactory.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarProducerFactory.java @@ -27,7 +27,6 @@ import org.apache.pulsar.client.api.Schema; * The strategy to create a {@link Producer} instance(s). * * @param producer payload type - * * @author Soby Chacko * @author Chris Bono */ @@ -35,8 +34,8 @@ public interface PulsarProducerFactory { /** * Create a producer. - * - * @param topic the topic the producer will send messages to or {@code null} to use the default topic + * @param topic the topic the producer will send messages to or {@code null} to use + * the default topic * @param schema the schema of the messages to be sent * @return the producer * @throws PulsarClientException if any error occurs @@ -45,19 +44,20 @@ public interface PulsarProducerFactory { /** * Create a producer. - * - * @param topic the topic the producer will send messages to or {@code null} to use the default topic + * @param topic the topic the producer will send messages to or {@code null} to use + * the default topic * @param schema the schema of the messages to be sent * @param messageRouter the optional message router to use * @return the producer * @throws PulsarClientException if any error occurs */ - Producer createProducer(String topic, Schema schema, MessageRouter messageRouter) throws PulsarClientException; + Producer createProducer(String topic, Schema schema, MessageRouter messageRouter) + throws PulsarClientException; /** * Return a map of configuration options to use when creating producers. - * * @return the map of configuration options */ Map getProducerConfig(); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarTemplate.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarTemplate.java index 5341448d..7e48f09c 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarTemplate.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/PulsarTemplate.java @@ -32,7 +32,6 @@ import org.springframework.core.log.LogAccessor; * A thread-safe template for executing high-level Pulsar operations. * * @param the message payload type - * * @author Soby Chacko * @author Chris Bono * @author Alexander Preuß @@ -45,14 +44,16 @@ public class PulsarTemplate implements PulsarOperations { /** * Constructs a template instance. - * @param producerFactory the producer factory used to create the backing Pulsar producers. + * @param producerFactory the producer factory used to create the backing Pulsar + * producers. */ public PulsarTemplate(PulsarProducerFactory producerFactory) { this.producerFactory = producerFactory; } @Override - public MessageId send(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) throws PulsarClientException { + public MessageId send(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, + MessageRouter messageRouter) throws PulsarClientException { try { return this.sendAsync(topic, message, typedMessageBuilderCustomizer, messageRouter).get(); } @@ -62,7 +63,9 @@ public class PulsarTemplate implements PulsarOperations { } @Override - public CompletableFuture sendAsync(String topic, T message, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) throws PulsarClientException { + public CompletableFuture sendAsync(String topic, T message, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter messageRouter) + throws PulsarClientException { final String topicName = ProducerUtils.resolveTopicName(topic, this.producerFactory); this.logger.trace(() -> String.format("Sending msg to '%s' topic", topicName)); final Producer producer = prepareProducerForSend(topic, message, messageRouter); @@ -70,22 +73,23 @@ public class PulsarTemplate implements PulsarOperations { if (typedMessageBuilderCustomizer != null) { typedMessageBuilderCustomizer.customize(messageBuilder); } - return messageBuilder.sendAsync() - .whenComplete((msgId, ex) -> { - if (ex == null) { - this.logger.trace(() -> String.format("Sent msg to '%s' topic", topicName)); - // TODO success metrics - } - else { - this.logger.error(ex, () -> String.format("Failed to send msg to '%s' topic", topicName)); - // TODO fail metrics - } - ProducerUtils.closeProducerAsync(producer, this.logger); - }); + return messageBuilder.sendAsync().whenComplete((msgId, ex) -> { + if (ex == null) { + this.logger.trace(() -> String.format("Sent msg to '%s' topic", topicName)); + // TODO success metrics + } + else { + this.logger.error(ex, () -> String.format("Failed to send msg to '%s' topic", topicName)); + // TODO fail metrics + } + ProducerUtils.closeProducerAsync(producer, this.logger); + }); } - private Producer prepareProducerForSend(String topic, T message, MessageRouter messageRouter) throws PulsarClientException { + private Producer prepareProducerForSend(String topic, T message, MessageRouter messageRouter) + throws PulsarClientException { Schema schema = SchemaUtils.getSchema(message); return this.producerFactory.createProducer(topic, schema, messageRouter); } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/SchemaUtils.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/SchemaUtils.java index 12e95d74..689dc23e 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/SchemaUtils.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/SchemaUtils.java @@ -31,7 +31,7 @@ public final class SchemaUtils { } @SuppressWarnings("unchecked") - public static Schema getSchema(T message) { + public static Schema getSchema(T message) { final String clazzName = message.getClass().getName(); return switch (clazzName) { case "java.lang.String" -> (Schema) Schema.STRING; @@ -52,4 +52,5 @@ public final class SchemaUtils { default -> (Schema) JSONSchema.of(message.getClass()); }; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/core/TypedMessageBuilderCustomizer.java b/spring-pulsar/src/main/java/org/springframework/pulsar/core/TypedMessageBuilderCustomizer.java index f5221834..e393cd9b 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/core/TypedMessageBuilderCustomizer.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/core/TypedMessageBuilderCustomizer.java @@ -22,7 +22,6 @@ import org.apache.pulsar.client.api.TypedMessageBuilder; * The interface to customize a {@link TypedMessageBuilder}. * * @param The message payload type - * * @author Alexander Preuß */ @FunctionalInterface @@ -33,4 +32,5 @@ public interface TypedMessageBuilderCustomizer { * @param messageBuilder the messageBuilder to customize */ void customize(TypedMessageBuilder messageBuilder); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/event/ConsumerStartedEvent.java b/spring-pulsar/src/main/java/org/springframework/pulsar/event/ConsumerStartedEvent.java index f85ef8e8..b5e6eeac 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/event/ConsumerStartedEvent.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/event/ConsumerStartedEvent.java @@ -40,4 +40,3 @@ public class ConsumerStartedEvent extends PulsarEvent { } } - diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/AbstractPulsarMessageListenerContainer.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/AbstractPulsarMessageListenerContainer.java index b4ce7b6a..4b67a2cc 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/AbstractPulsarMessageListenerContainer.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/AbstractPulsarMessageListenerContainer.java @@ -32,17 +32,17 @@ import org.springframework.pulsar.core.PulsarConsumerFactory; * Base implementation for the {@link PulsarMessageListenerContainer}. * * @param message type. - * * @author Soby Chacko */ -public abstract class AbstractPulsarMessageListenerContainer - implements PulsarMessageListenerContainer, BeanNameAware, ApplicationEventPublisherAware, - ApplicationContextAware { +public abstract class AbstractPulsarMessageListenerContainer implements PulsarMessageListenerContainer, + BeanNameAware, ApplicationEventPublisherAware, ApplicationContextAware { protected final LogAccessor logger = new LogAccessor(LogFactory.getLog(this.getClass())); // NOSONAR private ApplicationEventPublisher applicationEventPublisher; + private String beanName; + private ApplicationContext applicationContext; private final PulsarContainerProperties pulsarContainerProperties; @@ -50,17 +50,18 @@ public abstract class AbstractPulsarMessageListenerContainer private final PulsarConsumerFactory pulsarConsumerFactory; private boolean autoStartup = true; + private int phase; @SuppressWarnings("unchecked") protected AbstractPulsarMessageListenerContainer(PulsarConsumerFactory pulsarConsumerFactory, - PulsarContainerProperties pulsarContainerProperties) { + PulsarContainerProperties pulsarContainerProperties) { this.pulsarContainerProperties = pulsarContainerProperties; this.pulsarConsumerFactory = (PulsarConsumerFactory) pulsarConsumerFactory; } - @Override + @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } @@ -121,7 +122,6 @@ public abstract class AbstractPulsarMessageListenerContainer this.autoStartup = autoStartup; } - public void setPhase(int phase) { this.phase = phase; } @@ -130,4 +130,5 @@ public abstract class AbstractPulsarMessageListenerContainer public int getPhase() { return this.phase; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/Acknowledgement.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/Acknowledgement.java index d79f1e87..2234717d 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/Acknowledgement.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/Acknowledgement.java @@ -31,4 +31,5 @@ public interface Acknowledgement { void nack(); void nack(MessageId messageId); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/DefaultPulsarMessageListenerContainer.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/DefaultPulsarMessageListenerContainer.java index 6afc901b..c87673fd 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/DefaultPulsarMessageListenerContainer.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/DefaultPulsarMessageListenerContainer.java @@ -65,7 +65,8 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess private final AbstractPulsarMessageListenerContainer thisOrParentContainer; - public DefaultPulsarMessageListenerContainer(PulsarConsumerFactory pulsarConsumerFactory, PulsarContainerProperties pulsarContainerProperties) { + public DefaultPulsarMessageListenerContainer(PulsarConsumerFactory pulsarConsumerFactory, + PulsarContainerProperties pulsarContainerProperties) { super(pulsarConsumerFactory, pulsarContainerProperties); this.thisOrParentContainer = this; } @@ -86,8 +87,7 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess MessageListener messageListener = (MessageListener) messageListenerObject; if (consumerExecutor == null) { - consumerExecutor = new SimpleAsyncTaskExecutor( - (getBeanName() == null ? "" : getBeanName()) + "-C-"); + consumerExecutor = new SimpleAsyncTaskExecutor((getBeanName() == null ? "" : getBeanName()) + "-C-"); containerProperties.setConsumerTaskExecutor(consumerExecutor); } @@ -97,7 +97,8 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess this.listenerConsumerFuture = consumerExecutor.submitCompletable(this.listenerConsumer); try { - if (!this.startLatch.await(containerProperties.getConsumerStartTimeout().toMillis(), TimeUnit.MILLISECONDS)) { + if (!this.startLatch.await(containerProperties.getConsumerStartTimeout().toMillis(), + TimeUnit.MILLISECONDS)) { this.logger.error("Consumer thread failed to start - does the configured task executor " + "have enough threads to support all containers and concurrency?"); publishConsumerFailedToStart(); @@ -172,7 +173,7 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess private volatile Thread consumerThread; - @SuppressWarnings({"unchecked", "rawtypes"}) + @SuppressWarnings({ "unchecked", "rawtypes" }) Listener(MessageListener messageListener) { if (messageListener instanceof PulsarBatchMessageListener) { this.batchMessageListener = (PulsarBatchMessageListener) messageListener; @@ -193,11 +194,9 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess final BatchReceivePolicy batchReceivePolicy = new BatchReceivePolicy.Builder() .maxNumMessages(pulsarContainerProperties.getMaxNumMessages()) .maxNumBytes(pulsarContainerProperties.getMaxNumBytes()) - .timeout(pulsarContainerProperties.getBatchTimeout(), TimeUnit.MILLISECONDS) - .build(); + .timeout(pulsarContainerProperties.getBatchTimeout(), TimeUnit.MILLISECONDS).build(); this.consumer = getPulsarConsumerFactory().createConsumer( - (Schema) pulsarContainerProperties.getSchema(), - batchReceivePolicy, propertiesToOverride); + (Schema) pulsarContainerProperties.getSchema(), batchReceivePolicy, propertiesToOverride); } catch (PulsarClientException e) { DefaultPulsarMessageListenerContainer.this.logger.error(e, () -> "Pulsar client exceptions."); @@ -216,8 +215,7 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess propertiesToOverride.put("topicNames", strings); } if (StringUtils.hasText(pulsarContainerProperties.getSubscriptionName())) { - propertiesToOverride.put("subscriptionName", - pulsarContainerProperties.getSubscriptionName()); + propertiesToOverride.put("subscriptionName", pulsarContainerProperties.getSubscriptionName()); } return propertiesToOverride; } @@ -249,8 +247,9 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess if (messages.size() > 0) { if (this.batchMessageListener instanceof PulsarBatchAcknowledgingMessageListener) { this.batchMessageListener.received(this.consumer, messages, - this.containerProperties.getAckMode() == PulsarContainerProperties.AckMode.MANUAL ? - new ConsumerBatchAcknowledgment(this.consumer) : null); + this.containerProperties + .getAckMode() == PulsarContainerProperties.AckMode.MANUAL + ? new ConsumerBatchAcknowledgment(this.consumer) : null); } else { this.batchMessageListener.received(this.consumer, messages); @@ -266,7 +265,8 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess } } catch (Exception e) { - // the whole batch is negatively acknowledged in the event of an exception from the handler method. + // the whole batch is negatively acknowledged in the event of an + // exception from the handler method. this.consumer.negativeAcknowledge(messages); } } @@ -275,8 +275,9 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess try { if (this.listener instanceof PulsarAcknowledgingMessageListener) { this.listener.received(this.consumer, message, - this.containerProperties.getAckMode() == PulsarContainerProperties.AckMode.MANUAL ? - new ConsumerAcknowledgment(this.consumer, message) : null); + this.containerProperties + .getAckMode() == PulsarContainerProperties.AckMode.MANUAL + ? new ConsumerAcknowledgment(this.consumer, message) : null); } else if (this.listener != null) { this.listener.received(this.consumer, message); @@ -334,6 +335,7 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess this.consumer.negativeAcknowledge(message); } } + } private static final class ConsumerAcknowledgment implements Acknowledgement { @@ -376,6 +378,7 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess public void nack(MessageId messageId) { throw new UnsupportedOperationException(); } + } private static final class ConsumerBatchAcknowledgment implements Acknowledgement { @@ -427,5 +430,7 @@ public class DefaultPulsarMessageListenerContainer extends AbstractPulsarMess public void nack(MessageId messageId) { this.consumer.negativeAcknowledge(messageId); } + } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchAcknowledgingMessageListener.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchAcknowledgingMessageListener.java index fbd44589..05d9ae50 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchAcknowledgingMessageListener.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchAcknowledgingMessageListener.java @@ -26,4 +26,5 @@ public interface PulsarBatchAcknowledgingMessageListener extends PulsarBatchM } void received(Consumer consumer, Messages msg, Acknowledgement acknowledgement); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchMessageListener.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchMessageListener.java index 4bb5ea32..d3411451 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchMessageListener.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarBatchMessageListener.java @@ -34,4 +34,5 @@ public interface PulsarBatchMessageListener extends PulsarRecordMessageListen } void received(Consumer consumer, Messages msg); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarContainerProperties.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarContainerProperties.java index 7063471e..c34cda5e 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarContainerProperties.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarContainerProperties.java @@ -41,6 +41,7 @@ public class PulsarContainerProperties { * Enumeration for ack mode. */ public enum AckMode { + /** * Batch ack mode. */ @@ -53,6 +54,7 @@ public class PulsarContainerProperties { * Manual ack mode. */ MANUAL; + } private String[] topics; @@ -160,8 +162,8 @@ public class PulsarContainerProperties { } /** - * Set the timeout to wait for a consumer thread to start before logging - * an error. Default 30 seconds. + * Set the timeout to wait for a consumer thread to start before logging an error. + * Default 30 seconds. * @param consumerStartTimeout the consumer start timeout. */ public void setConsumerStartTimeout(Duration consumerStartTimeout) { @@ -208,4 +210,5 @@ public class PulsarContainerProperties { public void setSchemaType(SchemaType schemaType) { this.schemaType = schemaType; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarListenerContainerRegistry.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarListenerContainerRegistry.java index cd29b113..9891eb7d 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarListenerContainerRegistry.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarListenerContainerRegistry.java @@ -36,4 +36,5 @@ public interface PulsarListenerContainerRegistry { Collection getListenerContainers(); Collection getAllListenerContainers(); + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarMessageListenerContainer.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarMessageListenerContainer.java index 656fbf53..7ba4d48a 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarMessageListenerContainer.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarMessageListenerContainer.java @@ -20,8 +20,8 @@ import org.springframework.beans.factory.DisposableBean; import org.springframework.context.SmartLifecycle; /** - * Internal abstraction used by the framework representing a message - * listener container. Not meant to be implemented externally. + * Internal abstraction used by the framework representing a message listener container. + * Not meant to be implemented externally. * * @author Soby Chacko */ diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarRecordMessageListener.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarRecordMessageListener.java index 6aa40b7f..2396bc20 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarRecordMessageListener.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/PulsarRecordMessageListener.java @@ -24,7 +24,6 @@ import org.apache.pulsar.client.api.MessageListener; * Base record MessageListener that takes into account acknowledgments. * * @param message payload type - * * @author Soby Chacko */ public interface PulsarRecordMessageListener extends MessageListener { @@ -32,4 +31,5 @@ public interface PulsarRecordMessageListener extends MessageListener { default void received(Consumer consumer, Message msg, Acknowledgement acknowledgement) { throw new UnsupportedOperationException("Not supported"); } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/DelegatingInvocableHandler.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/DelegatingInvocableHandler.java index 753a11c7..ef98d896 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/DelegatingInvocableHandler.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/DelegatingInvocableHandler.java @@ -59,8 +59,7 @@ public class DelegatingInvocableHandler { private final ConcurrentMap, InvocableHandlerMethod> cachedHandlers = new ConcurrentHashMap<>(); - private final ConcurrentMap payloadMethodParameters = - new ConcurrentHashMap<>(); + private final ConcurrentMap payloadMethodParameters = new ConcurrentHashMap<>(); private final InvocableHandlerMethod defaultHandler; @@ -79,10 +78,10 @@ public class DelegatingInvocableHandler { private final PayloadValidator validator; public DelegatingInvocableHandler(List handlers, - @Nullable InvocableHandlerMethod defaultHandler, Object bean, - @Nullable BeanExpressionResolver beanExpressionResolver, - @Nullable BeanExpressionContext beanExpressionContext, - @Nullable BeanFactory beanFactory, @Nullable Validator validator) { + @Nullable InvocableHandlerMethod defaultHandler, Object bean, + @Nullable BeanExpressionResolver beanExpressionResolver, + @Nullable BeanExpressionContext beanExpressionContext, @Nullable BeanFactory beanFactory, + @Nullable Validator validator) { this.handlers = new ArrayList<>(); for (InvocableHandlerMethod handler : handlers) { this.handlers.add(wrapIfNecessary(handler)); @@ -92,8 +91,7 @@ public class DelegatingInvocableHandler { this.resolver = beanExpressionResolver; this.beanExpressionContext = beanExpressionContext; this.beanFactory = beanFactory instanceof ConfigurableListableBeanFactory - ? (ConfigurableListableBeanFactory) beanFactory - : null; + ? (ConfigurableListableBeanFactory) beanFactory : null; this.validator = validator == null ? null : new PayloadValidator(validator); } @@ -104,9 +102,10 @@ public class DelegatingInvocableHandler { } Parameter[] parameters = handler.getMethod().getParameters(); for (Parameter parameter : parameters) { -// if (parameter.getType().equals(ConsumerRecordMetadata.class)) { -// return new DelegatingInvocableHandler.MetadataAwareInvocableHandlerMethod(handler); -// } + // if (parameter.getType().equals(ConsumerRecordMetadata.class)) { + // return new + // DelegatingInvocableHandler.MetadataAwareInvocableHandlerMethod(handler); + // } } return handler; } @@ -124,10 +123,10 @@ public class DelegatingInvocableHandler { * @param message the message. * @param providedArgs additional arguments. * @return the result of the invocation. - * @throws Exception raised if no suitable argument resolver can be found, - * or the method raised an exception. + * @throws Exception raised if no suitable argument resolver can be found, or the + * method raised an exception. */ - public Object invoke(Message message, Object... providedArgs) throws Exception { //NOSONAR + public Object invoke(Message message, Object... providedArgs) throws Exception { // NOSONAR Class payloadClass = message.getPayload().getClass(); InvocableHandlerMethod handler = getHandlerForPayload(payloadClass); if (this.validator != null && this.defaultHandler != null) { @@ -138,10 +137,10 @@ public class DelegatingInvocableHandler { } Object result = null; if (handler instanceof MetadataAwareInvocableHandlerMethod) { -// Object[] args = new Object[providedArgs.length + 1]; -// args[0] = AdapterUtils.buildConsumerRecordMetadataFromArray(providedArgs); -// System.arraycopy(providedArgs, 0, args, 1, providedArgs.length); -// result = handler.invoke(message, args); + // Object[] args = new Object[providedArgs.length + 1]; + // args[0] = AdapterUtils.buildConsumerRecordMetadataFromArray(providedArgs); + // System.arraycopy(providedArgs, 0, args, 1, providedArgs.length); + // result = handler.invoke(message, args); } else { result = handler.invoke(message, providedArgs); @@ -162,8 +161,8 @@ public class DelegatingInvocableHandler { if (handler == null) { throw new PulsarException("No method found for " + payloadClass); } - this.cachedHandlers.putIfAbsent(payloadClass, handler); //NOSONAR - //setupReplyTo(handler); + this.cachedHandlers.putIfAbsent(payloadClass, handler); // NOSONAR + // setupReplyTo(handler); } return handler; } @@ -176,8 +175,8 @@ public class DelegatingInvocableHandler { if (result != null) { boolean resultIsDefault = result.equals(this.defaultHandler); if (!handler.equals(this.defaultHandler) && !resultIsDefault) { - throw new PulsarException("Ambiguous methods for payload type: " + payloadClass + ": " + - result.getMethod().getName() + " and " + handler.getMethod().getName()); + throw new PulsarException("Ambiguous methods for payload type: " + payloadClass + ": " + + result.getMethod().getName() + " and " + handler.getMethod().getName()); } if (!resultIsDefault) { continue; // otherwise replace the result with the actual match @@ -213,7 +212,7 @@ public class DelegatingInvocableHandler { } private MethodParameter findCandidate(Class payloadClass, Method method, - Annotation[][] parameterAnnotations) { + Annotation[][] parameterAnnotations) { MethodParameter foundCandidate = null; for (int i = 0; i < parameterAnnotations.length; i++) { MethodParameter methodParameter = new MethodParameter(method, i); @@ -236,7 +235,7 @@ public class DelegatingInvocableHandler { */ public String getMethodNameFor(Object payload) { InvocableHandlerMethod handlerForPayload = getHandlerForPayload(payload.getClass()); - return handlerForPayload == null ? "no match" : handlerForPayload.getMethod().toGenericString(); //NOSONAR + return handlerForPayload == null ? "no match" : handlerForPayload.getMethod().toGenericString(); // NOSONAR } public boolean hasDefaultHandler() { @@ -263,8 +262,7 @@ public class DelegatingInvocableHandler { @Override @Nullable - public Message toMessage(Object payload, @Nullable - MessageHeaders headers) { + public Message toMessage(Object payload, @Nullable MessageHeaders headers) { return null; } @@ -278,8 +276,12 @@ public class DelegatingInvocableHandler { } @Override - public void validate(Message message, MethodParameter parameter, Object target) { // NOSONAR - public + public void validate(Message message, MethodParameter parameter, Object target) { // NOSONAR + // - + // public super.validate(message, parameter, target); } + } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/HandlerAdapter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/HandlerAdapter.java index e5cb4657..6cf45989 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/HandlerAdapter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/HandlerAdapter.java @@ -21,8 +21,7 @@ import org.springframework.messaging.handler.invocation.InvocableHandlerMethod; /** * A wrapper for either an {@link InvocableHandlerMethod} or - * {@link DelegatingInvocableHandler}. All methods delegate to the - * underlying handler. + * {@link DelegatingInvocableHandler}. All methods delegate to the underlying handler. * * @author Soby Chacko */ @@ -50,7 +49,7 @@ public class HandlerAdapter { this.delegatingHandler = delegatingHandler; } - public Object invoke(Message message, Object... providedArgs) throws Exception { //NOSONAR + public Object invoke(Message message, Object... providedArgs) throws Exception { // NOSONAR if (this.invokerHandlerMethod != null) { return this.invokerHandlerMethod.invoke(message, providedArgs); // NOSONAR } @@ -87,5 +86,5 @@ public class HandlerAdapter { public InvocableHandlerMethod getInvokerHandlerMethod() { return this.invokerHandlerMethod; } -} +} diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/InvocationResult.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/InvocationResult.java index 9b37b771..f5ab6eb6 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/InvocationResult.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/InvocationResult.java @@ -56,10 +56,9 @@ public final class InvocationResult { @Override public String toString() { - return "InvocationResult [result=" + this.result - + ", sendTo=" + (this.sendTo == null ? "null" : this.sendTo.getExpressionString()) - + ", messageReturnType=" + this.messageReturnType + "]"; + return "InvocationResult [result=" + this.result + ", sendTo=" + + (this.sendTo == null ? "null" : this.sendTo.getExpressionString()) + ", messageReturnType=" + + this.messageReturnType + "]"; } } - diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarBatchMessagingMessageListenerAdapter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarBatchMessagingMessageListenerAdapter.java index 14e90fc4..65372b4d 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarBatchMessagingMessageListenerAdapter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarBatchMessagingMessageListenerAdapter.java @@ -34,12 +34,11 @@ import org.springframework.pulsar.support.converter.PulsarRecordMessageConverter import org.springframework.util.Assert; /** - * A {@link org.apache.pulsar.client.api.MessageListener MessageListener} - * adapter that invokes a configurable {@link HandlerAdapter}; used when the factory is - * configured for the listener to receive batches of messages. + * A {@link org.apache.pulsar.client.api.MessageListener MessageListener} adapter that + * invokes a configurable {@link HandlerAdapter}; used when the factory is configured for + * the listener to receive batches of messages. * * @param payload type. - * * @author Soby Chacko */ @SuppressWarnings("serial") @@ -87,15 +86,15 @@ public class PulsarBatchMessagingMessageListenerAdapter extends PulsarMessagi invoke(msg, consumer, message, acknowledgement); } - protected void invoke(Object records, Consumer consumer, - final Message messageArg, Acknowledgement acknowledgement) { + protected void invoke(Object records, Consumer consumer, final Message messageArg, + Acknowledgement acknowledgement) { Message message = messageArg; try { Object result = invokeHandler(records, message, consumer, acknowledgement); -// if (result != null) { -// handleResult(result, records, message); -// } + // if (result != null) { + // handleResult(result, records, message); + // } } catch (Exception e) { throw e; diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarMessagingMessageListenerAdapter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarMessagingMessageListenerAdapter.java index b8036faf..b020e187 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarMessagingMessageListenerAdapter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarMessagingMessageListenerAdapter.java @@ -44,12 +44,10 @@ import org.springframework.pulsar.support.converter.PulsarRecordMessageConverter import org.springframework.util.Assert; /** - * An abstract {@link org.apache.pulsar.client.api.MessageListener} adapter - * providing the necessary infrastructure to extract the payload from a - * Pulsar message. + * An abstract {@link org.apache.pulsar.client.api.MessageListener} adapter providing the + * necessary infrastructure to extract the payload from a Pulsar message. * * @param payload type. - * * @author Soby Chacko */ public abstract class PulsarMessagingMessageListenerAdapter { @@ -141,23 +139,22 @@ public abstract class PulsarMessagingMessageListenerAdapter { } protected final Object invokeHandler(Object data, org.springframework.messaging.Message message, - Consumer consumer, Acknowledgement acknowledgement) { + Consumer consumer, Acknowledgement acknowledgement) { try { return this.handlerMethod.invoke(message, data, consumer, acknowledgement); -// if (data instanceof List && !this.isConsumerRecordList) { -// return this.handlerMethod.invoke(message, consumer); -// } -// else { -// return this.handlerMethod.invoke(message, data, consumer); -// } + // if (data instanceof List && !this.isConsumerRecordList) { + // return this.handlerMethod.invoke(message, consumer); + // } + // else { + // return this.handlerMethod.invoke(message, data, consumer); + // } } catch (Exception ex) { throw new MessageConversionException("Cannot handle message", ex); } } - protected Type determineInferredType(Method method) { // NOSONAR complexity if (method == null) { return null; @@ -170,9 +167,9 @@ public abstract class PulsarMessagingMessageListenerAdapter { for (int i = 0; i < method.getParameterCount(); i++) { MethodParameter methodParameter = new MethodParameter(method, i); /* - * We're looking for a single non-annotated parameter, or one annotated with @Payload. - * We ignore parameters with type Message, Consumer, Ack, ConsumerRecord because they - * are not involved with conversion. + * We're looking for a single non-annotated parameter, or one annotated + * with @Payload. We ignore parameters with type Message, Consumer, Ack, + * ConsumerRecord because they are not involved with conversion. */ Type parameterType = methodParameter.getGenericParameterType(); boolean isNotConvertible = parameterIsType(parameterType, Message.class); @@ -182,7 +179,7 @@ public abstract class PulsarMessagingMessageListenerAdapter { } if (!isNotConvertible && !isMessageWithNoTypeInfo(parameterType) && (methodParameter.getParameterAnnotations().length == 0 - || methodParameter.hasParameterAnnotation(Payload.class))) { + || methodParameter.hasParameterAnnotation(Payload.class))) { if (genericParameterType == null) { genericParameterType = extractGenericParameterTypFromMethodParameter(methodParameter); } @@ -231,8 +228,8 @@ public abstract class PulsarMessagingMessageListenerAdapter { Type paramType = parameterizedType.getActualTypeArguments()[0]; this.isConsumerRecordList = paramType.equals(Messages.class); - boolean messageHasGeneric = paramType instanceof ParameterizedType - && ((ParameterizedType) paramType).getRawType().equals(org.springframework.messaging.Message.class); + boolean messageHasGeneric = paramType instanceof ParameterizedType && ((ParameterizedType) paramType) + .getRawType().equals(org.springframework.messaging.Message.class); this.isMessageList = paramType.equals(org.springframework.messaging.Message.class) || messageHasGeneric; if (messageHasGeneric) { genericParameterType = ((ParameterizedType) paramType).getActualTypeArguments()[0]; @@ -261,8 +258,8 @@ public abstract class PulsarMessagingMessageListenerAdapter { if (collectionType.equals(org.springframework.messaging.Message.class)) { return true; } - return collectionType instanceof ParameterizedType - && ((ParameterizedType) collectionType).getRawType().equals(org.springframework.messaging.Message.class); + return collectionType instanceof ParameterizedType && ((ParameterizedType) collectionType).getRawType() + .equals(org.springframework.messaging.Message.class); } } return false; @@ -284,13 +281,17 @@ public abstract class PulsarMessagingMessageListenerAdapter { if (parameterType instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) parameterType; Type rawType = parameterizedType.getRawType(); - if (rawType.equals(org.springframework.messaging.Message.class)) { + if (rawType.equals(org.springframework.messaging.Message.class)) { return parameterizedType.getActualTypeArguments()[0] instanceof WildcardType; } } - return parameterType.equals(org.springframework.messaging.Message.class); // could be Message without a generic type + return parameterType.equals(org.springframework.messaging.Message.class); // could + // be + // Message + // without + // a + // generic + // type } - - } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarRecordMessagingMessageListenerAdapter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarRecordMessagingMessageListenerAdapter.java index 75ae4efa..40c4ea49 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarRecordMessagingMessageListenerAdapter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/listener/adapter/PulsarRecordMessagingMessageListenerAdapter.java @@ -27,12 +27,11 @@ import org.springframework.pulsar.listener.Acknowledgement; import org.springframework.pulsar.listener.PulsarAcknowledgingMessageListener; /** - * A {@link MessageListener MessageListener} - * adapter that invokes a configurable {@link HandlerAdapter}; used when the factory is - * configured for the listener to receive individual messages. + * A {@link MessageListener MessageListener} adapter that invokes a configurable + * {@link HandlerAdapter}; used when the factory is configured for the listener to receive + * individual messages. * * @param payload type. - * * @author Soby Chacko */ @SuppressWarnings("serial") @@ -50,7 +49,7 @@ public class PulsarRecordMessagingMessageListenerAdapter extends PulsarMessag message = toMessagingMessage(record, consumer); } else { - //message = NULL_MESSAGE; + // message = NULL_MESSAGE; } if (logger.isDebugEnabled()) { this.logger.debug("Processing [" + message + "]"); @@ -58,7 +57,7 @@ public class PulsarRecordMessagingMessageListenerAdapter extends PulsarMessag try { Object result = invokeHandler(record, message, consumer, acknowledgement); if (result != null) { - //handleResult(result, record, message); + // handleResult(result, record, message); } } catch (Exception e) { // NOSONAR ex flow control diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/EndpointHandlerMethod.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/EndpointHandlerMethod.java index b4b90e58..c7053369 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/EndpointHandlerMethod.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/EndpointHandlerMethod.java @@ -115,11 +115,11 @@ public class EndpointHandlerMethod { private Method forClass(Class clazz) { if (this.method == null) { this.method = Arrays.stream(ReflectionUtils.getDeclaredMethods(clazz)) - .filter(mthd -> mthd.getName().equals(this.methodName)) - .findFirst() + .filter(mthd -> mthd.getName().equals(this.methodName)).findFirst() .orElseThrow(() -> new IllegalArgumentException( String.format("No method %s in class %s", this.methodName, clazz))); } return this.method; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/JavaUtils.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/JavaUtils.java index 65fbc0d5..9ead0326 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/JavaUtils.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/JavaUtils.java @@ -43,11 +43,10 @@ public final class JavaUtils { /** * Invoke {@link Consumer#accept(Object)} with the value if the condition is true. - * * @param condition the condition. - * @param value the value. - * @param consumer the consumer. - * @param the value type. + * @param value the value. + * @param consumer the consumer. + * @param the value type. * @return this. */ public JavaUtils acceptIfCondition(boolean condition, T value, Consumer consumer) { @@ -59,10 +58,9 @@ public final class JavaUtils { /** * Invoke {@link Consumer#accept(Object)} with the value if it is not null. - * - * @param value the value. + * @param value the value. * @param consumer the consumer. - * @param the value type. + * @param the value type. * @return this. */ public JavaUtils acceptIfNotNull(@Nullable T value, Consumer consumer) { @@ -74,8 +72,7 @@ public final class JavaUtils { /** * Invoke {@link Consumer#accept(Object)} with the value if it is not null or empty. - * - * @param value the value. + * @param value the value. * @param consumer the consumer. * @return this. */ @@ -89,10 +86,9 @@ public final class JavaUtils { /** * Invoke {@link Consumer#accept(Object)} with the cast value if the object is an * instance of the provided class. - * - * @param the type of the class to check and cast. - * @param type the type. - * @param value the value to be checked and cast. + * @param the type of the class to check and cast. + * @param type the type. + * @param value the value to be checked and cast. * @param consumer the consumer. * @return this. * @since 2.9 @@ -107,10 +103,9 @@ public final class JavaUtils { /** * Invoke {@link Consumer#accept(Object)} with the value if it is not null or empty. - * - * @param value the value. + * @param value the value. * @param consumer the consumer. - * @param the value type. + * @param the value type. * @return this. */ public JavaUtils acceptIfNotEmpty(List value, Consumer> consumer) { @@ -122,10 +117,9 @@ public final class JavaUtils { /** * Invoke {@link Consumer#accept(Object)} with the value if it is not null or empty. - * - * @param value the value. + * @param value the value. * @param consumer the consumer. - * @param the value type. + * @param the value type. * @return this. */ public JavaUtils acceptIfNotEmpty(T[] value, Consumer consumer) { @@ -138,13 +132,12 @@ public final class JavaUtils { /** * Invoke {@link BiConsumer#accept(Object, Object)} with the arguments if the * condition is true. - * * @param condition the condition. - * @param t1 the first consumer argument - * @param t2 the second consumer argument - * @param consumer the consumer. - * @param the first argument type. - * @param the second argument type. + * @param t1 the first consumer argument + * @param t2 the second consumer argument + * @param consumer the consumer. + * @param the first argument type. + * @param the second argument type. * @return this. */ public JavaUtils acceptIfCondition(boolean condition, T1 t1, T2 t2, BiConsumer consumer) { @@ -157,12 +150,11 @@ public final class JavaUtils { /** * Invoke {@link BiConsumer#accept(Object, Object)} with the arguments if the t2 * argument is not null. - * - * @param t1 the first argument - * @param t2 the second consumer argument + * @param t1 the first argument + * @param t2 the second consumer argument * @param consumer the consumer. - * @param the first argument type. - * @param the second argument type. + * @param the first argument type. + * @param the second argument type. * @return this. */ public JavaUtils acceptIfNotNull(T1 t1, T2 t2, BiConsumer consumer) { @@ -175,10 +167,9 @@ public final class JavaUtils { /** * Invoke {@link BiConsumer#accept(Object, Object)} with the arguments if the value * argument is not null or empty. - * - * @param t1 the first consumer argument. - * @param value the second consumer argument - * @param the first argument type. + * @param t1 the first consumer argument. + * @param value the second consumer argument + * @param the first argument type. * @param consumer the consumer. * @return this. */ @@ -188,5 +179,5 @@ public final class JavaUtils { } return this; } -} +} diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/MessageConverter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/MessageConverter.java index 7cbf7a27..41bacb30 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/MessageConverter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/MessageConverter.java @@ -22,4 +22,5 @@ package org.springframework.pulsar.support; * @author Soby Chacko */ public interface MessageConverter { + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessageConverter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessageConverter.java index 4ef43400..a9f3ff53 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessageConverter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessageConverter.java @@ -28,7 +28,6 @@ import org.springframework.pulsar.support.MessageConverter; * Pulsar batch message converter strategy. * * @param message type. - * * @author Soby Chacko */ public interface PulsarBatchMessageConverter extends MessageConverter { @@ -40,4 +39,5 @@ public interface PulsarBatchMessageConverter extends MessageConverter { default PulsarRecordMessageConverter getRecordMessageConverter() { return null; } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessagingMessageConverter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessagingMessageConverter.java index 24b492fa..8c784250 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessagingMessageConverter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarBatchMessagingMessageConverter.java @@ -33,7 +33,6 @@ import org.springframework.messaging.support.MessageBuilder; * Batch records message converter. * * @param message type. - * * @author Soby Chacko */ public class PulsarBatchMessagingMessageConverter implements PulsarBatchMessageConverter { @@ -59,22 +58,22 @@ public class PulsarBatchMessagingMessageConverter implements PulsarBatchMessa return MessageBuilder.createMessage(payloads, new MessageHeaders(Collections.emptyMap())); } - private Object obtainPayload(Type type, org.apache.pulsar.client.api.Message record, List conversionFailures) { - return this.recordConverter == null || !containerType(type) - ? extractAndConvertValue(record, type) + private Object obtainPayload(Type type, org.apache.pulsar.client.api.Message record, + List conversionFailures) { + return this.recordConverter == null || !containerType(type) ? extractAndConvertValue(record, type) : convert(record, type, conversionFailures); } private boolean containerType(Type type) { - return type instanceof ParameterizedType - && ((ParameterizedType) type).getActualTypeArguments().length == 1; + return type instanceof ParameterizedType && ((ParameterizedType) type).getActualTypeArguments().length == 1; } protected Object extractAndConvertValue(org.apache.pulsar.client.api.Message record, Type type) { return record.getValue(); } - protected Object convert(org.apache.pulsar.client.api.Message record, Type type, List conversionFailures) { + protected Object convert(org.apache.pulsar.client.api.Message record, Type type, + List conversionFailures) { try { Object payload = this.recordConverter .toMessage(record, null, ((ParameterizedType) type).getActualTypeArguments()[0]).getPayload(); @@ -91,4 +90,5 @@ public class PulsarBatchMessagingMessageConverter implements PulsarBatchMessa public T fromMessage(Messages message, String defaultTopic) { throw new UnsupportedOperationException(); } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarMessagingMessageConverter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarMessagingMessageConverter.java index c04dafe8..629674a6 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarMessagingMessageConverter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarMessagingMessageConverter.java @@ -30,11 +30,10 @@ import org.springframework.messaging.support.MessageBuilder; /** * - * A Messaging {@link org.springframework.pulsar.support.MessageConverter} implementation for a message listener that - * receives individual messages. + * A Messaging {@link org.springframework.pulsar.support.MessageConverter} implementation + * for a message listener that receives individual messages. * * @param message type - * * @author Soby Chacko */ public class PulsarMessagingMessageConverter implements PulsarRecordMessageConverter { @@ -44,7 +43,8 @@ public class PulsarMessagingMessageConverter implements PulsarRecordMessageCo @Override public Message toMessage(org.apache.pulsar.client.api.Message record, Consumer consumer, Type type) { - Message message = MessageBuilder.createMessage(extractAndConvertValue(record, type), new MessageHeaders(Collections.emptyMap())); + Message message = MessageBuilder.createMessage(extractAndConvertValue(record, type), + new MessageHeaders(Collections.emptyMap())); if (this.messagingConverter != null) { Class clazz = type instanceof Class ? (Class) type : type instanceof ParameterizedType ? (Class) ((ParameterizedType) type).getRawType() : Object.class; @@ -66,11 +66,9 @@ public class PulsarMessagingMessageConverter implements PulsarRecordMessageCo message = converted; } } - return null; //TODO + return null; // TODO } - - protected org.springframework.messaging.converter.MessageConverter getMessagingConverter() { return this.messagingConverter; } @@ -82,4 +80,5 @@ public class PulsarMessagingMessageConverter implements PulsarRecordMessageCo protected Object extractAndConvertValue(org.apache.pulsar.client.api.Message record, Type type) { return record.getValue(); } + } diff --git a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarRecordMessageConverter.java b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarRecordMessageConverter.java index 93a05f8f..a4907032 100644 --- a/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarRecordMessageConverter.java +++ b/spring-pulsar/src/main/java/org/springframework/pulsar/support/converter/PulsarRecordMessageConverter.java @@ -28,15 +28,12 @@ import org.springframework.pulsar.support.MessageConverter; * Pulsar specific record converter strategy. * * @param message type - * * @author Soby Chacko */ public interface PulsarRecordMessageConverter extends MessageConverter { - @NonNull - Message toMessage(org.apache.pulsar.client.api.Message record, Consumer consumer, - Type payloadType); + Message toMessage(org.apache.pulsar.client.api.Message record, Consumer consumer, Type payloadType); T fromMessage(Message message, String defaultTopic); diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/AbstractContainerBaseTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/AbstractContainerBaseTests.java index f4816432..092f261f 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/AbstractContainerBaseTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/AbstractContainerBaseTests.java @@ -37,5 +37,5 @@ abstract class AbstractContainerBaseTests { protected static String getHttpServiceUrl() { return PULSAR_CONTAINER.getHttpServiceUrl(); } -} +} diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/CachingPulsarProducerFactoryTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/CachingPulsarProducerFactoryTests.java index 1ff385b6..2d7b2497 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/CachingPulsarProducerFactoryTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/CachingPulsarProducerFactoryTests.java @@ -83,7 +83,8 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { Producer producer3 = producerFactory.createProducer("topic1", new StringSchema()); assertThat(producer1).isSameAs(producer2).isSameAs(producer3); - Cache, Producer> producerCache = getAssertedProducerCache(producerFactory, Collections.singletonList(cacheKey)); + Cache, Producer> producerCache = getAssertedProducerCache(producerFactory, + Collections.singletonList(cacheKey)); Producer cachedProducerProxy = producerCache.asMap().get(cacheKey); assertThat(cachedProducerProxy).isSameAs(producer1); } @@ -152,13 +153,11 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { Cache, Producer> producerCache = getAssertedProducerCache(producerFactory, Arrays.asList(cacheKey1, cacheKey2)); producerFactory.destroy(); - Awaitility.await() - .timeout(Duration.ofSeconds(5L)) - .untilAsserted(() -> { - assertThat(producerCache.asMap()).isEmpty(); - assertThat(actualProducer1.isConnected()).isFalse(); - assertThat(actualProducer2.isConnected()).isFalse(); - }); + Awaitility.await().timeout(Duration.ofSeconds(5L)).untilAsserted(() -> { + assertThat(producerCache.asMap()).isEmpty(); + assertThat(actualProducer1.isConnected()).isFalse(); + assertThat(actualProducer2.isConnected()).isFalse(); + }); } @Test @@ -171,13 +170,10 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { Cache, Producer> producerCache = getAssertedProducerCache(producerFactory, Collections.singletonList(cacheKey)); - Awaitility.await() - .pollDelay(Duration.ofSeconds(5L)) - .timeout(Duration.ofSeconds(10L)) - .untilAsserted(() -> { - assertThat(producerCache.asMap()).isEmpty(); - assertThat(actualProducer.isConnected()).isFalse(); - }); + Awaitility.await().pollDelay(Duration.ofSeconds(5L)).timeout(Duration.ofSeconds(10L)).untilAsserted(() -> { + assertThat(producerCache.asMap()).isEmpty(); + assertThat(actualProducer.isConnected()).isFalse(); + }); } @Test @@ -185,14 +181,14 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { pulsarClient = spy(pulsarClient); when(this.pulsarClient.newProducer(schema)).thenThrow(new RuntimeException("5150")); PulsarProducerFactory producerFactory = producerFactory(pulsarClient, Collections.emptyMap()); - assertThatThrownBy(() -> producerFactory.createProducer("topic1", schema)) - .isInstanceOf(RuntimeException.class) + assertThatThrownBy(() -> producerFactory.createProducer("topic1", schema)).isInstanceOf(RuntimeException.class) .hasMessage("5150"); getAssertedProducerCache(producerFactory, Collections.emptyList()); } @Override - protected void assertProducerHasTopicSchemaAndRouter(Producer producer, String topic, Schema schema, MessageRouter router) { + protected void assertProducerHasTopicSchemaAndRouter(Producer producer, String topic, Schema schema, + MessageRouter router) { super.assertProducerHasTopicSchemaAndRouter(actualProducerFrom(producer), topic, schema, router); } @@ -204,10 +200,10 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { } @SuppressWarnings("unchecked") - private Cache, Producer> getAssertedProducerCache(PulsarProducerFactory producerFactory, - List> expectedCacheKeys) { - Cache, Producer> producerCache = (Cache, Producer>) - ReflectionTestUtils.getField(producerFactory, "producerCache"); + private Cache, Producer> getAssertedProducerCache( + PulsarProducerFactory producerFactory, List> expectedCacheKeys) { + Cache, Producer> producerCache = (Cache, Producer>) ReflectionTestUtils + .getField(producerFactory, "producerCache"); assertThat(producerCache).isNotNull(); if (ObjectUtils.isEmpty(expectedCacheKeys)) { assertThat(producerCache.asMap()).isEmpty(); @@ -219,7 +215,8 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { } @Override - protected CachingPulsarProducerFactory producerFactory(PulsarClient pulsarClient, Map producerConfig) { + protected CachingPulsarProducerFactory producerFactory(PulsarClient pulsarClient, + Map producerConfig) { CachingPulsarProducerFactory producerFactory = new CachingPulsarProducerFactory<>(pulsarClient, producerConfig, Duration.ofMinutes(5L), 10L, 2); producerFactories.add(producerFactory); @@ -232,15 +229,13 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { @Test void nullSchemaIsNotAllowed() { assertThatThrownBy(() -> new ProducerCacheKey<>(null, "topic1", null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("'schema' must be non-null"); + .isInstanceOf(IllegalArgumentException.class).hasMessage("'schema' must be non-null"); } @Test void nullTopicIsNotAllowed() { assertThatThrownBy(() -> new ProducerCacheKey<>(schema, null, null)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("'topic' must be non-null"); + .isInstanceOf(IllegalArgumentException.class).hasMessage("'topic' must be non-null"); } @ParameterizedTest(name = "equals({0}) should be {2}") @@ -255,32 +250,33 @@ class CachingPulsarProducerFactoryTests extends PulsarProducerFactoryTests { static Stream equalsAndHashCodeTestProvider() { MessageRouter router1 = mock(MessageRouter.class); ProducerCacheKey key1 = new ProducerCacheKey<>(Schema.STRING, "topic1", router1); - return Stream.of( - arguments(Named.of("differentClass", key1), "someStrangeObject", false), + return Stream.of(arguments(Named.of("differentClass", key1), "someStrangeObject", false), arguments(Named.of("null", key1), null, false), arguments(Named.of("sameInstance", key1), key1, true), - arguments(Named.of("sameSchemaSameTopicSameNullRouter", + arguments( + Named.of("sameSchemaSameTopicSameNullRouter", new ProducerCacheKey<>(Schema.STRING, "topic1", null)), - new ProducerCacheKey<>(Schema.STRING, "topic1", null), true), - arguments(Named.of("sameSchemaSameTopicSameNonNullRouter", + new ProducerCacheKey<>(Schema.STRING, "topic1", null), true), + arguments( + Named.of("sameSchemaSameTopicSameNonNullRouter", new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), - new ProducerCacheKey<>(Schema.STRING, "topic1", router1), true), - arguments(Named.of("differentSchemaInstanceSameSchemaType", + new ProducerCacheKey<>(Schema.STRING, "topic1", router1), true), + arguments( + Named.of("differentSchemaInstanceSameSchemaType", new ProducerCacheKey<>(new StringSchema(), "topic1", router1)), - new ProducerCacheKey<>(new StringSchema(), "topic1", router1), true), - arguments(Named.of("differentSchemaType", + new ProducerCacheKey<>(new StringSchema(), "topic1", router1), true), + arguments(Named.of("differentSchemaType", new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), + new ProducerCacheKey<>(Schema.INT64, "topic1", router1), false), + arguments(Named.of("differentTopic", new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), + new ProducerCacheKey<>(Schema.STRING, "topic2", router1), false), + arguments( + Named.of("differentNonNullRouter", new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), - new ProducerCacheKey<>(Schema.INT64, "topic1", router1), false), - arguments(Named.of("differentTopic", - new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), - new ProducerCacheKey<>(Schema.STRING, "topic2", router1), false), - arguments(Named.of("differentNonNullRouter", - new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), - new ProducerCacheKey<>(Schema.STRING, "topic1", mock(MessageRouter.class)), false), - arguments(Named.of("differentNullRouter", - new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), - new ProducerCacheKey<>(Schema.STRING, "topic1", null), false) - ); + new ProducerCacheKey<>(Schema.STRING, "topic1", mock(MessageRouter.class)), false), + arguments(Named.of("differentNullRouter", new ProducerCacheKey<>(Schema.STRING, "topic1", router1)), + new ProducerCacheKey<>(Schema.STRING, "topic1", null), false)); } + } + } diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultConsumerTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultConsumerTests.java index 275fb5cb..d91fbbda 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultConsumerTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultConsumerTests.java @@ -44,24 +44,25 @@ class DefaultConsumerTests extends AbstractContainerBaseTests { strings.add("foobar-012"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-012"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); CountDownLatch latch = new CountDownLatch(1); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); - pulsarContainerProperties.setMessageListener( - (PulsarRecordMessageListener) (consumer, msg) -> latch.countDown()); + pulsarContainerProperties + .setMessageListener((PulsarRecordMessageListener) (consumer, msg) -> latch.countDown()); pulsarContainerProperties.setSchema(Schema.STRING); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( pulsarConsumerFactory, pulsarContainerProperties); container.start(); Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-012"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); final CompletableFuture future = pulsarTemplate.sendAsync("hello john doe"); latch.await(10, TimeUnit.SECONDS); pulsarClient.close(); } + } diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultPulsarProducerFactoryTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultPulsarProducerFactoryTests.java index c5db9040..456248b1 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultPulsarProducerFactoryTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/DefaultPulsarProducerFactoryTests.java @@ -48,7 +48,9 @@ class DefaultPulsarProducerFactoryTests extends PulsarProducerFactoryTests { } @Override - protected PulsarProducerFactory producerFactory(PulsarClient pulsarClient, Map producerConfig) { + protected PulsarProducerFactory producerFactory(PulsarClient pulsarClient, + Map producerConfig) { return new DefaultPulsarProducerFactory<>(pulsarClient, producerConfig); } + } diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/FailoverConsumerTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/FailoverConsumerTests.java index 04705256..686ecf58 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/FailoverConsumerTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/FailoverConsumerTests.java @@ -46,9 +46,7 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { @Test void testFailOverConsumersOnPartitionedTopic() throws Exception { - PulsarAdmin admin = PulsarAdmin.builder() - .serviceHttpUrl(getHttpServiceUrl()) - .build(); + PulsarAdmin admin = PulsarAdmin.builder().serviceHttpUrl(getHttpServiceUrl()).build(); String topicName = "persistent://public/default/my-part-topic-1"; int numPartitions = 3; @@ -59,13 +57,13 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { topics.add("my-part-topic-1"); config.put("topicNames", topics); config.put("subscriptionName", "my-part-subscription-1"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); CountDownLatch latch = new CountDownLatch(3); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); - pulsarContainerProperties.setMessageListener((PulsarRecordMessageListener) (consumer, msg) -> latch.countDown()); + pulsarContainerProperties + .setMessageListener((PulsarRecordMessageListener) (consumer, msg) -> latch.countDown()); pulsarContainerProperties.setSubscriptionType(SubscriptionType.Failover); pulsarContainerProperties.setSchema(Schema.STRING); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( @@ -80,7 +78,8 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "my-part-topic-1"); prodConfig.put("messageRoutingMode", MessageRoutingMode.CustomPartition); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); pulsarTemplate.sendAsync("hello john doe", new FooRouter()); @@ -90,7 +89,6 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { assertThat(await).isTrue(); } - static class FooRouter implements MessageRouter { @Serial @@ -100,6 +98,7 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { public int choosePartition(Message msg, TopicMetadata metadata) { return 0; } + } static class BarRouter implements MessageRouter { @@ -111,6 +110,7 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { public int choosePartition(Message msg, TopicMetadata metadata) { return 1; } + } static class BuzzRouter implements MessageRouter { @@ -122,5 +122,7 @@ class FailoverConsumerTests extends AbstractContainerBaseTests { public int choosePartition(Message msg, TopicMetadata metadata) { return 2; } + } + } diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarMessageListenerContainerTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarMessageListenerContainerTests.java index c8a46e26..b496430e 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarMessageListenerContainerTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarMessageListenerContainerTests.java @@ -51,7 +51,6 @@ import org.springframework.pulsar.listener.PulsarContainerProperties; import org.springframework.pulsar.listener.PulsarRecordMessageListener; import org.springframework.util.Assert; - /** * @author Soby Chacko */ @@ -64,15 +63,13 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { strings.add("foobar-011"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-011"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); - pulsarContainerProperties.setMessageListener( - (PulsarRecordMessageListener) (consumer, msg) -> { - }); + pulsarContainerProperties.setMessageListener((PulsarRecordMessageListener) (consumer, msg) -> { + }); pulsarContainerProperties.setSchema(Schema.STRING); pulsarContainerProperties.setAckMode(PulsarContainerProperties.AckMode.RECORD); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( @@ -89,7 +86,8 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-011"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); for (int i = 0; i < 10; i++) { pulsarTemplate.sendAsync("hello john doe"); @@ -106,15 +104,14 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { strings.add("foobar-012"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-012"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); CountDownLatch latch = new CountDownLatch(10); - pulsarContainerProperties.setMessageListener( - (PulsarRecordMessageListener) (consumer, msg) -> latch.countDown()); + pulsarContainerProperties + .setMessageListener((PulsarRecordMessageListener) (consumer, msg) -> latch.countDown()); pulsarContainerProperties.setSchema(Schema.STRING); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( pulsarConsumerFactory, pulsarContainerProperties); @@ -123,7 +120,8 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-012"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); for (int i = 0; i < 10; i++) { pulsarTemplate.sendAsync("hello john doe"); @@ -142,21 +140,18 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { strings.add("foobar-013"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-013"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); CountDownLatch latch = new CountDownLatch(10); - pulsarContainerProperties.setMessageListener( - (PulsarRecordMessageListener) (consumer, msg) -> { - latch.countDown(); - if (latch.getCount() % 2 == 0) { - throw new RuntimeException("fail"); - } - } - ); + pulsarContainerProperties.setMessageListener((PulsarRecordMessageListener) (consumer, msg) -> { + latch.countDown(); + if (latch.getCount() % 2 == 0) { + throw new RuntimeException("fail"); + } + }); pulsarContainerProperties.setSchema(Schema.STRING); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( pulsarConsumerFactory, pulsarContainerProperties); @@ -165,14 +160,16 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-013"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); for (int i = 0; i < 10; i++) { pulsarTemplate.sendAsync("hello john doe"); } assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); Thread.sleep(1_000); - // Half of the message get acknowledged, and the other half gets negatively acknowledged. + // Half of the message get acknowledged, and the other half gets negatively + // acknowledged. verify(containerConsumer, times(5)).acknowledge(any(Message.class)); verify(containerConsumer, times(5)).negativeAcknowledge(any(Message.class)); container.stop(); @@ -187,10 +184,9 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { strings.add("foobar-014"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-014"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); final List acksObjects = new ArrayList<>(); @@ -217,13 +213,15 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-014"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); for (int i = 0; i < 10; i++) { pulsarTemplate.sendAsync("hello john doe"); } assertThat(latch.await(30, TimeUnit.SECONDS)).isTrue(); - //We are asserting that we got 10 valid ack objects through the receive method invocation. + // We are asserting that we got 10 valid ack objects through the receive method + // invocation. assertThat(acksObjects.size()).isEqualTo(10); verify(containerConsumer, times(10)).acknowledge(any(Message.class)); @@ -239,10 +237,9 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { strings.add("foobar-015"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-015"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); pulsarContainerProperties.setMaxNumMessages(10); @@ -256,8 +253,7 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { return null; }).when(pulsarBatchMessageListener).received(any(Consumer.class), any(Messages.class)); - pulsarContainerProperties.setMessageListener( - pulsarBatchMessageListener); + pulsarContainerProperties.setMessageListener(pulsarBatchMessageListener); pulsarContainerProperties.setSchema(Schema.STRING); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( pulsarConsumerFactory, pulsarContainerProperties); @@ -266,7 +262,8 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-015"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); for (int i = 0; i < 10; i++) { pulsarTemplate.sendAsync("hello john doe"); @@ -286,10 +283,9 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { strings.add("foobar-016"); config.put("topicNames", strings); config.put("subscriptionName", "foobar-sb-016"); - final PulsarClient pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); - final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>(pulsarClient, config); + final PulsarClient pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); + final DefaultPulsarConsumerFactory pulsarConsumerFactory = new DefaultPulsarConsumerFactory<>( + pulsarClient, config); PulsarContainerProperties pulsarContainerProperties = new PulsarContainerProperties(); pulsarContainerProperties.setMaxNumMessages(10); @@ -303,8 +299,7 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { throw new RuntimeException(); }).when(pulsarBatchMessageListener).received(any(Consumer.class), any(Messages.class)); - pulsarContainerProperties.setMessageListener( - pulsarBatchMessageListener); + pulsarContainerProperties.setMessageListener(pulsarBatchMessageListener); pulsarContainerProperties.setSchema(Schema.STRING); DefaultPulsarMessageListenerContainer container = new DefaultPulsarMessageListenerContainer<>( pulsarConsumerFactory, pulsarContainerProperties); @@ -313,7 +308,8 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { Map prodConfig = new HashMap<>(); prodConfig.put("topicName", "foobar-016"); - final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>(pulsarClient, prodConfig); + final DefaultPulsarProducerFactory pulsarProducerFactory = new DefaultPulsarProducerFactory<>( + pulsarClient, prodConfig); final PulsarTemplate pulsarTemplate = new PulsarTemplate<>(pulsarProducerFactory); for (int i = 0; i < 10; i++) { pulsarTemplate.sendAsync("hello john doe"); @@ -329,16 +325,15 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { private Consumer spyOnConsumer(DefaultPulsarMessageListenerContainer container) { Consumer consumer = getPropertyValue(container, "listenerConsumer.consumer", Consumer.class); consumer = spy(consumer); - new DirectFieldAccessor(getPropertyValue(container, "listenerConsumer")) - .setPropertyValue("consumer", consumer); + new DirectFieldAccessor(getPropertyValue(container, "listenerConsumer")).setPropertyValue("consumer", consumer); return consumer; } /** - * Uses nested {@link DirectFieldAccessor}s to obtain a property using dotted notation to traverse fields; e.g. - * "foo.bar.baz" will obtain a reference to the baz field of the bar field of foo. Adopted from Spring Integration. - * - * @param root The object. + * Uses nested {@link DirectFieldAccessor}s to obtain a property using dotted notation + * to traverse fields; e.g. "foo.bar.baz" will obtain a reference to the baz field of + * the bar field of foo. Adopted from Spring Integration. + * @param root The object. * @param propertyPath The path. * @return The field. */ @@ -369,4 +364,5 @@ class PulsarMessageListenerContainerTests extends AbstractContainerBaseTests { } return (T) value; } + } diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarProducerFactoryTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarProducerFactoryTests.java index 07b29268..1c37c9f8 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarProducerFactoryTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarProducerFactoryTests.java @@ -35,7 +35,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; /** - * Common tests for {@link DefaultPulsarProducerFactory} and {@link CachingPulsarProducerFactory}. + * Common tests for {@link DefaultPulsarProducerFactory} and + * {@link CachingPulsarProducerFactory}. * * @author Chris Bono */ @@ -47,9 +48,7 @@ abstract class PulsarProducerFactoryTests extends AbstractContainerBaseTests { @BeforeEach void createPulsarClient() throws PulsarClientException { - pulsarClient = PulsarClient.builder() - .serviceUrl(getPulsarBrokerUrl()) - .build(); + pulsarClient = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build(); } @AfterEach @@ -78,7 +77,8 @@ abstract class PulsarProducerFactoryTests extends AbstractContainerBaseTests { @Test void createProducerWithDefaultTopic() throws PulsarClientException { - PulsarProducerFactory producerFactory = producerFactory(pulsarClient, Collections.singletonMap("topicName", "topic0")); + PulsarProducerFactory producerFactory = producerFactory(pulsarClient, + Collections.singletonMap("topicName", "topic0")); try (Producer producer = producerFactory.createProducer(null, schema)) { assertProducerHasTopicSchemaAndRouter(producer, "topic0", schema, null); } @@ -86,7 +86,8 @@ abstract class PulsarProducerFactoryTests extends AbstractContainerBaseTests { @Test void createProducerWithDefaultTopicAndMessageRouter() throws PulsarClientException { - PulsarProducerFactory producerFactory = producerFactory(pulsarClient, Collections.singletonMap("topicName", "topic0")); + PulsarProducerFactory producerFactory = producerFactory(pulsarClient, + Collections.singletonMap("topicName", "topic0")); MessageRouter router = mock(MessageRouter.class); try (Producer producer = producerFactory.createProducer(null, schema, router)) { assertProducerHasTopicSchemaAndRouter(producer, "topic0", schema, router); @@ -101,22 +102,22 @@ abstract class PulsarProducerFactoryTests extends AbstractContainerBaseTests { .hasMessage("Topic must be specified when no default topic is configured"); } - protected void assertProducerHasTopicSchemaAndRouter(Producer producer, String topic, Schema schema, MessageRouter router) { + protected void assertProducerHasTopicSchemaAndRouter(Producer producer, String topic, Schema schema, + MessageRouter router) { assertThat(producer.getTopic()).isEqualTo(topic); assertThat(producer).hasFieldOrPropertyWithValue("schema", schema); - assertThat(producer) - .extracting("conf").asInstanceOf(InstanceOfAssertFactories.type(ProducerConfigurationData.class)) - .extracting(ProducerConfigurationData::getCustomMessageRouter) - .isSameAs(router); + assertThat(producer).extracting("conf") + .asInstanceOf(InstanceOfAssertFactories.type(ProducerConfigurationData.class)) + .extracting(ProducerConfigurationData::getCustomMessageRouter).isSameAs(router); } /** * Subclasses override to provide concrete {@link PulsarProducerFactory} instance. - * * @param pulsarClient the Pulsar client * @param producerConfig the Pulsar producers config * @return a Pulsar producer factory instance to use for the tests */ - protected abstract PulsarProducerFactory producerFactory(PulsarClient pulsarClient, Map producerConfig); + protected abstract PulsarProducerFactory producerFactory(PulsarClient pulsarClient, + Map producerConfig); } diff --git a/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarTemplateTests.java b/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarTemplateTests.java index e5896fa1..2bd31804 100644 --- a/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarTemplateTests.java +++ b/spring-pulsar/src/test/java/org/springframework/pulsar/core/PulsarTemplateTests.java @@ -55,12 +55,15 @@ import org.junit.jupiter.params.provider.MethodSource; class PulsarTemplateTests extends AbstractContainerBaseTests { private static final String SAMPLE_MESSAGE_KEY = "sample-key"; - private static final TypedMessageBuilderCustomizer sampleMessageKeyCustomizer = - messageBuilder -> messageBuilder.key(SAMPLE_MESSAGE_KEY); + + private static final TypedMessageBuilderCustomizer sampleMessageKeyCustomizer = messageBuilder -> messageBuilder + .key(SAMPLE_MESSAGE_KEY); @ParameterizedTest(name = "{0}") @MethodSource("sendMessageTestProvider") - void sendMessageTest(String topic, Map producerConfig, SendHandler handler, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter router) throws Exception { + void sendMessageTest(String topic, Map producerConfig, SendHandler handler, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter router) + throws Exception { String subscription = topic + "-sub"; String msgPayload = topic + "-msg"; if (router != null) { @@ -69,11 +72,14 @@ class PulsarTemplateTests extends AbstractContainerBaseTests { } } try (PulsarClient client = PulsarClient.builder().serviceUrl(getPulsarBrokerUrl()).build()) { - try (Consumer consumer = client.newConsumer(Schema.STRING).topic(topic).subscriptionName(subscription).subscribe()) { - PulsarProducerFactory producerFactory = new DefaultPulsarProducerFactory<>(client, producerConfig); + try (Consumer consumer = client.newConsumer(Schema.STRING).topic(topic) + .subscriptionName(subscription).subscribe()) { + PulsarProducerFactory producerFactory = new DefaultPulsarProducerFactory<>(client, + producerConfig); PulsarTemplate pulsarTemplate = new PulsarTemplate<>(producerFactory); - Object sendResponse = handler.doSend(pulsarTemplate, topic, msgPayload, typedMessageBuilderCustomizer, router); + Object sendResponse = handler.doSend(pulsarTemplate, topic, msgPayload, typedMessageBuilderCustomizer, + router); if (sendResponse instanceof CompletableFuture) { sendResponse = ((CompletableFuture) sendResponse).get(3, TimeUnit.SECONDS); } @@ -86,9 +92,10 @@ class PulsarTemplateTests extends AbstractContainerBaseTests { } assertThat(msg.getData()).asString().isEqualTo(msgPayload); - // Make sure the producer was closed by the template (albeit indirectly as client removes closed producers) - await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> - assertThat(client).extracting("producers").asInstanceOf(InstanceOfAssertFactories.COLLECTION).isEmpty()); + // Make sure the producer was closed by the template (albeit indirectly as + // client removes closed producers) + await().atMost(Duration.ofSeconds(3)).untilAsserted(() -> assertThat(client).extracting("producers") + .asInstanceOf(InstanceOfAssertFactories.COLLECTION).isEmpty()); } } } @@ -96,72 +103,76 @@ class PulsarTemplateTests extends AbstractContainerBaseTests { static Stream sendMessageTestProvider() { return Stream.of( - - arguments(Named.of("sendMessageToDefaultTopic", "smt-topic-1"), Collections.singletonMap("topicName", "smt-topic-1"), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg), - null, + arguments(Named.of("sendMessageToDefaultTopic", "smt-topic-1"), + Collections.singletonMap("topicName", "smt-topic-1"), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg), null, null), - arguments(Named.of("sendMessageToDefaultTopicWithRouter", "smt-topic-2"), Collections.singletonMap("topicName", "smt-topic-2"), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg, router), - null, - mockRouter()), - arguments(Named.of("sendMessageToDefaultTopicWithCustomizer", "smt-topic-3"), Collections.singletonMap("topicName", "smt-topic-3"), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg, customizer), - sampleMessageKeyCustomizer, - null), - arguments(Named.of("sendMessageToDefaultTopicWithCustomizerAndRouter", "smt-topic-4"), Collections.singletonMap("topicName", "smt-topic-4"), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg, customizer, router), - sampleMessageKeyCustomizer, - mockRouter()), + arguments(Named.of("sendMessageToDefaultTopicWithRouter", "smt-topic-2"), + Collections.singletonMap("topicName", "smt-topic-2"), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg, + router), + null, mockRouter()), + arguments(Named.of("sendMessageToDefaultTopicWithCustomizer", "smt-topic-3"), + Collections.singletonMap("topicName", "smt-topic-3"), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg, + customizer), + sampleMessageKeyCustomizer, null), + arguments(Named.of("sendMessageToDefaultTopicWithCustomizerAndRouter", "smt-topic-4"), + Collections.singletonMap("topicName", "smt-topic-4"), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(msg, + customizer, router), + sampleMessageKeyCustomizer, mockRouter()), arguments(Named.of("sendMessageToSpecificTopic", "smt-topic-5"), Collections.emptyMap(), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(topic, msg), - null, - null), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(topic, + msg), + null, null), arguments(Named.of("sendMessageToSpecificTopicWithRouter", "smt-topic-6"), Collections.emptyMap(), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(topic, msg, null, router), - null, - mockRouter()), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(topic, msg, + null, router), + null, mockRouter()), arguments(Named.of("sendMessageToSpecificTopicWithCustomizer", "smt-topic-7"), Collections.emptyMap(), - (SendHandler) (template, topic, msg, customizer, router) -> template.send(topic, msg, customizer), - sampleMessageKeyCustomizer, - null), - arguments(Named.of("sendMessageToSpecificTopicWithCustomizerAndRouter", "smt-topic-8"), Collections.emptyMap(), - (SendHandler) PulsarTemplate::send, - sampleMessageKeyCustomizer, - mockRouter()), - arguments(Named.of("sendAsyncMessageToDefaultTopic", "smt-topic-9"), Collections.singletonMap("topicName", "smt-topic-9"), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(msg), - null, - null), - arguments(Named.of("sendAsyncMessageToDefaultTopicWithRouter", "smt-topic-10"), Collections.singletonMap("topicName", "smt-topic-10"), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(msg, router), - null, - mockRouter()), - arguments(Named.of("sendAsyncMessageToDefaultTopicWithCustomizer", "smt-topic-11"), Collections.singletonMap("topicName", "smt-topic-11"), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(msg, customizer), - sampleMessageKeyCustomizer, - null), - arguments(Named.of("sendAsyncMessageToDefaultTopicWithCustomizerAndRouter", "smt-topic-12"), Collections.singletonMap("topicName", "smt-topic-12"), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(msg, customizer, router), - sampleMessageKeyCustomizer, - mockRouter()), + (SendHandler) (template, topic, msg, customizer, router) -> template.send(topic, msg, + customizer), + sampleMessageKeyCustomizer, null), + arguments(Named.of("sendMessageToSpecificTopicWithCustomizerAndRouter", "smt-topic-8"), + Collections.emptyMap(), (SendHandler) PulsarTemplate::send, + sampleMessageKeyCustomizer, mockRouter()), + arguments(Named.of("sendAsyncMessageToDefaultTopic", "smt-topic-9"), + Collections.singletonMap("topicName", "smt-topic-9"), + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(msg), + null, null), + arguments(Named.of("sendAsyncMessageToDefaultTopicWithRouter", "smt-topic-10"), + Collections.singletonMap("topicName", "smt-topic-10"), + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(msg, router), + null, mockRouter()), + arguments(Named.of("sendAsyncMessageToDefaultTopicWithCustomizer", "smt-topic-11"), + Collections.singletonMap("topicName", "smt-topic-11"), + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(msg, customizer), + sampleMessageKeyCustomizer, null), + arguments(Named.of("sendAsyncMessageToDefaultTopicWithCustomizerAndRouter", "smt-topic-12"), + Collections.singletonMap("topicName", "smt-topic-12"), + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(msg, customizer, router), + sampleMessageKeyCustomizer, mockRouter()), arguments(Named.of("sendAsyncMessageToSpecificTopic", "smt-topic-13"), Collections.emptyMap(), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(topic, msg), - null, - null), + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(topic, msg), + null, null), arguments(Named.of("sendAsyncMessageToSpecificTopicWithRouter", "smt-topic-14"), Collections.emptyMap(), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(topic, msg, null, router), - null, - mockRouter()), - arguments(Named.of("sendAsyncMessageToSpecificTopicWithCustomizer", "smt-topic-15"), Collections.emptyMap(), - (SendHandler>) (template, topic, msg, customizer, router) -> template.sendAsync(topic, msg, customizer), - sampleMessageKeyCustomizer, - null), - arguments(Named.of("sendAsyncMessageToSpecificTopicWithCustomizerAndRouter", "smt-topic-16"), Collections.emptyMap(), - (SendHandler>) PulsarTemplate::sendAsync, - sampleMessageKeyCustomizer, - mockRouter()) - ); + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(topic, msg, null, router), + null, mockRouter()), + arguments(Named.of("sendAsyncMessageToSpecificTopicWithCustomizer", "smt-topic-15"), + Collections.emptyMap(), + (SendHandler>) (template, topic, msg, customizer, + router) -> template.sendAsync(topic, msg, customizer), + sampleMessageKeyCustomizer, null), + arguments(Named.of("sendAsyncMessageToSpecificTopicWithCustomizerAndRouter", "smt-topic-16"), + Collections.emptyMap(), (SendHandler>) PulsarTemplate::sendAsync, + sampleMessageKeyCustomizer, mockRouter())); } private static MessageRouter mockRouter() { @@ -171,7 +182,12 @@ class PulsarTemplateTests extends AbstractContainerBaseTests { } @FunctionalInterface - interface SendHandler { - V doSend(PulsarTemplate template, String topic, String msg, TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter router) throws PulsarClientException; + interface SendHandler { + + V doSend(PulsarTemplate template, String topic, String msg, + TypedMessageBuilderCustomizer typedMessageBuilderCustomizer, MessageRouter router) + throws PulsarClientException; + } + } diff --git a/src/nohttp/allowlist.lines b/src/nohttp/allowlist.lines new file mode 100644 index 00000000..6d24f3a6 --- /dev/null +++ b/src/nohttp/allowlist.lines @@ -0,0 +1,10 @@ +^http://[^/]*nabble.com.* +^http://blog.opensecurityresearch.com/.* +^http://iharder.sourceforge.net/current/java/base64/ +^http://jaspan.com.* +^http://lists.webappsec.org/.* +^http://webblaze.cs.berkeley.edu/.* +^http://www.w3.org/2000/09/xmldsig.* +^http://www.w3.org/2001/10/xml-exc-c14n +^http://www.w3.org/2001/04/xmldsig-more +^http://www.w3.org/2001/04/xmlenc \ No newline at end of file