diff --git a/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocument.java b/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocument.java index 66c888d627..3c5716ce11 100644 --- a/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocument.java +++ b/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocument.java @@ -48,6 +48,8 @@ public class ProjectRequestDocument { private String language; + private String configurationFileFormat; + private String packaging; private String packageName; @@ -116,6 +118,14 @@ public void setLanguage(String language) { this.language = language; } + public String getConfigurationFileFormat() { + return this.configurationFileFormat; + } + + public void setConfigurationFileFormat(String configurationFileFormat) { + this.configurationFileFormat = configurationFileFormat; + } + public String getPackaging() { return this.packaging; } @@ -177,6 +187,7 @@ public String toString() { .add("artifactId='" + this.artifactId + "'") .add("javaVersion='" + this.javaVersion + "'") .add("language='" + this.language + "'") + .add("configurationFileFormat='" + this.configurationFileFormat + "'") .add("packaging='" + this.packaging + "'") .add("packageName='" + this.packageName + "'") .add("version=" + this.version) @@ -331,6 +342,8 @@ public static class ErrorStateInformation { private Boolean language; + private Boolean configurationFileFormat; + private Boolean packaging; private Boolean type; @@ -359,6 +372,14 @@ public void setLanguage(Boolean language) { this.language = language; } + public Boolean getConfigurationFileFormat() { + return this.configurationFileFormat; + } + + public void setConfigurationFileFormat(Boolean configurationFileFormat) { + this.configurationFileFormat = configurationFileFormat; + } + public Boolean getPackaging() { return this.packaging; } @@ -396,6 +417,7 @@ public String toString() { return new StringJoiner(", ", "{", "}").add("invalid=" + this.invalid) .add("javaVersion=" + this.javaVersion) .add("language=" + this.language) + .add("configurationFileFormat=" + this.configurationFileFormat) .add("packaging=" + this.packaging) .add("type=" + this.type) .add("dependencies=" + this.dependencies) diff --git a/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocumentFactory.java b/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocumentFactory.java index aa5708ec64..d8d5004d6c 100644 --- a/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocumentFactory.java +++ b/initializr-actuator/src/main/java/io/spring/initializr/actuate/stat/ProjectRequestDocumentFactory.java @@ -68,6 +68,12 @@ public ProjectRequestDocument createDocument(ProjectRequestEvent event) { document.triggerError().setLanguage(true); } + document.setConfigurationFileFormat(request.getConfigurationFileFormat()); + if (StringUtils.hasText(request.getConfigurationFileFormat()) + && metadata.getConfigurationFileFormats().get(request.getConfigurationFileFormat()) == null) { + document.triggerError().setConfigurationFileFormat(true); + } + document.setPackaging(request.getPackaging()); if (StringUtils.hasText(request.getPackaging()) && metadata.getPackagings().get(request.getPackaging()) == null) { diff --git a/initializr-actuator/src/test/resources/stat/request-invalid-dependencies.json b/initializr-actuator/src/test/resources/stat/request-invalid-dependencies.json index ae1b5e8c0c..85406c68ee 100644 --- a/initializr-actuator/src/test/resources/stat/request-invalid-dependencies.json +++ b/initializr-actuator/src/test/resources/stat/request-invalid-dependencies.json @@ -6,6 +6,7 @@ "artifactId": "test", "javaVersion": "1.8", "language": "java", + "configurationFileFormat": "properties", "packaging": "jar", "packageName": "com.example.acme.test", "version": { diff --git a/initializr-actuator/src/test/resources/stat/request-invalid-java-version.json b/initializr-actuator/src/test/resources/stat/request-invalid-java-version.json index 8f3174b399..5362645657 100644 --- a/initializr-actuator/src/test/resources/stat/request-invalid-java-version.json +++ b/initializr-actuator/src/test/resources/stat/request-invalid-java-version.json @@ -6,6 +6,7 @@ "artifactId": "test", "javaVersion": "1.2", "language": "java", + "configurationFileFormat": "properties", "packaging": "jar", "packageName": "com.example.acme.test", "version": { diff --git a/initializr-actuator/src/test/resources/stat/request-invalid-language.json b/initializr-actuator/src/test/resources/stat/request-invalid-language.json index 8e4708342a..cd2e1ef9d1 100644 --- a/initializr-actuator/src/test/resources/stat/request-invalid-language.json +++ b/initializr-actuator/src/test/resources/stat/request-invalid-language.json @@ -6,6 +6,7 @@ "artifactId": "test", "javaVersion": "1.8", "language": "c", + "configurationFileFormat": "properties", "packaging": "jar", "packageName": "com.example.acme.test", "version": { diff --git a/initializr-actuator/src/test/resources/stat/request-invalid-type.json b/initializr-actuator/src/test/resources/stat/request-invalid-type.json index 7bc14e90ab..34091c09c2 100644 --- a/initializr-actuator/src/test/resources/stat/request-invalid-type.json +++ b/initializr-actuator/src/test/resources/stat/request-invalid-type.json @@ -5,6 +5,7 @@ "artifactId": "test", "javaVersion": "1.8", "language": "java", + "configurationFileFormat": "properties", "packaging": "jar", "packageName": "com.example.acme.test", "version": { diff --git a/initializr-actuator/src/test/resources/stat/request-no-client.json b/initializr-actuator/src/test/resources/stat/request-no-client.json index 4a59c19abb..7d80b2d95b 100644 --- a/initializr-actuator/src/test/resources/stat/request-no-client.json +++ b/initializr-actuator/src/test/resources/stat/request-no-client.json @@ -6,6 +6,7 @@ "artifactId": "test", "javaVersion": "1.8", "language": "java", + "configurationFileFormat": "properties", "packaging": "jar", "packageName": "com.example.acme.test", "version": { diff --git a/initializr-actuator/src/test/resources/stat/request-simple.json b/initializr-actuator/src/test/resources/stat/request-simple.json index 2a0bd50521..31b3ab39a3 100644 --- a/initializr-actuator/src/test/resources/stat/request-simple.json +++ b/initializr-actuator/src/test/resources/stat/request-simple.json @@ -6,6 +6,7 @@ "artifactId": "project", "javaVersion": "1.8", "language": "java", + "configurationFileFormat": "properties", "packaging": "jar", "packageName": "com.example.acme.project", "version": { diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationProperties.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationProperties.java index 4ceb038126..d2bc930ae4 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationProperties.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationProperties.java @@ -29,6 +29,8 @@ */ public class ApplicationProperties { + private static final String YAML_SPACE = " "; + private final Map properties = new HashMap<>(); /** @@ -73,6 +75,52 @@ void writeTo(PrintWriter writer) { } } + void writeYaml(PrintWriter writer) { + Map nested = flattenToNestedMap(this.properties); + writeYamlRecursive(nested, writer, 0); + } + + private static Map flattenToNestedMap(Map flatMap) { + Map nested = new HashMap<>(); + flatMap.forEach((key, value) -> { + String[] path = parseKeyPath(key); + insertValueAtPath(nested, path, value); + }); + return nested; + } + + private static String[] parseKeyPath(String key) { + return key.split("\\."); + } + + @SuppressWarnings("unchecked") + private static void insertValueAtPath(Map map, String[] path, Object value) { + Map current = map; + for (int i = 0; i < path.length - 1; i++) { + String segment = path[i]; + current = (Map) current.computeIfAbsent(segment, (k) -> new HashMap<>()); + } + current.put(path[path.length - 1], value); + } + + private static void writeYamlRecursive(Map map, PrintWriter writer, int indent) { + map.entrySet().forEach((entry) -> writeEntry(entry, writer, indent)); + } + + @SuppressWarnings("unchecked") + private static void writeEntry(Map.Entry entry, PrintWriter writer, int indent) { + String indentStr = YAML_SPACE.repeat(indent); + Object value = entry.getValue(); + + if (value instanceof Map nestedMap) { + writer.printf("%s%s:%n", indentStr, entry.getKey()); + writeYamlRecursive((Map) nestedMap, writer, indent + 1); + } + else { + writer.printf("%s%s: %s%n", indentStr, entry.getKey(), value); + } + } + private void add(String key, Object value) { Assert.state(!this.properties.containsKey(key), () -> "Property '%s' already exists".formatted(key)); this.properties.put(key, value); diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesProjectGenerationConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesProjectGenerationConfiguration.java index 2a3cb7b71e..86cbf83922 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesProjectGenerationConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesProjectGenerationConfiguration.java @@ -16,6 +16,9 @@ package io.spring.initializr.generator.spring.properties; +import io.spring.initializr.generator.condition.ConditionalOnConfigurationFileFormat; +import io.spring.initializr.generator.configuration.format.properties.PropertiesFormat; +import io.spring.initializr.generator.configuration.format.yaml.YamlFormat; import io.spring.initializr.generator.project.ProjectGenerationConfiguration; import org.springframework.beans.factory.ObjectProvider; @@ -37,8 +40,16 @@ ApplicationProperties applicationProperties(ObjectProvider parameters() { + return Stream.of( + Arguments.arguments(ConfigurationFileFormat.forId(PropertiesFormat.ID), "application.properties"), + Arguments.arguments(ConfigurationFileFormat.forId(YamlFormat.ID), "application.yml")); + } + + @ParameterizedTest + @MethodSource("parameters") + void applicationPropertiesGenerated(ConfigurationFileFormat format, String fileName) { + ProjectStructure project = generateProject(java, maven, "2.4.1", + (description) -> description.setConfigurationFileFormat(format)); + assertThat(project).filePaths().contains(String.format("src/main/resources/%s", fileName)); + } + + @ParameterizedTest + @MethodSource("parameters") + void applicationPropertiesWithCustomProperties(ConfigurationFileFormat format, String fileName) { + ProjectStructure project = generateProject(java, maven, "2.4.1", + (description) -> description.setConfigurationFileFormat(format), + (projectGenerationContext) -> projectGenerationContext.registerBean( + ApplicationPropertiesCustomizer.class, + () -> (properties) -> properties.add("spring.application.name", "app-name"))); + String path = "project/properties/" + format + "/" + getAssertFileName(fileName); + assertThat(project).textFile(String.format("src/main/resources/%s", fileName)) + .as("Resource " + path) + .hasSameContentAs(new ClassPathResource(path)); + } + + private String getAssertFileName(String fileName) { + return fileName + ".gen"; + } + +} diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesTests.java index 573b17b7df..7572222e51 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/properties/ApplicationPropertiesTests.java @@ -71,6 +71,20 @@ void shouldFailOnExistingProperty() { .withMessage("Property 'test' already exists"); } + @Test + void writeYaml() { + ApplicationProperties properties = new ApplicationProperties(); + properties.add("name", "testapp"); + properties.add("port", 8080); + properties.add("app.version", "1.0"); + properties.add("db.host", "localhost"); + properties.add("app.config.debug", true); + properties.add("db.connection.timeout", 30); + String written = writeYaml(properties); + assertThat(written).contains("name: testapp", "port: 8080", "app:\n config:\n debug: true\n version: 1.0", + "db:\n host: localhost\n connection:\n timeout: 30"); + } + private String write(ApplicationProperties properties) { StringWriter stringWriter = new StringWriter(); try (PrintWriter writer = new PrintWriter(stringWriter)) { @@ -79,4 +93,12 @@ private String write(ApplicationProperties properties) { return stringWriter.toString(); } + private String writeYaml(ApplicationProperties properties) { + StringWriter stringWriter = new StringWriter(); + try (PrintWriter writer = new PrintWriter(stringWriter)) { + properties.writeYaml(writer); + } + return stringWriter.toString(); + } + } diff --git a/initializr-generator-spring/src/test/resources/project/properties/properties/application.properties.gen b/initializr-generator-spring/src/test/resources/project/properties/properties/application.properties.gen new file mode 100644 index 0000000000..16df405c4f --- /dev/null +++ b/initializr-generator-spring/src/test/resources/project/properties/properties/application.properties.gen @@ -0,0 +1 @@ +spring.application.name=app-name diff --git a/initializr-generator-spring/src/test/resources/project/properties/yaml/application.yml.gen b/initializr-generator-spring/src/test/resources/project/properties/yaml/application.yml.gen new file mode 100644 index 0000000000..77a615c823 --- /dev/null +++ b/initializr-generator-spring/src/test/resources/project/properties/yaml/application.yml.gen @@ -0,0 +1,3 @@ +spring: + application: + name: app-name diff --git a/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java b/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java index 43d141dc60..fdcff7a1f3 100644 --- a/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java +++ b/initializr-generator-test/src/main/java/io/spring/initializr/generator/test/InitializrMetadataTestBuilder.java @@ -87,6 +87,7 @@ public InitializrMetadataTestBuilder addBasicDefaults() { return addDefaultTypes().addDefaultPackagings() .addDefaultJavaVersions() .addDefaultLanguages() + .addDefaultConfigurationFileFormats() .addDefaultBootVersions(); } @@ -166,6 +167,21 @@ public InitializrMetadataTestBuilder addLanguage(String id, boolean defaultValue return this; } + public InitializrMetadataTestBuilder addDefaultConfigurationFileFormats() { + return addConfigurationFileFormats("properties", true).addConfigurationFileFormats("yaml", false); + } + + public InitializrMetadataTestBuilder addConfigurationFileFormats(String id, boolean defaultValue) { + this.builder.withCustomizer((it) -> { + DefaultMetadataElement element = new DefaultMetadataElement(); + element.setId(id); + element.setName(id); + element.setDefault(defaultValue); + it.getConfigurationFileFormats().addContent(element); + }); + return this; + } + public InitializrMetadataTestBuilder addDefaultBootVersions() { return addBootVersion("2.2.17.RELEASE", false).addBootVersion("2.3.3.RELEASE", false) .addBootVersion("2.4.1", true) diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnConfigurationFileFormat.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnConfigurationFileFormat.java new file mode 100644 index 0000000000..2480d48b2f --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/ConditionalOnConfigurationFileFormat.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 - present 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.initializr.generator.condition; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; + +import org.springframework.context.annotation.Conditional; + +/** + * Condition that matches when a generated project uses a particular + * {@link ConfigurationFileFormat}. + * + * @author Sijun Yang + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Documented +@Conditional(OnConfigurationFileFormatCondition.class) +public @interface ConditionalOnConfigurationFileFormat { + + String value(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnConfigurationFileFormatCondition.java b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnConfigurationFileFormatCondition.java new file mode 100644 index 0000000000..cc62315797 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/condition/OnConfigurationFileFormatCondition.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012 - present 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.initializr.generator.condition; + +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; +import io.spring.initializr.generator.project.ProjectDescription; + +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +/** + * {@link ProjectGenerationCondition Condition} implementation for + * {@link ConditionalOnConfigurationFileFormat}. + * + * @author Sijun Yang + */ +class OnConfigurationFileFormatCondition extends ProjectGenerationCondition { + + @Override + protected boolean matches(ProjectDescription description, ConditionContext context, + AnnotatedTypeMetadata metadata) { + if (description.getConfigurationFileFormat() == null) { + return false; + } + String configurationFileFormatId = (String) metadata + .getAllAnnotationAttributes(ConditionalOnConfigurationFileFormat.class.getName()) + .getFirst("value"); + ConfigurationFileFormat configurationFileFormat = ConfigurationFileFormat.forId(configurationFileFormatId); + return description.getConfigurationFileFormat().id().equals(configurationFileFormat.id()); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/ConfigurationFileFormat.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/ConfigurationFileFormat.java new file mode 100644 index 0000000000..ce6dda9bc2 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/ConfigurationFileFormat.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012 - present 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.initializr.generator.configuration.format; + +import java.util.Objects; + +import org.springframework.core.io.support.SpringFactoriesLoader; + +public interface ConfigurationFileFormat { + + /** + * Return the id of the configuration file format. + * @return the id + */ + String id(); + + /** + * Creates the configuration file format for the given id. + * @param id the id + * @return the configuration file format + * @throws IllegalStateException if the configuration file format with the given id + * can't be found + */ + static ConfigurationFileFormat forId(String id) { + return SpringFactoriesLoader + .loadFactories(ConfigurationFileFormatFactory.class, ConfigurationFileFormat.class.getClassLoader()) + .stream() + .map((factory) -> factory.createConfigurationFileFormat(id)) + .filter(Objects::nonNull) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Unrecognized configuration file format id '" + id + "'")); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/ConfigurationFileFormatFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/ConfigurationFileFormatFactory.java new file mode 100644 index 0000000000..6e89a04f78 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/ConfigurationFileFormatFactory.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 - present 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.initializr.generator.configuration.format; + +/** + * A factory for creating a {@link ConfigurationFileFormat}. + * + * @author Sijun Yang + */ +public interface ConfigurationFileFormatFactory { + + /** + * Creates and returns a {@link ConfigurationFileFormat} for the given id. If the + * factory does not recognise the given {@code id}, {@code null} should be returned. + * @param id the id of the configuration file format + * @return the configuration file format or {@code null} + */ + ConfigurationFileFormat createConfigurationFileFormat(String id); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/package-info.java new file mode 100644 index 0000000000..8e127bc856 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 - present 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. + */ + +/** + * Configuration file format abstraction. + */ +package io.spring.initializr.generator.configuration.format; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/PropertiesFormat.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/PropertiesFormat.java new file mode 100644 index 0000000000..89f7c38aa3 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/PropertiesFormat.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 - present 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.initializr.generator.configuration.format.properties; + +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; + +/** + * Properties {@link ConfigurationFileFormat}. + * + * @author Sijun Yang + */ +public final class PropertiesFormat implements ConfigurationFileFormat { + + /** + * Properties {@link ConfigurationFileFormat} identifier. + */ + public static final String ID = "properties"; + + @Override + public String id() { + return ID; + } + + @Override + public String toString() { + return id(); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/PropertiesFormatFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/PropertiesFormatFactory.java new file mode 100644 index 0000000000..816950fe63 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/PropertiesFormatFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 - present 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.initializr.generator.configuration.format.properties; + +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormatFactory; + +/** + * {@link ConfigurationFileFormat Factory} for {@link PropertiesFormat}. + * + * @author Sijun Yang + */ +class PropertiesFormatFactory implements ConfigurationFileFormatFactory { + + @Override + public ConfigurationFileFormat createConfigurationFileFormat(String id) { + if (PropertiesFormat.ID.equals(id)) { + return new PropertiesFormat(); + } + return null; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/package-info.java new file mode 100644 index 0000000000..94248191e4 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/properties/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 - present 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. + */ + +/** + * Properties configuration file format. + */ +package io.spring.initializr.generator.configuration.format.properties; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/YamlFormat.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/YamlFormat.java new file mode 100644 index 0000000000..ac2fd09c83 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/YamlFormat.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 - present 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.initializr.generator.configuration.format.yaml; + +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; + +/** + * YAML {@link ConfigurationFileFormat}. + * + * @author Sijun Yang + */ +public final class YamlFormat implements ConfigurationFileFormat { + + /** + * YAML {@link ConfigurationFileFormat} identifier. + */ + public static final String ID = "yaml"; + + @Override + public String id() { + return ID; + } + + @Override + public String toString() { + return id(); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/YamlFormatFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/YamlFormatFactory.java new file mode 100644 index 0000000000..f49eec9ddd --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/YamlFormatFactory.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 - present 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.initializr.generator.configuration.format.yaml; + +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormatFactory; + +/** + * {@link ConfigurationFileFormat Factory} for {@link YamlFormat}. + * + * @author Sijun Yang + */ +class YamlFormatFactory implements ConfigurationFileFormatFactory { + + @Override + public ConfigurationFileFormat createConfigurationFileFormat(String id) { + if (YamlFormat.ID.equals(id)) { + return new YamlFormat(); + } + return null; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/package-info.java new file mode 100644 index 0000000000..9550696ce6 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/configuration/format/yaml/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012 - present 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. + */ + +/** + * YAML configuration file format. + */ +package io.spring.initializr.generator.configuration.format.yaml; diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java index 005f48ce6b..b833c81b74 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/MutableProjectDescription.java @@ -22,6 +22,7 @@ import io.spring.initializr.generator.buildsystem.BuildSystem; import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; import io.spring.initializr.generator.language.Language; import io.spring.initializr.generator.packaging.Packaging; import io.spring.initializr.generator.version.Version; @@ -43,6 +44,8 @@ public class MutableProjectDescription implements ProjectDescription { private Language language; + private ConfigurationFileFormat configurationFileFormat; + private final Map requestedDependencies = new LinkedHashMap<>(); private String groupId; @@ -136,6 +139,19 @@ public Language getLanguage() { return this.language; } + /** + * Sets the configuration file format. + * @param configurationFileFormat the configuration file format + */ + public void setConfigurationFileFormat(ConfigurationFileFormat configurationFileFormat) { + this.configurationFileFormat = configurationFileFormat; + } + + @Override + public ConfigurationFileFormat getConfigurationFileFormat() { + return this.configurationFileFormat; + } + /** * Sets the language. * @param language the language diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java index beb4bf4963..3765ae73de 100644 --- a/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/project/ProjectDescription.java @@ -20,6 +20,7 @@ import io.spring.initializr.generator.buildsystem.BuildSystem; import io.spring.initializr.generator.buildsystem.Dependency; +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; import io.spring.initializr.generator.language.Language; import io.spring.initializr.generator.packaging.Packaging; import io.spring.initializr.generator.version.Version; @@ -71,6 +72,12 @@ default ProjectDescription createCopy() { */ Language getLanguage(); + /** + * Return the {@link ConfigurationFileFormat} of the project. + * @return the configuration file format or {@code null} + */ + ConfigurationFileFormat getConfigurationFileFormat(); + /** * Return the build {@code groupId}. * @return the groupId or {@code null} diff --git a/initializr-generator/src/main/resources/META-INF/spring.factories b/initializr-generator/src/main/resources/META-INF/spring.factories index fe8d1129da..0c8ad54c5d 100644 --- a/initializr-generator/src/main/resources/META-INF/spring.factories +++ b/initializr-generator/src/main/resources/META-INF/spring.factories @@ -10,3 +10,7 @@ io.spring.initializr.generator.language.kotlin.KotlinLanguageFactory io.spring.initializr.generator.packaging.PackagingFactory=\ io.spring.initializr.generator.packaging.jar.JarPackagingFactory,\ io.spring.initializr.generator.packaging.war.WarPackagingFactory + +io.spring.initializr.generator.configuration.format.ConfigurationFileFormatFactory=\ +io.spring.initializr.generator.configuration.format.properties.PropertiesFormatFactory,\ +io.spring.initializr.generator.configuration.format.yaml.YamlFormatFactory diff --git a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadata.java b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadata.java index f88580a6af..57ee1b3a40 100644 --- a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadata.java +++ b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadata.java @@ -51,6 +51,9 @@ public class InitializrMetadata { private final SingleSelectCapability languages = new SingleSelectCapability("language", "Language", "programming language"); + private final SingleSelectCapability configurationFileFormats = new SingleSelectCapability( + "configurationFileFormat", "Configuration File Format", "configuration file format"); + private final TextCapability name = new TextCapability("name", "Name", "project name (infer application name)"); private final TextCapability description = new TextCapability("description", "Description", "project description"); @@ -99,6 +102,10 @@ public SingleSelectCapability getLanguages() { return this.languages; } + public SingleSelectCapability getConfigurationFileFormats() { + return this.configurationFileFormats; + } + public TextCapability getName() { return this.name; } @@ -135,6 +142,7 @@ public void merge(InitializrMetadata other) { this.packagings.merge(other.packagings); this.javaVersions.merge(other.javaVersions); this.languages.merge(other.languages); + this.configurationFileFormats.merge(other.configurationFileFormats); this.name.merge(other.name); this.description.merge(other.description); this.groupId.merge(other.groupId); @@ -247,6 +255,7 @@ public Map defaults() { defaults.put("packaging", defaultId(this.packagings)); defaults.put("javaVersion", defaultId(this.javaVersions)); defaults.put("language", defaultId(this.languages)); + defaults.put("configurationFileFormat", defaultId(this.configurationFileFormats)); defaults.put("groupId", this.groupId.getContent()); defaults.put("artifactId", this.artifactId.getContent()); defaults.put("version", this.version.getContent()); diff --git a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadataBuilder.java b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadataBuilder.java index 107d8e8763..3bed6f7488 100644 --- a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadataBuilder.java +++ b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrMetadataBuilder.java @@ -176,6 +176,7 @@ public void customize(InitializrMetadata metadata) { metadata.getPackagings().merge(this.properties.getPackagings()); metadata.getJavaVersions().merge(this.properties.getJavaVersions()); metadata.getLanguages().merge(this.properties.getLanguages()); + metadata.getConfigurationFileFormats().merge(this.properties.getConfigurationFileFormats()); this.properties.getGroupId().apply(metadata.getGroupId()); this.properties.getArtifactId().apply(metadata.getArtifactId()); this.properties.getVersion().apply(metadata.getVersion()); diff --git a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrProperties.java b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrProperties.java index 56f2ec0d74..f42e8f5cc2 100644 --- a/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrProperties.java +++ b/initializr-metadata/src/main/java/io/spring/initializr/metadata/InitializrProperties.java @@ -62,6 +62,12 @@ public class InitializrProperties extends InitializrConfiguration { @JsonIgnore private final List languages = new ArrayList<>(); + /** + * Available configuration file formats. + */ + @JsonIgnore + private final List configurationFileFormats = new ArrayList<>(); + /** * Available Spring Boot versions. */ @@ -124,6 +130,10 @@ public List getLanguages() { return this.languages; } + public List getConfigurationFileFormats() { + return this.configurationFileFormats; + } + public List getBootVersions() { return this.bootVersions; } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java index 84a0e1f232..2d78c72fd7 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java @@ -31,6 +31,7 @@ import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataV22JsonMapper; +import io.spring.initializr.web.mapper.InitializrMetadataV23JsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataV2JsonMapper; import io.spring.initializr.web.mapper.InitializrMetadataVersion; import io.spring.initializr.web.project.InvalidProjectRequestException; @@ -77,6 +78,11 @@ public ResponseEntity serviceCapabilitiesHal() { return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, HAL_JSON_CONTENT_TYPE); } + @GetMapping(path = { "/", "/metadata/client" }, produces = { "application/vnd.initializr.v2.3+json" }) + public ResponseEntity serviceCapabilitiesV23() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2_3); + } + @GetMapping(path = { "/", "/metadata/client" }, produces = { "application/vnd.initializr.v2.2+json" }) public ResponseEntity serviceCapabilitiesV22() { return serviceCapabilitiesFor(InitializrMetadataVersion.V2_2); @@ -93,6 +99,11 @@ public ResponseEntity serviceCapabilitiesV2() { return serviceCapabilitiesFor(InitializrMetadataVersion.V2); } + @GetMapping(path = "/dependencies", produces = "application/vnd.initializr.v2.3+json") + public ResponseEntity dependenciesV23(@RequestParam(required = false) String bootVersion) { + return dependenciesFor(InitializrMetadataVersion.V2_3, bootVersion); + } + @GetMapping(path = "/dependencies", produces = "application/vnd.initializr.v2.2+json") public ResponseEntity dependenciesV22(@RequestParam(required = false) String bootVersion) { return dependenciesFor(InitializrMetadataVersion.V2_2, bootVersion); @@ -185,7 +196,8 @@ protected InitializrMetadataJsonMapper createMetadataJsonMapper(InitializrMetada return switch (metadataVersion) { case V2 -> new InitializrMetadataV2JsonMapper(); case V2_1 -> new InitializrMetadataV21JsonMapper(); - default -> new InitializrMetadataV22JsonMapper(); + case V2_2 -> new InitializrMetadataV22JsonMapper(); + default -> new InitializrMetadataV23JsonMapper(); }; } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV23JsonMapper.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV23JsonMapper.java new file mode 100644 index 0000000000..f8bebe92b5 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataV23JsonMapper.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012 - present 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.initializr.web.mapper; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.Type; + +import org.springframework.hateoas.TemplateVariable; +import org.springframework.hateoas.TemplateVariables; + +/** + * A {@link InitializrMetadataJsonMapper} handling the metadata format for v2.3. + *

+ * Version 2.3 adds support for configuration file formats, allowing users to specify + * their preferred configuration file format (e.g., properties, YAML). + * + * @author Sijun Yang + * @see InitializrMetadataVersion#V2_3 + */ +public class InitializrMetadataV23JsonMapper extends InitializrMetadataV22JsonMapper { + + @Override + protected TemplateVariables getTemplateVariables(Type type) { + return super.getTemplateVariables(type) + .concat(new TemplateVariable("configurationFileFormat", TemplateVariable.VariableType.REQUEST_PARAM)); + } + + @Override + protected void customizeParent(ObjectNode parent, InitializrMetadata metadata) { + super.customizeParent(parent, metadata); + singleSelect(parent, metadata.getConfigurationFileFormats()); + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java index 3110c2ea84..9ba9c8e0d6 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/mapper/InitializrMetadataVersion.java @@ -40,7 +40,12 @@ public enum InitializrMetadataVersion { /** * Add support for SemVer compliant version format. */ - V2_2("application/vnd.initializr.v2.2+json"); + V2_2("application/vnd.initializr.v2.2+json"), + + /** + * Add support for selecting the project's configuration file format. + */ + V2_3("application/vnd.initializr.v2.3+json"); private final MediaType mediaType; diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java index 51981658f4..67321a19c8 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.configuration.format.ConfigurationFileFormat; import io.spring.initializr.generator.language.Language; import io.spring.initializr.generator.packaging.Packaging; import io.spring.initializr.generator.project.MutableProjectDescription; @@ -91,6 +92,7 @@ public void convert(ProjectRequest request, MutableProjectDescription descriptio description.setDescription(request.getDescription()); description.setGroupId(cleanInputValue(request.getGroupId())); description.setLanguage(Language.forId(request.getLanguage(), request.getJavaVersion())); + description.setConfigurationFileFormat(ConfigurationFileFormat.forId(request.getConfigurationFileFormat())); description.setName(cleanInputValue(request.getName())); description.setPackageName(cleanInputValue(request.getPackageName())); description.setPackaging(Packaging.forId(request.getPackaging())); @@ -114,6 +116,7 @@ private void validate(ProjectRequest request, InitializrMetadata metadata) { validatePlatformVersion(request, metadata); validateType(request.getType(), metadata); validateLanguage(request.getLanguage(), metadata); + validateConfigurationFileFormat(request.getConfigurationFileFormat(), metadata); validatePackaging(request.getPackaging(), metadata); validateDependencies(request, metadata); } @@ -149,6 +152,17 @@ private void validateLanguage(String language, InitializrMetadata metadata) { } } + private void validateConfigurationFileFormat(String configurationFileFormat, InitializrMetadata metadata) { + if (configurationFileFormat != null) { + DefaultMetadataElement configurationFileFormatFromMetadata = metadata.getConfigurationFileFormats() + .get(configurationFileFormat); + if (configurationFileFormatFromMetadata == null) { + throw new InvalidProjectRequestException( + "Unknown configuration file format '" + configurationFileFormat + "' check project metadata"); + } + } + } + private void validatePackaging(String packaging, InitializrMetadata metadata) { if (packaging != null) { DefaultMetadataElement packagingFromMetadata = metadata.getPackagings().get(packaging); diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequest.java b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequest.java index a2c97f242d..ae8e15d292 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequest.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequest.java @@ -50,6 +50,8 @@ public class ProjectRequest { private String language; + private String configurationFileFormat; + private String packageName; private String javaVersion; @@ -145,6 +147,14 @@ public void setLanguage(String language) { this.language = language; } + public String getConfigurationFileFormat() { + return this.configurationFileFormat; + } + + public void setConfigurationFileFormat(String configurationFileFormat) { + this.configurationFileFormat = configurationFileFormat; + } + public String getPackageName() { if (StringUtils.hasText(this.packageName)) { return this.packageName; diff --git a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java index 5c4fbec13d..52588d4aad 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/AbstractInitializrIntegrationTests.java @@ -167,8 +167,7 @@ protected void assertDefaultProject(ProjectStructure project) { protected void assertDefaultJavaProject(ProjectStructure project) { assertThat(project).containsFiles("src/main/java/com/example/demo/DemoApplication.java", - "src/test/java/com/example/demo/DemoApplicationTests.java", - "src/main/resources/application.properties"); + "src/test/java/com/example/demo/DemoApplicationTests.java"); } protected void assertHasWebResources(ProjectStructure project) { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java index 9f447cd97a..a9ca4ab42d 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java @@ -53,7 +53,37 @@ void simpleZipProject() { // alias: jpa -> data-jpa .hasDependency(Dependency.createSpringBootStarter("data-jpa")) .hasDependency(Dependency.createSpringBootStarter("test", Dependency.SCOPE_TEST)); + // default configuration file format is .properties + assertThat(project).containsFiles("src/main/resources/application.properties"); + } + + @Test + void simpleZipProjectWithPropertiesFormat() { + ResponseEntity entity = downloadArchive( + "/starter.zip?dependencies=web&configurationFileFormat=properties"); + assertArchiveResponseHeaders(entity, MediaType.valueOf("application/zip"), "demo.zip"); + ProjectStructure project = projectFromArchive(entity.getBody()); + assertDefaultProject(project); + assertHasWebResources(project); + assertThat(project).mavenBuild() + .hasDependenciesSize(2) + .hasDependency(Dependency.createSpringBootStarter("web")) + .hasDependency(Dependency.createSpringBootStarter("test", Dependency.SCOPE_TEST)); + assertThat(project).containsFiles("src/main/resources/application.properties"); + } + @Test + void simpleZipProjectWithYAMLFormat() { + ResponseEntity entity = downloadArchive("/starter.zip?dependencies=web&configurationFileFormat=yaml"); + assertArchiveResponseHeaders(entity, MediaType.valueOf("application/zip"), "demo.zip"); + ProjectStructure project = projectFromArchive(entity.getBody()); + assertDefaultProject(project); + assertHasWebResources(project); + assertThat(project).mavenBuild() + .hasDependenciesSize(2) + .hasDependency(Dependency.createSpringBootStarter("web")) + .hasDependency(Dependency.createSpringBootStarter("test", Dependency.SCOPE_TEST)); + assertThat(project).containsFiles("src/main/resources/application.yml"); } @Test diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java index 6e46c9c285..369bd5e7b3 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java @@ -72,6 +72,12 @@ void metadataWithV22AcceptHeader() { validateMetadata(response, InitializrMetadataVersion.V2_2.getMediaType(), "2.2.0", JSONCompareMode.STRICT); } + @Test + void metadataWithV23AcceptHeader() { + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.3+json"); + validateMetadata(response, InitializrMetadataVersion.V2_3.getMediaType(), "2.3.0", JSONCompareMode.STRICT); + } + @Test void metadataWithInvalidPlatformVersion() { try { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV23JsonMapperTests.java b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV23JsonMapperTests.java new file mode 100644 index 0000000000..bc1b597f2f --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/mapper/InitializrMetadataV23JsonMapperTests.java @@ -0,0 +1,68 @@ +/* + * Copyright 2012 - present 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.initializr.web.mapper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.spring.initializr.generator.test.InitializrMetadataTestBuilder; +import io.spring.initializr.metadata.InitializrMetadata; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link InitializrMetadataV23JsonMapper}. + * + * @author Sijun Yang + */ +class InitializrMetadataV23JsonMapperTests { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private final InitializrMetadataV23JsonMapper mapper = new InitializrMetadataV23JsonMapper(); + + @Test + void writeConfigurationFileFormatMetadata() throws JsonProcessingException { + InitializrMetadata metadata = new InitializrMetadataTestBuilder() + .addConfigurationFileFormats("properties", true) + .addConfigurationFileFormats("yaml", false) + .build(); + String json = this.mapper.write(metadata, null); + JsonNode result = objectMapper.readTree(json); + + assertThat(result.has("configurationFileFormat")).isTrue(); + JsonNode formatsNode = result.get("configurationFileFormat"); + assertThat(formatsNode.get("type").asText()).isEqualTo("single-select"); + assertThat(formatsNode.get("default").asText()).isEqualTo("properties"); + assertThat(formatsNode.get("values")).hasSize(2); + } + + @Test + void writeTemplatedUriWithConfigurationFileFormatParameter() throws JsonProcessingException { + InitializrMetadata metadata = new InitializrMetadataTestBuilder() + .addType("id", true, "action", "build", "dialect", "format") + .build(); + String json = this.mapper.write(metadata, null); + JsonNode result = objectMapper.readTree(json); + + assertThat(result.at("/_links/id/href").asText()) + .isEqualTo("/action?type=id{&dependencies,packaging,javaVersion," + + "language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}"); + } + +} diff --git a/initializr-web/src/test/resources/application-test-default.yml b/initializr-web/src/test/resources/application-test-default.yml index e9f47f8650..ea52986db5 100644 --- a/initializr-web/src/test/resources/application-test-default.yml +++ b/initializr-web/src/test/resources/application-test-default.yml @@ -150,6 +150,13 @@ initializr: - name: Kotlin id: kotlin default: false + configuration-file-formats: + - name: Properties + id: properties + default: true + - name: YAML + id: yaml + default: false bootVersions: - name : Latest SNAPSHOT id: 2.5.0-SNAPSHOT diff --git a/initializr-web/src/test/resources/metadata/config/test-default.json b/initializr-web/src/test/resources/metadata/config/test-default.json index 94912920aa..00df4fad60 100644 --- a/initializr-web/src/test/resources/metadata/config/test-default.json +++ b/initializr-web/src/test/resources/metadata/config/test-default.json @@ -320,6 +320,24 @@ "title": "Language", "type": "SINGLE_SELECT" }, + "configurationFileFormats": { + "content": [ + { + "default": true, + "id": "properties", + "name": "Properties" + }, + { + "default": false, + "id": "yaml", + "name": "YAML" + } + ], + "description": "configuration file format", + "id": "configurationFileFormat", + "title": "Configuration File Format", + "type": "SINGLE_SELECT" + }, "name": { "content": "demo", "description": "project name (infer application name)", diff --git a/initializr-web/src/test/resources/metadata/test-default-2.3.0.json b/initializr-web/src/test/resources/metadata/test-default-2.3.0.json new file mode 100644 index 0000000000..61b1fc711a --- /dev/null +++ b/initializr-web/src/test/resources/metadata/test-default-2.3.0.json @@ -0,0 +1,246 @@ +{ + "_links": { + "dependencies": { + "href": "http://@host@/dependencies{?bootVersion}", + "templated": true + }, + "maven-build": { + "href": "http://@host@/pom.xml?type=maven-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + "templated": true + }, + "maven-project": { + "href": "http://@host@/starter.zip?type=maven-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + "templated": true + }, + "gradle-build": { + "href": "http://@host@/build.gradle?type=gradle-build{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + "templated": true + }, + "gradle-project": { + "href": "http://@host@/starter.zip?type=gradle-project{&dependencies,packaging,javaVersion,language,bootVersion,groupId,artifactId,version,name,description,packageName,configurationFileFormat}", + "templated": true + } + }, + "dependencies": { + "type": "hierarchical-multi-select", + "values": [ + { + "name": "Core", + "values": [ + { + "id": "web", + "name": "Web", + "description": "Web dependency description", + "_links": { + "guide": { + "href": "https://example.com/guide", + "title": "Building a RESTful Web Service" + }, + "reference": { + "href": "https://example.com/doc" + } + } + }, + { + "id": "security", + "name": "Security" + }, + { + "id": "data-jpa", + "name": "Data JPA" + } + ] + }, + { + "name": "Other", + "values": [ + { + "id": "org.acme:foo", + "name": "Foo", + "_links": { + "guide": [ + { + "href": "https://example.com/guide1" + }, + { + "href": "https://example.com/guide2", + "title": "Some guide for foo" + } + ], + "reference": { + "href": "https://example.com/{bootVersion}/doc", + "templated": true + } + } + }, + { + "id": "org.acme:bar", + "name": "Bar" + }, + { + "id": "org.acme:biz", + "name": "Biz", + "versionRange": "2.6.0-SNAPSHOT" + }, + { + "id": "org.acme:bur", + "name": "Bur", + "versionRange": "[2.4.4,2.5.0-SNAPSHOT)" + }, + { + "id": "my-api", + "name": "My API" + } + ] + } + ] + }, + "type": { + "type": "action", + "default": "maven-project", + "values": [ + { + "id": "maven-build", + "name": "Maven POM", + "action": "/pom.xml", + "tags": { + "build": "maven", + "format": "build" + } + }, + { + "id": "maven-project", + "name": "Maven Project", + "action": "/starter.zip", + "tags": { + "build": "maven", + "format": "project" + } + }, + { + "id": "gradle-build", + "name": "Gradle Config", + "action": "/build.gradle", + "tags": { + "build": "gradle", + "format": "build" + } + }, + { + "id": "gradle-project", + "name": "Gradle Project", + "action": "/starter.zip", + "tags": { + "build": "gradle", + "format": "project" + } + } + ] + }, + "packaging": { + "type": "single-select", + "default": "jar", + "values": [ + { + "id": "jar", + "name": "Jar" + }, + { + "id": "war", + "name": "War" + } + ] + }, + "javaVersion": { + "type": "single-select", + "default": "1.8", + "values": [ + { + "id": "1.6", + "name": "1.6" + }, + { + "id": "1.7", + "name": "1.7" + }, + { + "id": "1.8", + "name": "1.8" + } + ] + }, + "language": { + "type": "single-select", + "default": "java", + "values": [ + { + "id": "groovy", + "name": "Groovy" + }, + { + "id": "java", + "name": "Java" + }, + { + "id": "kotlin", + "name": "Kotlin" + } + ] + }, + "configurationFileFormat": { + "type": "single-select", + "default": "properties", + "values": [ + { + "id": "properties", + "name": "Properties" + }, + { + "id": "yaml", + "name": "YAML" + } + ] + }, + "bootVersion": { + "type": "single-select", + "default": "2.4.4", + "values": [ + { + "id": "2.5.0-SNAPSHOT", + "name": "Latest SNAPSHOT" + }, + { + "id": "2.4.4", + "name": "2.4.4" + }, + { + "id": "2.3.10.RELEASE", + "name": "2.3.10" + } + ] + }, + "groupId": { + "type": "text", + "default": "com.example" + }, + "artifactId": { + "type": "text", + "default": "demo" + }, + "version": { + "type": "text", + "default": "0.0.1-SNAPSHOT" + }, + "name": { + "type": "text", + "default": "demo" + }, + "description": { + "type": "text", + "default": "Demo project for Spring Boot" + }, + "packageName": { + "type": "text", + "default": "com.example.demo" + } +}