[BUILD] Build improvements (#52)

* Each module has its own build.gradle
* Functionality moved into plugins in buildSrc/
* Add Spring javaformat
* Add release plugins (#20)

Fixes #21
This commit is contained in:
Chris Bono
2022-08-06 18:49:18 -05:00
committed by GitHub
parent 3b1a611159
commit de2d8a0600
147 changed files with 4782 additions and 1201 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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...

View File

@@ -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
}
}
}
}
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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<Project> {
@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
}
}
}
}
}
}

View File

@@ -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<Project> {
@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/'
}
}
}
}

View File

@@ -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:
*
* <ul>
* <li>The {@code https://repo.spring.io/release} Maven repository is configured and
* limited to dependencies in the following groups:
* <ul>
* <li>{@code io.spring.asciidoctor}
* <li>{@code io.spring.asciidoctor.backends}
* <li>{@code io.spring.docresources}
* </ul>
* <li>All warnings are made fatal.
* <li>The version of AsciidoctorJ is upgraded to 2.4.3.
* <li>An {@code asciidoctorExtensions} configuration is created.
* <li>For each {@link AsciidoctorTask} (HTML only):
* <ul>
* <li>A task is created to sync the documentation resources to its output directory.
* <li>{@code doctype} {@link AsciidoctorTask#options(Map) option} is configured.
* <li>The {@code backend} is configured.
* </ul>
* <li>For each {@link AbstractAsciidoctorTask} (HTML and PDF):
* <ul>
* <li>{@link AsciidoctorTask#attributes(Map) Attributes} are configured to enable
* warnings for references to missing attributes, the GitHub tag, the Artifactory repo for
* the current version, etc.
* <li>{@link AbstractAsciidoctorTask#baseDirFollowsSourceDir() baseDirFollowsSourceDir()}
* is enabled.
* <li>{@code asciidoctorExtensions} is added to the task's configurations.
* </ul>
* </ul>
*
* @author Andy Wilkinson
*/
public class AsciidoctorConventionsPlugin implements Plugin<Project> {
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<String, Object> 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;
}
}

View File

@@ -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<String, String> properties = new HashMap<>();
private final DirectoryProperty destinationDirectory;
private List<String> resourceNames = new ArrayList<>();
public ExtractResources() {
this.destinationDirectory = getProject().getObjects().directoryProperty();
}
@Input
public List<String> getResourceNames() {
return this.resourceNames;
}
public void setResourcesNames(List<String> 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<String, String> 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()));
}
}
}

View File

@@ -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:
*
* <ul>
* <li>The project is configured with source and target compatibility of 17
* <li>{@link SpringJavaFormatPlugin Spring Java Format}, {@link CheckstylePlugin
* Checkstyle}, {@link TestFailuresPlugin Test Failures}, and {@link TestRetryPlugin Test
* Retry} plugins are applied
* <li>{@link Test} tasks are configured:
* <ul>
* <li>to use JUnit Platform
* <li>with a max heap of 1024M
* <li>to run after any Checkstyle and format checking tasks
* </ul>
* <li>A {@code testRuntimeOnly} dependency upon
* {@code org.junit.platform:junit-platform-launcher} is added to projects with the
* {@link JavaPlugin} applied
* <li>{@link JavaCompile}, {@link Javadoc}, and {@link Format} tasks are configured to
* use UTF-8 encoding
* <li>{@link JavaCompile} tasks are configured to:
* <ul>
* <li>Use {@code -parameters}.
* <li>Treat warnings as errors
* <li>Enable {@code unchecked}, {@code deprecation}, {@code rawtypes}, and {@code varags}
* warnings
* </ul>
* <li>{@link Jar} tasks are configured to produce jars with LICENSE.txt and NOTICE.txt
* files and the following manifest entries:
* <ul>
* <li>{@code Automatic-Module-Name}
* <li>{@code Build-Jdk-Spec}
* <li>{@code Built-By}
* <li>{@code Implementation-Title}
* <li>{@code Implementation-Version}
* </ul>
* <li>{@code spring-pulsar-dependencies} is used for dependency management</li>
* </ul>
*
* <p/>
*
* @author Andy Wilkinson
* @author Christoph Dreis
* @author Mike Smithson
* @author Scott Frederick
* @author Chris Bono
*/
public class JavaConventionsPlugin implements Plugin<Project> {
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<String> sourceJarTaskNames = sourceSets.stream().map(SourceSet::getSourcesJarTaskName)
.collect(Collectors.toSet());
Set<String> 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<String, Object> 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<String> sourceJarTaskNames,
Set<String> 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<String> 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);
}
}

