diff --git a/initializr-docs/src/main/asciidoc/configuration-guide.adoc b/initializr-docs/src/main/asciidoc/configuration-guide.adoc index 177d392d..cd3f74d7 100644 --- a/initializr-docs/src/main/asciidoc/configuration-guide.adoc +++ b/initializr-docs/src/main/asciidoc/configuration-guide.adoc @@ -1069,3 +1069,32 @@ expiration settings accordingly. |Cache templates that are used to generate projects. |=== + + + +[[create-instance-advanced-config-custom-project-request]] +=== Bind to custom project request +Only attributes that are defined in the metadata can be bound to a `ProjectRequest` and +ultimately made available in `ProjectDescription`. A custom instance may chose however to +provide additional attributes. Please note that those attributes won't be supported by +official clients (i.e. IDEs). + +The first step is to define a custom `ProjectRequest` with your additional attributes and +create a custom `ProjectGenerationController` that binds to it: + +[source,java,indent=0,subs="verbatim,quotes,attributes"] +---- +include::{code-examples}/doc/generator/project/CustomProjectGenerationController.java[tag=code] +---- + +If you inherit from `WebProjectRequest`, defaults can be automatically applied from the +metadata as shown above but you may also chose to ignore that. If you define a `@Bean` +for that controller, the auto-configuration will back-off and use yours instead. + +The next step is to make sure that those additional attributes are made available in the +`ProjectGenerationContext`. The idiomatic way of doing this is to create your own +interface that extends from `ProjectDescription` and expose your custom attributes. To +make sure your view of `ProjectGeneration` is made available in the +`ProjectGenerationContext`, a custom `ProjectRequestToDescriptionConverter` should be +defined. When such a bean exists in the context it replaces the default that the +auto-configuration provides. diff --git a/initializr-docs/src/main/java/io/spring/initializr/doc/generator/project/CustomProjectGenerationController.java b/initializr-docs/src/main/java/io/spring/initializr/doc/generator/project/CustomProjectGenerationController.java new file mode 100644 index 00000000..bae50bad --- /dev/null +++ b/initializr-docs/src/main/java/io/spring/initializr/doc/generator/project/CustomProjectGenerationController.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-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 io.spring.initializr.doc.generator.project; + +import java.util.Map; + +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.controller.ProjectGenerationController; +import io.spring.initializr.web.project.ProjectGenerationInvoker; + +/** + * Example of a custom {@link ProjectGenerationController}. + * + * @author Stephane Nicoll + */ +// tag::code[] +public class CustomProjectGenerationController extends ProjectGenerationController { + + public CustomProjectGenerationController(InitializrMetadataProvider metadataProvider, + ProjectGenerationInvoker projectGenerationInvoker) { + super(metadataProvider, projectGenerationInvoker); + } + + @Override + public CustomProjectRequest projectRequest(Map headers) { + CustomProjectRequest request = new CustomProjectRequest(); + request.getParameters().putAll(headers); + request.initialize(getMetadata()); + return request; + } + +} +// end::code[] diff --git a/initializr-docs/src/main/java/io/spring/initializr/doc/generator/project/CustomProjectRequest.java b/initializr-docs/src/main/java/io/spring/initializr/doc/generator/project/CustomProjectRequest.java new file mode 100644 index 00000000..cc9f5db8 --- /dev/null +++ b/initializr-docs/src/main/java/io/spring/initializr/doc/generator/project/CustomProjectRequest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-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 io.spring.initializr.doc.generator.project; + +import io.spring.initializr.web.project.WebProjectRequest; + +/** + * A sample custom {@link WebProjectRequest}. + * + * @author Stephane Nicoll + */ +public class CustomProjectRequest extends WebProjectRequest { + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java index 401c59cd..d5b9fe5b 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfiguration.java @@ -34,9 +34,11 @@ import io.spring.initializr.metadata.InitializrMetadataBuilder; import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.metadata.InitializrProperties; import io.spring.initializr.web.controller.CommandLineMetadataController; +import io.spring.initializr.web.controller.DefaultProjectGenerationController; import io.spring.initializr.web.controller.ProjectGenerationController; import io.spring.initializr.web.controller.ProjectMetadataController; import io.spring.initializr.web.controller.SpringCliDistributionController; +import io.spring.initializr.web.project.DefaultProjectRequestToDescriptionConverter; import io.spring.initializr.web.project.ProjectGenerationInvoker; import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter; import io.spring.initializr.web.support.DefaultDependencyMetadataProvider; @@ -144,7 +146,7 @@ public class InitializrAutoConfiguration { @ConditionalOnMissingBean ProjectGenerationController projectGenerationController(InitializrMetadataProvider metadataProvider, ProjectGenerationInvoker projectGenerationInvoker) { - return new ProjectGenerationController(metadataProvider, projectGenerationInvoker); + return new DefaultProjectGenerationController(metadataProvider, projectGenerationInvoker); } @Bean @@ -171,14 +173,9 @@ public class InitializrAutoConfiguration { @ConditionalOnMissingBean ProjectGenerationInvoker projectGenerationInvoker(ApplicationContext applicationContext, ApplicationEventPublisher eventPublisher, - ProjectRequestToDescriptionConverter projectRequestToDescriptionConverter) { - return new ProjectGenerationInvoker(applicationContext, eventPublisher, - projectRequestToDescriptionConverter); - } - - @Bean - ProjectRequestToDescriptionConverter projectRequestToDescriptionConverter() { - return new ProjectRequestToDescriptionConverter(); + ObjectProvider projectRequestToDescriptionConverter) { + return new ProjectGenerationInvoker(applicationContext, eventPublisher, projectRequestToDescriptionConverter + .getIfAvailable(DefaultProjectRequestToDescriptionConverter::new)); } @Bean diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/DefaultProjectGenerationController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/DefaultProjectGenerationController.java new file mode 100644 index 00000000..c7f94d91 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/DefaultProjectGenerationController.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.controller; + +import java.util.Map; + +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.project.ProjectGenerationInvoker; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.initializr.web.project.WebProjectRequest; + +/** + * A default {@link ProjectGenerationController} that uses a standard + * {@link ProjectRequest} to map parameters of a project generation request. + * + * @author Stephane Nicoll + */ +public class DefaultProjectGenerationController extends ProjectGenerationController { + + public DefaultProjectGenerationController(InitializrMetadataProvider metadataProvider, + ProjectGenerationInvoker projectGenerationInvoker) { + super(metadataProvider, projectGenerationInvoker); + } + + @Override + public ProjectRequest projectRequest(Map headers) { + WebProjectRequest request = new WebProjectRequest(); + request.getParameters().putAll(headers); + request.initialize(getMetadata()); + return request; + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java index 869d1dab..d374406e 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java @@ -33,12 +33,12 @@ import javax.servlet.http.HttpServletResponse; import io.spring.initializr.generator.buildsystem.BuildSystem; import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem; import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataProvider; import io.spring.initializr.web.project.InvalidProjectRequestException; import io.spring.initializr.web.project.ProjectGenerationInvoker; import io.spring.initializr.web.project.ProjectGenerationResult; import io.spring.initializr.web.project.ProjectRequest; -import io.spring.initializr.web.project.WebProjectRequest; import org.apache.commons.compress.archivers.ArchiveEntry; import org.apache.commons.compress.archivers.ArchiveOutputStream; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; @@ -61,12 +61,13 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; /** - * {@link Controller} that provides endpoints for project generation. + * Base {@link Controller} that provides endpoints for project generation. * + * @param the {@link ProjectRequest} type to use to bind request parameters * @author Stephane Nicoll */ @Controller -public class ProjectGenerationController { +public abstract class ProjectGenerationController { private static final Log logger = LogFactory.getLog(ProjectGenerationController.class); @@ -80,12 +81,17 @@ public class ProjectGenerationController { this.projectGenerationInvoker = projectGenerationInvoker; } + /** + * Create an initialized {@link ProjectRequest} instance to use to bind the parameters + * of a project generation request. + * @param headers the headers of the request + * @return a new {@link ProjectRequest} instance + */ @ModelAttribute - public ProjectRequest projectRequest(@RequestHeader Map headers) { - WebProjectRequest request = new WebProjectRequest(); - request.getParameters().putAll(headers); - request.initialize(this.metadataProvider.get()); - return request; + public abstract R projectRequest(@RequestHeader Map headers); + + protected InitializrMetadata getMetadata() { + return this.metadataProvider.get(); } @ExceptionHandler @@ -96,7 +102,7 @@ public class ProjectGenerationController { @RequestMapping(path = { "/pom", "/pom.xml" }) @ResponseBody - public ResponseEntity pom(ProjectRequest request) { + public ResponseEntity pom(R request) { request.setType("maven-build"); byte[] mavenPom = this.projectGenerationInvoker.invokeBuildGeneration(request); return createResponseEntity(mavenPom, "application/octet-stream", "pom.xml"); @@ -104,7 +110,7 @@ public class ProjectGenerationController { @RequestMapping(path = { "/build", "/build.gradle" }) @ResponseBody - public ResponseEntity gradle(ProjectRequest request) { + public ResponseEntity gradle(R request) { request.setType("gradle-build"); byte[] gradleBuild = this.projectGenerationInvoker.invokeBuildGeneration(request); return createResponseEntity(gradleBuild, "application/octet-stream", "build.gradle"); @@ -112,7 +118,7 @@ public class ProjectGenerationController { @RequestMapping("/starter.zip") @ResponseBody - public ResponseEntity springZip(ProjectRequest request) throws IOException { + public ResponseEntity springZip(R request) throws IOException { ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); Path archive = createArchive(result, "zip", ZipArchiveOutputStream::new, ZipArchiveEntry::new, ZipArchiveEntry::setUnixMode); @@ -121,7 +127,7 @@ public class ProjectGenerationController { @RequestMapping(path = "/starter.tgz", produces = "application/x-compress") @ResponseBody - public ResponseEntity springTgz(ProjectRequest request) throws IOException { + public ResponseEntity springTgz(R request) throws IOException { ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); Path archive = createArchive(result, "tar.gz", this::createTarArchiveOutputStream, TarArchiveEntry::new, TarArchiveEntry::setMode); @@ -180,7 +186,7 @@ public class ProjectGenerationController { return UnixStat.FILE_FLAG | (entryName.equals(wrapperScript) ? 0755 : UnixStat.DEFAULT_FILE_PERM); } - private String generateFileName(ProjectRequest request, String extension) { + private String generateFileName(R request, String extension) { String candidate = (StringUtils.hasText(request.getArtifactId()) ? request.getArtifactId() : this.metadataProvider.get().getArtifactId().getContent()); String tmp = candidate.replaceAll(" ", "_"); 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 new file mode 100644 index 00000000..eed8b58c --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverter.java @@ -0,0 +1,252 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.project; + +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import io.spring.initializr.generator.buildsystem.BuildSystem; +import io.spring.initializr.generator.language.Language; +import io.spring.initializr.generator.packaging.Packaging; +import io.spring.initializr.generator.project.MutableProjectDescription; +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.metadata.DefaultMetadataElement; +import io.spring.initializr.metadata.Dependency; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.Type; +import io.spring.initializr.metadata.support.MetadataBuildItemMapper; + +import org.springframework.util.StringUtils; + +/** + * A default {@link ProjectRequestToDescriptionConverter} implementation that uses the + * {@link InitializrMetadata metadata} to set default values for missing attributes if + * necessary. + * + * @author Madhura Bhave + * @author HaiTao Zhang + */ +public class DefaultProjectRequestToDescriptionConverter implements ProjectRequestToDescriptionConverter { + + private static final Version VERSION_1_5_0 = Version.parse("1.5.0.RELEASE"); + + private static final char[] VALID_MAVEN_SPECIAL_CHARACTERS = new char[] { '_', '-', '.' }; + + @Override + public ProjectDescription convert(ProjectRequest request, InitializrMetadata metadata) { + MutableProjectDescription description = new MutableProjectDescription(); + convert(request, description, metadata); + return description; + } + + /** + * Validate the specified {@link ProjectRequest request} and initialize the specified + * {@link ProjectDescription description}. Override any attribute of the description + * that are managed by this instance. + * @param request the request to validate + * @param description the description to initialize + * @param metadata the metadata instance to use to apply defaults if necessary + */ + public void convert(ProjectRequest request, MutableProjectDescription description, InitializrMetadata metadata) { + validate(request, metadata); + String springBootVersion = getSpringBootVersion(request, metadata); + List resolvedDependencies = getResolvedDependencies(request, springBootVersion, metadata); + validateDependencyRange(springBootVersion, resolvedDependencies); + + description.setApplicationName(getApplicationName(request, metadata)); + description.setArtifactId(getArtifactId(request, metadata)); + description.setBaseDirectory(getBaseDirectory(request.getBaseDir(), request.getArtifactId())); + description.setBuildSystem(getBuildSystem(request, metadata)); + description + .setDescription(determineValue(request.getDescription(), () -> metadata.getDescription().getContent())); + description.setGroupId(getGroupId(request, metadata)); + description.setLanguage(Language.forId(request.getLanguage(), request.getJavaVersion())); + description.setName(getName(request, metadata)); + description.setPackageName(getPackageName(request, metadata)); + description.setPackaging(Packaging.forId(request.getPackaging())); + description.setPlatformVersion(Version.parse(springBootVersion)); + description.setVersion(determineValue(request.getVersion(), () -> metadata.getVersion().getContent())); + resolvedDependencies.forEach((dependency) -> description.addDependency(dependency.getId(), + MetadataBuildItemMapper.toDependency(dependency))); + } + + private String determineValue(String candidate, Supplier fallback) { + return (StringUtils.hasText(candidate)) ? candidate : fallback.get(); + } + + private String getBaseDirectory(String baseDir, String artifactId) { + if (baseDir != null && baseDir.equals(artifactId)) { + return cleanMavenCoordinate(baseDir, "-"); + } + return baseDir; + } + + private String getName(ProjectRequest request, InitializrMetadata metadata) { + String name = request.getName(); + if (!StringUtils.hasText(name)) { + return metadata.getName().getContent(); + } + if (name.equals(request.getArtifactId())) { + return cleanMavenCoordinate(name, "-"); + } + return name; + } + + private String getGroupId(ProjectRequest request, InitializrMetadata metadata) { + if (!StringUtils.hasText(request.getGroupId())) { + return metadata.getGroupId().getContent(); + } + return cleanMavenCoordinate(request.getGroupId(), "."); + } + + private String getArtifactId(ProjectRequest request, InitializrMetadata metadata) { + if (!StringUtils.hasText(request.getArtifactId())) { + return metadata.getArtifactId().getContent(); + } + return cleanMavenCoordinate(request.getArtifactId(), "-"); + } + + private String cleanMavenCoordinate(String coordinate, String delimiter) { + String[] elements = coordinate.split("[^\\w\\-.]+"); + if (elements.length == 1) { + return coordinate; + } + StringBuilder builder = new StringBuilder(); + for (String element : elements) { + if (shouldAppendDelimiter(element, builder)) { + builder.append(delimiter); + } + builder.append(element); + } + return builder.toString(); + } + + private boolean shouldAppendDelimiter(String element, StringBuilder builder) { + if (builder.length() == 0) { + return false; + } + for (char c : VALID_MAVEN_SPECIAL_CHARACTERS) { + int prevIndex = builder.length() - 1; + if (element.charAt(0) == c || builder.charAt(prevIndex) == c) { + return false; + } + } + return true; + } + + private void validate(ProjectRequest request, InitializrMetadata metadata) { + validateSpringBootVersion(request); + validateType(request.getType(), metadata); + validateLanguage(request.getLanguage(), metadata); + validatePackaging(request.getPackaging(), metadata); + validateDependencies(request, metadata); + } + + private void validateSpringBootVersion(ProjectRequest request) { + Version bootVersion = Version.safeParse(request.getBootVersion()); + if (bootVersion != null && bootVersion.compareTo(VERSION_1_5_0) < 0) { + throw new InvalidProjectRequestException( + "Invalid Spring Boot version " + bootVersion + " must be 1.5.0 or higher"); + } + } + + private void validateType(String type, InitializrMetadata metadata) { + if (type != null) { + Type typeFromMetadata = metadata.getTypes().get(type); + if (typeFromMetadata == null) { + throw new InvalidProjectRequestException("Unknown type '" + type + "' check project metadata"); + } + if (!typeFromMetadata.getTags().containsKey("build")) { + throw new InvalidProjectRequestException( + "Invalid type '" + type + "' (missing build tag) check project metadata"); + } + } + } + + private void validateLanguage(String language, InitializrMetadata metadata) { + if (language != null) { + DefaultMetadataElement languageFromMetadata = metadata.getLanguages().get(language); + if (languageFromMetadata == null) { + throw new InvalidProjectRequestException("Unknown language '" + language + "' check project metadata"); + } + } + } + + private void validatePackaging(String packaging, InitializrMetadata metadata) { + if (packaging != null) { + DefaultMetadataElement packagingFromMetadata = metadata.getPackagings().get(packaging); + if (packagingFromMetadata == null) { + throw new InvalidProjectRequestException( + "Unknown packaging '" + packaging + "' check project metadata"); + } + } + } + + private void validateDependencies(ProjectRequest request, InitializrMetadata metadata) { + List dependencies = (!request.getStyle().isEmpty() ? request.getStyle() : request.getDependencies()); + dependencies.forEach((dep) -> { + Dependency dependency = metadata.getDependencies().get(dep); + if (dependency == null) { + throw new InvalidProjectRequestException("Unknown dependency '" + dep + "' check project metadata"); + } + }); + } + + private void validateDependencyRange(String springBootVersion, List resolvedDependencies) { + resolvedDependencies.forEach((dep) -> { + if (!dep.match(Version.parse(springBootVersion))) { + throw new InvalidProjectRequestException("Dependency '" + dep.getId() + "' is not compatible " + + "with Spring Boot " + springBootVersion); + } + }); + } + + private BuildSystem getBuildSystem(ProjectRequest request, InitializrMetadata metadata) { + Type typeFromMetadata = metadata.getTypes().get(request.getType()); + return BuildSystem.forId(typeFromMetadata.getTags().get("build")); + } + + private String getPackageName(ProjectRequest request, InitializrMetadata metadata) { + return metadata.getConfiguration().cleanPackageName(request.getPackageName(), + metadata.getPackageName().getContent()); + } + + private String getApplicationName(ProjectRequest request, InitializrMetadata metadata) { + if (!StringUtils.hasText(request.getApplicationName())) { + return metadata.getConfiguration().generateApplicationName(request.getName()); + } + return request.getApplicationName(); + } + + private String getSpringBootVersion(ProjectRequest request, InitializrMetadata metadata) { + return (request.getBootVersion() != null) ? request.getBootVersion() + : metadata.getBootVersions().getDefault().getId(); + } + + private List getResolvedDependencies(ProjectRequest request, String springBootVersion, + InitializrMetadata metadata) { + List depIds = (!request.getStyle().isEmpty() ? request.getStyle() : request.getDependencies()); + Version requestedVersion = Version.parse(springBootVersion); + return depIds.stream().map((it) -> { + Dependency dependency = metadata.getDependencies().get(it); + return dependency.resolve(requestedVersion); + }).collect(Collectors.toList()); + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectGenerationInvoker.java b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectGenerationInvoker.java index 02728909..787cf8ad 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectGenerationInvoker.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectGenerationInvoker.java @@ -53,15 +53,15 @@ public class ProjectGenerationInvoker { private final ApplicationEventPublisher eventPublisher; - private final ProjectRequestToDescriptionConverter converter; + private final ProjectRequestToDescriptionConverter requestConverter; private transient Map> temporaryFiles = new LinkedHashMap<>(); public ProjectGenerationInvoker(ApplicationContext parentApplicationContext, - ApplicationEventPublisher eventPublisher, ProjectRequestToDescriptionConverter converter) { + ApplicationEventPublisher eventPublisher, ProjectRequestToDescriptionConverter requestConverter) { this.parentApplicationContext = parentApplicationContext; this.eventPublisher = eventPublisher; - this.converter = converter; + this.requestConverter = requestConverter; } /** @@ -73,7 +73,7 @@ public class ProjectGenerationInvoker { public ProjectGenerationResult invokeProjectStructureGeneration(ProjectRequest request) { InitializrMetadata metadata = this.parentApplicationContext.getBean(InitializrMetadataProvider.class).get(); try { - ProjectDescription description = this.converter.convert(request, metadata); + ProjectDescription description = this.requestConverter.convert(request, metadata); ProjectGenerator projectGenerator = new ProjectGenerator(( projectGenerationContext) -> customizeProjectGenerationContext(projectGenerationContext, metadata)); ProjectGenerationResult result = projectGenerator.generate(description, generateProject(request)); @@ -104,7 +104,7 @@ public class ProjectGenerationInvoker { public byte[] invokeBuildGeneration(ProjectRequest request) { InitializrMetadata metadata = this.parentApplicationContext.getBean(InitializrMetadataProvider.class).get(); try { - ProjectDescription description = this.converter.convert(request, metadata); + ProjectDescription description = this.requestConverter.convert(request, metadata); ProjectGenerator projectGenerator = new ProjectGenerator(( projectGenerationContext) -> customizeProjectGenerationContext(projectGenerationContext, metadata)); return projectGenerator.generate(description, generateBuild(request)); diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverter.java b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverter.java index b5ee01dd..dd0239fb 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverter.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverter.java @@ -16,222 +16,25 @@ package io.spring.initializr.web.project; -import java.util.List; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -import io.spring.initializr.generator.buildsystem.BuildSystem; -import io.spring.initializr.generator.language.Language; -import io.spring.initializr.generator.packaging.Packaging; -import io.spring.initializr.generator.project.MutableProjectDescription; import io.spring.initializr.generator.project.ProjectDescription; -import io.spring.initializr.generator.version.Version; -import io.spring.initializr.metadata.DefaultMetadataElement; -import io.spring.initializr.metadata.Dependency; import io.spring.initializr.metadata.InitializrMetadata; -import io.spring.initializr.metadata.Type; -import io.spring.initializr.metadata.support.MetadataBuildItemMapper; - -import org.springframework.util.StringUtils; /** - * Validates a {@link ProjectRequest} and creates a {@link ProjectDescription} from it. + * Convert a {@link ProjectRequest} to a {@link ProjectDescription}. * - * @author Madhura Bhave - * @author HaiTao Zhang + * @author Stephane Nicoll */ -public class ProjectRequestToDescriptionConverter { +@FunctionalInterface +public interface ProjectRequestToDescriptionConverter { - private static final Version VERSION_1_5_0 = Version.parse("1.5.0.RELEASE"); - - private static final char[] VALID_MAVEN_SPECIAL_CHARACTERS = new char[] { '_', '-', '.' }; - - public ProjectDescription convert(ProjectRequest request, InitializrMetadata metadata) { - validate(request, metadata); - String springBootVersion = getSpringBootVersion(request, metadata); - List resolvedDependencies = getResolvedDependencies(request, springBootVersion, metadata); - validateDependencyRange(springBootVersion, resolvedDependencies); - MutableProjectDescription description = new MutableProjectDescription(); - description.setApplicationName(getApplicationName(request, metadata)); - description.setArtifactId(getArtifactId(request, metadata)); - description.setBaseDirectory(getBaseDirectory(request.getBaseDir(), request.getArtifactId())); - description.setBuildSystem(getBuildSystem(request, metadata)); - description - .setDescription(determineValue(request.getDescription(), () -> metadata.getDescription().getContent())); - description.setGroupId(getGroupId(request, metadata)); - description.setLanguage(Language.forId(request.getLanguage(), request.getJavaVersion())); - description.setName(getName(request, metadata)); - description.setPackageName(getPackageName(request, metadata)); - description.setPackaging(Packaging.forId(request.getPackaging())); - description.setPlatformVersion(Version.parse(springBootVersion)); - description.setVersion(determineValue(request.getVersion(), () -> metadata.getVersion().getContent())); - resolvedDependencies.forEach((dependency) -> description.addDependency(dependency.getId(), - MetadataBuildItemMapper.toDependency(dependency))); - - return description; - } - - private String determineValue(String candidate, Supplier fallback) { - return (StringUtils.hasText(candidate)) ? candidate : fallback.get(); - } - - private String getBaseDirectory(String baseDir, String artifactId) { - if (baseDir != null && baseDir.equals(artifactId)) { - return cleanMavenCoordinate(baseDir, "-"); - } - return baseDir; - } - - private String getName(ProjectRequest request, InitializrMetadata metadata) { - String name = request.getName(); - if (!StringUtils.hasText(name)) { - return metadata.getName().getContent(); - } - if (name.equals(request.getArtifactId())) { - return cleanMavenCoordinate(name, "-"); - } - return name; - } - - private String getGroupId(ProjectRequest request, InitializrMetadata metadata) { - if (!StringUtils.hasText(request.getGroupId())) { - return metadata.getGroupId().getContent(); - } - return cleanMavenCoordinate(request.getGroupId(), "."); - } - - private String getArtifactId(ProjectRequest request, InitializrMetadata metadata) { - if (!StringUtils.hasText(request.getArtifactId())) { - return metadata.getArtifactId().getContent(); - } - return cleanMavenCoordinate(request.getArtifactId(), "-"); - } - - private String cleanMavenCoordinate(String coordinate, String delimiter) { - String[] elements = coordinate.split("[^\\w\\-.]+"); - if (elements.length == 1) { - return coordinate; - } - StringBuilder builder = new StringBuilder(); - for (String element : elements) { - if (shouldAppendDelimiter(element, builder)) { - builder.append(delimiter); - } - builder.append(element); - } - return builder.toString(); - } - - private boolean shouldAppendDelimiter(String element, StringBuilder builder) { - if (builder.length() == 0) { - return false; - } - for (char c : VALID_MAVEN_SPECIAL_CHARACTERS) { - int prevIndex = builder.length() - 1; - if (element.charAt(0) == c || builder.charAt(prevIndex) == c) { - return false; - } - } - return true; - } - - private void validate(ProjectRequest request, InitializrMetadata metadata) { - validateSpringBootVersion(request); - validateType(request.getType(), metadata); - validateLanguage(request.getLanguage(), metadata); - validatePackaging(request.getPackaging(), metadata); - validateDependencies(request, metadata); - } - - private void validateSpringBootVersion(ProjectRequest request) { - Version bootVersion = Version.safeParse(request.getBootVersion()); - if (bootVersion != null && bootVersion.compareTo(VERSION_1_5_0) < 0) { - throw new InvalidProjectRequestException( - "Invalid Spring Boot version " + bootVersion + " must be 1.5.0 or higher"); - } - } - - private void validateType(String type, InitializrMetadata metadata) { - if (type != null) { - Type typeFromMetadata = metadata.getTypes().get(type); - if (typeFromMetadata == null) { - throw new InvalidProjectRequestException("Unknown type '" + type + "' check project metadata"); - } - if (!typeFromMetadata.getTags().containsKey("build")) { - throw new InvalidProjectRequestException( - "Invalid type '" + type + "' (missing build tag) check project metadata"); - } - } - } - - private void validateLanguage(String language, InitializrMetadata metadata) { - if (language != null) { - DefaultMetadataElement languageFromMetadata = metadata.getLanguages().get(language); - if (languageFromMetadata == null) { - throw new InvalidProjectRequestException("Unknown language '" + language + "' check project metadata"); - } - } - } - - private void validatePackaging(String packaging, InitializrMetadata metadata) { - if (packaging != null) { - DefaultMetadataElement packagingFromMetadata = metadata.getPackagings().get(packaging); - if (packagingFromMetadata == null) { - throw new InvalidProjectRequestException( - "Unknown packaging '" + packaging + "' check project metadata"); - } - } - } - - private void validateDependencies(ProjectRequest request, InitializrMetadata metadata) { - List dependencies = (!request.getStyle().isEmpty() ? request.getStyle() : request.getDependencies()); - dependencies.forEach((dep) -> { - Dependency dependency = metadata.getDependencies().get(dep); - if (dependency == null) { - throw new InvalidProjectRequestException("Unknown dependency '" + dep + "' check project metadata"); - } - }); - } - - private void validateDependencyRange(String springBootVersion, List resolvedDependencies) { - resolvedDependencies.forEach((dep) -> { - if (!dep.match(Version.parse(springBootVersion))) { - throw new InvalidProjectRequestException("Dependency '" + dep.getId() + "' is not compatible " - + "with Spring Boot " + springBootVersion); - } - }); - } - - private BuildSystem getBuildSystem(ProjectRequest request, InitializrMetadata metadata) { - Type typeFromMetadata = metadata.getTypes().get(request.getType()); - return BuildSystem.forId(typeFromMetadata.getTags().get("build")); - } - - private String getPackageName(ProjectRequest request, InitializrMetadata metadata) { - return metadata.getConfiguration().cleanPackageName(request.getPackageName(), - metadata.getPackageName().getContent()); - } - - private String getApplicationName(ProjectRequest request, InitializrMetadata metadata) { - if (!StringUtils.hasText(request.getApplicationName())) { - return metadata.getConfiguration().generateApplicationName(request.getName()); - } - return request.getApplicationName(); - } - - private String getSpringBootVersion(ProjectRequest request, InitializrMetadata metadata) { - return (request.getBootVersion() != null) ? request.getBootVersion() - : metadata.getBootVersions().getDefault().getId(); - } - - private List getResolvedDependencies(ProjectRequest request, String springBootVersion, - InitializrMetadata metadata) { - List depIds = (!request.getStyle().isEmpty() ? request.getStyle() : request.getDependencies()); - Version requestedVersion = Version.parse(springBootVersion); - return depIds.stream().map((it) -> { - Dependency dependency = metadata.getDependencies().get(it); - return dependency.resolve(requestedVersion); - }).collect(Collectors.toList()); - } + /** + * Validate and convert the specified {@link ProjectRequest} to a + * {@link ProjectDescription} used as the source of project generation. + * @param request the request to convert + * @param metadata the metadata instance to use + * @return a validated {@link ProjectDescription} to use to generate a project that + * matches the specified {@code request} + */ + ProjectDescription convert(ProjectRequest request, InitializrMetadata metadata); } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java index dcfdba01..930a4b71 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/autoconfigure/InitializrAutoConfigurationTests.java @@ -28,15 +28,12 @@ import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter; import io.spring.initializr.web.support.DefaultInitializrMetadataUpdateStrategy; import io.spring.initializr.web.support.InitializrMetadataUpdateStrategy; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.beans.DirectFieldAccessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; -import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration; import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration; -import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; @@ -57,9 +54,11 @@ import static org.mockito.Mockito.mock; */ class InitializrAutoConfigurationTests { + private static final AutoConfigurations BASIC_AUTO_CONFIGURATIONS = AutoConfigurations + .of(RestTemplateAutoConfiguration.class, JacksonAutoConfiguration.class, InitializrAutoConfiguration.class); + private ApplicationContextRunner contextRunner = new ApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class, - JacksonAutoConfiguration.class, InitializrAutoConfiguration.class)); + .withConfiguration(BASIC_AUTO_CONFIGURATIONS); @Test void autoConfigRegistersTemplateRenderer() { @@ -127,13 +126,10 @@ class InitializrAutoConfigurationTests { @Test void webConfiguration() { WebApplicationContextRunner webContextRunner = new WebApplicationContextRunner() - .withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class, - JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class, - WebMvcAutoConfiguration.class, InitializrAutoConfiguration.class)); + .withConfiguration(BASIC_AUTO_CONFIGURATIONS); webContextRunner.run((context) -> { assertThat(context).hasSingleBean(InitializrWebConfig.class); assertThat(context).hasSingleBean(ProjectGenerationInvoker.class); - assertThat(context).hasSingleBean(ProjectRequestToDescriptionConverter.class); assertThat(context).hasSingleBean(ProjectGenerationController.class); assertThat(context).hasSingleBean(ProjectMetadataController.class); assertThat(context).hasSingleBean(CommandLineMetadataController.class); @@ -141,6 +137,17 @@ class InitializrAutoConfigurationTests { }); } + @Test + void autoConfigWithCustomProjectRequestConverter() { + new WebApplicationContextRunner().withConfiguration(BASIC_AUTO_CONFIGURATIONS) + .withUserConfiguration(CustomProjectRequestToDescriptionConverter.class).run((context) -> { + assertThat(context).hasSingleBean(ProjectGenerationInvoker.class); + assertThat(context.getBean(ProjectGenerationInvoker.class)).hasFieldOrPropertyWithValue( + "requestConverter", context.getBean("testProjectRequestToDescriptionConverter")); + }); + + } + @Test void webConfigurationConditionalOnWebApplication() { this.contextRunner.run((context) -> { @@ -180,7 +187,7 @@ class InitializrAutoConfigurationTests { @Bean TemplateRenderer testTemplateRenderer() { - return Mockito.mock(TemplateRenderer.class); + return mock(TemplateRenderer.class); } } @@ -190,7 +197,7 @@ class InitializrAutoConfigurationTests { @Bean InitializrMetadataUpdateStrategy testInitializrMetadataUpdateStrategy() { - return Mockito.mock(InitializrMetadataUpdateStrategy.class); + return mock(InitializrMetadataUpdateStrategy.class); } } @@ -200,7 +207,7 @@ class InitializrAutoConfigurationTests { @Bean InitializrMetadataProvider testInitializrMetadataProvider() { - return Mockito.mock(InitializrMetadataProvider.class); + return mock(InitializrMetadataProvider.class); } } @@ -210,7 +217,17 @@ class InitializrAutoConfigurationTests { @Bean DependencyMetadataProvider testDependencyMetadataProvider() { - return Mockito.mock(DependencyMetadataProvider.class); + return mock(DependencyMetadataProvider.class); + } + + } + + @Configuration + static class CustomProjectRequestToDescriptionConverter { + + @Bean + ProjectRequestToDescriptionConverter testProjectRequestToDescriptionConverter() { + return mock(ProjectRequestToDescriptionConverter.class); } } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java new file mode 100644 index 00000000..cc135c95 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectContributor.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.controller.custom; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.project.contributor.ProjectContributor; + +/** + * A {@link ProjectContributor} that adds an {@code custom.txt} file at the root of the + * project when the registered description is a {@link CustomProjectDescription} and its + * {@code customFlag} is {@code enabled}. + * + * @author Stephane Nicoll + */ +class CustomProjectContributor implements ProjectContributor { + + private final ProjectDescription description; + + CustomProjectContributor(ProjectDescription description) { + this.description = description; + } + + @Override + public void contribute(Path projectRoot) throws IOException { + if (this.description instanceof CustomProjectDescription + && ((CustomProjectDescription) this.description).isCustomFlag()) { + Files.createFile(projectRoot.resolve("custom.txt")); + } + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java new file mode 100644 index 00000000..dc7fbaa2 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectDescription.java @@ -0,0 +1,39 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.controller.custom; + +import io.spring.initializr.generator.project.MutableProjectDescription; +import io.spring.initializr.generator.project.ProjectDescription; + +/** + * A custom {@link ProjectDescription} to convey the additional flags to contributors. + * + * @author Stephane Nicoll + */ +class CustomProjectDescription extends MutableProjectDescription { + + private boolean customFlag; + + boolean isCustomFlag() { + return this.customFlag; + } + + void setCustomFlag(boolean customFlag) { + this.customFlag = customFlag; + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectGenerationController.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectGenerationController.java new file mode 100644 index 00000000..01c9a88c --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectGenerationController.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.controller.custom; + +import java.util.Map; + +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.controller.ProjectGenerationController; +import io.spring.initializr.web.project.ProjectGenerationInvoker; + +/** + * A custom {@link ProjectGenerationController} that binds request attributes to a + * {@link CustomProjectRequest}. + * + * @author Stephane Nicoll + */ +class CustomProjectGenerationController extends ProjectGenerationController { + + CustomProjectGenerationController(InitializrMetadataProvider metadataProvider, + ProjectGenerationInvoker projectGenerationInvoker) { + super(metadataProvider, projectGenerationInvoker); + } + + @Override + public CustomProjectRequest projectRequest(Map headers) { + CustomProjectRequest request = new CustomProjectRequest(); + request.getParameters().putAll(headers); + request.initialize(getMetadata()); + return request; + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectRequest.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectRequest.java new file mode 100644 index 00000000..5474da91 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/CustomProjectRequest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.controller.custom; + +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.initializr.web.project.WebProjectRequest; + +/** + * A custom {@link ProjectRequest} with an additional custom boolean flag. This type has + * to be public for the {@code customFlag} request attribute to be mapped properly. + * + * @author Stephane Nicoll + */ +public class CustomProjectRequest extends WebProjectRequest { + + private boolean customFlag; + + public boolean isCustomFlag() { + return this.customFlag; + } + + public void setCustomFlag(boolean customFlag) { + this.customFlag = customFlag; + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java new file mode 100644 index 00000000..77472664 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/custom/ProjectGenerationControllerCustomRequestIntegrationTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2012-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 io.spring.initializr.web.controller.custom; + +import io.spring.initializr.generator.project.ProjectDescription; +import io.spring.initializr.generator.test.project.ProjectStructure; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import io.spring.initializr.web.controller.ProjectGenerationController; +import io.spring.initializr.web.controller.custom.ProjectGenerationControllerCustomRequestIntegrationTests.CustomProjectGenerationConfiguration; +import io.spring.initializr.web.project.DefaultProjectRequestToDescriptionConverter; +import io.spring.initializr.web.project.ProjectGenerationInvoker; +import io.spring.initializr.web.project.ProjectRequest; +import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter; +import org.junit.jupiter.api.Test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for a {@link ProjectGenerationController} that maps to a custom + * request. + * + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +@Import(CustomProjectGenerationConfiguration.class) +public class ProjectGenerationControllerCustomRequestIntegrationTests + extends AbstractInitializrControllerIntegrationTests { + + @Test + void createProjectWithCustomFlagEnabled() { + ProjectStructure project = downloadZip("/starter.zip?dependencies=web&customFlag=true"); + assertThat(project).containsFiles("custom.txt"); + } + + @Test + void createProjectWithCustomFlagDisabled() { + ProjectStructure project = downloadZip("/starter.zip?dependencies=web&customFlag=false"); + assertThat(project).doesNotContainFiles("custom.txt"); + } + + @Test + void createProjectWithOverriddenRequestParams() { + ProjectStructure project = downloadZip("/starter.zip?groupId=com.acme&artifactId=test"); + assertThat(project).containsFiles("src/main/java/org/example/custom/CustomApp.java", + "src/test/java/org/example/custom/CustomAppTests.java"); + assertThat(project).doesNotContainDirectories("src/main/java/com", "src/test/java/com"); + assertThat(project).doesNotContainFiles("custom.txt"); + } + + @Configuration + static class CustomProjectGenerationConfiguration { + + @Bean + CustomProjectGenerationController customProjectGenerationController(InitializrMetadataProvider metadataProvider, + ProjectGenerationInvoker projectGenerationInvoker) { + return new CustomProjectGenerationController(metadataProvider, projectGenerationInvoker); + } + + @Bean + ProjectRequestToDescriptionConverter customProjectRequestToDescriptionConverter() { + return new CustomProjectRequestToDescriptionConverter(); + } + + } + + static class CustomProjectRequestToDescriptionConverter implements ProjectRequestToDescriptionConverter { + + @Override + public ProjectDescription convert(ProjectRequest request, InitializrMetadata metadata) { + CustomProjectRequest customRequest = (CustomProjectRequest) request; + CustomProjectDescription description = new CustomProjectDescription(); + new DefaultProjectRequestToDescriptionConverter().convert(request, description, metadata); + description.setCustomFlag(customRequest.isCustomFlag()); + // Override attributes for test purposes + description.setPackageName("org.example.custom"); + description.setApplicationName("CustomApp"); + return description; + } + + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverterTests.java b/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java similarity index 98% rename from initializr-web/src/test/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverterTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java index 28e88f35..8a7a126c 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/ProjectRequestToDescriptionConverterTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/DefaultProjectRequestToDescriptionConverterTests.java @@ -32,17 +32,17 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; /** - * Tests for {@link ProjectRequestToDescriptionConverter}. + * Tests for {@link DefaultProjectRequestToDescriptionConverter}. * * @author Madhura Bhave * @author Stephane Nicoll * @author HaiTao Zhang */ -class ProjectRequestToDescriptionConverterTests { +class DefaultProjectRequestToDescriptionConverterTests { private InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults().build(); - private final ProjectRequestToDescriptionConverter converter = new ProjectRequestToDescriptionConverter(); + private final DefaultProjectRequestToDescriptionConverter converter = new DefaultProjectRequestToDescriptionConverter(); @Test void convertWhenTypeIsInvalidShouldThrowException() { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/ProjectGenerationInvokerTests.java b/initializr-web/src/test/java/io/spring/initializr/web/project/ProjectGenerationInvokerTests.java index f9e567e8..3b9616f9 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/ProjectGenerationInvokerTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/project/ProjectGenerationInvokerTests.java @@ -69,8 +69,8 @@ public class ProjectGenerationInvokerTests { @BeforeEach void setup() { setupContext(); - ProjectRequestToDescriptionConverter converter = new ProjectRequestToDescriptionConverter(); - this.invoker = new ProjectGenerationInvoker(this.context, this.eventPublisher, converter); + this.invoker = new ProjectGenerationInvoker(this.context, this.eventPublisher, + new DefaultProjectRequestToDescriptionConverter()); } @AfterEach diff --git a/initializr-web/src/test/resources/META-INF/spring.factories b/initializr-web/src/test/resources/META-INF/spring.factories new file mode 100644 index 00000000..025fb832 --- /dev/null +++ b/initializr-web/src/test/resources/META-INF/spring.factories @@ -0,0 +1 @@ +io.spring.initializr.generator.project.ProjectGenerationConfiguration=io.spring.initializr.web.controller.custom.CustomProjectContributor \ No newline at end of file