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 b08c086b..401c59cd 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 @@ -33,7 +33,10 @@ 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.web.project.MainController; +import io.spring.initializr.web.controller.CommandLineMetadataController; +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.ProjectGenerationInvoker; import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter; import io.spring.initializr.web.support.DefaultDependencyMetadataProvider; @@ -139,11 +142,29 @@ public class InitializrAutoConfiguration { @Bean @ConditionalOnMissingBean - MainController initializrMainController(InitializrMetadataProvider metadataProvider, - TemplateRenderer templateRenderer, DependencyMetadataProvider dependencyMetadataProvider, + ProjectGenerationController projectGenerationController(InitializrMetadataProvider metadataProvider, ProjectGenerationInvoker projectGenerationInvoker) { - return new MainController(metadataProvider, templateRenderer, dependencyMetadataProvider, - projectGenerationInvoker); + return new ProjectGenerationController(metadataProvider, projectGenerationInvoker); + } + + @Bean + @ConditionalOnMissingBean + ProjectMetadataController projectMetadataController(InitializrMetadataProvider metadataProvider, + DependencyMetadataProvider dependencyMetadataProvider) { + return new ProjectMetadataController(metadataProvider, dependencyMetadataProvider); + } + + @Bean + @ConditionalOnMissingBean + CommandLineMetadataController commandLineMetadataController(InitializrMetadataProvider metadataProvider, + TemplateRenderer templateRenderer) { + return new CommandLineMetadataController(metadataProvider, templateRenderer); + } + + @Bean + @ConditionalOnMissingBean + SpringCliDistributionController cliDistributionController(InitializrMetadataProvider metadataProvider) { + return new SpringCliDistributionController(metadataProvider); } @Bean diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/AbstractInitializrController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/AbstractMetadataController.java similarity index 72% rename from initializr-web/src/main/java/io/spring/initializr/web/project/AbstractInitializrController.java rename to initializr-web/src/main/java/io/spring/initializr/web/controller/AbstractMetadataController.java index 04453e9e..a37c425d 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/AbstractInitializrController.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/AbstractMetadataController.java @@ -14,16 +14,13 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; -import java.io.IOException; - -import javax.servlet.http.HttpServletResponse; +import java.nio.charset.StandardCharsets; import io.spring.initializr.metadata.InitializrMetadataProvider; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.util.DigestUtils; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; /** @@ -31,30 +28,16 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; * * @author Stephane Nicoll */ -public abstract class AbstractInitializrController { +public abstract class AbstractMetadataController { protected final InitializrMetadataProvider metadataProvider; private Boolean forceSsl; - protected AbstractInitializrController(InitializrMetadataProvider metadataProvider) { + protected AbstractMetadataController(InitializrMetadataProvider metadataProvider) { this.metadataProvider = metadataProvider; } - public boolean isForceSsl() { - if (this.forceSsl == null) { - this.forceSsl = this.metadataProvider.get().getConfiguration().getEnv().isForceSsl(); - } - return this.forceSsl; - - } - - @ExceptionHandler - public void invalidProjectRequest(HttpServletResponse response, InvalidProjectRequestException ex) - throws IOException { - response.sendError(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); - } - /** * Generate a full URL of the service, mostly for use in templates. * @return the app URL @@ -68,4 +51,18 @@ public abstract class AbstractInitializrController { return builder.build().toString(); } + protected String createUniqueId(String content) { + StringBuilder builder = new StringBuilder(); + DigestUtils.appendMd5DigestAsHex(content.getBytes(StandardCharsets.UTF_8), builder); + return builder.toString(); + } + + private boolean isForceSsl() { + if (this.forceSsl == null) { + this.forceSsl = this.metadataProvider.get().getConfiguration().getEnv().isForceSsl(); + } + return this.forceSsl; + + } + } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/CommandLineMetadataController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/CommandLineMetadataController.java new file mode 100644 index 00000000..4f7e9d0d --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/CommandLineMetadataController.java @@ -0,0 +1,81 @@ +/* + * 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.io.IOException; + +import io.spring.initializr.generator.io.template.TemplateRenderer; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.support.Agent; +import io.spring.initializr.web.support.Agent.AgentId; +import io.spring.initializr.web.support.CommandLineHelpGenerator; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.ResponseEntity.BodyBuilder; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * {@link Controller} that handles assistance for CLI support using a + * {@link CommandLineHelpGenerator}. + * + * @author Stephane Nicoll + */ +@Controller +public class CommandLineMetadataController extends AbstractMetadataController { + + private final CommandLineHelpGenerator commandLineHelpGenerator; + + public CommandLineMetadataController(InitializrMetadataProvider metadataProvider, + TemplateRenderer templateRenderer) { + super(metadataProvider); + this.commandLineHelpGenerator = new CommandLineHelpGenerator(templateRenderer); + } + + @RequestMapping(path = "/", produces = "text/plain") + public ResponseEntity serviceCapabilitiesText( + @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) throws IOException { + String appUrl = generateAppUrl(); + InitializrMetadata metadata = this.metadataProvider.get(); + + BodyBuilder builder = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN); + if (userAgent != null) { + Agent agent = Agent.fromUserAgent(userAgent); + if (agent != null) { + if (AgentId.CURL.equals(agent.getId())) { + String content = this.commandLineHelpGenerator.generateCurlCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + if (AgentId.HTTPIE.equals(agent.getId())) { + String content = this.commandLineHelpGenerator.generateHttpieCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + if (AgentId.SPRING_BOOT_CLI.equals(agent.getId())) { + String content = this.commandLineHelpGenerator.generateSpringBootCliCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + } + } + String content = this.commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl); + return builder.eTag(createUniqueId(content)).body(content); + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java similarity index 53% rename from initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java rename to initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java index 070a16f0..869d1dab 100644 --- a/initializr-web/src/main/java/io/spring/initializr/web/project/MainController.java +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectGenerationController.java @@ -14,39 +14,31 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; +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.io.template.TemplateRenderer; import io.spring.initializr.generator.project.ProjectDescription; -import io.spring.initializr.generator.version.Version; -import io.spring.initializr.metadata.DependencyMetadata; -import io.spring.initializr.metadata.DependencyMetadataProvider; -import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataProvider; -import io.spring.initializr.web.mapper.DependencyMetadataV21JsonMapper; -import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper; -import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper; -import io.spring.initializr.web.mapper.InitializrMetadataV2JsonMapper; -import io.spring.initializr.web.mapper.InitializrMetadataVersion; -import io.spring.initializr.web.support.Agent; -import io.spring.initializr.web.support.Agent.AgentId; -import io.spring.initializr.web.support.CommandLineHelpGenerator; +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; @@ -58,48 +50,33 @@ import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.http.CacheControl; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.http.ResponseEntity.BodyBuilder; import org.springframework.stereotype.Controller; -import org.springframework.util.DigestUtils; import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; /** - * The main initializr controller provides access to the configured metadata and serves as - * a central endpoint to generate projects or build files. + * {@link Controller} that provides endpoints for project generation. * - * @author Dave Syer * @author Stephane Nicoll */ @Controller -public class MainController extends AbstractInitializrController { +public class ProjectGenerationController { - private static final Log logger = LogFactory.getLog(MainController.class); + private static final Log logger = LogFactory.getLog(ProjectGenerationController.class); - /** - * HAL JSON content type. - */ - public static final MediaType HAL_JSON_CONTENT_TYPE = MediaType.parseMediaType("application/hal+json"); - - private final DependencyMetadataProvider dependencyMetadataProvider; - - private final CommandLineHelpGenerator commandLineHelpGenerator; + private final InitializrMetadataProvider metadataProvider; private final ProjectGenerationInvoker projectGenerationInvoker; - public MainController(InitializrMetadataProvider metadataProvider, TemplateRenderer templateRenderer, - DependencyMetadataProvider dependencyMetadataProvider, ProjectGenerationInvoker projectGenerationInvoker) { - super(metadataProvider); - this.dependencyMetadataProvider = dependencyMetadataProvider; - this.commandLineHelpGenerator = new CommandLineHelpGenerator(templateRenderer); + public ProjectGenerationController(InitializrMetadataProvider metadataProvider, + ProjectGenerationInvoker projectGenerationInvoker) { + this.metadataProvider = metadataProvider; this.projectGenerationInvoker = projectGenerationInvoker; } @@ -111,101 +88,10 @@ public class MainController extends AbstractInitializrController { return request; } - @RequestMapping(path = "/metadata/config", produces = "application/json") - @ResponseBody - public InitializrMetadata config() { - return this.metadataProvider.get(); - } - - @RequestMapping(path = "/", produces = "text/plain") - public ResponseEntity serviceCapabilitiesText( - @RequestHeader(value = HttpHeaders.USER_AGENT, required = false) String userAgent) throws IOException { - String appUrl = generateAppUrl(); - InitializrMetadata metadata = this.metadataProvider.get(); - - BodyBuilder builder = ResponseEntity.ok().contentType(MediaType.TEXT_PLAIN); - if (userAgent != null) { - Agent agent = Agent.fromUserAgent(userAgent); - if (agent != null) { - if (AgentId.CURL.equals(agent.getId())) { - String content = this.commandLineHelpGenerator.generateCurlCapabilities(metadata, appUrl); - return builder.eTag(createUniqueId(content)).body(content); - } - if (AgentId.HTTPIE.equals(agent.getId())) { - String content = this.commandLineHelpGenerator.generateHttpieCapabilities(metadata, appUrl); - return builder.eTag(createUniqueId(content)).body(content); - } - if (AgentId.SPRING_BOOT_CLI.equals(agent.getId())) { - String content = this.commandLineHelpGenerator.generateSpringBootCliCapabilities(metadata, appUrl); - return builder.eTag(createUniqueId(content)).body(content); - } - } - } - String content = this.commandLineHelpGenerator.generateGenericCapabilities(metadata, appUrl); - return builder.eTag(createUniqueId(content)).body(content); - } - - @RequestMapping(path = { "/", "/metadata/client" }, produces = "application/hal+json") - public ResponseEntity serviceCapabilitiesHal() { - return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, HAL_JSON_CONTENT_TYPE); - } - - @RequestMapping(path = { "/", "/metadata/client" }, - produces = { "application/vnd.initializr.v2.1+json", "application/json" }) - public ResponseEntity serviceCapabilitiesV21() { - return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1); - } - - @RequestMapping(path = { "/", "/metadata/client" }, produces = "application/vnd.initializr.v2+json") - public ResponseEntity serviceCapabilitiesV2() { - return serviceCapabilitiesFor(InitializrMetadataVersion.V2); - } - - private ResponseEntity serviceCapabilitiesFor(InitializrMetadataVersion version) { - return serviceCapabilitiesFor(version, version.getMediaType()); - } - - private ResponseEntity serviceCapabilitiesFor(InitializrMetadataVersion version, MediaType contentType) { - String appUrl = generateAppUrl(); - String content = getJsonMapper(version).write(this.metadataProvider.get(), appUrl); - return ResponseEntity.ok().contentType(contentType).eTag(createUniqueId(content)).varyBy("Accept") - .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content); - } - - private static InitializrMetadataJsonMapper getJsonMapper(InitializrMetadataVersion version) { - switch (version) { - case V2: - return new InitializrMetadataV2JsonMapper(); - default: - return new InitializrMetadataV21JsonMapper(); - } - } - - @RequestMapping(path = "/dependencies", produces = { "application/vnd.initializr.v2.1+json", "application/json" }) - public ResponseEntity dependenciesV21(@RequestParam(required = false) String bootVersion) { - return dependenciesFor(InitializrMetadataVersion.V2_1, bootVersion); - } - - private ResponseEntity dependenciesFor(InitializrMetadataVersion version, String bootVersion) { - InitializrMetadata metadata = this.metadataProvider.get(); - Version v = (bootVersion != null) ? Version.parse(bootVersion) - : Version.parse(metadata.getBootVersions().getDefault().getId()); - DependencyMetadata dependencyMetadata = this.dependencyMetadataProvider.get(metadata, v); - String content = new DependencyMetadataV21JsonMapper().write(dependencyMetadata); - return ResponseEntity.ok().contentType(version.getMediaType()).eTag(createUniqueId(content)) - .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content); - } - - @RequestMapping(path = { "/spring", "/spring.zip" }) - public String spring() { - String url = this.metadataProvider.get().createCliDistributionURl("zip"); - return "redirect:" + url; - } - - @RequestMapping(path = { "/spring.tar.gz", "spring.tgz" }) - public String springTgz() { - String url = this.metadataProvider.get().createCliDistributionURl("tar.gz"); - return "redirect:" + url; + @ExceptionHandler + public void invalidProjectRequest(HttpServletResponse response, InvalidProjectRequestException ex) + throws IOException { + response.sendError(HttpStatus.BAD_REQUEST.value(), ex.getMessage()); } @RequestMapping(path = { "/pom", "/pom.xml" }) @@ -229,7 +115,7 @@ public class MainController extends AbstractInitializrController { public ResponseEntity springZip(ProjectRequest request) throws IOException { ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); Path archive = createArchive(result, "zip", ZipArchiveOutputStream::new, ZipArchiveEntry::new, - (entry, mode) -> entry.setUnixMode(mode)); + ZipArchiveEntry::setUnixMode); return upload(archive, result.getRootDirectory(), generateFileName(request, "zip"), "application/zip"); } @@ -238,7 +124,7 @@ public class MainController extends AbstractInitializrController { public ResponseEntity springTgz(ProjectRequest request) throws IOException { ProjectGenerationResult result = this.projectGenerationInvoker.invokeProjectStructureGeneration(request); Path archive = createArchive(result, "tar.gz", this::createTarArchiveOutputStream, TarArchiveEntry::new, - (entry, mode) -> entry.setMode(mode)); + TarArchiveEntry::setMode); return upload(archive, result.getRootDirectory(), generateFileName(request, "tar.gz"), "application/x-compress"); } @@ -327,10 +213,4 @@ public class MainController extends AbstractInitializrController { .header("Content-Disposition", contentDispositionValue).body(content); } - private String createUniqueId(String content) { - StringBuilder builder = new StringBuilder(); - DigestUtils.appendMd5DigestAsHex(content.getBytes(StandardCharsets.UTF_8), builder); - return builder.toString(); - } - } diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java new file mode 100644 index 00000000..39f91d8b --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/ProjectMetadataController.java @@ -0,0 +1,118 @@ +/* + * 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.concurrent.TimeUnit; + +import io.spring.initializr.generator.version.Version; +import io.spring.initializr.metadata.DependencyMetadata; +import io.spring.initializr.metadata.DependencyMetadataProvider; +import io.spring.initializr.metadata.InitializrMetadata; +import io.spring.initializr.metadata.InitializrMetadataProvider; +import io.spring.initializr.web.mapper.DependencyMetadataV21JsonMapper; +import io.spring.initializr.web.mapper.InitializrMetadataJsonMapper; +import io.spring.initializr.web.mapper.InitializrMetadataV21JsonMapper; +import io.spring.initializr.web.mapper.InitializrMetadataV2JsonMapper; +import io.spring.initializr.web.mapper.InitializrMetadataVersion; + +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * {@link Controller} that exposes metadata and service configuration. + * + * @author Stephane Nicoll + */ +@Controller +public class ProjectMetadataController extends AbstractMetadataController { + + /** + * HAL JSON content type. + */ + public static final MediaType HAL_JSON_CONTENT_TYPE = MediaType.parseMediaType("application/hal+json"); + + private final DependencyMetadataProvider dependencyMetadataProvider; + + public ProjectMetadataController(InitializrMetadataProvider metadataProvider, + DependencyMetadataProvider dependencyMetadataProvider) { + super(metadataProvider); + this.dependencyMetadataProvider = dependencyMetadataProvider; + } + + @RequestMapping(path = "/metadata/config", produces = "application/json") + @ResponseBody + public InitializrMetadata config() { + return this.metadataProvider.get(); + } + + @RequestMapping(path = { "/", "/metadata/client" }, produces = "application/hal+json") + public ResponseEntity serviceCapabilitiesHal() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1, HAL_JSON_CONTENT_TYPE); + } + + @RequestMapping(path = { "/", "/metadata/client" }, + produces = { "application/vnd.initializr.v2.1+json", "application/json" }) + public ResponseEntity serviceCapabilitiesV21() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2_1); + } + + @RequestMapping(path = { "/", "/metadata/client" }, produces = "application/vnd.initializr.v2+json") + public ResponseEntity serviceCapabilitiesV2() { + return serviceCapabilitiesFor(InitializrMetadataVersion.V2); + } + + @RequestMapping(path = "/dependencies", produces = { "application/vnd.initializr.v2.1+json", "application/json" }) + public ResponseEntity dependenciesV21(@RequestParam(required = false) String bootVersion) { + return dependenciesFor(InitializrMetadataVersion.V2_1, bootVersion); + } + + private ResponseEntity dependenciesFor(InitializrMetadataVersion version, String bootVersion) { + InitializrMetadata metadata = this.metadataProvider.get(); + Version v = (bootVersion != null) ? Version.parse(bootVersion) + : Version.parse(metadata.getBootVersions().getDefault().getId()); + DependencyMetadata dependencyMetadata = this.dependencyMetadataProvider.get(metadata, v); + String content = new DependencyMetadataV21JsonMapper().write(dependencyMetadata); + return ResponseEntity.ok().contentType(version.getMediaType()).eTag(createUniqueId(content)) + .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content); + } + + private ResponseEntity serviceCapabilitiesFor(InitializrMetadataVersion version) { + return serviceCapabilitiesFor(version, version.getMediaType()); + } + + private ResponseEntity serviceCapabilitiesFor(InitializrMetadataVersion version, MediaType contentType) { + String appUrl = generateAppUrl(); + String content = getJsonMapper(version).write(this.metadataProvider.get(), appUrl); + return ResponseEntity.ok().contentType(contentType).eTag(createUniqueId(content)).varyBy("Accept") + .cacheControl(CacheControl.maxAge(7, TimeUnit.DAYS)).body(content); + } + + private static InitializrMetadataJsonMapper getJsonMapper(InitializrMetadataVersion version) { + switch (version) { + case V2: + return new InitializrMetadataV2JsonMapper(); + default: + return new InitializrMetadataV21JsonMapper(); + } + } + +} diff --git a/initializr-web/src/main/java/io/spring/initializr/web/controller/SpringCliDistributionController.java b/initializr-web/src/main/java/io/spring/initializr/web/controller/SpringCliDistributionController.java new file mode 100644 index 00000000..01014d52 --- /dev/null +++ b/initializr-web/src/main/java/io/spring/initializr/web/controller/SpringCliDistributionController.java @@ -0,0 +1,50 @@ +/* + * 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 io.spring.initializr.metadata.InitializrMetadataProvider; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * {@link Controller} that provides access to the Spring CLI. + * + * @author Stephane Nicoll + */ +@Controller +public class SpringCliDistributionController { + + private final InitializrMetadataProvider metadataProvider; + + public SpringCliDistributionController(InitializrMetadataProvider metadataProvider) { + this.metadataProvider = metadataProvider; + } + + @RequestMapping(path = { "/spring", "/spring.zip" }) + public String spring() { + String url = this.metadataProvider.get().createCliDistributionURl("zip"); + return "redirect:" + url; + } + + @RequestMapping(path = { "/spring.tar.gz", "spring.tgz" }) + public String springTgz() { + String url = this.metadataProvider.get().createCliDistributionURl("tar.gz"); + return "redirect:" + url; + } + +} 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 6765cccc..dcfdba01 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 @@ -19,7 +19,10 @@ package io.spring.initializr.web.autoconfigure; import io.spring.initializr.generator.io.template.TemplateRenderer; import io.spring.initializr.metadata.DependencyMetadataProvider; import io.spring.initializr.metadata.InitializrMetadataProvider; -import io.spring.initializr.web.project.MainController; +import io.spring.initializr.web.controller.CommandLineMetadataController; +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.ProjectGenerationInvoker; import io.spring.initializr.web.project.ProjectRequestToDescriptionConverter; import io.spring.initializr.web.support.DefaultInitializrMetadataUpdateStrategy; @@ -131,7 +134,10 @@ class InitializrAutoConfigurationTests { assertThat(context).hasSingleBean(InitializrWebConfig.class); assertThat(context).hasSingleBean(ProjectGenerationInvoker.class); assertThat(context).hasSingleBean(ProjectRequestToDescriptionConverter.class); - assertThat(context).hasSingleBean(MainController.class); + assertThat(context).hasSingleBean(ProjectGenerationController.class); + assertThat(context).hasSingleBean(ProjectMetadataController.class); + assertThat(context).hasSingleBean(CommandLineMetadataController.class); + assertThat(context).hasSingleBean(SpringCliDistributionController.class); }); } @@ -139,7 +145,10 @@ class InitializrAutoConfigurationTests { void webConfigurationConditionalOnWebApplication() { this.contextRunner.run((context) -> { assertThat(context).doesNotHaveBean(InitializrWebConfig.class); - assertThat(context).doesNotHaveBean(MainController.class); + assertThat(context).doesNotHaveBean(ProjectGenerationController.class); + assertThat(context).doesNotHaveBean(ProjectMetadataController.class); + assertThat(context).doesNotHaveBean(CommandLineMetadataController.class); + assertThat(context).doesNotHaveBean(SpringCliDistributionController.class); }); } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineExampleIntegrationTests.java similarity index 98% rename from initializr-web/src/test/java/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineExampleIntegrationTests.java index 1f0db9f5..53ccf1f5 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/CommandLineExampleIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineExampleIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; import io.spring.initializr.generator.test.buildsystem.maven.MavenBuildAssert; import io.spring.initializr.generator.test.project.ProjectStructure; diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java new file mode 100644 index 00000000..d94af304 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerIntegrationTests.java @@ -0,0 +1,129 @@ +/* + * 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 io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import io.spring.initializr.web.AbstractInitializrIntegrationTests; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link CommandLineMetadataController}. + * + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +public class CommandLineMetadataControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { + + @Test + void curlReceivesTextByDefault() { + ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); + validateCurlHelpContent(response); + } + + @Test + // make sure curl can still receive metadata with json + void curlWithAcceptHeaderJson() { + ResponseEntity response = invokeHome("curl/1.2.4", "application/json"); + validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(response.getBody()); + } + + @Test + void curlWithAcceptHeaderTextPlain() { + ResponseEntity response = invokeHome("curl/1.2.4", "text/plain"); + validateCurlHelpContent(response); + } + + @Test + void httpieReceivesTextByDefault() { + ResponseEntity response = invokeHome("HTTPie/0.8.0", "*/*"); + validateHttpIeHelpContent(response); + } + + @Test + // make sure curl can still receive metadata with json + void httpieWithAcceptHeaderJson() { + ResponseEntity response = invokeHome("HTTPie/0.8.0", "application/json"); + validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(response.getBody()); + } + + @Test + void httpieWithAcceptHeaderTextPlain() { + ResponseEntity response = invokeHome("HTTPie/0.8.0", "text/plain"); + validateHttpIeHelpContent(response); + } + + @Test + void unknownCliWithTextPlain() { + ResponseEntity response = invokeHome(null, "text/plain"); + validateGenericHelpContent(response); + } + + @Test + void springBootCliReceivesJsonByDefault() { + ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "*/*"); + validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(response.getBody()); + } + + @Test + void springBootCliWithAcceptHeaderText() { + ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "text/plain"); + validateSpringBootHelpContent(response); + } + + @Test + void doNotForceSslByDefault() { + ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); + String body = response.getBody(); + assertThat(body).as("Must not force https").doesNotContain("https://"); + } + + private void validateCurlHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + assertThat(response.getBody()).contains("Spring Initializr", "Examples:", "curl"); + } + + private void validateHttpIeHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + assertThat(response.getBody()).contains("Spring Initializr", "Examples:", "http").doesNotContain("curl"); + } + + private void validateGenericHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + assertThat(response.getBody()).contains("Spring Initializr").doesNotContain("Examples:", "curl"); + } + + private void validateSpringBootHelpContent(ResponseEntity response) { + validateContentType(response, MediaType.TEXT_PLAIN); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + assertThat(response.getBody()).contains("Service capabilities", "Supported dependencies") + .doesNotContain("Examples:", "curl"); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerSslIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerSslIntegrationTests.java new file mode 100644 index 00000000..9131898b --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/CommandLineMetadataControllerSslIntegrationTests.java @@ -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 + * + * 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 io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import org.junit.jupiter.api.Test; + +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link CommandLineMetadataController} with {@code forceSsl} + * enabled. + * + * @author Stephane Nicoll + */ +@ActiveProfiles({ "test-default", "test-ssl" }) +public class CommandLineMetadataControllerSslIntegrationTests extends AbstractInitializrControllerIntegrationTests { + + @Test + void forceSsl() { + ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); + String body = response.getBody(); + assertThat(body).as("Must force https").contains("https://start.spring.io/"); + assertThat(body).as("Must force https").doesNotContain("http://"); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerArchiveIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerArchiveIntegrationTests.java similarity index 81% rename from initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerArchiveIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerArchiveIntegrationTests.java index fddcea72..aee39762 100644 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerArchiveIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerArchiveIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; import io.spring.initializr.generator.test.project.ProjectStructure; import io.spring.initializr.web.AbstractFullStackInitializrIntegrationTests; @@ -23,10 +23,12 @@ import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; /** + * Integration tests for {@link ProjectGenerationController} on a real http server. + * * @author HaiTao Zhang */ @ActiveProfiles("test-default") -public class MainControllerArchiveIntegrationTests extends AbstractFullStackInitializrIntegrationTests { +public class ProjectGenerationControllerArchiveIntegrationTests extends AbstractFullStackInitializrIntegrationTests { @Test void baseDirectorySeparatedBySpace() { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomDefaultsIntegrationTests.java similarity index 84% rename from initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomDefaultsIntegrationTests.java index c9282b5c..4746fc6d 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDefaultsIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomDefaultsIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; import io.spring.initializr.generator.test.buildsystem.maven.MavenBuildAssert; import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; @@ -23,10 +23,12 @@ import org.junit.jupiter.api.Test; import org.springframework.test.context.ActiveProfiles; /** + * Integration tests for {@link ProjectGenerationController} that uses custom defaults. + * * @author Stephane Nicoll */ @ActiveProfiles({ "test-default", "test-custom-defaults" }) -class MainControllerDefaultsIntegrationTests extends AbstractInitializrControllerIntegrationTests { +class ProjectGenerationControllerCustomDefaultsIntegrationTests extends AbstractInitializrControllerIntegrationTests { // see defaults customization diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java similarity index 70% rename from initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java index 7532118c..a9c87234 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerEnvIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerCustomEnvIntegrationTests.java @@ -14,36 +14,24 @@ * limitations under the License. */ -package io.spring.initializr.web.project; - -import java.net.URI; +package io.spring.initializr.web.controller; import io.spring.initializr.generator.test.project.ProjectStructure; import io.spring.initializr.metadata.Dependency; import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; import org.junit.jupiter.api.Test; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; import static org.assertj.core.api.Assertions.assertThat; /** - * Integration tests with custom environment. + * Integration tests for {@link ProjectGenerationController} with custom environment. * * @author Stephane Nicoll */ @ActiveProfiles({ "test-default", "test-custom-env" }) -class MainControllerEnvIntegrationTests extends AbstractInitializrControllerIntegrationTests { - - @Test - void downloadCliWithCustomRepository() throws Exception { - ResponseEntity entity = getRestTemplate().getForEntity(createUrl("/spring"), String.class); - assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); - String expected = "https://repo.spring.io/lib-release/org/springframework/boot/spring-boot-cli/2.1.4.RELEASE/spring-boot-cli-2.1.4.RELEASE-bin.zip"; - assertThat(entity.getHeaders().getLocation()).isEqualTo(new URI(expected)); - } +class ProjectGenerationControllerCustomEnvIntegrationTests extends AbstractInitializrControllerIntegrationTests { @Test void generateProjectWithInvalidName() { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java similarity index 50% rename from initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java index 954784b1..2451f566 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectGenerationControllerIntegrationTests.java @@ -14,23 +14,15 @@ * limitations under the License. */ -package io.spring.initializr.web.project; - -import java.net.URI; -import java.net.URISyntaxException; +package io.spring.initializr.web.controller; import io.spring.initializr.generator.test.project.ProjectStructure; import io.spring.initializr.metadata.Dependency; import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; -import io.spring.initializr.web.AbstractInitializrIntegrationTests; -import io.spring.initializr.web.mapper.InitializrMetadataVersion; import org.json.JSONException; import org.json.JSONObject; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONCompareMode; -import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -41,10 +33,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; /** + * Integration tests for {@link ProjectGenerationController}. + * * @author Stephane Nicoll */ @ActiveProfiles("test-default") -class MainControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { +class ProjectGenerationControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { @Test void simpleZipProject() { @@ -150,206 +144,6 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra assertHasWebResources(project); } - @Test - void downloadCli() throws Exception { - assertSpringCliRedirect("/spring", "zip"); - } - - @Test - void downloadCliAsZip() throws Exception { - assertSpringCliRedirect("/spring.zip", "zip"); - } - - @Test - void downloadCliAsTarGz() throws Exception { - assertSpringCliRedirect("/spring.tar.gz", "tar.gz"); - } - - @Test - void downloadCliAsTgz() throws Exception { - assertSpringCliRedirect("/spring.tgz", "tar.gz"); - } - - private void assertSpringCliRedirect(String context, String extension) throws URISyntaxException { - ResponseEntity entity = getRestTemplate().getForEntity(createUrl(context), ResponseEntity.class); - assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); - String expected = "https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.1.4.RELEASE/spring-boot-cli-2.1.4.RELEASE-bin." - + extension; - assertThat(entity.getHeaders().getLocation()).isEqualTo(new URI(expected)); - } - - @Test - void metadataWithNoAcceptHeader() { - // rest template sets application/json by default - ResponseEntity response = invokeHome(null, "*/*"); - validateCurrentMetadata(response); - } - - @Test - @Disabled("Need a comparator that does not care about the number of elements in an array") - void currentMetadataCompatibleWithV2() { - ResponseEntity response = invokeHome(null, "*/*"); - validateMetadata(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE, "2.0.0", - JSONCompareMode.LENIENT); - } - - @Test - void metadataWithV2AcceptHeader() { - ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2+json"); - validateMetadata(response, InitializrMetadataVersion.V2.getMediaType(), "2.0.0", JSONCompareMode.STRICT); - } - - @Test - void metadataWithCurrentAcceptHeader() { - getRequests().setFields("_links.maven-project", "dependencies.values[0]", "type.values[0]", - "javaVersion.values[0]", "packaging.values[0]", "bootVersion.values[0]", "language.values[0]"); - ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json"); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); - } - - @Test - void metadataWithSeveralAcceptHeader() { - ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json", - "application/vnd.initializr.v2+json"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); - } - - @Test - void metadataWithHalAcceptHeader() { - ResponseEntity response = invokeHome(null, "application/hal+json"); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - validateContentType(response, MainController.HAL_JSON_CONTENT_TYPE); - validateCurrentMetadata(response.getBody()); - } - - @Test - void metadataWithUnknownAcceptHeader() { - try { - invokeHome(null, "application/vnd.initializr.v5.4+json"); - } - catch (HttpClientErrorException ex) { - assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_ACCEPTABLE); - } - } - - @Test - void curlReceivesTextByDefault() { - ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); - validateCurlHelpContent(response); - } - - @Test - void curlCanStillDownloadZipArchive() { - ResponseEntity response = execute("/starter.zip", byte[].class, "curl/1.2.4", "*/*"); - assertDefaultProject(projectFromArchive(response.getBody())); - } - - @Test - void curlCanStillDownloadTgzArchive() { - ResponseEntity response = execute("/starter.tgz", byte[].class, "curl/1.2.4", "*/*"); - assertDefaultProject(tgzProjectAssert(response.getBody())); - } - - @Test - // make sure curl can still receive metadata with json - void curlWithAcceptHeaderJson() { - ResponseEntity response = invokeHome("curl/1.2.4", "application/json"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); - } - - @Test - void curlWithAcceptHeaderTextPlain() { - ResponseEntity response = invokeHome("curl/1.2.4", "text/plain"); - validateCurlHelpContent(response); - } - - @Test - void unknownAgentReceivesJsonByDefault() { - ResponseEntity response = invokeHome("foo/1.0", "*/*"); - validateCurrentMetadata(response); - } - - @Test - void httpieReceivesTextByDefault() { - ResponseEntity response = invokeHome("HTTPie/0.8.0", "*/*"); - validateHttpIeHelpContent(response); - } - - @Test - // make sure curl can still receive metadata with json - void httpieWithAcceptHeaderJson() { - ResponseEntity response = invokeHome("HTTPie/0.8.0", "application/json"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); - } - - @Test - void httpieWithAcceptHeaderTextPlain() { - ResponseEntity response = invokeHome("HTTPie/0.8.0", "text/plain"); - validateHttpIeHelpContent(response); - } - - @Test - void unknownCliWithTextPlain() { - ResponseEntity response = invokeHome(null, "text/plain"); - validateGenericHelpContent(response); - } - - @Test - void springBootCliReceivesJsonByDefault() { - ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "*/*"); - validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); - validateCurrentMetadata(response.getBody()); - } - - @Test - void springBootCliWithAcceptHeaderText() { - ResponseEntity response = invokeHome("SpringBootCli/1.2.0", "text/plain"); - validateSpringBootHelpContent(response); - } - - @Test - // Test that the current output is exactly what we expect - void validateCurrentProjectMetadata() { - validateCurrentMetadata(getMetadataJson()); - } - - @Test - void doNotForceSslByDefault() { - ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); - String body = response.getBody(); - assertThat(body).as("Must not force https").doesNotContain("https://"); - } - - private void validateCurlHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - assertThat(response.getBody()).contains("Spring Initializr", "Examples:", "curl"); - } - - private void validateHttpIeHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - assertThat(response.getBody()).contains("Spring Initializr", "Examples:", "http").doesNotContain("curl"); - } - - private void validateGenericHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - assertThat(response.getBody()).contains("Spring Initializr").doesNotContain("Examples:", "curl"); - } - - private void validateSpringBootHelpContent(ResponseEntity response) { - validateContentType(response, MediaType.TEXT_PLAIN); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - assertThat(response.getBody()).contains("Service capabilities", "Supported dependencies") - .doesNotContain("Examples:", "curl"); - } - @Test void missingDependencyProperException() { try { @@ -375,12 +169,6 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra } } - @Test - void homeIsJson() { - String body = invokeHome(null, (String[]) null).getBody(); - assertThat(body).contains("\"dependencies\""); - } - @Test void webIsAddedPom() { String body = getRestTemplate().getForObject(createUrl("/pom.xml?packaging=war"), String.class); @@ -403,18 +191,15 @@ class MainControllerIntegrationTests extends AbstractInitializrControllerIntegra } @Test - void installer() { - ResponseEntity response = getRestTemplate().getForEntity(createUrl("install.sh"), String.class); - assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(response.getBody()).isNotNull(); + void curlCanStillDownloadZipArchive() { + ResponseEntity response = execute("/starter.zip", byte[].class, "curl/1.2.4", "*/*"); + assertDefaultProject(projectFromArchive(response.getBody())); } - private String getMetadataJson() { - return getMetadataJson(null); - } - - private String getMetadataJson(String userAgentHeader, String... acceptHeaders) { - return invokeHome(userAgentHeader, acceptHeaders).getBody(); + @Test + void curlCanStillDownloadTgzArchive() { + ResponseEntity response = execute("/starter.tgz", byte[].class, "curl/1.2.4", "*/*"); + assertDefaultProject(tgzProjectAssert(response.getBody())); } private static void assertStandardErrorBody(String body, String message) { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerServiceMetadataIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java similarity index 72% rename from initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerServiceMetadataIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java index bb391fcf..b01a53be 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerServiceMetadataIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerCustomDefaultsIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; import io.spring.initializr.metadata.InitializrMetadata; import io.spring.initializr.metadata.InitializrMetadataBuilder; @@ -28,6 +28,7 @@ import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.UrlResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -37,10 +38,12 @@ import org.springframework.web.client.HttpClientErrorException; import static org.assertj.core.api.Assertions.assertThat; /** + * Integration tests for {@link ProjectMetadataController} on a real http server. + * * @author Stephane Nicoll */ @ActiveProfiles("test-default") -class MainControllerServiceMetadataIntegrationTests extends AbstractFullStackInitializrIntegrationTests { +class ProjectMetadataControllerCustomDefaultsIntegrationTests extends AbstractFullStackInitializrIntegrationTests { @Autowired private InitializrMetadataProvider metadataProvider; @@ -84,4 +87,26 @@ class MainControllerServiceMetadataIntegrationTests extends AbstractFullStackIni validateCurrentMetadata(response); } + @Test + void noBootVersion() throws JSONException { + ResponseEntity response = execute("/dependencies", String.class, null, "application/json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); + validateDependenciesOutput("2.1.4", response.getBody()); + } + + @Test + void filteredDependencies() throws JSONException { + ResponseEntity response = execute("/dependencies?bootVersion=2.2.1.RELEASE", String.class, null, + "application/json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); + validateDependenciesOutput("2.2.1", response.getBody()); + } + + protected void validateDependenciesOutput(String version, String actual) throws JSONException { + JSONObject expected = readJsonFrom("metadata/dependencies/test-dependencies-" + version + ".json"); + JSONAssert.assertEquals(expected, new JSONObject(actual), JSONCompareMode.STRICT); + } + } diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java new file mode 100644 index 00000000..2b6d5ea2 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerIntegrationTests.java @@ -0,0 +1,125 @@ +/* + * 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 io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import io.spring.initializr.web.AbstractInitializrIntegrationTests; +import io.spring.initializr.web.mapper.InitializrMetadataVersion; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.skyscreamer.jsonassert.JSONCompareMode; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.client.HttpClientErrorException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ProjectMetadataController}. + * + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +public class ProjectMetadataControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { + + @Test + void metadataWithNoAcceptHeader() { + // rest template sets application/json by default + ResponseEntity response = invokeHome(null, "*/*"); + validateCurrentMetadata(response); + } + + @Test + @Disabled("Need a comparator that does not care about the number of elements in an array") + void currentMetadataCompatibleWithV2() { + ResponseEntity response = invokeHome(null, "*/*"); + validateMetadata(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE, "2.0.0", + JSONCompareMode.LENIENT); + } + + @Test + void metadataWithV2AcceptHeader() { + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2+json"); + validateMetadata(response, InitializrMetadataVersion.V2.getMediaType(), "2.0.0", JSONCompareMode.STRICT); + } + + @Test + void metadataWithCurrentAcceptHeader() { + getRequests().setFields("_links.maven-project", "dependencies.values[0]", "type.values[0]", + "javaVersion.values[0]", "packaging.values[0]", "bootVersion.values[0]", "language.values[0]"); + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(response.getBody()); + } + + @Test + void metadataWithSeveralAcceptHeader() { + ResponseEntity response = invokeHome(null, "application/vnd.initializr.v2.1+json", + "application/vnd.initializr.v2+json"); + validateContentType(response, AbstractInitializrIntegrationTests.CURRENT_METADATA_MEDIA_TYPE); + validateCurrentMetadata(response.getBody()); + } + + @Test + void metadataWithHalAcceptHeader() { + ResponseEntity response = invokeHome(null, "application/hal+json"); + assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); + validateContentType(response, ProjectMetadataController.HAL_JSON_CONTENT_TYPE); + validateCurrentMetadata(response.getBody()); + } + + @Test + void metadataWithUnknownAcceptHeader() { + try { + invokeHome(null, "application/vnd.initializr.v5.4+json"); + } + catch (HttpClientErrorException ex) { + assertThat(ex.getStatusCode()).isEqualTo(HttpStatus.NOT_ACCEPTABLE); + } + } + + @Test + void homeIsJson() { + String body = invokeHome(null, (String[]) null).getBody(); + assertThat(body).contains("\"dependencies\""); + } + + @Test + void unknownAgentReceivesJsonByDefault() { + ResponseEntity response = invokeHome("foo/1.0", "*/*"); + validateCurrentMetadata(response); + } + + @Test + // Test that the current output is exactly what we expect + void validateCurrentProjectMetadata() { + validateCurrentMetadata(getMetadataJson()); + } + + private String getMetadataJson() { + return getMetadataJson(null); + } + + private String getMetadataJson(String userAgentHeader, String... acceptHeaders) { + return invokeHome(userAgentHeader, acceptHeaders).getBody(); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerSslIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerSslIntegrationTests.java similarity index 74% rename from initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerSslIntegrationTests.java rename to initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerSslIntegrationTests.java index ec84d618..2f99c66b 100755 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerSslIntegrationTests.java +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/ProjectMetadataControllerSslIntegrationTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.spring.initializr.web.project; +package io.spring.initializr.web.controller; import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; import io.spring.initializr.web.mapper.InitializrMetadataVersion; @@ -24,23 +24,13 @@ import org.skyscreamer.jsonassert.JSONCompareMode; import org.springframework.http.ResponseEntity; import org.springframework.test.context.ActiveProfiles; -import static org.assertj.core.api.Assertions.assertThat; - /** - * Integration tests with {@code forceSsl} enabled. + * Integration tests for {@link ProjectMetadataController} with {@code forceSsl} enabled. * * @author Stephane Nicoll */ @ActiveProfiles({ "test-default", "test-ssl" }) -class MainControllerSslIntegrationTests extends AbstractInitializrControllerIntegrationTests { - - @Test - void forceSsl() { - ResponseEntity response = invokeHome("curl/1.2.4", "*/*"); - String body = response.getBody(); - assertThat(body).as("Must force https").contains("https://start.spring.io/"); - assertThat(body).as("Must force https").doesNotContain("http://"); - } +class ProjectMetadataControllerSslIntegrationTests extends AbstractInitializrControllerIntegrationTests { @Test void forceSslInMetadata() { diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/SpringCliDistributionControllerCustomEnvsIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/SpringCliDistributionControllerCustomEnvsIntegrationTests.java new file mode 100644 index 00000000..252ab4e7 --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/SpringCliDistributionControllerCustomEnvsIntegrationTests.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.net.URI; + +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link SpringCliDistributionController} with custom defaults. + * + * @author Stephane Nicoll + */ +@ActiveProfiles({ "test-default", "test-custom-env" }) +public class SpringCliDistributionControllerCustomEnvsIntegrationTests + extends AbstractInitializrControllerIntegrationTests { + + @Test + void downloadCliWithCustomRepository() throws Exception { + ResponseEntity entity = getRestTemplate().getForEntity(createUrl("/spring"), String.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + String expected = "https://repo.spring.io/lib-release/org/springframework/boot/spring-boot-cli/2.1.4.RELEASE/spring-boot-cli-2.1.4.RELEASE-bin.zip"; + assertThat(entity.getHeaders().getLocation()).isEqualTo(new URI(expected)); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/controller/SpringCliDistributionControllerIntegrationTests.java b/initializr-web/src/test/java/io/spring/initializr/web/controller/SpringCliDistributionControllerIntegrationTests.java new file mode 100644 index 00000000..083115cb --- /dev/null +++ b/initializr-web/src/test/java/io/spring/initializr/web/controller/SpringCliDistributionControllerIntegrationTests.java @@ -0,0 +1,74 @@ +/* + * 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.net.URI; +import java.net.URISyntaxException; + +import io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ActiveProfiles; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link SpringCliDistributionController}. + * + * @author Stephane Nicoll + */ +@ActiveProfiles("test-default") +public class SpringCliDistributionControllerIntegrationTests extends AbstractInitializrControllerIntegrationTests { + + @Test + void downloadCli() throws Exception { + assertSpringCliRedirect("/spring", "zip"); + } + + @Test + void downloadCliAsZip() throws Exception { + assertSpringCliRedirect("/spring.zip", "zip"); + } + + @Test + void downloadCliAsTarGz() throws Exception { + assertSpringCliRedirect("/spring.tar.gz", "tar.gz"); + } + + @Test + void downloadCliAsTgz() throws Exception { + assertSpringCliRedirect("/spring.tgz", "tar.gz"); + } + + @Test + void installer() { + ResponseEntity response = getRestTemplate().getForEntity(createUrl("install.sh"), String.class); + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).isNotNull(); + } + + private void assertSpringCliRedirect(String context, String extension) throws URISyntaxException { + ResponseEntity entity = getRestTemplate().getForEntity(createUrl(context), ResponseEntity.class); + assertThat(entity.getStatusCode()).isEqualTo(HttpStatus.FOUND); + String expected = "https://repo.spring.io/release/org/springframework/boot/spring-boot-cli/2.1.4.RELEASE/spring-boot-cli-2.1.4.RELEASE-bin." + + extension; + assertThat(entity.getHeaders().getLocation()).isEqualTo(new URI(expected)); + } + +} diff --git a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDependenciesTests.java b/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDependenciesTests.java deleted file mode 100755 index 5c125d44..00000000 --- a/initializr-web/src/test/java/io/spring/initializr/web/project/MainControllerDependenciesTests.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 io.spring.initializr.web.AbstractInitializrControllerIntegrationTests; -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.jupiter.api.Test; -import org.skyscreamer.jsonassert.JSONAssert; -import org.skyscreamer.jsonassert.JSONCompareMode; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.ActiveProfiles; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * @author Stephane Nicoll - */ -@ActiveProfiles("test-default") -class MainControllerDependenciesTests extends AbstractInitializrControllerIntegrationTests { - - @Test - void noBootVersion() throws JSONException { - ResponseEntity response = execute("/dependencies", String.class, null, "application/json"); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); - validateDependenciesOutput("2.1.4", response.getBody()); - } - - @Test - void filteredDependencies() throws JSONException { - ResponseEntity response = execute("/dependencies?bootVersion=2.2.1.RELEASE", String.class, null, - "application/json"); - assertThat(response.getHeaders().getFirst(HttpHeaders.ETAG)).isNotNull(); - validateContentType(response, CURRENT_METADATA_MEDIA_TYPE); - validateDependenciesOutput("2.2.1", response.getBody()); - } - - protected void validateDependenciesOutput(String version, String actual) throws JSONException { - JSONObject expected = readJsonFrom("metadata/dependencies/test-dependencies-" + version + ".json"); - JSONAssert.assertEquals(expected, new JSONObject(actual), JSONCompareMode.STRICT); - } - -}