Switch project structure to use the new generator

This commit removes the former `ProjectGenerator` api based on mustache
template in favour of a new DSL infrastructure to be detailed in further
commits.

Event handling is now web-specific with a `ProjectRequest` and a
`WebProjectRequest` that gathers the base input from the request and
some additional web-specific metadata, respectively. As a consequence
the `initializr-actuator` module has now a dependency on the
`initializr-web` module.

See gh-340

Co-authored-by: Stephane Nicoll <snicoll@pivotal.io>
This commit is contained in:
Madhura Bhave
2018-11-21 15:20:08 +01:00
committed by Stephane Nicoll
parent 0628829cd3
commit e281480426
129 changed files with 1153 additions and 8245 deletions

View File

@@ -79,6 +79,18 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.spring.initializr.experimental</groupId>
<artifactId>initializr-generator</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.spring.initializr.experimental</groupId>
<artifactId>initializr-generator-spring</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@@ -0,0 +1,37 @@
/*
* 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
*
* http://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;
import io.spring.initializr.InitializrException;
/**
* Thrown when a {@link ProjectRequest} is invalid.
*
* @author Stephane Nicoll
*/
@SuppressWarnings("serial")
public class InvalidProjectRequestException extends InitializrException {
public InvalidProjectRequestException(String message, Throwable cause) {
super(message, cause);
}
public InvalidProjectRequestException(String message) {
super(message);
}
}

View File

@@ -0,0 +1,72 @@
/*
* 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
*
* http://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;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;
/**
* Locate project resources.
*
* @author Stephane Nicoll
*/
public class ProjectResourceLocator {
private static final Charset UTF_8 = Charset.forName("UTF-8");
/**
* Return the binary content of the resource at the specified location.
* @param location a resource location
* @return the content of the resource
*/
@Cacheable("initializr.project-resources")
public byte[] getBinaryResource(String location) {
try (InputStream stream = getInputStream(location)) {
return StreamUtils.copyToByteArray(stream);
}
catch (IOException ex) {
throw new IllegalStateException("Cannot get resource", ex);
}
}
/**
* Return the textual content of the resource at the specified location.
* @param location a resource location
* @return the content of the resource
*/
@Cacheable("initializr.project-resources")
public String getTextResource(String location) {
try (InputStream stream = getInputStream(location)) {
return StreamUtils.copyToString(stream, UTF_8);
}
catch (IOException ex) {
throw new IllegalStateException("Cannot get resource", ex);
}
}
private InputStream getInputStream(String location) throws IOException {
URL url = ResourceUtils.getURL(location);
return url.openStream();
}
}

View File

@@ -16,30 +16,30 @@
package io.spring.initializr.web.autoconfigure;
import java.util.ArrayList;
import java.util.List;
import java.nio.file.Files;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.CreatedExpiryPolicy;
import javax.cache.expiry.Duration;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.generator.ProjectGenerator;
import io.spring.initializr.generator.ProjectRequestPostProcessor;
import io.spring.initializr.generator.ProjectRequestResolver;
import io.spring.initializr.generator.ProjectResourceLocator;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.io.SimpleIndentStrategy;
import io.spring.initializr.generator.project.ProjectDirectoryFactory;
import io.spring.initializr.metadata.DependencyMetadataProvider;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataBuilder;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.metadata.InitializrProperties;
import io.spring.initializr.util.TemplateRenderer;
import io.spring.initializr.web.ProjectResourceLocator;
import io.spring.initializr.web.project.MainController;
import io.spring.initializr.web.project.ProjectGenerationInvoker;
import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter;
import io.spring.initializr.web.support.DefaultDependencyMetadataProvider;
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider;
import io.spring.initializr.web.ui.UiController;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -50,6 +50,8 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfigu
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
@@ -60,9 +62,6 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
* Auto-configuration} to configure Spring initializr. In a web environment, configures
* the necessary controller to serve the applications from the root context.
*
* <p>
* Project generation can be customized by defining a custom {@link ProjectGenerator}.
*
* @author Stephane Nicoll
*/
@Configuration
@@ -71,18 +70,16 @@ import org.springframework.web.servlet.resource.ResourceUrlProvider;
RestTemplateAutoConfiguration.class })
public class InitializrAutoConfiguration {
private final List<ProjectRequestPostProcessor> postProcessors;
public InitializrAutoConfiguration(
ObjectProvider<List<ProjectRequestPostProcessor>> postProcessors) {
List<ProjectRequestPostProcessor> list = postProcessors.getIfAvailable();
this.postProcessors = (list != null) ? list : new ArrayList<>();
@Bean
@ConditionalOnMissingBean
public ProjectDirectoryFactory projectDirectoryFactory() {
return (description) -> Files.createTempDirectory("project-");
}
@Bean
@ConditionalOnMissingBean
public ProjectGenerator projectGenerator() {
return new ProjectGenerator();
public IndentingWriterFactory indentingWriterFactory() {
return IndentingWriterFactory.create(new SimpleIndentStrategy("\t"));
}
@Bean
@@ -95,12 +92,6 @@ public class InitializrAutoConfiguration {
return templateRenderer;
}
@Bean
@ConditionalOnMissingBean
public ProjectRequestResolver projectRequestResolver() {
return new ProjectRequestResolver(this.postProcessors);
}
@Bean
@ConditionalOnMissingBean
public ProjectResourceLocator projectResourceLocator() {
@@ -142,10 +133,26 @@ public class InitializrAutoConfiguration {
InitializrMetadataProvider metadataProvider,
TemplateRenderer templateRenderer,
ResourceUrlProvider resourceUrlProvider,
ProjectGenerator projectGenerator,
DependencyMetadataProvider dependencyMetadataProvider) {
DependencyMetadataProvider dependencyMetadataProvider,
ProjectGenerationInvoker projectGenerationInvoker) {
return new MainController(metadataProvider, templateRenderer,
resourceUrlProvider, projectGenerator, dependencyMetadataProvider);
resourceUrlProvider, dependencyMetadataProvider,
projectGenerationInvoker);
}
@Bean
@ConditionalOnMissingBean
public ProjectGenerationInvoker projectGenerationInvoker(
ApplicationContext applicationContext,
ApplicationEventPublisher eventPublisher,
ProjectRequestToDescriptionConverter projectRequestToDescriptionConverter) {
return new ProjectGenerationInvoker(applicationContext, eventPublisher,
projectRequestToDescriptionConverter);
}
@Bean
public ProjectRequestToDescriptionConverter projectRequestToDescriptionConverter() {
return new ProjectRequestToDescriptionConverter();
}
@Bean
@@ -177,6 +184,7 @@ public class InitializrAutoConfiguration {
CreatedExpiryPolicy.factoryOf(Duration.TEN_MINUTES)));
cacheManager.createCache("initializr.dependency-metadata", config());
cacheManager.createCache("initializr.project-resources", config());
cacheManager.createCache("initializr.templates", config());
};
}