View File

@@ -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<File>) () -> new File(this.sourceSet.getOutput().getResourcesDir(),
"META-INF/spring.factories"))
.withPathSensitivity(PathSensitivity.RELATIVE).withPropertyName("spring.factories");
getInputs()
.file((Callable<File>) () -> 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<String>) () -> 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<String> classNames = new LinkedHashSet<>();
classNames.addAll(readSpringFactories());
classNames.addAll(readAutoConfigurationsFile());
Set<String> 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<String> 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<String> 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<String> 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;
}
}

View File

@@ -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:
*
* <ul>
* <li>Applying the {@link ConfigurationPropertiesPlugin}.
* <li>Adding a dependency on the auto-configuration annotation processor.
* <li>Defining a task that produces metadata describing the auto-configuration. The
* metadata is made available as an artifact in the
* </ul>
*
* @author Andy Wilkinson
*/
public class AutoConfigurationPlugin implements Plugin<Project> {
/**
* 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<File>) task::getOutputFile), (artifact) -> artifact.builtBy(task));
});
});
}
}

View File

@@ -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<AutoConfigurationClass> classes;
private AutoConfiguration(String module, Set<String> 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<AutoConfigurationClass> {
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);
}
}
}

View File

@@ -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<String> 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;
}
}

View File

@@ -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";
}
}

View File

@@ -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.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Project> {
@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()

View File

@@ -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;

View File

@@ -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");
}
}

View File

@@ -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}.

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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<Project> {
/**
* 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);
});
});
}
}

View File

@@ -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<Project> {
/**
* 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;
}
}

View File

@@ -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<Project> {
private static final Spec<Task> 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"));
}
}

View File

@@ -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<Project> {
@Override
public void apply(Project project) {
Provider<TestResultsOverview> 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<TestDescriptor> failures = new ArrayList<>();
private final Provider<TestResultsOverview> testResultsOverview;
private final Test test;
private FailureRecordingTestListener(Provider<TestResultsOverview> 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) {
}
}
}

View File

@@ -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<BuildServiceParameters.None>, OperationCompletionListener, AutoCloseable {
private final Map<Test, List<TestFailure>> testFailures = new TreeMap<>(
(one, two) -> one.getPath().compareTo(two.getPath()));
private final Object monitor = new Object();
void addFailures(Test test, List<TestDescriptor> failureDescriptors) {
List<TestFailure> 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<TestFailure> {
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;
}
}
}

View File

@@ -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<JavaLanguageVersion> maximumCompatibleJavaVersion;
private final ListProperty<String> 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<JavaLanguageVersion> getMaximumCompatibleJavaVersion() {
return this.maximumCompatibleJavaVersion;
}
public ListProperty<String> getTestJvmArgs() {
return this.testJvmArgs;
}
JavaLanguageVersion getJavaVersion() {
return this.javaVersion;
}
}

View File

@@ -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<Project> {
@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<String> jvmArgs = new ArrayList<>();
jvmArgs.addAll(toolchain.getTestJvmArgs().getOrElse(Collections.emptyList()));
project.getTasks().withType(Test.class, (test) -> test.jvmArgs(jvmArgs));
}
}

View File

@@ -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<Project> {
@Override
public void apply(Project project) {
new AsciidoctorConventions().apply(project);
}
}

View File

@@ -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<Project> {
@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);
});
});
});
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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<Project> {
@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"));
}
}
}

View File

@@ -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<Project> {
@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());
}));
}
}

View File

@@ -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<Project> {
@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"));
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<String> 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());
}
}

View File

@@ -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<Project> {
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);

View File

@@ -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();
}
}

View File

@@ -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<String, Object> 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<String, Object> json, Analysis analysis) {
List<Map<String, Object>> groups = (List<Map<String, Object>>) json.get(key);
List<String> names = groups.stream().map((group) -> (String) group.get("name")).collect(Collectors.toList());
List<String> 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<String> sortedCopy(Collection<String> original) {
List<String> copy = new ArrayList<>(original);
Collections.sort(copy);
return copy;
}
private static final class Report implements Iterable<String> {
private final List<Analysis> 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<String> iterator() {
List<String> 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<String> problems = new ArrayList<>();
private final Path source;
private Analysis(Path source) {
this.source = source;
}
}
}

View File

@@ -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<String> 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("|");
}
}

View File

@@ -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<String, ConfigurationProperty> byName;
private ConfigurationProperties(List<ConfigurationProperty> properties) {
Map<String, ConfigurationProperty> 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<ConfigurationProperty> stream() {
return this.byName.values().stream();
}
@SuppressWarnings("unchecked")
static ConfigurationProperties fromFiles(Iterable<File> files) {
try {
ObjectMapper objectMapper = new ObjectMapper();
List<ConfigurationProperty> properties = new ArrayList<>();
for (File file : files) {
Map<String, Object> json = objectMapper.readValue(file, Map.class);
for (Map<String, Object> property : (List<Map<String, Object>>) json.get("properties")) {
properties.add(ConfigurationProperty.fromJsonProperties(property));
}
}
return new ConfigurationProperties(properties);
}
catch (IOException ex) {
throw new RuntimeException("Failed to load configuration metadata", ex);
}
}
}

View File

@@ -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:
*
* <ul>
* <li>Adding a dependency on the configuration properties annotation processor.
* <li>Configuring the additional metadata locations annotation processor compiler
* argument.
* <li>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.
* <li>Registering a {@link CheckAdditionalSpringConfigurationMetadata} task and
* configuring the {@code check} task to depend upon it.
* <li>Defining an artifact for the resulting configuration property metadata so that it
* can be consumed by downstream projects.
* </ul>
*
* @author Andy Wilkinson
* @author Chris Bono
*/
public class ConfigurationPropertiesPlugin implements Plugin<Project> {
// 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<CheckAdditionalSpringConfigurationMetadata> 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));
}
}

