From 5b293d21161e946bf241d9e974b9af93cfafaaac Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Thu, 14 Sep 2023 16:01:50 -0500 Subject: [PATCH] Automate spring-security.xsd Closes gh-13819 --- .../gradle/convention/SchemaZipPlugin.groovy | 21 +-- .../gradle/xsd/CreateVersionlessXsdTask.java | 147 ++++++++++++++++++ .../xsd/CreateVersionlessXsdTaskTests.java | 94 +++++++++++ config/spring-security-config.gradle | 11 ++ .../security/config/spring-security.xsd | 1 - 5 files changed, 263 insertions(+), 11 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/gradle/xsd/CreateVersionlessXsdTask.java create mode 100644 buildSrc/src/test/java/org/springframework/gradle/xsd/CreateVersionlessXsdTaskTests.java delete mode 120000 config/src/main/resources/org/springframework/security/config/spring-security.xsd diff --git a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy index caafb07ab7..3fccb2aef8 100644 --- a/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy +++ b/buildSrc/src/main/groovy/io/spring/gradle/convention/SchemaZipPlugin.groovy @@ -4,6 +4,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.JavaPlugin import org.gradle.api.tasks.bundling.Zip +import org.springframework.gradle.xsd.CreateVersionlessXsdTask public class SchemaZipPlugin implements Plugin { @@ -15,7 +16,10 @@ public class SchemaZipPlugin implements Plugin { schemaZip.archiveClassifier = 'schema' schemaZip.description = "Builds -${schemaZip.archiveClassifier} archive containing all " + "XSDs for deployment at static.springframework.org/schema." - + def versionlessXsd = project.tasks.create("versionlessXsd", CreateVersionlessXsdTask) { + description = "Generates spring-security.xsd" + versionlessXsdFile = project.layout.buildDirectory.file("versionlessXsd/spring-security.xsd") + } project.rootProject.subprojects.each { module -> module.getPlugins().withType(JavaPlugin.class).all { @@ -36,17 +40,14 @@ public class SchemaZipPlugin implements Plugin { duplicatesStrategy 'exclude' from xsdFile.path } - } - File symlink = module.sourceSets.main.resources.find { - it.path.endsWith('org/springframework/security/config/spring-security.xsd') - } - if (symlink != null) { - schemaZip.into('security') { - duplicatesStrategy 'exclude' - from symlink.path - } + versionlessXsd.getInputFiles().from(xsdFile.path) } } } + + schemaZip.into("security") { + from(versionlessXsd.getOutputs()) + } + } } diff --git a/buildSrc/src/main/java/org/springframework/gradle/xsd/CreateVersionlessXsdTask.java b/buildSrc/src/main/java/org/springframework/gradle/xsd/CreateVersionlessXsdTask.java new file mode 100644 index 0000000000..02f149831d --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/gradle/xsd/CreateVersionlessXsdTask.java @@ -0,0 +1,147 @@ +/* + * Copyright 2019-2023 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.gradle.xsd; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.RegularFileProperty; +import org.gradle.api.tasks.*; +import org.gradle.work.DisableCachingByDefault; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Creates the spring-security.xsd automatically + * + * @author Rob Winch + */ +@DisableCachingByDefault(because = "not worth it") +public abstract class CreateVersionlessXsdTask extends DefaultTask { + + @InputFiles + public abstract ConfigurableFileCollection getInputFiles(); + + @OutputFile + abstract RegularFileProperty getVersionlessXsdFile(); + + @TaskAction + void createVersionlessXsd() throws IOException { + XsdFileMajorMinorVersion largest = null; + ConfigurableFileCollection inputFiles = getInputFiles(); + if (inputFiles.isEmpty()) { + throw new IllegalStateException("No Inputs configured"); + } + for (File file : inputFiles) { + XsdFileMajorMinorVersion current = XsdFileMajorMinorVersion.create(file); + if (current == null) { + continue; + } + if (largest == null) { + largest = current; + } + else if (current.getVersion().isGreaterThan(largest.getVersion())) { + largest = current; + } + } + if (largest == null) { + throw new IllegalStateException("Could not create versionless xsd file because no files matching spring-security-.xsd were found in " + inputFiles.getFiles()); + } + Path to = getVersionlessXsdFile().getAsFile().get().toPath(); + Path from = largest.getFile().toPath(); + Files.copy(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + } + + static class XsdFileMajorMinorVersion { + private final File file; + + private final MajorMinorVersion version; + + private XsdFileMajorMinorVersion(File file, MajorMinorVersion version) { + this.file = file; + this.version = version; + } + + private static final Pattern FILE_MAJOR_MINOR_VERSION_PATTERN = Pattern.compile("^spring-security-(\\d+)\\.(\\d+)\\.xsd$"); + + /** + * If matches xsd with major minor version (e.g. spring-security-5.1.xsd returns it, otherwise null + * @param file + * @return + */ + static XsdFileMajorMinorVersion create(File file) { + String fileName = file.getName(); + Matcher matcher = FILE_MAJOR_MINOR_VERSION_PATTERN.matcher(fileName); + if (!matcher.find()) { + return null; + } + int major = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + MajorMinorVersion version = new MajorMinorVersion(major, minor); + return new XsdFileMajorMinorVersion(file, version); + } + + public File getFile() { + return file; + } + + public MajorMinorVersion getVersion() { + return version; + } + } + + static class MajorMinorVersion { + private final int major; + + private final int minor; + + MajorMinorVersion(int major, int minor) { + this.major = major; + this.minor = minor; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public boolean isGreaterThan(MajorMinorVersion version) { + if (getMajor() > version.getMajor()) { + return true; + } + if (getMajor() < version.getMajor()) { + return false; + } + if (getMinor() > version.getMinor()) { + return true; + } + if (getMinor() < version.getMinor()) { + return false; + } + // they are equal + return false; + } + } +} diff --git a/buildSrc/src/test/java/org/springframework/gradle/xsd/CreateVersionlessXsdTaskTests.java b/buildSrc/src/test/java/org/springframework/gradle/xsd/CreateVersionlessXsdTaskTests.java new file mode 100644 index 0000000000..bd907064b5 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/gradle/xsd/CreateVersionlessXsdTaskTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019-2023 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.gradle.xsd; + +import org.junit.jupiter.api.Test; +import org.springframework.gradle.xsd.CreateVersionlessXsdTask.MajorMinorVersion; +import org.springframework.gradle.xsd.CreateVersionlessXsdTask.XsdFileMajorMinorVersion; + +import java.io.File; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +/** + * @author Rob Winch + */ +class CreateVersionlessXsdTaskTests { + + @Test + void xsdCreateWhenValid() { + File file = new File("spring-security-2.0.xsd"); + XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file); + assertThat(xsdFile).isNotNull(); + assertThat(xsdFile.getFile()).isEqualTo(file); + assertThat(xsdFile.getVersion().getMajor()).isEqualTo(2); + assertThat(xsdFile.getVersion().getMinor()).isEqualTo(0); + } + + @Test + void xsdCreateWhenPatchReleaseThenNull() { + File file = new File("spring-security-2.0.1.xsd"); + XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file); + assertThat(xsdFile).isNull(); + } + + @Test + void xsdCreateWhenNotXsdFileThenNull() { + File file = new File("spring-security-2.0.txt"); + XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file); + assertThat(xsdFile).isNull(); + } + + @Test + void xsdCreateWhenNotStartWithSpringSecurityThenNull() { + File file = new File("spring-securityNO-2.0.xsd"); + XsdFileMajorMinorVersion xsdFile = XsdFileMajorMinorVersion.create(file); + assertThat(xsdFile).isNull(); + } + + @Test + void isGreaterWhenMajorLarger() { + MajorMinorVersion larger = new MajorMinorVersion(2,0); + MajorMinorVersion smaller = new MajorMinorVersion(1,0); + assertThat(larger.isGreaterThan(smaller)).isTrue(); + assertThat(smaller.isGreaterThan(larger)).isFalse(); + } + + @Test + void isGreaterWhenMinorLarger() { + MajorMinorVersion larger = new MajorMinorVersion(1,1); + MajorMinorVersion smaller = new MajorMinorVersion(1,0); + assertThat(larger.isGreaterThan(smaller)).isTrue(); + assertThat(smaller.isGreaterThan(larger)).isFalse(); + } + + @Test + void isGreaterWhenMajorAndMinorLarger() { + MajorMinorVersion larger = new MajorMinorVersion(2,1); + MajorMinorVersion smaller = new MajorMinorVersion(1,0); + assertThat(larger.isGreaterThan(smaller)).isTrue(); + assertThat(smaller.isGreaterThan(larger)).isFalse(); + } + + @Test + void isGreaterWhenSame() { + MajorMinorVersion first = new MajorMinorVersion(1,0); + MajorMinorVersion second = new MajorMinorVersion(1,0); + assertThat(first.isGreaterThan(second)).isFalse(); + assertThat(second.isGreaterThan(first)).isFalse(); + } +} diff --git a/config/spring-security-config.gradle b/config/spring-security-config.gradle index 2ce079d305..e2ea7a6527 100644 --- a/config/spring-security-config.gradle +++ b/config/spring-security-config.gradle @@ -1,4 +1,5 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.springframework.gradle.xsd.CreateVersionlessXsdTask apply plugin: 'io.spring.convention.spring-module' apply plugin: 'trang' @@ -113,6 +114,16 @@ dependencies { testRuntimeOnly 'org.hsqldb:hsqldb' } +def versionlessXsd = project.tasks.create("versionlessXsd", CreateVersionlessXsdTask) { + inputFiles.from(project.sourceSets.main.resources) + versionlessXsdFile = project.layout.buildDirectory.file("versionlessXsd/spring-security.xsd") +} + +processResources { + from(versionlessXsd) { + into 'org/springframework/security/config/' + } +} rncToXsd { rncDir = file('src/main/resources/org/springframework/security/config/') diff --git a/config/src/main/resources/org/springframework/security/config/spring-security.xsd b/config/src/main/resources/org/springframework/security/config/spring-security.xsd deleted file mode 120000 index 976a03ccd6..0000000000 --- a/config/src/main/resources/org/springframework/security/config/spring-security.xsd +++ /dev/null @@ -1 +0,0 @@ -spring-security-5.7.xsd \ No newline at end of file