View File

@@ -23,10 +23,10 @@ import java.util.function.Function;
import javax.servlet.http.HttpServletResponse;
import io.spring.initializr.generator.InvalidProjectRequestException;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.metadata.TypeCapability;
import io.spring.initializr.web.InvalidProjectRequestException;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.http.HttpStatus;

View File

@@ -28,9 +28,6 @@ import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import com.samskivert.mustache.Mustache;
import io.spring.initializr.generator.BasicProjectRequest;
import io.spring.initializr.generator.ProjectGenerator;
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.metadata.DependencyMetadata;
import io.spring.initializr.metadata.DependencyMetadataProvider;
import io.spring.initializr.metadata.InitializrMetadata;
@@ -87,26 +84,25 @@ public class MainController extends AbstractInitializrController {
public static final MediaType HAL_JSON_CONTENT_TYPE = MediaType
.parseMediaType("application/hal+json");
private final ProjectGenerator projectGenerator;
private final DependencyMetadataProvider dependencyMetadataProvider;
private final CommandLineHelpGenerator commandLineHelpGenerator;
private final ProjectGenerationInvoker projectGenerationInvoker;
public MainController(InitializrMetadataProvider metadataProvider,
TemplateRenderer templateRenderer, ResourceUrlProvider resourceUrlProvider,
ProjectGenerator projectGenerator,
DependencyMetadataProvider dependencyMetadataProvider) {
DependencyMetadataProvider dependencyMetadataProvider,
ProjectGenerationInvoker projectGenerationInvoker) {
super(metadataProvider, resourceUrlProvider);
this.projectGenerator = projectGenerator;
this.dependencyMetadataProvider = dependencyMetadataProvider;
this.commandLineHelpGenerator = new CommandLineHelpGenerator(templateRenderer);
this.projectGenerationInvoker = projectGenerationInvoker;
}
@ModelAttribute
public BasicProjectRequest projectRequest(
@RequestHeader Map<String, String> headers) {
ProjectRequest request = new ProjectRequest();
public ProjectRequest projectRequest(@RequestHeader Map<String, String> headers) {
WebProjectRequest request = new WebProjectRequest();
request.getParameters().putAll(headers);
request.initialize(this.metadataProvider.get());
return request;
@@ -246,32 +242,27 @@ public class MainController extends AbstractInitializrController {
@RequestMapping(path = { "/pom", "/pom.xml" })
@ResponseBody
public ResponseEntity<byte[]> pom(BasicProjectRequest request) {
public ResponseEntity<byte[]> pom(ProjectRequest request) {
request.setType("maven-build");
byte[] mavenPom = this.projectGenerator
.generateMavenPom((ProjectRequest) request);
byte[] mavenPom = this.projectGenerationInvoker.invokeBuildGeneration(request);
return createResponseEntity(mavenPom, "application/octet-stream", "pom.xml");
}
@RequestMapping(path = { "/build", "/build.gradle" })
@ResponseBody
public ResponseEntity<byte[]> gradle(BasicProjectRequest request) {
public ResponseEntity<byte[]> gradle(ProjectRequest request) {
request.setType("gradle-build");
byte[] gradleBuild = this.projectGenerator
.generateGradleBuild((ProjectRequest) request);
byte[] gradleBuild = this.projectGenerationInvoker.invokeBuildGeneration(request);
return createResponseEntity(gradleBuild, "application/octet-stream",
"build.gradle");
}
@RequestMapping("/starter.zip")
@ResponseBody
public ResponseEntity<byte[]> springZip(BasicProjectRequest basicRequest)
throws IOException {
ProjectRequest request = (ProjectRequest) basicRequest;
File dir = this.projectGenerator.generateProjectStructure(request);
File download = this.projectGenerator.createDistributionFile(dir, ".zip");
public ResponseEntity<byte[]> springZip(ProjectRequest request) throws IOException {
File dir = this.projectGenerationInvoker
.invokeProjectStructureGeneration(request);
File download = this.projectGenerationInvoker.createDistributionFile(dir, ".zip");
String wrapperScript = getWrapperScript(request);
new File(dir, wrapperScript).setExecutable(true);
Zip zip = new Zip();
@@ -296,13 +287,11 @@ public class MainController extends AbstractInitializrController {
@RequestMapping(path = "/starter.tgz", produces = "application/x-compress")
@ResponseBody
public ResponseEntity<byte[]> springTgz(BasicProjectRequest basicRequest)
throws IOException {
ProjectRequest request = (ProjectRequest) basicRequest;
File dir = this.projectGenerator.generateProjectStructure(request);
File download = this.projectGenerator.createDistributionFile(dir, ".tar.gz");
public ResponseEntity<byte[]> springTgz(ProjectRequest request) throws IOException {
File dir = this.projectGenerationInvoker
.invokeProjectStructureGeneration(request);
File download = this.projectGenerationInvoker.createDistributionFile(dir,
".tar.gz");
String wrapperScript = getWrapperScript(request);
new File(dir, wrapperScript).setExecutable(true);
Tar zip = new Tar();
@@ -338,7 +327,8 @@ public class MainController extends AbstractInitializrController {
}
private static String getWrapperScript(ProjectRequest request) {
String script = ("gradle".equals(request.getBuild()) ? "gradlew" : "mvnw");
String script = (request.getType() != null
&& request.getType().startsWith("gradle")) ? "gradlew" : "mvnw";
return (request.getBaseDir() != null) ? request.getBaseDir() + "/" + script
: script;
}
@@ -349,7 +339,7 @@ public class MainController extends AbstractInitializrController {
log.info("Uploading: {} ({} bytes)", download, bytes.length);
ResponseEntity<byte[]> result = createResponseEntity(bytes, contentType,
fileName);
this.projectGenerator.cleanTempFiles(dir);
this.projectGenerationInvoker.cleanTempFiles(dir);
return result;
}

View File

@@ -0,0 +1,44 @@
/*
* 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
*
* http://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 io.spring.initializr.metadata.InitializrMetadata;
/**
* Event published when an error occurred trying to generate a project.
*
* @author Stephane Nicoll
*/
public class ProjectFailedEvent extends ProjectRequestEvent {
private final Exception cause;
public ProjectFailedEvent(ProjectRequest request, InitializrMetadata metadata,
Exception cause) {
super(request, metadata);
this.cause = cause;
}
/**
* Return the cause of the failure.
* @return the cause of the failure
*/
public Exception getCause() {
return this.cause;
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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
*
* http://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 io.spring.initializr.metadata.InitializrMetadata;
/**
* Event published when a new project has been generated successfully.
*
* @author Stephane Nicoll
*/
public class ProjectGeneratedEvent extends ProjectRequestEvent {
public ProjectGeneratedEvent(ProjectRequest request, InitializrMetadata metadata) {
super(request, metadata);
}
}

View File

@@ -0,0 +1,215 @@
/*
* 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
*
* http://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.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import io.spring.initializr.InitializrException;
import io.spring.initializr.generator.buildsystem.Build;
import io.spring.initializr.generator.buildsystem.BuildItemResolver;
import io.spring.initializr.generator.project.DefaultProjectAssetGenerator;
import io.spring.initializr.generator.project.ProjectAssetGenerator;
import io.spring.initializr.generator.project.ProjectDescription;
import io.spring.initializr.generator.project.ProjectGenerationContext;
import io.spring.initializr.generator.project.ProjectGenerationException;
import io.spring.initializr.generator.project.ProjectGenerator;
import io.spring.initializr.generator.project.ResolvedProjectDescription;
import io.spring.initializr.generator.spring.build.BuildWriter;
import io.spring.initializr.generator.spring.build.MetadataBuildItemResolver;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.util.FileSystemUtils;
/**
* Invokes the project generation API. This is an intermediate layer that can consume a
* {@link ProjectRequest} and trigger project generation based on the request.
*
* @author Madhura Bhave
*/
public class ProjectGenerationInvoker {
private final ApplicationContext parentApplicationContext;
private final ApplicationEventPublisher eventPublisher;
private final ProjectRequestToDescriptionConverter converter;
private transient Map<String, List<File>> temporaryFiles = new LinkedHashMap<>();
public ProjectGenerationInvoker(ApplicationContext parentApplicationContext,
ApplicationEventPublisher eventPublisher,
ProjectRequestToDescriptionConverter converter) {
this.parentApplicationContext = parentApplicationContext;
this.eventPublisher = eventPublisher;
this.converter = converter;
}
/**
* Invokes the project generation API that generates the entire project structure for
* the specified {@link WebProjectRequest}. Returns a directory containing the
* project.
* @param request the project request
* @return the generated project structure
*/
public File invokeProjectStructureGeneration(ProjectRequest request) {
InitializrMetadata metadata = this.parentApplicationContext
.getBean(InitializrMetadataProvider.class).get();
try {
ProjectDescription projectDescription = this.converter.convert(request,
metadata);
ProjectGenerator projectGenerator = new ProjectGenerator(
(projectGenerationContext) -> customizeProjectGenerationContext(
projectGenerationContext, metadata));
Path path = projectGenerator.generate(projectDescription,
generateProject(request));
File file = path.toFile();
String name = file.getName();
addTempFile(name, file);
return file;
}
catch (ProjectGenerationException | InitializrException ex) {
publishProjectFailedEvent(request, metadata, ex);
throw ex;
}
}
private ProjectAssetGenerator<Path> generateProject(ProjectRequest request) {
return (context) -> {
Path projectDir = new DefaultProjectAssetGenerator().generate(context);
publishProjectGeneratedEvent(request, context);
return projectDir;
};
}
/**
* Invokes the project generation API that knows how to just write the build file.
* Returns a directory containing the project for the specified
* {@link WebProjectRequest}.
* @param request the project request
* @return the generated build content
*/
public byte[] invokeBuildGeneration(ProjectRequest request) {
InitializrMetadata metadata = this.parentApplicationContext
.getBean(InitializrMetadataProvider.class).get();
try {
ProjectDescription projectDescription = this.converter.convert(request,
metadata);
ProjectGenerator projectGenerator = new ProjectGenerator(
(projectGenerationContext) -> customizeProjectGenerationContext(
projectGenerationContext, metadata));
return projectGenerator.generate(projectDescription, generateBuild(request));
}
catch (ProjectGenerationException | InitializrException ex) {
publishProjectFailedEvent(request, metadata, ex);
throw ex;
}
}
private ProjectAssetGenerator<byte[]> generateBuild(ProjectRequest request) {
return (context) -> {
byte[] content = generateBuild(context);
publishProjectGeneratedEvent(request, context);
return content;
};
}
/**
* Create a file in the same directory as the given directory using the directory name
* and extension.
* @param dir the directory used to determine the path and name of the new file
* @param extension the extension to use for the new file
* @return the newly created file
*/
public File createDistributionFile(File dir, String extension) {
File download = new File(dir.getParent(), dir.getName() + extension);
addTempFile(dir.getName(), download);
return download;
}
private void addTempFile(String group, File file) {
this.temporaryFiles.computeIfAbsent(group, (key) -> new ArrayList<>()).add(file);
}
/**
* Clean all the temporary files that are related to this root directory.
* @param dir the directory to clean
* @see #createDistributionFile
*/
public void cleanTempFiles(File dir) {
List<File> tempFiles = this.temporaryFiles.remove(dir.getName());
if (!tempFiles.isEmpty()) {
tempFiles.forEach((File file) -> {
if (file.isDirectory()) {
FileSystemUtils.deleteRecursively(file);
}
else if (file.exists()) {
file.delete();
}
});
}
}
private byte[] generateBuild(ProjectGenerationContext context) throws IOException {
ResolvedProjectDescription projectDescription = context
.getBean(ResolvedProjectDescription.class);
StringWriter out = new StringWriter();
BuildWriter buildWriter = context.getBeanProvider(BuildWriter.class)
.getIfAvailable();
if (buildWriter != null) {
buildWriter.writeBuild(out);
return out.toString().getBytes();
}
else {
throw new IllegalStateException("No BuildWriter implementation found for "
+ projectDescription.getLanguage());
}
}
private void customizeProjectGenerationContext(
AnnotationConfigApplicationContext context, InitializrMetadata metadata) {
context.setParent(this.parentApplicationContext);
context.registerBean(InitializrMetadata.class, () -> metadata);
context.registerBean(BuildItemResolver.class,
() -> new MetadataBuildItemResolver(metadata));
}
private void publishProjectGeneratedEvent(ProjectRequest request,
ProjectGenerationContext context) {
Build build = context.getBeanProvider(Build.class).getIfAvailable();
InitializrMetadata metadata = context.getBean(InitializrMetadata.class);
ProjectGeneratedEvent event = new ProjectGeneratedEvent(request, metadata);
this.eventPublisher.publishEvent(event);
}
private void publishProjectFailedEvent(ProjectRequest request,
InitializrMetadata metadata, Exception cause) {
ProjectFailedEvent event = new ProjectFailedEvent(request, metadata, cause);
this.eventPublisher.publishEvent(event);
}
}

View File

@@ -0,0 +1,188 @@
/*
* 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
*
* http://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.ArrayList;
import java.util.List;
import org.springframework.util.StringUtils;
/**
* The base settings of a project request. Only these can be bound by user's input.
*
* @author Stephane Nicoll
*/
public class ProjectRequest {
private List<String> style = new ArrayList<>();
private List<String> dependencies = new ArrayList<>();
private String name;
private String type;
private String description;
private String groupId;
private String artifactId;
private String version;
private String bootVersion;
private String packaging;
private String applicationName;
private String language;
private String packageName;
private String javaVersion;
// The base directory to create in the archive - no baseDir by default
private String baseDir;
public List<String> getStyle() {
return this.style;
}
public void setStyle(List<String> style) {
this.style = style;
}
public List<String> getDependencies() {
return this.dependencies;
}
public void setDependencies(List<String> dependencies) {
this.dependencies = dependencies;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return this.type;
}
public void setType(String type) {
this.type = type;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getGroupId() {
return this.groupId;
}
public void setGroupId(String groupId) {
this.groupId = groupId;
}
public String getArtifactId() {
return this.artifactId;
}
public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
public String getVersion() {
return this.version;
}
public void setVersion(String version) {
this.version = version;
}
public String getBootVersion() {
return this.bootVersion;
}
public void setBootVersion(String bootVersion) {
this.bootVersion = bootVersion;
}
public String getPackaging() {
return this.packaging;
}
public void setPackaging(String packaging) {
this.packaging = packaging;
}
public String getApplicationName() {
return this.applicationName;
}
public void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public String getLanguage() {
return this.language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getPackageName() {
if (StringUtils.hasText(this.packageName)) {
return this.packageName;
}
if (StringUtils.hasText(this.groupId) && StringUtils.hasText(this.artifactId)) {
return getGroupId() + "." + getArtifactId();
}
return null;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getJavaVersion() {
return this.javaVersion;
}
public void setJavaVersion(String javaVersion) {
this.javaVersion = javaVersion;
}
public String getBaseDir() {
return this.baseDir;
}
public void setBaseDir(String baseDir) {
this.baseDir = baseDir;
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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
*
* http://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 io.spring.initializr.metadata.InitializrMetadata;
/**
* Event published when a {@link ProjectRequest} has been processed.
*
* @author Stephane Nicoll
* @see ProjectGeneratedEvent
* @see ProjectFailedEvent
*/
public abstract class ProjectRequestEvent {
private final ProjectRequest request;
private final InitializrMetadata metadata;
private final long timestamp;
protected ProjectRequestEvent(ProjectRequest request, InitializrMetadata metadata) {
this.request = request;
this.metadata = metadata;
this.timestamp = System.currentTimeMillis();
}
/**
* Return the {@link ProjectRequest} used to generate the project.
* @return the project request
*/
public ProjectRequest getProjectRequest() {
return this.request;
}
/**
* Return the timestamp at which the request was processed.
* @return the timestamp that the request was processed
*/
public long getTimestamp() {
return this.timestamp;
}
/**
* Return the metadata that was used to generate the project.
* @return the metadata
*/
public InitializrMetadata getMetadata() {
return this.metadata;
}
}

View File

@@ -0,0 +1,167 @@
/*
* 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
*
* http://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.stream.Collectors;
import io.spring.initializr.generator.buildsystem.BuildSystem;
import io.spring.initializr.generator.buildsystem.gradle.GradleBuildSystem;
import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
import io.spring.initializr.generator.language.Language;
import io.spring.initializr.generator.packaging.Packaging;
import io.spring.initializr.generator.project.ProjectDescription;
import io.spring.initializr.generator.spring.build.MetadataBuildItemMapper;
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.util.Version;
import io.spring.initializr.web.InvalidProjectRequestException;
import org.springframework.util.StringUtils;
/**
* Validates a {@link ProjectRequest} and creates a {@link ProjectDescription} from it.
*
* @author Madhura Bhave
*/
public class ProjectRequestToDescriptionConverter {
private static final Version VERSION_1_5_0 = Version.parse("1.5.0.RELEASE");
public ProjectDescription convert(ProjectRequest request,
InitializrMetadata metadata) {
validate(request, metadata);
ProjectDescription description = new ProjectDescription();
description.setApplicationName(getApplicationName(request, metadata));
description.setArtifactId(request.getArtifactId());
description.setBaseDirectory(request.getBaseDir());
description.setBuildSystem(getBuildSystem(request));
description.setDescription(request.getDescription());
description.setGroupId(request.getGroupId());
description.setLanguage(
Language.forId(request.getLanguage(), request.getJavaVersion()));
description.setName(request.getName());
description.setPackageName(getPackageName(request, metadata));
description.setPackaging(Packaging.forId(request.getPackaging()));
String springBootVersion = getSpringBootVersion(request, metadata);
description
.setPlatformVersion(MetadataBuildItemMapper.toVersion(springBootVersion));
getResolvedDependencies(request, springBootVersion, metadata)
.forEach((dependency) -> description.addDependency(dependency.getId(),
MetadataBuildItemMapper.toDependency(dependency)));
return description;
}
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");
}
}
}
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<String> 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 BuildSystem getBuildSystem(ProjectRequest request) {
return (request.getType().startsWith("gradle")) ? new GradleBuildSystem()
: new MavenBuildSystem();
}
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<Dependency> getResolvedDependencies(ProjectRequest request,
String springBootVersion, InitializrMetadata metadata) {
List<String> 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());
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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
*
* http://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.LinkedHashMap;
import java.util.Map;
import io.spring.initializr.metadata.InitializrMetadata;
import org.springframework.beans.BeanWrapperImpl;
/**
* A {@link ProjectRequest} with some additional information to identify the request.
*
* @author Madhura Bhave
*/
public class WebProjectRequest extends ProjectRequest {
private final Map<String, Object> parameters = new LinkedHashMap<>();
/**
* Return the additional parameters that can be used to further identify the request.
* @return the parameters
*/
public Map<String, Object> getParameters() {
return this.parameters;
}
/**
* Initialize the state of this request with defaults defined in the
* {@link InitializrMetadata metadata}.
* @param metadata the metadata to use
*/
public void initialize(InitializrMetadata metadata) {
BeanWrapperImpl bean = new BeanWrapperImpl(this);
metadata.defaults().forEach((key, value) -> {
if (bean.isWritableProperty(key)) {
// We want to be able to infer a package name if none has been
// explicitly set
if (!key.equals("packageName")) {
bean.setPropertyValue(key, value);
}
}
});
}
}

View File

@@ -28,11 +28,11 @@ import java.util.List;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.spring.initializr.generator.spring.test.ProjectAssert;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataBuilder;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.metadata.InitializrProperties;
import io.spring.initializr.test.generator.ProjectAssert;
import io.spring.initializr.web.AbstractInitializrIntegrationTests.Config;
import io.spring.initializr.web.mapper.InitializrMetadataVersion;
import io.spring.initializr.web.support.DefaultInitializrMetadataProvider;

View File

@@ -16,13 +16,13 @@
package io.spring.initializr.web.autoconfigure;
import io.spring.initializr.generator.ProjectGenerator;
import io.spring.initializr.generator.ProjectRequestResolver;
import io.spring.initializr.generator.ProjectResourceLocator;
import io.spring.initializr.metadata.DependencyMetadataProvider;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.util.TemplateRenderer;
import io.spring.initializr.web.ProjectResourceLocator;
import io.spring.initializr.web.project.MainController;
import io.spring.initializr.web.project.ProjectGenerationInvoker;
import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter;
import io.spring.initializr.web.ui.UiController;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
@@ -58,22 +58,6 @@ class InitializrAutoConfigurationTests {
.withConfiguration(AutoConfigurations.of(RestTemplateAutoConfiguration.class,
JacksonAutoConfiguration.class, InitializrAutoConfiguration.class));
@Test
void autoConfigRegistersProjectGenerator() {
this.contextRunner.run(
(context) -> assertThat(context).hasSingleBean(ProjectGenerator.class));
}
@Test
void autoConfigWhenProjectGeneratorBeanPresentDoesNotRegisterProjectGenerator() {
this.contextRunner
.withUserConfiguration(CustomProjectGeneratorConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(ProjectGenerator.class);
assertThat(context).hasBean("testProjectGenerator");
});
}
@Test
void autoConfigRegistersTemplateRenderer() {
this.contextRunner.run(
@@ -90,22 +74,6 @@ class InitializrAutoConfigurationTests {
});
}
@Test
void autoConfigRegistersProjectRequestResolver() {
this.contextRunner.run((context) -> assertThat(context)
.hasSingleBean(ProjectRequestResolver.class));
}
@Test
void autoConfigWhenProjectRequestResolverBeanPresentDoesNotRegisterProjectRequestResolver() {
this.contextRunner
.withUserConfiguration(CustomProjectRequestResolverConfiguration.class)
.run((context) -> {
assertThat(context).hasSingleBean(ProjectRequestResolver.class);
assertThat(context).hasBean("testProjectRequestResolver");
});
}
@Test
void autoConfigRegistersProjectResourceLocator() {
this.contextRunner.run((context) -> assertThat(context)
@@ -180,6 +148,8 @@ class InitializrAutoConfigurationTests {
InitializrAutoConfiguration.class));
webContextRunner.run((context) -> {
assertThat(context).hasSingleBean(InitializrWebConfig.class);
assertThat(context).hasSingleBean(ProjectGenerationInvoker.class);
assertThat(context).hasSingleBean(ProjectRequestToDescriptionConverter.class);
assertThat(context).hasSingleBean(MainController.class);
assertThat(context).hasSingleBean(UiController.class);
});
@@ -221,16 +191,6 @@ class InitializrAutoConfigurationTests {
}
@Configuration
static class CustomProjectGeneratorConfiguration {
@Bean
public ProjectGenerator testProjectGenerator() {
return Mockito.mock(ProjectGenerator.class);
}
}
@Configuration
static class CustomTemplateRendererConfiguration {
@@ -241,16 +201,6 @@ class InitializrAutoConfigurationTests {
}
@Configuration
static class CustomProjectRequestResolverConfiguration {
@Bean
public ProjectRequestResolver testProjectRequestResolver() {
return Mockito.mock(ProjectRequestResolver.class);
}
}
@Configuration
static class CustomProjectResourceLocatorConfiguration {

View File

@@ -16,7 +16,7 @@
package io.spring.initializr.web.project;
import io.spring.initializr.test.generator.PomAssert;
import io.spring.initializr.generator.spring.test.build.PomAssert;
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests;
import org.junit.jupiter.api.Test;

View File

@@ -16,7 +16,7 @@
package io.spring.initializr.web.project;
import io.spring.initializr.test.generator.PomAssert;
import io.spring.initializr.generator.spring.test.build.PomAssert;
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests;
import org.junit.jupiter.api.Test;
@@ -39,8 +39,8 @@ class MainControllerDefaultsIntegrationTests
String.class);
PomAssert pomAssert = new PomAssert(content);
pomAssert.hasGroupId("org.foo").hasArtifactId("foo-bar")
.hasVersion("1.2.4-SNAPSHOT").hasPackaging("jar").hasName("FooBar")
.hasDescription("FooBar Project");
.hasVersion("1.2.4-SNAPSHOT").doesNotHaveNode("/project/packaging")
.hasName("FooBar").hasDescription("FooBar Project");
}
@Test

View File

@@ -18,7 +18,7 @@ package io.spring.initializr.web.project;
import java.net.URI;
import io.spring.initializr.test.generator.ProjectAssert;
import io.spring.initializr.generator.spring.test.ProjectAssert;
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests;
import org.junit.jupiter.api.Test;

View File

@@ -69,7 +69,7 @@ class MainControllerIntegrationTests
Dependency biz = Dependency.create("org.acme", "biz", "1.3.5", "runtime");
downloadTgz("/starter.tgz?style=org.acme:biz&bootVersion=2.2.1.RELEASE")
.isJavaProject().isMavenProject().hasStaticAndTemplatesResources(false)
.pomAssert().hasDependenciesCount(2).hasDependency(biz);
.pomAssert().hasDependenciesCount(3).hasDependency(biz);
}
@Test

View File

@@ -16,55 +16,61 @@
package io.spring.initializr.web.project;
import io.spring.initializr.generator.ProjectRequest;
import io.spring.initializr.generator.ProjectRequestPostProcessor;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.generator.language.java.JavaLanguage;
import io.spring.initializr.generator.project.ProjectDescription;
import io.spring.initializr.generator.project.ProjectDescriptionCustomizer;
import io.spring.initializr.generator.version.Version;
import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests;
import io.spring.initializr.web.project.ProjectGenerationPostProcessorTests.ProjectRequestPostProcessorConfiguration;
import io.spring.initializr.web.project.ProjectGenerationDescriptionCustomizerTests.ProjectDescriptionCustomizerConfiguration;
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.core.annotation.Order;
import org.springframework.test.context.ActiveProfiles;
@ActiveProfiles("test-default")
@Import(ProjectRequestPostProcessorConfiguration.class)
class ProjectGenerationPostProcessorTests
@Import(ProjectDescriptionCustomizerConfiguration.class)
class ProjectGenerationDescriptionCustomizerTests
extends AbstractInitializrControllerIntegrationTests {
@Test
void postProcessorsInvoked() {
void projectDescriptionCustomizersAreInvoked() {
downloadZip("/starter.zip?bootVersion=2.0.4.RELEASE&javaVersion=1.8")
.isJavaProject().isMavenProject().pomAssert()
.hasSpringBootParent("2.2.3.RELEASE").hasProperty("java.version", "1.7");
}
@Configuration
static class ProjectRequestPostProcessorConfiguration {
static class ProjectDescriptionCustomizerConfiguration {
@Bean
@Order(2)
ProjectRequestPostProcessor secondPostProcessor() {
return new ProjectRequestPostProcessor() {
public ProjectDescriptionCustomizer secondPostProcessor() {
return new ProjectDescriptionCustomizer() {
@Override
public void postProcessBeforeResolution(ProjectRequest request,
InitializrMetadata metadata) {
request.setJavaVersion("1.7");
public void customize(ProjectDescription description) {
description.setLanguage(new JavaLanguage("1.7"));
}
@Override
public int getOrder() {
return 2;
}
};
}
@Bean
@Order(1)
ProjectRequestPostProcessor firstPostProcessor() {
return new ProjectRequestPostProcessor() {
public ProjectDescriptionCustomizer firstPostProcessor() {
return new ProjectDescriptionCustomizer() {
@Override
public void postProcessBeforeResolution(ProjectRequest request,
InitializrMetadata metadata) {
request.setJavaVersion("1.2");
request.setBootVersion("2.2.3.RELEASE");
public void customize(ProjectDescription description) {
description.setLanguage(new JavaLanguage("1.2"));
description.setPlatformVersion(Version.parse("2.2.3.RELEASE"));
}
@Override
public int getOrder() {
return 1;
}
};
}

View File

@@ -0,0 +1,253 @@
/*
* 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
*
* http://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.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.io.SimpleIndentStrategy;
import io.spring.initializr.generator.project.ProjectDirectoryFactory;
import io.spring.initializr.generator.spring.test.ProjectAssert;
import io.spring.initializr.generator.spring.test.build.GradleBuildAssert;
import io.spring.initializr.generator.spring.test.build.PomAssert;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.metadata.InitializrMetadataProvider;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentMatcher;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ProjectGenerationInvoker}.
*
* @author Madhura Bhave
*/
public class ProjectGenerationInvokerTests {
private static final InitializrMetadata metadata = InitializrMetadataTestBuilder
.withDefaults().build();
private ProjectGenerationInvoker invoker;
private AnnotationConfigApplicationContext context;
private final ApplicationEventPublisher eventPublisher = mock(
ApplicationEventPublisher.class);
@BeforeEach
void setup() {
setupContext();
ProjectRequestToDescriptionConverter converter = new ProjectRequestToDescriptionConverter();
this.invoker = new ProjectGenerationInvoker(this.context, this.eventPublisher,
converter);
}
@AfterEach
void cleanup() {
if (this.context != null) {
this.context.close();
}
}
@Test
@SuppressWarnings("unchecked")
void invokeProjectStructureGeneration() {
WebProjectRequest request = new WebProjectRequest();
request.setType("maven-project");
request.initialize(metadata);
File file = this.invoker.invokeProjectStructureGeneration(request);
new ProjectAssert(file).isJavaProject();
Map<String, List<File>> tempFiles = (Map<String, List<File>>) ReflectionTestUtils
.getField(this.invoker, "temporaryFiles");
assertThat(tempFiles.get(file.getName())).contains(file);
verifyProjectSuccessfulEventFor(request);
}
@Test
void invokeProjectStructureGenerationFailureShouldPublishFailureEvent() {
WebProjectRequest request = new WebProjectRequest();
request.initialize(metadata);
request.setType("foo-bar");
try {
this.invoker.invokeProjectStructureGeneration(request);
}
catch (Exception ex) {
verifyProjectFailedEventFor(request, ex);
}
}
@Test
void invokeBuildGenerationForMavenBuild() {
WebProjectRequest request = new WebProjectRequest();
request.setType("maven-project");
request.initialize(metadata);
byte[] bytes = this.invoker.invokeBuildGeneration(request);
String content = new String(bytes);
new PomAssert(content).hasGroupId(request.getGroupId())
.hasArtifactId(request.getArtifactId()).hasVersion(request.getVersion())
.doesNotHaveNode("/project/packaging").hasName(request.getName())
.hasDescription(request.getDescription())
.hasJavaVersion(request.getJavaVersion())
.hasSpringBootParent(request.getBootVersion());
verifyProjectSuccessfulEventFor(request);
}
@Test
void invokeBuildGenerationForGradleBuild() {
WebProjectRequest request = new WebProjectRequest();
request.initialize(metadata);
request.setType("gradle-project");
byte[] bytes = this.invoker.invokeBuildGeneration(request);
String content = new String(bytes);
new GradleBuildAssert(content).hasVersion(request.getVersion())
.hasSpringBootPlugin(request.getBootVersion())
.hasJavaVersion(request.getJavaVersion());
verifyProjectSuccessfulEventFor(request);
}
@Test
void invokeBuildGenerationFailureShouldPublishFailureEvent() {
WebProjectRequest request = new WebProjectRequest();
request.initialize(metadata);
request.setType("foo-bar");
try {
this.invoker.invokeBuildGeneration(request);
}
catch (Exception ex) {
verifyProjectFailedEventFor(request, ex);
}
}
@Test
@SuppressWarnings("unchecked")
void createDistributionDirectory(@TempDir Path tempDir) {
ProjectRequest request = new ProjectRequest();
request.setType("gradle-project");
File dir = tempDir.toFile();
File distributionFile = this.invoker.createDistributionFile(dir, ".zip");
assertThat(distributionFile.toString()).isEqualTo(dir.toString() + ".zip");
Map<String, List<File>> tempFiles = (Map<String, List<File>>) ReflectionTestUtils
.getField(this.invoker, "temporaryFiles");
assertThat(tempFiles.get(dir.getName())).contains(distributionFile);
}
@Test
void cleanupTempFilesShouldOnlyCleanupSpecifiedDir() {
WebProjectRequest request = new WebProjectRequest();
request.initialize(metadata);
request.setType("gradle-project");
File file = this.invoker.invokeProjectStructureGeneration(request);
this.invoker.cleanTempFiles(file);
assertThat(file.listFiles()).isNull();
}
private void setupContext() {
InitializrMetadataProvider metadataProvider = mock(
InitializrMetadataProvider.class);
given(metadataProvider.get())
.willReturn(InitializrMetadataTestBuilder.withDefaults().build());
this.context = new AnnotationConfigApplicationContext();
this.context.register(TestConfiguration.class);
this.context.refresh();
}
protected void verifyProjectSuccessfulEventFor(ProjectRequest request) {
verify(this.eventPublisher, times(1))
.publishEvent(argThat(new ProjectGeneratedEventMatcher(request)));
}
protected void verifyProjectFailedEventFor(ProjectRequest request, Exception ex) {
verify(this.eventPublisher, times(1))
.publishEvent(argThat(new ProjectFailedEventMatcher(request, ex)));
}
@Configuration
static class TestConfiguration {
@Bean
public IndentingWriterFactory factory() {
return IndentingWriterFactory.create(new SimpleIndentStrategy("\t"));
}
@Bean
public ProjectDirectoryFactory projectDirectoryFactory() {
return (description) -> Files.createTempDirectory("project-");
}
@Bean
public InitializrMetadataProvider initializrMetadataProvider() {
return () -> metadata;
}
}
private static class ProjectFailedEventMatcher
implements ArgumentMatcher<ProjectFailedEvent> {
private final ProjectRequest request;
private final Exception cause;
ProjectFailedEventMatcher(ProjectRequest request, Exception cause) {
this.request = request;
this.cause = cause;
}
@Override
public boolean matches(ProjectFailedEvent event) {
return this.request.equals(event.getProjectRequest())
&& this.cause.equals(event.getCause());
}
}
private static class ProjectGeneratedEventMatcher
implements ArgumentMatcher<ProjectGeneratedEvent> {
private final ProjectRequest request;
ProjectGeneratedEventMatcher(ProjectRequest request) {
this.request = request;
}
@Override
public boolean matches(ProjectGeneratedEvent event) {
return this.request.equals(event.getProjectRequest());
}
}
}

View File

@@ -21,7 +21,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
import io.spring.initializr.test.generator.ProjectAssert;
import io.spring.initializr.generator.spring.test.ProjectAssert;
import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assumptions;
@@ -208,7 +208,7 @@ class ProjectGenerationSmokeTests extends AbstractFullStackInitializrIntegration
projectAssert.hasBaseDir("demo").isMavenProject().isJavaWarProject().pomAssert()
.hasPackaging("war").hasDependenciesCount(3)
.hasSpringBootStarterDependency("web") // Added with war packaging
.hasSpringBootStarterTomcat().hasSpringBootStarterTest();
.hasSpringBootStarterDependency("tomcat").hasSpringBootStarterTest();
}
@Test

View File

@@ -0,0 +1,177 @@
/*
* 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
*
* http://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.Collections;
import io.spring.initializr.generator.project.ProjectDescription;
import io.spring.initializr.generator.version.Version;
import io.spring.initializr.metadata.InitializrMetadata;
import io.spring.initializr.test.metadata.InitializrMetadataTestBuilder;
import io.spring.initializr.web.InvalidProjectRequestException;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
* Tests for {@link ProjectRequestToDescriptionConverter}.
*
* @author Madhura Bhave
*/
public class ProjectRequestToDescriptionConverterTests {
private InitializrMetadata metadata = InitializrMetadataTestBuilder.withDefaults()
.build();
private final ProjectRequestToDescriptionConverter converter = new ProjectRequestToDescriptionConverter();
@Test
public void convertWhenTypeIsInvalidShouldThrowException() {
ProjectRequest request = getProjectRequest();
request.setType("foo-build");
assertThatExceptionOfType(InvalidProjectRequestException.class)
.isThrownBy(() -> this.converter.convert(request, this.metadata))
.withMessage("Unknown type 'foo-build' check project metadata");
}
@Test
void convertWhenSpringBootVersionInvalidShouldThrowException() {
ProjectRequest request = getProjectRequest();
request.setBootVersion("1.2.3.M4");
assertThatExceptionOfType(InvalidProjectRequestException.class)
.isThrownBy(() -> this.converter.convert(request, this.metadata))
.withMessage(
"Invalid Spring Boot version 1.2.3.M4 must be 1.5.0 or higher");
}
@Test
public void convertWhenPackagingIsInvalidShouldThrowException() {
ProjectRequest request = getProjectRequest();
request.setPackaging("star");
assertThatExceptionOfType(InvalidProjectRequestException.class)
.isThrownBy(() -> this.converter.convert(request, this.metadata))
.withMessage("Unknown packaging 'star' check project metadata");
}
@Test
public void convertWhenLanguageIsInvalidShouldThrowException() {
ProjectRequest request = getProjectRequest();
request.setLanguage("english");
assertThatExceptionOfType(InvalidProjectRequestException.class)
.isThrownBy(() -> this.converter.convert(request, this.metadata))
.withMessage("Unknown language 'english' check project metadata");
}
@Test
void convertWhenDependencyNotPresentShouldThrowException() {
ProjectRequest request = getProjectRequest();
request.setDependencies(Collections.singletonList("invalid"));
assertThatExceptionOfType(InvalidProjectRequestException.class)
.isThrownBy(() -> this.converter.convert(request, this.metadata))
.withMessage("Unknown dependency 'invalid' check project metadata");
}
@Test
void convertShouldSetApplicationNameForProjectDescriptionFromRequestWhenPresent() {
ProjectRequest request = getProjectRequest();
request.setApplicationName("MyApplication");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getApplicationName()).isEqualTo("MyApplication");
}
@Test
void convertShouldSetApplicationNameForProjectDescriptionUsingNameWhenAbsentFromRequest() {
ProjectRequest request = getProjectRequest();
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getApplicationName()).isEqualTo("DemoApplication");
}
@Test
void convertShouldSetGroupIdAndArtifactIdFromRequest() {
ProjectRequest request = getProjectRequest();
request.setArtifactId("foo");
request.setGroupId("com.example");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getGroupId()).isEqualTo("com.example");
assertThat(description.getArtifactId()).isEqualTo("foo");
}
@Test
void convertShouldSetBaseDirectoryFromRequest() {
ProjectRequest request = getProjectRequest();
request.setBaseDir("my-path");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getBaseDirectory()).isEqualTo("my-path");
}
@Test
void convertShouldSetBuildSystemFromRequestType() {
ProjectRequest request = getProjectRequest();
request.setType("gradle-build");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getBuildSystem().id()).isEqualTo("gradle");
}
@Test
void convertShouldSetDescriptionFromRequest() {
ProjectRequest request = getProjectRequest();
request.setDescription("This is my demo project");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getDescription()).isEqualTo("This is my demo project");
}
@Test
void convertShouldSetPackagingFromRequest() {
ProjectRequest request = getProjectRequest();
request.setPackaging("war");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getPackaging().id()).isEqualTo("war");
}
@Test
void convertShouldSetPlatformVersionFromRequest() {
ProjectRequest request = getProjectRequest();
request.setBootVersion("2.0.3");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getPlatformVersion()).isEqualTo(Version.parse("2.0.3"));
}
@Test
void convertShouldUseDefaultPlatformVersionFromMetadata() {
ProjectRequest request = getProjectRequest();
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getPlatformVersion())
.isEqualTo(Version.parse("2.1.1.RELEASE"));
}
@Test
void convertShouldSetLanguageForProjectDescriptionFromRequest() {
ProjectRequest request = getProjectRequest();
request.setJavaVersion("1.8");
ProjectDescription description = this.converter.convert(request, this.metadata);
assertThat(description.getLanguage().id()).isEqualTo("java");
assertThat(description.getLanguage().jvmVersion()).isEqualTo("1.8");
}
private ProjectRequest getProjectRequest() {
WebProjectRequest request = new WebProjectRequest();
request.initialize(this.metadata);
return request;
}
}