View File

@@ -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<String, Object> 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);
}
}

View File

@@ -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;

View File

@@ -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<Row> {
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);
}

View File

@@ -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("<", "&lt;").replace(">", "&gt;");
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, "+`");
}
}
}

View File

@@ -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<String> prefixes;
private final Map<String, String> overrides;
Snippet(String anchor, String title, Consumer<Config> config) {
Set<String> prefixes = new LinkedHashSet<>();
Map<String, String> 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<String> action) {
this.prefixes.forEach(action);
}
void forEachOverride(BiConsumer<String, String> 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);
}
}

View File

@@ -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<Snippet> snippets = new ArrayList<>();
Snippets(FileCollection configurationPropertyMetadata) {
this.properties = ConfigurationProperties.fromFiles(configurationPropertyMetadata);
}
void add(String anchor, String title, Consumer<Snippet.Config> config) {
this.snippets.add(new Snippet(anchor, title, config));
}
void writeTo(Path outputDirectory) throws IOException {
createDirectory(outputDirectory);
Set<String> remaining = this.properties.stream().filter((property) -> !property.isDeprecated())
.map(ConfigurationProperty::getName).collect(Collectors.toSet());
for (Snippet snippet : this.snippets) {
Set<String> 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<String> writeSnippet(Path outputDirectory, Snippet snippet, Set<String> remaining) throws IOException {
Table table = new Table();
Set<String> 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");
}
}
}

View File

@@ -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<Row> 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("|===");
}
}

View File

@@ -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:
*
* <ul>
* <li>If the {@code deploymentRepository} property has been set, a Maven artifact repository
* is configured to publish to it
* it.
* <li>The poms of all {@link MavenPublication Maven publications} are customized to meet
* Maven Central's requirements.
* <li>If the {@link JavaPlugin Java plugin} has also been applied:
* <ul>
* <li>Creation of Javadoc and source jars is enabled.
* <li>Publication metadata (poms and Gradle module metadata) is configured to use
* resolved versions.
* </ul>
* </ul>
*
* @author Andy Wilkinson
* @author Christoph Dreis
* @author Mike Smithson
* @author Chris Bono
*/
class MavenPublishingConventionsPlugin implements Plugin<Project> {
@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());
}
}

View File

@@ -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<Project> {
@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"));
});
});
});
}
}

View File

@@ -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<Project> {
@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");
}
});
}
}

View File

@@ -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<Project> {
@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"));
});
});
}
}

View File

@@ -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<Project> {
@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));
}
}

View File

@@ -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<Project> {
@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);
}
}

View File

@@ -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<Project> {
@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<Boolean>) () -> 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);
});
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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'

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -7,7 +7,7 @@ For a quick but less detailed introduction, see <<index.adoc#quick-intro>>.
[[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.

View File

@@ -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.

View File

@@ -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 {
}

View File

@@ -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)
}

View File

@@ -40,8 +40,8 @@ public class SpringPulsarBootApp {
}
/*
* Basic publisher using PulsarTemplate<String> and a PulsarListener using
* an exclusive subscription to consume.
* Basic publisher using PulsarTemplate<String> and a PulsarListener using an
* exclusive subscription to consume.
*/
@Bean
public ApplicationRunner runner1(PulsarTemplate<String> pulsarTemplate) {
@@ -61,8 +61,8 @@ public class SpringPulsarBootApp {
}
/*
* Basic publisher using PulsarTemplate<Integer> and a PulsarListener using
* an exclusive subscription to consume.
* Basic publisher using PulsarTemplate<Integer> and a PulsarListener using an
* exclusive subscription to consume.
*/
@Bean
public ApplicationRunner runner2(PulsarTemplate<Integer> 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<Foo> 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<Foo> 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 + '\'' + '}';
}
}

View File

@@ -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;
}
}
}

View File

@@ -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'
}

View File

@@ -93,4 +93,5 @@ public class PulsarAutoConfiguration {
public PulsarConsumerFactory<?> pulsarConsumerFactory(PulsarClient pulsarClient) {
return new DefaultPulsarConsumerFactory<>(pulsarClient, this.properties.buildConsumerProperties());
}
}

View File

@@ -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 {
<V> java.util.function.Consumer<V> in(String key) {
return (value) -> put(key, value);
}
}
}

View File

@@ -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<String> 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<String> 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<String> 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<DefaultPulsarMessageListenerContainer<String>> listenerContainerFactory = mock(PulsarListenerContainerFactory.class);
PulsarListenerContainerFactory<DefaultPulsarMessageListenerContainer<String>> 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<String, String> listenerAnnotationBeanPostProcessor = mock(PulsarListenerAnnotationBeanPostProcessor.class);
PulsarListenerAnnotationBeanPostProcessor<String, String> 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);
}
}
}

View File

@@ -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<String> 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<String> 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<String> foo) {
foo.forEach(t -> latch2.countDown());
}
}
}

View File

@@ -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'
}

View File

@@ -33,4 +33,5 @@ public class PulsarException extends NestedRuntimeException {
public PulsarException(String msg, Throwable cause) {
super(msg, cause);
}
}

View File

@@ -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 {
}

View File

@@ -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}.
*
* <p>This configuration class is automatically imported when using the @{@link EnablePulsar}
* <p>
* This configuration class is automatically imported when using the @{@link EnablePulsar}
* annotation.
*
* @author Soby Chacko
* @author Chris Bono
*
* @see PulsarListenerAnnotationBeanPostProcessor
* @see PulsarListenerEndpointRegistry
* @see EnablePulsar

View File

@@ -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 <em>default</em> 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 <em>default</em> container factory
* is assumed to be available with a bean name of {@code pulsarListenerContainerFactory}
* unless an explicit default has been provided through configuration.
*
* <p>
* 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.
* <p>If none is specified an auto-generated id is used.
* <p>SpEL {@code #{...}} and property place holders {@code ${...}} are supported.
* <p>
* If none is specified an auto-generated id is used.
* <p>
* 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.
* <p>
* 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.
* <p>SpEL {@code #{...}} and property place holders {@code ${...}} are supported.
* <p>
* 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'.
* <p>
* @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).
* <p>
* <b>Supported Syntax</b>
* <p>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}:
* <p>
* 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}:
* <ul>
* <li>{@code key=value}</li>
* <li>{@code key:value}</li>
* <li>{@code key value}</li>
* </ul>
* {@code group.id} and {@code client.id} are ignored.
* <p>SpEL {@code #{...}} and property place holders {@code ${...}} are supported.
* SpEL expressions must resolve to a {@link String}, a @{link String[]} or a
* <p>
* SpEL {@code #{...}} and property place holders {@code ${...}} are supported. SpEL
* expressions must resolve to a {@link String}, a @{link String[]} or a
* {@code Collection<String>} where each member of the array or collection is a
* property name + value with the above formats.
* @return the properties.
*/
String[] properties() default {};
}

View File

@@ -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.
*
* <p>Annotated methods can use flexible arguments as defined by {@link PulsarListener}.
* <p>
* Annotated methods can use flexible arguments as defined by {@link PulsarListener}.
*
* <p>This post-processor is automatically registered by the {@link EnablePulsar}
* annotation.
* <p>
* This post-processor is automatically registered by the {@link EnablePulsar} annotation.
*
* <p>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.
* <p>
* 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 <K> the key type.
* @param <V> 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<K, V> implements BeanPostProcessor, Ordered, ApplicationContextAware, InitializingBean, SmartInitializingSingleton {
public class PulsarListenerAnnotationBeanPostProcessor<K, V>
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<K, V> 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<K, V> implements BeanPost
private void buildEnhancer() {
if (this.applicationContext != null) {
List<AnnotationEnhancer> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> implements BeanPost
}
}
else {
throw new IllegalStateException("'properties' must resolve to a String, a String[] or "
+ "Collection<String>");
throw new IllegalStateException(
"'properties' must resolve to a String, a String[] or " + "Collection<String>");
}
}
endpoint.setConsumerProperties(properties);
@@ -452,8 +448,8 @@ public class PulsarListenerAnnotationBeanPostProcessor<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<K, V> 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<HandlerMethodArgumentResolver> customArgumentsResolver =
new ArrayList<>(PulsarListenerAnnotationBeanPostProcessor.this.registrar.getCustomMethodArgumentResolvers());
List<HandlerMethodArgumentResolver> 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<K, V> implements BeanPost
private static class BytesToStringConverter implements Converter<byte[], String> {
private final Charset charset;
BytesToStringConverter(Charset charset) {
@@ -770,10 +764,8 @@ public class PulsarListenerAnnotationBeanPostProcessor<K, V> implements BeanPost
}
public interface AnnotationEnhancer extends BiFunction<Map<String, Object>, AnnotatedElement, Map<String, Object>> {
}
}

View File

@@ -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 <em>programmatic</em> fashion as
* opposed to the <em>declarative</em> 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
* <em>programmatic</em> fashion as opposed to the <em>declarative</em> 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);
}

View File

@@ -25,10 +25,10 @@ import java.lang.annotation.Target;
/**
* Container annotation that aggregates several {@link PulsarListener} annotations.
* <p>
* 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

View File

@@ -37,14 +37,14 @@ import org.springframework.pulsar.support.MessageConverter;
*
* @param <C> the {@link AbstractPulsarMessageListenerContainer} implementation type.
* @param <T> Message payload type.
*
* @author Soby Chacko
*/
public abstract class AbstractPulsarListenerContainerFactory<C extends AbstractPulsarMessageListenerContainer<T>, T>
implements PulsarListenerContainerFactory<C>, 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<C extends AbstractP
this.applicationContext = applicationContext;
}
public void setPulsarConsumerFactory(PulsarConsumerFactory<? super T> consumerFactory) {
this.consumerFactory = consumerFactory;
}
@@ -76,12 +75,10 @@ public abstract class AbstractPulsarListenerContainerFactory<C extends AbstractP
return this.consumerFactory;
}
public void setAutoStartup(Boolean autoStartup) {
this.autoStartup = autoStartup;
}
public void setPhase(int phase) {
this.phase = phase;
}
@@ -94,12 +91,10 @@ public abstract class AbstractPulsarListenerContainerFactory<C extends AbstractP
this.messageConverter = messageConverter;
}
public Boolean isBatchListener() {
return this.batchListener;
}
public void setBatchListener(Boolean batchListener) {
this.batchListener = batchListener;
}
@@ -109,7 +104,6 @@ public abstract class AbstractPulsarListenerContainerFactory<C extends AbstractP
this.applicationEventPublisher = applicationEventPublisher;
}
public PulsarContainerProperties getContainerProperties() {
return this.containerProperties;
}
@@ -123,15 +117,14 @@ public abstract class AbstractPulsarListenerContainerFactory<C extends AbstractP
@Override
public C createListenerContainer(PulsarListenerEndpoint endpoint) {
C instance = createContainerInstance(endpoint);
JavaUtils.INSTANCE
.acceptIfNotNull(endpoint.getSubscriptionName(), instance::setBeanName);
JavaUtils.INSTANCE.acceptIfNotNull(endpoint.getSubscriptionName(), instance::setBeanName);
if (endpoint instanceof AbstractPulsarListenerEndpoint) {
configureEndpoint((AbstractPulsarListenerEndpoint<C>) endpoint);
}
endpoint.setupListenerContainer(instance, this.messageConverter);
initializeContainer(instance, endpoint);
//customizeContainer(instance);
// customizeContainer(instance);
return instance;
}
@@ -140,20 +133,19 @@ public abstract class AbstractPulsarListenerContainerFactory<C extends AbstractP
private void configureEndpoint(AbstractPulsarListenerEndpoint<C> 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<C extends AbstractP
instance.setAutoStartup(this.autoStartup);
}
JavaUtils.INSTANCE
.acceptIfNotNull(this.phase, instance::setPhase)
JavaUtils.INSTANCE.acceptIfNotNull(this.phase, instance::setPhase)
.acceptIfNotNull(this.applicationContext, instance::setApplicationContext)
.acceptIfNotNull(this.applicationEventPublisher, instance::setApplicationEventPublisher);
}

View File

@@ -46,10 +46,10 @@ import org.springframework.util.Assert;
* Base implementation for {@link PulsarListenerEndpoint}.
*
* @param <K> Message payload type.
*
* @author Soby Chacko
*/
public abstract class AbstractPulsarListenerEndpoint<K> implements PulsarListenerEndpoint, BeanFactoryAware, InitializingBean {
public abstract class AbstractPulsarListenerEndpoint<K>
implements PulsarListenerEndpoint, BeanFactoryAware, InitializingBean {
private final LogAccessor logger = new LogAccessor(LogFactory.getLog(getClass()));
@@ -72,7 +72,9 @@ public abstract class AbstractPulsarListenerEndpoint<K> implements PulsarListene
private BeanResolver beanResolver;
private Boolean autoStartup;
private Properties consumerProperties;
private Boolean batchListener;
@Override
@@ -156,25 +158,24 @@ public abstract class AbstractPulsarListenerEndpoint<K> 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<K> 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<K> createMessageListener(PulsarMessageListenerContainer container,
@Nullable MessageConverter messageConverter);
protected abstract PulsarMessagingMessageListenerAdapter<K> createMessageListener(
PulsarMessageListenerContainer container, @Nullable MessageConverter messageConverter);
public void setConsumerProperties(Properties consumerProperties) {
this.consumerProperties = consumerProperties;
@@ -185,7 +186,6 @@ public abstract class AbstractPulsarListenerEndpoint<K> implements PulsarListene
return this.batchListener;
}
public void setBatchListener(boolean batchListener) {
this.batchListener = batchListener;
}
@@ -194,7 +194,6 @@ public abstract class AbstractPulsarListenerEndpoint<K> implements PulsarListene
return this.batchListener == null ? false : this.batchListener;
}
public SubscriptionType getSubscriptionType() {
return this.subscriptionType;
}
@@ -210,4 +209,5 @@ public abstract class AbstractPulsarListenerEndpoint<K> implements PulsarListene
public void setSchemaType(SchemaType schemaType) {
this.schemaType = schemaType;
}
}

View File

@@ -30,11 +30,11 @@ import org.springframework.util.StringUtils;
*
* @param <C> container implementation type.
* @param <T> message type in the listener.
*
* @author Soby Chacko
* @author Chris Bono
*/
public class DefaultPulsarListenerContainerFactory<C, T> extends AbstractPulsarListenerContainerFactory<DefaultPulsarMessageListenerContainer<T>, T> {
public class DefaultPulsarListenerContainerFactory<C, T>
extends AbstractPulsarListenerContainerFactory<DefaultPulsarMessageListenerContainer<T>, T> {
@Override
protected DefaultPulsarMessageListenerContainer<T> createContainerInstance(PulsarListenerEndpoint endpoint) {
@@ -67,7 +67,7 @@ public class DefaultPulsarListenerContainerFactory<C, T> extends AbstractPulsarL
@Override
protected void initializeContainer(DefaultPulsarMessageListenerContainer<T> instance,
PulsarListenerEndpoint endpoint) {
PulsarListenerEndpoint endpoint) {
super.initializeContainer(instance, endpoint);
}
@@ -84,7 +84,8 @@ public class DefaultPulsarListenerContainerFactory<C, T> extends AbstractPulsarL
};
DefaultPulsarMessageListenerContainer<T> container = createContainerInstance(endpoint);
initializeContainer(container, endpoint);
//customizeContainer(container);
// customizeContainer(container);
return container;
}
}

View File

@@ -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 <V> Message payload type
*
* @author Soby Chacko
*/
public class MethodPulsarListenerEndpoint<V> extends AbstractPulsarListenerEndpoint<V> {
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<V> 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<V> extends AbstractPulsarListenerEndpo
@Override
protected PulsarMessagingMessageListenerAdapter<V> createMessageListener(PulsarMessageListenerContainer container,
@Nullable MessageConverter messageConverter) {
@Nullable MessageConverter messageConverter) {
Assert.state(this.messageHandlerMethodFactory != null,
"Could not create message listener - MessageHandlerMethodFactory not set");
PulsarMessagingMessageListenerAdapter<V> 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<MethodParameter> 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<MethodParameter> 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<V> 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<V> 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<V> 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<V> createMessageListenerInstance(
@Nullable MessageConverter messageConverter) {

View File

@@ -43,4 +43,5 @@ public class PulsarClientConfiguration {
public Map<String, Object> getConfigs() {
return this.configs;
}
}

View File

@@ -46,9 +46,7 @@ public class PulsarClientFactoryBean extends AbstractFactoryBean<PulsarClient> {
@Override
protected PulsarClient createInstance() throws Exception {
return PulsarClient.builder()
.loadConf(this.pulsarClientConfiguration.getConfigs())
.build();
return PulsarClient.builder().loadConf(this.pulsarClientConfiguration.getConfigs()).build();
}
@Override

View File

@@ -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";
}

Some files were not shown because too many files have changed in this diff Show More